Open-source project
deeplethe/forkd avatar
deeplethe/forkd

forkd: fork(2)-style copy-on-write spawning for Firecracker AI agent microVMs

Fork() for AI agent microVMs. Spawn 100 children in ~100ms from a warm parent; BRANCH a live VM in ~150ms. KVM-isolated, snapshot CoW.

2,840 stars213 forksRustApache-2.0

At a glance

What is it?
forkd is a Rust microVM runtime that boots a parent Firecracker VM once, pauses it to disk, and spawns children that map the parent memory image with MAP_PRIVATE so the kernel shares pages until they diverge. The README's headline numbers are strong; the per-link cost of its v0.5 snapshot chains is the part that decides whether it fits your workload.
Who is it for?
Adopt forkd if you run many short-lived, near-identical agent sandboxes on Linux 5.7 or newer and can accept a vendored Firecracker fork, a userfaultfd sysctl or CAP_SYS_PTRACE, and a chain design whose measured per-link spawn tax runs in the hundreds of milliseconds. Do not adopt it if you need one long-lived stateful VM per tenant, or if you cannot run privileged KVM workloads.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The cold-boot tax that forkd is built to remove

Every agent sandbox that boots its own kernel pays for that boot. If you want a hundred sandboxes running the same Python environment with the same dependencies already imported, you pay that cost a hundred times, and the work each sandbox does may be a few seconds of tool calls. forkd targets exactly this fan-out pattern. The README describes the audience as AI agent fan-out: cases where a parent VM boots once, imports your runtime (the README names Python with your dependencies, a JIT-warmed JVM, and an already-loaded ML model as examples), and is then paused to disk. Children are not clones booting from scratch; they inherit the parent's warmed address space. The claim in the README's opening line is that you can fork 100 microVMs in 101 ms. That figure comes from the project's own material, not from independent measurement, and the README notes the quickstart recording was made on a machine where the rootfs sidecar was already cached.

How the copy-on-write spawn actually works

forkd is built on Firecracker. Each child is a separate Firecracker process that mmaps the parent's memory image with MAP_PRIVATE, and the kernel implements copy-on-write at the page level. Until a child writes to a page, it reads the parent's resident memory. Two properties follow from that design: per-child KVM isolation, because each child is its own Firecracker process rather than a thread in a shared address space, and a spawn cost the README compares to fork(2) rather than to a cold boot. The trade-off is implicit in the mechanism. Page sharing only holds while children stay close to the parent. An agent that loads a large model or writes a large buffer diverges from the shared image and pulls private pages, and the memory advantage narrows toward the cost of running that many full VMs. The README does not quantify divergence behaviour, so treat memory savings as workload-dependent.

BRANCH: snapshotting a VM mid-thought

Forking only at warm-up is limiting if the interesting state is produced after the agent has been running for a while. forkd's BRANCH pauses a running sandbox, snapshots its in-flight state, and resumes. The README quotes roughly 150 ms for that path, and documents a v0.3.4 fix for a slow-path regression where repeated BRANCHes on the same parent ballooned from 150 ms to 2.7 s (issue #146); the project says the chain now stays flat and reports a 17.6x improvement on the sixth consecutive BRANCH. v0.4 adds a live mode: the source-pause window drops from about 200 ms in the Diff path to 56 ms p50 and 64 ms p90 on a 1.5 GiB source, measured in the project's own bench/live-fork-pause-window/RESULTS-v0.4.md. The mechanism is that the memory copy runs after resume rather than during the pause, so the pause is disk-independent. With wait set to false, the caller returns in roughly 70 ms and the background copy finishes asynchronously. Live mode has a prerequisite the README states plainly: the source must boot with live_fork=True, which means memfd-backed RAM, because userfaultfd write-protection needs to observe writes from the running parent.

Getting it running, and the two prerequisites that bite first

The README's CLI example requires root and a flag set: sudo -E forkd fork --tag pyagent -n 1 --per-child-netns --live-fork, followed by sudo -E forkd snapshot --from-sandbox <sb-id> --live --no-wait. The Python path is a Controller object: c.spawn_sandboxes("pyagent", n=1, live_fork=True) returns sandboxes, and c.branch_sandbox(parent["id"], mode="live", wait=False) returns after roughly 10 ms with status set to writing. You then poll list_snapshots until status reads ready. The same switches appear as mode: "live" and wait: false over REST, and in the Python, TypeScript and MCP SDKs. Two host requirements come before any of this. Linux 5.7 or newer, and vm.unprivileged_userfaultfd=1 or CAP_SYS_PTRACE. On top of that, forkd depends on a vendored Firecracker fork, deeplethe/firecracker on branch forkd-v0.4-mem-backend-shared-v1.12, not upstream Firecracker. The README says forkd doctor probes both the kernel requirement and the vendored fork, so that command is the first thing to run on a candidate host. A stated gap: the CLI's local fork path and the daemon-tracked path do not compose yet, and daemon-side spawn from the CLI is tracked in issue #209.

v0.5 diff-snapshot chains stack layers, and the per-link tax is real

