Adds subagents to the experimental [ACP v2](https://agentclientprotocol.com/protocol/v2/overview) surface. With them, it covers v2's baseline session model, so this is the `feat:` that announces v2 support. The README gains a section on how to enable it and what it serves. Based on #1324, a v1 fix that this PR's golden scenarios exposed. Merge #1324 first; this PR then retargets to `main`. ## Experimental ACP v2 Started with `CLAUDE_AGENT_ACP_EXPERIMENTAL_V2=1`, the adapter serves the draft ACP v2 to a client that asks for protocol version 2 or later in `initialize`, and v1 to every other client, as before. - **Served on v2:** - the session baseline (`session/new`, `list`, `resume` with replay, `close`, `prompt`, `cancel`) - `session/delete`, `session/fork`, and additional directories - config options (the mode is one), plans, notices, compaction - display terminals for commands - permission requests, elicitation, providers, auth - and now subagents - **v1 only for now:** steering and the AIR extensions. The draft can still change, and the surface changes with it. ## What changes The [subagents RFD](https://github.com/agentclientprotocol/agent-client-protocol/blob/main/docs/rfds/subagents.mdx) makes subagent updates part of v2's baseline: "support is assumed rather than negotiated". - **Every v2 client gets subagents.** The v2 bridge declares `subagents` in the v1 request it makes for a v2 client (`v1InitializeRequest`), so the agent reports each Agent or Task subagent in the RFD form from #1320–#1322. - **The updates pass through:** `subagent_update`, `session_message` and `session_message_chunk` have the same shape in v1 and v2. - **The child's state goes to its own stream too.** v2 reports a child's work state on the child's own stream, mirrored on the parent. So a `subagent_update` with a `state` also becomes the child's `state_update`, sent right after the parent's copy (`v2ChildStateUpdate`). The announcement thus comes first, and both copies of a state come before any later one, as the RFD requires. This covers `requires_action`, and a failed child's `idle`/`error` with its JSON-RPC `error`. - **A state the RFD doesn't define throws,** as an update kind the surface doesn't translate already does. The SDK types the open members of v1's and v2's `StateUpdate` apart, so the bridge checks each state against the RFD's before passing it on. - **Replay doesn't flatten a child's inline text onto the root.** On replay, a subagent's text goes to the root only for a client that gets it there live: one without native subagent sessions that asked for the flattened transcript. Before, an RFD client (v1, and now v2) got the inline subagent text of a transcript replayed on the root, though the RFD form doesn't replay children yet. Current Claude Code keeps subagent messages in `subagents/`, not inline, so only the `session-load-replay` scenario's inline message showed it. AIR's replay is unchanged. ## Live run Against real Claude Code (Sonnet), with a v2 client that checks every `session/update` and permission request against the SDK's v2 schema, there were no schema violations: - **Launch:** - the child is announced on the root, then reports its own `running`, then gets its prompt as a `session_message` from the root - its tool calls arrive on its session, each with a title on its first report - the Agent call's 35-second heartbeat stays on the root - **Permission:** - `requires_action` appears on the child's stream and on the parent's copy, and on the root, whose turn waits on the child - the request goes to the child's session - then the child is back to `running` - **SendMessage resume:** - the same child session runs again, with the root as sender - the root turn goes idle while the resumed child keeps running, and the child reports `idle`/`end_turn` later, which v2 allows for background work - **Cancel:** the child is `idle`/`cancelled` on both streams, then the root is `idle`/`cancelled`. - A tool call still running at the cancel never gets a final status. That's a known bug on `main` for every client, fixed in a separate PR. ## Tests - **End to end on v2:** an Agent call whose child fails with an SDK error. Traffic arrives in order on both streams, and the child's `idle` carries `error: { code: -32603, message }`. - **Unit:** - the pass-through of the three updates - the child's `state_update`, and no copy for an association update without a state - the throw for a state and a stop reason the RFD doesn't define - **v2 golden files:** - the six subagent scenarios now show the RFD form: announcements, prompts, child sessions, both copies of each state, and permission requests on the child session - `session-load-replay` is unchanged from `main` - the scenarios also check every message against the v2 schema, and that each tool call's first report has a title (that check found #1324) - **Mutation checks:** each of these fails tests: - not declaring `subagents` - dropping the child's copy - sending it before the parent's - dropping the state check - dropping the replay rule - **v1 golden files:** unchanged. - **Checks:** `npm run check`, build and all 2882 tests pass. CI runs only on PRs to `main`, so I ran `ci.yml` on this branch by hand. ## Release Titled `feat:`, so its title becomes the release note that announces v2.
6.0 KiB
ACP adapter for the Claude Agent SDK
Use Claude Agent SDK from ACP-compatible clients!
This tool implements an ACP agent by using the official Claude Agent SDK, supporting:
- Context @-mentions
- Images
- Tool calls (with permission requests)
- Compact file changes through the negotiated AIR diff patch extension
- Following
- Edit review
- TODO lists
- Nested subagent transcripts
- Interactive (and background) terminals
- Custom Slash commands
- Client MCP servers
/mcpin the chat: the MCP server status as a list. The adapter runs/mcp reconnect,/mcp enable, and/mcp disablethrough the SDK control API, because Claude Code refuses them in SDK mode. A reconnect of an ACP server that needs authentication starts MCP OAuth through URL elicitation- Session-scoped long-running goals for AIR through the goal extension under
_meta.jetbrains.air.goal - Structured errors, recovery, and warnings through the opt-in session failure extension
- Concrete model and effort defaults through the opt-in recommended config value extension
- Tool permission presentation, editable choices, and durable effects through the permission extension
- One fact per field in every tool call report, as the ACP tool call contract defines
- All AIR extensions, capabilities, and
_metakeys: AIR extensions
Learn more about the Agent Client Protocol.
To try changes that have landed on main but are not released yet, install from the
preview channel — every push to main publishes one. See
docs/RELEASES.md.
npm install @agentclientprotocol/claude-agent-acp@preview
Subagent sessions
A client that declares clientCapabilities.subagents, other than JetBrains AIR, gets each Agent or
Task subagent as its own child session, following the subagents RFD:
subagent_updateon the parent announces the child before any of its traffic, with the parent'stitleanddescriptionfor it, and reports its work state:running,requires_actionwhile one of its requests is open, andidlewhen its work ends, withend_turnorcancelledwhen the SDK gives the reason. A failure isidlewith theerrorstop reason and the SDK's error text as itserror. The child keeps its session for every delegation to it.- The prompt of the Agent or Task call that launches the child, and the message of each
SendMessage call that resumes it, is a
session_messagein the child's transcript. The tool call stays a tool call of the session that made it. session/cancelon the parent interrupts Claude Code, which stops the foreground children that the turn waits on and, currently, background children too. Each stopped child reportsidlewithcancelledfrom Claude Code's own events, and the open requests of the foreground ones are withdrawn. The adapter ends no child at a cancel itself, so a child that keeps running keeps its open requests. A child that still runs when Claude Code exits isidlewith no stop reason.- A child whose parent is not yet known when it ends is not exposed, since the RFD forbids guessing its parent.
- A client cannot prompt, cancel, or otherwise change a child yet, and a replay does not restore children yet.
An ACP v2 client gets the same without declaring anything, since subagents
are part of v2's baseline. Each child also reports its work state as its own state_update, right
after the parent's copy.
JetBrains AIR keeps the earlier draft of the RFD (subagent_spawned, subagent_state_update),
which it enables with nativeSubagentSessions in _meta.jetbrains.air.capabilities. See
AIR extensions.
Without either signal, Agent and Task stay ordinary tool calls and child interactions stay on the
root session. Clients that use the historical _meta["subagent-transcript"] capability or the
forwardSubagentText session option retain the flattened child transcript.
Experimental ACP v2
Started with CLAUDE_AGENT_ACP_EXPERIMENTAL_V2=1, the adapter also serves the
draft ACP v2: a client that asks for
protocol version 2 or later in initialize gets v2, and every other client gets v1, as before.
The draft can still change, and this surface changes with it.
On v2 the adapter serves the session baseline (session/new, session/list, session/resume
with replay, session/close, session/prompt, session/cancel), session/delete,
session/fork, additional directories, config options (the mode is one), plans, notices,
compaction, display terminals for commands, permission requests, elicitation, providers, auth, and
subagents. Steering and the AIR extensions are v1 only for now.
Contribution Policy
This project does not require a Contributor License Agreement (CLA). Instead, contributions are accepted under the following terms:
By contributing to this project, you agree that your contributions will be licensed under the Apache License, Version 2.0. You affirm that you have the legal right to submit your work, that you are not including code you do not have rights to, and that you understand contributions are made without requiring a Contributor License Agreement (CLA).