# Run code from an AI agent in an isolated VM

> Run an AI agent's commands in a microVM of its own: create a sandbox, run each command with a timeout, move files in and out, and delete it, with what it costs.

URL: https://prized.dev/docs/sandboxes-run-agent-code

Code an agent writes can delete files, fill a disk or loop forever, so run it away from the machine that runs the agent. A sandbox is a microVM made for that:

* **Its own kernel.** A separate virtual machine, not a container sharing a kernel.
* **No sudo.** Commands run as the user `prized`, so there is no `apt`; `pip` installs into a venv.
* **Outbound only.** It reaches the internet, nothing reaches it from outside, and sandboxes cannot reach each other.
* **Gone when you delete it,** disk and memory included.

## 1. Get an API key [#1-get-an-api-key]

[Open Sandboxes](https://prized.dev/onboarding/sandboxes) and choose **Create API key**, or make one under **Dashboard → Settings → API keys** with **Full access**, or **Custom** with **Sandboxes: Write**. Then, where your agent runs, with `curl` and `jq` installed:

```bash
export PRIZED_API_KEY=dcp_…
API=https://api.prized.dev/api/v1
AUTH="Authorization: Bearer $PRIZED_API_KEY"
```

## 2. Create a sandbox [#2-create-a-sandbox]

```bash
SBX=$(curl -s -X POST "$API/sandboxes" -H "$AUTH" -H "Content-Type: application/json" -d '{"template":"prized-base-v1","resources":{"vcpu":2,"memoryMb":4096},"maxRuntimeMin":60}' | jq -er '.sandbox.id // error(.error)') &&
while [ "$(curl -s "$API/sandboxes/$SBX" -H "$AUTH" | jq -r .sandbox.state)" = starting ]; do sleep 1; done &&
TOOLBOX=$(curl -s "$API/sandboxes/$SBX" -H "$AUTH" | jq -er .sandbox.toolboxBase) &&
curl -sS --retry 5 --retry-all-errors --fail -o /dev/null -X POST "$TOOLBOX/process/execute" -H "$AUTH" -H "Content-Type: application/json" -d '{"command":"true"}'
```

`prized-base-v1` has Python 3, Node.js 22 and git; `prized-agent-v1` adds Claude Code and Codex ([Templates](https://prized.dev/docs/sandboxes#templates)). `maxRuntimeMin` deletes the sandbox an hour after the create, so one your agent forgets stops costing money.

The `&&`s stop at the first failure, so a refused create prints its error and sends nothing else. The last line runs `true` until the sandbox takes commands: for a moment after `running`, a request can still answer `409` or `502` ([The edge](https://prized.dev/docs/sandboxes-api#the-edge)). Retry only a harmless command like this one, since after a `502` the command may have run.

## 3. Run the agent's command [#3-run-the-agents-command]

```bash
CMD='python3 -c "print(sum(range(10)))"'
curl -s -X POST "$TOOLBOX/process/execute" -H "$AUTH" -H "Content-Type: application/json" -d "$(jq -n --arg c "$CMD" '{command: $c, timeout: 120}')"
# {"stdout":"45\n","stderr":"","exitCode":0,"durationMs":24}
```

`jq --arg` passes the command through unchanged, quotes included; hand `stdout`, `stderr` and `exitCode` back to the agent as the tool's result.

* **A failed command still answers `200`;** read `exitCode`.
* **`timeout`** is in seconds, 300 unless set and 3,600 at most. At the limit, the command and the processes it started are killed (`"exitCode": -1, "timedOut": true`); one that left its process group, such as with `setsid`, runs on until you delete the sandbox.
* **State carries over** from one command to the next. Commands run in `/home/prized/work` unless they name another `cwd`, and keep the first 4 MiB of each output stream.

## 4. Move files in and out [#4-move-files-in-and-out]

Here the agent's script `main.py` goes in and runs, and the `out.csv` it writes comes back:

```bash
curl -sS --fail -X POST "$TOOLBOX/files/upload?path=/home/prized/work/main.py" -H "$AUTH" --data-binary @main.py
curl -s -X POST "$TOOLBOX/process/execute" -H "$AUTH" -H "Content-Type: application/json" -d '{"command":"python3 main.py"}' | jq .exitCode
curl -sS --fail "$TOOLBOX/files/download?path=/home/prized/work/out.csv" -H "$AUTH" -o out.csv
```

Paths are absolute and inside `/home/prized`, up to 100 MiB a file ([Sandbox API: Files](https://prized.dev/docs/sandboxes-api#files)). With `curl --fail`, a failed copy says so and exits non-zero, rather than saving the error as `out.csv`; delete the sandbox only after the download worked.

## 5. Delete it [#5-delete-it]

```bash
curl -sS --fail -X DELETE "$API/sandboxes/$SBX" -H "$AUTH" > /dev/null
```

> [!WARNING]
> Deleting erases the sandbox's disk and memory, with no undo. Download what you need first.

## From the CLI [#from-the-cli]

An agent with a shell can use the `prized` CLI (v0.21.0 or later), which reads `PRIZED_API_KEY` ([CLI](https://prized.dev/docs/cli#sandboxes)):

```bash
ID=$(prized sandbox create --vcpu 2 --memory 4 --max-runtime 1h)
prized sandbox cp main.py $ID:main.py
prized sandbox exec $ID --timeout 2m -- python3 main.py
prized sandbox cp $ID:out.csv out.csv &&
prized sandbox rm $ID
```

`exec` exits with the command's code, or 124 on a timeout.

## What it costs [#what-it-costs]

A running sandbox bills per second, with no minimum: $0.04 per vCPU-hour, $0.0125 per GB-hour of memory, and $0.0001 per GB-hour of disk ([Billing: Sandboxes](https://prized.dev/docs/billing#sandboxes)).

|                           | 2 vCPU, 4 GB | 4 vCPU, 8 GB (the default) |
| ------------------------- | ------------ | -------------------------- |
| Running, per hour         | $0.131       | $0.261                     |
| One sandbox for 5 minutes | $0.011       | $0.022                     |
| 1,000 of them             | $10.92       | $21.75                     |

Both include the 10 GB disk; paused, a sandbox bills only its disk and saved memory.

## On the free credit [#on-the-free-credit]

The $30 of free credit covers about 2,700 five minute sandboxes at 2 vCPU. Until the workspace has paid in, it runs 4 sandboxes at once, 8 vCPU and 16 GB in total, each up to 2 vCPU, 4 GB and 60 minutes. A top-up or a plan lifts this to the full quotas and a lifetime of up to 24 hours, paused time included ([Free credit limits](https://prized.dev/docs/sandboxes#free-tier)).
