--- title: "Session Transcript Schema" description: "Universal event schema for session transcripts across all agents." icon: "brackets-curly" --- 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 | ✓ | ✓ | ✓ | - | ✓ | 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 Basic message exchange between user and assistant. Visibility into tool invocations (file reads, command execution, etc.) and their results. When not natively supported, tool activity is embedded in message content. Interactive questions the agent asks the user. Emits `question.requested` and `question.resolved` events. Permission requests for sensitive operations. Emits `permission.requested` and `permission.resolved` events. Support for image attachments in messages. Support for file attachments in messages. Native `session.started` and `session.ended` events. When not supported, the daemon emits synthetic lifecycle events. Structured error events for runtime failures. Extended thinking or reasoning content with visibility controls. Detailed command execution events with stdout/stderr. Structured file modification events with diffs. Model Context Protocol tool support. Native streaming of content deltas. When not supported, the daemon emits a single synthetic delta before `item.completed`. 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? }` | **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 granted or denied | `{ 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`, `approved`, `denied` | | `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 | | `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 ```