# Run Claude Managed Agents in Prized Sandboxes

> Run each Claude Managed Agents session's tools in a Prized sandbox of its own: a self-hosted environment, a spawn script for Anthropic's worker, and the limits.

URL: https://prized.dev/docs/sandboxes-claude-managed-agents

[Claude Managed Agents](https://platform.claude.com/docs/en/managed-agents/overview) can run an agent's tools on machines you control, through a [self-hosted environment](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes). This guide gives each session its own sandbox: Claude runs on Anthropic's side, and the sandbox runs the agent's tool calls and holds its files.

## How it works [#how-it-works]

1. You start a session on a self-hosted environment, and Anthropic queues it.
2. `ant beta:worker poll`, on a machine you keep running, claims the session and runs `spawn.sh`.
3. `spawn.sh` creates a sandbox, installs Anthropic's `ant` CLI in it without root, and starts `ant beta:worker run`, which runs the session's tool calls.
4. A minute after the session goes idle, the worker exits, and `spawn.sh` saves the output and deletes the sandbox.
5. A message that wakes the session again starts the cycle over, in a new sandbox that gets the session's files back.

Nothing connects in: the poller and the worker call out to `api.anthropic.com` over HTTPS.

## Before you start [#before-you-start]

* **An agent and a self-hosted environment.** Create an agent with [Anthropic's quickstart](https://platform.claude.com/docs/en/managed-agents/quickstart). In the [Claude Console](https://platform.claude.com/workspaces/default/environments), choose **Environments → New → Self-hosted**, then **Generate environment key** on the environment's page.
* **A Prized API key** with **Full access**, or **Custom** with **Sandboxes: Write** ([Sandboxes: Quickstart](https://prized.dev/docs/sandboxes#quickstart)).
* **A poller machine** that stays on, Linux or macOS, with [`ant`](https://github.com/anthropics/anthropic-cli/releases), `jq`, and `prized` v0.21.0 or later ([CLI](https://prized.dev/docs/cli#sandboxes)).

## 1. Set the keys [#1-set-the-keys]

On the poller machine:

```bash
export ANTHROPIC_ENVIRONMENT_ID=env_…
export ANTHROPIC_ENVIRONMENT_KEY=sk-ant-oat01-…
export PRIZED_API_KEY=dcp_…
```

The poller needs the environment key, never your Claude API key.

## 2. Save the spawn script [#2-save-the-spawn-script]

Save this as `spawn.sh` in the folder you run the poller from, and run `chmod +x spawn.sh`.

```bash title="spawn.sh"
#!/bin/bash
# spawn.sh: `ant beta:worker poll --on-work ./spawn.sh` runs this once per work item (a session's run until it goes idle), with the session's ids and the environment key in ANTHROPIC_* variables and the work item as JSON on stdin.
set -euo pipefail
ANT_VERSION=1.40.0
MAX_MIN=60  # the sandbox's lifetime in minutes: up to 1440 after a top-up or a plan
ANTHROPIC_WORK_SECRET=$(jq -r '.secret // empty')
DIR="sessions/$ANTHROPIC_SESSION_ID"  # the session's working directory as work.tgz, and a log per work item
OUT="$DIR/$ANTHROPIC_WORK_ID"
mkdir -p "$DIR"

# Run the session in the background, so the poller can claim the next one now.
(
  # One run of a session at a time, so a follow-up starts from the files the run before it saved.
  # A run ends with its sandbox, so a lock older than that was left by a run that was killed.
  until mkdir "$DIR/lock" 2>/dev/null; do
    find "$DIR/lock" -maxdepth 0 -mmin +$((MAX_MIN + 10)) -exec rmdir {} \; 2>/dev/null || true
    sleep 5
  done
  trap 'rmdir "$DIR/lock"' EXIT

  SECONDS=0  # from here, the sandbox's lifetime
  SBX=$(prized sandbox create --no-wait --template prized-base-v1 --max-runtime "$MAX_MIN" --label "anthropic-session=$ANTHROPIC_SESSION_ID")
  echo "sandbox: $SBX"
  trap 'prized sandbox rm "$SBX" || true; rmdir "$DIR/lock"' EXIT  # until the session is over, a failed step deletes the sandbox

  # Hand the worker its keys in a file, through stdin: other users of this machine can read a command line.
  for v in ANTHROPIC_ENVIRONMENT_ID ANTHROPIC_ENVIRONMENT_KEY ANTHROPIC_SESSION_ID ANTHROPIC_WORK_ID ANTHROPIC_WORK_SECRET; do printf 'export %s=%q\n' "$v" "${!v}"; done |
    prized sandbox cp - "$SBX:~/worker.env"

  # A session that went idle and woke again carries on with its files.
  if [ -e "$DIR/work.tgz" ]; then
    prized sandbox cp "$DIR/work.tgz" "$SBX:~/work.tgz"
    prized sandbox exec "$SBX" -- 'tar xzf ~/work.tgz -C ~/work'
  fi

  # Install the ant CLI without root, and start the worker. It stops 5 minutes before the sandbox's lifetime ends, to leave time to save its output.
  prized sandbox exec "$SBX" -- "mkdir -p ~/.local/bin && curl -fsSL https://github.com/anthropics/anthropic-cli/releases/download/v$ANT_VERSION/ant_${ANT_VERSION}_linux_amd64.tar.gz | tar -xz -C ~/.local/bin ant"
  prized sandbox exec "$SBX" -- "nohup bash -c '. ~/worker.env; timeout $((MAX_MIN * 60 - 300 - SECONDS)) ~/.local/bin/ant beta:worker run --workdir /home/prized/work; touch ~/worker.done' > ~/worker.log 2>&1 &"

  # The worker exits a minute after the session goes idle, and leaves ~/worker.done behind. Check every 30 seconds.
  until prized sandbox exec "$SBX" -- 'test -e ~/worker.done' || [ "$SECONDS" -ge $((MAX_MIN * 60 - 240)) ]; do sleep 30; done

  # Save the working directory and the worker's log, then delete the sandbox. If saving fails, the sandbox and the session's lock stay until its lifetime ends.
  trap - EXIT
  prized sandbox exec "$SBX" -- 'tar czf ~/work.tgz -C ~/work .'
  prized sandbox cp "$SBX:~/work.tgz" "$DIR/work.tgz"
  prized sandbox cp "$SBX:~/worker.log" "$OUT.worker.log"
  prized sandbox rm "$SBX" || true
  rmdir "$DIR/lock"  # after the delete, so a follow-up's create finds the sandbox's slot free
) > "$OUT.log" 2>&1 < /dev/null &
```

* **`MAX_MIN`** is each sandbox's lifetime in minutes: 60, the most the [free tier](https://prized.dev/docs/sandboxes#free-tier) allows. After a [top-up](https://prized.dev/docs/billing#topping-up) or a plan, it can be up to 1,440 (24 hours). The worker is stopped 5 minutes before the end, so its output can still be saved.
* **`ANT_VERSION`** pins the `ant` release; newer ones are on [GitHub](https://github.com/anthropics/anthropic-cli/releases).
* **Sessions run side by side**, a sandbox each: the script returns at once, so the poller claims the next session while this one runs. A follow-up to one session waits until its previous run has saved.
* **Output** goes to `sessions/<session id>/`: the working directory as `work.tgz`, replaced whole when a later run of the session saves, and for each work item the script's log as `<work id>.log` and the worker's as `<work id>.worker.log`.
* **If saving fails**, for example on an archive over the 100 MB a copy takes, the sandbox is kept until its lifetime ends, and the session's next run waits until then: copy what you need with `prized sandbox cp`, then delete the sandbox and `sessions/<session id>/lock`. Its id is at the top of the work item's log.

## 3. Start the poller [#3-start-the-poller]

```bash
ant beta:worker poll --on-work ./spawn.sh
```

## 4. Run a session [#4-run-a-session]

Start a session on the environment and send it `Write the output of "uname -a" to hello.txt in your working directory.` While it runs, `prized sandbox ls` shows its sandbox, labelled `anthropic-session=sesn_…`. About a minute after the session goes idle, `hello.txt` is in `sessions/sesn_…/work.tgz`.

## Keys in the sandbox [#keys-in-the-sandbox]

The environment key and the session's secret reach the sandbox in `~/worker.env`, which the worker loads, so the agent's tool calls can read them. The key reaches only its environment's queue: use one environment per trust boundary ([Security model](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes-security)). Never pass your Claude API key or your Prized API key into a sandbox.

## Limits [#limits]

| Limit                  | In a sandbox                                                                                                                                                                                                                                           |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Working directory      | `/home/prized/work`: tools run as the user `prized`, without root, so there is no `/workspace`. If the agent uses skills, tell it in its system prompt that they are in `/home/prized/work/skills`                                                     |
| Memory stores          | Not supported: they need a writable `/mnt/memory`                                                                                                                                                                                                      |
| Files and repositories | Anthropic does not mount them on self-hosted environments; fetch them in `spawn.sh` before the worker starts ([Stage files for a session](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes-workers#stage-files-for-a-session)) |
| Lifetime               | 24 hours, or 60 minutes on the free tier; a session still running then loses its sandbox                                                                                                                                                               |
| At once                | 4 sandboxes of up to 2 vCPU and 4 GB on the free tier; paid quotas are on [Limits](https://prized.dev/docs/limits#sandboxes)                                                                                                                                             |
| Network                | Outbound internet, with no allow or deny lists yet                                                                                                                                                                                                     |
| Region                 | `us-west-2` (Oregon)                                                                                                                                                                                                                                   |

Prized Sandboxes and Claude Managed Agents are both in beta.

## Troubleshooting [#troubleshooting]

| Symptom                                                                                 | Fix                                                                                                                                                                                                                                   |
| --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| The poller exits with `401 Unauthorized` and `OAuth access token is invalid`            | The environment key is wrong, revoked, or for another environment than `ANTHROPIC_ENVIRONMENT_ID`: generate a new one                                                                                                                 |
| The poller exits with `on-work script "./spawn.sh" is unrunnable`                       | Run `chmod +x spawn.sh`, and start the poller in the script's folder                                                                                                                                                                  |
| A session stays queued                                                                  | Start the poller. From your own machine, with your Claude API key, `ant beta:environments:work stats --environment-id env_…` then shows `workers_polling` of 1 or more                                                                |
| A follow-up waits, and its log stays empty                                              | The session's previous run is still going, or its save failed (**If saving fails** above). A lock left by a run that was killed, for example by a reboot, clears itself a little over `MAX_MIN` + 10 minutes after that run started   |
| A session's log starts with a `prized:` error                                           | Prized refused the sandbox, so that session has none; the `↳` line says what to change. `MAX_MIN` above 60 needs a top-up or a plan. An error after the `sandbox:` line while saving keeps the sandbox: see **If saving fails** above |
| A session goes idle at once, and its log says `cannot create the memory store's folder` | Start the session without memory stores                                                                                                                                                                                               |
