Skip to content

Releases: deeplethe/forkd

v0.5.2

08 Jun 01:57
c65593e

Choose a tag to compare

v0.5.2 is a small feature + security-fix release headlined by forkd's first external contribution.

🎉 First external PR — @theflashwin

#230 plumbs hugepage-backed memfd snapshots end-to-end — Rust MemoryBackend::MemfdShared { use_hugepages: bool }, REST + CLI + Python SDK + TypeScript SDK all carry the flag, a forkd doctor hugepage-pool check, an ENOMEM → normal-page fallback with warning, and a fresh A/B bench harness (bench/live-fork-pause-window/bench-hugepages.py) that interleaves baseline/hugepages iterations for fair numbers.

Unusually complete for a first PR. Welcome aboard, @theflashwin.

What it does

Sandboxes spawned with --live-fork can now back their guest RAM with 2 MiB hugepages (MFD_HUGETLB | MFD_HUGE_2MB) instead of 4 KiB pages, reducing TLB pressure on spawn-many and live-BRANCH bulk-copy. Opt in per spawn:

# CLI
forkd fork --tag my-snapshot --live-fork --hugepages -n 10

# REST
POST /v1/sandboxes  { "snapshot_tag": "my-snapshot", "live_fork": true, "hugepages": true, "n": 10 }

# Python SDK
sandbox = Sandbox.spawn(tag="my-snapshot", live_fork=True, hugepages=True)

# TypeScript SDK
const sb = await client.sandboxes.create({ snapshotTag: "my-snapshot", liveFork: true, hugepages: true });

Pre-flight check: forkd doctor now reports HugePages_Total / HugePages_Free. Reserve pages with echo 512 | sudo tee /proc/sys/vm/nr_hugepages before use.

N=4 spawn numbers from @theflashwin's hardware are in the PR README. N=100 numbers are deferred to #234 — fresh dev-host setup needed.

🔒 axum-server 0.7 → 0.8 — closes #192

Drops the unmaintained rustls-pemfile dependency flagged by RUSTSEC-2025-0134. The daemon's TLS path now goes through rustls-pki-types directly. Internal-only change; HTTPS bind / cert loading are byte-compatible.

