---
title: "Session Transcript Schema"
description: "Universal event schema for session transcripts across all agents."
---

Each coding agent outputs events in its own native format. The sandbox-agent converts these into a universal event schema, giving you a consistent session transcript regardless of which agent you use.

The schema is defined in [OpenAPI format](https://github.com/rivet-dev/sandbox-agent/blob/main/docs/openapi.json). See the [HTTP API Reference](/api-reference) for endpoint documentation.

## Coverage Matrix

This table shows which agent feature coverage appears in the universal event stream. All agents retain their full native feature coverage—this only reflects what's normalized into the schema.

| Feature            | Claude | Codex | OpenCode     | Amp          | Pi (RPC)     |
|--------------------|:------:|:-----:|:------------:|:------------:|:------------:|
| Stability          | Stable | Stable| Experimental | Experimental | Experimental |
| Text Messages      |   ✓    |   ✓   |      ✓       |      ✓       |      ✓       |
| Tool Calls         |   ✓    |   ✓   |      ✓       |      ✓       |      ✓       |
| Tool Results       |   ✓    |   ✓   |      ✓       |      ✓       |      ✓       |
| Questions (HITL)   |   ✓    |       |      ✓       |              |              |
| Permissions (HITL) |   ✓    |   ✓   |      ✓       |      -       |              |
| Images             |   -    |   ✓   |      ✓       |      -       |      ✓       |
| File Attachments   |   -    |   ✓   |      ✓       |      -       |              |
| Session Lifecycle  |   -    |   ✓   |      ✓       |      -       |              |
| Error Events       |   -    |   ✓   |      ✓       |      ✓       |      ✓       |
| Reasoning/Thinking |   -    |   ✓   |      -       |      -       |      ✓       |
| Command Execution  |   -    |   ✓   |      -       |      -       |              |
| File Changes       |   -    |   ✓   |      -       |      -       |              |
| MCP Tools          |   ✓    |   ✓   |      ✓       |      ✓       |              |
| Streaming Deltas   |   ✓    |   ✓   |      ✓       |      -       |      ✓       |
| Variants           |        |   ✓   |      ✓       |      ✓       |      ✓       |

Agents: [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview) · [Codex](https://github.com/openai/codex) · [OpenCode](https://github.com/opencode-ai/opencode) · [Amp](https://ampcode.com) · [Pi](https://buildwithpi.ai/pi-cli)

- ✓ = Appears in session events
- \- = Agent supports natively, schema conversion coming soon
- (blank) = Not supported by agent
- Pi runtime model is router-managed per-session RPC (`pi --mode rpc`); it does not use generic subprocess streaming.

<AccordionGroup>
  <Accordion title="Text Messages">
    Basic message exchange between user and assistant.
  </Accordion>
  <Accordion title="Tool Calls & Results">
    Visibility into tool invocations (file reads, command execution, etc.) and their results. When not natively supported, tool activity is embedded in message content.
  </Accordion>
  <Accordion title="Questions (HITL)">
    Interactive questions the agent asks the user. Emits `question.requested` and `question.resolved` events.
  </Accordion>
  <Accordion title="Permissions (HITL)">
    Permission requests for sensitive operations. Emits `permission.requested` and `permission.resolved` events.
  </Accordion>
  <Accordion title="Images">
    Support for image attachments in messages.
  </Accordion>
  <Accordion title="File Attachments">
    Support for file attachments in messages.
  </Accordion>
  <Accordion title="Session Lifecycle">
    Native `session.started` and `session.ended` events. When not supported, the daemon emits synthetic lifecycle events.
  </Accordion>
  <Accordion title="Error Events">
    Structured error events for runtime failures.
  </Accordion>
  <Accordion title="Reasoning/Thinking">
    Extended thinking or reasoning content with visibility controls.
  </Accordion>
  <Accordion title="Command Execution">
    Detailed command execution events with stdout/stderr.
  </Accordion>
  <Accordion title="File Changes">
    Structured file modification events with diffs.
  </Accordion>
  <Accordion title="MCP Tools">
    Model Context Protocol tool support.
  </Accordion>
  <Accordion title="Streaming Deltas">
    Native streaming of content deltas. When not supported, the daemon emits a single synthetic delta before `item.completed`.
  </Accordion>
  <Accordion title="Variants">
    Model variants such as reasoning effort or depth. Agents may expose different variant sets per model.
  </Accordion>
</AccordionGroup>

Want support for another agent? [Open an issue](https://github.com/rivet-dev/sandbox-agent/issues/new) to request it.

## UniversalEvent

Every event from the API is wrapped in a `UniversalEvent` envelope.

| Field | Type | Description |
|-------|------|-------------|
| `event_id` | string | Unique identifier for this event |
| `sequence` | integer | Monotonic sequence number within the session (starts at 1) |
| `time` | string | RFC3339 timestamp |
| `session_id` | string | Daemon-generated session identifier |
| `native_session_id` | string? | Provider-native session/thread identifier (e.g., Codex `threadId`, OpenCode `sessionID`) |
| `source` | string | Event origin: `agent` (native) or `daemon` (synthetic) |
| `synthetic` | boolean | Whether this event was generated by the daemon to fill gaps |
| `type` | string | Event type (see [Event Types](#event-types)) |
| `data` | object | Event-specific payload |
| `raw` | any? | Original provider payload (only when `include_raw=true`) |

```json
{
  "event_id": "evt_abc123",
  "sequence": 1,
  "time": "2025-01-28T12:00:00Z",
  "session_id": "my-session",
  "native_session_id": "thread_xyz",
  "source": "agent",
  "synthetic": false,
  "type": "item.completed",
  "data": { ... }
}
```

## Event Types

### Session Lifecycle

| Type | Description | Data |
|------|-------------|------|
| `session.started` | Session has started | `{ metadata?: any }` |
| `session.ended` | Session has ended | `{ reason, terminated_by, message?, exit_code? }` |

### Turn Lifecycle

| Type | Description | Data |
|------|-------------|------|
| `turn.started` | Turn has started | `{ phase: "started", turn_id?, metadata? }` |
| `turn.ended` | Turn has ended | `{ phase: "ended", turn_id?, metadata? }` |

**SessionEndedData**

| Field | Type | Values |
|-------|------|--------|
| `reason` | string | `completed`, `error`, `terminated` |
| `terminated_by` | string | `agent`, `daemon` |
| `message` | string? | Error message (only present when reason is `error`) |
| `exit_code` | int? | Process exit code (only present when reason is `error`) |
| `stderr` | StderrOutput? | Structured stderr output (only present when reason is `error`) |

**StderrOutput**

| Field | Type | Description |
|-------|------|-------------|
| `head` | string? | First 20 lines of stderr (if truncated) or full stderr (if not truncated) |
| `tail` | string? | Last 50 lines of stderr (only present if truncated) |
| `truncated` | boolean | Whether the output was truncated |
| `total_lines` | int? | Total number of lines in stderr |

### Item Lifecycle

| Type | Description | Data |
|------|-------------|------|
| `item.started` | Item creation | `{ item }` |
| `item.delta` | Streaming content delta | `{ item_id, native_item_id?, delta }` |
| `item.completed` | Item finalized | `{ item }` |

Items follow a consistent lifecycle: `item.started` → `item.delta` (0 or more) → `item.completed`.

### HITL (Human-in-the-Loop)

| Type | Description | Data |
|------|-------------|------|
| `permission.requested` | Permission request pending | `{ permission_id, action, status, metadata? }` |
| `permission.resolved` | Permission decision recorded | `{ permission_id, action, status, metadata? }` |
| `question.requested` | Question pending user input | `{ question_id, prompt, options, status }` |
| `question.resolved` | Question answered or rejected | `{ question_id, prompt, options, status, response? }` |

**PermissionEventData**

| Field | Type | Description |
|-------|------|-------------|
| `permission_id` | string | Identifier for the permission request |
| `action` | string | What the agent wants to do |
| `status` | string | `requested`, `accept`, `accept_for_session`, `reject` |
| `metadata` | any? | Additional context |

**QuestionEventData**

| Field | Type | Description |
|-------|------|-------------|
| `question_id` | string | Identifier for the question |
| `prompt` | string | Question text |
| `options` | string[] | Available answer options |
| `status` | string | `requested`, `answered`, `rejected` |
| `response` | string? | Selected answer (when resolved) |

### Errors

| Type | Description | Data |
|------|-------------|------|
| `error` | Runtime error | `{ message, code?, details? }` |
| `agent.unparsed` | Parse failure | `{ error, location, raw_hash? }` |

The `agent.unparsed` event indicates the daemon failed to parse an agent payload. This should be treated as a bug.

## UniversalItem

Items represent discrete units of content within a session.

| Field | Type | Description |
|-------|------|-------------|
| `item_id` | string | Daemon-generated identifier |
| `native_item_id` | string? | Provider-native item/message identifier |
| `parent_id` | string? | Parent item ID (e.g., tool call/result parented to a message) |
| `kind` | string | Item category (see below) |
| `role` | string? | Actor role for message items |
| `status` | string | Lifecycle status |
| `content` | ContentPart[] | Ordered list of content parts |

### ItemKind

| Value | Description |
|-------|-------------|
| `message` | User or assistant message |
| `tool_call` | Tool invocation |
| `tool_result` | Tool execution result |
| `system` | System message |
| `status` | Status update |
| `unknown` | Unrecognized item type |

### ItemRole

| Value | Description |
|-------|-------------|
| `user` | User message |
| `assistant` | Assistant response |
| `system` | System prompt |
| `tool` | Tool-related message |

### ItemStatus

| Value | Description |
|-------|-------------|
| `in_progress` | Item is streaming or pending |
| `completed` | Item is finalized |
| `failed` | Item execution failed |

## Content Parts

The `content` array contains typed parts that make up an item's payload.

### text

Plain text content.

```json
{ "type": "text", "text": "Hello, world!" }
```

### json

Structured JSON content.

```json
{ "type": "json", "json": { "key": "value" } }
```

### tool_call

Tool invocation.

| Field | Type | Description |
|-------|------|-------------|
| `name` | string | Tool name |
| `arguments` | string | JSON-encoded arguments |
| `call_id` | string | Unique call identifier |

```json
{
  "type": "tool_call",
  "name": "read_file",
  "arguments": "{\"path\": \"/src/main.ts\"}",
  "call_id": "call_abc123"
}
```

### tool_result

Tool execution result.

| Field | Type | Description |
|-------|------|-------------|
| `call_id` | string | Matching call identifier |
| `output` | string | Tool output |

```json
{
  "type": "tool_result",
  "call_id": "call_abc123",
  "output": "File contents here..."
}
```

### file_ref

File reference with optional diff.

| Field | Type | Description |
|-------|------|-------------|
| `path` | string | File path |
| `action` | string | `read`, `write`, `patch` |
| `diff` | string? | Unified diff (for patches) |

```json
{
  "type": "file_ref",
  "path": "/src/main.ts",
  "action": "write",
  "diff": "@@ -1,3 +1,4 @@\n+import { foo } from 'bar';"
}
```

### image

Image reference.

| Field | Type | Description |
|-------|------|-------------|
| `path` | string | Image file path |
| `mime` | string? | MIME type |

```json
{ "type": "image", "path": "/tmp/screenshot.png", "mime": "image/png" }
```

### reasoning

Model reasoning/thinking content.

| Field | Type | Description |
|-------|------|-------------|
| `text` | string | Reasoning text |
| `visibility` | string | `public` or `private` |

```json
{ "type": "reasoning", "text": "Let me think about this...", "visibility": "public" }
```

### status

Status indicator.

| Field | Type | Description |
|-------|------|-------------|
| `label` | string | Status label |
| `detail` | string? | Additional detail |

```json
{ "type": "status", "label": "Running tests", "detail": "3 of 10 passed" }
```

## Source & Synthetics

### EventSource

The `source` field indicates who emitted the event:

| Value | Description |
|-------|-------------|
| `agent` | Native event from the agent |
| `daemon` | Synthetic event generated by the daemon |

### Synthetic Events

The daemon emits synthetic events (`synthetic: true`, `source: "daemon"`) to provide a consistent event stream across all agents. Common synthetics:

| Synthetic | When |
|-----------|------|
| `session.started` | Agent doesn't emit explicit session start |
| `session.ended` | Agent doesn't emit explicit session end |
| `turn.started` | Agent doesn't emit explicit turn start |
| `turn.ended` | Agent doesn't emit explicit turn end |
| `item.started` | Agent doesn't emit item start events |
| `item.delta` | Agent doesn't stream deltas natively |
| `question.*` | Claude Code plan mode (from ExitPlanMode tool) |

### Raw Payloads

Pass `include_raw=true` to event endpoints to receive the original agent payload in the `raw` field. Useful for debugging or accessing agent-specific data not in the universal schema.

```typescript
const events = await client.getEvents("my-session", { includeRaw: true });
// events[0].raw contains the original agent payload
```
