---
title: Sandboxes (ScaiBunker)
path: concepts/sandboxes-and-bunkers
status: published
---

# Sandboxes (ScaiBunker)

ScaiBunker is the sandboxed-compute integration. When a flow needs to execute untrusted-ish code (running a user's Python snippet, sandboxing a tool plugin's side effects, materializing files), it provisions a Bunker, does its work, and tears it down.

## The five compute nodes

| Node | Plugin call | Returns |
|---|---|---|
| `compute_provision` | `scaibunker.create(image, lifecycle, ...)` | bunker handle |
| `compute_exec` | `scaibunker.exec(command, timeout_s)` | exec result |
| `compute_file_upload` | `scaibunker.write_file(path, content)` | — |
| `compute_file_download` | `scaibunker.read_file(path)` | content |
| `compute_destroy` | `scaibunker.destroy()` | — |

All five compile to `plugin_call` statements (inside the parent `rigid` block, no nested wrappers).

## Lifecycle

Tied to the Core's activation mode:

- **`ephemeral`** — provisioned per-invocation, destroyed when the flow exits. The right choice for stateless workloads.
- **`session`** — provisioned per-conversation (entity-mode Cores). Persists across multiple invocations to the same instance.
- **`persistent`** — long-lived, shared across invocations. Use carefully.
- **`appliance`** — v0.2 NetworkSegment-bound profile. Pinned to a specific cluster slice.

## The flow-level `bunker` capability

In addition to the per-node `compute_provision` config (which sets the args for the `scaibunker.create` plugin call), the **flow-level** `flow.config.bunker` block declares the manifest-time capability ScaiGrid needs to know about at deploy time:

```jsonc
"bunker": {
  "lifecycle": "ephemeral",
  "image": "python-3.12",
  "cpu_millicores": 1000,
  "memory_mb": 512,
  "disk_mb": 2048,
  "gpu_count": 0,
  "network_profile": "registry"
}
```

The canvas's BunkerCapability editor exposes this in the Flow properties panel. If you leave it unset but have any `compute_*` node on the canvas, the compiler auto-defaults to `{lifecycle: "ephemeral", image: "python-3.12"}` so the deploy doesn't fail. Set explicit limits if you need anything bigger.

## Network profiles

Coarse policy on what the Bunker can reach:

| Profile | What it allows |
|---|---|
| `isolated` | Loopback only. Sandbox can't reach anything. |
| `registry` | Image registry + a small allowlist for fetching dependencies. Default. |
| `allowlisted` | `network_allowlist` is consulted (wildcard subdomains supported). |
| `unrestricted` | Full egress. Use carefully. |
| `transit` | Reserved for NetworkSegment-bound profiles. |

## Implicit plugin registration

When any `compute_*` node lands on the canvas, the compiler auto-adds `scaibunker` to `flow.config.plugins` if it isn't there. You don't have to manually maintain that list.

## Resources at the flow level vs. per-call args

The flow-level `bunker` is the capability declaration — `core.capabilities[].bunker` in the YAML manifest. The per-`compute_provision`-node config is the actual args to the `scaibunker.create` plugin call at runtime. They typically agree, but if you're provisioning multiple distinct Bunkers in one flow you might set different per-call args while the flow-level capability sets the upper bound the cluster allows.

## Reading state from the canvas

The proxy `GET /v1/scaibunker/bunkers` lists currently-active bunkers visible to your tenant. Wired into the canvas's Live Runs panel as a deferred feature; for now you can hit the proxy directly for ops insight.
