Streaming session events
Every action the Copilot agent takes—thinking, writing code, running tools—is emitted as a session event you can subscribe to. This guide is a field-level reference for each event type so you know exactly what data to expect without reading the SDK source.
Overview
When streaming: true is set on a session, the SDK emits ephemeral events in real time (deltas, progress updates) alongside persisted events (complete messages, tool results). All events share a common envelope and carry a data payload whose shape depends on the event type.
sequenceDiagram
participant App as Your App
participant SDK as SDK Session
participant Agent as Copilot Agent
App->>SDK: send({ prompt })
SDK->>Agent: JSON-RPC
Agent-->>SDK: assistant.turn_start
SDK-->>App: event
loop Streaming response
Agent-->>SDK: assistant.message_delta (ephemeral)
SDK-->>App: event
end
Agent-->>SDK: assistant.message
SDK-->>App: event
loop Tool execution
Agent-->>SDK: tool.execution_start
SDK-->>App: event
Agent-->>SDK: tool.execution_complete
SDK-->>App: event
end
Agent-->>SDK: assistant.turn_end
SDK-->>App: event
Agent-->>SDK: session.idle (ephemeral)
SDK-->>App: event
| Concept |
Description |
| Ephemeral event |
Transient; streamed in real time but not persisted to the session log. Not replayed on session resume. |
| Persisted event |
Saved to the session event log on disk. Replayed when resuming a session. |
| Delta event |
An ephemeral streaming chunk (text or reasoning). Accumulate deltas to build the complete content. |
parentId chain |
Each event’s parentId points to the previous event, forming a linked list you can walk. |
Event envelope
Every session event, regardless of type, includes these fields:
| Field |
Type |
Description |
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 |
Subscribing to events
Node.js / TypeScript
```typescript
// All events
session.on((event) => {
console.log(event.type, event.data);
});
// Specific event type — data is narrowed automatically
session.on("assistant.message_delta", (event) => {
process.stdout.write(event.data.deltaContent);
});
```
Python
```python
from copilot import CopilotClient
from copilot.session_events import SessionEventType
client = CopilotClient()
session = None # assume session is created elsewhere
def handle(event):
if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA:
print(event.data.delta_content, end="", flush=True)
# session.on(handle)
```
```python
from copilot.session_events import SessionEventType
def handle(event):
if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA:
print(event.data.delta_content, end="", flush=True)
session.on(handle)
```
Go
```go
package main
import (
"context"
"fmt"
copilot "github.com/github/copilot-sdk/go"
"github.com/github/copilot-sdk/go/rpc"
)
func main() {
ctx := context.Background()
client := copilot.NewClient(nil)
session, _ := client.CreateSession(ctx, &copilot.SessionConfig{
Model: "gpt-5.4",
Streaming: copilot.Bool(true),
OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) {
return &rpc.PermissionDecisionApproveOnce{}, nil
},
})
session.On(func(event copilot.SessionEvent) {
if d, ok := event.Data.(*copilot.AssistantMessageDeltaData); ok {
fmt.Print(d.DeltaContent)
}
})
_ = session
}
```
```go
session.On(func(event copilot.SessionEvent) {
if d, ok := event.Data.(*copilot.AssistantMessageDeltaData); ok {
fmt.Print(d.DeltaContent)
}
})
```
.NET
```csharp
using GitHub.Copilot;
public static class StreamingEventsExample
{
public static async Task Example(CopilotSession session)
{
session.On(evt =>
{
if (evt is AssistantMessageDeltaEvent delta)
{
Console.Write(delta.Data.DeltaContent);
}
});
}
}
```
```csharp
session.On(evt =>
{
if (evt is AssistantMessageDeltaEvent delta)
{
Console.Write(delta.Data.DeltaContent);
}
});
```
</details>
Java
```java
// All events
session.on(event -> System.out.println(event.getType()));
// Specific event type — data is narrowed to the matching class
session.on(AssistantMessageDeltaEvent.class, event ->
System.out.print(event.getData().deltaContent())
);
```
> [!TIP]
> **(Python / Go)** These SDKs use separate, per-event data types (for example, `AssistantMessageDeltaData`), so only the relevant fields exist on each type.
>
> [!TIP]
> **(.NET)** The .NET SDK uses separate, strongly-typed data classes per event (e.g., `AssistantMessageDeltaData`), so only the relevant fields exist on each type.
>
> [!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.
TypeScript
```typescript
import type { CopilotSession } from "@github/copilot-sdk";
export function subscribeParentResponse(session: CopilotSession): void {
session.on("assistant.message_delta", (event) => {
if (!event.agentId) {
process.stdout.write(event.data.deltaContent);
}
});
}
```
Python
```python
from copilot import CopilotSession, SessionEvent, SessionEventType
from copilot.session_events import AssistantMessageDeltaData
def subscribe_parent_response(session: CopilotSession) -> None:
def handle(event: SessionEvent) -> None:
if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA and event.agent_id is None:
data = event.data
if isinstance(data, AssistantMessageDeltaData):
print(data.delta_content, end="", flush=True)
session.on(handle)
```
Go
```go
package example
import (
"fmt"
copilot "github.com/github/copilot-sdk/go"
)
func subscribeParentResponse(session *copilot.Session) {
session.On(func(event copilot.SessionEvent) {
if event.AgentID != nil {
return
}
if d, ok := event.Data.(*copilot.AssistantMessageDeltaData); ok {
fmt.Print(d.DeltaContent)
}
})
}
```
.NET
```csharp
using System;
using GitHub.Copilot;
static class ParentAgentResponseExample
{
public static void SubscribeParentResponse(CopilotSession session)
{
session.On(evt =>
{
if (evt.AgentId is null)
{
Console.Write(evt.Data.DeltaContent);
}
});
}
}
```
</details>
Java
```java
import com.github.copilot.CopilotSession;
import com.github.copilot.generated.AssistantMessageDeltaEvent;
final class ParentAgentResponseExample {
static void subscribeParentResponse(CopilotSession session) {
session.on(AssistantMessageDeltaEvent.class, event -> {
if (event.getAgentId() == null) {
System.out.print(event.getData().deltaContent());
}
});
}
}
```
Rust
```rust
use github_copilot_sdk::session::Session;
async fn subscribe_parent_response(session: &Session) {
let mut events = session.subscribe();
while let Ok(event) = events.recv().await {
if event.event_type == "assistant.message_delta" && event.agent_id.is_none() {
if let Some(delta) = event.data.get("deltaContent").and_then(|v| v.as_str()) {
print!("{delta}");
}
}
}
}
```
## Assistant events
These events track the agent's response lifecycle—from turn start through streaming chunks to the final message.
### `assistant.turn_start`
Emitted when the agent begins processing a turn.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `turnId` | `string` | ✅ | Turn identifier (typically a stringified turn number) |
| `interactionId` | `string` | | CAPI interaction ID for telemetry correlation |
### `assistant.intent`
Ephemeral. Short description of what the agent is currently doing, updated as it works.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `intent` | `string` | ✅ | Human-readable intent (e.g., "Exploring codebase") |
### `assistant.reasoning`
Complete extended thinking block from the model. Emitted after reasoning is finished.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `reasoningId` | `string` | ✅ | Unique identifier for this reasoning block |
| `content` | `string` | ✅ | The complete extended thinking text |
### `assistant.reasoning_delta`
Ephemeral. Incremental chunk of the model's extended thinking, streamed in real time.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `reasoningId` | `string` | ✅ | Matches the corresponding `assistant.reasoning` event |
| `deltaContent` | `string` | ✅ | Text chunk to append to reasoning content |
### `assistant.message`
The assistant's complete response for this LLM call. May include tool invocation requests.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `messageId` | `string` | ✅ | Unique identifier for this message |
| `content` | `string` | ✅ | The assistant's text response |
| `toolRequests` | `ToolRequest[]` | | Tool calls the assistant wants to make (see below) |
| `reasoningOpaque` | `string` | | Encrypted extended thinking (Anthropic models); session-bound |
| `reasoningText` | `string` | | Readable reasoning text from extended thinking |
| `encryptedContent` | `string` | | Encrypted reasoning content (OpenAI models); session-bound |
| `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` | | Deprecated. Use envelope-level `agentId` for sub-agent attribution |
**`ToolRequest` fields:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `toolCallId` | `string` | ✅ | Unique ID for this tool call |
| `name` | `string` | ✅ | Tool name (e.g., `"bash"`, `"edit"`, `"grep"`) |
| `arguments` | `object` | | Parsed arguments for the tool |
| `type` | `"function" \| "custom"` | | Call type; defaults to `"function"` when absent |
### `assistant.message_delta`
Ephemeral. Incremental chunk of the assistant's text response, streamed in real time.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `messageId` | `string` | ✅ | Matches the corresponding `assistant.message` event |
| `deltaContent` | `string` | ✅ | Text chunk to append to the message |
| `parentToolCallId` | `string` | | Deprecated. Use envelope-level `agentId` for sub-agent attribution |
### `assistant.turn_end`
Emitted when the agent finishes a turn (all tool executions complete, final response delivered).
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `turnId` | `string` | ✅ | Matches the corresponding `assistant.turn_start` event |
### `assistant.usage`
Ephemeral. Token usage and cost information for an individual API call.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `model` | `string` | ✅ | Model identifier (e.g., `"gpt-5.4"`) |
| `inputTokens` | `number` | | Input tokens consumed |
| `outputTokens` | `number` | | Output tokens produced |
| `reasoningTokens` | `number` | | Output tokens used for reasoning/chain-of-thought (subset of `outputTokens`) |
| `cacheReadTokens` | `number` | | Tokens read from prompt cache |
| `cacheWriteTokens` | `number` | | Tokens written to prompt cache |
| `cacheExpiresAt` | `string` | | ISO 8601 timestamp when the prompt cache for this model call expires |
| `contentFilterTriggered` | `boolean` | | Whether the response was blocked or truncated by content filtering (`finish_reason === 'content_filter'`) |
| `finishReason` | `string` | | Model finish reason (e.g., `"stop"`, `"length"`, `"tool_calls"`, `"content_filter"`) |
| `cost` | `number` | | Model multiplier cost for billing |
| `duration` | `number` | | API call duration in milliseconds |
| `timeToFirstTokenMs` | `number` | | Time from request dispatch to first token received (streaming latency) |
| `interTokenLatencyMs` | `number` | | Average latency between consecutive tokens (streaming throughput) |
| `reasoningEffort` | `string` | | Reasoning effort level used for this call (e.g., `"low"`, `"medium"`, `"high"`) |
| `initiator` | `string` | | What triggered this call (e.g., `"sub-agent"`); absent for user-initiated |
| `apiCallId` | `string` | | Completion ID from the provider (e.g., `chatcmpl-abc123`) |
| `serviceRequestId` | `string` | | Copilot service request ID (`x-copilot-service-request-id`) for CAPI log correlation |
| `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` | | Deprecated. Use envelope-level `agentId` for sub-agent attribution |
| `quotaSnapshots` | `Record<string, QuotaSnapshot>` | | Per-quota resource usage, keyed by quota identifier |
| `copilotUsage` | `CopilotUsage` | | Itemized token cost breakdown from the API |
### `assistant.streaming_delta`
Ephemeral. Low-level network progress indicator—total bytes received from the streaming API response.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `totalResponseSizeBytes` | `number` | ✅ | Cumulative bytes received so far |
## Tool execution events
These events track the full lifecycle of each tool invocation—from the model requesting a tool call through execution to completion.
### `tool.execution_start`
Emitted when a tool begins executing.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `toolCallId` | `string` | ✅ | Unique identifier for this tool call |
| `toolName` | `string` | ✅ | Name of the tool (e.g., `"bash"`, `"edit"`, `"grep"`) |
| `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` | | Deprecated. Use envelope-level `agentId` for sub-agent attribution |
### `tool.execution_partial_result`
Ephemeral. Incremental output from a running tool (e.g., streaming bash output).
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `toolCallId` | `string` | ✅ | Matches the corresponding `tool.execution_start` |
| `partialOutput` | `string` | ✅ | Incremental output chunk |
### `tool.execution_progress`
Ephemeral. Human-readable progress status from a running tool (e.g., MCP server progress notifications).
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `toolCallId` | `string` | ✅ | Matches the corresponding `tool.execution_start` |
| `progressMessage` | `string` | ✅ | Progress status message |
### `tool.execution_complete`
Emitted when a tool finishes executing—successfully or with an error.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `toolCallId` | `string` | ✅ | Matches the corresponding `tool.execution_start` |
| `success` | `boolean` | ✅ | Whether execution succeeded |
| `model` | `string` | | Model that generated this tool call |
| `interactionId` | `string` | | CAPI interaction ID |
| `isUserRequested` | `boolean` | | `true` when the user explicitly requested this tool call |
| `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` | | Deprecated. Use envelope-level `agentId` for sub-agent attribution |
**`Result` fields:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `content` | `string` | ✅ | Concise result sent to the LLM (may be truncated for token efficiency) |
| `detailedContent` | `string` | | Full result for display, preserving complete content like diffs |
| `contents` | `ContentBlock[]` | | Structured content blocks (text, terminal, image, audio, resource) |
### `tool.user_requested`
Emitted when the user explicitly requests a tool invocation (rather than the model choosing to call one).
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `toolCallId` | `string` | ✅ | Unique identifier for this tool call |
| `toolName` | `string` | ✅ | Name of the tool the user wants to invoke |
| `arguments` | `object` | | Arguments for the invocation |
## Session lifecycle events
### `session.idle`
Ephemeral. The agent has finished all processing and is ready for the next message. This is the signal that a turn is fully complete.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `backgroundTasks` | `BackgroundTasks` | | Background agents/shells still running when the agent became idle |
### `session.error`
An error occurred during session processing.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `errorType` | `string` | ✅ | Error category (e.g., `"authentication"`, `"quota"`, `"rate_limit"`) |
| `message` | `string` | ✅ | Human-readable error message |
| `stack` | `string` | | Error stack trace |
| `statusCode` | `number` | | HTTP status code from the upstream request |
| `providerCallId` | `string` | | GitHub request tracing ID for server-side log correlation |
### `session.compaction_start`
Context window compaction has begun. **Data payload is empty (`{}`)**.
### `session.compaction_complete`
Context window compaction finished.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `success` | `boolean` | ✅ | Whether compaction succeeded |
| `error` | `string` | | Error message if compaction failed |
| `preCompactionTokens` | `number` | | Tokens before compaction |
| `postCompactionTokens` | `number` | | Tokens after compaction |
| `preCompactionMessagesLength` | `number` | | Message count before compaction |
| `messagesRemoved` | `number` | | Messages removed |
| `tokensRemoved` | `number` | | Tokens removed |
| `summaryContent` | `string` | | LLM-generated summary of compacted history |
| `checkpointNumber` | `number` | | Checkpoint snapshot number created for recovery |
| `checkpointPath` | `string` | | File path where the checkpoint was stored |
| `compactionTokensUsed` | `{ input, output, cachedInput }` | | Token usage for the compaction LLM call |
| `requestId` | `string` | | GitHub request tracing ID for the compaction call |
### `session.title_changed`
Ephemeral. The session's auto-generated title was updated.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `title` | `string` | ✅ | New session title |
### `session.context_changed`
The session's working directory or repository context changed.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `cwd` | `string` | ✅ | Current working directory |
| `gitRoot` | `string` | | Git repository root |
| `repository` | `string` | | Repository in `"owner/name"` format |
| `branch` | `string` | | Current git branch |
### `session.usage_info`
Ephemeral. Context window utilization snapshot.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `tokenLimit` | `number` | ✅ | Maximum tokens for the model's context window |
| `currentTokens` | `number` | ✅ | Current tokens in the context window |
| `messagesLength` | `number` | ✅ | Current message count in the conversation |
### `session.session_limits_changed`
Session limits changed for the current accounting window. A `null` `sessionLimits` value means no limits are active.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `sessionLimits` | `SessionLimitsConfig \| null` | ✅ | Current session limits, or `null` when no limits are active |
| `sessionLimits.maxAiCredits` | `number` | | Maximum AI Credits allowed across the session's current accounting window |
### `session.usage_checkpoint`
Durable aggregate usage checkpoint used to reconstruct accounting when a session is resumed.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `totalNanoAiu` | `number` | ✅ | Session-wide accumulated nano-AI units cost at checkpoint time |
| `totalPremiumRequests` | `number` | | Total number of premium API requests used at checkpoint time |
### `session.task_complete`
The agent has completed its assigned task.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `summary` | `string` | | Summary of the completed task |
### `session.shutdown`
The session has ended.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `shutdownType` | `"routine" \| "error"` | ✅ | Normal shutdown or crash |
| `errorReason` | `string` | | Error description when `shutdownType` is `"error"` |
| `totalPremiumRequests` | `number` | ✅ | Total premium API requests used |
| `totalApiDurationMs` | `number` | ✅ | Cumulative API call time in milliseconds |
| `sessionStartTime` | `number` | ✅ | Unix timestamp (ms) when the session started |
| `codeChanges` | `{ linesAdded, linesRemoved, filesModified }` | ✅ | Aggregate code change metrics |
| `modelMetrics` | `Record<string, ModelMetric>` | ✅ | Per-model usage breakdown |
| `currentModel` | `string` | | Model selected at shutdown time |
## Permission and user input events
These events are emitted when the agent needs approval or input from the user before continuing.
### `permission.requested`
The agent needs permission to perform an action (run a command, write a file, etc.).
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `requestId` | `string` | ✅ | Use this to respond via `session.respondToPermission()` |
| `permissionRequest` | `PermissionRequest` | ✅ | Details of the permission being requested |
The `permissionRequest` is a discriminated union on `kind`:
| `kind` | Key Fields | Description |
|--------|------------|-------------|
| `"shell"` | `fullCommandText`, `intention`, `commands[]`, `possiblePaths[]` | Execute a shell command |
| `"write"` | `fileName`, `diff`, `intention`, `newFileContents?` | Write/modify a file |
| `"read"` | `path`, `intention` | Read a file or directory |
| `"mcp"` | `serverName`, `toolName`, `toolTitle`, `args?`, `readOnly` | Call an MCP tool |
| `"url"` | `url`, `intention` | Fetch a URL |
| `"memory"` | `subject`, `fact`, `citations` | Store a memory |
| `"custom-tool"` | `toolName`, `toolDescription`, `args?` | Call a custom tool |
All `kind` variants also include an optional `toolCallId` linking back to the tool call that triggered the request.
### `permission.completed`
A permission request was resolved.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `requestId` | `string` | ✅ | Matches the corresponding `permission.requested` |
| `result.kind` | `string` | ✅ | One of: `"approved"`, `"denied-by-rules"`, `"denied-interactively-by-user"`, `"denied-no-approval-rule-and-could-not-request-from-user"`, `"denied-by-content-exclusion-policy"` |
### `user_input.requested`
Ephemeral. The agent is asking the user a question.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `requestId` | `string` | ✅ | Use this to respond via `session.respondToUserInput()` |
| `question` | `string` | ✅ | The question to present to the user |
| `choices` | `string[]` | | Predefined choices for the user |
| `allowFreeform` | `boolean` | | Whether free-form text input is allowed |
### `user_input.completed`
Ephemeral. A user input request was resolved.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `requestId` | `string` | ✅ | Matches the corresponding `user_input.requested` |
### `elicitation.requested`
Ephemeral. The agent needs structured form input from the user (MCP elicitation protocol).
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `requestId` | `string` | ✅ | Use this to respond via `session.respondToElicitation()` |
| `message` | `string` | ✅ | Description of what information is needed |
| `mode` | `"form"` | | Elicitation mode (currently only `"form"`) |
| `requestedSchema` | `{ type: "object", properties, required? }` | ✅ | JSON Schema describing the form fields |
### `elicitation.completed`
Ephemeral. An elicitation request was resolved.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `requestId` | `string` | ✅ | Matches the corresponding `elicitation.requested` |
## Sub-agent and skill events
### `subagent.started`
A custom agent was invoked as a sub-agent.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `toolCallId` | `string` | ✅ | Parent tool call that spawned this sub-agent |
| `agentName` | `string` | ✅ | Internal name of the sub-agent |
| `agentDisplayName` | `string` | ✅ | Human-readable display name |
| `agentDescription` | `string` | ✅ | Description of what the sub-agent does |
| `model` | `string` | | Model the sub-agent will run with, when known at start |
### `subagent.completed`
A sub-agent finished successfully.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `toolCallId` | `string` | ✅ | Matches the corresponding `subagent.started` |
| `agentName` | `string` | ✅ | Internal name |
| `agentDisplayName` | `string` | ✅ | Display name |
| `model` | `string` | | Model used by the sub-agent |
| `durationMs` | `number` | | Wall-clock execution duration in milliseconds |
| `totalTokens` | `number` | | Total input and output tokens consumed |
| `totalToolCalls` | `number` | | Total tool calls made |
### `subagent.failed`
A sub-agent encountered an error.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `toolCallId` | `string` | ✅ | Matches the corresponding `subagent.started` |
| `agentName` | `string` | ✅ | Internal name |
| `agentDisplayName` | `string` | ✅ | Display name |
| `error` | `string` | ✅ | Error message |
| `model` | `string` | | Model selected for the sub-agent, when known |
| `durationMs` | `number` | | Wall-clock execution duration in milliseconds |
| `totalTokens` | `number` | | Total input and output tokens consumed before failure |
| `totalToolCalls` | `number` | | Total tool calls made before failure |
### `subagent.selected`
A custom agent was selected (inferred) to handle the current request.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `agentName` | `string` | ✅ | Internal name of the selected agent |
| `agentDisplayName` | `string` | ✅ | Display name |
| `tools` | `string[] \| null` | ✅ | Tool names available to this agent; `null` for all tools |
### `subagent.deselected`
A custom agent was deselected, returning to the default agent. **Data payload is empty (`{}`)**.
### `skill.invoked`
A skill was activated for the current conversation.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `name` | `string` | ✅ | Skill name |
| `path` | `string` | ✅ | File path to the SKILL.md definition |
| `content` | `string` | ✅ | Full skill content injected into the conversation |
| `allowedTools` | `string[]` | | Tools auto-approved while this skill is active |
| `pluginName` | `string` | | Plugin the skill originated from |
| `pluginVersion` | `string` | | Plugin version |
## Other events
### `abort`
The current turn was aborted.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `reason` | `string` | ✅ | Why the turn was aborted (e.g., `"user initiated"`) |
### `user.message`
The user sent a message. Recorded for the session timeline.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `content` | `string` | ✅ | The user's message text |
| `transformedContent` | `string` | | Transformed version after preprocessing |
| `attachments` | `Attachment[]` | | File, directory, selection, blob, or GitHub reference attachments |
| `source` | `string` | | Message source identifier |
| `agentMode` | `string` | | Agent mode: `"interactive"`, `"plan"`, `"autopilot"`, or `"shell"` |
| `interactionId` | `string` | | CAPI interaction ID |
### `system.message`
A system or developer prompt was injected into the conversation.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `content` | `string` | ✅ | The prompt text |
| `role` | `"system" \| "developer"` | ✅ | Message role |
| `name` | `string` | | Source identifier |
| `metadata` | `{ promptVersion?, variables? }` | | Prompt template metadata |
### `external_tool.requested`
The agent wants to invoke an external tool (one provided by the SDK consumer).
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `requestId` | `string` | ✅ | Use this to respond via `session.respondToExternalTool()` |
| `sessionId` | `string` | ✅ | Session this request belongs to |
| `toolCallId` | `string` | ✅ | Tool call ID for this invocation |
| `toolName` | `string` | ✅ | Name of the external tool |
| `arguments` | `object` | | Arguments for the tool |
### `external_tool.completed`
An external tool request was resolved.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `requestId` | `string` | ✅ | Matches the corresponding `external_tool.requested` |
### `exit_plan_mode.requested`
Ephemeral. The agent has created a plan and wants to exit plan mode.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `requestId` | `string` | ✅ | Use this to respond via `session.respondToExitPlanMode()` |
| `summary` | `string` | ✅ | Summary of the plan |
| `planContent` | `string` | ✅ | Full plan file content |
| `actions` | `string[]` | ✅ | Available user actions (e.g., approve, edit, reject) |
| `recommendedAction` | `string` | ✅ | Suggested action |
### `exit_plan_mode.completed`
Ephemeral. An exit plan mode request was resolved.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `requestId` | `string` | ✅ | Matches the corresponding `exit_plan_mode.requested` |
### `command.queued`
Ephemeral. A slash command was queued for execution.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `requestId` | `string` | ✅ | Use this to respond via `session.respondToQueuedCommand()` |
| `command` | `string` | ✅ | The slash command text (e.g., `/help`, `/clear`) |
### `command.completed`
Ephemeral. A queued command was resolved.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `requestId` | `string` | ✅ | Matches the corresponding `command.queued` |
### `session_limits_exhausted.requested`
Ephemeral. The current session budget was exhausted and the runtime needs a user decision before continuing.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `requestId` | `string` | ✅ | Use this ID when responding to the pending exhausted-limit request |
| `maxAiCredits` | `number` | ✅ | Configured max AI Credits for the current accounting window |
| `usedAiCredits` | `number` | ✅ | AI Credits already consumed in the current accounting window |
### `session_limits_exhausted.completed`
Ephemeral. A pending exhausted-limit request was resolved.
| Data Field | Type | Required | Description |
|------------|------|----------|-------------|
| `requestId` | `string` | ✅ | Matches the corresponding `session_limits_exhausted.requested` event |
| `response.action` | `"add" \| "set" \| "unset" \| "cancel"` | ✅ | Action selected for the exhausted-limit request |
| `response.additionalAiCredits` | `number` | | AI Credits to add to the current max when `response.action` is `"add"` |
| `response.maxAiCredits` | `number` | | New absolute max AI Credits when `response.action` is `"set"` |
## Quick reference: agentic turn flow
A typical agentic turn emits events in this order:
```
assistant.turn_start → Turn begins
├── assistant.intent → What the agent plans to do (ephemeral)
├── assistant.reasoning_delta → Streaming thinking chunks (ephemeral, repeated)
├── assistant.reasoning → Complete thinking block
├── assistant.message_delta → Streaming response chunks (ephemeral, repeated)
├── assistant.message → Complete response (may include toolRequests)
├── assistant.usage → Token usage for this API call (ephemeral)
│
├── [If tools were requested:]
│ ├── permission.requested → Needs user approval
│ ├── permission.completed → Approval result
│ ├── tool.execution_start → Tool begins
│ ├── tool.execution_partial_result → Streaming tool output (ephemeral, repeated)
│ ├── tool.execution_progress → Progress updates (ephemeral, repeated)
│ ├── tool.execution_complete → Tool finished
│ │
│ └── [Agent loops: more reasoning → message → tool calls...]
│
assistant.turn_end → Turn complete
session.idle → Ready for next message (ephemeral)
```
## All event types at a glance
This table lists key `data` payload fields. Common envelope fields are documented above.
| Event Type | Ephemeral | Category | Key Data Fields |
|------------|-----------|----------|-----------------|
| `assistant.turn_start` | | Assistant | `turnId`, `interactionId?` |
| `assistant.intent` | ✅ | Assistant | `intent` |
| `assistant.reasoning` | | Assistant | `reasoningId`, `content` |
| `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` |
| `assistant.turn_end` | | Assistant | `turnId` |
| `assistant.usage` | ✅ | Assistant | `model`, `apiEndpoint?`, `inputTokens?`, `outputTokens?`, `cost?`, `duration?` |
| `tool.user_requested` | | Tool | `toolCallId`, `toolName`, `arguments?` |
| `tool.execution_start` | | Tool | `toolCallId`, `toolName`, `arguments?`, `mcpServerName?` |
| `tool.execution_partial_result` | ✅ | Tool | `toolCallId`, `partialOutput` |
| `tool.execution_progress` | ✅ | Tool | `toolCallId`, `progressMessage` |
| `tool.execution_complete` | | Tool | `toolCallId`, `success`, `result?`, `error?` |
| `session.idle` | ✅ | Session | `backgroundTasks?` |
| `session.error` | | Session | `errorType`, `message`, `statusCode?` |
| `session.compaction_start` | | Session | *(empty)* |
| `session.compaction_complete` | | Session | `success`, `preCompactionTokens?`, `summaryContent?` |
| `session.title_changed` | ✅ | Session | `title` |
| `session.context_changed` | | Session | `cwd`, `gitRoot?`, `repository?`, `branch?` |
| `session.usage_info` | ✅ | Session | `tokenLimit`, `currentTokens`, `messagesLength` |
| `session.session_limits_changed` | | Session | `sessionLimits` |
| `session.usage_checkpoint` | | Session | `totalNanoAiu`, `totalPremiumRequests?` |
| `session.task_complete` | | Session | `summary?` |
| `session.shutdown` | | Session | `shutdownType`, `codeChanges`, `modelMetrics` |
| `permission.requested` | | Permission | `requestId`, `permissionRequest` |
| `permission.completed` | | Permission | `requestId`, `result.kind` |
| `user_input.requested` | ✅ | User Input | `requestId`, `question`, `choices?` |
| `user_input.completed` | ✅ | User Input | `requestId` |
| `elicitation.requested` | ✅ | User Input | `requestId`, `message`, `requestedSchema` |
| `elicitation.completed` | ✅ | User Input | `requestId` |
| `subagent.started` | | Sub-Agent | `toolCallId`, `agentName`, `agentDisplayName`, `model?` |
| `subagent.completed` | | Sub-Agent | `toolCallId`, `agentName`, `agentDisplayName`, `model?`, `durationMs?`, `totalTokens?`, `totalToolCalls?` |
| `subagent.failed` | | Sub-Agent | `toolCallId`, `agentName`, `error`, `model?`, `durationMs?`, `totalTokens?`, `totalToolCalls?` |
| `subagent.selected` | | Sub-Agent | `agentName`, `agentDisplayName`, `tools` |
| `subagent.deselected` | | Sub-Agent | *(empty)* |
| `skill.invoked` | | Skill | `name`, `path`, `content`, `allowedTools?` |
| `abort` | | Control | `reason` |
| `user.message` | | User | `content`, `attachments?`, `agentMode?` |
| `system.message` | | System | `content`, `role` |
| `external_tool.requested` | | External Tool | `requestId`, `toolName`, `arguments?` |
| `external_tool.completed` | | External Tool | `requestId` |
| `command.queued` | ✅ | Command | `requestId`, `command` |
| `command.completed` | ✅ | Command | `requestId` |
| `session_limits_exhausted.requested` | ✅ | Session | `requestId`, `maxAiCredits`, `usedAiCredits` |
| `session_limits_exhausted.completed` | ✅ | Session | `requestId`, `response.action` |
| `exit_plan_mode.requested` | ✅ | Plan Mode | `requestId`, `summary`, `planContent`, `actions` |
| `exit_plan_mode.completed` | ✅ | Plan Mode | `requestId` |