# Sandbox

A Sandbox is an isolated execution environment where you can run code, manage files, and control network access. Each sandbox is created from a [Template](/docs/sandbox/template) and has a writable root filesystem that is checkpointed across pause/resume for the same sandbox identity.

For shell access and file copy over standard SSH clients, see [SSH](/docs/sandbox/ssh).

<Callout variant="info">
Sandbox0 Cloud clients should use `https://api.sandbox0.ai`. Set `SANDBOX0_BASE_URL` only when you are connecting to a self-hosted or private deployment.
</Callout>

## Sandbox Model

### Key Identifiers

| Field | Description |
|-------|-------------|
| `id` | Unique sandbox identifier (e.g., `sb_abc123`) |
| `template_id` | Template used to create this sandbox |
| `team_id` | Team that owns this sandbox |
| `user_id` | User who claimed this sandbox |
| `pod_name` | Kubernetes pod name (internal) |
| `cluster_id` | Cluster where sandbox runs in multi-cluster deployments. Present on claim and list responses, not on sandbox detail responses. |

### Lifecycle States

| Status | Description |
|--------|-------------|
| `starting` | Sandbox is being initialized |
| `running` | Sandbox is active and ready to use |
| `paused` | Sandbox has no runtime; identity and latest rootfs checkpoint are preserved |
| `terminating` | Sandbox identity and durable state are being deleted |
| `failed` | Sandbox encountered an error |

<Callout variant="info">
Pause and resume use internal lifecycle transactions, but `pausing` and `resuming` are not caller-visible statuses. Use the `paused` boolean as a convenience for `status == "paused"`. Pause does not preserve running processes, memory, sockets, PID state, or live REPL sessions.
</Callout>

### Persistent Root Filesystem

Sandbox0 persists the sandbox writable root filesystem as part of checkpointed pause/resume:

- when `ttl` expires or pause is requested, Sandbox0 saves a rootfs checkpoint before releasing the runtime pod
- when the sandbox resumes, Sandbox0 creates or reuses a runtime pod and restores the latest rootfs checkpoint before initializing sandbox processes
- files written outside mounted volumes survive pause/resume for the same sandbox identity after a checkpoint succeeds
- rootfs checkpoints for the sandbox identity are deleted when the sandbox is deleted or `hard_ttl` expires

Use the root filesystem for transparent same-sandbox continuity. Use [Snapshot And Restore](/docs/sandbox/snapshot-restore) for named rootfs snapshots, restore, and fork operations. Use [Volumes](/docs/sandbox/volume) for storage that must be mounted by multiple sandboxes or accessed directly through Volume APIs.

---

## Claim a Sandbox

Claim a sandbox from a template. The sandbox is created from a pre-warmed pool for fast startup (&lt;200ms cold start).

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

### Request Body

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

### Sandbox Configuration

| Field | Type | Description |
|-------|------|-------------|
| `env_vars` | object | Sandbox-level default environment variables for new procd-managed processes |
| `ttl` | integer | Time to live in seconds (soft limit, triggers auto-pause; default: `0`, disabled) |
| `hard_ttl` | integer | Hard sandbox lifetime in seconds (deletes identity and durable state; default: `0`, disabled) |
| `resources` | object | Optional per-sandbox resource override. Only `resources.memory` is accepted; CPU is derived from the platform memory-per-CPU ratio. |
| `network` | object | `SandboxNetworkPolicy`. Controls traffic rules, protocol controls, credential bindings, and destination-scoped egress auth |
| `webhook` | object | Webhook configuration |
| `auto_resume` | boolean | Auto-resume when accessed (default: true) |
| `services` | array | Sandbox Services for public HTTP routes, including Sandbox Functions |

When the cluster claim/start admission budget is full, the claim API returns `429` with `error.code` set to `claim_start_throttled` and a `Retry-After` header in seconds. Retry after that delay. Team quota failures also return `429`, but use `error.code` set to `quota_exceeded`.

See [Network](/docs/sandbox/network) and [Protocol Controls](/docs/sandbox/protocol-controls) for outbound control, [Sandbox Services](/docs/sandbox/services) for public HTTP controls, [Sandbox Functions](/docs/sandbox/functions) for inline public handlers, and [Credential](/docs/sandbox/credential) for outbound auth and secret handling.

Sandbox `env_vars` override template/container environment variables for new contexts, command services, and function executions. Per-context, per-command, service runtime, and function `env_vars` override sandbox `env_vars` for that narrower scope. Warm processes start before the sandbox is claimed, so they do not receive claim-time sandbox `env_vars`.

