Proposal: session/inject for mid-turn queue + steer #1220
Replies: 4 comments
|
Hi Kenneth! This is a great start! I think you've encapsulated the problem nicely here and I think this is generally going in the right direction (or by right i mean, it matches my gut feeling, having not tried to implement it in ACP yet 😄 ) I'm trying to work out some kinks for the plumbing of v2 so we can start experimenting with the new prompt lifecycle and stuff like this, but if you want to open an RFD I'd be happy to put it in the v2 bucket! Also happy to hop on a call to say hi and also talk more if you like |
|
Drafted as PR #1261 ( |
|
Use-case from Kandev (an ACP client running multiple agents) +1 on The One adjacent gap: a turn that's idle but held open only by background work (a subagent, a background shell) currently looks the same as an actively-generating turn. A signal for "foreground idle, only background work left" would let clients stop reporting those sessions as busy (see also Happy to test an implementation against Kandev when it lands. |
|
One implementation detail worth making explicit in the RFD is that acceptance, delivery, and application are three different states. For queue, an accepted response should mean the agent has durably recorded the input, not that the model has seen it. For steer, delivery means the input crossed a declared safe breakpoint; application means the replacement/continuation turn actually incorporated it. I would model this as a small lifecycle keyed by messageId: accepted → delivered → applied, with terminal cancelled/rejected states. A reconnecting client can then reconcile by messageId instead of guessing from state_change or user_message echoes. Two edge cases fall out of that model:
The same distinction has been useful in Better Agent, a local workspace supervising native Claude, Codex, and Gemini sessions: queued operator input is backend-owned and survives UI/backend reconnects, while the UI only reflects acknowledgement and delivery state. The implementation is source-available and free for non-commercial use; commercial use requires separate permission: https://github.com/ofekron/better-agent AI-assistance disclosure: this comment was drafted by Codex under the maintainer’s authorization and reviewed in Better Agent. |
Uh oh!
There was an error while loading. Please reload this page.
TL;DR: Two adjacent inputs — queue (buffer for next turn) and steer (deliver mid-turn at next break-point) — have converged across most public coding-agent UIs, but ACP has no surface for either. Filing here per the RFD process before drafting a PR. Looking for a champion who'd like to co-design this as a follow-on to the v2 prompt lifecycle.
What converged
Enter-steer (opt-in via/experimental) is the most fleshed-out implementation. The Codex codebase has its own pending-steers queue, requeue-on-turn-complete, pause-on-usage-limits, and a separate stop-reason / replay edge-case surface — see PRs #12569, #15235, #22226, #22879 and issues #19975, #22815.ACP today
session/cancel+ a freshsession/promptis the only mid-turn lever, and it discards in-flight context.stopReasonhas no value for steer-interrupted turns.The v2 prompt lifecycle RFD already calls out queueing as future work: "Queueing messages (still an ongoing discussion) would also fit much nicer in this pattern... potentially the agent could decide whether it cancels the current turn and inserts it immediately, or inserts it at the next convenient break point." The substrate this needs — short-lived
session/prompt,user_messageecho notification,state_changerunning/idle/requires_action — is the v2 prompt lifecycle. So this is intended as a v2 follow-on, not a v1 patch.Adjacent RFDs in motion that this composes with: message-id (handle for ack and revocation), multi-client attach #533 (who's allowed to inject), session rewind #1214 (destructive-history sibling), turn-complete signal #644 (queue-flush barrier).
Sketch
One method, two modes:
{ "jsonrpc": "2.0", "id": 42, "method": "session/inject", "params": { "sessionId": "sess_abc", "mode": "steer", "content": [{ "type": "text", "text": "stop using the legacy auth path, see auth_v2.go" }] } }Response carries the
messageIdper the v2 prompt lifecycle pattern. Capability:{ "agentCapabilities": { "session": { "inject": { "modes": ["queue", "steer"] } } } }Agent advertises which modes it supports. Streaming-only agent:
["queue"]. Tool-loop agent that can break safely:["queue", "steer"].Semantics
steer: delivered as the next user message at the earliest safe break-point. Mid-tool-call: complete the call, then deliver. Mid-stream with no tool call in flight: agent-defined — interrupt and re-prompt, or finish the stream and deliver.queue: buffered, delivered as the next user message afterstate_change: idle. FIFO.user_messagenotification at the moment of delivery, so multi-client observers see consistent history andsession/loadreplays in order.Open questions for the discussion
$/cancel_requeston the inject request keyed bymessageId? Steered messages once handed to the LLM are not revocable.session/cancelinteraction: cancel-during-steer ordering — Codex #22815 is a real bug surface if left implicit.session/request_permissioninteraction: while blocked on permission, queue accumulates; steer rejected or queued (agent-defined, capability bit).cancelledfor steer-interrupted LLM calls — no new reason needed.ContentBlockarray, same shape assession/prompt.params.prompt.session/load.Looking for
A core-team champion willing to co-design this as a v2 follow-on. Happy to draft the RFD PR once direction is agreed — or rework if a different framing (e.g. two separate methods, or rolling queue into the v2 prompt lifecycle RFD itself) is preferred.
Process note: I workshopped this draft with a coding agent for structure and prior-art digging — happy to take feedback on framing, or to hop on Zulip / a call if a maintainer wants to talk it through live.
All reactions