diff --git a/.github/media/inspector.png b/.github/media/inspector.png
new file mode 100644
index 0000000..1c16ed2
Binary files /dev/null and b/.github/media/inspector.png differ
diff --git a/CLAUDE.md b/CLAUDE.md
index 6cf9786..5d4edcd 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -30,6 +30,7 @@ Universal schema guidance:
- Keep CLI subcommands in sync with every HTTP endpoint.
- Update `CLAUDE.md` to keep CLI endpoints in sync with HTTP API changes.
+- When adding or modifying CLI commands, update `docs/cli.mdx` to reflect the changes.
- When changing the HTTP API, update the TypeScript SDK and CLI together.
- Do not make breaking changes to API endpoints.
- When changing API routes, ensure the HTTP/SSE test suite has full coverage of every route.
@@ -39,10 +40,10 @@ Universal schema guidance:
- Never use synthetic data or mocked responses in tests.
- Never manually write agent types; always use generated types in `resources/agent-schemas/`. If types are broken, fix the generated types.
- The universal schema must provide consistent behavior across providers; avoid requiring frontend/client logic to special-case agents.
-- The UI must reflect every field in AgentCapabilities; keep it in sync with the README feature matrix and `agent_capabilities_for`.
+- The UI must reflect every field in AgentCapabilities; keep it in sync with the README feature matrix, `docs/agent-compatibility.mdx`, and `agent_capabilities_for`.
- When parsing agent data, if something is unexpected or does not match the schema, bail out and surface the error rather than trying to continue with partial parsing.
- When defining the universal schema, choose the option most compatible with native agent APIs, and add synthetics to fill gaps for other agents.
-- Use `docs/glossary.md` as the source of truth for universal schema terminology and keep it updated alongside schema changes.
+- Use `docs/universal-schema.mdx` as the source of truth for universal schema terminology and keep it updated alongside schema changes.
- On parse failures, emit an `agent.unparsed` event (source=daemon, synthetic=true) and treat it as a test failure. Preserve raw payloads when `include_raw=true`.
- Track subagent support in `docs/conversion.md`. For now, normalize subagent activity into normal message/tool flow, but revisit explicit subagent modeling later.
- Keep the FAQ in `README.md` and `frontend/packages/website/src/components/FAQ.tsx` in sync. When adding or modifying FAQ entries, update both files.
@@ -56,6 +57,7 @@ Universal schema guidance:
- `sandbox-agent api sessions create` ↔ `POST /v1/sessions/{sessionId}`
- `sandbox-agent api sessions send-message` ↔ `POST /v1/sessions/{sessionId}/messages`
- `sandbox-agent api sessions send-message-stream` ↔ `POST /v1/sessions/{sessionId}/messages/stream`
+- `sandbox-agent api sessions terminate` ↔ `POST /v1/sessions/{sessionId}/terminate`
- `sandbox-agent api sessions events` / `get-messages` ↔ `GET /v1/sessions/{sessionId}/events`
- `sandbox-agent api sessions events-sse` ↔ `GET /v1/sessions/{sessionId}/events/sse`
- `sandbox-agent api sessions reply-question` ↔ `POST /v1/sessions/{sessionId}/questions/{questionId}/reply`
diff --git a/README.md b/README.md
index 24ca32b..9ed0393 100644
--- a/README.md
+++ b/README.md
@@ -9,11 +9,11 @@
- **Any coding agent**: Universal API to interact with all agents with full feature coverage
- **Server or SDK mode**: Run as an HTTP server or with the TypeScript SDK
-- **Universal session schema**: Universal schema to store agent transcripts
+- **Universal session schema**: [Universal schema](https://sandboxagent.dev/docs/universal-schema) to store agent transcripts
- **Supports your sandbox provider**: Daytona, E2B, Vercel Sandboxes, and more
- **Lightweight, portable Rust binary**: Install anywhere with 1 curl command
- **Automatic agent installation**: Agents are installed on-demand when first used
-- **OpenAPI spec**: https://sandboxagent.dev/docs/api
+- **OpenAPI spec**: Well documented and easy to integrate
[Documentation](https://sandboxagent.dev/docs) — [Discord](https://rivet.dev/discord)
@@ -37,7 +37,7 @@
| MCP Tools | | ✓ | | |
| Streaming Deltas | | ✓ | ✓ | |
-* Claude headless CLI does not natively support tool calls/results or HITL questions/permissions yet; these are WIP.
+\* Coming imminently
Want support for another agent? [Open an issue](https://github.com/anthropics/sandbox-agent/issues/new) to request it.
@@ -116,7 +116,7 @@ for await (const event of client.streamEvents("demo", { offset: 0 })) {
}
```
-[Documentation](https://sandboxagent.dev/docs/sdks/typescript)
+[Documentation](https://sandboxagent.dev/docs/sdks/typescript) — [Building a Chat UI](https://sandboxagent.dev/docs/building-chat-ui) — [Managing Sessions](https://sandboxagent.dev/docs/manage-sessions)
### Server
@@ -144,8 +144,7 @@ To disable auth locally:
sandbox-agent server --no-token --host 127.0.0.1 --port 2468
```
-[Documentation](https://sandboxagent.dev/docs/quickstart)
-[Integration guides](https://sandboxagent.dev/docs/deployments)
+[Documentation](https://sandboxagent.dev/docs/quickstart) - [Integration guides](https://sandboxagent.dev/docs/deploy)
### CLI
@@ -169,7 +168,23 @@ You can also use npx like:
npx sandbox-agent --help
```
-[Documentation](https://rivet.dev/docs/cli)
+[Documentation](https://sandboxagent.dev/docs/cli)
+
+### Inspector
+
+Debug sessions and events with the [Inspector UI](https://inspect.sandboxagent.dev).
+
+
+
+[Documentation](https://sandboxagent.dev/docs/inspector)
+
+### OpenAPI Specification
+
+[Explore API](https://sandboxagent.dev/docs/api-reference) — [View Specification](https://github.com/rivet-dev/sandbox-agent/blob/main/docs/openapi.json)
+
+### Universal Schema
+
+All events follow a [universal schema](https://sandboxagent.dev/docs/universal-schema) that normalizes differences between agents.
### Tip: Extract credentials
@@ -183,33 +198,47 @@ This prints environment variables for your OpenAI/Anthropic/etc API keys to test
## FAQ
-**Does this replace the Vercel AI SDK?**
+
+Does this replace the Vercel AI SDK?
No, they're complementary. AI SDK is for building chat interfaces and calling LLMs. This SDK is for controlling autonomous coding agents that write code and run commands. Use AI SDK for your UI, use this when you need an agent to actually code.
+
-**Which coding agents are supported?**
+
+Which coding agents are supported?
Claude Code, Codex, OpenCode, and Amp. The SDK normalizes their APIs so you can swap between them without changing your code.
+
-**How is session data persisted?**
+
+How is session data persisted?
-This SDK does not handle persisting session data. Events stream in a universal JSON schema that you can persist anywhere. Consider using Postgres or [Rivet Actors](https://rivet.gg) for data persistence.
+This SDK does not handle persisting session data. Events stream in a universal JSON schema that you can persist anywhere. See [Managing Sessions](https://sandboxagent.dev/docs/manage-sessions) for patterns using Postgres or [Rivet Actors](https://rivet.gg).
+
-**Can I run this locally or does it require a sandbox provider?**
+
+Can I run this locally or does it require a sandbox provider?
Both. Run locally for development, deploy to E2B, Daytona, or Vercel Sandboxes for production.
+
-**Does it support [platform]?**
+
+Does it support [platform]?
The server is a single Rust binary that runs anywhere with a curl install. If your platform can run Linux binaries (Docker, VMs, etc.), it works. See the deployment guides for E2B, Daytona, and Vercel Sandboxes.
+
-**Can I use this with my personal API keys?**
+
+Can I use this with my personal API keys?
Yes. Use `sandbox-agent credentials extract-env` to extract API keys from your local agent configs (Claude Code, Codex, OpenCode, Amp) and pass them to the sandbox environment.
+
-**Why Rust and not [language]?**
+
+Why Rust and not [language]?
Rust gives us a single static binary, fast startup, and predictable memory usage. That makes it easy to run inside sandboxes or in CI without shipping a large runtime, such as Node.js.
+
## Project Goals
diff --git a/docs/agent-compatibility.mdx b/docs/agent-compatibility.mdx
index 2c87c07..3f38bd8 100644
--- a/docs/agent-compatibility.mdx
+++ b/docs/agent-compatibility.mdx
@@ -1,28 +1,96 @@
---
title: "Agent Compatibility"
-description: "Supported agents, install methods, and streaming formats."
+description: "Feature support across coding agents."
+icon: "table"
---
-## Compatibility matrix
+The universal API normalizes different coding agents into a consistent interface. Each agent has different native capabilities; the daemon fills gaps with synthetic events where possible.
-| Agent | Provider | Binary | Install method | Session ID | Streaming format |
-|-------|----------|--------|----------------|------------|------------------|
-| Claude Code | Anthropic | `claude` | curl raw binary from GCS | `session_id` | JSONL via stdout |
-| Codex | OpenAI | `codex` | curl tarball from GitHub releases | `thread_id` | JSON-RPC over stdio |
-| OpenCode | Multi-provider | `opencode` | curl tarball from GitHub releases | `session_id` | SSE or JSONL |
-| Amp | Sourcegraph | `amp` | curl raw binary from GCS | `session_id` | JSONL via stdout |
-| Mock | Built-in | — | bundled | `mock-*` | daemon-generated |
+## Feature Matrix
-## Agent modes
+| Feature | [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) |
+|---------|:-----------:|:-----:|:--------:|:---:|
+| Stability | Stable | Stable | 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 | | ✓ | ✓ | |
-- **OpenCode**: discovered via the server API.
-- **Claude Code / Codex / Amp**: hardcoded modes (typically `build`, `plan`, or `custom`).
+\* Coming imminently
-## Capability notes
+## Feature Descriptions
-- **Questions / permissions**: OpenCode natively supports these workflows. Claude plan approval is normalized into a question event (tests do not currently exercise Claude question/permission flows).
-- **Streaming**: all agents stream events; OpenCode uses SSE, Codex uses JSON-RPC over stdio, others use JSONL. Codex is currently normalized to thread/turn starts plus user/assistant completed items (deltas and tool/reasoning items are not emitted yet).
-- **User messages**: Claude CLI output does not include explicit user-message events in our snapshots, so only assistant messages are surfaced for Claude today.
-- **Files and images**: normalized via `UniversalMessagePart` with `File` and `Image` parts.
+### Text Messages
-See [Universal API](/universal-api) for feature coverage details.
+Basic message exchange between user and assistant.
+
+### 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.
+
+### Questions (HITL)
+
+Interactive questions the agent asks the user. Emits `question.requested` and `question.resolved` events.
+
+### Permissions (HITL)
+
+Permission requests for sensitive operations. Emits `permission.requested` and `permission.resolved` events.
+
+### Images
+
+Support for image attachments in messages.
+
+### File Attachments
+
+Support for file attachments in messages.
+
+### Session Lifecycle
+
+Native `session.started` and `session.ended` events. When not supported, the daemon emits synthetic lifecycle events.
+
+### Error Events
+
+Structured error events for runtime failures.
+
+### Reasoning/Thinking
+
+Extended thinking or reasoning content with visibility controls.
+
+### Command Execution
+
+Detailed command execution events with stdout/stderr.
+
+### File Changes
+
+Structured file modification events with diffs.
+
+### MCP Tools
+
+Model Context Protocol tool support.
+
+### Streaming Deltas
+
+Native streaming of content deltas. When not supported, the daemon emits a single synthetic delta before `item.completed`.
+
+## Synthetic Events
+
+For features not natively supported, the daemon generates synthetic events to maintain a consistent event stream. Synthetic events have:
+
+- `source: "daemon"`
+- `synthetic: true`
+
+This lets you build UIs that work with any agent without special-casing each provider.
+
+## Request Support
+
+Want support for another agent? [Open an issue](https://github.com/rivet-dev/sandbox-agent/issues/new) to request it.
diff --git a/docs/building-chat-ui.mdx b/docs/building-chat-ui.mdx
index 240b8a4..0f3b559 100644
--- a/docs/building-chat-ui.mdx
+++ b/docs/building-chat-ui.mdx
@@ -1,57 +1,76 @@
---
title: "Building a Chat UI"
-description: "Design a client that renders universal session events consistently across providers."
+description: "Build a chat interface using the universal event stream."
+icon: "comments"
---
-This guide explains how to build a chat UI that works across all agents using the universal event
-stream.
+## Setup
-## High-level flow
+### List agents
-1. List agents and read their capabilities.
-2. Create a session for the selected agent.
-3. Send user messages.
-4. Subscribe to events (polling or SSE).
-5. Render items and deltas into a stable message timeline.
+```ts
+const { agents } = await client.listAgents();
-## Use agent capabilities
+// Each agent has capabilities that determine what UI to show
+const claude = agents.find((a) => a.id === "claude");
+if (claude?.capabilities.permissions) {
+ // Show permission approval UI
+}
+if (claude?.capabilities.questions) {
+ // Show question response UI
+}
+```
-Capabilities tell you which features are supported for the selected agent:
+### Create a session
-- `tool_calls` and `tool_results` indicate tool execution events.
-- `questions` and `permissions` indicate HITL flows.
-- `plan_mode` indicates that the agent supports plan-only execution.
-- `reasoning` and `status` indicate that the agent can emit reasoning/status content parts.
-- `item_started` indicates that the agent emits `item.started` on its own; when false the daemon will emit a synthetic `item.started` immediately after sending a user message.
+```ts
+const sessionId = `session-${crypto.randomUUID()}`;
-Use these to enable or disable UI affordances (tool panels, approval buttons, etc.).
+await client.createSession(sessionId, {
+ agent: "claude",
+ agentMode: "code", // Optional: agent-specific mode
+ permissionMode: "default", // Optional: "default" | "plan" | "bypass"
+ model: "claude-sonnet-4", // Optional: model override
+});
+```
-## Event model
+### Send a message
-Every event includes:
+```ts
+await client.postMessage(sessionId, { message: "Hello, world!" });
+```
-- `event_id`, `sequence`, and `time` for ordering.
-- `session_id` for the universal session.
-- `native_session_id` for provider-specific debugging.
-- `type` with one of:
- - `session.started`, `session.ended`
- - `item.started`, `item.delta`, `item.completed`
- - `permission.requested`, `permission.resolved`
- - `question.requested`, `question.resolved`
- - `error`, `agent.unparsed`
-- `data` which holds the payload for the event type.
-- `synthetic` and `source` to show daemon-generated events.
-- `raw` (optional) when `include_raw=true`.
+### Stream events
-## Rendering items
+Three options for receiving events:
-Items are emitted in three phases:
+```ts
+// Option 1: SSE (recommended for real-time UI)
+const stream = client.streamEvents(sessionId, { offset: 0 });
+for await (const event of stream) {
+ handleEvent(event);
+}
-- `item.started`: first snapshot of a message or tool item.
-- `item.delta`: incremental updates (token streaming or synthetic deltas).
-- `item.completed`: final snapshot.
+// Option 2: Polling
+const { events, hasMore } = await client.getEvents(sessionId, { offset: 0 });
+events.forEach(handleEvent);
-Recommended render flow:
+// Option 3: Turn streaming (send + stream in one call)
+const stream = client.streamTurn(sessionId, { message: "Hello" });
+for await (const event of stream) {
+ handleEvent(event);
+}
+```
+
+Use `offset` to track the last seen `sequence` number and resume from where you left off.
+
+---
+
+## Handling Events
+
+### Bare minimum
+
+Handle these three events to render a basic chat:
```ts
type ItemState = {
@@ -60,108 +79,278 @@ type ItemState = {
};
const items = new Map();
-const order: string[] = [];
-function applyEvent(event: UniversalEvent) {
- if (event.type === "item.started") {
- const item = event.data.item;
- items.set(item.item_id, { item, deltas: [] });
- order.push(item.item_id);
- }
-
- if (event.type === "item.delta") {
- const { item_id, delta } = event.data;
- const state = items.get(item_id);
- if (state) {
- state.deltas.push(delta);
+function handleEvent(event: UniversalEvent) {
+ switch (event.type) {
+ case "item.started": {
+ const { item } = event.data as ItemEventData;
+ items.set(item.item_id, { item, deltas: [] });
+ break;
}
- }
- if (event.type === "item.completed") {
- const item = event.data.item;
- const state = items.get(item.item_id);
- if (state) {
- state.item = item;
+ case "item.delta": {
+ const { item_id, delta } = event.data as ItemDeltaData;
+ const state = items.get(item_id);
+ if (state) {
+ state.deltas.push(delta);
+ }
+ break;
+ }
+
+ case "item.completed": {
+ const { item } = event.data as ItemEventData;
+ const state = items.get(item.item_id);
+ if (state) {
+ state.item = item;
+ state.deltas = []; // Clear deltas, use final content
+ }
+ break;
}
}
}
```
-When rendering, combine the item content with accumulated deltas. If you receive a delta before a
-started event (should not happen), treat it as an error.
+When rendering, show a loading indicator while `item.status === "in_progress"`:
-## Content parts
+```ts
+function renderItem(state: ItemState) {
+ const { item, deltas } = state;
+ const isLoading = item.status === "in_progress";
-Each `UniversalItem` has `content` parts. Your UI can branch on `part.type`:
+ // For streaming text, combine item content with accumulated deltas
+ const text = item.content
+ .filter((p) => p.type === "text")
+ .map((p) => p.text)
+ .join("");
+ const streamedText = text + deltas.join("");
-- `text` for normal chat text.
-- `tool_call` and `tool_result` for tool execution.
-- `file_ref` for file read/write/patch previews.
-- `reasoning` if you display public reasoning text.
-- `status` for progress updates.
-- `image` for image outputs.
-
-Treat `item.kind` as the primary layout decision (message vs tool call vs system), and use content
-parts for the detailed rendering.
-
-## Questions and permissions
-
-Question and permission events are out-of-band from item flow. Render them as modal or inline UI
-blocks that must be resolved via:
-
-- `POST /v1/sessions/{session_id}/questions/{question_id}/reply`
-- `POST /v1/sessions/{session_id}/questions/{question_id}/reject`
-- `POST /v1/sessions/{session_id}/permissions/{permission_id}/reply`
-
-If an agent does not advertise these capabilities, keep those UI controls hidden.
-
-## Error and unparsed events
-
-- `error` events are structured failures from the daemon or agent.
-- `agent.unparsed` indicates the provider emitted something the converter could not parse.
-
-Treat `agent.unparsed` as a hard failure in development so you can fix converters quickly.
-
-## Event ordering
-
-Prefer `sequence` for ordering. It is monotonic for a given session. The `time` field is for
-timestamps, not ordering.
-
-## Handling session end
-
-`session.ended` includes the reason and who terminated it. Disable input after a terminal event.
-
-## Optional raw payloads
-
-If you need provider-level debugging, pass `include_raw=true` when streaming or polling events
-(including one-turn streams) to receive the `raw` payload for each event.
-
-## SSE vs polling vs turn streaming
-
-- SSE gives low-latency updates and simplifies streaming UIs.
-- Polling is simpler to debug and works in any environment.
-- Turn streaming (`POST /v1/sessions/{session_id}/messages/stream`) is a one-shot stream tied to a
- single prompt. The stream closes automatically once the turn completes.
-
-Both yield the same event payloads.
-
-## Mock agent for UI testing
-
-Use the built-in `mock` agent to exercise UI behaviors without external credentials:
-
-```bash
-curl -X POST http://127.0.0.1:2468/v1/sessions/demo-session \
- -H "content-type: application/json" \
- -d '{"agent":"mock"}'
+ return {
+ content: streamedText,
+ isLoading,
+ role: item.role,
+ kind: item.kind,
+ };
+}
```
-The mock agent sends a prompt telling you what commands it accepts. Send messages like `demo`,
-`markdown`, or `permission` to emit specific event sequences. Any other text is echoed back as an
-assistant message so you can test rendering, streaming, and approval flows on demand.
+### Extra events
-## Reference implementation
+Handle these for a complete implementation:
-The [Inspector chat UI](https://github.com/rivet-dev/sandbox-agent/blob/main/frontend/packages/inspector/src/App.tsx)
-is a complete reference implementation showing how to build a chat interface using the universal event
-stream. It demonstrates session management, event rendering, item lifecycle handling, and HITL approval
-flows.
+```ts
+function handleEvent(event: UniversalEvent) {
+ switch (event.type) {
+ // ... bare minimum events above ...
+
+ case "session.started": {
+ // Session is ready
+ break;
+ }
+
+ case "session.ended": {
+ const { reason, terminated_by } = event.data as SessionEndedData;
+ // Disable input, show end reason
+ // reason: "completed" | "error" | "terminated"
+ // terminated_by: "agent" | "daemon"
+ break;
+ }
+
+ case "error": {
+ const { message, code } = event.data as ErrorData;
+ // Display error to user
+ break;
+ }
+
+ case "agent.unparsed": {
+ const { error, location } = event.data as AgentUnparsedData;
+ // Parsing failure - treat as bug in development
+ console.error(`Parse error at ${location}: ${error}`);
+ break;
+ }
+ }
+}
+```
+
+### Content parts
+
+Each item has `content` parts. Render based on `type`:
+
+```ts
+function renderContentPart(part: ContentPart) {
+ switch (part.type) {
+ case "text":
+ return {part.text};
+
+ case "tool_call":
+ return ;
+
+ case "tool_result":
+ return ;
+
+ case "file_ref":
+ return ;
+
+ case "reasoning":
+ return {part.text};
+
+ case "status":
+ return ;
+
+ case "image":
+ return ;
+ }
+}
+```
+
+---
+
+## Handling Permissions
+
+When `permission.requested` arrives, show an approval UI:
+
+```ts
+const pendingPermissions = new Map();
+
+function handleEvent(event: UniversalEvent) {
+ if (event.type === "permission.requested") {
+ const data = event.data as PermissionEventData;
+ pendingPermissions.set(data.permission_id, data);
+ }
+
+ if (event.type === "permission.resolved") {
+ const data = event.data as PermissionEventData;
+ pendingPermissions.delete(data.permission_id);
+ }
+}
+
+// User clicks approve/deny
+async function replyPermission(id: string, reply: "once" | "always" | "reject") {
+ await client.replyPermission(sessionId, id, { reply });
+ pendingPermissions.delete(id);
+}
+```
+
+Render permission requests:
+
+```ts
+function PermissionRequest({ data }: { data: PermissionEventData }) {
+ return (
+
+
Allow: {data.action}
+
+
+
+
+ );
+}
+```
+
+---
+
+## Handling Questions
+
+When `question.requested` arrives, show a selection UI:
+
+```ts
+const pendingQuestions = new Map();
+
+function handleEvent(event: UniversalEvent) {
+ if (event.type === "question.requested") {
+ const data = event.data as QuestionEventData;
+ pendingQuestions.set(data.question_id, data);
+ }
+
+ if (event.type === "question.resolved") {
+ const data = event.data as QuestionEventData;
+ pendingQuestions.delete(data.question_id);
+ }
+}
+
+// User selects answer(s)
+async function answerQuestion(id: string, answers: string[][]) {
+ await client.replyQuestion(sessionId, id, { answers });
+ pendingQuestions.delete(id);
+}
+
+async function rejectQuestion(id: string) {
+ await client.rejectQuestion(sessionId, id);
+ pendingQuestions.delete(id);
+}
+```
+
+Render question requests:
+
+```ts
+function QuestionRequest({ data }: { data: QuestionEventData }) {
+ const [selected, setSelected] = useState([]);
+
+ return (
+
+
{data.prompt}
+ {data.options.map((option) => (
+
+ ))}
+
+
+
+ );
+}
+```
+
+---
+
+## Testing with Mock Agent
+
+The `mock` agent lets you test UI behaviors without external credentials:
+
+```ts
+await client.createSession("test-session", { agent: "mock" });
+```
+
+Send `help` to see available commands:
+
+| Command | Tests |
+|---------|-------|
+| `help` | Lists all commands |
+| `demo` | Full UI coverage sequence with markers |
+| `markdown` | Streaming markdown rendering |
+| `tool` | Tool call + result with file refs |
+| `status` | Status item updates |
+| `image` | Image content part |
+| `permission` | Permission request flow |
+| `question` | Question request flow |
+| `error` | Error + unparsed events |
+| `end` | Session ended event |
+| `echo ` | Echo text as assistant message |
+
+Any unrecognized text is echoed back as an assistant message.
+
+---
+
+## Reference Implementation
+
+The [Inspector UI](https://github.com/rivet-dev/sandbox-agent/blob/main/frontend/packages/inspector/src/App.tsx)
+is a complete reference showing session management, event rendering, and HITL flows.
diff --git a/docs/cli.mdx b/docs/cli.mdx
index 16cea87..39508c4 100644
--- a/docs/cli.mdx
+++ b/docs/cli.mdx
@@ -1,140 +1,310 @@
---
-title: "CLI"
-description: "CLI reference and server flags."
+title: "CLI Reference"
+description: "Complete CLI reference for sandbox-agent."
+sidebarTitle: "CLI"
+icon: "terminal"
---
-The `sandbox-agent api` subcommand mirrors the HTTP API so you can script everything without writing client code.
+## Server
-## Server flags
+Start the HTTP server:
```bash
-sandbox-agent server --token "$SANDBOX_TOKEN" --host 127.0.0.1 --port 2468
+sandbox-agent server [OPTIONS]
```
-- `--token`: global token for all requests.
-- `--no-token`: disable auth (local dev only).
-- `--host`, `--port`: bind address.
-- `--cors-allow-origin`, `--cors-allow-method`, `--cors-allow-header`, `--cors-allow-credentials`: configure CORS.
-- `--no-telemetry`: disable anonymous telemetry.
+| Option | Default | Description |
+|--------|---------|-------------|
+| `-t, --token ` | - | Authentication token for all requests |
+| `-n, --no-token` | - | Disable authentication (local dev only) |
+| `-H, --host ` | `127.0.0.1` | Host to bind to |
+| `-p, --port ` | `2468` | Port to bind to |
+| `-O, --cors-allow-origin ` | - | CORS allowed origin (repeatable) |
+| `-M, --cors-allow-method ` | - | CORS allowed method (repeatable) |
+| `-A, --cors-allow-header ` | - | CORS allowed header (repeatable) |
+| `-C, --cors-allow-credentials` | - | Enable CORS credentials |
+| `--no-telemetry` | - | Disable anonymous telemetry |
-## Install agent (no server required)
+```bash
+sandbox-agent server --token "$TOKEN" --port 3000
+```
-
-install-agent
+---
+
+## Install Agent (Local)
+
+Install an agent without running the server:
+
+```bash
+sandbox-agent install-agent [OPTIONS]
+```
+
+| Option | Description |
+|--------|-------------|
+| `-r, --reinstall` | Force reinstall even if already installed |
```bash
sandbox-agent install-agent claude --reinstall
```
-
-## API agent commands
+---
-
-api agents list
+## Credentials
+
+### Extract
+
+Extract locally discovered credentials:
```bash
-sandbox-agent api agents list --endpoint http://127.0.0.1:2468
+sandbox-agent credentials extract [OPTIONS]
```
-
-
-api agents install
+| Option | Description |
+|--------|-------------|
+| `-a, --agent ` | Filter by agent (`claude`, `codex`, `opencode`, `amp`) |
+| `-p, --provider ` | Filter by provider (`anthropic`, `openai`) |
+| `-d, --home-dir ` | Custom home directory for credential search |
+| `-r, --reveal` | Show full credential values (default: redacted) |
+| `--no-oauth` | Exclude OAuth credentials |
```bash
-sandbox-agent api agents install claude --reinstall --endpoint http://127.0.0.1:2468
+sandbox-agent credentials extract --agent claude --reveal
+sandbox-agent credentials extract --provider anthropic
```
-
-
-api agents modes
+### Extract as Environment Variables
+
+Output credentials as shell environment variables:
```bash
-sandbox-agent api agents modes claude --endpoint http://127.0.0.1:2468
+sandbox-agent credentials extract-env [OPTIONS]
```
-
-## API session commands
-
-
-api sessions list
+| Option | Description |
+|--------|-------------|
+| `-e, --export` | Prefix each line with `export` |
+| `-d, --home-dir ` | Custom home directory for credential search |
+| `--no-oauth` | Exclude OAuth credentials |
```bash
-sandbox-agent api sessions list --endpoint http://127.0.0.1:2468
+# Source directly into shell
+eval "$(sandbox-agent credentials extract-env --export)"
```
-
-
-api sessions create
+---
+
+## API Commands
+
+The `sandbox-agent api` subcommand mirrors the HTTP API for scripting without client code.
+
+All API commands support:
+
+| Option | Default | Description |
+|--------|---------|-------------|
+| `-e, --endpoint ` | `http://127.0.0.1:2468` | API endpoint |
+| `-t, --token ` | - | Authentication token |
+
+---
+
+### Agents
+
+#### List Agents
+
+```bash
+sandbox-agent api agents list
+```
+
+#### Install Agent
+
+```bash
+sandbox-agent api agents install [OPTIONS]
+```
+
+| Option | Description |
+|--------|-------------|
+| `-r, --reinstall` | Force reinstall |
+
+```bash
+sandbox-agent api agents install claude --reinstall
+```
+
+#### Get Agent Modes
+
+```bash
+sandbox-agent api agents modes
+```
+
+```bash
+sandbox-agent api agents modes claude
+```
+
+---
+
+### Sessions
+
+#### List Sessions
+
+```bash
+sandbox-agent api sessions list
+```
+
+#### Create Session
+
+```bash
+sandbox-agent api sessions create [OPTIONS]
+```
+
+| Option | Description |
+|--------|-------------|
+| `-a, --agent ` | Agent identifier (required) |
+| `-g, --agent-mode ` | Agent mode |
+| `-p, --permission-mode ` | Permission mode (`default`, `plan`, `bypass`) |
+| `-m, --model ` | Model override |
+| `-v, --variant ` | Model variant |
+| `-A, --agent-version ` | Agent version |
```bash
sandbox-agent api sessions create my-session \
--agent claude \
- --agent-mode build \
- --permission-mode default \
- --endpoint http://127.0.0.1:2468
+ --agent-mode code \
+ --permission-mode default
```
-
-
-api sessions send-message
+#### Send Message
+
+```bash
+sandbox-agent api sessions send-message [OPTIONS]
+```
+
+| Option | Description |
+|--------|-------------|
+| `-m, --message ` | Message text (required) |
```bash
sandbox-agent api sessions send-message my-session \
- --message "Summarize the repository" \
- --endpoint http://127.0.0.1:2468
+ --message "Summarize the repository"
```
-
-
-api sessions send-message-stream
+#### Send Message (Streaming)
+
+Send a message and stream the response:
+
+```bash
+sandbox-agent api sessions send-message-stream [OPTIONS]
+```
+
+| Option | Description |
+|--------|-------------|
+| `-m, --message ` | Message text (required) |
+| `--include-raw` | Include raw agent data |
```bash
sandbox-agent api sessions send-message-stream my-session \
- --message "Summarize the repository" \
- --endpoint http://127.0.0.1:2468
+ --message "Help me debug this"
```
-
-
-api sessions events
+#### Terminate Session
```bash
-sandbox-agent api sessions events my-session --offset 0 --limit 50 --endpoint http://127.0.0.1:2468
+sandbox-agent api sessions terminate
```
-
-
-
-api sessions events-sse
```bash
-sandbox-agent api sessions events-sse my-session --offset 0 --endpoint http://127.0.0.1:2468
+sandbox-agent api sessions terminate my-session
```
-
-
-api sessions reply-question
+#### Get Events
+
+Fetch session events:
```bash
-sandbox-agent api sessions reply-question my-session QUESTION_ID \
- --answers "yes" \
- --endpoint http://127.0.0.1:2468
+sandbox-agent api sessions events [OPTIONS]
```
-
-
-api sessions reject-question
+| Option | Description |
+|--------|-------------|
+| `-o, --offset ` | Event offset |
+| `-l, --limit ` | Max events to return |
+| `--include-raw` | Include raw agent data |
```bash
-sandbox-agent api sessions reject-question my-session QUESTION_ID --endpoint http://127.0.0.1:2468
+sandbox-agent api sessions events my-session --offset 0 --limit 50
```
-
-
-api sessions reply-permission
+`get-messages` is an alias for `events`.
+
+#### Stream Events (SSE)
+
+Stream session events via Server-Sent Events:
```bash
-sandbox-agent api sessions reply-permission my-session PERMISSION_ID \
- --reply once \
- --endpoint http://127.0.0.1:2468
+sandbox-agent api sessions events-sse [OPTIONS]
```
-
+
+| Option | Description |
+|--------|-------------|
+| `-o, --offset ` | Event offset to start from |
+| `--include-raw` | Include raw agent data |
+
+```bash
+sandbox-agent api sessions events-sse my-session --offset 0
+```
+
+#### Reply to Question
+
+```bash
+sandbox-agent api sessions reply-question [OPTIONS]
+```
+
+| Option | Description |
+|--------|-------------|
+| `-a, --answers ` | JSON array of answers (required) |
+
+```bash
+sandbox-agent api sessions reply-question my-session q1 \
+ --answers '[["yes"]]'
+```
+
+#### Reject Question
+
+```bash
+sandbox-agent api sessions reject-question
+```
+
+```bash
+sandbox-agent api sessions reject-question my-session q1
+```
+
+#### Reply to Permission
+
+```bash
+sandbox-agent api sessions reply-permission [OPTIONS]
+```
+
+| Option | Description |
+|--------|-------------|
+| `-r, --reply ` | `once`, `always`, or `reject` (required) |
+
+```bash
+sandbox-agent api sessions reply-permission my-session perm1 --reply once
+```
+
+---
+
+## CLI to HTTP Mapping
+
+| CLI Command | HTTP Endpoint |
+|-------------|---------------|
+| `api agents list` | `GET /v1/agents` |
+| `api agents install` | `POST /v1/agents/{agent}/install` |
+| `api agents modes` | `GET /v1/agents/{agent}/modes` |
+| `api sessions list` | `GET /v1/sessions` |
+| `api sessions create` | `POST /v1/sessions/{sessionId}` |
+| `api sessions send-message` | `POST /v1/sessions/{sessionId}/messages` |
+| `api sessions send-message-stream` | `POST /v1/sessions/{sessionId}/messages/stream` |
+| `api sessions terminate` | `POST /v1/sessions/{sessionId}/terminate` |
+| `api sessions events` | `GET /v1/sessions/{sessionId}/events` |
+| `api sessions events-sse` | `GET /v1/sessions/{sessionId}/events/sse` |
+| `api sessions reply-question` | `POST /v1/sessions/{sessionId}/questions/{questionId}/reply` |
+| `api sessions reject-question` | `POST /v1/sessions/{sessionId}/questions/{questionId}/reject` |
+| `api sessions reply-permission` | `POST /v1/sessions/{sessionId}/permissions/{permissionId}/reply` |
diff --git a/docs/cors.mdx b/docs/cors.mdx
new file mode 100644
index 0000000..6d79b79
--- /dev/null
+++ b/docs/cors.mdx
@@ -0,0 +1,60 @@
+---
+title: "CORS Configuration"
+description: "Configure CORS for browser-based applications."
+sidebarTitle: "CORS"
+icon: "globe"
+---
+
+When calling the Sandbox Agent server from a browser, you need to enable CORS (Cross-Origin Resource Sharing) explicitly.
+
+## Basic Configuration
+
+```bash
+sandbox-agent server \
+ --token "$SANDBOX_TOKEN" \
+ --cors-allow-origin "http://localhost:5173" \
+ --cors-allow-method "GET" \
+ --cors-allow-method "POST" \
+ --cors-allow-header "Authorization" \
+ --cors-allow-header "Content-Type" \
+ --cors-allow-credentials
+```
+
+## Options
+
+| Flag | Description |
+|------|-------------|
+| `--cors-allow-origin` | Origins allowed to make requests (e.g., `http://localhost:5173`) |
+| `--cors-allow-method` | HTTP methods to allow (can be specified multiple times) |
+| `--cors-allow-header` | Headers to allow (can be specified multiple times) |
+| `--cors-allow-credentials` | Allow credentials (cookies, authorization headers) |
+
+## Multiple Origins
+
+You can allow multiple origins by specifying the flag multiple times:
+
+```bash
+sandbox-agent server \
+ --token "$SANDBOX_TOKEN" \
+ --cors-allow-origin "http://localhost:5173" \
+ --cors-allow-origin "http://localhost:3000" \
+ --cors-allow-method "GET" \
+ --cors-allow-method "POST" \
+ --cors-allow-header "Authorization" \
+ --cors-allow-header "Content-Type"
+```
+
+## Production
+
+In production, replace `localhost` origins with your actual domain:
+
+```bash
+sandbox-agent server \
+ --token "$SANDBOX_TOKEN" \
+ --cors-allow-origin "https://your-app.com" \
+ --cors-allow-method "GET" \
+ --cors-allow-method "POST" \
+ --cors-allow-header "Authorization" \
+ --cors-allow-header "Content-Type" \
+ --cors-allow-credentials
+```
diff --git a/docs/deploy/daytona.mdx b/docs/deploy/daytona.mdx
index 2500cb4..d78efde 100644
--- a/docs/deploy/daytona.mdx
+++ b/docs/deploy/daytona.mdx
@@ -1,21 +1,90 @@
---
title: "Daytona"
-description: "Run the daemon in a Daytona workspace."
+description: "Run the daemon in a Daytona workspace."
---
-## Steps
+
+Daytona has [network egress limits](https://www.daytona.io/docs/en/network-limits/) on lower tiers. OpenAI and Anthropic APIs are whitelisted on all tiers, but other external services may be restricted on Tier 1 & 2.
+
-1. Create a Daytona workspace with Rust and curl available.
-2. Install or build the sandbox-agent binary.
-3. Start the daemon and expose port `2468` (or your preferred port).
+## Prerequisites
-```bash
-export SANDBOX_TOKEN="..."
+- `DAYTONA_API_KEY` environment variable
+- `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` for the coding agents
-cargo run -p sandbox-agent -- server \
- --token "$SANDBOX_TOKEN" \
- --host 0.0.0.0 \
- --port 2468
+## TypeScript Example
+
+```typescript
+import { Daytona, Image } from "@daytonaio/sdk";
+import { SandboxAgent } from "sandbox-agent";
+
+const daytona = new Daytona();
+
+// Pass API keys to the sandbox
+const envVars: Record = {};
+if (process.env.ANTHROPIC_API_KEY) envVars.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
+if (process.env.OPENAI_API_KEY) envVars.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
+
+const sandbox = await daytona.create({ envVars });
+
+// Install sandbox-agent
+await sandbox.process.executeCommand(
+ "curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh"
+);
+
+// Start the server in the background
+await sandbox.process.executeCommand(
+ "nohup sandbox-agent server --no-token --host 0.0.0.0 --port 3000 >/tmp/sandbox-agent.log 2>&1 &"
+);
+
+// Wait for server to be ready
+await new Promise((r) => setTimeout(r, 2000));
+
+// Get the public URL
+const baseUrl = (await sandbox.getSignedPreviewUrl(3000, 4 * 60 * 60)).url;
+
+// Connect and use the SDK
+const client = await SandboxAgent.connect({ baseUrl });
+
+await client.createSession("my-session", {
+ agent: "claude",
+ permissionMode: "default",
+});
+
+// Cleanup when done
+await sandbox.delete();
```
-4. Use your Daytona port forwarding to reach the daemon from your client.
+## Using Snapshots for Faster Startup
+
+For production, use snapshots with pre-installed binaries:
+
+```typescript
+import { Daytona, Image } from "@daytonaio/sdk";
+
+const daytona = new Daytona();
+const SNAPSHOT = "sandbox-agent-ready";
+
+// Create snapshot once (takes 2-3 minutes)
+const hasSnapshot = await daytona.snapshot.get(SNAPSHOT).then(() => true, () => false);
+
+if (!hasSnapshot) {
+ await daytona.snapshot.create({
+ name: SNAPSHOT,
+ image: Image.base("ubuntu:22.04").runCommands(
+ "apt-get update && apt-get install -y curl ca-certificates",
+ "curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh",
+ "sandbox-agent install-agent claude",
+ "sandbox-agent install-agent codex",
+ ),
+ });
+}
+
+// Now sandboxes start instantly
+const sandbox = await daytona.create({
+ snapshot: SNAPSHOT,
+ envVars,
+});
+```
+
+See [Daytona Snapshots](https://daytona.io/docs/snapshots) for details.
diff --git a/docs/deploy/docker.mdx b/docs/deploy/docker.mdx
index 5cccd43..5152f60 100644
--- a/docs/deploy/docker.mdx
+++ b/docs/deploy/docker.mdx
@@ -1,27 +1,75 @@
---
-title: "Docker (dev)"
+title: "Docker"
description: "Build and run the daemon in a Docker container."
---
-## Build the binary
+
+Docker is not recommended for production. Standard Docker containers don't provide sufficient isolation for running untrusted code. Use a dedicated sandbox provider like E2B or Daytona for production workloads.
+
-Use the release Dockerfile to build a static binary:
+## Quick Start
+
+Run sandbox-agent in a container with agents pre-installed:
+
+```bash
+docker run --rm -p 3000:3000 \
+ -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
+ -e OPENAI_API_KEY="$OPENAI_API_KEY" \
+ debian:bookworm-slim bash -lc "\
+ apt-get update && apt-get install -y curl ca-certificates && \
+ curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh && \
+ sandbox-agent install-agent claude && \
+ sandbox-agent install-agent codex && \
+ sandbox-agent server --no-token --host 0.0.0.0 --port 3000"
+```
+
+Access the API at `http://localhost:3000`.
+
+## TypeScript with dockerode
+
+```typescript
+import Docker from "dockerode";
+import { SandboxAgent } from "sandbox-agent";
+
+const docker = new Docker();
+const PORT = 3000;
+
+const container = await docker.createContainer({
+ Image: "debian:bookworm-slim",
+ Cmd: ["bash", "-lc", [
+ "apt-get update && apt-get install -y curl ca-certificates",
+ "curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh",
+ "sandbox-agent install-agent claude",
+ "sandbox-agent install-agent codex",
+ `sandbox-agent server --no-token --host 0.0.0.0 --port ${PORT}`,
+ ].join(" && ")],
+ ExposedPorts: { [`${PORT}/tcp`]: {} },
+ HostConfig: {
+ AutoRemove: true,
+ PortBindings: { [`${PORT}/tcp`]: [{ HostPort: `${PORT}` }] },
+ },
+});
+
+await container.start();
+
+// Wait for server and connect
+const baseUrl = `http://127.0.0.1:${PORT}`;
+const client = await SandboxAgent.connect({ baseUrl });
+
+// Use the client...
+await client.createSession("my-session", {
+ agent: "claude",
+ permissionMode: "default",
+});
+```
+
+## Building from Source
+
+To build a static binary for use in minimal containers:
```bash
docker build -f docker/release/linux-x86_64.Dockerfile -t sandbox-agent-build .
-
docker run --rm -v "$PWD/artifacts:/artifacts" sandbox-agent-build
```
-The binary will be written to `./artifacts/sandbox-agent-x86_64-unknown-linux-musl`.
-
-## Run the daemon
-
-```bash
-docker run --rm -p 2468:2468 \
- -v "$PWD/artifacts:/artifacts" \
- debian:bookworm-slim \
- /artifacts/sandbox-agent-x86_64-unknown-linux-musl server --token "$SANDBOX_TOKEN" --host 0.0.0.0 --port 2468
-```
-
-You can now access the API at `http://localhost:2468`.
+The binary will be at `./artifacts/sandbox-agent-x86_64-unknown-linux-musl`.
diff --git a/docs/deploy/e2b.mdx b/docs/deploy/e2b.mdx
index 682c6f8..233a9af 100644
--- a/docs/deploy/e2b.mdx
+++ b/docs/deploy/e2b.mdx
@@ -3,23 +3,77 @@ title: "E2B"
description: "Deploy the daemon inside an E2B sandbox."
---
-## Steps
+## Prerequisites
-1. Start an E2B sandbox with network access.
-2. Install the agent binaries you need (Claude, Codex, OpenCode, Amp).
-3. Run the daemon and expose its port.
+- `E2B_API_KEY` environment variable
+- `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` for the coding agents
-Example startup script:
+## TypeScript Example
-```bash
-export SANDBOX_TOKEN="..."
+```typescript
+import { Sandbox } from "@e2b/code-interpreter";
+import { SandboxAgent } from "sandbox-agent";
-# Install sandbox-agent binary (or build from source)
-# TODO: replace with release download once published
-cargo run -p sandbox-agent -- server \
- --token "$SANDBOX_TOKEN" \
- --host 0.0.0.0 \
- --port 2468
+// Pass API keys to the sandbox
+const envs: Record = {};
+if (process.env.ANTHROPIC_API_KEY) envs.ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
+if (process.env.OPENAI_API_KEY) envs.OPENAI_API_KEY = process.env.OPENAI_API_KEY;
+
+const sandbox = await Sandbox.create({ envs });
+
+// Install sandbox-agent
+await sandbox.commands.run(
+ "curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh"
+);
+
+// Start the server in the background
+await sandbox.commands.run(
+ "sandbox-agent server --no-token --host 0.0.0.0 --port 3000",
+ { background: true }
+);
+
+// Connect to the server
+const baseUrl = `https://${sandbox.getHost(3000)}`;
+const client = await SandboxAgent.connect({ baseUrl });
+
+// Wait for server to be ready
+for (let i = 0; i < 30; i++) {
+ try {
+ await client.getHealth();
+ break;
+ } catch {
+ await new Promise((r) => setTimeout(r, 1000));
+ }
+}
+
+// Install agents (or pre-install in a custom template)
+await client.installAgent("claude");
+await client.installAgent("codex");
+
+// Create a session and start coding
+await client.createSession("my-session", {
+ agent: "claude",
+ permissionMode: "default",
+});
+
+await client.postMessage("my-session", {
+ message: "Summarize this repository",
+});
+
+for await (const event of client.streamEvents("my-session")) {
+ console.log(event.type, event.data);
+}
+
+// Cleanup
+await sandbox.kill();
```
-4. Configure your client to connect to the sandbox endpoint.
+## Faster Cold Starts
+
+For faster startup, create a custom E2B template with sandbox-agent and agents pre-installed:
+
+1. Create a template with the install script baked in
+2. Pre-install agents: `sandbox-agent install-agent claude codex`
+3. Use the template ID when creating sandboxes
+
+See [E2B Custom Templates](https://e2b.dev/docs/sandbox-template) for details.
diff --git a/docs/deploy/index.mdx b/docs/deploy/index.mdx
index aa4b1b3..8b29c30 100644
--- a/docs/deploy/index.mdx
+++ b/docs/deploy/index.mdx
@@ -1,4 +1,21 @@
---
-sidebarTitle: Overview
+title: "Deploy"
+sidebarTitle: "Overview"
+description: "Choose where to run the sandbox-agent server."
+icon: "server"
---
+
+
+ Run locally for development. The SDK can auto-spawn the server.
+
+
+ Deploy inside an E2B sandbox with network access.
+
+
+ Run in a Daytona workspace with port forwarding.
+
+
+ Build and run in a container (development only).
+
+
diff --git a/docs/deploy/local.mdx b/docs/deploy/local.mdx
new file mode 100644
index 0000000..e70a14f
--- /dev/null
+++ b/docs/deploy/local.mdx
@@ -0,0 +1,43 @@
+---
+title: "Local"
+description: "Run the daemon locally for development."
+---
+
+For local development, you can run the daemon directly on your machine.
+
+## With the CLI
+
+```bash
+# Install
+curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh
+
+# Run
+sandbox-agent server --no-token --host 127.0.0.1 --port 2468
+```
+
+Or with npm:
+
+```bash
+npx sandbox-agent server --no-token --host 127.0.0.1 --port 2468
+```
+
+## With the TypeScript SDK
+
+The SDK can automatically spawn and manage the server as a subprocess:
+
+```typescript
+import { SandboxAgent } from "sandbox-agent";
+
+// Spawns sandbox-agent server as a subprocess
+const client = await SandboxAgent.start();
+
+await client.createSession("my-session", {
+ agent: "claude",
+ permissionMode: "default",
+});
+
+// When done
+await client.dispose();
+```
+
+This installs the binary (if needed) and starts the server on a random available port. No manual setup required.
diff --git a/docs/deploy/vercel-sandboxes.mdx b/docs/deploy/vercel-sandboxes.mdx
deleted file mode 100644
index ea460ae..0000000
--- a/docs/deploy/vercel-sandboxes.mdx
+++ /dev/null
@@ -1,21 +0,0 @@
----
-title: "Vercel Sandboxes"
-description: "Run the daemon inside Vercel Sandboxes."
----
-
-## Steps
-
-1. Provision a Vercel Sandbox with network access and storage.
-2. Install the agent binaries you need.
-3. Run the daemon and expose the port.
-
-```bash
-export SANDBOX_TOKEN="..."
-
-cargo run -p sandbox-agent -- server \
- --token "$SANDBOX_TOKEN" \
- --host 0.0.0.0 \
- --port 2468
-```
-
-4. Configure your client to use the sandbox URL.
diff --git a/docs/docs.json b/docs/docs.json
index bbf3174..dddabed 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -34,38 +34,43 @@
"pages": [
{
"group": "Getting started",
- "pages": [
- "index",
- "quickstart",
- "architecture",
- "agent-compatibility",
- "universal-api",
- "frontend",
- "building-chat-ui",
- "manage-session-state"
- ]
- },
- {
- "group": "SDKs",
- "pages": ["sdks/typescript"]
- },
- {
- "group": "AI",
- "pages": ["ai/skill", "ai/llms-txt"]
- },
- {
- "group": "Reference",
- "pages": ["cli", "telemetry", "http-api"]
+ "pages": ["quickstart", "building-chat-ui", "manage-sessions"]
},
{
"group": "Deploy",
"pages": [
"deploy/index",
- "deploy/docker",
+ "deploy/local",
"deploy/e2b",
"deploy/daytona",
- "deploy/vercel-sandboxes"
+ "deploy/docker"
]
+ },
+ {
+ "group": "SDKs",
+ "pages": ["sdks/typescript", "sdks/python"]
+ },
+ {
+ "group": "Reference",
+ "pages": [
+ "cli",
+ "inspector",
+ "universal-schema",
+ "agent-compatibility",
+ "cors",
+ {
+ "group": "AI",
+ "pages": ["ai/skill", "ai/llms-txt"]
+ },
+ {
+ "group": "Advanced",
+ "pages": ["telemetry"]
+ }
+ ]
+ },
+ {
+ "group": "HTTP API Reference",
+ "openapi": "openapi.json"
}
]
}
diff --git a/docs/favicon.svg b/docs/favicon.svg
index b785c73..d36cc7e 100644
--- a/docs/favicon.svg
+++ b/docs/favicon.svg
@@ -1,19 +1,10 @@
-