Fork 100 microVMs in 101 ms. BRANCH a live VM in 56 ms (v0.4 live mode).
Unedited: `forkd pull` a portable snapshot, then fork 100 microVMs — 100/100 alive, ~200 ms wall-clock. asciicast. (First pull also fetches the rootfs sidecar once; cached here.)
A microVM sandbox runtime for AI agent fan-out. Children fork from a warmed parent snapshot, inheriting its address space copy-on-write instead of cold-booting their own kernel.
forkd is built on Firecracker. The parent VM boots once, imports
your runtime (Python + your dependencies, a JIT-warmed JVM, an
already-loaded ML model) and is paused to disk. Each child is a
separate Firecracker process that mmaps the parent's memory image
with MAP_PRIVATE; the kernel implements copy-on-write at the page
level, so children share the parent's resident memory until they
diverge.
The result is two properties at once: per-child KVM isolation, and a
spawn cost that's closer to fork(2) than to a cold-boot VM.
forkd also supports BRANCH: pause a running sandbox, snapshot its in-flight state, and resume — all in ~150 ms — so an agent can fork mid-thought, not only at warm-up. v0.3.4 fixed a slow-path regression where repeated BRANCHes on the same parent ballooned from 150 ms to 2.7 s (#146); the chain now stays flat (17.6× faster on the 6th consecutive BRANCH).
v0.4 live BRANCH collapses the source-pause window from ~200 ms
(Diff) to 56 ms p50 / 64 ms p90 on a 1.5 GiB source — measured
on a real BRANCH workload, bench/live-fork-pause-window/RESULTS-v0.4.md.
3.6× faster pause vs v0.3 Diff at p50, and the gap widens on
slower storage because Live's pause is disk-independent (memory
copy runs after resume, not during). With wait: false the caller
returns in ~70 ms while the background copy completes asynchronously
— a 200× RT improvement for fire-and-forget BRANCH from agent
code. Pass --live / --no-wait on the CLI, mode: "live" /
wait: false on REST, or the same on the Python / TypeScript / MCP
SDKs.
from forkd import Controller
c = Controller()
# Source must boot with live_fork=True (memfd-backed RAM, the prereq
# for UFFD_WP to see writes from the running parent).
parent = c.spawn_sandboxes("pyagent", n=1, live_fork=True)[0]
# ... drive parent ... then BRANCH live + fire-and-forget:
branch = c.branch_sandbox(parent["id"], mode="live", wait=False)
# Returns after ~10 ms with status="writing"; poll list_sna