mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-10-27 12:40:32 +00:00
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.
This commit is contained in:
@@ -58,8 +58,11 @@ Task subagent as its own child session, following the [subagents RFD](https://gi
|
||||
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 client cannot prompt, cancel, or otherwise change a child yet, and a replay does not restore
|
||||
children yet.
|
||||
- A replay restores each child under its live session id, from its own transcript: its prompt and
|
||||
each SendMessage message are `session_message`s, 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](#experimental-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
|
||||
|
||||
Reference in New Issue
Block a user