Repository navigation
feat(acp): surface Claude Code subagent parent on ACPToolCallEvent - #5453
Draft
JoaVirtudes19 wants to merge 1 commit into
Draft
JoaVirtudes19 wants to merge 1 commit into
JoaVirtudes19 wants to merge 1 commit into
Conversation
claude-agent-acp stamps _meta.claudeCode.parentToolUseId on every update a subagent produces. ACPToolCallEvent now carries it as parent_tool_call_id, read on ToolCallStart and ToolCallProgress and kept on the terminal and supersede events, so consumers can tell a subagent's tool calls from the main agent's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
|
📁 PR Artifacts Notice This PR contains a |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
HUMAN:
AGENT:
Why
When an
ACPAgentruns Claude Code, Claude Code can hand work to subagents through its Task/Agent tool. Their tool calls arrive on the same ACP session as the main agent's, so everyACPToolCallEventlooks the same. A consumer cannot tell the main agent's calls from a subagent's, or which subagent made which call, and with subagents running in parallel the event stream is hard to follow.claude-agent-acp already sends the link: it stamps
_meta.claudeCode.parentToolUseId, thetool_call_idof the spawning Task call, on every update a subagent produces (agentclientprotocol/claude-agent-acp#341).ACPAgentdrops_metawhen it builds the event.Summary
ACPToolCallEventgets an optionalparent_tool_call_id: str | None = None. It holds thetool_call_idof the tool call that spawned the subagent making the call. It is None when the ACP server reports no parent, as for the main agent's own calls.field_metaon bothToolCallStartandToolCallProgress, and carries it to the terminal event and to the supersede event emitted by_cancel_inflight_tool_calls.ToolCallStart/ToolCallProgressmocks now setfield_meta = None, because reading an unset field onMagicMock(spec=...)raises. feat(acp): add Gemini CLI as supported ACP server #2619 did the same forcontent.Issue Number
To be filed before this leaves draft. Related: #3907, which asks for the same parent link for the SDK's own TaskToolSet subagents.
How to Test
Unit tests:
1529 passed.
pre-commitpasses on the changed files, andmake test-server-schemapasses.Live, against Claude Code through a LiteLLM proxy, using claude-agent-acp 0.63.0 (the SDK's pinned version):
The main agent runs
echo main-agent, then spawns two subagents in parallel that runecho subagent-aandecho subagent-b. EveryACPToolCallEventonconversation.state.events:Each subagent's calls point at the Task call that spawned it. The main agent's own command and its two Task calls carry no parent, and the main agent's event serializes exactly as before.
Video/Screenshots
N/A: there is no UI change. The live output above is the evidence.
Design Doc
Not added: the change is one optional field, and the before/after shape is in the output above.
Type
Notes
Compatibility.
/events/searchand the events WebSocket serialize withexclude_none=True, so main-agent calls and non-Claude harnesses produce exactly the same payload as before._event_data, plainmodel_dump) and the single and batchGET /eventsendpoints return"parent_tool_call_id": nullon everyACPToolCallEvent. They already do the same forcontent(feat(acp): add Gemini CLI as supported ACP server #2619) andparent_id(feat(sdk): conversation history tree (parent_id, leaf HEAD, fork-from-event, navigate) #3922).Eventisextra="forbid".RemoteConversationdrops them on the WebSocket and fails a REST resync. This is the same risk class as feat(acp): add Gemini CLI as supported ACP server #2619, feat(sdk): conversation history tree (parent_id, leaf HEAD, fork-from-event, navigate) #3922 and feat(skills): add path-triggered rules #3994. Unlike feat(tool): add structured outcome to FinishAction for infeasible tasks #4147, the default is None, so only subagent tool calls carry the key. A separate event kind would not help, since an unknownkindis rejected the same way.Relation to ACP subagent sessions.
ACPAgentdeclares no client capabilities, and agent-client-protocol 0.12.1 has no subagent types. Servers therefore keep subagent activity on the root session, and claude-agent-acp documentsparentToolUseIdon every update for such clients.ACPAgentlater opts into native sessions, child activity becomes per-session and this field simply stays None for those events. Nothing needs to migrate or be removed.tool_call_idvocabulary. It is distinct from the conversation-treeEvent.parent_id.Why a parsed field, not a
_metapassthrough.ACPAgentalready reads harness-specific_meta, for example Gemini's_meta.quotain_extract_token_usage. The parse here is unconditional and type-checked; it does not branch on which harness is running.toolResponseon every update. That would duplicateraw_outputand bypass_mask_tool_call_entry.ToolCallProgressis merged too because claude-agent-acp attaches the parent best-effort. If a call is first announced by the permission flow, the start notification can lack the parent and a latertool_call_updaterestores it. In that case the started event has None and the terminal event carries the parent, which fits the "last event pertool_call_idwins" handling inRemoteConversation.ACPAgentreceives.Docs. No OpenHands/docs page documents
ACPToolCallEventfields today. I'm happy to add a short "Subagent tool calls" note tosdk/guides/agent-acp.mdxin a companion PR.🤖 Generated with Claude Code