# Prompt an agent remotely

> Prompt Claude Code, Codex, OpenCode or pi on your box from your machine or any script, on the model and backend you pick, follow the run as it happens, and interrupt it; the run lives on the box, so nothing depends on your laptop staying open.

URL: https://prized.dev/docs/remote-agents

## Prompt the agent [#prompt-the-agent]

```bash
prized prompt --provider claude "fix the failing tests in app/ and run them again"
```

This starts a headless Claude Code run on the box (`claude -p`, in your home directory), streams its events to your terminal, and exits when the agent is done. The first prompt installs three small helper scripts on the box; the first OpenCode or pi prompt also installs that agent ([OpenCode and pi](https://prized.dev/docs/agents#opencode-and-pi)).

| Command                                                              | What it does                                                                                                                                                                               |
| -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `prized prompt --provider claude "…"`                                | Run Claude Code on the default box and follow it. `--provider codex`, `opencode` or `pi` runs that agent.                                                                                  |
| `prized prompt box-a7f3 --provider codex --cwd app "…"`              | Run on a named box, in `~/app` (`--cwd` is relative to your home, or absolute).                                                                                                            |
| `prized prompt --provider claude --model opus --effort high "…"`     | Pick the model and the reasoning effort (`low`, `medium`, `high`).                                                                                                                         |
| `prized prompt --provider codex --model deepseek/deepseek-flash "…"` | Run on another backend: `amazon-bedrock/…`, `openrouter/…` or `deepseek/…` ([Models and backends](#models-and-backends)).                                                                  |
| `prized prompt models`                                               | The known `--model` values per agent and backend, with the efforts each takes (`--provider` narrows it).                                                                                   |
| `prized prompt --provider claude --continue "…"`                     | Continue the provider's newest session on the box instead of starting fresh.                                                                                                               |
| `prized prompt --provider claude --conversation cv_… "…"`            | Continue that conversation with the provider you name. See [Conversations](#conversations).                                                                                                |
| `prized prompt --provider claude --auto "…"`                         | Let the agent edit and run commands without approvals. See [Approvals](#approvals).                                                                                                        |
| `prized prompt --provider claude --detach "…"`                       | Start the run and print its id; follow it later with `prized events`.                                                                                                                      |
| `prized prompt --provider claude --queue "…"`                        | Run after the conversation's turn in progress instead of refusing.                                                                                                                         |
| `cat brief.md \| prized prompt --provider claude -`                  | Read the prompt from stdin.                                                                                                                                                                |
| `prized prompt conversations`                                        | Every conversation on the box, most recently active first, with its prompt count and whether a turn is running.                                                                            |
| `prized prompt ls`                                                   | Every run on the box, newest first (at most 50), with its state and conversation; `--conversation cv_…` keeps to one.                                                                      |
| `prized events --follow`                                             | Follow every run in progress until none is left (or show the newest run); `--conversation cv_…` keeps to one conversation, `--run pr_…` follows one run, `--after N` starts after event N. |
| `prized interrupt`                                                   | Stop every run in progress (TERM to each one's process group, KILL after 5 s); `--conversation cv_…` stops one conversation's, `--run pr_…` one run.                                       |

* **Output:** the agent's text, each tool call and result as one dim line, and a last line with the verdict and event count; `-v` adds the provider's system events.
* **Exit code:** 0 when the run ends cleanly; 1 when the provider's last verdict is an error (one it retried past does not count), it exits non-zero, or the run was interrupted; 130 on Ctrl-C, which also interrupts the run on the box (`prized events --follow` leaves the run alone on Ctrl-C).
* **The prompt travels as a file**, never on a command line, so it stays out of the box's process list and shell history.
* **Several runs at once:** `prized events --follow` names each run as it starts and, while more than one is in progress, starts each line with its conversation id; its exit code is the worst of the runs it followed.
* **Waking:** `prized prompt` wakes a suspended box; `prized events`, `prized prompt ls`, `prized prompt conversations` and `prized interrupt` do not.

## Sign in first [#sign-in-first]

The agent runs as you, with a credential already on the box. Without one, `prized prompt` refuses with `provider_not_signed_in` and names what to set. It looks in a login shell for:

| Agent                  | Credential                                                                                                                                                                                |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Claude Code            | `~/.claude/.credentials.json` (hand yours over with `prized agents handoff claude`), `CLAUDE_CODE_OAUTH_TOKEN`, `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, or `CLAUDE_CODE_USE_BEDROCK` |
| Codex                  | `~/.codex/auth.json` (`prized agents handoff codex`), `OPENAI_API_KEY` or `CODEX_API_KEY`                                                                                                 |
| OpenCode               | Nothing required: with no key it offers only its free models; `opencode auth login` on the box or a provider key opens the rest                                                           |
| pi                     | `~/.pi/agent/auth.json` (`pi`, then `/login` on the box) or any provider key, such as `ANTHROPIC_API_KEY` or `OPENROUTER_API_KEY`                                                         |
| Any agent on a backend | That backend's key ([Keys for other agents and backends](https://prized.dev/docs/credentials#keys-for-other-agents-and-backends))                                                                           |

## Models and backends [#models-and-backends]

`--model` takes the agent's own model ids and aliases. Written as `<backend>/<model id>`, it runs the agent on that backend with the key you set on the box as a Secret or env var:

| Prefix            | Backend                            | Claude Code                            | Codex                            | Example                                         |
| ----------------- | ---------------------------------- | -------------------------------------- | -------------------------------- | ----------------------------------------------- |
| `amazon-bedrock/` | Amazon Bedrock in your AWS account | Bedrock inference profile ids and ARNs | Bedrock's `openai.*` models      | `amazon-bedrock/us.anthropic.claude-sonnet-4-6` |
| `openrouter/`     | OpenRouter                         | Any OpenRouter id                      | Ids that serve the Responses API | `openrouter/anthropic/claude-opus-5.5`          |
| `deepseek/`       | DeepSeek                           | `deepseek-flash`, `deepseek-v4-pro`    | Same                             | `deepseek/deepseek-flash`                       |

OpenCode and pi already name models as `provider/model`, so the same strings work for them unchanged, along with every other provider they support (`opencode models` and `pi --list-models` on the box list them). The prefix applies per run and never edits the agent's config files; a model with no prefix runs on whatever the agent is set up for.

`prized prompt models` lists known ids per agent and backend. Bedrock grants model access per AWS account and region: a model your account has not enabled fails with Bedrock's error.

`--effort` maps onto each agent's own control: `claude --effort`, Codex's `model_reasoning_effort`, `opencode run --variant` (a model without that variant ignores it), `pi --thinking` (clamped to what the model supports). DeepSeek models take `low` and `high`.

## Approvals [#approvals]

By default the agent can edit files but not act freely, since a headless run cannot ask you; `--auto` lifts that, which "run the tests and fix them" needs. Each agent draws the line its own way:

| Agent       | Default                                                                                                           | `--auto`                                                                  |
| ----------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| Claude Code | `claude --permission-mode acceptEdits`: edits allowed, commands denied                                            | `claude --dangerously-skip-permissions`                                   |
| Codex       | Its `workspace-write` sandbox: edits and commands inside the working directory, no network                        | `codex --dangerously-bypass-approvals-and-sandbox`                        |
| OpenCode    | Shell commands set to ask, which a headless run answers with a rejection; edits and its other tools as configured | `opencode run --auto`: every permission not explicitly denied is approved |
| pi          | pi has no approvals, so its `bash` tool is left out: `read`, `edit`, `write`, `grep`, `find`, `ls`                | The same tools plus `bash`                                                |

MCP tools stay available to OpenCode and pi in both modes.

> [!WARNING]
> `--auto` means no approvals at all: the agent runs any command and edits any file your account on the box can, with your credentials and synced projects in reach. Use it for work you would let the agent do unattended, and interrupt it when it strays.

## Continuity [#continuity]

`--continue` resumes the newest session of the same provider on the box, so "now also update the docs" works; `--conversation` resumes a given one. Each agent resumes in its own way: `claude --resume <id>`, `codex exec resume <id>`, `opencode run --session <id>`, `pi --session-id <id>`. Claude Code and pi keep sessions per working directory, so continue from the same `--cwd`. `prized prompt ls --json` shows each run's `sessionId` and `resumedFrom`. With no earlier session on the box, `--continue` refuses (exit 5, `no_session_to_continue` on the API) rather than starting fresh.

## Conversations [#conversations]

Every prompt is a turn of a conversation: one provider session, with the agent's memory of the turns before it. A conversation's id looks like `cv_0123456789ab`; `prized prompt` prints it as the run starts, `--json` and the API answer it as `conversationId`, and `prized prompt conversations` lists them.

| Flag                  | The turn's conversation                                                    |
| --------------------- | -------------------------------------------------------------------------- |
| None, or `--new`      | A new one, which runs beside the others.                                   |
| `--continue`          | The provider's newest conversation on the box ([Continuity](#continuity)). |
| `--conversation cv_…` | That one; explicitly name its provider with `--provider`.                  |

* **Choose the provider locally:** `--provider` is required on every prompt, including `--conversation`. If omitted, the CLI exits 2 before reading stdin or contacting the box. Conversation files on the box are mutable, so they cannot authorize which agent receives your next prompt. This does not protect prompts or credentials from a compromised box.
* **One provider per conversation:** a prompt for a conversation with another `--provider` refuses (exit 5, `provider_mismatch`). All four agents hold conversations the same way, and the first turn of OpenCode or pi installs it if needed.
* **Model per turn:** each turn takes its own `--model` and `--effort`, a [backend](#models-and-backends) included; the conversation lists its newest turn's model.
* **Pauses:** conversations are files on the box, so they survive a pause and ride snapshots and forks; continue one by id afterwards.

## Parallel runs and the queue [#parallel-runs-and-the-queue]

A box runs several conversations at once, up to its limit: one run in progress per 2 GB of the box's memory, at most 16 ([Limits](https://prized.dev/docs/limits#prompting-an-agent-remotely)). A new run beyond it answers `prompt_limit_reached` (exit 6). To change the limit, write a number from 1 to 64 to `~/.prized/prompts/max-runs` on the box; each run is a full agent process, so raise it only on a box with memory to spare.

A conversation takes one turn at a time. A prompt for a conversation with a turn in progress answers `prompt_in_progress` (exit 6); with `--queue` it waits and runs when that turn ends, continuing its session. `--queue` with no other flag joins the provider's newest turn in progress, so a `--queue "then lint"` right after a prompt still follows it; with nothing in progress it starts a new conversation.

Two commands on the box do the same, so a setup script or the agent itself can drive it:

```bash
queuePrompt "then run the linter and fix what it finds"
stopAgent
```

| Command           | What it does                                                                                                                                                                                                                                                     |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `queuePrompt "…"` | Queue a prompt (from its arguments, or stdin): inside a run, behind that run's conversation; elsewhere behind the newest run in progress; with nothing running, after the next run to finish. `PRIZED_CONVERSATION=cv_… queuePrompt "…"` picks the conversation. |
| `stopAgent`       | Stop every run in progress; `stopAgent cv_…` stops one conversation's.                                                                                                                                                                                           |

Both are in `~/.prized/bin`, on the PATH of login shells after the first prompt. A run sees its own conversation and run id as `PRIZED_CONVERSATION` and `PRIZED_RUN`.

* **A queued prompt keeps its request:** a `--queue` prompt keeps its own model, effort, working directory and `--auto`; a `queuePrompt` one inherits them from the run it follows.
* **An interrupted run** leaves its conversation's queue for that conversation's next turn.

## Where a run lives on the box [#where-a-run-lives-on-the-box]

Each run is a directory on the box, so it survives a pause, rides a snapshot, and reads with `ls` and `tail -f`:

| File in `~/.prized/prompts/<run id>/` | Contents                                                                                                                                                    |
| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `prompt.txt`, `meta.json`             | The prompt as given; provider, model, reasoning effort, and whether `--auto` was on.                                                                        |
| `cmd`, `next`                         | The provider command line for this run, and the one a continuation uses.                                                                                    |
| `cwd`, `pid`, `started_at`, `exit`    | The working directory, the process group id, the start time, the exit code when done.                                                                       |
| `events.jsonl`, `stderr.log`          | The provider's raw event stream (`claude --output-format stream-json`, `codex exec --json`, `opencode run --format json`, `pi --mode json`) and its stderr. |
| `session`, `resume`, `parent`         | The provider's session id once seen; for a continued run, the session it resumed; for a queued one, the run it followed.                                    |
| `conversation`                        | The conversation the run is a turn of.                                                                                                                      |
| `interrupted`                         | Present when `prized interrupt` or `stopAgent` stopped the run.                                                                                             |

`~/.prized/prompts/conversations/<id>/queue/` holds a conversation's queued prompts, one directory each; `~/.prized/prompts/queue/` holds the ones queued with nothing running.

> Run directories are never deleted for you: `rm -r ~/.prized/prompts/pr_…` on the box when you are done with one.

## JSON and JSON Lines [#json-and-json-lines]

`--json` on `prized prompt` prints the started run as one object and does not follow. `--jsonl` follows and prints one normalized event per line; with `prized events --jsonl`, it is the only command that prints more than one JSON document. A follow of several runs (`prized events --follow` without `--run`) adds `runId` and `conversationId` to each event.

```json
{"seq": 7, "type": "tool_use", "text": "Let me run the suite.", "tool": {"name": "Bash", "input": {"command": "npm test"}}, "raw": {…}}
```

| Field  | Meaning                                                                                                                          |
| ------ | -------------------------------------------------------------------------------------------------------------------------------- |
| `seq`  | The event's line number in `events.jsonl`, from 1; paging and `--after` use it (a page holds up to 200 events or 1 MiB).         |
| `at`   | The provider's timestamp, when the line carries one.                                                                             |
| `type` | `system`, `assistant`, `tool_use`, `tool_result`, `result`, `error`, or `raw` for a line the mapping does not know.              |
| `text` | The readable content: the agent's text, a command, a tool's output, the final answer, an error (capped at 8 KiB).                |
| `tool` | For tool events, `{name, input}`; Codex's are `command_execution`, `file_change`, `mcp_tool_call` (`server/tool`), `web_search`. |
| `raw`  | The provider's line as written.                                                                                                  |

The agents differ in what they report, and the mapping keeps one line per event:

* **OpenCode** reports a tool call once, when it finishes, so each is one `tool_result` carrying the tool, its input and its output. The last step's finish is the `result`; an error event is `error`.
* **pi** streams token deltas as `system` events with no text; each finished message is one `assistant` event, tool calls are `tool_use` then `tool_result`, and the run's final answer is the `result`. A failed model call is an `error` event, and pi may retry past it; a run whose last model call failed exits 1, so its status is `failed` in `prized prompt ls` and the API too.

A run object (`prized prompt --json`, `prized prompt ls --json`, `prized events --json`) has `id, provider, model, reasoningEffort, auto, status (running, done, failed, interrupted), exitCode, pid, cwd, startedAt, finishedAt, sessionId, resumedFrom, parent, eventCount, prompt`.

Runs also carry `conversationId`. A conversation object (`prized prompt conversations --json`, under `conversations` beside `running` and `limit`) has `id, provider, model` (its newest turn's), `prompts` (turns so far), `queued`, `running`, `lastRunId, lastStatus, sessionId, lastPrompt` (the first 160 bytes), `createdAt, updatedAt`.

## The edge API [#the-edge-api]

The same surface is HTTP for scripts that hold an API key, under `$EDGE/v1/box/{box}/` with the key as the bearer, found as for [running commands](https://prized.dev/docs/exec#the-edge-api):

| Route                             | What it does                                                                                                                                                                         |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `POST /prompts`                   | Starts or queues a run; `new`, `conversationId` and `continue` pick its conversation. The only route that wakes a suspended box.                                                     |
| `GET /prompts`                    | Lists runs; `conversation=cv_…` keeps to one.                                                                                                                                        |
| `GET /prompts/conversations`      | Lists conversations.                                                                                                                                                                 |
| `GET /prompts/{runId}/events`     | Pages the events, or streams them as Server-Sent Events (`follow=1`, or `Accept: text/event-stream`); a stream holds one of the token's concurrent request slots until the run ends. |
| `GET /prompts/events`             | Streams every run in progress as Server-Sent Events until none is left; `conversation=cv_…` keeps to one.                                                                            |
| `POST /prompts/{runId}/interrupt` | Stops a run.                                                                                                                                                                         |
| `POST /prompts/interrupt`         | Stops every run in progress; `conversation=cv_…` stops one conversation's.                                                                                                           |

Bodies and answers are on [API reference: Prompts and events](https://prized.dev/docs/api#prompts-and-events).

## Errors [#errors]

A failed request answers `{"ok": false, "error": {"code", "message"}}`. Beyond the shared edge codes, these routes add `invalid_provider`, `prompt_required`, `invalid_request`, `prompt_in_progress`, `provider_not_signed_in`, `provider_not_installed` and `no_session_to_continue` ([API reference: Errors](https://prized.dev/docs/api#errors)). `provider_not_installed` also answers an OpenCode or pi install that failed, with the reason (no network, or a checksum that no longer matches the pin). From the CLI, a run in progress exits 6 (`CONFLICT`) and the rest exit 5 (`CHECK_FAILED`) with a hint.

Conversations add `prompt_limit_reached` (the box is at its limit; exit 6 from the CLI) and `provider_mismatch` (`conversationId` names a conversation of another provider; exit 5), and `prompt_in_progress` names the conversation whose turn is in progress.

## Limits [#limits]

Every cap for prompts, runs, and events is on [Limits](https://prized.dev/docs/limits#prompting-an-agent-remotely).

## Compared with `prized agents run` [#compared-with-prized-agents-run]

[`prized agents run`](https://prized.dev/docs/agents) starts the agent interactively in a tmux session you attach to and approve things in; `prized prompt` is the headless form, with a verdict at the end and an API for scripts. Both use the box's sign-in, and both install OpenCode and pi on first use.
