MCPcopy Index your code
hub / github.com/deeplethe/forkd

github.com/deeplethe/forkd @ts-v0.5.2

Chat with this repo
repository ↗ · DeepWiki ↗ · release ts-v0.5.2 ↗ · + Follow
854 symbols 2,396 edges 79 files 262 documented · 31% updated 12d agov0.5.2 · 2026-06-08★ 2,70710 open issues
What it actually does AI analysis from the code graph — generated when you open this
loading…
README
<img alt="forkd" src="https://github.com/deeplethe/forkd/raw/ts-v0.5.2/docs/logo.svg" width="220">

CI Release PyPI License English README Stars

101 毫秒 fork 100 个 microVM,56 毫秒 BRANCH 一个运行中的 VM(v0.4 live 模式)。

面向 AI Agent 扇出(fan-out)场景的 microVM 沙箱运行时。子 VM 从一个已"暖启动"的父快照 fork 而来,通过写时复制(CoW)继承 父进程的地址空间,而不是冷启动一个新内核。

forkd 基于 Firecracker 构建。父 VM 启动一次,把运行时(Python + 依赖,JIT 已暖的 JVM,已加载的 ML 模型)导入内存,然后暂停并落盘。 每个子 VM 是一个独立的 Firecracker 进程,通过 MAP_PRIVATE 方式 mmap 父快照的内存镜像;内核在页面级别实现写时复制,因此 在子 VM 发生写入分歧之前,它们共享父 VM 的常驻内存。

由此同时获得两个特性:每个子 VM 都是独立的 KVM 隔离, 同时单个子 VM 的启动成本接近 fork(2),而非冷启动 VM

