Expand description
§Collaborative Cancellation
Cancel is a request (not a demand). The agent drains in-flight tool results
before stopping, and stopReason: 'cancelled' is set on the last assistant message.
See Decision #4c.
§Transport contract
- Framing: newline-delimited JSON. One JSON-RPC message per line; no whitespace padding, no length prefix.
- Request/response matching: each request carries an
idfield (string or number). The server echoes the sameidin its response. - Notifications: a JSON-RPC message without an
idfield is a notification. Notifications are consumed silently — no response is written to stdout. - Batch semantics: when the input line is a JSON array, the server treats it as a batch. Every element is processed independently and the server writes back a JSON array of responses (notifications excluded). An empty batch is an error (-32600).
- Stdout/stderr contract: every byte written to stdout is valid
JSON-RPC (
"jsonrpc":"2.0"present). All logging, diagnostics, and tracing output goes to stderr only.
§Handshake state machine
The server tracks initialization state:
- Uninitialized (startup): only
initializeand method-not-found responses are produced. Known session methods (session/new,session/prompt,session/cancel) return-32002(“Server not initialized”). Truly unknown methods return-32601(“Method not found”) regardless of state per the JSON-RPC 2.0 spec. - Initialized (after successful
initialize):initializeis rejected as a duplicate (-32002). Other methods are dispatched.
§Cooperative Cancel
session/cancel implements a two-phase cooperative abort:
Phase 1 — LLM stream abort. Each provider’s SSE parse loop
(OpenAiProvider::parse_sse_stream, AnthropicProvider::parse_sse_stream)
uses tokio::select! on a tokio_util::sync::CancellationToken alongside
the SSE chunk read. When the token fires, the reqwest::Response is dropped
(closing the TCP connection) and the stream returns Err(Error::Cancelled).
Phase 2 — ACP client RPC abort. Agent→client RPCs (e.g.
session/request_permission) are also guarded by the same CancellationToken
plus a 30-second timeout. If session/cancel arrives while the agent is
blocked waiting for a client response, the token fires and unblocks the RPC
with a cancelled error.
Token lifecycle. session/new materializes a fresh CancellationToken
and stores it on the AcpSession. session/cancel fires that token.
The same token instance is passed to
AgentRuntime::set_interrupt_token
exactly once at the start of each turn. After a turn ends (regardless of
finish reason), a fresh CancellationToken is created for the next
turn — a cancel only affects the currently in-flight turn, never a future one.
Transcript repair. After a cancelled turn, the transcript remains
structurally valid: every assistant message containing tool_calls has a
corresponding tool result message. In-flight tool calls that were cancelled
mid-execution get a synthetic tool_result with content indicating
cancellation, so session/load replays without orphaned tool_calls and
subsequent turns work correctly.
Structs§
- AcpServer
- ACP stdio JSON-RPC server.
Functions§
- client_
rpc_ timeout_ ms - Timeout for all in-flight agent→client fs/* RPCs, in milliseconds.
Override via the
ACP_CLIENT_RPC_TIMEOUT_MSenvironment variable. Default: 30_000 ms (30 seconds).