# Snapshot And Restore

Sandbox rootfs snapshots are point-in-time copies of a sandbox writable root filesystem. Use them when you want to checkpoint an initialized workspace, claim new sandboxes from that known filesystem state, roll an existing sandbox back, or fork a sandbox into an isolated child sandbox.

Use this page when you need to:

- create named rootfs snapshots from a running or paused sandbox
- claim a new sandbox with a named rootfs snapshot
- restore a paused sandbox rootfs from a snapshot
- fork a running or paused sandbox into another paused sandbox
- rebase a paused sandbox onto a newer attested Base artifact
- understand how rootfs snapshots differ from pause/resume

<Callout variant="warning">
Creating snapshots and forking accept a running or paused source sandbox. With the default filesystem-only fork, running sources are briefly barriered and checkpointed, then remain running. Restore still requires a paused target sandbox because it replaces that sandbox's rootfs head. Claiming with `snapshot_id` creates a new running sandbox from an existing snapshot and does not require a paused target sandbox.
</Callout>

## How It Differs From Pause And Resume

| Capability | Purpose | Creates a named restore point | Creates another sandbox | Requires paused sandbox |
|------------|---------|-------------------------------|-------------------------|-------------------------|
| Pause/resume | Release compute and later continue the same sandbox identity from its latest checkpoint | No | No | Pause starts from a running sandbox; resume starts from a paused sandbox |
| Snapshot | Publish an immutable point-in-time rootfs record | Yes | No | No; running sources are checkpointed first |
| Claim with `snapshot_id` | Create a running sandbox initialized from a named snapshot | Uses an existing snapshot | Yes | No target sandbox required |
| Restore | Move a paused sandbox rootfs back to a snapshot | Uses an existing snapshot | No | Yes |
| Fork | Create an isolated paused sandbox from a source sandbox rootfs | No | Yes | No; running sources are checkpointed first |
| Rebase | Carry file-level changes onto another attested Base artifact | Retains the old head for a bounded rollback window | No | Yes |

Pause/resume keeps the latest checkpoint for one sandbox identity. Rootfs snapshots are explicit records that you can list, fetch, claim from, restore, and delete. Fork creates a new sandbox with Copy-on-Write rootfs isolation from the source rootfs state.

Use sandbox rootfs snapshots when the default writable filesystem is the state you want to version or fan out into another sandbox identity.

## State Model

- `Create snapshot` creates a snapshot record from the source sandbox's current rootfs head. If the source is running, Sandbox0 briefly barriers the sandbox, checkpoints its writable rootfs, and then releases the source back to running.
- `Claim with snapshot_id` creates a running sandbox and initializes its writable rootfs from the selected snapshot before process APIs are initialized.
- `Restore` changes a paused target sandbox rootfs head to the selected snapshot and leaves the sandbox paused.
- `Fork` creates a new paused sandbox with the source sandbox configuration and an isolated rootfs fork. If the source is running, Sandbox0 briefly barriers the sandbox, checkpoints its writable rootfs, and then releases the source back to running. The fork request can override lifecycle `ttl` and `hard_ttl`.
- `Rebase` applies the paused sandbox's file-level changes to a different attested immutable Base artifact, publishes one new durable generation, and retains the old generation for a bounded rollback window.
- `Resume` is required before reading or writing files through sandbox process and file APIs after restore or fork. A sandbox claimed with `snapshot_id` starts running.
- 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.

### Rootfs Path Semantics

Snapshots, snapshot claims, restore, and filesystem-only fork use the same block-COW
persistence boundary as pause and resume. Ordinary writable RootFS paths such
as `/var/tmp` and workspace directories are captured. `/tmp`, virtual runtime
mounts such as `/proc`, `/dev`, and `/sys`, and template-defined
`ephemeralMounts` are recreated for each runtime generation and are not
snapshot content.

The default `/tmp` capacity is half the sandbox memory limit. Its temporary
filesystem uses a private disk file on the compute node. Cached file pages still
count toward memory usage, but can be reclaimed under memory pressure. The
capacity itself reserves no memory. A template can set an explicit `/tmp`
capacity using `ephemeralMounts`. `/dev/shm` and other ephemeral mounts remain
memory-backed. Temporary files are discarded when the runtime ends, including
filesystem-only pause/resume, and never enter a RootFS checkpoint. Explicit memory
checkpoints can retain supported runtime tmpfs state separately from RootFS.

