Sandboxes are live Firecracker child VMs forked copy-on-write from a registered snapshot. Each child is a fully-isolated KVM microVM with its own vCPU state, network namespace, and cgroup memory limit, but shares the parent snapshot’s memory pages until it diverges. Forking 100 sandboxes from a warm parent takes roughly 100 ms on typical hardware — closer toDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/deeplethe/forkd/llms.txt
Use this file to discover all available pages before exploring further.
fork(2) than to a cold VM boot.
Once a sandbox is running, you can execute subprocesses inside it, evaluate Python expressions against the already-warmed PID 1 interpreter, and branch the live VM into a new snapshot so other sandboxes can fork from that mid-execution checkpoint.
POST /v1/sandboxes
Fork n children from a registered snapshot tag. All children start from the same warm parent state copy-on-write. Returns an array of SandboxInfo objects, one per child.
Request body
The registered snapshot tag to fork from. Returns
404 if no such tag is
registered.Number of children to fork in this call. Must be between
1 and 1000
inclusive.When
true, place each child in its own pre-provisioned network namespace
(forkd-child-<i>). The host must have created those namespaces before
forking via scripts/netns-setup.sh N.When false, all children share the host’s default network namespace. This
is fine for local development but means children can observe each other’s
network traffic.Optional. Set a
memory.max cgroup v2 limit in MiB for each child. Requires
cgroup v2 unified hierarchy and write access to /sys/fs/cgroup/forkd/.
When not set, children have no memory cap beyond available host RAM.v0.4+. Spawn each sandbox with a memfd-backed RAM region instead of a
file-backed one. Required for later
POST /v1/sandboxes/:id/branch calls
using mode: "live" — UFFD_WP only works on shmem/memfd-backed VMAs.Requires Linux ≥ 5.7 and the vendored Firecracker fork. Run forkd doctor
to verify both prerequisites are met.Sandboxes spawned without live_fork: true can still take full and
diff branches; they just cannot take live branches.201 Created
An array of SandboxInfo objects, one per forked child.
| Status | Cause |
|---|---|
400 Bad Request | n is out of range, or request body is malformed. |
404 Not Found | snapshot_tag is not registered. |
500 Internal Server Error | Firecracker restore failure — check daemon logs. |
GET /v1/sandboxes
List all currently active sandboxes. Returns an array of SandboxInfo objects.
Response — 200 OK
GET /v1/sandboxes/:id
Fetch metadata for a single sandbox.
Path parameter
The sandbox ID (e.g.
sb-67a1b3-0000).200 OK
Returns a single SandboxInfo object.
Errors
| Status | Cause |
|---|---|
404 Not Found | No sandbox with that ID is currently alive. |
DELETE /v1/sandboxes/:id
Terminate a sandbox. Kills the Firecracker process and removes the cgroup leaf for that child. Returns 204 No Content.
Path parameter
The sandbox ID to terminate.
Termination does not delete any snapshot that was branched from this sandbox.
Snapshots produced by
POST /v1/sandboxes/:id/branch are independent of the
source sandbox’s lifecycle and persist until explicitly deleted via
DELETE /v1/snapshots/:tag.| Status | Cause |
|---|---|
404 Not Found | No sandbox with that ID is alive. |
POST /v1/sandboxes/:id/ping
Round-trip to the guest agent (PID 1) inside the VM. Use this to verify the sandbox is healthy and to confirm which runtime version is loaded.
Path parameter
The sandbox ID to ping.
200 OK
Always
true on a healthy response.The numpy version imported in the warmed PID 1 interpreter. Useful for
verifying the snapshot’s runtime version without spawning a subprocess.
PID of the in-guest agent process. Always
1 when the agent runs as the
init process.POST /v1/sandboxes/:id/exec
Spawn a subprocess inside the sandbox and capture its output. The command runs as a fresh child process, so it pays the full subprocess startup cost including any Python import time.
Path parameter
The sandbox ID to execute in.
Argv list for the subprocess, e.g.
["python3", "-c", "print(2+2)"].Maximum time in seconds to wait for the subprocess to exit before the
daemon kills it and returns an error.
200 OK
Captured standard output from the subprocess.
Captured standard error from the subprocess.
Exit status of the subprocess.
0 on success.POST /v1/sandboxes/:id/eval
Evaluate a Python expression against the already-warmed interpreter running as PID 1 inside the sandbox. Because this reuses the existing interpreter process rather than spawning a new one, it avoids the full Python startup and import cost.
| Path | Latency | Mechanism |
|---|---|---|
eval | ~1 ms | Reuses warmed PID 1 interpreter |
exec python3 -c ... | ~96 ms | Cold subprocess re-imports everything |
The sandbox ID to evaluate in.
A Python expression string, e.g.
"numpy.zeros(5).sum()". The expression
is evaluated in the context of the already-imported modules in PID 1.200 OK
String representation of the expression’s return value.
null when the
expression raised an exception.Error message if the expression raised an exception.
null on success.0 on success, non-zero if the eval failed.POST /v1/sandboxes/:id/branch
Pause a running sandbox, snapshot its in-flight memory and vCPU state into a new snapshot tag, then resume the sandbox. The resulting snapshot is completely independent of the source sandbox’s lifecycle — fork from it or delete it regardless of whether the source is still alive. Volumes inherited from the source snapshot are carried forward automatically, so grandchildren see the same persistent disks.
This is forkd’s key primitive for mid-execution fan-out: an agent can reach a decision point, branch its entire VM state, and have multiple independent children each explore a different path — all inheriting the same prior computation cost-free.
Path parameter
The ID of the running sandbox to branch.
Optional tag for the new snapshot. When unset, the daemon auto-generates
branch-<source-id>-<unix-ts>. Must match
^[A-Za-z0-9_][A-Za-z0-9._-]{0,63}$ when provided.v0.4+. Branch mode controlling the source VM’s pause window and the
mechanism used to capture memory. One of
"full", "diff", or "live".Legacy v0.3 compatibility. Equivalent to
mode: "diff". Use mode
instead for new code. Mutually exclusive with mode — sending both returns
400 Bad Request.v0.4+, only meaningful with
mode: "live". When false, the daemon
returns a SnapshotInfo with status: "writing"
as soon as the source VM resumes (~10 ms). The background memory copy
continues asynchronously and status flips to "ready" (or "failed")
when complete. Poll GET /v1/snapshots to detect completion.When true (the default), the call blocks until the full copy finishes
before returning status: "ready".Setting wait: false without mode: "live" returns 400 Bad Request.201 Created
Returns a SnapshotInfo with branched_from set to the source sandbox ID and pause_ms populated with the measured pause window.
mode: "live" and wait: false, the response returns while the background copy is still running:
"full" due to the longer pause window).
| Mode | Source pause p50 | Notes |
|---|---|---|
"full" | 0.5–8 s | Whole guest RAM written during pause. Highest pause cost; simplest restore. ~150–500 ms for 512 MiB on ext4 SSD. |
"diff" | ~200 ms idle | Dirty-page diff during pause; full memory.bin reconstructed asynchronously. v0.3+. |
"live" | sub-50 ms | UFFD_WP: vmstate-only dump during pause, memory streamed asynchronously. v0.4+. 56 ms p50 / 64 ms p90 on 1.5 GiB source. |
If
resume fails after a successful snapshot, the snapshot file is intact
and returned to the caller. The source sandbox may be left in an unknown
state. The controller logs this as a warning rather than failing the request,
because the caller’s primary expectation — a valid new snapshot — has been
met.| Status | Cause |
|---|---|
400 Bad Request | Both mode and diff were set, or wait: false was sent without mode: "live". |
404 Not Found | Source sandbox ID is not in the live VM registry. |
409 Conflict | A snapshot with the requested tag already exists on disk — DELETE it first. |
409 Conflict | A branch for this exact tag is already in flight. |
503 Service Unavailable | Daemon is at its branch concurrency cap (default 4). Retry after a short delay. |
500 Internal Server Error | Pause, snapshot, or resume failure — check daemon logs. |
Schemas
SandboxInfo
Returned byPOST /v1/sandboxes, GET /v1/sandboxes, and GET /v1/sandboxes/:id.
Unique sandbox identifier generated by the daemon, e.g.
sb-67a1b3-0000.
Use this in all subsequent per-sandbox API calls.The snapshot tag this sandbox was forked from.
Name of the network namespace this sandbox is placed in, e.g.
forkd-child-1. null or absent when per_child_netns was false at
fork time.The in-guest TCP address of the guest agent, in
host:port form (e.g.
10.42.0.2:8888). The Python and TypeScript SDKs connect here for
exec and eval.Unix timestamp (seconds) when this sandbox was forked.
Host-side PID of the Firecracker process for this sandbox. Useful for
attaching debuggers or verifying the process is still alive. May be absent
if the daemon hasn’t yet recorded it.
cgroup v2
memory.max limit in MiB, if one was set at fork time. Absent
when no limit was requested.SnapshotInfo from branch
See SnapshotInfo in the Snapshots reference for the full field descriptions. When returned byPOST /v1/sandboxes/:id/branch, the following fields are always populated in addition to the base fields:
The source sandbox ID that was paused to produce this snapshot.
Measured pause window in milliseconds.
"ready" for synchronous branch modes (full, diff, and live with
wait: true). "writing" for mode: "live" with wait: false until the
background copy completes.