### Sandbox Resources

Set `config.resources.memory` when one sandbox needs a different memory limit from its template. The minimum is `128Mi`. The platform maximum defaults to `32Gi` and can be changed by the operator with `services.manager.config.sandboxMaxMemory`.

Sandbox0 keeps CPU and memory in the platform-defined ratio. The request only accepts memory; manager derives CPU from `services.manager.config.teamTemplateMemoryPerCpu` and applies the result to the sandbox runtime container. Existing running sandboxes can update memory with the Update Sandbox API.

SDK helpers expose this as `WithSandboxMemory` / `UpdateSandboxMemory` in Go, `memory` / `updateMemory` in TypeScript, `memory` / `update_memory` in Python, and `--memory` in the `s0` CLI.

### TTL vs Hard TTL

Sandbox0 uses two-tier TTL to balance resource efficiency and flexibility:

| Field | Behavior | Use Case |
|-------|----------|----------|
| `ttl` | **Runtime soft pause**: When expired, Sandbox0 checkpoints the writable root filesystem, deletes the runtime pod, and marks the sandbox `paused`. | Keep sandboxes alive during active use while freeing compute resources during idle periods. |
| `hard_ttl` | **Sandbox hard delete**: When expired, Sandbox0 deletes the sandbox identity and durable state, including paused rootfs checkpoints. | Enforce a maximum lifetime for compute and storage resources. |

<Callout variant="info">
The relationship: <code>ttl {'<='} hard_ttl</code>. When <code>ttl</code> expires first, sandbox pauses but can be resumed.
When <code>hard_ttl</code> expires, the sandbox is deleted and later access returns not found. Resume starts a new runtime generation from the latest rootfs checkpoint only while the sandbox is paused and still within its hard TTL.
</Callout>

**Example timeline (sandbox created with `ttl=300` and `hard_ttl=3600`):**
- `t=0`: Sandbox created
- `t=300`: TTL expires → sandbox auto-pauses
- `t=310`: User calls refresh → TTL reset to 300, Hard TTL reset to 3600 (new hard deadline at `t=3910`)
- `t=610`: TTL expires again → sandbox auto-pauses
- `t=3910`: Hard TTL expires → sandbox identity and durable state are deleted

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `ctx := context.Background()
client, err := sandbox0.NewClient(
    sandbox0.WithToken(os.Getenv("SANDBOX0_TOKEN")),
    sandbox0.WithBaseURL(os.Getenv("SANDBOX0_BASE_URL")),
)
if err != nil {
    log.Fatal(err)
}

// Claim a sandbox from the "default" template
sandbox, err := client.ClaimSandbox(ctx, "default",
    sandbox0.WithSandboxHardTTL(300),
    sandbox0.WithSandboxMemory("512Mi"),
    sandbox0.WithSandboxEnvVars(map[string]string{
        "APP_ENV": "development",
    }),
)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Sandbox ID: %s\\n", sandbox.ID)
defer client.DeleteSandbox(ctx, sandbox.ID)`
    },
    {
      label: "Python",
      language: "python",
      code: `import os
from sandbox0 import Client
from sandbox0.apispec.models.sandbox_config import SandboxConfig

client = Client(
    token=os.environ["SANDBOX0_TOKEN"],
    base_url=os.environ.get("SANDBOX0_BASE_URL", "https://api.sandbox0.ai"),
)

# Claim a sandbox from the "default" template
sandbox = client.claim_sandbox(
    template="default",
    config=SandboxConfig.from_dict({
        "hard_ttl": 300,
        "env_vars": {"APP_ENV": "development"},
    }),
    memory="512Mi",
)
print(f"Sandbox ID: {sandbox.id}")

# Cleanup
client.delete_sandbox(sandbox.id)`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `import { Client } from 'sandbox0';

const client = new Client({
    token: process.env.SANDBOX0_TOKEN!,
    baseUrl: process.env.SANDBOX0_BASE_URL || 'https://api.sandbox0.ai',
});

// Claim a sandbox from the "default" template
const sandbox = await client.sandboxes.claim('default', {
    hardTtl: 300,
    memory: '512Mi',
    envVars: { APP_ENV: 'development' },
});
console.log('Sandbox ID:', sandbox.id);

// Cleanup
await client.sandboxes.delete(sandbox.id);`
    },
    {
      label: "CLI",
      language: "bash",
      code: `# Claim a sandbox from the "default" template
s0 sandbox create --template default --hard-ttl 300 --memory 512Mi`
    }
  ]}