<Callout variant="info">
The rootfs snapshot API stores snapshot metadata including `name`, `description`, and optional `expires_at`. Delete snapshots explicitly when cleanup workflows no longer need them.
</Callout>

## Storage Encryption

When application-layer object encryption is enabled, manager and ctld encrypt
persisted RootFS block data and descriptors before writing them to
S3-compatible storage. With `aes256gcm-rsa`, each object receives a random data
key, its contents are independently authenticated, and the data key is wrapped
with the installation's RSA key.

This is service-side encryption, not end-to-end encryption: manager and the
active ctld hold the installation key and can decrypt RootFS objects while
serving sandbox operations. Enabling encryption does not rewrite historical
objects automatically. Self-hosted deployments configure
`rootfs_object_storage.object_encryption_enabled`, key path, and algorithm on
manager and ctld; see [Configuration](/docs/sandbox/self-hosted/configuration#rootfs-object-storage).

## Initialize Once, Claim Many

RootFS snapshots plus claim-time `snapshot_id` can act like a lightweight
custom RootFS when your changes are ordinary filesystem state. Start from a
compatible template, initialize the sandbox, publish a RootFS snapshot, then
claim new sandboxes from that snapshot. All compatible templates share the
same resource-neutral carrier pool.

Use this pattern for:

- package installs and dependency caches
- cloned repositories and checked-out branches
- generated build artifacts that future runs should inherit
- local tool configuration written into the sandbox filesystem

Typical workflow:

1. claim a sandbox from a compatible builtin or team-owned template
2. install dependencies or prepare the workspace inside the sandbox rootfs
3. create a named rootfs snapshot for the initialized state
4. optionally pause or delete the seed sandbox after the snapshot is created
5. claim each task sandbox with the same template and `snapshot_id`

```bash
SEED_SANDBOX_ID="$(s0 -o json sandbox create --template default --hard-ttl 86400 | jq -r '.id')"

# Initialize the rootfs with your normal sandbox commands, file writes, or SSH session.

SNAPSHOT_ID="$(s0 -o json sandbox snapshot create "$SEED_SANDBOX_ID" \
    --name python-deps-v1 \
    --description "Default template after Python dependency install" \
    | jq -r '.id')"

TASK_SANDBOX_ID="$(s0 -o json sandbox create \
    --template default \
    --snapshot-id "$SNAPSHOT_ID" \
    --hard-ttl 3600 \
    | jq -r '.id')"
```

Claiming with `snapshot_id` is the preferred path for reusable custom rootfs state because the result is a fresh running sandbox that still uses the builtin template's image, resources, mounts, services, and network defaults. Use restore when you need to roll an existing paused sandbox back to a snapshot. Use fork when you specifically need a paused child sandbox cloned from a running or paused source.

For a longer walkthrough of this pattern, see [Initialize Once, Claim Many](/blog/2026-06/initialize-once-fork-many-custom-ai-agent-sandboxes).

<Callout variant="warning">
This pattern does not replace template-level configuration. Use a custom template when you need a different container image, resource limits, default network policy, environment defaults, or privileged runtime fields.
</Callout>

## Rootfs Operation Lifecycle

Create a snapshot from a running or paused sandbox. Pause the sandbox and wait until `status` is `paused` before restoring into it. Forking also accepts a running or paused source sandbox.

<Endpoint method="POST">
/api/v1/sandboxes/{'{id}'}/pause
</Endpoint>

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `paused, err := client.PauseSandboxAndWait(ctx, sandbox.ID, nil)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Sandbox status: %s\\n", paused.Status)`
    },
    {
      label: "Python",
      language: "python",
      code: `paused = client.sandboxes.pause_and_wait(sandbox.id)
print(f"Sandbox status: {paused.status}")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `const paused = await client.sandboxes.pauseAndWait(sandbox.id);
console.log("Sandbox status:", paused.status);`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 sandbox pause sb_abc123

# Wait until this shows status "paused".
s0 sandbox get sb_abc123`
    }
  ]}
