Describe the bug
When copilot --acp runs multiple synchronous subagents in parallel, subagent text deltas are forwarded into the parent ACP session as ordinary agent_message_chunk updates, but their source identity is removed.
Copilot's internal/SDK events already distinguish these messages with agentId and data.parentToolCallId. The outer task tool calls also have stable, distinct toolCallId values. However, the ACP notifications for the corresponding text contain only:
{
"method": "session/update",
"params": {
"sessionId": "<parent-session>",
"update": {
"sessionUpdate": "agent_message_chunk",
"content": {
"type": "text",
"text": "..."
}
}
}
}
There is no _meta, parentToolCallId, agentId, message ID, or child session ID on these updates. Consequently, an ACP client cannot tell whether a chunk came from the primary agent, subagent A, or subagent B. When parallel subagents stream concurrently, their text can be interleaved and rendered as a corrupted primary-agent response.
This appears to be an ACP adapter regression/gap rather than a core event-model limitation. In particular, #2265 was closed after newer SDK versions added parentToolCallId to assistant.message and assistant.message_delta; that field is available before the ACP mapping but is not preserved in the ACP output.
Affected version
GitHub Copilot CLI 1.0.71-0
Steps to reproduce the behavior
-
Start Copilot in ACP stdio mode:
-
From an ACP client, call initialize, then session/new.
-
Send a prompt that requires exactly two synchronous Explore subagents to run in parallel, with distinguishable output. For example:
Launch exactly two Explore subagents concurrently.
Subagent A must return a long report beginning with ALPHA_SUBAGENT_START
and ending with ALPHA_SUBAGENT_END.
Subagent B must return a long report beginning with BETA_SUBAGENT_START
and ending with BETA_SUBAGENT_END.
Wait for both, then reply PRIMARY_FINAL.
-
Record every session/update notification without transforming it.
-
Observe that ACP first emits two distinct outer tool_call updates. Each has a different toolCallId and a rawInput object identifying the subagent invocation, for example:
{
"sessionUpdate": "tool_call",
"toolCallId": "<subagent-A-tool-call>",
"status": "pending",
"rawInput": {
"name": "alpha-probe",
"agent_type": "explore",
"description": "...",
"prompt": "..."
}
}
-
While those tasks run, observe many agent_message_chunk notifications. All of them have the exact same field shape and none carries either outer task's toolCallId or another source identifier.
-
Compare with the session's native events. The corresponding subagent events are distinguishable:
{
"type": "assistant.message",
"agentId": "<subagent-A-tool-call>",
"data": {
"parentToolCallId": "<subagent-A-tool-call>",
"interactionId": "<subagent-A-interaction>",
"content": "ALPHA_SUBAGENT_START..."
}
}
In one controlled run with two parallel Explore subagents, ACP emitted 2,333 agent_message_chunk notifications. Every chunk had only sessionUpdate, content.type, and content.text. No source metadata was present.
The final outer tool_call_update for each task does contain that subagent's complete, correctly separated response in rawOutput.content, and it exactly matches the corresponding native assistant.message. However, this arrives only at completion, after the unscoped deltas have already been emitted into the parent message stream.
Expected behavior
ACP clients should be able to distinguish primary-agent output from each subagent's output.
A minimal backward-compatible fix would be to preserve the identity already present on the native event, for example in _meta on the ACP notification or update:
{
"_meta": {
"copilot": {
"agentId": "<subagent-A-tool-call>",
"parentToolCallId": "<subagent-A-tool-call>"
}
}
}
Subagent lifecycle events should ideally also be exposed so clients can create and finish the correct UI activity item.
Longer term, this could align with ACP's child-session/subagent direction. Until source-aware ACP updates are available, a safer fallback would be to suppress subagent message/reasoning deltas from the parent ACP message stream rather than emit them as indistinguishable primary-agent chunks. The completed task result is already available through the corresponding outer tool_call_update.rawOutput.
Additional context
- Reproduced on Linux x86_64 using ACP over stdio.
- This is most visible with two or more parallel synchronous subagents because their streamed text may interleave.
- Primary-agent output is also represented as the same unscoped
agent_message_chunk shape, so an ACP client cannot safely filter or route individual chunks by source.
- The two outer
task tool calls remain distinguishable by toolCallId; the loss occurs specifically when native assistant/reasoning events are mapped to ACP message/thought chunks.
- Related but not duplicate:
Describe the bug
When
copilot --acpruns multiple synchronous subagents in parallel, subagent text deltas are forwarded into the parent ACP session as ordinaryagent_message_chunkupdates, but their source identity is removed.Copilot's internal/SDK events already distinguish these messages with
agentIdanddata.parentToolCallId. The outertasktool calls also have stable, distincttoolCallIdvalues. However, the ACP notifications for the corresponding text contain only:{ "method": "session/update", "params": { "sessionId": "<parent-session>", "update": { "sessionUpdate": "agent_message_chunk", "content": { "type": "text", "text": "..." } } } }There is no
_meta,parentToolCallId,agentId, message ID, or child session ID on these updates. Consequently, an ACP client cannot tell whether a chunk came from the primary agent, subagent A, or subagent B. When parallel subagents stream concurrently, their text can be interleaved and rendered as a corrupted primary-agent response.This appears to be an ACP adapter regression/gap rather than a core event-model limitation. In particular, #2265 was closed after newer SDK versions added
parentToolCallIdtoassistant.messageandassistant.message_delta; that field is available before the ACP mapping but is not preserved in the ACP output.Affected version
GitHub Copilot CLI
1.0.71-0Steps to reproduce the behavior
Start Copilot in ACP stdio mode:
From an ACP client, call
initialize, thensession/new.Send a prompt that requires exactly two synchronous Explore subagents to run in parallel, with distinguishable output. For example:
Record every
session/updatenotification without transforming it.Observe that ACP first emits two distinct outer
tool_callupdates. Each has a differenttoolCallIdand arawInputobject identifying the subagent invocation, for example:{ "sessionUpdate": "tool_call", "toolCallId": "<subagent-A-tool-call>", "status": "pending", "rawInput": { "name": "alpha-probe", "agent_type": "explore", "description": "...", "prompt": "..." } }While those tasks run, observe many
agent_message_chunknotifications. All of them have the exact same field shape and none carries either outer task'stoolCallIdor another source identifier.Compare with the session's native events. The corresponding subagent events are distinguishable:
{ "type": "assistant.message", "agentId": "<subagent-A-tool-call>", "data": { "parentToolCallId": "<subagent-A-tool-call>", "interactionId": "<subagent-A-interaction>", "content": "ALPHA_SUBAGENT_START..." } }In one controlled run with two parallel Explore subagents, ACP emitted 2,333
agent_message_chunknotifications. Every chunk had onlysessionUpdate,content.type, andcontent.text. No source metadata was present.The final outer
tool_call_updatefor each task does contain that subagent's complete, correctly separated response inrawOutput.content, and it exactly matches the corresponding nativeassistant.message. However, this arrives only at completion, after the unscoped deltas have already been emitted into the parent message stream.Expected behavior
ACP clients should be able to distinguish primary-agent output from each subagent's output.
A minimal backward-compatible fix would be to preserve the identity already present on the native event, for example in
_metaon the ACP notification or update:{ "_meta": { "copilot": { "agentId": "<subagent-A-tool-call>", "parentToolCallId": "<subagent-A-tool-call>" } } }Subagent lifecycle events should ideally also be exposed so clients can create and finish the correct UI activity item.
Longer term, this could align with ACP's child-session/subagent direction. Until source-aware ACP updates are available, a safer fallback would be to suppress subagent message/reasoning deltas from the parent ACP message stream rather than emit them as indistinguishable primary-agent chunks. The completed task result is already available through the corresponding outer
tool_call_update.rawOutput.Additional context
agent_message_chunkshape, so an ACP client cannot safely filter or route individual chunks by source.tasktool calls remain distinguishable bytoolCallId; the loss occurs specifically when native assistant/reasoning events are mapped to ACP message/thought chunks.parentToolCallIdwas added to SDK events. This report is about that identity being dropped by the ACP adapter.