/>

---

## Get Sandbox Details

Retrieve full details about a sandbox.

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

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `sb, err := client.GetSandbox(ctx, sandbox.ID)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Status: %s\\n", sb.Status)
fmt.Printf("Template: %s\\n", sb.TemplateID)
fmt.Printf("Expires at: %s\\n", sb.ExpiresAt)`
    },
    {
      label: "Python",
      language: "python",
      code: `sb = client.get_sandbox(sandbox.id)
print(f"Status: {sb.status}")
print(f"Template: {sb.template_id}")
print(f"Expires at: {sb.expires_at}")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `const sb = await client.sandboxes.get(sandbox.id);
console.log('Status:', sb.status);
console.log('Template:', sb.templateId);
console.log('Expires at:', sb.expiresAt);`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 sandbox get sb_abc123`
    }
  ]}
/>

---

## Get Sandbox Status

Get the current status of a sandbox (lighter weight than full details).

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

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

---

## Observability

Sandbox0 exposes chart-ready runtime metrics alongside historical logs, lifecycle events, and network audit events. These APIs read asynchronous per-sandbox projections and never wake a paused sandbox. They are separate from platform service telemetry and the metering usage ledger.

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

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

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

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

### Runtime metrics

The metrics endpoint returns bounded, server-downsampled series ready for charts. The default query covers the last hour and requests CPU utilization, memory utilization, and network I/O. A query cannot span more than 30 days.

| Parameter | Type | Description |
|-----------|------|-------------|
| `start_time` | RFC3339 timestamp | Start of the window; defaults to one hour before `end_time` |
| `end_time` | RFC3339 timestamp | End of the window; defaults to the current time |
| `metrics` | comma-separated string | Canonical metric names from the catalog |
| `step_seconds` | integer | Requested bucket width; the server uses at least 15 seconds and may increase it to honor `max_points` |
| `statistic` | string | `auto`, `average`, `minimum`, `maximum`, `last`, or `rate`; `auto` averages gauges and rates counters |
| `max_points` | integer | Maximum points per series (default: 240, max: 1000) |

The built-in catalog is intentionally bounded:

| Metric | Kind | Unit | Meaning |
|--------|------|------|---------|
| `sandbox.cpu.utilization` | gauge | ratio | CPU usage divided by the configured CPU limit |
| `sandbox.cpu.usage` | gauge | cores | CPU cores currently used |
| `sandbox.cpu.time` | counter | seconds | Cumulative CPU time; `auto` returns its rate in cores |
| `sandbox.cpu.limit` | gauge | cores | Configured CPU limit |
| `sandbox.memory.usage` | gauge | bytes | Cgroup memory usage |
| `sandbox.memory.working_set` | gauge | bytes | Memory working set |
| `sandbox.memory.available` | gauge | bytes | Memory available within the sandbox limit |
| `sandbox.memory.limit` | gauge | bytes | Configured memory limit |
| `sandbox.memory.utilization` | gauge | ratio | Working set divided by the configured memory limit |
| `sandbox.network.io` | counter | bytes | Receive or transmit bytes, selected by the `direction` dimension; `auto` returns bytes per second |
| `sandbox.network.errors` | counter | count | Receive or transmit errors, selected by the `direction` dimension; `auto` returns errors per second |
| `sandbox.process.count` | gauge | count | Current sandbox process count |
| `sandbox.rootfs.writable.usage` | gauge | bytes | Main runtime container writable-layer usage when the CRI reports it |
| `sandbox.rootfs.writable.inodes` | gauge | count | Main runtime container writable-layer inodes when the CRI reports them |

Each series contains zero or more continuous `segments`. Runtime resets and collection gaps start a new segment, so clients must not connect lines across segments. The response also reports typed `gaps`, freshness relative to the requested `end_time`, the effective `step_seconds`, and whether the result is `partial`. Missing or unsupported observations are omitted and never synthesized as zero.

```typescript
import { Client, SandboxRuntimeMetricName } from "sandbox0";

const client = new Client({ token: process.env.SANDBOX0_TOKEN! });
const metrics = await client.sandbox("sb_abc123").getMetrics({
    startTime: new Date(Date.now() - 60 * 60 * 1000),
    metrics: [
        SandboxRuntimeMetricName.SandboxCpuUtilization,
        SandboxRuntimeMetricName.SandboxMemoryWorkingSet,
        SandboxRuntimeMetricName.SandboxNetworkIo,
    ],
    maxPoints: 120,
});
```

