Ben Brandt df21b3fb56 feat: restore a subagent from before a reload when SendMessage resumes it, for subagents RFD clients (#1332)
After `session/load` or `session/resume`, Claude Code can resume an
agent that ran before the reload: a SendMessage to it runs it again from
its transcript. The adapter did not know that agent, so it was never
exposed, and its activity was dropped. Now it runs in its session from
before, the agent id, for every subagents RFD client: v1 clients that
declare `subagents` and are not AIR, and every v2 client.

## What Claude Code sends

From a live run, after a resume in a new adapter process:

- `task_started` with the agent id, the **SendMessage** call's id as
`tool_use_id`, and the message as `prompt`.
- The resumed run's messages under `parent_tool_use_id` = the agent's
**original Agent launch id**, from before the reload (the `toolUseId` in
`subagents/agent-<id>.meta.json`), not the SendMessage id.
- The SendMessage result with `resumedAgentId`, then `task_updated` /
`task_notification` as usual.

The runtime found no child for the agent id, and a SendMessage call
establishes no parent, so the child waited for a parent that never came
and was withdrawn when it ended.

## What changes

- **The signal:** an agent that the runtime does not know, resumed by a
SendMessage call that it saw. A new launch never counts: its
`task_started` names an Agent or Task call.
- **`subagentLineage`** (`subagent-history.ts`) reads the agent and its
ancestors from the session's history, root first. The metadata names
each launch call but not who made it, so a launch's parent is the
session whose transcript has the call: the root's, or an agent's. That
is the rule the replay (#1328) uses, so live restore and replay agree.
An agent or ancestor the history cannot place gives an empty lineage.
- **The runtime** (`NativeSubagentRuntime.restore`) registers the agent
and its ancestors under their agent ids and their launch ids. Then it
announces, parent before child, each ancestor that the client was not
told about in this connection, as the RFD requires before the agent's
traffic. An ancestor is announced with no work state, since nothing says
that it runs. Then the agent, as `running`. From there the resume goes
through the existing path (#1321): the SendMessage text is the child's
`session_message`, and its end is `idle`.
- **Unchanged:** an agent the history cannot place stays unexposed, as
the RFD forbids guessing a parent.
- **Load and replay:** after `session/load`, the replay has already
announced the agent. The live resume announces it once more, as
`running`, which repeats an association the client has (the RFD allows
it).
- **Refactor:** `subagentLaunchIds` moves from `acp-agent.ts` to
`subagent-history.ts`, so the replay and the lineage find launches the
same way.

## Evidence

**Live, against Claude Code, with every update checked against the
schema** (no violations):

| Run | Result |
|---|---|
| v1, `session/resume`, SendMessage to a root-level agent |
`subagent_update` running under its old id, the message from the root,
its `PING` in its own session, `idle end_turn` |
| v1, `session/load`, same | The replay announces it; the live resume
after the response runs in the same session |
| v1, `session/resume`, root → coordinator → echoer, SendMessage from
the root to echoer | coordinator announced under the root with no state,
then echoer under coordinator as running, its run in its session, its
`idle` on coordinator |
| v2, `session/resume` without replay, same nested session | as above;
echoer also reports `running` and `idle` as its own `state_update`,
coordinator has none |

Before this change, the first run showed no `subagent_update` at all,
and the child's `PING` was dropped.

**Tests:**

- `native-subagents.test.ts`: restore under the agent id, with routing
by the launch id; ancestors first and with no state; an ancestor
announced once for two agents; output that waited is delivered; restore
at the SendMessage result alone; an agent the history cannot place or
read stays unexposed; no history read for a new launch or a known child.
- `subagent-history.test.ts` (real transcripts on disk): a root-level
agent, a nested agent next to a sibling, an agent whose launch no
transcript has, a nested agent whose ancestor no transcript placed, an
agent without metadata.
- `acp-agent.test.ts`: the agent's own runtime restores an agent from a
history on disk, from the stream Claude Code sends.
- **Mutation checks:** removing each of the 12 rules fails at least one
test.

`npm run check`, build and the full suite (2922 tests) pass, with no
golden changes.

## Not covered

- **AIR** is unchanged: this PR changes only the RFD form, so on AIR an
agent from before a reload still stays unexposed.
- **A fork** cannot resume an agent of the original session, since the
SDK's `forkSession` copies no `subagents/` history.
2026-10-13 19:09:07 +02:00
2026-07-21 18:53:56 +02:00
2025-09-02 19:06:32 -03:00
2026-03-26 12:19:06 +01:00

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.
  • After a load or resume, with or without replay, a SendMessage that resumes an agent from before it runs that agent in its session from before. Before the agent's traffic, each of its ancestors that the client was not told about in this connection is announced, parent before child and with no work state, as the RFD requires. An agent whose launch the session's history does not record stays unexposed.
  • 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).

Description
Pull mirror of agentclientprotocol/claude-agent-acp (Apache-2.0): the ACP bridge that lets Buzz drive Claude Code
Readme 107 MiB
Languages
TypeScript 99.2%
JavaScript 0.6%
Shell 0.2%