github / github/copilot-cli

ACP drops subagent source identity and flattens parallel output into the parent stream

Offen
#4,106 0 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

area:agents area:non-interactive
Vorherrschende Sprache
Shell
Sterne
11.2k
Forks
1.9k
Ø Merge
14 Std. 16 Min.
Gemergte PRs (30 T.)
6

Beschreibung

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
  1. Start Copilot in ACP stdio mode:

    copilot --acp
    
  2. From an ACP client, call initialize, then session/new.

  3. 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.
    
  4. Record every session/update notification without transforming it.

  5. 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": "..."
      }
    }
    
  6. 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.

  7. 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:
    • #2265 concerns SDK/Fleet per-subagent output and was closed after parentToolCallId was added to SDK events. This report is about that identity being dropped by the ACP adapter.
    • #2645 concerns missing SDK delta events. Here, deltas are emitted over ACP, but without their source identity.

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Beginne beim Einstiegspunkt copilot --acp und dem Mapping, das native assistant.message- und assistant.message_delta-Ereignisse in ACP-session/update-Benachrichtigungen umwandelt. Reproduziere zwei parallele Explore-Subagents und vergleiche dann jedes agent_message_chunk mit der toolCallId der äußeren Aufgabe und der nativen parentToolCallId. Erledigt ist die Aufgabe, wenn ACP-Clients primäre und Subagent-Ausgaben unterscheiden können oder Subagent-Deltas sicher unterdrückt werden, bis das getrennte Aufgabenergebnis eintrifft.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
shell
Bereich
api, cli
Issue-Typ
Bug
Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Aktivitätsstatus
Ruhig
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
45/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.