Files
claude-agent-acp/README.md
Ben Brandt 21eef57655 feat: replay each subagent under its live session id for subagents RFD clients (#1328)
A replay now restores subagents for a client on the [subagents
RFD](https://github.com/agentclientprotocol/agent-client-protocol/blob/main/docs/rfds/subagents.mdx)
form: a v1 client that declares `subagents` and isn't AIR, and every v2
client. Until now, its replay left children out. **AIR's replay is
unchanged.**

## The rules

The RFD asks that replayed children keep their original session ids and
known parentage, and that announcements come before child traffic. It
forbids guessing a parent or inventing an id, and asks that known gaps
be made apparent. It also requires all selected history before the
response.

- **Ids:** each child replays under the session id it had live, the SDK
agent id. Claude Code's `subagents/agent-<id>.meta.json` names the Agent
or Task call that launched agent `<id>`.
- **Order:**
- The child is announced on the session that made the launch, right
after the launch, with the same labels as live: the `name` and the role
as `title`, and the short `description`.
  - Its own transcript then goes to the child session.
  - A nested child is announced on the child that launched it.
- **Incoming messages:** the child's prompt is a `session_message` with
the launch's id and the parent as sender, as live. A message that a
SendMessage call delivered (`origin.kind: "coordinator"`) is matched to
its call by the call's text. Claude Code frames it with a preface and an
instruction, so the match checks that the delivered text contains the
call's message. It then has the SendMessage's id, text, and sender, as
live; without a match, its transcript id and the delivered text.
- **Harness text:** other user text in a child's transcript, such as
"[Request interrupted by user for tool use]", isn't shown, as the live
child session doesn't show it. Tool results stay.
- **Ends:** every child's end is an `idle` before the response. The end
comes from the first of these that applies:
  1. its last persisted task notification (its task id is the agent id)
  2. `cancelled`, if `meta.json` says the user stopped it
3. its foreground launch's result (a background launch's result is only
its launch)
  4. otherwise, `idle` with no stop reason
- **Gaps:**
- A launch without metadata has no known id, so it isn't announced; its
Agent call stays an ordinary tool call of the parent. A child whose
transcript can't be read replays only its association, as the RFD
allows.
- Either case adds one notice for a client that takes notices: "Some
subagent history is not available".
- A subagent message that the session transcript stores inline is left
out, since the child's history comes from its own transcript. Current
Claude Code doesn't store such messages.
- **v2:** a replayed child's messages are cleared before their first
replayed chunk, as the parent's already are. A v2 client may still hold
them.

## Changes in the code

- **`src/subagent-replay.ts` (new):** `RfdSubagentReplay` holds the
replay's children, deliveries, and ends. The replay loop in
`acp-agent.ts` calls it at the launch, for each child message, and at
the end.
- **`subagentHistory`:** returns each launch's whole metadata record
(name, role, description, background, stopped by the user). AIR and the
RFD form share `readSubagentMessages`.
- **The v2 bridge:** registers a child that a replay announces as
replaying too.

## Evidence

- **Live, v1:** I loaded a real Claude Code session from a live run: a
foreground `scout` resumed once by SendMessage, and a background
`sleeper` that a cancel stopped.
  - Both children replay under their live agent ids, with their labels.
- `scout`'s prompt and its SendMessage delivery have exactly the ids and
senders the live run used (`toolu_…HvPNZm` and `toolu_…cB3K9t`, from the
root).
- `scout` ends `end_turn`, and `sleeper` ends `cancelled`, from
`stoppedByUser`.
- **Live, v2:** the same session over v2 with replay. There were no v2
schema violations, each child's state is mirrored on its own stream, and
all of it comes before the resume response.
- **Tests:**
- v1: a full replay order with a nested child, a matched delivery,
dropped harness text, the ends, and the notice for a launch without
metadata; ends for a stopped child and for one with no known end; inline
subagent messages left out.
  - v2: a child's replayed message is cleared on the child session.
- **Mutation checks:** removing any of the rules fails a test:
  - the v2 child registration
  - the RFD replay
  - the delivery match
  - the inline skip
  - `stoppedByUser`
  - ignoring a background launch's result
  - dropping harness text
  - the notice
- **Golden files:** only the v2 `session-load-replay` changes. It gains
the notice, because its Task launch has no metadata. The AIR golden
files are unchanged.
- **Checks:** `npm run check`, build and all 2886 tests pass.

## Not in this PR

- **A SendMessage after a reload:** a SendMessage to an agent from
before a reload still finds no known child in the live runtime, so the
agent stays unexposed. A follow-up registers the replayed children, and
the children of a resume without replay, in the runtime.

## Release

Titled `feat:`, since a v1 client on the RFD form gets new replay
output. Retitle it if you'd rather not release it.
2026-10-13 18:13:21 +02:00

6.6 KiB

ACP adapter for the Claude Agent SDK

npm

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
  • /mcp in the chat: the MCP server status as a list. The adapter runs /mcp reconnect, /mcp enable, and /mcp disable through 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 _meta keys: 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_update on the parent announces the child before any of its traffic, with the parent's title and description for it, and reports its work state: running, requires_action while one of its requests is open, and idle when its work ends, with end_turn or cancelled when the SDK gives the reason. A failure is idle with the error stop reason and the SDK's error text as its error. 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_message in the child's transcript. The tool call stays a tool call of the session that made it.
  • session/cancel on the parent interrupts Claude Code, which stops the foreground children that the turn waits on and, currently, background children too. Each stopped child reports idle with cancelled from 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 and its open tool calls. A tool call of a stopped child that Claude Code reports no result for, such as the command it was running, ends in the child's session before the cancelled prompt resolves. A child that still runs when Claude Code exits is idle with 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 replay restores each child under its live session id, from its own transcript: its prompt and each SendMessage message are session_messages, and its end is an idle before the response. A launch without Claude Code's metadata file for the child stays an ordinary tool call of the parent, and a notice, for a client that takes notices, says that subagent history is missing.
  • A client cannot prompt, cancel, or otherwise change a child 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).