cargo audit on forkd v0.5.2 is now clean (0 advisories). (#232)

📚 Non-AI recipes

recipes/README.md now leads with a "by problem you're solving" table that surfaces CI test parallelization, database fixture forking, and browser farming alongside the AI-agent use case, instead of pitching forkd as AI-only.

New recipe recipes/ci-parallel-pytest ships a complete Python pytest fan-out demo (5 test files, ~30 tests across numpy / pandas / sklearn / regex / stdlib) backed by a forkd snapshot so each worker starts with import numpy etc. already paid for in the parent. Dev-box numbers: ~20 ms per worker batch-spawned vs ~2–3 s per cold container. (#231)

Install / upgrade

# Linux x86_64 binary (forkd + forkd-controller + forkd-uffd-handler)
curl -L https://github.com/deeplethe/forkd/releases/download/v0.5.2/forkd-v0.5.2-x86_64-linux.tar.gz | tar xz

# Python SDK
pip install -U forkd==0.5.2

# TypeScript / Node SDK
npm install @deeplethe/forkd@0.5.2

No breaking changes — --hugepages requires --live-fork; everything else is additive. Existing spawn / branch flows work unchanged.

Thanks

  • @theflashwin — first external contribution; hugepage memfd path
  • Everyone who filed RUSTSEC-2025-0134 follow-ups (#192)

Full Changelog: v0.5.1...v0.5.2

What's Changed

  • bench(v0.5.1): real-package pip-install chain spawn numbers by @WaylandYang in #228
  • chore(sdk): bump Python + TS SDK versions to 0.5.1 — unblock PyPI publish by @WaylandYang in #229
  • recipes: surface non-AI use cases + ship ci-parallel-pytest by @WaylandYang in #231
  • chore(deps): bump axum-server 0.7 → 0.8 — closes #192 by @WaylandYang in #232
  • feat(live-fork, memfd): Back Mem Snapshot with Hugepages by @theflashwin in #230
  • release: v0.5.2 — hugepage memfd + axum-server 0.8 + non-AI recipes by @WaylandYang in #233
  • fix(docker): include experiments/ in builder so workspace manifest parses by @WaylandYang in #235

New Contributors

Full Changelog: v0.5.1...v0.5.2

v0.5.1 — guest kernel rebuild closes #218 + #225

05 Jun 03:35
a1b3256

Choose a tag to compare

v0.5.1 is a bug-fix release that finally makes pip install (and anything else that hits OpenSSL) work inside forkd guests.

The bug

Pre-v0.5.1 forkd shipped a Linux 4.14.174 guest kernel built 2021-07-14. That kernel lacked every entropy modernization since:

Feature Available since 4.14.174
CONFIG_HW_RANDOM_VIRTIO (virtio-rng driver) not built in
CONFIG_RANDOM_TRUST_CPU (RDRAND credit-init) not set
random.trust_cpu= cmdline option Linux 4.19 silently ignored
CONFIG_VMGENID (FC-fork CRNG reseed) Linux 5.20 absent

Net effect: getrandom(2) blocked forever after FC restore, since the CRNG never marked itself initialized. Any guest workload that touched OpenSSL — pip install, urllib.request HTTPS, requests — hung indefinitely. Tracked in #218 (symptom) + #225 (root cause).

The fix

Swap to Firecracker's CI-blessed Linux 6.1.141 vmlinux. SHA-256 pinned: b36a4a1b10f33b9cfdcde3d1a787d9c090556a3edb211cd06d1f3f9a6c7e8724. End-to-end verified on the dev box on 2026-06-05:

before after
uname -r 4.14.174 6.1.141
/dev/hwrng absent crw, virtio-rng
getrandom(GRND_NONBLOCK) EAGAIN 16 bytes, errno=0
ssl.create_default_context() 0.01 s
urllib.urlopen("https://pypi.org/…") 200, 0.25 s
pip install numpy==2.0.2 exit 1, empty output 21 s, exit 0
VMGENID after restore n/a dmesg: random: crng reseeded due to virtual machine fork

Two new helper scripts

  • scripts/install-guest-kernel.sh — downloads the FC CI vmlinux into /var/lib/forkd/kernels/vmlinux. SHA-256 pinned. Default path — recommended for almost everyone:
    sudo ./scripts/install-guest-kernel.sh
  • scripts/build-guest-kernel.sh — clones Linux 6.1 LTS, applies FC's reference microvm config (auto-asserts the entropy options), builds vmlinux from source. ~10 min on a 20-core box. For users who want to audit / tune the kernel config or target a non-x86_64 architecture.

forkd doctor's "kernel image" check now suggests these scripts when no vmlinux is found.

Migration

Existing snapshot vmstates from 4.14 restore fine on 6.1 — they just don't gain the new entropy device until they're re-baked. Run forkd from-image <image> --tag <tag> for each snapshot you care about to pick up CRNG init + /dev/hwrng + VMGENID.

Diff vs v0.5.0

  • #224 — wire virtio-rng + random.trust_cpu=on (forward-prep)
  • #226 — replace 4.14 guest vmlinux with Linux 6.1.141 (the actual fix)
  • #227 — workspace version bump + CHANGELOG header

What this unlocks

The v0.5 chain story can now be demoed for real:

forkd snapshot-diff --from py-base   --tag py-numpy   --exec "pip install numpy"
forkd snapshot-diff --from py-numpy  --tag py-pandas  --exec "pip install pandas"
forkd snapshot-diff --from py-pandas --tag py-sklearn --exec "pip install scikit-learn"
forkd fork --tag py-sklearn -n 1   # walks the chain transparently

Previously this hung at "Collecting numpy" forever. v0.5.1 runs to completion.

What's Changed

Full Changelog: v0.5.0...v0.5.1

v0.5.0 — diff-snapshot chains + v0.4 live BRANCH

04 Jun 16:15
7308a62

Choose a tag to compare

v0.5.0 ships v0.5 (diff-snapshot chains) and folds in v0.4 (live-fork BRANCH) — the first formal release since v0.3.4.

v0.5: diff-snapshot chains

Stack snapshots into a chain — base → +numpy → +pandas → +sklearn — instead of one flat snapshot per dependency set. The daemon walks the chain at spawn time and assembles memory in one pass.

forkd snapshot-diff --from py-base   --tag py-numpy   --exec "pip install numpy==2.0.2"
forkd snapshot-diff --from py-numpy  --tag py-pandas  --exec "pip install pandas==2.2.3"
forkd fork --tag py-pandas -n 1               # daemon walks the chain transparently

forkd snapshot-info py-pandas                 # depth, parent, ancestors, dependents
forkd rmi py-numpy                            # 409 + actionable msg if children
forkd rmi py-numpy --cascade                  # subtree delete
forkd snapshot-compact --from py-pandas --to py-pandas-flat
forkd pack --tag py-pandas --out chain.tar.zst # v2 manifest, bundles every ancestor
forkd unpack chain.tar.zst                    # one snapshot dir per chain link

Phase 5 bench (RESULTS-v0.5.md)

chain head depth spawn p50 per-link tax
base (flat) 0 59 ms
+numpy 1 751 ms +692 ms
+pandas 2 1 222 ms +471 ms
+sklearn 3 1 668 ms +446 ms
flat-equivalent 1 746 ms

Correctness: 90/90 (100%) probe passes across L1/L2/L3 + flat-equivalent. The design's vmstate-drift question is empirically closed.

The ~460 ms per-link tax tracks SHA-256 of the base (1.1 GiB/s) — the mmap-once incremental verify optimization is queued for v0.6.

v0.4: live-fork BRANCH (also captured here)

Sub-50 ms source-pause BRANCH via UFFD_WP + memfd_create. 56 ms p50 / 64 ms p90 on a 1.5 GiB python-numpy source — 3.6× faster than v0.3 Diff at p50 and disk-independent. With wait: false the caller returns in ~70 ms while the background memory copy completes asynchronously (200× RT improvement for fire-and-forget BRANCH).

Reachable via mode: "live" / wait: false on REST, --live / --no-wait on CLI, and the Python / TypeScript / MCP SDKs. forkd doctor probes the kernel and FC prerequisites.

Requires Linux ≥ 5.7, vm.unprivileged_userfaultfd=1 (or CAP_SYS_PTRACE), and the vendored Firecracker fork (33-line mem_backend.shared patch, upstream proposal at #5912).

Real bugs caught during ship-out

Two bugs the test suite hadn't caught — both surfaced by the Phase 5 bench / Phase 6 E2E fixture:

  • #217: Phase 2a chain-assembled memory.bin was unlinked by restore_many_with's startup sweep before FC's /snapshot/load could open it. Symptom: every spawn from a chained snapshot returned HTTP 500. Fixed; regression test work_dir_sweep_preserves_chainstage_subdirectory added.
  • #219: Pre-Phase-4 forkd rmi silently orphaned chained children. Now 409 with actionable message + --cascade / --force carve-outs.

What's in this tag

  • forkd snapshot-diff (v0.5 Phase 2b) — build chain layers
  • forkd snapshot-info (v0.5 Phase 4) — inspect chain shape + dependents
  • forkd snapshot-compact (v0.5 Phase 4) — flatten a chain head
  • forkd rmi --cascade | --force (v0.5 Phase 4) — chain-aware delete
  • forkd pack / unpack manifest v2 (v0.5 Phase 3) — chain-aware bundles
  • forkd doctor v0.4 checks (uffd_wp + memfd_create)
  • mode: "live" BRANCH (v0.4) via REST / CLI / SDKs
  • scripts/dev/v05-e2e.sh — 15-assertion smoke test for the whole v0.5 surface

Reproduce

End-to-end on a Linux + KVM host:

git checkout v0.5.0
cargo build --release -p forkd-cli -p forkd-controller
sudo target/release/forkd-controller serve --bind 127.0.0.1:8889 ...
FORKD_BIN=target/release/forkd FORKD_BASE_TAG=demo-pyt ./scripts/dev/v05-e2e.sh

Full v0.5 phase-by-phase notes: see CHANGELOG.md and the v0.5 tracking issue #216.

What's Changed

  • chore: post-#146 cleanup — remove stale warnings + mark probes resolved by @WaylandYang in #154
  • fix(cli): forkd doctor no longer warns on idle TAP (admin-UP + no peer) by @WaylandYang in #155
  • RFC: v0.4 live-fork via userfaultfd write-protect by @WaylandYang in #156
  • v0.4 foundation: WpBranch primitive + 3 PoCs + wp-bench CLI by @WaylandYang in #157
  • experiment: v0.4 Phase 4 PoC — WpBranch snapshot round-trip + restore by @WaylandYang in #160
  • fix: address external issues #158 + #159 by @WaylandYang in #161
  • security: fix bearer-token length oracle in constant_time_eq (#162) by @WaylandYang in #164
  • docs: lead Quick start with forkd pull, plus v0.3.4 / doctor-screenshot polish by @WaylandYang in #165
  • hub: add deeplethe/python-numpy v1 by @WaylandYang in #166
  • hub: add deeplethe/playwright-browser v1 (M1.1 headliner) by @WaylandYang in #167
  • hub: add deeplethe/postgres-fixture v1 (fork-per-test DB) by @WaylandYang in #168
  • hub: add deeplethe/coding-agent v1 (SWE-bench) by @WaylandYang in #169
  • hub: add deeplethe/nodejs v1 (generic JS base) by @WaylandYang in #170
  • hub: add deeplethe/e2b-codeinterpreter v1 (E2B drop-in) by @WaylandYang in #171
  • hub: add deeplethe/jupyter-kernel v1 by @WaylandYang in #172
  • docs: close M1.2 — 9 recipes in Hub, hosting decision, README done by @WaylandYang in #173
  • RFC: v0.4 user-facing API surface (CLI / REST / SDK) by @WaylandYang in #174
  • fix(vmm): cap volume_device_name at MAX_VOLUMES (closes #175) by @WaylandYang in #176
  • feat(controller): expose branch_concurrency via CLI + env var (closes #177) by @WaylandYang in #179
  • fix(vmm): apply_diff rejects diff/base size mismatch (closes #180) by @WaylandYang in #182
  • feat(metrics): expose forkd_branches_in_flight + cap (follow-up to #177) by @WaylandYang in #183
  • packaging(systemd): add cgroup to RestrictNamespaces (closes #163) by @WaylandYang in #184
  • docs(v0.4): vendor firecracker, document fork strategy by @WaylandYang in #185
  • feat(vmm): v0.4 Phase 5a — memfd_create() helper for live-fork backing by @WaylandYang in #186
  • feat(vmm): v0.4 Phase 5b — MemoryBackend::MemfdShared via restore_many_with by @WaylandYang in #187
  • chore(vendor): refresh FC patch to v1.12 with full build-clean diff by @WaylandYang in #188
  • docs(v0.4): add Phase 6 implementation blueprint by @WaylandYang in #189
  • feat(vmm): Phase 6.1 — Vm::snapshot_vmstate_only + VmstateOnly FC patch by @WaylandYang in #190
  • fix(docs): clear all rustdoc errors under -D warnings by @WaylandYang in #191
  • docs(v0.4): Phase 6.1.5 — FC POST /uffd/wp endpoint shipped by @WaylandYang in #194
  • feat(vmm): Phase 6.2 — Vm::memfd_handle + Vm::request_wp_uffd by @WaylandYang in #195
  • fix(controller): /healthz auth bypass accepts trailing slash too by @WaylandYang in #197
  • feat(uffd,vmm): Phase 6.3a — WpBranch::begin_with_external_uffd + PauseGuard by @WaylandYang in #198
  • feat(controller): Phase 6.3b — mode="live" path in branch_sandbox by @WaylandYang in #199
  • feat(controller): Phase 6.4 — wait=false live BRANCH + InFlightBranches by @WaylandYang in #200
  • feat(controller): expose live_fork on CreateSandboxRequest by @WaylandYang in #201
  • fix(v0.4): wire cross-process WP correctly + 2 latent bugs caught by E2E by @WaylandYang in #202
  • feat(controller): Phase 7.1 — canonical mode field on BRANCH by @WaylandYang in #204
  • feat(cli): Phase 7.2 — --live and --no-wait flags on forkd snapshot by @WaylandYang in #205
  • feat(sdk): Phase 7.3 — mode/wait/live_fork across Python, TS, MCP by @WaylandYang in #206
  • feat(doctor,uffd): Phase 7.4 — uffd_wp + memfd_create capability checks by @WaylandYang in #207
  • docs: align README + API.md + CHANGELOG with shipped v0.4 live-fork surface by @WaylandYang in #208
  • bench(v0.4): Phase 7.5 — live BRANCH pause-window data on a clean source by @WaylandYang in #210
  • feat(cli): forkd fork --live-fork — memfd-backed children for local v0.4 path by @WaylandYang in #211
  • feat(vmm): v0.5 Phase 1 — diff snapshot chain resolver + schema by @WaylandYang in h...
Read more

Hub: deeplethe/python-numpy v1

27 May 04:37
5500678

Choose a tag to compare

Hub snapshot pack for deeplethe/python-numpy — Python 3.12-slim + numpy preinstalled.

  • Base image: python:3.12-slim
  • Memory: 1536 MiB
  • Compressed size: 15.8 MiB (1.50 GiB uncompressed, 97× zstd)
  • Recipe: recipes/python-numpy/
forkd pull deeplethe/python-numpy
sudo -E forkd fork --tag python-numpy -n 5 --per-child-netns

Hub: deeplethe/postgres-fixture v1

27 May 04:58
bfcd388

Choose a tag to compare

Hub snapshot pack for deeplethe/postgres-fixture — PostgreSQL 16 with initdb complete and postmaster pre-launched.

  • Base image: postgres:16
  • Memory: 1024 MiB
  • Compressed size: 38.0 MiB (1.00 GiB uncompressed, 26.9× zstd)
  • Recipe: recipes/postgres-fixture/
  • Per-child fork-per-test: ~10 ms (vs ~2 s for a fresh container start + initdb).
forkd pull deeplethe/postgres-fixture
sudo -E forkd fork --tag postgres-fixture -n 20 --per-child-netns --memory-limit-mib 512

Each child accepts psql at 10.42.0.2:5432 (user=forkd, db=forkd_test). Connect from the host via the child's netns or expose via your usual netns plumbing.

Hub: deeplethe/playwright-browser v1

27 May 04:49
5500678

Choose a tag to compare

Hub snapshot pack for deeplethe/playwright-browser — Microsoft's official Playwright image + Chromium pre-warmed with one about:blank tab.

  • Base image: mcr.microsoft.com/playwright:v1.50.0-jammy
  • Memory: 2048 MiB (Chromium OOMs on the 512 MiB default)
  • Compressed size: 105.5 MiB (2.00 GiB uncompressed, 19.4× zstd)
  • Recipe: recipes/playwright-browser/
  • Per-child fork cost: ~56 ms (vs 2–3 s cold-starting a headless Chromium container).
forkd pull deeplethe/playwright-browser
sudo -E forkd fork --tag playwright-browser -n 3 --per-child-netns

Per-child sb.eval("return await page.title()") routes to the warmed Chromium via the recipe's JSON-RPC bridge.

Hub: deeplethe/nodejs v1

27 May 05:39
aab07e1

Choose a tag to compare

Hub snapshot pack for deeplethe/nodejs — Node.js 22 slim runtime preloaded.

  • Base image: node:22-slim
  • Memory: 512 MiB
  • Compressed size: 10.5 MiB (512 MiB uncompressed, 49× zstd)
  • Recipe: recipes/nodejs/
forkd pull deeplethe/nodejs
sudo -E forkd fork --tag nodejs -n 5 --per-child-netns

Generic JS workload base — useful for npm-based agents, JS test runners, or any tool that needs a real Node process per child.

Hub: deeplethe/jupyter-kernel v1

27 May 06:24
ebf36b6

Choose a tag to compare

Hub snapshot pack for deeplethe/jupyter-kernel — Jupyter scipy-notebook image, IPython + numpy + scipy + pandas + matplotlib pre-imported.

  • Base image: quay.io/jupyter/scipy-notebook:latest
  • Memory: 2048 MiB
  • Compressed size: 15.5 MiB (2.00 GiB uncompressed, 132× zstd)
  • Recipe: recipes/jupyter-kernel/
forkd pull deeplethe/jupyter-kernel
sudo -E forkd fork --tag jupyter-kernel -n 5 --per-child-netns

Children fork into a ready-to-execute-notebook-cell state — IPython kernel is already up, the scientific Python stack is already imported. Useful for parallel notebook execution / per-user kernel isolation.

Hub: deeplethe/e2b-codeinterpreter v1

27 May 05:59
1dbbe89

Choose a tag to compare

Hub snapshot pack for deeplethe/e2b-codeinterpreter — the E2B code-interpreter image (Python + Node + Jupyter + ML libs) preloaded, drop-in for the E2B sandbox API.

  • Base image: e2bdev/code-interpreter:latest
  • Memory: 2048 MiB
  • Compressed size: 21.6 MiB (2.00 GiB uncompressed, 94.8× zstd)
  • Recipe: recipes/e2b-codeinterpreter/
forkd pull deeplethe/e2b-codeinterpreter
sudo -E forkd fork --tag e2b-codeinterpreter -n 3 --per-child-netns

Use with forkd's Python SDK as a drop-in for e2b:

from forkd import Sandbox  # same surface as: from e2b import Sandbox

Hub: deeplethe/coding-agent v1

27 May 05:36
aab07e1

Choose a tag to compare

Hub snapshot pack for deeplethe/coding-agent — Python 3.12 + git + build-essential + gh CLI + pytest, the default for SWE-bench-style parallel evals.

  • Base image: python:3.12 (wrapped with apt + pip extras via recipe)
  • Memory: 1024 MiB
  • Compressed size: 15.2 MiB (1.00 GiB uncompressed, 67.3× zstd)
  • Recipe: recipes/coding-agent/
forkd pull deeplethe/coding-agent
sudo -E forkd fork --tag coding-agent -n 10 --per-child-netns

Each child gets an isolated git clone + pip install + pytest-ready Python workspace. The fork-per-test isolation that makes parallel SWE-bench rollouts safe.