/>

## Create A Snapshot

Create a rootfs snapshot from a running or paused sandbox. A running source remains running after the snapshot is created.

<Endpoint method="POST">
/api/v1/sandboxes/{'{id}'}/snapshots
</Endpoint>

### Request Body

| Field | Type | Description |
|-------|------|-------------|
| `name` | string | Optional snapshot name |
| `description` | string | Optional snapshot description |
| `expires_at` | string | Optional RFC3339 timestamp stored as snapshot expiration metadata |

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `snapshot, err := client.CreateSandboxRootFSSnapshot(ctx, sandbox.ID, &apispec.CreateSandboxRootFSSnapshotRequest{
    Name:        apispec.NewOptString("before-agent-task"),
    Description: apispec.NewOptString("Workspace after dependency install"),
})
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Snapshot ID: %s\\n", snapshot.ID)`
    },
    {
      label: "Python",
      language: "python",
      code: `from sandbox0.apispec.models.create_sandbox_root_fs_snapshot_request import CreateSandboxRootFSSnapshotRequest

snapshot = client.sandboxes.create_rootfs_snapshot(
    sandbox.id,
    CreateSandboxRootFSSnapshotRequest(
        name="before-agent-task",
        description="Workspace after dependency install",
    ),
)
print(f"Snapshot ID: {snapshot.id}")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `const snapshot = await client.sandboxes.createRootFSSnapshot(sandbox.id, {
    name: "before-agent-task",
    description: "Workspace after dependency install",
});
console.log("Snapshot ID:", snapshot.id);`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 sandbox snapshot create sb_abc123 \\
    --name before-agent-task \\
    --description "Workspace after dependency install"`
    }
  ]}
/>

On the Nomad/gVisor runtime, a running-source snapshot is bound to the exact
live allocation, node incarnation, rootfs writer grant, writer epoch, and
source generation observed when the operation starts. The checkpoint is
published as a separate immutable filesystem, so the source writer and source
rootfs head are unchanged. If the API connection or manager restarts, Sandbox0
recovers the same pending operation from PostgreSQL; it never substitutes a
checkpoint from a newer source runtime.

## List And Get Snapshots

List snapshots created from a sandbox, or fetch a snapshot directly by snapshot ID.

<Endpoint method="GET">
/api/v1/sandboxes/{'{id}'}/snapshots
</Endpoint>

<Endpoint method="GET">
/api/v1/sandbox-rootfs-snapshots/{'{snapshot_id}'}
</Endpoint>

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `snapshots, err := client.ListSandboxRootFSSnapshots(ctx, sandbox.ID)
if err != nil {
    log.Fatal(err)
}
for _, item := range snapshots.Snapshots {
    name := item.Name.Or("")
    fmt.Printf("%s %s\\n", item.ID, name)
}

snapshot, err = client.GetSandboxRootFSSnapshot(ctx, snapshot.ID)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Created at: %s\\n", snapshot.CreatedAt)`
    },
    {
      label: "Python",
      language: "python",
      code: `snapshots = client.sandboxes.list_rootfs_snapshots(sandbox.id)
for item in snapshots:
    print(item.id, item.name)

snapshot = client.sandboxes.get_rootfs_snapshot(snapshot.id)
print(f"Created at: {snapshot.created_at}")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `const snapshots = await client.sandboxes.listRootFSSnapshots(sandbox.id);
for (const item of snapshots) {
    console.log(item.id, item.name);
}

const fetched = await client.sandboxes.getRootFSSnapshot(snapshot.id);
console.log("Created at:", fetched.createdAt);`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 sandbox snapshot list sb_abc123
s0 sandbox snapshot get rootfs-snapshot-abc123`
    }
  ]}
/>

## Claim From A Snapshot

Use `snapshot_id` on sandbox claim when you want a new running sandbox initialized from a named rootfs snapshot. The requested template still controls image, resources, mount declarations, services, network defaults, and other runtime shape. Sandbox0 applies the snapshot rootfs before file APIs, contexts, services, and SSH are initialized.

<Endpoint method="POST">
/api/v1/sandboxes
</Endpoint>

### Request Body

