# Running agents

> Start Claude Code, Codex or opencode in a moatbox space, follow the run, steer it, continue it, and watch the agent's browser screen.

moatbox runs coding agents inside spaces: Firecracker microVMs with their own disk, on moatbox servers or on a server your team links. A run is one agent working on one task in one space. This page covers starting a run from the dashboard or the API, following it, steering it while it works, continuing it after it ends, and watching the agent's browser on a live screen. To create a space first, see [Spaces](https://docs.moatbox.dev/spaces.md).

## Supported agents

| `agent` value | Dashboard label |
| --- | --- |
| `claude` | Claude Code |
| `codex` | Codex |
| `opencode` | opencode |

moatbox starts each run in a gVisor sandbox container inside your space. All agents share the space's `/workspace` directory, and each agent keeps its own `/home/agent`. The agent CLI runs with its own permission prompts turned off (`--dangerously-skip-permissions` for Claude Code, `--dangerously-bypass-approvals-and-sandbox` for Codex, `--auto` for opencode). moatbox's gateway decides which actions wait for a person instead; see [Approvals](https://docs.moatbox.dev/approvals.md).

An agent needs a model account before it can do any work: a subscription sign-in inside the space, or provider keys from a team AI profile. See [Signing agents in](https://docs.moatbox.dev/agent-sign-in.md).

## Start a run from the dashboard

1. Open the space. Its **Agents** page lists the space's runs.
2. Click **New agent**.
3. Type the task in the box ("What should the agent do? Drop files here to attach them.").
4. Adjust the settings in the bar under the box, if you need to.
5. Click **Start** or press Enter. Shift+Enter adds a new line.

The dashboard opens the run's page once the run has started.

The bar under the task box holds these settings:

- **Agent and model menu.** The button reads like `Claude Code / Default`. Pick the agent on the left (Claude Code is preselected), then a model. **Default** leaves the choice to the agent's own configuration. For a model the list doesn't show, type its id in the field at the bottom and press Enter (opencode takes `provider/model`). An agent that isn't signed in carries a **sign in** tag.
- **Effort.** A slider in the same menu with the levels the chosen model offers. `default` keeps the agent's own setting.
- **Fast mode.** A switch, shown for models that have one. Only Claude Code and Codex support it.
- **Working directory.** The button that shows `/workspace`. Click it to browse folders and pick where the agent starts. If the space is asleep, the dashboard can't list folders, so you type a path under `/workspace`.
- **Screen.** Gives the agent a browser on a screen you can watch and take over. The button reads **Screen on** when set. See [Watch the agent's screen](#watch-the-agents-screen).
- **Branch.** Optional. The placeholder shows the branch the workspace is on.
- **+** attaches files. You can also drop or paste files into the box. The dashboard uploads each file to `/home/agent/uploads/` in the space (2 GiB at most per file) and appends an "Attached files:" list with their paths to your task.

### Working directory and branch

The working directory must be `/workspace` or an existing folder under it, with no `..` in the path. It defaults to `/workspace`.

When you set a branch, moatbox switches the git checkout in the working directory to that branch before the agent starts, and creates the branch if it doesn't exist. moatbox runs the switch with git hooks turned off. Without a branch, the run records the branch the checkout is on.

moatbox refuses a branch while another run of the same agent is live in the space, because the switch would move that run's checkout. Wait for that run to end or stop it.

### Sleeping and blocked spaces

Starting a run wakes a sleeping space. If the space takes longer than about 20 seconds to wake, the API answers `503` with "the space is waking up; start the run again in a minute" and a `Retry-After: 15` header.

A space that is suspended (`423`), still being set up (`409`), or read-only because its plan isn't active (`402`) refuses new runs. The dashboard shows **New agent** only on an active space.

## Start a run from the API

Send `POST /api/v1/spaces/{space}/runs` with an API key that has the `run` action (see [API keys](https://docs.moatbox.dev/api-keys.md#actions)).

```bash
curl -X POST https://app.moatbox.dev/api/v1/spaces/my-space/runs \
  -H "Authorization: Bearer $MOATBOX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"agent": "claude", "task": "fix the failing test", "branch": "fix-test", "effort": "high"}'
```

moatbox answers `202 Accepted` with the run id:

```json
{"id": "3f9a1c2e"}
```

| Field | Required | Meaning |
| --- | --- | --- |
| `agent` | yes | `claude`, `codex` or `opencode`. |
| `task` | yes | The prompt. |
| `model` | no | Model id, passed to the agent CLI. Left out, the agent uses its own default. Up to 100 characters: letters, digits, `.`, `_`, `:`, `/`, `@`, `-`, starting with a letter or digit. |
| `effort` | no | Up to 16 lowercase letters. moatbox passes it as `--effort` to Claude Code, `model_reasoning_effort` to Codex, and `--variant` to opencode. |
| `speed` | no | `fast`, or leave it out. Claude Code and Codex only. |
| `cwd` | no | Working directory. Defaults to `/workspace`. |
| `branch` | no | Git branch to switch to, created when missing. |
| `desktop` | no | `true` gives the agent a browser on a watchable screen. |
| `resume` | no | Id of an ended run to continue. See [Continue a run](#continue-a-run). |
| `env` | no | Environment variables for this run: at most 50, each value at most 32 KiB. moatbox refuses names it reserves. |
| `broker` | no | Integrations the run may use, such as `["github"]`. The gateway gives the agent a placeholder and puts the real key on requests to that service. See [Integrations](https://docs.moatbox.dev/integrations.md). |
| `hold` | no | MCP tool name patterns to hold for approval, such as `["linear.*"]`. See [Approvals](https://docs.moatbox.dev/approvals.md). |
| `allow` | no | Extra hosts the run may reach, added to the defaults. |
| `ai_profile` | no | Name or id of a team AI profile. The run's model keys then go through the moatbox AI gateway. |

A request without `agent`, or without `task`, gets `400` with "agent and task are required". A sign-in run takes no task; see [Signing agents in](https://docs.moatbox.dev/agent-sign-in.md#sign-in-over-the-api).

The moatbox MCP server offers these operations as the `start_run`, `list_runs`, `get_run`, `get_run_timeline`, `send_message` and `stop_run` tools. Its `start_run` takes only `agent`, `task`, `model`, `effort`, `cwd` and `branch`; use the API for the other fields. See [MCP server](https://docs.moatbox.dev/mcp.md).

## Follow a run

### In the dashboard

The run page header shows the model, the working directory, the branch with a marker for "Uncommitted changes" or "Clean", and token usage. **Info** expands the details: who launched it, start and end times, the image and the run id.

- **Conversation** shows the agent's messages and tool calls as they arrive.
- **Log** shows the agent's raw output lines, with **Follow**, **Expand all** and **Copy**.
- The **Workspace** panel has three tabs: **Files**, **Changes** (changed files in the workspace), and **Terminal** (the commands the agent ran). On a narrow screen, **Workspace** is a tab next to the other two.

The **Agents** page lists runs with their task, directory and branch, who launched them, status, duration and start time. Filter by **All**, **Waiting**, **Running**, **Failed** or **Done**. The status badge reads **Running**, **Waiting for you** (an action is held for approval, see [Approvals](https://docs.moatbox.dev/approvals.md)), **Done** (exit code 0) or **Failed** (any other exit code). SSH sessions and commands, which the gateway records, show under an **SSH** tab when there are any; see [Connect to a space](https://docs.moatbox.dev/connect.md).

The **Live** page lists running agents across all your spaces. Each row shows **Working**, **Waiting on approval** or **Box asleep**, the latest event, and a **Stop** button. A waiting run gets a **Review** link; an asleep one gets **Wake**.

When a run ends, moatbox compares the workspace with its state at the start and records the change on the timeline as a `diff` event. The patch stops at 1 MiB.

### From the API

```bash
curl https://app.moatbox.dev/api/v1/spaces/my-space/runs?limit=20 \
  -H "Authorization: Bearer $MOATBOX_API_KEY"
```

| Request | Returns |
| --- | --- |
| `GET /api/v1/spaces/{space}/runs` | `{"runs": [...]}`, newest first. `limit` takes 1 to 200 (default 50); `before` takes a run id. |
| `GET /api/v1/spaces/{space}/runs/{run}` | One run. |
| `GET /api/v1/spaces/{space}/runs/{run}/timeline` | `{"run": {...}, "events": [...]}` |
| `GET /api/v1/spaces/{space}/stream` | Server-sent events for the whole space. Resume with `?after=<event id>` or the `Last-Event-ID` header. Filter on `run_id` yourself. |

A run object carries `id`, `agent`, `task`, `launched_by`, `started`, `ended` (empty while it runs), `exit`, `model`, `effort`, `speed`, `cwd`, `branch`, `parent` (the run it continues) and `session` (the agent's own session id). Agent sign-ins appear in the list too, with `"kind": "login"`.

Each event has `id`, `run_id`, `ts`, `kind`, `summary`, `text`, and `evidence`: `observed` when moatbox saw it happen, `reported` when it comes from the agent's own output. These kinds mark a run's life:

| `kind` | Meaning |
| --- | --- |
| `run.start` | The run started. |
| `user.message` | Someone sent the agent a message. The summary starts with who. |
| `run.interrupt` | Someone interrupted the agent. |
| `run.stop` | Someone stopped the run. |
| `diff` | The workspace changes, recorded when the run ends. |
| `run.error` | moatbox hit an error running the agent. |
| `run.end` | The run ended. The summary reads `exit <code>`. |

For webhooks on these events, see [Events, webhooks and telemetry](https://docs.moatbox.dev/events-and-webhooks.md).

## Steer a running agent

| Action | Dashboard | API | Agents |
| --- | --- | --- | --- |
| Send a message | Box under the conversation, **Send** | `POST .../runs/{run}/messages` with `{"text": "..."}` | Claude Code |
| Interrupt | **Interrupt**, or Esc | `POST .../runs/{run}/interrupt` | Claude Code |
| Stop | **Stop**, or ⌘. or Ctrl+. | `POST .../runs/{run}/stop` | All |

### Send a message

While a Claude Code run works, type in the box at the bottom ("Message the agent while it works") and click **Send**. moatbox writes the message into the agent's session and records it as a `user.message` event with your name.

```bash
curl -X POST https://app.moatbox.dev/api/v1/spaces/my-space/runs/3f9a1c2e/messages \
  -H "Authorization: Bearer $MOATBOX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "also update the changelog"}'
```

Codex and opencode run in one-shot mode and read no messages while they work. The dashboard says "You can reply once this run ends", and the API answers `409`. Once Claude Code has answered every message and is finishing, a new message also gets `409` ("reply to start a follow-up run"). Continue the run instead.

### Interrupt

**Interrupt** (or Esc, when you aren't typing in a non-empty field) stops Claude Code's current step. The run stays open, its status shows **Waiting for you**, and the agent waits for your next message. For Codex and opencode, the API answers `409`: moatbox has no control channel into those agents, so you stop the run and continue it.

### Stop

**Stop** (or ⌘. or Ctrl+.) ends the run for any agent. The **Live** page has a **Stop** button per run too. moatbox records `run.stop` with your name, stops the sandbox, saves the workspace diff and ends the run with the agent's exit code. Stopping a run that has already ended gets `409`.

## After a run ends

The run page swaps the controls for **Changes**, **Terminal**, **Retry** and **Fork**, and the box at the bottom turns into **Continue**.

### Continue a run

Type a reply in the box ("Reply to continue in a new run that resumes this session") and click **Continue**. You can change the model, effort and fast mode in the same box. moatbox starts a new run that resumes the agent's own session: `--resume` for Claude Code, `codex exec resume` for Codex, `--session` for opencode. The new run's page shows "Follows" with the earlier run's id, and the earlier run shows "Continued in".

From the API, start a run with `resume` set to the earlier run's id and your reply as `task`:

```bash
curl -X POST https://app.moatbox.dev/api/v1/spaces/my-space/runs \
  -H "Authorization: Bearer $MOATBOX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"agent": "claude", "task": "now add a test for the edge case", "resume": "3f9a1c2e"}'
```

The new run keeps the earlier run's working directory, model, effort and speed unless you set them. moatbox refuses to continue when:

- the agent differs from the earlier run's agent,
- the earlier run is still running (send it a message instead),
- the earlier run reported no agent session. The dashboard then says "This run reported no agent session, so it can't be continued".

### Retry, Terminal, Changes, Fork

- **Retry** starts a new run with the same agent, task, model, effort, fast mode, directory and branch, in a fresh session.
- **Terminal** offers an SSH command for a shell in the space. Use `ssh moatbox-<space>`, the host that `moatbox connect` writes to your SSH config; see [Connect to a space](https://docs.moatbox.dev/connect.md).
- **Changes** opens the changed files in the Workspace panel.
- **Fork** copies the space into a new one. Only team owners and admins can fork.

## Watch the agent's screen

Turn on **Screen** when you start the run, or send `"desktop": true` to the API. moatbox starts a Chromium browser on a virtual desktop beside the agent's sandbox and gives the agent a `browser` MCP server to drive it. The browser's page loads leave through the space's gateway, like the agent's own traffic. moatbox records each browser action with a screenshot and redacts text typed into password-like fields. It holds navigations and clicks whose URL or target mentions words such as pay, checkout, password, send, delete, publish or deploy, so a person can approve them.

The run page shows a **Screen** section with the live desktop:

| Status line | Meaning |
| --- | --- |
| "Watching. Only the agent acts." | You see the screen; the agent drives. |
| "You have control. The agent's browser waits." | You drive the browser; the agent's browser actions wait. |
| "(teammate's name) has control. The agent's browser waits." | A teammate drives the browser. |
| "The run ended. The screen is closed." | The run is over. |

- **Take control** lets you use the mouse and keyboard. One person holds control at a time, and screens narrower than 768 pixels get a view-only screen.
- **Hand back** returns control to the agent. Control also lapses after 5 minutes without input from you.
- **Hide** and **Show** collapse and restore the screen. **Reconnect** appears if the connection drops and the automatic retries fail.

moatbox records on the run's timeline each viewer who connects and how long they stayed, and takes a screenshot every 5 seconds while someone watches. Opening the screen, taking control and handing it back go into the space's audit log.

The screen opens only from the dashboard in a signed-in browser. An API key can read `GET /api/v1/spaces/{space}/runs/{run}/desktop`, which returns `{"live": false}` when the run has no screen, but it can't take control or open the screen stream.