forkd 同时支持 BRANCH:把正在运行的沙箱暂停,把它当下的 in-flight 状态做成快照,再恢复 —— 整套 ~150 ms —— 让 agent 能在"思考过程中"叉出多条路径,而不只在暖启动时刻 fork。 v0.3.4 修复了一个慢路径回归:在同一个父 VM 上反复 BRANCH 时, pause 时间会从 150 ms 涨到 2.7 s (#146);修复后 连续 BRANCH 保持平直(第 6 次 BRANCH 快了 17.6×)。

v0.4 live BRANCH 把源 VM 卡顿窗口从 ~200 ms(Diff)压到 56 ms p50 / 64 ms p90(1.5 GiB 源 VM,实测, bench/live-fork-pause-window/RESULTS-v0.4.md)。 p50 比 v0.3 Diff 快 3.6 倍,而且在慢盘上这个比值变得更大—— 因为 Live 的 pause 是 disk-independent 的(内存拷贝跑在 resume 之 后,不占临界区)。加 wait: false 让调用方 ~70 ms 就返回,背景 拷贝异步完成——对于 agent 代码的 fire-and-forget BRANCH 是 200× 的 RT 改进。CLI 用 --live / --no-wait,REST 用 mode: "live" / wait: false,Python / TypeScript / MCP SDK 同名。

from forkd import Controller
c = Controller()
# 源 VM 必须用 live_fork=True 启动(memfd 后端 RAM,UFFD_WP 看到
# 运行中父 VM 写的前提条件)。
parent = c.spawn_sandboxes("pyagent", n=1, live_fork=True)[0]
# ... 驱动 parent ... 然后 live BRANCH + fire-and-forget:
branch = c.branch_sandbox(parent["id"], mode="live", wait=False)
# ~10 ms 返回,status="writing";poll list_snapshots 等到
# status="ready" 即背景拷贝完成。
# CLI:本地 fork live-fork-capable 子 VM,然后对 daemon 追踪的那个
# 做 live BRANCH。两条路径暂时还不组合 ——daemon 侧 spawn 走 CLI
# 是下一个缺口(状态见 issue #209)。
sudo -E forkd fork --tag pyagent -n 1 --per-child-netns --live-fork
sudo -E forkd snapshot --from-sandbox <sb-id> --live --no-wait

需要 Linux ≥ 5.7、vm.unprivileged_userfaultfd=1(或 CAP_SYS_PTRACE),以及 vendored Firecracker 分支 deeplethe/firecracker:forkd-v0.4-mem-backend-shared-v1.12 ——forkd doctor 会探测这两项。完整设计: DESIGN-v0.4.md。PoC 实证数据: experiments/v0.4-*-poc/。跟踪 issue #101

Demo:让一个思考中的 agent 分裂

一个 24 秒的演示 —— 源 agent 在 LangGraph ReAct loop 中跑到一半, 被 BRANCH,3 个子沙箱继承同一份认知状态,各自收到不同的引导 ("深度文化派"、"极简派"、"省钱派"),输出三条显著不同的 第一天行程。

forkd 分裂思考中的 agent

最关键的分裂证据:源 agent(无引导)第一天下午选 Nishiki Market (锦市场,$$);3 个被引导的子沙箱各自独立地都换成了 Arashiyama Bamboo Grove(岚山竹林,free);"省钱派" 还在 $$ 餐厅那里加上了 "may be pricey" 的警告语,其他两个没有。模型并没有被告知"换景点" —— 是 hint 影响了下一次 LLM 调用,前置推理整套都没变。

完整机制 + 数据 + 原始 transcript 在 recipes/langgraph-react/recipes/langgraph-react/DEMO.md

不仅是推理状态,文件系统状态也继承

针对"难道你不能直接并行调 3 次 LLM 吗"这个常见反驳,看 recipes/coding-agent-fork/ —— 50 MiB 的二进制 blob 通过一次 BRANCH 字节完全一致地传到 4 个 沙箱里。3 个孙沙箱各自对同一个有 bug 的 Python 包应用不同的修复; 它们的 __pycache__/ 和编辑互不影响,但那 50 MiB 的继承是共享的。 字节是塞不进 prompt 的。BRANCH 操作的 pause 时间 3.3 秒。

关键特性

  • 硬件级隔离。 每个子 VM 都是独立的 Firecracker microVM, 基于 KVM。要逃逸出来需要 hypervisor 或内核漏洞,而不是 runc 的一个回归 bug。
  • 暖启动的运行时免费继承。 导入、JIT 编译、模型权重、预取的 缓存——只要父 VM 做过的事,子 VM 直接拿到。
  • 每个子 VM 都是真 Linux。 多 vCPU、完整 TCP 网络、apt install、出站 HTTPS。和那些为了极致启动速度牺牲掉 单 vCPU + 串行 I/O 的函数级快照运行时不同,forkd 的子 VM 可以跑真实的 Python 服务、模型推理或任何需要完整内核的负载。
  • 从设计上就是多租户。 每个子 VM 独立 network namespace、 独立 cgroup v2 内存限制、独立 /dev/urandom(Linux 5.20+ 通过 vmgenid 重新播种)。
  • 为 Agent 扇出而生。 单次请求扇出到大量短生命周期沙箱的 AI Agent 负载——代码解释器、工具调用、评估 rollout——是 设计目标。暖启动的父 VM 把每次请求的 import numpy / import torch 开销在整个 cohort 间分摊到接近零。
  • 可运维。 守护进程持有状态、REST API(Unix 或 TCP)、 Prometheus /metrics、append-only JSON 审计日志、systemd 单元。
  • 开源。 Apache 2.0,没有厂商 SDK 锁定。

基准测试

同一台 Linux 主机(Ubuntu 24.04,Linux 6.14,20 vCPU,30 GiB,KVM)。 负载:启动 100 个沙箱,每个执行 import numpy; numpy.zeros(5).tolist()

Spawn time at N=100

Host memory per sandbox

后端 N=100 总耗时 每个沙箱内存增量 说明
forkd 101 ms 0.12 MiB 从暖启动快照 CoW fork
CubeSandbox¹ 1.06 s 5 MiB RustVMM microVM,冷启动(池 fast path)
BoxLite² 113.2 s KVM microVM,冷启动 OCI rootfs
OpenSandbox³ 122.0 s 经抽象层调用 Docker 运行时
Firecracker 冷启动 759 ms 84 MiB 裸 VM 启动,无编排
gVisor (runsc) 288.6 s 用户态内核容器
Docker (runc) 335.3 s 4 MiB 标准容器运行时

¹ CubeSandbox:1.06 s 是本机的 fast-path N=100 数字(1056 ± 14 ms, 五次连跑,每次 100 % 成功),用 pre-warm Python ThreadPoolExecutor 的改进 bench 脚本测,避免 client 侧懒初始化污染测量。之前在同一台 机器上跑的 slow-path 是 20.3 s / 77 成功 —— 那次模板的 writable layer 是 2 GiB,与默认 1 GiB 池不匹配,每个沙箱都走了一遍 mkfs.ext4 + reflink-copy;维护者在 #235 澄清后,我们把 2Gi 加进 pool_default_format_size_list 重测。 机器跑的是 cube v0.2.0,这版有一个 ~50 ms 延迟回归, PR #234 在 v0.2.1 中修复 —— 上面的数字是 v0.2.0 baseline。CubeSandbox 公布单实例冷启动 <60 ms(96 vCPU 主机,N=100 并发 P99 200 ms), 我们没在此处复测那个形态。详见 bench/CUBESANDBOX.md、以及两个上游 PR #236 / #237

² BoxLite 的设计目标是每个负载一个长生命周期、有状态的 Box, 而不是 100 个并发的全新 microVM。冷启动扇出数据放在这里仅为 直接对比。详见 bench/BOXLITE.md

³ OpenSandbox 是 Docker / K8s / gVisor / Kata / Firecracker 上层的 抽象层;此处数字是它默认的 Docker 运行时。详见 bench/OPENSANDBOX.md

复现:bench/bench-spawn-100.sh 然后 bench/generate_charts.py

对单个沙箱执行同一个 numpy 表达式的两种方式:

调用 耗时 做了什么
sandbox.eval("numpy.zeros(5).tolist()") 1 ms 复用 PID 1 里已暖的 Python
sandbox.commands.run("python3 -c '...'") 96 ms 冷子进程重新 import numpy

工作原理

flowchart TB
    %% ─── parent ───────────────────────────────────────────────
    subgraph PARENT["父 VM(启动一次并暖启动)"]
        direction TB
        runtime["PID 1

Python + numpy + 你的依赖

已导入 RAM"]
    end

    PARENT -- "暂停 + 快照" --> SNAP["磁盘上的快照

memory.bin(CoW 源)

vmstate(vCPU + 设备)"]

    %% ─── controller ───────────────────────────────────────────
    CLIENT["客户端(CLI / Python SDK)"] -- "POST /v1/sandboxes n=100" --> CTL["forkd-controller

REST · 鉴权 · 审计 · /metrics"]
    CTL -- "restore_many_with(...)" --> SNAP

    %% ─── children ─────────────────────────────────────────────
    subgraph CHILDREN["100 个子 Firecracker 进程(内核每页 CoW)"]
        direction LR
        subgraph NS1["netns forkd-child-1"]
            C1["子 VM 1

mmap MAP_PRIVATE

cgroup memory.max"]
        end
        subgraph NS2["netns forkd-child-2"]
            C2["子 VM 2

mmap MAP_PRIVATE

cgroup memory.max"]
        end
        subgraph NSN["netns forkd-child-100"]
            CN["子 VM 100

mmap MAP_PRIVATE

cgroup memory.max"]
        end
    end

    SNAP -. "共享文件

(基本只读)" .-> C1
    SNAP -. "共享文件" .-> C2
    SNAP -. "共享文件" .-> CN

    %% ─── network ──────────────────────────────────────────────
    C1 -- "veth" --> BR["宿主机桥 forkd-br0

MASQUERADE"]
    C2 -- "veth" --> BR
    CN -- "veth" --> BR
    BR --> UPLINK(("uplink → 公网"))

    %% styling
    classDef parent fill:#e8f3ec,stroke:#4c956c,color:#1f2933;
    classDef snap   fill:#fff3df,stroke:#d4a259,color:#1f2933;
    classDef ctl    fill:#e6efff,stroke:#5b7dba,color:#1f2933;
    classDef child  fill:#ffffff,stroke:#52606d,color:#1f2933;
    classDef net    fill:#f1f3f5,stroke:#8d99ae,color:#1f2933;
    class PARENT,runtime parent;
    class SNAP snap;
    class CTL,CLIENT ctl;
    class NS1,NS2,NSN,C1,C2,CN child;
    class BR,UPLINK net;

完整设计、以及当前架构留下的开放问题,见 DESIGN.md

forkd 与其他方案对比

沙箱运行时这个领域的设计差异很大。下表把 forkd 和最常被提及的 开源项目放在一起对比。引号内的数字是上游项目自己公布的数字, 除非已经在上面我们的基准图里。forkd 不会去测别的项目在它们 本来就不为之设计的负载形态。

项目 隔离原语 冷启动 (N=100) Fork 自暖快照 配额 鉴权 / TLS 协议
forkd Firecracker + 快照 CoW 101 ms cgroup memory.max bearer + rustls Apache 2.0
CubeSandbox RustVMM + KVM microVM 1.06 s¹ "coming soon" <5 MiB / 实例 闭源不开放 Apache 2.0
Daytona OCI workspace <90 ms² 每 workspace 平台 API key AGPL-3.0
OpenSandbox Docker / K8s + gVisor / Kata / FC 122 s 取决于底层 k8s 网关 Apache 2.0
E2B Firecracker(在 infra 中) 开源不含 平台侧 云 API key Apache 2.0
BoxLite KVM / Hypervisor.framework + OCI 113 s ✗ 有状态 Box KVM + seccomp 仅出站策略 Apache 2.0
Modal 闭源快照 fork 不公开 闭源
Firecracker 裸用 只有 microVM 759 ms 手工 n/a n/a Apache 2.0
Docker (runc) OCI 容器 335 s cgroups n/a Apache 2.0
gVisor (runsc) 用户态内核 289 s cgroups n/a Apache 2.0

¹ N=100 并发,本机裸金属(systemd-detect-virt: none,i7-12700, 20 vCPU,无嵌套虚拟化)。这是 fast path 的数字 —— pool_default_format_size_list 已包含模板的 writable-layer 尺寸, 每个沙箱复用预格式化的池条目,不走 mkfs.ext4 + reflink-copy。 五次连跑均值 1056 ± 14 ms,每次 100 % 成功,bench 脚本会先 pre-warm Python 的 ThreadPoolExecutor,把 client 侧懒初始化排除在测量外。 机器跑的是 cube v0.2.0,这版有 ~50 ms 延迟回归, PR #234 在 v0.2.1 修 —— 上面的数字是 v0.2.0 baseline。之前在同一台机器上的 slow-path 测得 20.3 s / 77 成功 —— 那是我们模板配错的结果,维护者 在 #235 指出后修正。Cube 自称单实例冷启动 <60 ms(96 vCPU 主机, N=100 并发 P99 200 ms),我们没复测。注意这行是把 forkd 的 fork-from-warm 跟其他项目的 冷启动 放在一起 —— 它们是不同 的工作点,不是等价原语。详见 bench/CUBESANDBOX.md,以及为上游 cmdTimeout race 提的两个 PR #236 / #237

² Daytona 自己公布的数字,我们没测它(workspace 运行时,不属于 可对比的扇出形态)。

forkd 适合什么场景。

  • 代码解释器和 Jupyter kernel 沙箱。 每一次对话或工具调用 都开一个全新 kernel;暖启动的父 VM 带着 SciPy / ML 运行时, 所以每次请求的 import numpy / import torch 直接降到零开销。 这就是设计目标——Anthropic / OpenAI / Modal 的代码解释器 产品全是这种负载形态。
  • 评估测试集 harness。 几百个仓库 checkout 或测试 rollout 并行跑——SWE-bench 那种形状——又不用每个 task 付 Docker 冷启动的代价。
  • 大规模扇出的多用户代码执行。 大量短生命周期沙箱共享 同一个暖启动父 VM,每个子 VM 都是 KVM 隔离。
  • CI 里执行不可信代码。 git clonepip installpytest 跑在真实的 Linux VM 里,而不是一个容器 namespace。
  • 托管沙箱 SaaS 的自托管替代品。 一台 Linux + KVM,单二进制 守护进程,Apache 2.0——没有按秒计费的云账单,也没有厂商锁定。

别的项目更合适什么。 CubeSandbox:更快的纯冷启动(自称 <60 ms)。Daytona:每个用户拥有一个长生命周期沙箱的 workspace 形态。OpenSandbox:用一套编排 API 适配多种隔离后端。BoxLite: 可嵌入、不需要守护进程、跨平台(macOS 走 Hypervisor.framework)。 Modal:同一种原语的闭源托管版本。

forkd 的不适用场景。 函数级快照运行时为了极致启动速度 放弃了真实 Linux(单 vCPU、只有串行 I/O),它们能在 forkd 的 ~100 ms 基础上再快一个数量级——代价是跑不了真实的 Python 服务、apt install、出站 HTTPS。

企业部署 FAQ

给平台 / 采购团队的速查答案:

能直接上 Kubernetes 吗? 可以——一个 forkd-controller Pod 承载 N 个沙箱子 VM,K8s 调度器只在 Pod 创建时跑一次,跟扇出数量无关(对比 Kata / Firecracker-on-K8s 那种"每个沙箱一个 Pod"的设计)。起步 manifest 在 packaging/k8s/。节点要有 /dev/kvm + cgroup v2;托管 K8s(GKE / EKS / AKS)通常得选裸金属 SKU 或显式开嵌套虚拟化才行。

一个 Pod 能塞多少沙箱? 用 512 MiB 暖好的 Python+numpy 父 VM,大致定容:

  • 每 vCPU 跑 ~1 个活跃 agent(算力瓶颈)
  • 每 8 GiB Pod RAM 装 ~50 个空闲池里的 agent(进程状态瓶颈,不是内存)

N=100 实测 CoW 开销是 每个 child 0.12 MiB(详见 bench/),内存几乎从来不是扇出的天花板,真正卡住的是 vCPU 和进程数。父 VM 更重的负载(浏览器、ML 推理)更快撞顶,具体定容请用自家父 VM 实测。

现有 agent 怎么接入?

  • REST —— POST /v1/sandboxes n=100,跟语言无关,bearer-token 鉴权
  • Python SDK —— pip install forkd(from forkd import Sandbox 可替 from e2b import Sandbox)
  • TypeScript SDK —— npm install @deeplethe/forkd,Node.js 18+,与 Python SDK surface 对齐
  • LangGraph / AutoGen / CrewAI / Swarm —— 通过 SDK,无需特殊适配。完整示例见 recipes/ 下的 crewai-fanout/autogen-branch/openai-swarm/
  • MCP —— pip install forkd-mcp 提供 MCP server,可接入 Claude Desktop / Claude Code / Cursor / Cline,详见 sdk/mcp/

生产场景形态(对应仓内 recipe):

  • AI 代码解释器 —— 一个暖好的父 VM(SciPy / torch 已 import),每个对话回合 fork 一个 child。Recipe:e2b-codeinterpreter/
  • SWE-bench 风格并行评测 —— N 个并行 repo checkout,每个 child 独立跑 pytest。Recipe:coding-agent/
  • 多用户代码执行规模化 —— 共享暖父 VM,每个用户的 child 由 KVM 隔离
  • CI 跑不可信代码 —— git clone + pip install + pytest 在真 Linux VM 里跑,不是容器 namespace
  • 每测试隔离数据库 —— Recipe:[postgres-fixture/](./recipes/postgres-fixture/

Extension points exported contracts — how you extend this code

ControllerOptions (Interface)
(no doc)
sdk/typescript/src/controller.ts
SnapshotInfo (Interface)
(no doc)
sdk/typescript/src/types.ts
SandboxInfo (Interface)
(no doc)
sdk/typescript/src/types.ts
SpawnOptions (Interface)
(no doc)
sdk/typescript/src/types.ts
BranchOptions (Interface)
(no doc)
sdk/typescript/src/types.ts

Core symbols most depended-on inside this repo

server_error
called by 49
crates/forkd-controller/src/http.rs
run
called by 46
sdk/python/forkd/sandbox.py
path
called by 42
crates/forkd-controller/src/audit.rs
router
called by 40
crates/forkd-controller/src/http.rs
write
called by 38
crates/forkd-controller/src/audit.rs
read
called by 28
crates/forkd-cli/src/hub.rs
bad_request
called by 26
crates/forkd-controller/src/http.rs
flush
called by 25
crates/forkd-controller/src/state.rs

Shape

Function 572
Method 159
Class 103
Enum 11
Interface 9

Languages

Rust65%
Python30%
TypeScript4%

Modules by API surface

crates/forkd-controller/src/http.rs108 symbols
crates/forkd-vmm/src/lib.rs76 symbols
crates/forkd-cli/src/main.rs61 symbols
crates/forkd-cli/src/hub.rs36 symbols
crates/forkd-cli/src/doctor.rs26 symbols
crates/forkd-controller/src/api.rs25 symbols
crates/forkd-controller/src/state.rs23 symbols
bench/pause-window/test_analyze.py22 symbols
sdk/typescript/src/controller.ts18 symbols
crates/forkd-vmm/src/chain.rs17 symbols
crates/forkd-vmm/src/memfd.rs16 symbols
sdk/python/forkd/controller.py15 symbols

For agents

$ claude mcp add forkd \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact