POSTEngineering log
All postsInitialize Once, Claim Many: Custom AI Agent Sandboxes on Sandbox0
- Written by
- Sandbox0 Team
- Published
The slow part of an AI agent sandbox is not always creating the sandbox.
For coding agents, data agents, browser agents, and internal automation, the hidden cost is often everything that happens after the sandbox is already alive:
- install project dependencies
- clone or fetch a repository
- warm package manager caches
- generate indexes, build artifacts, or language server state
- write local tool configuration
- prepare a workspace for task-specific execution
One answer is a custom template.
That is the right answer when the runtime shape changes: a different digest-pinned image, resource default, network policy, security class, or ephemeral mount. It is more machinery than you need when the existing runtime shape already fits and only ordinary writable filesystem state changes.
Sandbox0 does not create a private warm pool for every template. Compatible templates share resource-neutral Nomad carriers, while exact CPU and memory are leased at claim time. A new template can still mean another immutable artifact, configuration object, and lifecycle to build, attest, publish, and maintain.
Sandbox0 gives you another path:
claim from a compatible template, initialize the writable root filesystem
once, create a rootfs snapshot, then claim new sandboxes with that
snapshot_id.
That turns a generic builtin template into a reusable initialized environment without creating a new template ID for every variation.
The Layering Matters#
Sandbox0 separates two things that are easy to collapse into one bucket:
| Layer | What it controls | Best use |
|---|---|---|
| Template | Digest-pinned image, resource and RootFS defaults, security class, ephemeral mounts, environment, and default network policy | Reusable runtime shape |
| Sandbox rootfs | Writable filesystem tied to a sandbox identity and checkpointed across pause/resume | Initialized environment state inside one sandbox shape |
Rootfs snapshots live in the middle layer.
They are not a replacement for templates. They do not change the base image,
resource defaults, default network policy, security class, or ephemeral mounts.
When you claim with snapshot_id, the requested template still controls the
runtime shape while the snapshot initializes the writable rootfs.
Rootfs snapshots are for a different question:
Can I reuse the initialized filesystem state of a sandbox when the template shape already fits?
For many agent workloads, the answer is yes.
Why This Exists#
Agent workloads are latency-sensitive, but dependency sets are often more granular than runtime templates.
Sandbox0 keeps a shared pool of resource-neutral carrier allocations. A claim
atomically leases one compatible carrier plus exact node CPU, memory, and
RootFS capacity, then starts the requested digest-pinned template. Templates
such as default, Docker-in-Sandbox, browser-enabled environments, or
organization-approved bases define broad reusable runtime shapes.
A platform may have:
- one Python base template, but hundreds of package combinations
- one Node.js base template, but many monorepo workspaces
- one Docker-in-Sandbox template, but different project setup scripts
- one agent runtime template, but different tool caches per team
You can build all of those into images or captured templates. Sometimes that is correct. But it turns workspace setup variation into artifact and template lifecycle work.
Rootfs snapshots let you keep the template catalog focused on runtime shape while storing per-team or per-project initialization as filesystem state.
That is the core pattern: initialize once, claim many.
What Rootfs Snapshot And Claim Do#
Sandbox0 rootfs snapshots are point-in-time copies of a running or paused sandbox writable root filesystem.
The API surface is intentionally small:
POST /api/v1/sandboxes/{id}/snapshotscreates a named rootfs snapshot from a running or paused sandbox.GET /api/v1/sandboxes/{id}/snapshotslists snapshots for a sandbox.GET /api/v1/sandbox-rootfs-snapshots/{snapshot_id}fetches a snapshot by ID.POST /api/v1/sandboxeswithsnapshot_idclaims a new sandbox and initializes its writable rootfs from that snapshot.DELETE /api/v1/sandbox-rootfs-snapshots/{snapshot_id}deletes a named snapshot.
For a running source, Sandbox0 briefly barriers the active writer, publishes a committed RootFS checkpoint, and releases the source back to running. You can pause first when you also want to release compute, but pause is not a snapshot prerequisite.
Claiming with snapshot_id is different: it consumes an existing rootfs snapshot and creates a new running sandbox. The requested template still controls the base image, resources, services, network defaults, and other runtime shape.
Restore and fork are still available, but they solve different problems:
POST /api/v1/sandboxes/{id}/rootfs/restorerolls an existing paused sandbox rootfs back to a snapshot.POST /api/v1/sandboxes/{id}/forkcreates a new paused sandbox from a running or paused source sandbox rootfs.PUT /api/v1/sandboxes/{id}/rootfs/rebasecarries a paused sandbox's file-level changes onto another attested immutable Base artifact.
Sandbox0 is working with filesystem state, not live process state. Pause checkpoints the writable rootfs and releases the runtime allocation. It does not preserve cgroups, memory, sockets, PID state, or live REPL sessions. Restore and rebase require a paused target. A fork starts paused. After restore or fork, resume the sandbox before using files, commands, services, SSH, or contexts.
A Minimal Workflow#
Start from a builtin template such as default:
bashSEED_SANDBOX_ID="$(s0 -o json sandbox create --template default --hard-ttl 86400 | jq -r '.id')"
Initialize the environment inside the sandbox:
bashs0 sandbox exec "$SEED_SANDBOX_ID" -- /bin/sh -lc ' set -e python3 -m venv /workspace/.venv . /workspace/.venv/bin/activate pip install ruff pytest mkdir -p /workspace/project printf "initialized at %s\n" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > /workspace/project/ENVIRONMENT '
Create a named rootfs snapshot. The seed can stay running:
bashSNAPSHOT_ID="$(s0 -o json sandbox snapshot create "$SEED_SANDBOX_ID" \ --name python-test-tools-v1 \ --description "default template with venv, ruff, and pytest" \ | jq -r '.id')"
Claim a fresh task sandbox from the same builtin template and the rootfs snapshot:
bashTASK_SANDBOX_ID="$(s0 -o json sandbox create \ --template default \ --snapshot-id "$SNAPSHOT_ID" \ --hard-ttl 3600 \ | jq -r '.id')"
At this point, the task sandbox has its own sandbox ID and writable rootfs initialized from the snapshot. Writes made in the task sandbox do not modify the seed sandbox or the immutable snapshot.
Claim, Restore, Or Fork?#
Use snapshot plus claim with snapshot_id when you need reusable custom rootfs state.
That path is good when you want to:
- give the checkpoint a stable name and description
- list and inspect available restore points
- create many fresh running sandboxes from the same initialized state
- keep an expiration metadata value for cleanup workflows
- record a known-good environment version
Use restore when you need to roll an existing paused sandbox back to a known filesystem state. Restore is destructive for the target sandbox rootfs and leaves the sandbox paused until you resume it.
Use fork when you specifically need another paused sandbox cloned from a running or paused source sandbox. Fork preserves the source sandbox's template and sandbox configuration while giving the child sandbox its own identity and writable rootfs head. A running source remains running after the short checkpoint barrier.
Deleting a snapshot does not modify any sandbox already claimed or restored from it, and it does not affect forks created from the same rootfs state.
What Happens Under the Hood#
Sandbox0 stores persistent rootfs state as immutable block-COW generations and filesystem heads.
A rootfs snapshot is an immutable pointer to one committed rootfs head.
Claiming with snapshot_id creates a new sandbox identity and isolated writable
head from that snapshot before procd, file APIs, services, SSH, and contexts are
initialized. Sandbox0 stores durable RootFS objects in S3-compatible storage
and transaction state in PostgreSQL; sandbox containers do not access either
store directly.
The useful operational detail is that each claimed task sandbox starts from the same snapshot state and diverges through later writes. The snapshot remains immutable; task-specific writes belong to the newly claimed sandbox.
That makes claim-time snapshots a practical primitive for agent branching:
- run several attempts from the same initialized workspace
- compare different prompts, models, or tool policies
- keep failed branches isolated
- discard losing branches without modifying the seed
It is the same mental model as branching a working tree, but at the sandbox rootfs layer.
Where This Helps#
Rootfs snapshots and claim-time snapshot_id fit best when your initialization is ordinary filesystem state on top of an existing template.
The same pattern powers the Sandbox0 backend for Vercel Eve: Eve prewarm runs bootstrap and seed files once, the adapter captures a Sandbox0 rootfs snapshot, and later Eve sessions claim fresh sandboxes from that snapshot. See Vercel Eve Sandbox Backend for the Eve-specific lifecycle.
Good candidates:
- Python virtual environments
- npm, pnpm, uv, pip, or cargo caches
- checked-out repositories
- generated build artifacts
- local CLIs installed into the writable filesystem
- test fixtures and lightweight seed data
- tool configuration in home directories or workspace paths
- language server indexes or project metadata that can be regenerated
For coding agents, this can remove a large chunk of per-task setup. The task sandbox can start from a workspace that already has the repository, dependency cache, and tool configuration in place.
For eval systems, this gives every run the same initialized baseline without asking every worker to rebuild that baseline.
For multi-agent search, it gives each branch an isolated filesystem while keeping source setup centralized.
Where It Does Not Help#
Rootfs snapshots are not magic templates.
Use a custom template when you need to change:
- the container image
- memory or immutable RootFS storage defaults (CPU is platform-derived)
- default network policy
- standard or privileged guest security class
- claim-lifetime ephemeral mounts
- environment defaults that should exist before any sandbox command runs
Do not use rootfs snapshots to preserve live runtime state. Running processes, memory, sockets, in-flight requests, and live REPL sessions are not preserved by rootfs snapshot, claim, restore, or fork.
The right split is usually:
- template for broad runtime shape
- rootfs snapshot plus claim with
snapshot_idfor initialized environment state - external systems for authoritative application state
The Operational Boundary#
Templates and snapshots solve different lifecycle problems.
The platform can maintain a focused set of digest-pinned builtin and team-owned
templates while compatible claims draw from the same resource-neutral carrier
pool. Users can customize an environment by writing normal filesystem state,
creating a snapshot from the running or paused seed, and claiming future
sandboxes with that snapshot_id.
That is the distinction:
templates define reusable runtime inputs; rootfs snapshots preserve initialized state that compatible new sandboxes can claim.
Snapshots still consume durable object storage and metadata, so they need retention and cleanup policies. They avoid turning every dependency cache or checked-out workspace into another template artifact and configuration object.
Operational Pattern#
For production agent infrastructure, treat rootfs snapshots as versioned initialization artifacts:
- Choose the smallest builtin or custom template whose runtime shape is correct.
- Claim a seed sandbox from that template.
- Run deterministic setup commands inside the seed sandbox.
- Write a marker file with the setup version, repository commit, lockfile hash, or toolchain version.
- Create a named rootfs snapshot for the initialized state; the seed may stay running.
- Claim each task sandbox with the same template and
snapshot_id. - Run task-specific work in the claimed sandbox.
- Delete obsolete snapshots and old seed or task sandboxes through a cleanup workflow.
Names matter. Use names that identify what the rootfs contains:
textpython-tools-2026-06-17 repo-api-main-9f31c2a eval-baseline-py312-v4 frontend-agent-cache-lockfile-8c0d9b
The snapshot name is not the source of truth, but it is what humans will see when they debug environment drift.
Why This Is Useful for AI Agents#
AI agents benefit from environments that feel like real workspaces.
A one-shot code runner can start empty, run a snippet, and disappear. A production coding agent usually cannot. It needs repository state, tools, dependency caches, generated files, test fixtures, and repeatable recovery points.
Custom templates solve the runtime-shape part of that problem.
Rootfs snapshots and claim-time snapshot IDs fill the gap between them:
- lighter than building a new image for every initialized variation
- reusable without creating another template artifact
- isolated enough for parallel task sandboxes
- explicit enough to claim, restore, inspect, and clean up
- compatible with the shared resource-neutral carrier pool
That is why "initialize once, claim many" is a useful design pattern for AI agent infrastructure.
It lets platform teams keep the runtime surface small while giving users a practical way to customize the environment they actually run in.
Read the API workflow in Snapshot And Restore and the template tradeoff in Template.