> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ascii.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Integrated agents

> Box runs coding-agent harnesses for you: the boilerplate you would otherwise build on a bare cloud computer, with every seam to plug into, customize, or replace.

A Box ships with coding-agent harnesses installed and wired up: **Claude Code, Codex, pi, OpenCode, Prime Agent, and Kimi Code**. `box prompt` hands one of them a task, streams back what it does, keeps its memory, runs many tasks at once, survives the Box stopping and resuming, and lets you switch model or harness mid-task.

None of that is magic. It is exactly what you would build if you rented a bare cloud computer and put an agent on it. This page shows that boilerplate, how Box implements it, and where you plug in your own instructions, tools, keys, or even your own harness.

## What you'd have to build

Put a coding agent on a plain cloud machine and you always end up writing the same pieces:

| You'd have to build                                                 | Box does it as                                                                                                                                                               |
| ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Install the harness, keep it updated                                | Six harnesses preinstalled and refreshed on every Box                                                                                                                        |
| Hand it API keys, or your user's own keys                           | Your keys from the [Agents dashboard](https://box.ascii.dev/box/dashboard?tab=agents), or per-Box keys with `box new -e` (see [Whose keys](#whose-keys-yours-or-your-users)) |
| Spawn the harness with a prompt in the right directory              | `box prompt "…"` / `POST /prompt`, runs in `/home/user`                                                                                                                      |
| Capture stdout, parse tool calls, stream to your UI                 | `box events` / `GET /events`: structured prompt, response, and tool-call events                                                                                              |
| Remember the session id so the next prompt continues the thread     | A **conversation**. Box keeps the history and the harness session                                                                                                            |
| Run two tasks at once without their sessions colliding              | Many parallel conversations per Box, each with its own harness process                                                                                                       |
| Send SIGINT to the right process                                    | `box interrupt --convo <id>`                                                                                                                                                 |
| Type at the agent while it is working, per harness                  | `box steer "…"` / `POST /steer`, see [Steering a running turn](#steering-a-running-turn)                                                                                     |
| Persist sessions to disk so a reboot loses nothing                  | Conversations survive `box stop`, `box resume`, `box fork`                                                                                                                   |
| Let the user pick a model, or a thinking level                      | `--model` and `--reasoning-effort` on every prompt                                                                                                                           |
| Let the user pick a harness, and carry the context when they switch | `--provider` on every prompt; the conversation history carries across                                                                                                        |
| Inject house rules, MCP servers, skills, tools                      | Ordinary harness config files in the Box home, see [Customize the harness](#customize-the-harness)                                                                           |
| Attach a screenshot or a file                                       | `box prompt --attach`                                                                                                                                                        |
| Give the agent eyes and hands on a real screen                      | The `computer` tools, on every harness, see [Computer use](#computer-use)                                                                                                    |

You can still do any of these by hand. Box never stops you from `box ssh`-ing in and running whatever you like (see [Bring your own harness](#bring-your-own-harness)).

## How it fits together

```mermaid theme={null}
flowchart LR
  You["You: CLI, API, SDK"]
  subgraph Backend["Box backend"]
    Q["Queue prompts<br/>pick harness + model"]
    E["Collect events"]
  end
  subgraph Box["Your Box"]
    AS["Agent server<br/>(always on)"]
    A["Conversation A<br/>claude process"]
    B["Conversation B<br/>pi process"]
    C["Conversation C<br/>codex process"]
    D[("/home/user<br/>files, config, sessions")]
  end
  You -- "box prompt" --> Q --> AS
  AS --> A & B & C
  A & B & C --> D
  AS -- "events" --> E -- "box events" --> You
```

The agent server is a small always-on process on every Box. Each **conversation** owns one harness process and one history. The harnesses are ordinary processes running as `user` in `/home/user`, reading ordinary config files and credentials, which is why everything below is inspectable and replaceable.

## Harnesses and models

Pick the harness per prompt with `--provider`, or omit it to use the one you chose on the Agents dashboard.

| `--provider` | Harness                     | Models                                                                            |
| ------------ | --------------------------- | --------------------------------------------------------------------------------- |
| `claude`     | Claude Code                 | Anthropic models, or DeepSeek models on a DeepSeek key                            |
| `codex`      | OpenAI Codex                | OpenAI models, or DeepSeek models on a DeepSeek key                               |
| `pi`         | pi                          | Anthropic, OpenAI, and OpenRouter or llmgateway-routed models (DeepSeek included) |
| `opencode`   | OpenCode                    | same multi-vendor catalog                                                         |
| `prime`      | Prime Agent                 | same multi-vendor catalog                                                         |
| `kimi`       | Kimi Code CLI (Moonshot AI) | Kimi models: K3, Kimi for Coding, K2.7 Code, K2.6                                 |

Every model accepts a different set of reasoning levels, and the catalog changes often, so it is served live rather than written here: `box prompt --help` prints it, `GET /provider-models` returns it as JSON. Change model or thinking level on any prompt, in the same conversation:

```bash theme={null}
box prompt --provider pi --model claude-sonnet-5 --reasoning-effort high "Refactor the billing module"
box prompt --model gpt-5.6-terra "Same task, second opinion"        # same conversation, other model
```

## Whose keys: yours or your users'

Two situations, one mechanism. The harness reads its credentials from the Box's environment and auth files, and Box fills those from one of two sources:

| Source                      | How                                                                                                                               | Who sees the keys                                 |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
| **Your account**            | Connect keys or subscriptions on the [Agents dashboard](https://box.ascii.dev/box/dashboard?tab=agents) once; every Box gets them | You, on your own Boxes                            |
| **Per Box, from your user** | `box new --no-env -e ANTHROPIC_API_KEY=… -e OPENAI_API_KEY=…` (API: `noEnv: true` plus `env`)                                     | Only that Box. Your account keys never land on it |

Subscriptions connect on the Agents dashboard the same way for all three vendors: **Claude** (Pro or Max), **ChatGPT** (Plus, Pro or Team) and **Kimi** (Kimi Code). Click Sign in, approve on the vendor's page (ChatGPT and Kimi show a short code to confirm), and the dashboard reports the subscription as connected. Box keeps the sign-in alive for you: the token is refreshed server-side before every prompt and every Box start, so a Box never comes up on an expired one. Sign out on the same row to drop it everywhere.

`--no-env` withholds every credential of yours; the `-e` values are the only ones the Box ever has. That is the shape of a product where each end user brings their own key or subscription and picks a harness in a selector:

```mermaid theme={null}
sequenceDiagram
  participant U as Your user
  participant App as Your app
  participant API as Box API
  U->>App: pastes key, picks Claude Code
  App->>API: POST /boxes {noEnv: true, env: {ANTHROPIC_API_KEY}}
  API-->>App: box id
  App->>API: POST /prompt {provider: "claude", prompt}
  API-->>App: events (streamed)
  App-->>U: live output
```

Variables the harnesses read (any subset works; a harness whose vendor key is missing refuses the prompt with a credential error, the others keep working):

| Variable                                            | Used by                                                                                                                                                    |
| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ANTHROPIC_API_KEY`                                 | Claude Code, pi, OpenCode, Prime Agent                                                                                                                     |
| `OPENAI_API_KEY`                                    | Codex, pi, OpenCode, Prime Agent                                                                                                                           |
| `OPENROUTER_API_KEY`                                | pi, OpenCode, Prime Agent                                                                                                                                  |
| `LLMGATEWAY_API_KEY`                                | pi, OpenCode, Prime Agent                                                                                                                                  |
| `CLAUDE_CODE_OAUTH_TOKEN`                           | Claude Code, a Claude Pro or Max subscription token from `claude setup-token`                                                                              |
| `MOONSHOT_API_KEY`                                  | Kimi Code, a Kimi open platform key (api.moonshot.ai)                                                                                                      |
| `KIMI_CODE_ACCESS_TOKEN`, `KIMI_CODE_REFRESH_TOKEN` | Kimi Code, a Kimi Code subscription token pair (the `access_token` and `refresh_token` a `kimi login` stores in `~/.kimi-code/credentials/kimi-code.json`) |
| `DEEPSEEK_API_KEY`                                  | Claude Code and Codex, a [DeepSeek platform key](https://platform.deepseek.com/api_keys), see [DeepSeek](#deepseek)                                        |
| `AWS_BEARER_TOKEN_BEDROCK` + `AWS_REGION`           | Claude Code (with `CLAUDE_CODE_USE_BEDROCK=1`) and Codex (with `OPENAI_BASE_URL`), see [Amazon Bedrock](#amazon-bedrock)                                   |

Kimi Code reads no credential from the environment itself; the Box writes these into its `~/.kimi-code/config.toml` and token file before the first prompt, then the CLI refreshes the subscription token on its own.

Keys are per Box, not per conversation. A Box shared by several users runs on one set of credentials; give each paying user their own Box when their keys must stay apart, and share one Box across conversations when the keys are yours.

### DeepSeek

Claude Code and Codex can run on DeepSeek's models instead of Anthropic's or OpenAI's. One credential covers both: a [DeepSeek platform key](https://platform.deepseek.com/api_keys), a single string starting with `sk-`. DeepSeek publishes an Anthropic-shaped endpoint and an OpenAI-shaped one against the same key, so neither harness is modified and neither needs a gateway in front of it.

| Source                      | How                                                                                                                                                                        |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Your account**            | Agents dashboard, the DeepSeek API key row: paste the key once, then point Claude Code or Codex (or both) at it and pick a DeepSeek model                                  |
| **Per Box, from your user** | `box new --no-env -e DEEPSEEK_API_KEY=sk-…`. That one variable configures both harnesses; pass another vendor's key alongside it and that harness keeps its own credential |

What the Box exports to each harness, and which model ids to pass:

| Harness     | Environment                                                                                                                                                                                                                                          | Model ids                                                                                                                                                    |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Claude Code | `ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic`, `ANTHROPIC_AUTH_TOKEN` = the key, `ANTHROPIC_API_KEY` empty (Claude Code otherwise sends an `x-api-key` header the endpoint rejects)                                                        | `deepseek-flash`, `deepseek-v4-pro`. DeepSeek also maps Claude's own names onto these, so the harness's small, fast and subagent models resolve on their own |
| Codex       | `OPENAI_BASE_URL=https://api.deepseek.com/v1` and the key; the agent server writes a `model_providers.deepseek` block pointing at `https://api.deepseek.com/` with `wire_api = "responses"` (Codex ignores `OPENAI_BASE_URL` itself), web search off | `deepseek-flash`, `deepseek-v4-pro`. Reasoning levels are `low` and `high`, the two both Codex and DeepSeek accept                                           |

<CodeGroup>
  ```bash CLI theme={null}
  box new --no-env -e DEEPSEEK_API_KEY=sk-…
  box prompt --provider claude --model deepseek-flash "Summarize this repo"
  box prompt --provider codex --model deepseek-v4-pro --reasoning-effort high "Now review the diff"
  ```

  ```bash curl theme={null}
  curl -sS -X POST "$BOX_API_BASE/boxes" \
    -H "Authorization: Bearer $BOX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"noEnv":true,"env":{"DEEPSEEK_API_KEY":"sk-…"}}'
  curl -sS -X POST "$BOX_API_BASE/boxes/$BOX_ID/prompt" \
    -H "Authorization: Bearer $BOX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"provider":"claude","model":"deepseek-flash","prompt":"Summarize this repo"}'
  ```

  ```ts TypeScript theme={null}
  const created = await box.create({
    createBoxRequest: { noEnv: true, env: { DEEPSEEK_API_KEY: "sk-…" } },
  });
  await box.prompt({
    boxId: created.box.id,
    promptRequest: { provider: "claude", model: "deepseek-flash", prompt: "Summarize this repo" },
  });
  ```

  ```python Python theme={null}
  created = box.create(CreateBoxRequest(no_env=True, env={"DEEPSEEK_API_KEY": "sk-…"}))
  box.prompt(created.box.id, PromptRequest(provider="claude", model="deepseek-flash", prompt="Summarize this repo"))
  ```
</CodeGroup>

pi, OpenCode and Prime Agent reach DeepSeek a different way: through your OpenRouter key, with no DeepSeek account at all. Those models are `openrouter:deepseek/deepseek-v4.1-flash`, `openrouter:deepseek/deepseek-v4-flash-0731` and `openrouter:deepseek/deepseek-v4-pro-0813`, and they need only `OPENROUTER_API_KEY`.

### Amazon Bedrock

Claude Code and Codex can run on models served by your AWS account instead of Anthropic's or OpenAI's API. The simplest credential is a [Bedrock API key](https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys.html): one string starting with `ABSK`, generated in the Bedrock console under API keys, valid in every region. IAM access keys (access key id, secret, optional session token) work too.

| Source                      | How                                                                                                                                              |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Your account**            | Agents dashboard, the Bedrock row under Claude Code or Codex: region, then API key or IAM keys. Point the harness at it and pick a Bedrock model |
| **Per Box, from your user** | `box new --no-env -e AWS_BEARER_TOKEN_BEDROCK=ABSK… -e AWS_REGION=us-east-1 -e CLAUDE_CODE_USE_BEDROCK=1`                                        |

What the Box exports to each harness, and which model ids to pass:

| Harness     | Environment                                                                                                                                                                                                                                                                                                                                       | Model ids                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Claude Code | `CLAUDE_CODE_USE_BEDROCK=1`, `AWS_REGION`, the key (or `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`)                                                                                                                                                                                                                                             | Bedrock cross-region inference profiles on the Anthropic line: `us.anthropic.claude-sonnet-4-6`, `us.anthropic.claude-opus-4-6-v1`, `us.anthropic.claude-sonnet-4-5-20250929-v1:0`, `us.anthropic.claude-opus-4-5-20251101-v1:0`, `us.anthropic.claude-haiku-4-5-20251001-v1:0`, `us.anthropic.claude-sonnet-5`, `us.anthropic.claude-opus-5`, `us.anthropic.claude-fable-5-1`, `us.anthropic.claude-fable-5`, `us.anthropic.claude-opus-4-8`, `us.anthropic.claude-opus-4-7`. With the Bedrock credential in use these are the only Claude Code models offered, and the default becomes Sonnet 4.6: the plain `sonnet` and `opus` aliases resolve inside Claude Code to model ids many AWS accounts have not enabled |
| Codex       | `OPENAI_BASE_URL=https://bedrock-mantle.<region>.api.aws/v1` and the key; the agent server selects Codex's built-in `amazon-bedrock` provider for that region (Codex ignores `OPENAI_BASE_URL` itself) on Bedrock's OpenAI-compatible [Mantle endpoint](https://docs.aws.amazon.com/bedrock/latest/userguide/bedrock-mantle.html), web search off | The OpenAI line on Mantle: `openai.gpt-5.6-terra`, `openai.gpt-5.6-sol`, `openai.gpt-5.6-luna`, `openai.gpt-5.5`, `openai.gpt-5.5-2026-04-23`, `openai.gpt-5.4`, `openai.gpt-5.4-2026-03-05`. Codex on Bedrock needs the API key; IAM keys alone do not authenticate the OpenAI-compatible endpoint. The open-weight `gpt-oss` models and the third-party models on Mantle (Kimi, MiniMax, Qwen, DeepSeek, GLM, Mistral) are not listed: they answer Mantle's `/v1` routes but not the `/openai/v1/responses` route Codex calls                                                                                                                                                                                       |

<CodeGroup>
  ```bash CLI theme={null}
  box new --no-env -e AWS_BEARER_TOKEN_BEDROCK=ABSK… -e AWS_REGION=us-east-1 -e CLAUDE_CODE_USE_BEDROCK=1
  box prompt --provider claude --model us.anthropic.claude-sonnet-4-6 "Summarize this repo"
  ```

  ```bash curl theme={null}
  curl -sS -X POST "$BOX_API_BASE/boxes" \
    -H "Authorization: Bearer $BOX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"noEnv":true,"env":{"AWS_BEARER_TOKEN_BEDROCK":"ABSK…","AWS_REGION":"us-east-1","CLAUDE_CODE_USE_BEDROCK":"1"}}'
  curl -sS -X POST "$BOX_API_BASE/boxes/$BOX_ID/prompt" \
    -H "Authorization: Bearer $BOX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"provider":"claude","model":"us.anthropic.claude-sonnet-4-6","prompt":"Summarize this repo"}'
  ```

  ```ts TypeScript theme={null}
  const created = await box.create({
    createBoxRequest: { noEnv: true, env: { AWS_BEARER_TOKEN_BEDROCK: "ABSK…", AWS_REGION: "us-east-1", CLAUDE_CODE_USE_BEDROCK: "1" } },
  });
  await box.prompt({
    boxId: created.box.id,
    promptRequest: { provider: "claude", model: "us.anthropic.claude-sonnet-4-6", prompt: "Summarize this repo" },
  });
  ```

  ```python Python theme={null}
  created = box.create(CreateBoxRequest(no_env=True, env={"AWS_BEARER_TOKEN_BEDROCK": "ABSK…", "AWS_REGION": "us-east-1", "CLAUDE_CODE_USE_BEDROCK": "1"}))
  box.prompt(created.box.id, PromptRequest(provider="claude", model="us.anthropic.claude-sonnet-4-6", prompt="Summarize this repo"))
  ```
</CodeGroup>

#### Model access is granted per AWS account and region

The catalog above lists what Amazon Bedrock offers for each harness, not what your account may call today. Bedrock grants model access per AWS account and per region, so a model is listed here as soon as Bedrock serves it, including frontier models AWS has not granted you yet. Whether you can call one is between your account and AWS.

To request access: open the [Amazon Bedrock console](https://console.aws.amazon.com/bedrock/) in the region your credential uses, go to Model catalog, find the model, and choose Request model access. Some models ask for a short use case form and are approved by AWS rather than instantly. Access granted in one region does not carry to another.

Pick a model your account has not been granted and the prompt comes back naming the model, the reason and the fix, for example:

```
Amazon Bedrock has not granted this AWS account access to anthropic.claude-opus-5. Bedrock grants
model access per AWS account and per region, so a model in the Box catalog still has to be enabled
on your side: open the Amazon Bedrock console in the region this Box's credential uses, find
anthropic.claude-opus-5 in the Model catalog, request access, and retry once AWS approves it. Pick a
model you already have access to in the meantime.
```

Two related failures read differently. A key AWS rejects outright surfaces as an authentication error, with the harness naming the endpoint it called. A model id whose region prefix does not match your credential's region comes back as `The provided model identifier is invalid`: the `us.` profiles above only resolve from US regions, so from `eu-west-1` pass `eu.anthropic.claude-sonnet-4-6` instead, and likewise `au.` or `jp.`. A `global.` prefix works from any supported source region and needs a wider IAM policy, see [global cross-region inference](https://docs.aws.amazon.com/bedrock/latest/userguide/global-cross-region-inference.html).

## Conversations: the harness's memory

The first `box prompt` on a Box starts a **conversation**: a thread of prompts and responses with its own memory, backed by the harness's native session. Every later prompt continues it. You never handle the session id, but it is there when you want it: every prompt prints its conversation id (`conversation: <id>` under `queued:`, `conversationId` in `--json` and in the API response) and every event carries one.

```bash theme={null}
box prompt "Now add tests"                              # this shell's current conversation
box prompt --new "Look into the flaky CI job"           # a brand-new conversation
box prompt --resume <id> "Continue where we left off"   # a specific one
box conversations                                       # every conversation on the Box, with ids
```

`box conversations` (`GET /boxes/{id}/conversations`) lists them newest first with prompt count, whether a turn is running, the last harness and model, a preview of the last prompt, and which one is the Box's current conversation. That is how a thread started yesterday, or from another shell, gets its id back for `--resume`.

**Current is per shell.** Like the `current` Box id, the current conversation is scoped to the shell you are in: `--new` makes the new conversation this shell's current one, and a bare `box prompt` continues it. Two shells, two people, or two machines prompting the same Box each keep their own thread and never trip over each other.

## Parallel conversations

Conversations run at the same time, each in its own harness process, with isolated histories:

```mermaid theme={null}
gantt
  dateFormat  s
  axisFormat  %S s
  section A claude
  refactor billing        :a1, 0, 40
  add tests (waits for A) :a2, after a1, 25
  section B pi
  fix flaky test          :b1, 0, 30
  section C codex
  write the changelog     :c1, 5, 20
```

The rules are the ones you would write yourself:

* **One turn at a time per conversation.** A second prompt to the same conversation waits for the current turn, because it needs that turn's context. Prompts to different conversations run concurrently.
* **A per-Box cap** on concurrent turns, sized to the Box's memory: a `default` (8 GB) Box runs roughly two dozen at once, `large` more, `small` fewer. Past the cap, prompts queue and start as turns finish; nothing is dropped. Pin an exact number with the `ASCII_MAX_PARALLEL_CONVERSATIONS` environment variable on the Box, or size the Box ([Machine Capabilities](/box/machines)).
* **List them** with `box conversations`: each row says whether a turn is running in it, so you can see the parallel work at a glance.
* **Events stream all conversations by default**, tagged with `conversationId`. Watch one with `box events --convo <id>`.
* **Interrupt is scoped.** `box interrupt --convo <id>` stops one turn; the others keep running. Bare `box interrupt` stops everything on the Box.
* **Steer is scoped too.** `box steer --convo <id> "..."` changes one running turn without stopping it. See [Steering a running turn](#steering-a-running-turn).

## Steering a running turn

A second `box prompt` waits for the running turn. `box interrupt` throws it away. **`box steer` is the third option**: it hands the running turn a new message, the agent takes it into account, and it keeps everything it was doing. This is what typing into a coding agent while it works does.

```bash theme={null}
box prompt "Refactor billing and run the full test suite"
box steer "Skip the integration tests, the unit tests are enough"
box steer --convo <id> "Also update the changelog when you are done"
```

Omit `--convo` and it steers the conversation this shell last prompted, exactly like a bare `box prompt` continues it.

```mermaid theme={null}
sequenceDiagram
  participant You
  participant Turn as The running turn
  You->>Turn: box prompt "refactor billing, run the tests"
  Note over Turn: working, tests running
  You->>Turn: box steer "skip the integration tests"
  Note over Turn: still the same turn, now with your instruction
  Turn-->>You: finishes the whole thing
```

**How it behaves per harness.** Four of the six take a mid-turn message natively, so nothing is stopped. OpenCode and Kimi Code have no such primitive (their protocol has "prompt" and "cancel", nothing in between), so Box does the next best thing transparently:

| Harness     | Delivery                                                                                                                                                   | Turn interrupted?               |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- |
| Claude Code | Native. Delivered inside the turn when the harness folds it in; otherwise continued right after, as its own turn on the same session.                      | No                              |
| Codex       | Native. `turn/steer` adds your message to the in-flight turn.                                                                                              | No                              |
| pi          | Native. Delivered at the turn's next model call, which is usually right after the tool in flight finishes.                                                 | No                              |
| Prime Agent | Native. Same as pi.                                                                                                                                        | No                              |
| OpenCode    | Fallback. Box stops the turn and immediately starts a new one on the **same** conversation, carrying "Continue what you were doing" plus your instruction. | Yes, and it continues by itself |
| Kimi Code   | Fallback. Same as OpenCode.                                                                                                                                | Yes, and it continues by itself |

**A steer is always acted on, never just accepted.** Handing a message to a harness is not the same as the harness doing something about it, so Box does not take the harness's word for it. It watches the harness's own steering queue where there is one, waits for the queue to drain before it calls the turn finished, and where it cannot see delivery it runs the instruction as its own turn the moment the turn ends. So when `box steer` returns and the turn later reports `finished`, the instruction has been carried out, not merely delivered.

The API response and the event both carry `native`, and the event carries the exact `mode`: `native`, `native-continued`, `fallback`, or `late`. **`native-continued`** is the honest middle case: the harness accepted the message but its turn ended without acting on it, so Box ran it immediately as its own turn on the same session. Nothing was interrupted and nothing was forgotten, the instruction simply lands a turn boundary later.

The `fallback` path keeps the harness session and all of its memory too, so nothing is forgotten there either; what it costs is the one tool call that was in flight, and the agent may redo a little of what it had just started.

A steer is its own event in the stream, never a queued prompt:

```bash theme={null}
box events --convo <id>
```

```
you [prompt]: Refactor billing and run the full test suite
you [steer]: Skip the integration tests, the unit tests are enough
...
```

Steering a conversation with nothing in flight is refused with `no_running_turn` (409). That is deliberate: you asked to change a turn that is running, and quietly queueing a new one instead would be a surprise. Use `box prompt` for that.

## Agent lifecycle

A prompt moves through a small state machine you can observe with `box events` or `GET /prompts/{promptId}`:

```mermaid theme={null}
stateDiagram-v2
  [*] --> queued: box prompt
  queued --> running: conversation free and Box under its cap
  running --> running: box steer (message joins this turn)
  running --> finished
  running --> interrupted: box interrupt
  running --> failed: harness or credential error (reason in the event)
  finished --> [*]
  interrupted --> [*]
  failed --> [*]
```

A steer never creates a state of its own: the turn that was already running is still the turn that finishes, and the work your message causes is part of it.

The Box lifecycle sits underneath, and conversations ride along with it:

```mermaid theme={null}
flowchart LR
  N["box new"] --> R["ready"]
  R --> P["prompt … prompt"]
  P --> S["box stop<br/>disk snapshot incl. sessions"]
  S --> R2["box resume / box fork"]
  R2 --> P2["box prompt --resume id<br/>same memory"]
```

Stopping snapshots the disk, including every conversation's history and the harness's native session files under `/home/user`. A resumed or forked Box restores them, so `--resume <id>` picks up with full memory. Processes the harness started by hand (a dev server it launched, a tunnel) do not survive a stop, same as on any reboot; conversations and every config file below do.

## Switching harness or model mid-conversation

Continue a conversation on a different harness and it keeps the thread:

```mermaid theme={null}
sequenceDiagram
  participant You
  participant Conv as Conversation A
  participant CC as Claude Code
  participant Pi as pi
  You->>Conv: prompt 1, prompt 2 (--provider claude)
  Conv->>CC: turns
  You->>Conv: --resume A --provider pi "take it from here"
  Conv->>Pi: full earlier transcript + new prompt
  Pi-->>You: continues with the whole context
```

```bash theme={null}
box prompt --resume <A> --provider pi --model claude-sonnet-5 "Take it from here"
```

Box freezes the earlier harness's transcript into the conversation and hands it to the new one, so the switch is one flag rather than a context-rebuilding script. A user-facing selector for harness and model maps directly onto `provider` and `model` on `POST /prompt`.

## Computer use

Every Box runs a Linux desktop, and every harness on it can see and drive that desktop as ordinary tools. Nothing to install, nothing to register:

```bash theme={null}
box prompt "Open example.com in the browser and tell me the page title"
box prompt --provider codex "Log into the admin app in Chrome and screenshot the dashboard"
```

The tools come from the [Cua Driver](https://github.com/trycua/cua), preinstalled on every Box and registered as an MCP server named `computer` for all six harnesses:

```mermaid theme={null}
flowchart LR
  P["box prompt"] --> H["Harness process<br/>claude / codex / pi / opencode / prime / kimi"]
  H -- "stdio MCP<br/>server name: computer" --> M["cua-driver mcp"]
  M -- "unix socket" --> D["cua-driver daemon<br/>systemd user service"]
  D --> X["The Box desktop<br/>Xorg :0, 1920x1080, Chrome"]
  X -.-> S["box desktop<br/>watch it live"]
```

The daemon runs as the desktop user, so the tools act on the very screen `box desktop` streams: open a stream in one window and watch the agent work in it.

### What the agent gets

| Group            | Tools                                                                                                                                        |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| Look             | `get_window_state` (screenshot plus the accessibility tree in one call), `get_desktop_state`, `get_screen_size`, `list_windows`, `list_apps` |
| Point and type   | `click`, `double_click`, `right_click`, `drag`, `scroll`, `move_cursor`, `type_text`, `press_key`, `hotkey`, `set_value`                     |
| Apps and windows | `launch_app`, `kill_app`, `bring_to_front`, `set_window_frame`, `invoke_menu`                                                                |
| Browser          | `browser_navigate`, `browser_click`, `browser_type`, `get_browser_state`, `browser_download`                                                 |
| Clipboard        | `clipboard_read`, `clipboard_write`                                                                                                          |

Clicks and keystrokes are delivered in the background by default: they land on the target window without raising it or moving the real pointer, so several windows can be driven without a focus fight. When a widget only accepts input while focused, the tool says so and the agent can retry with `delivery_mode: "foreground"`, which activates the window, acts, and puts focus back.

Tool names reach `box events` namespaced by the harness, for example `mcp__computer__click` on Claude Code and Kimi Code, and `mcp:computer/click` on Codex.

### Where it is registered

The registration is an ordinary entry in each harness's own config file, written for you at Box start, and merged with anything you put there yourself:

| Harness     | File                               | Entry                                       |
| ----------- | ---------------------------------- | ------------------------------------------- |
| Claude Code | `~/.claude.json`                   | `mcpServers.computer`                       |
| Codex       | `~/.codex/config.toml`             | `[mcp_servers.computer]`, in a marked block |
| pi          | `~/.pi/agent/mcp.json`             | `mcpServers.computer`                       |
| OpenCode    | `~/.config/opencode/opencode.json` | `mcp.computer`                              |
| Prime Agent | `~/.prime/agent/settings.json`     | `mcpServers.computer`                       |
| Kimi Code   | `~/.kimi-code/mcp.json`            | `mcpServers.computer`                       |

Your own MCP servers in those files are untouched. Remove `computer` from a file and that harness simply loses the tools; it is rewritten on the next Box start, so delete the Box's daemon if you want it gone for good.

One tool, `browser_prepare`, is switched off for OpenCode in `opencode.json` `tools`, because OpenCode forwards MCP tool schemas to the model provider unchanged and the Anthropic API rejects that tool's schema, which would otherwise fail every OpenCode turn on an Anthropic model. Prime Agent reaches the server from inside its Python tool, as `await mcp.call_tool("computer", "click", {...})`.

### Practical notes

* **One screen, shared.** All conversations on a Box drive the same desktop. Two agents clicking at once will fight over it. Give each parallel GUI task its own Box, or serialize them.
* **It survives stop, resume and fork.** The daemon is a systemd user service on the Box, brought back automatically. The socket lives on `/run`, so a resumed Box never inherits a stale one.
* **Logins persist.** A Chrome profile the agent signed into is part of the Box's disk, so it comes back on resume and travels to a fork.
* **Screenshots are large.** A full-desktop screenshot is a real image in the model's context. Ask for a specific window (`get_window_state` with a window id) when a task loops.
* **Watch it, and record it.** Open the stream with `box desktop` to see the agent work, and capture the run to an MP4 with `ascii-record-desktop`. See [Desktop Streaming](/box/desktop-streaming).

If a prompt says it has no computer tools, check the daemon:

```bash theme={null}
box exec 'systemctl --user status cua-driver.service --no-pager'
box exec '/opt/ascii/cua-driver/cua-driver status --socket /run/ascii-cua/driver.sock'
box exec 'systemctl --user restart cua-driver.service'
```

## Customize the harness

Every harness reads its own config files from the Box home. Box adds nothing on top except its own short system prompt (which tells the harness it is running headless in a Box and that the `box` CLI exists) and the `box` skill. Everything else is yours to add, over `box ssh`, `box exec`, `box scp`, a file the agent writes, an [environment setup script](/box/environments), or a [named snapshot](/box/snapshots) so every new Box starts with it.

All of it was verified end to end through `box prompt` on the current harness versions; each row names the file that made the harness change its answer.

### Instructions and hidden rules

Drop a rules file and every prompt on that Box obeys it, without the prompt mentioning it:

| Harness     | Files read (all applied together)                                                             | Not read                                                                |
| ----------- | --------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| Claude Code | `~/CLAUDE.md`, `~/.claude/CLAUDE.md`                                                          | `AGENTS.md`                                                             |
| Codex       | `~/AGENTS.md`, `~/.codex/AGENTS.md`                                                           | `config.toml` `developer_instructions` (Box sets the base instructions) |
| pi          | `~/AGENTS.md`, `~/.pi/agent/AGENTS.md`, `~/.pi/agent/APPEND_SYSTEM.md`                        | `CLAUDE.md` when an `AGENTS.md` exists                                  |
| OpenCode    | `~/AGENTS.md`, `~/.config/opencode/AGENTS.md`, files listed in `opencode.json` `instructions` |                                                                         |
| Prime Agent | `~/AGENTS.md`, `~/.prime/agent/AGENTS.md`, `~/.prime/agent/APPEND_SYSTEM.md`                  |                                                                         |
| Kimi Code   | `~/AGENTS.md`, `~/.kimi-code/AGENTS.md`, `~/.agents/AGENTS.md`                                | `CLAUDE.md`                                                             |

One `~/AGENTS.md` plus one `~/CLAUDE.md` therefore covers all six. `APPEND_SYSTEM.md` (pi, Prime) is appended to the system prompt itself rather than to the project context. When an environment clones a single repository, Claude Code starts inside that repository, so a `CLAUDE.md` there applies too.

```bash theme={null}
box exec "printf '# House rules\nAlways run the test suite before saying you are done.\n' > ~/AGENTS.md; cp ~/AGENTS.md ~/CLAUDE.md"
```

### MCP servers

| Harness     | How to register                                                | Config written                                                                           |
| ----------- | -------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| Claude Code | `claude mcp add --scope user <name> -- <command>`              | `~/.claude.json` (project scope: `~/.mcp.json`)                                          |
| Codex       | `codex mcp add <name> -- <command>`                            | `~/.codex/config.toml` `[mcp_servers.<name>]`                                            |
| pi          | `pi install npm:pi-mcp-extension`, then `~/.pi/agent/mcp.json` | `{"mcpServers": {"<name>": {"transport": "stdio", "command": …, "lifecycle": "eager"}}}` |
| OpenCode    | `~/.config/opencode/opencode.json`                             | `"mcp": {"<name>": {"type": "local", "command": [...]}}`                                 |
| Prime Agent | `prime-agent mcp add <name> -- <command>`                      | `~/.prime/agent/settings.json` `mcpServers`                                              |
| Kimi Code   | `~/.kimi-code/mcp.json`                                        | `{"mcpServers": {"<name>": {"command": …, "args": [...]}}}` (`{"url": …}` for HTTP)      |

Remote (HTTP) servers work the same way with a URL instead of a command. Claude Code's MCP resource browsing tools are disabled on a Box; MCP tools are not.

Every Box already has one server registered this way, `computer`, in exactly these files. Adding yours next to it changes nothing about it, and it never overwrites yours. See [Computer use](#computer-use).

### Reach your own app from the Box

A Box has no route back to your laptop, so an MCP server, a local model, or a webhook receiver running on `localhost` is invisible to the agents inside it. `box forward --reverse` opens that route: a port on your machine starts answering at `127.0.0.1:<port>` inside the Box, over the same SSH session `box ssh` uses, with nothing exposed publicly on either end.

```bash theme={null}
# leave this running; your MCP server on localhost:7777 is now in the Box
box forward bx_f7k2q9hd --reverse --local 7777
```

Register it once inside the Box and every harness can call it:

```bash theme={null}
box exec bx_f7k2q9hd "claude mcp add --scope user mine --transport http http://127.0.0.1:7777/mcp"
```

The registration lives in the Box home, so it survives `box stop`, `box resume`, and `box fork`; the tunnel does not, and has to be started again next time you want the agent to reach you. A local model works the same way: `box forward <id> --reverse --local 11434` puts Ollama on `http://127.0.0.1:11434` inside the Box, ready for any harness pointed at that base URL. See [`box forward --reverse`](/box/cli-reference#reverse-reach-your-machine-from-the-box) for the flags and the redial behaviour.

Products that do not ship the CLI get the same tunnel in three lines, because the CLI is only wrapping stock OpenSSH:

```bash theme={null}
# 1. authorize your public key on the Box; the reply carries machineIp
curl -s -X POST https://ascii.dev/api/box/v1/boxes/$BOX_ID/sshkey \
  -H "Authorization: Bearer $BOX_API_KEY" -H 'Content-Type: application/json' \
  -d "{\"key\": \"$(cat ~/.ssh/id_ed25519.pub)\"}"
# 2. open the reverse tunnel and leave it running
ssh -o ExitOnForwardFailure=yes -i ~/.ssh/id_ed25519 -N -R 7777:127.0.0.1:7777 user@<machineIp>
```

### Skills

A skill is a folder with a `SKILL.md` (frontmatter `name` and `description`, then the instructions). Every harness on a Box already has a skills directory with the `box` skill in it; add yours next to it:

| Harness     | Skills directory                                                                |
| ----------- | ------------------------------------------------------------------------------- |
| Claude Code | `~/.claude/skills/<name>/SKILL.md`                                              |
| Codex       | `~/.codex/skills/<name>/SKILL.md`                                               |
| pi          | `~/.pi/agent/skills/<name>/SKILL.md`                                            |
| OpenCode    | `~/.config/opencode/skills/<name>/SKILL.md`                                     |
| Prime Agent | `~/.prime/agent/skills/<name>/SKILL.md`                                         |
| Kimi Code   | `~/.kimi-code/skills/<name>/SKILL.md` (also `~/.agents/skills/<name>/SKILL.md`) |

### Command-line tools

Anything on `PATH` is a tool. Put a script in `~/.local/bin` or `/usr/local/bin` (or `npm i -g`, `pip install`, `apt install` it) and ask for it by name; all six harnesses run it through their shell tool.

### Custom in-process tools and extensions

For a tool that should show up as a native function call rather than a shell command:

| Harness     | Mechanism                                                                                                      | Path                                                                                           |
| ----------- | -------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| pi          | extension calling `pi.registerTool({...})`                                                                     | `~/.pi/agent/extensions/<name>.ts`                                                             |
| Prime Agent | same extension API                                                                                             | `~/.prime/agent/extensions/<name>.ts`                                                          |
| OpenCode    | `tool({description, args, execute})` from `@opencode-ai/plugin` (dependencies install themselves on first run) | `~/.config/opencode/tools/<name>.ts`                                                           |
| Claude Code | hooks in `~/.claude/settings.json`, plugins                                                                    | see [Claude Code docs](https://code.claude.com/docs/en/hooks)                                  |
| Codex       | hooks in `~/.codex/hooks.json`                                                                                 | see [Codex docs](https://learn.chatgpt.com/docs/hooks)                                         |
| Kimi Code   | `[[hooks]]` in `~/.kimi-code/config.toml`, plugins                                                             | see [Kimi Code docs](https://www.kimi.com/code/docs/en/kimi-code-cli/customization/hooks.html) |

### What is shared, what persists

* **Config is per Box, not per conversation.** Every parallel conversation on a Box, whatever its harness, reads the same home directory: one `AGENTS.md`, one skills folder, one MCP list. Per-user rules need per-user Boxes, or rules stated in the prompt.
* **Everything in `/home/user` is captured on stop.** Rules, MCP registrations, skills, extensions, tools you installed under home, and the harness sessions all come back on `box resume` and `box fork`. Tools installed outside home (`apt`, `/usr/local`) are part of the system snapshot too.
* **Bake it once.** An [environment](/box/environments) setup script or a [named snapshot](/box/snapshots) gives every new Box the same rules, tools, and servers from the first prompt.

## Bring your own harness

Box's built-in harnesses are ordinary binaries on `PATH` with the same credentials the agent server uses. Three ways to go beyond `box prompt`:

* **Drive a built-in harness yourself.** `box ssh` or `box exec` and run `claude`, `codex`, `pi`, `opencode`, `prime-agent`, or `kimi` directly, in any mode they support. The agent server does not lock the files or the processes.
* **Install a harness Box does not ship.** `box exec "npm i -g <harness>"` (or bake it into an environment or snapshot) and drive it over `box exec` or SSH. It coexists with the built-in ones and reads the same per-Box `-e` keys.
* **Run your own agent loop.** Put a small HTTP daemon in the Box and talk to it directly: the [Platform Guide](/box/platform-guide#bring-your-own-harness-the-daemon-pattern) walks through it. `box host` gives it a URL, [Webhooks](/box/webhooks) tell your control plane when the Box is up.

Whatever you run, `box events`, attachments under `~/attachments`, [desktop streaming](/box/desktop-streaming), snapshots, and forks keep working around it.

## Reference

* CLI flags: [`box prompt`](/box/cli-reference#box-prompt-id-prompt), [`box conversations`](/box/cli-reference#box-conversations-id), [`box steer`](/box/cli-reference#box-steer-id-message), `box events --convo`, `box interrupt --convo`
* API: [Prompt](/box/api/reference/agent/prompt-box-agent), [Conversations](/box/api/reference/agent/list-box-conversations), [Events](/box/api/reference/agent/list-box-events), [Steer](/box/api/reference/agent/steer-a-running-turn), [Interrupt](/box/api/reference/agent/interrupt-running-agent-work), with the same `new`, `conversationId`, and `conversation` fields
* SDKs: [Python](/box/sdks/python), [TypeScript](/box/sdks/typescript)
* Building a product on top: [Use in code](/box/use-in-code), [Platform Guide](/box/platform-guide)
