Repository navigation
Replies: 3 comments
|
One more data point, since it removes an obvious objection: the ACP protocol itself has no standard way to advertise steering or queueing. In the ACP schema bundled with the app ( So an ACP agent cannot tell a generic client "I can steer a running turn", and a generic client cannot discover it. That is why The practical consequence: the only interoperable behaviours left for an ACP provider are (a) queue the message inside the agent, or (b) reject it with an error that names the callable provider extension. For reference, MiniMax Code does exactly (b) today — it advertises This also suggests a provider-agnostic option for Paseo: instead of reading vendor |
|
Correction to one sentence in my original post, and one additional finding. Correction. I wrote: " The rest of the analysis stands, and this makes it sharper: Additional finding. An agent created as a detached root agent ( This also means the "detach" escape hatch in my original post is not sufficient on its own for an agent created as detached — detaching an existing subagent does stop the reports, but creating one detached does not. |
|
Correction to one sentence in my earlier comment, because it changes which options are technically available. Correction. I wrote that there is "no occurrence of
// node_modules/@agentclientprotocol/sdk/dist/acp.d.ts
export declare class ClientSideConnection implements Agent { // line 212
/**
* Extension method
*
* Allows the Agent to send an arbitrary request that is not part of the ACP spec.
*/
extMethod(method: string, params: Record<string, unknown>): Promise<Record<string, unknown>>; // line 409
extNotification(method: string, params: Record<string, unknown>): Promise<void>; // line 415
}So a generic client can call What still holds from my earlier comment:
Consequence for the options in this discussion: implementing steer for agents that advertise it, via |
Uh oh!
There was an error while loading. Please reload this page.
The problem
Sending anything to an agent session that is currently in a turn is the sharpest edge in the agent-to-agent path. ACP has no standard way to steer a running turn, so the behaviour is decided by a fallback chain, and the current fallback is "cancel the running turn and send the message as a replacement prompt":
packages/server/src/server/agent/agent-manager.ts:3016steerAgentRun()returns{ status: "unavailable" }when the session exposes nosteerActiveTurn.packages/server/src/server/agent/agent-manager.ts:3044steerOrReplaceActiveTurn()then callsreplaceAdmittedForegroundTurn()(:3084).packages/server/src/server/agent/agent-manager.ts:3141replaceAdmittedForegroundTurn()cancels the active turn (cancelAgentRunBefore(..., "replace")) and immediately starts a replacement prompt.packages/server/src/server/agent/providers/acp-agent.ts:2546interrupt()sends ACPsession/cancel. That resolves when the provider acknowledges the cancel — not when the provider's turn has actually ended.Two consequences, both visible to users:
A background message can cancel real work. The finish notification for a subagent is enqueued to the caller with
activeTurnBehavior: "steer"(packages/server/src/server/agent/finish-subscriptions/index.ts:185). With no steering available it takes the replace path, so the parent's running turn is dropped and replaced by the notification.The replacement prompt can fail. If the provider has not released the session yet, the new prompt is rejected. With MiniMax Code 0.7.1 the parent chat shows:
I filed the reproduction with the raw daemon log here: bug: the subagent finish notification cancels the busy parent's turn and then fails with -32603 (ACP provider) #8300
"prompt.steer"currently occurs inpackages/server/srconly inpackages/server/src/server/agent/plugin-provider.ts:1236, so it is a plugin-provider capability today and no ACP provider can steer. Every message to a busy ACP session takes the cancel-and-replace path.What providers actually offer
The providers differ, and the ACP surface does not describe this:
codex-app-server-agent.ts)opencode-agent.ts)Some ACP providers implement both natively, but only as vendor extensions. MiniMax Code 0.7.1 advertises them in
initialize:Neither
"mcode/session"nor"queue/enqueue"occurs anywhere inpackages/server/src, so Paseo never calls them. The provider's own error message ("Use queue send to deliver the message after it") points at a capability the client is not using.Options
A. Wait for the session to be released. After
session/cancel, wait (bounded) until the provider actually reports the turn as ended, then send the replacement prompt. Fixes the race, keeps today's replace semantics. Downside: it still cancels work, and the wait has to be bounded carefully — on timeout the prompt must not be sent blindly.B. Queue inside Paseo. When the provider cannot steer, hold the message in a Paseo-side pending queue and deliver it when the target's turn ends. No turn is cancelled, nothing is lost, and it works for every provider without provider-specific code. Downside: the notification is delayed until the target's current turn finishes, which for a long turn can be a long time — so the UI needs to show that a message is pending rather than silently dropping it.
C. Use the provider's queue extension. When
initializeadvertises a queue method in_meta, call it instead ofsession/prompt. Best fidelity for the provider, but provider-specific, adds a second source of queue state, and does nothing for the other ACP providers.What I would propose as a first step
Keep the change narrow and provider-agnostic:
activeTurnBehavior: "steer"a user's message would use.If the provider turns out to expose a queue (option C), that can be used as the delivery mechanism instead of the Paseo-side queue, but it should not be required for the narrow fix.
A regression test for the narrow fix would need to assert that delivering a finish notification to a busy parent does not cancel the parent's turn and does not start a second prompt, and that the notification is delivered exactly once after the parent's turn ends. On the current code that test fails on the cancel / second prompt.
Questions
initialize._metaat all, or is that better handled by provider-specific adapters (e.g. aminimax-codeprovider that mapsmcode/session/queue/*onto a shared queue interface)?Workarounds available today
For anyone hitting this while it is being worked on:
create_agent/send_agent_promptwithnotifyOnFinish: false— no parent callback is registered, so nothing is delivered into the parent's session.paseo agent detach <id>— makes the child independent (ParentAgentIdbecomesnull), so it has no parent to notify.paseo send <id>) has no parent callback either.All reactions