--- title: "Universal Schema" description: "Reference for the universal event and item schema." icon: "brackets-curly" --- The universal schema normalizes events from all supported agents (Claude Code, Codex, OpenCode, Amp) into a consistent format. This lets you build UIs and persistence layers that work with any agent without special-casing. 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. ## 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 ```