Concurrency Model · v0.11.7

What happens when 10 calls arrive at once

It depends on where they land. A session's ledger is a single-owner file lock — Mutex<Option<SessionLog>> that a run .take()s — so one run per session at a time. Ten calls to one session, ten sessions in one workspace, and ten different workspaces behave completely differently. Pick a scenario and press play.

running queued rejected idle / evicted

Ingress · 10 calls

Gate

Session lanes

Choose a scenario and press Play.

The three answers

Same code path, three outcomes

What decides the result is the identity the 10 calls resolve to — one ledger, ten ledgers in one core, or ten workspaces in the pool.

A · Same session

10 → one ledger

The first call .take()s the ledger and runs. The other nine find None and get 409 CONFLICT — "run already active." The intended way to add to a live conversation is /steering, which queues input consumed between model steps. Nothing typed while busy is dropped.

1 runs · 9 → 409 (or steer)

B · Many sessions, one workspace

10 ledgers · 1 shared core

Each session owns its own ledger lock, so all ten .take() succeed and run as ten concurrent async tasks over one Core. The ceiling is the per-IP rate limiter: runs_per_min = 10 by default, so the 11th in a 60s window gets 429 with a retry-after.

10 run in parallel

C · Many workspaces (gateway)

CorePool · bounded + idle-evicted

The pool lazily starts a Core per workspace, keyed by (workspace, override). The default workspace is pinned warm; when the pool hits capacity, the oldest idle core is evicted to admit the next. Each is built via Core::new_with_trust — no shared shortcut.

capacity-bounded parallelism
EndpointDefault limitOn excess
POST /sessions/:id/run10 / min / IP429 + retry-after
POST /sessions5 / min / IP429 + retry-after
POST /gateway/inbound30 / min / IP429 + retry-after
other POST20 / min / IP429 + retry-after
second run on a busy session—409 CONFLICT