### Logs and events

| Parameter | Type | Description |
|-----------|------|-------------|
| `start_time` | RFC3339 timestamp | Include events that occurred at or after this time |
| `end_time` | RFC3339 timestamp | Include events that occurred at or before this time |
| `limit` | integer | Maximum events to return (default: 100, max: 1000) |
| `cursor` | string | Opaque cursor from a previous response; in watch mode, use the cursor from a `watermark` line |
| `watch` | boolean | Stream matching records as `application/x-ndjson` until the client disconnects |
| `context_id` | string | Log context filter |
| `stream` | string | Log stream filter: `stdout`, `stderr`, or `pty` |
| `source` | string | Event producer filter: `manager`, `netd`, or `procd` |
| `event_type` | string | Event projection filter: `lifecycle`, `network_audit`, or `runtime_stats` |
| `outcome` | string | Event outcome filter: `completed`, `denied`, `error`, `succeeded`, or `failed` |

Every returned event and log entry includes `team_id`, `sandbox_id`, `region_id`, `cluster_id`, `occurred_at`, `ingested_at`, and `cursor`. Event rows also include `source`, `event_type`, and `watermark`; log rows include `stream` and `message`. The response cursor is opaque and separate from each producer cursor.

Set `watch=true` to stream matching log or event records as NDJSON in ingestion order. Without `cursor` or `start_time`, a watch stream starts at request time and does not replay historical rows. When `watch=true`, `end_time` is not supported. A watch stream emits `event` or `log` lines, `watermark` lines with a resume cursor, periodic `heartbeat` lines, and an `error` line before termination if the backend fails after the stream starts. Runtime metrics are queried as bounded time series and do not expose a watch mode.

ClickHouse-backed deployments store events, logs, and wide runtime samples in asynchronous per-sandbox projection tables with independent retention TTLs. Netd publishes network audit events, manager publishes logs, and node-local ctld instances collect sandbox-wide CRI runtime samples every 15 seconds with jitter.

Historical data query endpoints return `503` when the backend is disabled or unavailable. The static metric catalog remains available so clients can build selectors without probing storage.

<Callout variant="info">
Metering windows are separate usage-ledger records for quota, showback, and billing export. Historical observability tables do not store metering windows, even when both use the same region ClickHouse component. File audit and SandboxVolume usage are not part of these endpoints. Writable rootfs metrics describe only the runtime container layer and are omitted when the CRI cannot report them.
</Callout>

---

## List Sandboxes

List all sandboxes with optional filters.

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

### Query Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `status` | string | Filter by status (`starting`, `running`, `failed`, `completed`, `terminating`) |
| `template_id` | string | Filter by template ID |
| `paused` | boolean | Filter by paused state independently of `status` |
| `limit` | integer | Max results per page (default: 50, max: 200) |
| `offset` | integer | Pagination offset (default: 0) |

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `limit := 10
sandboxes, err := client.ListSandboxes(ctx, &sandbox0.ListSandboxesOptions{
    Status:     "running",
    TemplateID: "default",
    Limit:      &limit,
})
if err != nil {
    log.Fatal(err)
}
for _, sb := range sandboxes.Sandboxes {
    fmt.Printf("- %s (status: %s)\\n", sb.ID, sb.Status)
}`
    },
    {
      label: "Python",
      language: "python",
      code: `sandboxes = client.sandboxes.list(
    status="running",
    template_id="default",
    limit=10,
)
for sb in sandboxes:
    print(f"- {sb.id} (status: {sb.status})")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `const { sandboxes } = await client.sandboxes.list({
    status: 'running',
    templateId: 'default',
    limit: 10,
});
for (const sb of sandboxes) {
    console.log(\`- \${sb.id} (status: \${sb.status})\`);
}`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 sandbox list --status running --template-id default`
    }
  ]}
/>

---

## Update Sandbox

Update sandbox configuration at runtime without restarting the sandbox.

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

### Updatable Fields

Only the following fields can be updated at runtime:

| Field | Type | Description |
|-------|------|-------------|
| `ttl` | integer | Time to live in seconds (soft limit) |
| `hard_ttl` | integer | Hard sandbox lifetime in seconds |
| `resources` | object | Runtime resource override. Only `resources.memory` is accepted. |
| `network` | object | Network policy configuration |
| `auto_resume` | boolean | Auto-resume when accessed |
| `services` | array | Sandbox Services for public HTTP routes, including Sandbox Functions |

<Callout variant="warning">
<code>env_vars</code> and <code>webhook</code> cannot be updated at runtime. These fields only take effect when the sandbox is created. To use different sandbox-level environment variables, create a new sandbox.
</Callout>

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `_, err = client.UpdateSandboxMemory(ctx, sandbox.ID, "2Gi")
if err != nil {
    log.Fatal(err)
}`
    },
    {
      label: "Python",
      language: "python",
      code: `client.sandboxes.update_memory(sandbox.id, "2Gi")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `await client.sandboxes.updateMemory(sandbox.id, '2Gi');`
    },
    {
      label: "CLI",
      language: "bash",
      code: `s0 sandbox update sb_abc123 --memory 2Gi`
    }
  ]}