The v0.5 feature addresses a practical waste: if an agent caches pip install numpy, then pandas, then scikit-learn as three separate snapshots, you do not want three copies of the same 1.5 GiB base. Diff-snapshot chains record a parent_tag plus a content-hash edge to the layer below, and the daemon walks the chain at spawn time and assembles the memory image in one pass. The commands are forkd snapshot-diff --from py-base --tag py-numpy --exec "pip install numpy==2.0.2", then the same with --from py-numpy for pandas. Spawning from the chain head is a single forkd fork --tag py-pandas -n 1, which the README says is one POST /v1/sandboxes round-trip for the caller. forkd snapshot-info prints chain depth, parent_tag, ancestors and dependents. Deletion is guarded: forkd rmi py-numpy returns HTTP 409 and names the dependent, with --cascade to delete the subtree or --force to orphan children. forkd snapshot-compact --from py-pandas --to py-pandas-flat flattens a deep chain, and forkd pack --tag py-pandas --out py-pandas-chain.tar.zst plus forkd unpack ships the whole chain. The catch is in the project's own Phase 5 bench table on a 512 MiB base, ext4, i7-12700: a flat base spawns at 59 ms p50, but +numpy at depth 1 costs 751 ms, +pandas at depth 2 costs 1222 ms, and +sklearn at depth 3 costs 1668 ms. The README attributes the per-link tax to SHA-256 of the base, around 460 ms, and notes that a flat-equivalent snapshot with all three packages in one diff spawns at 746 ms. Deep chains are convenient to build and expensive to spawn from. That is the single most important operational fact in the v0.5 material, and it argues for compacting chains before they reach production traffic.

Where forkd is the wrong tool

The requirements exclude a lot of environments before performance enters the picture. Linux 5.7 or newer, plus vm.unprivileged_userfaultfd=1 or CAP_SYS_PTRACE, plus a vendored Firecracker fork. A managed container platform without KVM access, or a host where you cannot change that sysctl or grant that capability, is out. macOS and Windows developers cannot run the local path at all. The vendored fork is a second constraint: you are not tracking upstream Firecracker, so kernel or VMM changes that land upstream have to be reconciled by the project. The chain bench suggests a second mismatch. If your agents each diverge heavily after spawn, or each need distinct large state, copy-on-write buys you little and you are paying KVM overhead for the privilege. And if your problem is one long-lived stateful VM per tenant, there is nothing here to fork from. The README also flags that CLI local spawn and daemon-tracked spawn do not compose yet, so scripting around the daemon from the CLI is not a finished path.

What forkd is not: a comparison with plain Firecracker

The obvious alternative is Firecracker on its own, which forkd uses underneath. Upstream Firecracker boots a microVM from a kernel and rootfs; each boot is independent, and there is no shared memory image between VMs. If you want a hundred warmed Python environments, you either boot a hundred times or you build your own snapshot-and-restore layer. forkd's difference is precisely that layer: a paused parent, an mmap with MAP_PRIVATE, page-level copy-on-write, and a daemon that tracks snapshots, chains, and content hashes. It also adds the BRANCH path, which upstream Firecracker does not offer as a documented pause-snapshot-resume flow with a live mode. The cost of that difference is the vendored Firecracker fork and the userfaultfd requirement. If your workload is a small number of long-lived VMs, plain Firecracker or a container runtime is simpler and avoids both. If your workload is fan-out from a warmed image, the layer is the whole point.

Maintenance cost and the licence position

The repository is active, not archived, with the last push dated 2026-09-02 and releases v0.5.1 through v0.5.3 between June and July 2026. The v0.5.1 release note mentions a guest kernel rebuild that closed issues #218 and #225, which is a reminder that guest kernel work is part of the maintenance surface here, not just userspace Rust. Upgrading means tracking the vendored Firecracker branch as well as forkd itself, and the README ties live mode to a specific branch name, forkd-v0.4-mem-backend-shared-v1.12, which suggests the fork is versioned alongside the feature set rather than tracking upstream continuously. The project is Apache-2.0, which permits commercial use and modification and includes a patent grant; that is a permissive licence, but the vendored Firecracker fork and any guest kernel components may carry their own terms, and the README does not spell those out. Check the LICENSE file and the vendored tree before shipping. This is not legal advice.

Editorial conclusion

Adopt forkd if you run many short-lived, near-identical agent sandboxes on Linux 5.7 or newer and can accept a vendored Firecracker fork, a userfaultfd sysctl or CAP_SYS_PTRACE, and a chain design whose measured per-link spawn tax runs in the hundreds of milliseconds. Do not adopt it if you need one long-lived stateful VM per tenant, or if you cannot run privileged KVM workloads. Verify first: that `forkd doctor` reports both the kernel and the vendored Firecracker fork as satisfied on your host, and that your snapshot chains stay shallow enough that the per-link tax in bench/chain-spawn/RESULTS-v0.5.md does not dominate your spawn budget.

Official sources

  1. deeplethe/forkd on GitHub
  2. Issues
  3. License: Apache-2.0
  4. README
  5. Releases
Community notes

Community notes