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

<!-- markdownlint-disable GHD046 GHD005 -->

<!-- Suppressed: GHD046 (outdated release terminology), GHD005 (hardcoded data variable) -->

## 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`.

![Diagram: Sequence diagram showing the described process.](/assets/images/help/copilot/copilot-sdk/features-streaming-events-diagram-0.png)

| 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

<div class="ghd-codetabs">
<div class="ghd-codetab" data-lang="typescript" data-label="TypeScript"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">TypeScript</div>

```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);
});
```

</div>

<div class="ghd-codetab" data-lang="python" data-label="Python"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Python</div>

```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)
```

</div>

<div class="ghd-codetab" data-lang="go" data-label="Go"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Go</div>

```golang
session.On(func(event copilot.SessionEvent) {
    if d, ok := event.Data.(*copilot.AssistantMessageDeltaData); ok {
        fmt.Print(d.DeltaContent)
    }
})
```

</div>

<div class="ghd-codetab" data-lang="dotnet" data-label=".NET"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">.NET</div>

```csharp
session.On<SessionEvent>(evt =>
{
    if (evt is AssistantMessageDeltaEvent delta)
    {
        Console.Write(delta.Data.DeltaContent);
    }
});
```

</div>

<div class="ghd-codetab" data-lang="java" data-label="Java"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Java</div>

<!-- docs-validate: skip -->

```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())
);
```

</div>

</div>

> \[!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.

<div class="ghd-codetabs">
<div class="ghd-codetab" data-lang="typescript" data-label="TypeScript"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">TypeScript</div>

```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);
        }
    });
}
```

</div>

<div class="ghd-codetab" data-lang="python" data-label="Python"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Python</div>

```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)
```

</div>

<div class="ghd-codetab" data-lang="go" data-label="Go"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Go</div>

```golang
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)
		}
	})
}
```

</div>

<div class="ghd-codetab" data-lang="dotnet" data-label=".NET"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">.NET</div>

```csharp
using System;
using GitHub.Copilot;

static class ParentAgentResponseExample
{
    public static void SubscribeParentResponse(CopilotSession session)
    {
        session.On<AssistantMessageDeltaEvent>(evt =>
        {
            if (evt.AgentId is null)
            {
                Console.Write(evt.Data.DeltaContent);
            }
        });
    }
}
```

</div>

<div class="ghd-codetab" data-lang="java" data-label="Java"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Java</div>

```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());
            }
        });
    }
}
```

</div>

<div class="ghd-codetab" data-lang="rust" data-label="Rust"><div class="ghd-codetab-fallback-label" role="heading" aria-level="3">Rust</div>

```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}");
            }
        }
    }
}
```

</div>

</div>

## 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                                                 |
| ---------- | --------- | -------- | ----------------------------------------------------------- |
| `aborted`  | `boolean` |          | True when the preceding turn was cancelled via abort signal |

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

```text
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       | `aborted?`                                                                                                |
| `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`                                                                                               |