Expand description
ACP connection adapter over the agent-client-protocol crate.
The crate frames JSON-RPC over stdio itself (newline-delimited JSON) and drives a
connection through a closure-based API: Client.builder()...connect_with(transport, async |cx| { ... }). That closure owns the connection for as long as it runs — once it
returns, the whole connection (including its background dispatch loop) shuts down. That
doesn’t fit the shape Tasks 4/5/7 need: a Connection handle with independent async
methods (initialize, new_session, prompt) callable at any time from any task.
This module bridges the two shapes: Connection::spawn starts a background “driver”
task that runs the crate’s connect_with closure as a command loop for the connection’s
whole lifetime, and the public methods send commands into that loop over a channel and
await the reply.
§Threading
agent-client-protocol builds its connection state on futures::channel::mpsc (not
tokio) and requires spawned work to be Send + 'static (see the crate’s internal
Task::new bound). That makes the whole stack executor-agnostic and Send, so the driver
task runs on a plain tokio::spawn — no dedicated thread or LocalSet is required.
Connection::spawn takes generic tokio AsyncRead/AsyncWrite halves and adapts them to
the futures::io traits the crate’s ByteStreams transport expects via
tokio_util::compat.
§Notifications
session/update notifications are handled once, connection-wide, via a single
on_receive_notification handler registered on the builder (the crate does not scope
notification handlers per-session at this layer). Every notification the agent sends for
the life of the connection is forwarded to the mpsc::UnboundedReceiver<SessionNotification>
returned by Connection::spawn; callers that run multiple sessions on one connection
must filter by SessionNotification::session_id themselves. The channel is unbounded (not
bounded) because on_receive_notification runs inside the crate’s single dispatch loop —
see the comment at the channel construction site in Connection::spawn for why a bounded
channel would risk deadlocking every in-flight request.
Structs§
- Connection
- A handle to a live ACP connection driven by the
agent-client-protocolcrate. - Permission
Request - AU‑4: an agent→client
session/request_permissionrequest routed out to the supervisor, carrying the [Responder] it must answer. The supervisor classifies the tool call (auto-allow reads / review file edits) and callsresponder.respond(...)— possibly much later, after a diff verdict. The [Responder] isSend + 'static(itssend_fnis a boxedFnOnce + Send), so answering off the connection’s dispatch loop is sound. - Permission
Request Payload - Re-exported so the supervisor (AU‑4) can inspect the permission request +
build the response without depending on
agent-client-protocoldirectly. Request for user permission to execute a tool call. - Session
Id - A lattice-local session identifier.
- Session
Notification - Re-exported so callers (Task 6) can match on
session/updatepayloads without depending onagent-client-protocoldirectly. Notification containing a session update from the agent.