diff --git a/docs/agent-sessions.mdx b/docs/agent-sessions.mdx
index 0154537..0f9e2ab 100644
--- a/docs/agent-sessions.mdx
+++ b/docs/agent-sessions.mdx
@@ -51,108 +51,6 @@ await session.prompt([
unsubscribe();
```
-### Event types
-
-Each event's `payload` contains a session update. The `sessionUpdate` field identifies the type.
-
-
-
-Streamed text or content from the agent's response.
-
-```json
-{
- "sessionUpdate": "agent_message_chunk",
- "content": { "type": "text", "text": "Here's how the repository is structured..." }
-}
-```
-
-
-
-Internal reasoning from the agent (chain-of-thought / extended thinking).
-
-```json
-{
- "sessionUpdate": "agent_thought_chunk",
- "content": { "type": "text", "text": "I should start by looking at the project structure..." }
-}
-```
-
-
-
-Echo of the user's prompt being processed.
-
-```json
-{
- "sessionUpdate": "user_message_chunk",
- "content": { "type": "text", "text": "Summarize the repository structure." }
-}
-```
-
-
-
-The agent invoked a tool (file edit, terminal command, etc.).
-
-```json
-{
- "sessionUpdate": "tool_call",
- "toolCallId": "tc_abc123",
- "title": "Read file",
- "status": "in_progress",
- "rawInput": { "path": "/src/index.ts" }
-}
-```
-
-
-
-Progress or result update for an in-progress tool call.
-
-```json
-{
- "sessionUpdate": "tool_call_update",
- "toolCallId": "tc_abc123",
- "status": "completed",
- "content": [{ "type": "text", "text": "import express from 'express';\n..." }]
-}
-```
-
-
-
-The agent's execution plan for the current task.
-
-```json
-{
- "sessionUpdate": "plan",
- "entries": [
- { "content": "Read the project structure", "status": "completed" },
- { "content": "Identify main entrypoints", "status": "in_progress" },
- { "content": "Write summary", "status": "pending" }
- ]
-}
-```
-
-
-
-Token usage metrics for the current turn.
-
-```json
-{
- "sessionUpdate": "usage_update"
-}
-```
-
-
-
-Session metadata changed (e.g. agent-generated title).
-
-```json
-{
- "sessionUpdate": "session_info_update",
- "title": "Repository structure analysis"
-}
-```
-
-
-
## Fetch persisted event history
```ts
diff --git a/docs/deploy/foundry-self-hosting.mdx b/docs/deploy/foundry-self-hosting.mdx
new file mode 100644
index 0000000..8fd43ae
--- /dev/null
+++ b/docs/deploy/foundry-self-hosting.mdx
@@ -0,0 +1,155 @@
+---
+title: "Foundry Self-Hosting"
+description: "Environment, credentials, and deployment setup for Sandbox Agent Foundry auth, GitHub, and billing."
+---
+
+This guide documents the deployment contract for the Foundry product surface: app auth, GitHub onboarding, repository import, and billing.
+
+It also covers the local-development bootstrap that uses `.env.development` only when `NODE_ENV=development`.
+
+## Local Development
+
+For backend local development, the Foundry backend now supports a development-only dotenv bootstrap:
+
+- It loads `.env.development.local` and `.env.development`
+- It does this **only** when `NODE_ENV=development`
+- It does **not** load dotenv files in production
+
+The example file lives at [`/.env.development.example`](https://github.com/rivet-dev/sandbox-agent/blob/main/.env.development.example).
+
+To use it locally:
+
+```bash
+cp .env.development.example .env.development
+```
+
+Run the backend with:
+
+```bash
+just foundry-backend-start
+```
+
+That recipe sets `NODE_ENV=development`, which enables the dotenv loader.
+
+### Local Defaults
+
+These values can be safely defaulted for local development:
+
+- `APP_URL=http://localhost:4173`
+- `BETTER_AUTH_URL=http://localhost:7741`
+- `BETTER_AUTH_SECRET=sandbox-agent-foundry-development-only-change-me`
+- `GITHUB_REDIRECT_URI=http://localhost:7741/v1/auth/callback/github`
+
+These should be treated as development-only values.
+
+## Production Environment
+
+For production or self-hosting, set these as real environment variables in your deployment platform. Do not rely on dotenv file loading.
+
+### App/Auth
+
+| Variable | Required | Notes |
+|---|---:|---|
+| `APP_URL` | Yes | Public frontend origin |
+| `BETTER_AUTH_URL` | Yes | Public auth base URL |
+| `BETTER_AUTH_SECRET` | Yes | Strong random secret for auth/session signing |
+
+### GitHub OAuth
+
+| Variable | Required | Notes |
+|---|---:|---|
+| `GITHUB_CLIENT_ID` | Yes | GitHub OAuth app client id |
+| `GITHUB_CLIENT_SECRET` | Yes | GitHub OAuth app client secret |
+| `GITHUB_REDIRECT_URI` | Yes | GitHub OAuth callback URL |
+
+Use GitHub OAuth for:
+
+- user sign-in
+- user identity
+- org selection
+- access to the signed-in user’s GitHub context
+
+## GitHub App
+
+If your Foundry deployment uses GitHub App-backed organization install and repo import, also configure:
+
+| Variable | Required | Notes |
+|---|---:|---|
+| `GITHUB_APP_ID` | Yes | GitHub App id |
+| `GITHUB_APP_CLIENT_ID` | Yes | GitHub App client id |
+| `GITHUB_APP_CLIENT_SECRET` | Yes | GitHub App client secret |
+| `GITHUB_APP_PRIVATE_KEY` | Yes | PEM private key for installation auth |
+
+For `.env.development` and `.env.development.local`, store `GITHUB_APP_PRIVATE_KEY` as a quoted single-line value with `\n` escapes instead of raw multi-line PEM text.
+
+Recommended GitHub App permissions:
+
+- Repository `Metadata: Read`
+- Repository `Contents: Read & Write`
+- Repository `Pull requests: Read & Write`
+- Repository `Checks: Read`
+- Repository `Commit statuses: Read`
+
+Set the webhook URL to `https:///v1/webhooks/github` and generate a webhook secret. Store the secret as `GITHUB_WEBHOOK_SECRET`.
+
+This is required, not optional. Foundry depends on GitHub App webhook delivery for installation lifecycle changes, repo access changes, and ongoing repo / pull request sync. If the GitHub App is not installed for the workspace, or webhook delivery is misconfigured, Foundry will remain in an install / reconnect state and core GitHub-backed functionality will not work correctly.
+
+Recommended webhook subscriptions:
+
+- `installation`
+- `installation_repositories`
+- `pull_request`
+- `pull_request_review`
+- `pull_request_review_comment`
+- `push`
+- `create`
+- `delete`
+- `check_suite`
+- `check_run`
+- `status`
+
+Use the GitHub App for:
+
+- installation/reconnect state
+- org repo import
+- repository sync
+- PR creation and updates
+
+Use GitHub OAuth for:
+
+- who the user is
+- which orgs they can choose
+
+## Stripe
+
+For live billing, configure:
+
+| Variable | Required | Notes |
+|---|---:|---|
+| `STRIPE_SECRET_KEY` | Yes | Server-side Stripe secret key |
+| `STRIPE_PUBLISHABLE_KEY` | Yes | Client-side Stripe publishable key |
+| `STRIPE_WEBHOOK_SECRET` | Yes | Signing secret for billing webhooks |
+| `STRIPE_PRICE_TEAM` | Yes | Stripe price id for the Team plan checkout session |
+
+Stripe should own:
+
+- hosted checkout
+- billing portal
+- subscription status
+- invoice history
+- webhook-driven state sync
+
+## Mock Invariant
+
+Foundry’s mock client path should continue to work end to end even when the real auth/GitHub/Stripe path exists.
+
+That includes:
+
+- sign-in
+- org selection/import
+- settings
+- billing UI
+- workspace/task/session flow
+- seat accrual
+
+Use mock mode for deterministic UI review and local product development. Use the real env-backed path for integration and self-hosting.
diff --git a/docs/docs.json b/docs/docs.json
index dbcc407..8b0f858 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -1,6 +1,6 @@
{
"$schema": "https://mintlify.com/docs.json",
- "theme": "mint",
+ "theme": "willow",
"name": "Sandbox Agent SDK",
"appearance": {
"default": "dark",
@@ -25,6 +25,11 @@
},
"navbar": {
"links": [
+ {
+ "label": "Gigacode",
+ "icon": "terminal",
+ "href": "https://github.com/rivet-dev/sandbox-agent/tree/main/gigacode"
+ },
{
"label": "Discord",
"icon": "discord",
@@ -85,10 +90,13 @@
"group": "System",
"pages": ["file-system", "processes", "computer-use", "common-software"]
},
+ {
+ "group": "Orchestration",
+ "pages": ["orchestration-architecture", "session-persistence", "observability", "multiplayer", "security"]
+ },
{
"group": "Reference",
"pages": [
- "troubleshooting",
"architecture",
"cli",
"inspector",
@@ -121,10 +129,5 @@
}
]
},
- "__removed": [
- {
- "group": "Orchestration",
- "pages": ["orchestration-architecture", "session-persistence", "observability", "multiplayer", "security"]
- }
- ]
+ "styles": ["/theme.css"]
}
diff --git a/docs/gigacode.mdx b/docs/gigacode.mdx
new file mode 100644
index 0000000..ccc9e39
--- /dev/null
+++ b/docs/gigacode.mdx
@@ -0,0 +1,6 @@
+---
+title: Gigacode
+url: "https://github.com/rivet-dev/sandbox-agent/tree/main/gigacode"
+---
+
+
diff --git a/docs/pi-support-plan.md b/docs/pi-support-plan.md
new file mode 100644
index 0000000..5e207a5
--- /dev/null
+++ b/docs/pi-support-plan.md
@@ -0,0 +1,210 @@
+# Pi Agent Support Plan (pi-mono)
+
+## Implementation Status Update
+
+- Runtime selection now supports two internal modes:
+ - `PerSession` (default for unknown/non-allowlisted Pi capabilities)
+ - `Shared` (allowlist-only compatibility path)
+- Pi sessions now use per-session process isolation by default, enabling true concurrent Pi sessions in Inspector and API clients.
+- Shared Pi server code remains available and is used only when capability checks allow multiplexing.
+- Session termination for per-session Pi mode hard-kills the underlying Pi process and clears queued prompts/pending waiters.
+- In-session concurrent sends are serialized with an unbounded daemon-side FIFO queue per session.
+
+## Investigation Summary
+
+### Pi CLI modes and RPC protocol
+- Pi supports multiple modes including interactive, print/JSON output, RPC, and SDK usage. JSON mode outputs a stream of JSON events suitable for parsing, and RPC mode is intended for programmatic control over stdin/stdout.
+- RPC mode is started with `pi --mode rpc` and supports options like `--provider`, `--model`, `--no-session`, and `--session-dir`.
+- The RPC protocol is newline-delimited JSON over stdin/stdout:
+ - Commands are JSON objects written to stdin.
+ - Responses are JSON objects with `type: "response"` and optional `id`.
+ - Events are JSON objects without `id`.
+- `prompt` can include images using `ImageContent` (base64 or URL) alongside text.
+- JSON/print mode (`pi -p` or `pi --print --mode json`) produces JSONL for non-interactive parsing and can resume sessions with a token.
+
+### RPC commands
+RPC commands listed in `rpc.md` include:
+- `new_session`, `get_state`, `list_sessions`, `delete_session`, `rename_session`, `clear_session`
+- `prompt`, `queue_message`, `abort`, `get_queued_messages`
+
+### RPC event types
+RPC events listed in `rpc.md` include:
+- `agent_start`, `agent_end`
+- `turn_start`, `turn_end`
+- `message_start`, `message_update`, `message_end`
+- `tool_execution_start`, `tool_execution_update`, `tool_execution_end`
+- `auto_compaction`, `auto_retry`, `hook_error`
+
+`message_update` uses `assistantMessageEvent` deltas such as:
+- `start`, `text_start`, `text_delta`, `text_end`
+- `thinking_start`, `thinking_delta`, `thinking_end`
+- `toolcall_start`, `toolcall_delta`, `toolcall_end`
+- `toolcall_args_start`, `toolcall_args_delta`, `toolcall_args_end`
+- `done`, `error`
+
+`tool_execution_update` includes `partialResult`, which is described as accumulated output so far.
+
+### Schema source locations (pi-mono)
+RPC types are documented as living in:
+- `packages/ai/src/types.ts` (Model types)
+- `packages/agent/src/types.ts` (AgentResponse types)
+- `packages/coding-agent/src/core/messages.ts` (message types)
+- `packages/coding-agent/src/modes/rpc/rpc-types.ts` (RPC protocol types)
+
+### Distribution assets
+Pi releases provide platform-specific binaries such as:
+- `pi-darwin-arm64`, `pi-darwin-x64`
+- `pi-linux-arm64`, `pi-linux-x64`
+- `pi-win-x64.zip`
+
+## Integration Decisions
+- Follow the OpenCode pattern: a shared long-running process (stdio RPC) with session multiplexing.
+- Primary integration path is RPC streaming (`pi --mode rpc`).
+- JSON/print mode is a fallback only (diagnostics or non-interactive runs).
+- Create sessions via `new_session`; store the returned `sessionId` as `native_session_id`.
+- Use `get_state` as a re-sync path after server restarts.
+- Use `prompt` for send-message, with optional image content.
+- Convert Pi events into universal events; emit daemon synthetic `session.started` on session creation and `session.ended` only on errors/termination.
+
+## Implementation Plan
+
+### 1) Agent Identity + Capabilities
+Files:
+- `server/packages/agent-management/src/agents.rs`
+- `server/packages/sandbox-agent/src/router.rs`
+- `docs/cli.mdx`, `docs/conversion.mdx`, `docs/session-transcript-schema.mdx`
+- `README.md`, `frontend/packages/website/src/components/FAQ.tsx`
+
+Tasks:
+- Add `AgentId::Pi` with string/binary name `"pi"` and parsing rules.
+- Add Pi to `all_agents()` and agent lists.
+- Define `AgentCapabilities` for Pi:
+ - `tool_calls=true`, `tool_results=true`
+ - `text_messages=true`, `streaming_deltas=true`, `item_started=true`
+ - `reasoning=true` (from `thinking_*` deltas)
+ - `images=true` (ImageContent in `prompt`)
+ - `permissions=false`, `questions=false`, `mcp_tools=false`
+ - `shared_process=true`, `session_lifecycle=false` (no native session events)
+ - `error_events=true` (hook_error)
+ - `command_execution=false`, `file_changes=false`, `file_attachments=false`
+
+### 2) Installer and Binary Resolution
+Files:
+- `server/packages/agent-management/src/agents.rs`
+
+Tasks:
+- Add `install_pi()` that:
+ - Downloads the correct release asset per platform (`pi-`).
+ - Handles `.zip` on Windows and raw binaries elsewhere.
+ - Marks binary executable.
+- Add Pi to `AgentManager::install`, `is_installed`, `version`.
+- Version detection: try `--version`, `version`, `-V`.
+
+### 3) Schema Extraction for Pi
+Files:
+- `resources/agent-schemas/src/pi.ts` (new)
+- `resources/agent-schemas/src/index.ts`
+- `resources/agent-schemas/artifacts/json-schema/pi.json`
+- `server/packages/extracted-agent-schemas/build.rs`
+- `server/packages/extracted-agent-schemas/src/lib.rs`
+
+Tasks:
+- Implement `extractPiSchema()`:
+ - Download pi-mono sources (zip/tarball) into a temp dir.
+ - Use `ts-json-schema-generator` against `packages/coding-agent/src/modes/rpc/rpc-types.ts`.
+ - Include dependent files per `rpc.md` (ai/types, agent/types, core/messages).
+ - Extract `RpcEvent`, `RpcResponse`, `RpcCommand` unions (exact type names from source).
+- Add fallback schema if remote fetch fails (minimal union with event/response fields).
+- Wire pi into extractor index and artifact generation.
+
+### 4) Universal Schema Conversion (Pi -> Universal)
+Files:
+- `server/packages/universal-agent-schema/src/agents/pi.rs` (new)
+- `server/packages/universal-agent-schema/src/agents/mod.rs`
+- `server/packages/universal-agent-schema/src/lib.rs`
+- `server/packages/sandbox-agent/src/router.rs`
+
+Mapping rules:
+- `message_start` -> `item.started` (kind=message, role=assistant, native_item_id=messageId)
+- `message_update`:
+ - `text_*` -> `item.delta` (assistant text delta)
+ - `thinking_*` -> `item.delta` with `ContentPart::Reasoning` (visibility=Private)
+ - `toolcall_*` and `toolcall_args_*` -> ignore for now (tool_execution_* is authoritative)
+ - `error` -> `item.completed` with `ItemStatus::Failed` (if no later message_end)
+- `message_end` -> `item.completed` (finalize assistant message)
+- `tool_execution_start` -> `item.started` (kind=tool_call, ContentPart::ToolCall)
+- `tool_execution_update` -> `item.delta` for a synthetic tool_result item:
+ - Maintain a per-toolCallId buffer to compute delta from accumulated `partialResult`.
+- `tool_execution_end` -> `item.completed` (kind=tool_result, output from `result.content`)
+ - If `isError=true`, set item status to failed.
+- `agent_start`, `turn_start`, `turn_end`, `agent_end`, `auto_compaction`, `auto_retry`, `hook_error`:
+ - Map to `ItemKind::Status` with a label like `pi.agent_start`, `pi.auto_retry`, etc.
+ - Do not emit `session.ended` for these events.
+- If event parsing fails, emit `agent.unparsed` (source=daemon, synthetic=true) and fail tests.
+
+### 5) Shared RPC Server Integration
+Files:
+- `server/packages/sandbox-agent/src/router.rs`
+
+Tasks:
+- Add a new managed stdio server type for Pi, similar to Codex:
+ - Create `PiServer` struct with:
+ - stdin sender
+ - pending request map keyed by request id
+ - per-session native session id mapping
+ - Extend `ManagedServerKind` to include Pi.
+ - Add `ensure_pi_server()` and `spawn_pi_server()` using `pi --mode rpc`.
+ - Add a `handle_pi_server_output()` loop to parse stdout lines into events/responses.
+- Session creation:
+ - On `create_session`, ensure Pi server is running, send `new_session`, store sessionId.
+ - Register session with `server_manager.register_session` for native mapping.
+- Sending messages:
+ - Use `prompt` command; include sessionId and optional images.
+ - Emit synthetic `item.started` only if Pi does not emit `message_start`.
+
+### 6) Router + Streaming Path Changes
+Files:
+- `server/packages/sandbox-agent/src/router.rs`
+
+Tasks:
+- Add Pi handling to:
+ - `create_session` (new_session)
+ - `send_message` (prompt)
+ - `parse_agent_line` (Pi event conversion)
+ - `agent_modes` (default to `default` unless Pi exposes a mode list)
+ - `agent_supports_resume` (true if Pi supports session resume)
+
+### 7) Tests
+Files:
+- `server/packages/sandbox-agent/tests/...`
+- `server/packages/universal-agent-schema/tests/...` (if present)
+
+Tasks:
+- Unit tests for conversion:
+ - `message_start/update/end` -> item.started/delta/completed
+ - `tool_execution_*` -> tool call/result mapping with partialResult delta
+ - failure -> agent.unparsed
+- Integration tests:
+ - Start Pi RPC server, create session, send prompt, stream events.
+ - Validate `native_session_id` mapping and event ordering.
+- Update HTTP/SSE test coverage to include Pi agent if relevant.
+
+## Risk Areas / Edge Cases
+- `tool_execution_update.partialResult` is cumulative; must compute deltas.
+- `message_update` may emit `done`/`error` without `message_end`; handle both paths.
+- No native session lifecycle events; rely on daemon synthetic events.
+- Session recovery after RPC server restart requires `get_state` + re-register sessions.
+
+## Acceptance Criteria
+- Pi appears in `/v1/agents`, CLI list, and docs.
+- `create_session` returns `native_session_id` from Pi `new_session`.
+- Streaming prompt yields universal events with proper ordering:
+ - message -> item.started/delta/completed
+ - tool execution -> tool call + tool result
+- Tests pass and no synthetic data is used in test fixtures.
+
+## Sources
+- https://upd.dev/badlogic/pi-mono/src/commit/d36e0ea07303d8a76d51b4a7bd5f0d6d3c490860/packages/coding-agent/docs/rpc.md
+- https://buildwithpi.ai/pi-cli
+- https://takopi.dev/docs/pi-cli/
+- https://upd.dev/badlogic/pi-mono/releases
diff --git a/docs/session-transcript-schema.mdx b/docs/session-transcript-schema.mdx
new file mode 100644
index 0000000..c9c004a
--- /dev/null
+++ b/docs/session-transcript-schema.mdx
@@ -0,0 +1,388 @@
+---
+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.
+
+
+
+ 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`.
+
+
+ Model variants such as reasoning effort or depth. Agents may expose different variant sets per model.
+
+
+
+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
+```
diff --git a/docs/troubleshooting.mdx b/docs/troubleshooting.mdx
index 18186d6..838cc28 100644
--- a/docs/troubleshooting.mdx
+++ b/docs/troubleshooting.mdx
@@ -29,6 +29,25 @@ Verify the agent is installed:
ls -la ~/.local/share/sandbox-agent/bin/
```
+### 4. Binary libc mismatch (musl vs glibc)
+
+Claude Code binaries are available in both musl and glibc variants. If you see errors like:
+
+```
+cannot execute: required file not found
+Error loading shared library libstdc++.so.6: No such file or directory
+```
+
+This means the wrong binary variant was downloaded.
+
+**For sandbox-agent 0.2.0+**: Platform detection is automatic. The correct binary (musl or glibc) is downloaded based on the runtime environment.
+
+**For sandbox-agent 0.1.x**: Use Alpine Linux which has native musl support:
+
+```dockerfile
+FROM alpine:latest
+RUN apk add --no-cache curl ca-certificates libstdc++ libgcc bash
+```
## Daytona Network Restrictions