| Field | Type | Description |
|-------|------|-------------|
| `template` | string | Template ID to use |
| `snapshot_id` | string | Rootfs snapshot ID used to initialize the new sandbox writable rootfs |
| `config` | object | Optional sandbox configuration |

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `sandbox, err := client.ClaimSandbox(ctx, "default",
    sandbox0.WithSandboxSnapshotID(snapshot.ID),
    sandbox0.WithSandboxHardTTL(3600),
)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Sandbox ID: %s\\n", sandbox.ID)`
    },
    {
      label: "Python",
      language: "python",
      code: `sandbox = client.sandboxes.claim(
    "default",
    snapshot_id=snapshot.id,
)
print(f"Sandbox ID: {sandbox.id}")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `const sandbox = await client.sandboxes.claim("default", {
    snapshotId: snapshot.id,
    config: { hardTtl: 3600 },
});
console.log("Sandbox ID:", sandbox.id);`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 sandbox create --template default --snapshot-id rootfs-snapshot-abc123`
    }
  ]}
/>

## Restore A Sandbox Rootfs

Restore moves a paused target sandbox rootfs to the selected snapshot and returns the sandbox status. The sandbox stays paused after restore; resume it before using files, contexts, services, or SSH.

<Endpoint method="POST">
/api/v1/sandboxes/{'{id}'}/rootfs/restore
</Endpoint>

### Request Body

| Field | Type | Description |
|-------|------|-------------|
| `snapshot_id` | string | Rootfs snapshot ID to restore |

<Callout variant="warning">
Restore is destructive for the target sandbox rootfs. Files changed after the restored snapshot are no longer the target sandbox head after restore.
</Callout>

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `restored, err := client.RestoreSandboxRootFS(ctx, sandbox.ID, apispec.RestoreSandboxRootFSRequest{
    SnapshotID: snapshot.ID,
})
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Restored snapshot %s, status=%s\\n", restored.SnapshotID, restored.Status)

_, err = client.ResumeSandboxAndWait(ctx, sandbox.ID, nil)
if err != nil {
    log.Fatal(err)
}`
    },
    {
      label: "Python",
      language: "python",
      code: `from sandbox0.apispec.models.restore_sandbox_root_fs_request import RestoreSandboxRootFSRequest

restored = client.sandboxes.restore_rootfs(
    sandbox.id,
    RestoreSandboxRootFSRequest(snapshot_id=snapshot.id),
)
print(f"Restored snapshot {restored.snapshot_id}, status={restored.status}")

client.sandboxes.resume_and_wait(sandbox.id)`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `const restored = await client.sandboxes.restoreRootFS(sandbox.id, {
    snapshotId: snapshot.id,
});
console.log("Restored snapshot:", restored.snapshotId, "status:", restored.status);

await client.sandboxes.resumeAndWait(sandbox.id);`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 sandbox snapshot restore sb_abc123 rootfs-snapshot-abc123
s0 sandbox resume sb_abc123`
    }
  ]}
/>

## Fork A Sandbox

Fork creates a new paused sandbox from a running or paused source sandbox rootfs. A running source is briefly barriered and checkpointed during the operation, then remains running. The fork receives its own sandbox ID. Writes made after resuming the fork do not modify the source sandbox rootfs.

On the Nomad/gVisor runtime, Sandbox0 persists the fork operation and paused target before contacting the source node. The authenticated node request is bound to the exact live allocation, node incarnation, rootfs writer grant, writer epoch, and source generation. If the API connection or manager restarts after checkpoint publication, background reconciliation recovers the committed target from PostgreSQL without freezing a newer source state again. A never-run fork target starts at runtime generation `0`; its first resume claims a fresh slot as generation `1`.

<Endpoint method="POST">
/api/v1/sandboxes/{'{id}'}/fork
</Endpoint>

Omit the request body to inherit the source sandbox lifecycle configuration. To set a new lifecycle window for the fork, pass `config.ttl` and/or `config.hard_ttl`. Positive values are counted from the fork creation time, and `0` disables that expiration path.

```json
{
    "config": {
        "ttl": 300,
        "hard_ttl": 3600
    }
}
```

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `forked, err := client.ForkSandbox(ctx, sandbox.ID, nil)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Forked sandbox: %s\\n", forked.Sandbox.ID)

_, err = client.ResumeSandboxAndWait(ctx, forked.Sandbox.ID, nil)
if err != nil {
    log.Fatal(err)
}`
    },
    {
      label: "Python",
      language: "python",
      code: `forked = client.sandboxes.fork(sandbox.id)
print(f"Forked sandbox: {forked.sandbox.id}")

client.sandboxes.resume_and_wait(forked.sandbox.id)`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `const forked = await client.sandboxes.fork(sandbox.id);
console.log("Forked sandbox:", forked.sandbox.id);

await client.sandboxes.resumeAndWait(forked.sandbox.id);`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 sandbox fork sb_abc123

# Resume the forked sandbox ID returned by the fork command.
s0 sandbox resume sb_forked123`
    }
  ]}
