PLATINUM DOCS

Develop inside a sandbox

Turn a sandbox into a persistent development machine with an agent inside it that can spawn its own child sandboxes.

A sandbox can be your development machine. You keep a repository, a shell, and a coding agent inside it, and reach them from any device. This page is the setup we run, and the mistakes that cost us time.

This is the inverse of AI agents. There, an agent on your laptop drives Platinum. Here, the agent lives inside the sandbox.

What you get

  • A persistent box. Stop and resume it; the disk survives.
  • Terminals that outlive the browser, through Tabs.
  • A coding agent that can spawn child sandboxes for heavy work.
  • One scoped credential, so the agent cannot touch anything else you own.

Build the template

Do this once. debian-dev is not built in — templates are private to the organization that built them, so a name someone else used is not a name you have. Skipping this step is why -t debian-dev comes back as "template not found".

It carries what the rest of this page assumes: tmux (Tabs needs it for terminals that survive a browser close), git, a couple of language runtimes, and Claude Code.

pt api POST /v1/templates/from-spec -d '{
  "name": "debian-dev",
  "base_image": "docker.io/library/debian:12",
  "default_cpu": 2, "default_ram_mb": 6144, "default_disk_gb": 20,
  "size_mb": 5120,
  "steps": [{"op": "run", "cmd": "export DEBIAN_FRONTEND=noninteractive; apt-get update && apt-get install -y --no-install-recommends tmux git curl ca-certificates less nano ripgrep openssh-client python3 nodejs npm && (npm install -g @anthropic-ai/claude-code || true) && apt-get clean && rm -rf /var/lib/apt/lists/*"}]
}'

The build runs in the background — buildingready, a few minutes on a cold base image. Watch it with pt template list, and boot nothing until it is ready.

Change the step list to taste; it is an ordinary template. Add a Rust toolchain, swap nodejs for a specific version, whatever your work needs. Just keep tmux if you want Tabs.

Create the sandbox

Give it real resources. A coding agent plus a language toolchain needs headroom.

pt sandbox create -t debian-dev --cpu 2 --ram 6144 --disk 20 --name dev

Warning: size the disk for build output. A Rust target/ directory reached 13 GB on a 20 GB disk and filled it. A full disk breaks the terminal server, not just the build.

You can grow the disk later, so this is recoverable. See Grow the disk.

Grow the disk

Disk can grow at any time. It cannot shrink: the host grows the filesystem offline with resize2fs, and shrinking a populated filesystem risks data loss.

The sandbox must be stopped. The resize then cold-boots it.

pt sandbox stop <id>
pt sandbox resize <id> --disk 35

Measured on a real box, 20 GB to 35 GB took about 30 seconds end to end, including the stop and the boot. Every file survived.

Warning: stopping ends every process in the sandbox. Terminal sessions are lost, though the disk and its files are not. Commit your work first.

Run the agent as a non-root user

Sandboxes give you a root shell. Claude Code refuses --dangerously-skip-permissions as root, so create an ordinary user and work there.

useradd -m -s /bin/bash dev
usermod -aG sudo dev
echo 'dev ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/dev

Move the repository, the agent's credentials, and its memory into that user's home. Own every file:

chown -R dev:dev /home/dev

Root can still run the agent if you export IS_SANDBOX=1, which tells Claude Code the sandbox is the isolation boundary. Prefer the non-root user. It does not depend on that escape hatch.

Fix the login shell first

Terminals open a login shell. A login shell reads ~/.bash_profile or ~/.profile. It does not read ~/.bashrc. Anything you put only in .bashrc is invisible.

printf '[ -f ~/.bashrc ] && . ~/.bashrc\n' > ~/.bash_profile

This one file is why an alias, an environment variable, or a shell enhancement can look broken while the file that defines it is correct.

Shell quality of life

Install ble.sh for fish-style autosuggestions and syntax highlighting in bash. Then set a large, shared history:

export HISTSIZE=100000 HISTFILESIZE=200000 HISTCONTROL=ignoreboth:erasedups
shopt -s histappend
bleopt complete_auto_complete=1
bleopt history_share=1

history_share=1 matters with multiple terminals. Without it each terminal keeps its own history until it exits. A command run in one tab never appears in another tab's suggestions, which reads as broken autocomplete.

Tabs: terminals that survive

Open https://www.platinum.dev/tabs?id=<sandbox-id>. Each tab is a tmux session inside the sandbox, so terminals survive a browser close, a reload, and a device switch. Close a tab with ✕ to end that session.

Warning: set window-size manual per window, never globally. Global window-size manual segfaults tmux 3.3a when a second session is created, which kills every terminal at once.

tmux set -gu window-size                      # clear a global setting
tmux set -w window-size manual                # scope it to the window

Give the agent its own key

Mint a sandbox-scoped key. It can drive this sandbox and its children, and nothing else in your organization.

pt key create --name agent --scope "sandbox:sbx_01K…"

Inject it into the sandbox. A sandbox never mints its own key.

cat > /etc/pt-agent.env <<'EOS'
export PLATINUM_API_KEY="pt_live_…"
export PT_API_URL="https://api.platinum.dev"
EOS

Source that file from every shell, so the agent inherits it whichever user it runs as.

Spawn children for heavy work

This is the payoff. The agent runs a build, a migration, or a load test in a child sandbox instead of the box it lives on.

pt sandbox create -t debian-dev --cpu 2 --ram 6144 --disk 20 --no-wait
pt sandbox list --parent <this-sandbox-id>
pt sandbox exec <child-id> -- bash -lc 'cargo check'
pt sandbox rm <child-id> --yes

A scoped key needs no --parent. New sandboxes are parented to the scoped sandbox automatically, so the agent cannot create a sandbox it later cannot see.

Children run concurrently. Four created in parallel took 5 seconds of wall clock, against roughly 20 seconds one after another.

Children are siblings, not a tree. A child holding the same key creates siblings of itself, because the scope covers one level. Use this for parallel work pools, not for recursive agent hierarchies.

Deleting children safely

Warning: pt sandbox children <id> -o json prints each child's parentId. A loop that greps sandbox ids out of that output matches the parent too, and deletes the sandbox you are working on.

Take ids from the id field only:

pt sandbox children <id> -o json \
  | python3 -c 'import sys,json;[print(x["id"]) for x in json.load(sys.stdin)]'

A sandbox-scoped key is refused with 403 cannot_delete_scope_root if it tries to delete the sandbox it is scoped to. A full-access key is not. Parse ids carefully whichever key you hold.

Delete children before the parent. A parent with live children returns 409 and names them.

Back up before you experiment

Sandboxes back up to object storage. Check that a backup exists before risky work:

pt sandbox get <id> -o json | grep -i backup

A deleted sandbox is recoverable from its backup while the row survives. You lose everything written after the last backup, so take one before a migration or a large refactor.

See also

  • Sandboxes — lifecycle, parent and child sandboxes
  • API keys — scoping a key to one sandbox
  • CLI — the pt command reference
  • AI agents — driving Platinum from an agent outside it