Problem
In webchat, while a turn is still running, a newly sent message is queued behind
the per-sessionKey serial gate instead of reaching the running agent. The user has
to wait for the current turn to finish before their new input has any effect — even
when that input is meant to redirect the agent right now ("stop, do it this way
instead", "also check X", "no, not that file").
Today's behavior (daemon serial gate, packages/daemon/src/daemon.ts):
dispatchWebchatTurn → admission → the serial gate; if a turn is in flight for the
same sessionKey, the message is appended to serialQueue and dispatched only
after the current turn settles.
- Depth is capped by
MAX_QUEUED_PER_SESSION (10); past that the message is rejected
with reason: 'busy'.
- If the running turn fails, queued messages are not auto-run at all
(TurnFailedError — "queued messages were not auto-run").
So the only way to steer today is cancel + resend, which throws away the partial work
of the running turn.
Requested change
Support mid-turn steering for webchat: a message sent while a turn is running
should be delivered into the running turn as additional user input, not parked in
the queue. This is the behavior users already expect from Claude Code / Codex CLI,
where typing during a turn injects the text into the ongoing turn.
Sketch of what needs to happen:
- Feed the new text to the in-flight ACP turn instead of enqueueing it (agent-side
support permitting — needs a capability check per runtime; Claude and Codex ACP
runtimes may differ, and a runtime without support must fall back to today's queue).
- Keep the serial gate for everything that genuinely must serialize (platform
ingress, hooks, scheduler wakes) — this is specifically about interactive webchat
input on an already-running session.
- Transcript/UI: the steering message should render inline in the running turn, not
as a separate queued turn, and the composer should stop showing the message as
"queued".
- Bound it: cap steering injections per turn, and define what happens to steering
text that arrives after the turn has already ended (fall back to a normal new turn).
Notes / open questions
- Does the current ACP wire contract (
packages/protocol/src/frames/agent.ts,
packages/protocol/src/frames/webchat.ts) already have a slot for mid-turn user
input, or does this need a new frame?
- Interaction with permission prompts: if the turn is parked on a permission request,
steering text should probably not be swallowed by the prompt.
- Fencing (
sessionEpoch / seq / launchId) semantics for injected input.
- Whether
!queue stays as the explicit opt-in for "actually queue this, don't steer".
Problem
In webchat, while a turn is still running, a newly sent message is queued behind
the per-sessionKey serial gate instead of reaching the running agent. The user has
to wait for the current turn to finish before their new input has any effect — even
when that input is meant to redirect the agent right now ("stop, do it this way
instead", "also check X", "no, not that file").
Today's behavior (daemon serial gate,
packages/daemon/src/daemon.ts):dispatchWebchatTurn→ admission → the serial gate; if a turn is in flight for thesame
sessionKey, the message is appended toserialQueueand dispatched onlyafter the current turn settles.
MAX_QUEUED_PER_SESSION(10); past that the message is rejectedwith
reason: 'busy'.(
TurnFailedError— "queued messages were not auto-run").So the only way to steer today is cancel + resend, which throws away the partial work
of the running turn.
Requested change
Support mid-turn steering for webchat: a message sent while a turn is running
should be delivered into the running turn as additional user input, not parked in
the queue. This is the behavior users already expect from Claude Code / Codex CLI,
where typing during a turn injects the text into the ongoing turn.
Sketch of what needs to happen:
support permitting — needs a capability check per runtime; Claude and Codex ACP
runtimes may differ, and a runtime without support must fall back to today's queue).
ingress, hooks, scheduler wakes) — this is specifically about interactive webchat
input on an already-running session.
as a separate queued turn, and the composer should stop showing the message as
"queued".
text that arrives after the turn has already ended (fall back to a normal new turn).
Notes / open questions
packages/protocol/src/frames/agent.ts,packages/protocol/src/frames/webchat.ts) already have a slot for mid-turn userinput, or does this need a new frame?
steering text should probably not be swallowed by the prompt.
sessionEpoch/seq/launchId) semantics for injected input.!queuestays as the explicit opt-in for "actually queue this, don't steer".