/>

## Explicit Memory Fork

Fork defaults to filesystem-only state. Experimental memory fork uses the same
endpoint with `memory: true` and requires a stable `Idempotency-Key` header.
The key and request body must stay unchanged on retries.

```http
POST /api/v1/sandboxes/sb_source/fork
Content-Type: application/json
Idempotency-Key: branch-task-001

{
    "memory": true,
    "config": {
        "ttl": 300,
        "hard_ttl": 3600
    }
}
```

For a paused source, memory fork requires a retained memory checkpoint matching
its RootFS head. For a running source, the current implementation captures and
retires the source runtime, commits the paused child and resumes the parent into
a new carrier. It does not keep the original parent runtime executing during
capture. The parent's runtime ID and generation change; its original soft TTL
is retained, and deletion or expiry cannot be undone by fork completion.

The operation returns `503 unavailable` while capture or parent resume is still
pending. Retry with the same key and body. A `201` response identifies the paused
child; it does not start the child. Resume that child with `memory: true` to
continue its captured processes. The parent and child use independent writable
RootFS branches and resource leases; immutable checkpoint image data is shared.

```python
from sandbox0.apispec.models.fork_sandbox_request import ForkSandboxRequest

forked = client.sandboxes.fork(
    sandbox.id,
    ForkSandboxRequest(memory=True),
    idempotency_key="branch-task-001",
)
client.sandboxes.resume_and_wait(forked.sandbox.id, memory=True)
```

The SDK call above performs one request. If it reports a pending operation,
retry it with the same key and body before attempting child resume. In TypeScript,
use `client.sandboxes.fork(id, { memory: true }, { idempotencyKey: key })`.
In Go, use `ForkSandboxWithOptions` with `ForkSandboxRequest.Memory` and
`ForkSandboxOptions.IdempotencyKey`.

Fork rebinds the captured runtime and session identity to the child without
restarting guest processes. Previously queued parent webhook events are retained
as inherited records and are not sent as child events. New child events carry
the child's identity. External connections are not guaranteed to survive.

Memory fork has the compatibility and transfer costs described in
[Pause And Resume](/docs/sandbox/pause-resume#explicit-memory-pause-and-resume).
Named snapshots, snapshot claims, restore and rebase remain RootFS operations;
they do not create or restore process memory.

## Rebase A Paused Sandbox

Use rebase when a sandbox must move to a newer immutable Base artifact while
preserving file-level changes from its current writable rootfs. The target Base
artifact must already be attested and available in the same region. Rebase does
not accept an image tag or build an image implicitly.

<Endpoint method="PUT">
/api/v1/sandboxes/{'{id}'}/rootfs/rebase
</Endpoint>

### Request Body

| Field | Type | Description |
|-------|------|-------------|
| `target_base_artifact_digest` | string | Required canonical SHA-256 digest of the target attested Base artifact |
| `rollback_ttl` | integer | Optional rollback retention in seconds; defaults to 86400 and cannot exceed 604800 |

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `rebased, err := client.RebaseSandboxRootFS(ctx, sandbox.ID, apispec.RebaseSandboxRootFSRequest{
    TargetBaseArtifactDigest: "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
    RollbackTTL:              apispec.NewOptInt32(86400),
})
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Rebased generation: %s\\n", rebased.GenerationID)`
    },
    {
      label: "Python",
      language: "python",
      code: `from sandbox0.apispec.models.rebase_sandbox_root_fs_request import RebaseSandboxRootFSRequest

rebased = client.sandboxes.rebase_rootfs(
    sandbox.id,
    RebaseSandboxRootFSRequest(
        target_base_artifact_digest="sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
        rollback_ttl=86400,
    ),
)
print(f"Rebased generation: {rebased.generation_id}")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `const rebased = await client.sandboxes.rebaseRootFS(sandbox.id, {
    targetBaseArtifactDigest: "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
    rollbackTtl: 86400,
});
console.log("Rebased generation:", rebased.generationId);`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 sandbox rebase sb_abc123 \
    --target-base-artifact-digest sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef \
    --rollback-ttl 86400`
    }
  ]}
