Skip to main content

Module server

Module server 

Source
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 id field (string or number). The server echoes the same id in its response.
  • Notifications: a JSON-RPC message without an id field 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 initialize and 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): initialize is 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_MS environment variable. Default: 30_000 ms (30 seconds).