/>

---

## Pause And Resume

Pause and resume is covered in a dedicated page because it affects TTL, `auto_resume`, service routes, SSH, and webhook behavior.

See [Pause And Resume](/docs/sandbox/pause-resume) for explicit pause, state inspection, resume, and auto-resume behavior.

See [Snapshot And Restore](/docs/sandbox/snapshot-restore) for named rootfs snapshots, restore, and fork operations. Snapshot and fork accept a running or paused source sandbox; restore requires a paused target sandbox.

---

## Refresh Sandbox TTL

Extend the sandbox time-to-live. This resets both `ttl` and `hard_ttl` (if configured) from the current time while the sandbox has a runtime.

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

### Request Body

| Field | Type | Description |
|-------|------|-------------|
| `duration` | integer | Duration to extend TTL in seconds (optional, defaults to original TTL) |

<Callout variant="info">
If <code>duration</code> is not specified, both <code>ttl</code> and <code>hard_ttl</code> are reset to their original configured values. Use this to keep a sandbox alive as long as the user is actively using it.
</Callout>

<Tabs
  tabs={[
    {
      label: "Go",
      language: "go",
      code: `// Refresh with default duration (original TTL)
resp, err := client.RefreshSandbox(ctx, sandbox.ID, nil)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("New expires at: %s\\n", resp.ExpiresAt)

// Refresh with custom duration (e.g., 1 minute)
resp, err = client.RefreshSandbox(ctx, sandbox.ID, &apispec.SandboxRefreshRequest{
    Duration: apispec.NewOptInt32(60),
})
if err != nil {
    log.Fatal(err)
}
fmt.Printf("New expires at: %s\\n", resp.ExpiresAt)`
    },
    {
      label: "Python",
      language: "python",
      code: `from sandbox0.apispec.models.sandbox_refresh_request import SandboxRefreshRequest

# Refresh with default duration (original TTL)
resp = client.sandboxes.refresh(sandbox.id)
print(f"New expires at: {resp.expires_at}")

# Refresh with custom duration (e.g., 1 minute)
resp = client.sandboxes.refresh(sandbox.id, SandboxRefreshRequest(duration=60))
print(f"New expires at: {resp.expires_at}")`
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `// Refresh with default duration (original TTL)
const resp = await client.sandboxes.refresh(sandbox.id);
console.log('New expires at:', resp.expiresAt);

// Refresh with custom duration (e.g., 1 minute)
const resp2 = await client.sandboxes.refresh(sandbox.id, { duration: 60 });
console.log('New expires at:', resp2.expiresAt);`
    },
    {
      label: "CLI",
      language: "bash",
      code: `# Refresh with default duration
s0 sandbox refresh sb_abc123

# Output includes the new expiry timestamp`
    }
  ]}
/>

---

## Delete Sandbox

Terminate and delete a sandbox. This action is irreversible.

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

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

---

## Next Steps

<CardGroup>
  <Card title="Pause And Resume" href="/docs/sandbox/pause-resume" cta="Continue">
    Control checkpointed pause and resume behavior before wiring long-lived workflows.
  </Card>

  <Card title="Snapshot And Restore" href="/docs/sandbox/snapshot-restore" cta="Continue">
    Create named rootfs snapshots, restore paused sandboxes, and fork sandbox state.
  </Card>

  <Card title="Contexts" href="/docs/sandbox/contexts" cta="Continue">
    Run REPL and command contexts inside a sandbox and stream process output.
  </Card>
</CardGroup>