/>

The sandbox must already be paused, have no live runtime slot or writer grant,
and use the block-COW storage format. An exact retry returns the same target
generation. A changed target digest or rollback window for the same operation
is rejected with `409 Conflict`.

Rebase is file-aware rather than a raw block transplant: the worker mounts the
old Base, current source, and target Base in isolated branches, computes the
filesystem change set, applies it to the target, validates the result, and only
then publishes immutable objects. PostgreSQL commits the target generation and
rollback pin atomically. The API can therefore recover a committed result after
a manager restart or response loss without executing the merge again.

<Callout variant="warning">
Keep the sandbox paused until rebase returns successfully. The previous head is
retained only until `rollback_expires_at`; expiry permits normal RootFS garbage
collection to reclaim it.
</Callout>

## Delete A Snapshot

Delete a rootfs snapshot when you no longer need it. This does not modify any sandbox that already restored from that snapshot, and it does not affect forks created from the same rootfs state.

<Endpoint method="DELETE">
/api/v1/sandbox-rootfs-snapshots/{'{snapshot_id}'}
</Endpoint>

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `_, err := client.DeleteSandboxRootFSSnapshot(ctx, snapshot.ID)
if err != nil {
    log.Fatal(err)
}
fmt.Println("Snapshot deleted")`
    },
    {
      label: "Python",
      language: "python",
      code: `client.sandboxes.delete_rootfs_snapshot(snapshot.id)
print("Snapshot deleted")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `await client.sandboxes.deleteRootFSSnapshot(snapshot.id);
console.log("Snapshot deleted");`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 sandbox snapshot delete rootfs-snapshot-abc123`
    }
  ]}
/>

## Typical Pattern

For coding-agent and test-generation workflows, a common pattern is:

1. claim a sandbox and initialize the repository or dependency cache
2. write a marker, lockfile, or workspace state that future tasks should inherit
3. create a rootfs snapshot for that initialized state; a running source remains running
4. claim each task sandbox with the same template and `snapshot_id`
5. run task-specific work in the newly claimed sandbox

## Next Steps

<CardGroup>
  <Card title="Pause And Resume" href="/docs/sandbox/pause-resume" cta="Continue">
    Release compute and reach the paused state required before restore and rebase operations.
  </Card>

  <Card title="Files" href="/docs/sandbox/files" cta="Continue">
    Read and write sandbox files before creating a rootfs snapshot.
  </Card>

  <Card title="Initialize Once, Claim Many" href="/blog/2026-06/initialize-once-fork-many-custom-ai-agent-sandboxes" cta="Read">
    Use rootfs snapshots and claim-time snapshot IDs as lightweight custom rootfs state.
  </Card>
</CardGroup>

## Snapshot retention quota

Each sandbox retains at most 10 public RootFS snapshots by default. Creating
another snapshot automatically removes the oldest excess snapshots while keeping
the new one. This applies to both running and paused sandboxes. Operators can
change the regional default or a team's `snapshots_per_sandbox` quota (minimum 1).
Reduced quotas and existing excess snapshots are reconciled by background
maintenance. Internal template-build captures are excluded.

Automatically removed snapshots no longer appear in snapshot lists and cannot be
used for a new restore or claim. Sandboxes and templates already derived from a
snapshot retain their own references; shared blocks remain until no owner needs
them. Physical storage reclamation happens asynchronously through RootFS GC.
