From 34ebb52f46f887e04475018324d89706594274bd Mon Sep 17 00:00:00 2001 From: Logan Rosen Date: Wed, 1 Jul 2026 16:02:33 -0400 Subject: [PATCH 1/6] docs: document sub-agent event attribution Document envelope-level agentId in streaming events and clarify parent-agent rendering guidance. Mark parentToolCallId as deprecated for sub-agent attribution. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- docs/features/custom-agents.md | 2 ++ docs/features/streaming-events.md | 44 ++++++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/docs/features/custom-agents.md b/docs/features/custom-agents.md index fb2f81fd1c..1bb09c8300 100644 --- a/docs/features/custom-agents.md +++ b/docs/features/custom-agents.md @@ -434,6 +434,8 @@ By default, all custom agents are available for automatic selection (`infer: tru When a sub-agent runs, the parent session emits lifecycle events. Subscribe to these events to build UIs that visualize agent activity. +Sub-agent-originated session events share the parent session stream and include envelope-level `agentId`. Root/main agent events and session-level events omit `agentId`, so renderers can keep the parent response separate from sub-agent traces by checking the event envelope. + ### Event types | Event | Emitted when | Data | diff --git a/docs/features/streaming-events.md b/docs/features/streaming-events.md index 3703f871d3..fd22477845 100644 --- a/docs/features/streaming-events.md +++ b/docs/features/streaming-events.md @@ -56,6 +56,7 @@ Every session event, regardless of type, includes these fields: | `id` | `string` (UUID v4) | Unique event identifier | | `timestamp` | `string` (ISO 8601) | When the event was created | | `parentId` | `string \| null` | ID of the previous event in the chain; `null` for the first event | +| `agentId` | `string?` | Sub-agent instance ID for sub-agent-originated events; absent for root/main agent and session-level events | | `ephemeral` | `boolean?` | `true` for transient events; absent or `false` for persisted events | | `type` | `string` | Event type discriminator (see tables below) | | `data` | `object` | Event-specific payload | @@ -217,6 +218,35 @@ session.on(AssistantMessageDeltaEvent.class, event -> > [!TIP] > **(TypeScript)** The TypeScript SDK uses a discriminated union—when you match on `event.type`, the `data` payload is automatically narrowed to the correct shape. +## Render only the parent agent response + +Sub-agent events share the parent session stream and include envelope-level `agentId`. Root/main agent events and session-level events omit `agentId`, so main-chat renderers can ignore assistant events where `agentId` is set and route those events to traces or progress UI instead. + +
+Node.js / TypeScript + +```typescript +session.on("assistant.message_delta", (event) => { + if (!event.agentId) process.stdout.write(event.data.deltaContent); +}); +``` + +
+
+Python + +```python +from copilot.session_events import SessionEventType + +def handle(event): + if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA and event.agent_id is None: + print(event.data.delta_content, end="", flush=True) + +session.on(handle) +``` + +
+ ## Assistant events These events track the agent's response lifecycle—from turn start through streaming chunks to the final message. @@ -271,7 +301,7 @@ The assistant's complete response for this LLM call. May include tool invocation | `phase` | `string` | | Generation phase (e.g., `"thinking"` vs `"response"`) | | `outputTokens` | `number` | | Actual output token count from the API response | | `interactionId` | `string` | | CAPI interaction ID for telemetry | -| `parentToolCallId` | `string` | | Set when this message originates from a sub-agent | +| `parentToolCallId` | `string` | | Deprecated. Use envelope-level `agentId` for sub-agent attribution | **`ToolRequest` fields:** @@ -290,7 +320,7 @@ Ephemeral. Incremental chunk of the assistant's text response, streamed in real |------------|------|----------|-------------| | `messageId` | `string` | ✅ | Matches the corresponding `assistant.message` event | | `deltaContent` | `string` | ✅ | Text chunk to append to the message | -| `parentToolCallId` | `string` | | Set when originating from a sub-agent | +| `parentToolCallId` | `string` | | Deprecated. Use envelope-level `agentId` for sub-agent attribution | ### `assistant.turn_end` @@ -317,7 +347,7 @@ Ephemeral. Token usage and cost information for an individual API call. | `apiCallId` | `string` | | Completion ID from the provider (e.g., `chatcmpl-abc123`) | | `apiEndpoint` | `"/chat/completions" \| "/v1/messages" \| "/responses" \| "ws:/responses"` | | API endpoint used for the model call; useful for observability and cost attribution. `ws:/responses` is the websocket variant of the responses API | | `providerCallId` | `string` | | GitHub request tracing ID (`x-github-request-id`) | -| `parentToolCallId` | `string` | | Set when usage originates from a sub-agent | +| `parentToolCallId` | `string` | | Deprecated. Use envelope-level `agentId` for sub-agent attribution | | `quotaSnapshots` | `Record` | | Per-quota resource usage, keyed by quota identifier | | `copilotUsage` | `CopilotUsage` | | Itemized token cost breakdown from the API | @@ -344,7 +374,7 @@ Emitted when a tool begins executing. | `arguments` | `object` | | Parsed arguments passed to the tool | | `mcpServerName` | `string` | | MCP server name, when the tool is provided by an MCP server | | `mcpToolName` | `string` | | Original tool name on the MCP server | -| `parentToolCallId` | `string` | | Set when invoked by a sub-agent | +| `parentToolCallId` | `string` | | Deprecated. Use envelope-level `agentId` for sub-agent attribution | ### `tool.execution_partial_result` @@ -378,7 +408,7 @@ Emitted when a tool finishes executing—successfully or with an error. | `result` | `Result` | | Present on success (see below) | | `error` | `{ message, code? }` | | Present on failure | | `toolTelemetry` | `object` | | Tool-specific telemetry (e.g., CodeQL check counts) | -| `parentToolCallId` | `string` | | Set when invoked by a sub-agent | +| `parentToolCallId` | `string` | | Deprecated. Use envelope-level `agentId` for sub-agent attribution | **`Result` fields:** @@ -789,6 +819,8 @@ session.idle → Ready for next message (ephemeral) ## All event types at a glance +This table lists key `data` payload fields. For sub-agent attribution, use the envelope-level `agentId`. + | Event Type | Ephemeral | Category | Key Data Fields | |------------|-----------|----------|-----------------| | `assistant.turn_start` | | Assistant | `turnId`, `interactionId?` | @@ -797,7 +829,7 @@ session.idle → Ready for next message (ephemeral) | `assistant.reasoning_delta` | ✅ | Assistant | `reasoningId`, `deltaContent` | | `assistant.streaming_delta` | ✅ | Assistant | `totalResponseSizeBytes` | | `assistant.message` | | Assistant | `messageId`, `content`, `toolRequests?`, `outputTokens?`, `phase?` | -| `assistant.message_delta` | ✅ | Assistant | `messageId`, `deltaContent`, `parentToolCallId?` | +| `assistant.message_delta` | ✅ | Assistant | `messageId`, `deltaContent` | | `assistant.turn_end` | | Assistant | `turnId` | | `assistant.usage` | ✅ | Assistant | `model`, `apiEndpoint?`, `inputTokens?`, `outputTokens?`, `cost?`, `duration?` | | `tool.user_requested` | | Tool | `toolCallId`, `toolName`, `arguments?` | From a3c4106a94d3ca83eb6756fd729f77caa3b86bc5 Mon Sep 17 00:00:00 2001 From: Logan Rosen Date: Wed, 1 Jul 2026 16:05:26 -0400 Subject: [PATCH 2/6] docs: fix Python streaming event snippet validation Add a hidden validation-only Python block for the parent-agent streaming example so docs validation has a typed session placeholder while keeping the visible snippet concise. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/features/streaming-events.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/features/streaming-events.md b/docs/features/streaming-events.md index fd22477845..63cb63160e 100644 --- a/docs/features/streaming-events.md +++ b/docs/features/streaming-events.md @@ -235,6 +235,22 @@ session.on("assistant.message_delta", (event) => {
Python + +```python +from typing import Any + +from copilot.session_events import SessionEventType + +session: Any = None # assume session is created elsewhere + +def handle(event): + if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA and event.agent_id is None: + print(event.data.delta_content, end="", flush=True) + +session.on(handle) +``` + + ```python from copilot.session_events import SessionEventType From ca26dad0d1c6b87ed0285d244c9a94b9c41afd27 Mon Sep 17 00:00:00 2001 From: Logan Rosen Date: Wed, 1 Jul 2026 16:09:43 -0400 Subject: [PATCH 3/6] docs: avoid Any in Python streaming example Use a typed CopilotSession helper in the hidden validation snippet instead of typing the session as Any. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- docs/features/streaming-events.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/features/streaming-events.md b/docs/features/streaming-events.md index 63cb63160e..5d04203341 100644 --- a/docs/features/streaming-events.md +++ b/docs/features/streaming-events.md @@ -237,17 +237,15 @@ session.on("assistant.message_delta", (event) => { ```python -from typing import Any - +from copilot import CopilotSession from copilot.session_events import SessionEventType -session: Any = None # assume session is created elsewhere - -def handle(event): - if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA and event.agent_id is None: - print(event.data.delta_content, end="", flush=True) +def subscribe_parent_response(session: CopilotSession): + def handle(event): + if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA and event.agent_id is None: + print(event.data.delta_content, end="", flush=True) -session.on(handle) + session.on(handle) ``` From 650947c593dcc2355824380be802a1538662df1e Mon Sep 17 00:00:00 2001 From: Logan Rosen Date: Wed, 1 Jul 2026 16:10:53 -0400 Subject: [PATCH 4/6] docs: use supported TypeScript tab label Use the docs pipeline's supported TypeScript summary label for the new streaming-events tabbed example. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- docs/features/streaming-events.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/streaming-events.md b/docs/features/streaming-events.md index 5d04203341..5090c3e476 100644 --- a/docs/features/streaming-events.md +++ b/docs/features/streaming-events.md @@ -223,7 +223,7 @@ session.on(AssistantMessageDeltaEvent.class, event -> Sub-agent events share the parent session stream and include envelope-level `agentId`. Root/main agent events and session-level events omit `agentId`, so main-chat renderers can ignore assistant events where `agentId` is set and route those events to traces or progress UI instead.
-Node.js / TypeScript +TypeScript ```typescript session.on("assistant.message_delta", (event) => { From ee7936293c14c61d2b4a877f686c3c6db69f127e Mon Sep 17 00:00:00 2001 From: Logan Rosen Date: Wed, 1 Jul 2026 16:18:48 -0400 Subject: [PATCH 5/6] docs: generalize event quick reference note Keep the quick-reference table note focused on payload fields rather than repeating sub-agent attribution guidance. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- docs/features/streaming-events.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/streaming-events.md b/docs/features/streaming-events.md index 5090c3e476..095570ef17 100644 --- a/docs/features/streaming-events.md +++ b/docs/features/streaming-events.md @@ -833,7 +833,7 @@ session.idle → Ready for next message (ephemeral) ## All event types at a glance -This table lists key `data` payload fields. For sub-agent attribution, use the envelope-level `agentId`. +This table lists key `data` payload fields. Common envelope fields are documented above. | Event Type | Ephemeral | Category | Key Data Fields | |------------|-----------|----------|-----------------| From ba0413cda323ed2b87c309dbe24237433d35cf5e Mon Sep 17 00:00:00 2001 From: Logan Rosen Date: Wed, 1 Jul 2026 16:20:10 -0400 Subject: [PATCH 6/6] docs: collapse Python streaming example Use a single visible Python snippet for parent-response filtering instead of a hidden validation block plus duplicate visible example. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- docs/features/streaming-events.md | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/docs/features/streaming-events.md b/docs/features/streaming-events.md index 095570ef17..322a3dc1a1 100644 --- a/docs/features/streaming-events.md +++ b/docs/features/streaming-events.md @@ -235,7 +235,6 @@ session.on("assistant.message_delta", (event) => {
Python - ```python from copilot import CopilotSession from copilot.session_events import SessionEventType @@ -247,17 +246,6 @@ def subscribe_parent_response(session: CopilotSession): session.on(handle) ``` - - -```python -from copilot.session_events import SessionEventType - -def handle(event): - if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA and event.agent_id is None: - print(event.data.delta_content, end="", flush=True) - -session.on(handle) -```