github / github/copilot-cli

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

オープン
#4,106 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る
area:agents area:non-interactive
主要言語
Shell
スター
11.2k
フォーク
1.9k
平均マージ
14時間 16分
マージ済み PR(30日)
6

説明

### 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:

```json
{
"method": "session/update",
"params": {
"sessionId": "",
"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:

```bash
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:

```text
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:

```json
{
"sessionUpdate": "tool_call",
"toolCallId": "",
"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:

```json
{
"type": "assistant.message",
"agentId": "",
"data": {
"parentToolCallId": "",
"interactionId": "",
"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:

```json
{
"_meta": {
"copilot": {
"agentId": "",
"parentToolCallId": ""
}
}
}
```

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.

コントリビューションガイド

コントリビューションガイドを開く

調査の方向性

エントリポイント `copilot --acp` と、ネイティブの `assistant.message` および `assistant.message_delta` イベントを ACP の `session/update` 通知に変換するマッピングから始めます。2 つの並列な Explore サブエージェントを再現し、その後、各 `agent_message_chunk` を外側のタスクの `toolCallId` およびネイティブの `parentToolCallId` と比較します。ACP クライアントがプライマリ出力とサブエージェント出力を区別できるか、または分離されたタスクの結果が到着するまでサブエージェントのデルタが安全に抑制されれば完了です。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
shell
領域
api, cli
issue の種類
バグ
難易度
4/5
見積もり時間
3〜5日
活発さ
静か
明瞭さ
おおむね明確
初心者へのやさしさ
45/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。