# forkd roadmap

Living document. Issues that are tracked individually live in
[GitHub issues](https://github.com/deeplethe/forkd/issues); this file
gives the high-level shape across releases.

Current release: **v0.3.4** ([release notes](https://github.com/deeplethe/forkd/releases/tag/v0.3.4)).
Source-pause window for `POST /v1/sandboxes/:id/branch` shipped at
**143× ceiling / 6-15× typical agent workload**; v0.3.4 also closed
the multi-BRANCH pause anomaly (#146) — pause now stays flat across
10+ consecutive BRANCHes on the same source (17.6× faster at BRANCH 6,
8.5× median across BRANCH 3-10). See
[`bench/pause-window/RESULTS-v0.3.md`](../bench/pause-window/RESULTS-v0.3.md).

The v0.4 RFC for sub-10 ms BRANCH pause via `UFFDIO_WRITEPROTECT` is
under design with empirical PoCs (Phases 1-4 all passing) — see
[`DESIGN-v0.4.md`](../DESIGN-v0.4.md) and tracking issue
[#101](https://github.com/deeplethe/forkd/issues/101).

## What we're working on next — v0.3.x → v0.4 (6-item plan)

Post-v0.3.1 reprioritization (2026-05-19). The phase 2 / phase 3
pause-window items in the v0.3-candidates section below are real
but no longer top of the queue — current pause numbers are
already category-changing, and the bigger lever for adoption is
**distribution + ecosystem**, not further engineering on a number
that's already 6-15× ahead of vanilla. Sequence:

| # | Item | Effort | Cumulative | Track | Tracking |
|---|---|---|---|---|---|
| 1 | **MCP server** — `sdk/mcp/` finished, registered with Claude Desktop / Cursor / other MCP clients. forkd's BRANCH primitive becomes a tool any MCP agent can call. | 3-5 days | week 1 | distribution | TBD |
| 2 | **GitHub Action** — `uses: deeplethe/forkd-action@v1`. Open N sandboxes from a snapshot tag in CI; parallelize PR test runs / agent fan-out. Marketplace visibility. | 1 week | week 2 | distribution | TBD |
| 3 | **TypeScript SDK** — surface parity with the Python SDK (`Controller`, `Sandbox`, branch options). JS/TS is the larger half of the agent ecosystem. | 1-2 weeks | weeks 3-4 | distribution | TBD |
| 4 | **Stateful workspaces** — sessions that survive daemon / host restart, picking up where they left off. Daytona's killer feature; forkd's Hub + snapshot machinery is most of what's needed, the gap is UX. | 2 weeks | weeks 5-6 | stickiness | TBD |
| 5 | **3-5 agent-framework recipes** — `langgraph-react` exists; add `crewai-fanout`, `autogen-branch`, `openai-swarm`, `mcp-agent`. Each is a 2-3 day landing-page-grade demo. | 2-3 weeks | weeks 7-9 | adoption | TBD |
| 6 | **Phase 2 + 3 pause-window** — NVMe + io_uring snapshot writer + pre-emptive background snapshot. Targets total BRANCH API latency (today still bandwidth-bound on cp) and non-first-BRANCH pause. | 3 weeks | weeks 10-12 | technical depth | TBD |

**Track meaning**:
- *distribution* = make forkd reachable from where developers already are
- *stickiness* = make users come back / commit
- *adoption* = landing-page-grade material that converts the curious
- *technical depth* = keep the primitive ahead, but **only once we have users feeding us workload signal**

The reprioritization rationale: pause-window engineering past 143×
ceiling becomes a diminishing-returns problem until we have real
fan-out workloads pointing at where the next bottleneck actually
is. Items 1-3 unblock that user signal; item 6 returns to the
metric once we know what to optimize for.

## v0.3 candidates — picked (pause-window track)

### Cut pause-window without forking Firecracker

**Problem.** Today's `POST /v1/sandboxes/:id/branch` pauses the source
sandbox while `vm.snapshot_to()` writes `memory.bin` — typically
0.5–8 s depending on memory size. That window blocks the source's
TCP keepalives and progress; it's also the only remaining trade-off
in the branching primitive (see `docs/design/branching.md`).

**First-cut measurement (forkd v0.2).** Pause window is dominated
by snapshot-write throughput, not by VMM control-path work.
For a 513 MiB source running a TCP ping/pong agent:
**163 ms ± 7 ms on tmpfs-backed snapshot storage** (4 trials),
degrading to **4.26 s ± 0.41 s on SATA SSD with fsync** (5
trials). Same forkd code, only the storage backend differs.
External observers see the full gap; in-guest agents are nearly
pause-blind (connection survival 5/5, in-flight loss 0/5,
post-resume RTT returns to baseline) because kvmclock's
monotonic catch-up on resume races the recv data delivery. Full
methodology and raw data in
[`bench/pause-window/RESULTS-v0.2.md`](../bench/pause-window/RESULTS-v0.2.md).

**Approach.** Three engineering wins that stack and don't require any
Firecracker fork. The original "live branching via memfd + uffd_wp"
plan is deferred to v0.4+ — see
[issue #101](https://github.com/deeplethe/forkd/issues/101) for the
honest cost-benefit reasoning that led to the deferral. Scaffolding
from that earlier plan (the design doc, `crates/forkd-uffd/`,
`MemoryBackend::Userfault` enum) is preserved as record. The
`firecracker-patch/` directory was REMOVED in v0.3.0 after deciding
not to fork Firecracker — see
[`docs/design/userfaultfd.md`](./design/userfaultfd.md) §
"Why we won't fork Firecracker".

| Phase | What | Measured / expected | Status |
|---|---|---|---|
| 1 | **Diff snapshots.** `POST /v1/sandboxes/:id/branch` with `"diff": true`. Parallel cp + Diff during pause + apply on resume. Phase 1d (v0.3.1) added multi-BRANCH via previous-output chain — no separate shadow file. | **Source pause 29.3 s → 205 ms (143×) on 4 GiB SSD; 1.19 s → 190 ms (6.3×) on tmpfs. Typical agent workload (30-300 MiB dirty): 6-15×. 5 consecutive BRANCHes: 14× aggregate.** | **Shipped (PR #102, #107).** See [`docs/design/diff-snapshots.md`](./design/diff-snapshots.md) + [`bench/pause-window/RESULTS-v0.3.md`](../bench/pause-window/RESULTS-v0.3.md). |
| 2 | **NVMe + io_uring snapshot writer.** Daemon flag for memory.bin writes. Targets the underlying full-copy path (still bottlenecks total BRANCH API latency on SSD even with diff mode). | Expected SSD 10×+ on the full-copy backbone. | Pending. |
| 3 | **Pre-emptive background snapshot.** Background thread flushes dirty pages on a 1 s tick; at BRANCH, only flush what's dirty since the last tick. Bounds pause-window regardless of source size, including non-first BRANCHes. | Expected pause ≈ tick interval. | Pending. |
| 4 | **Measurement + RESULTS-v0.3.md.** A/B numbers for each phase plus the stacked combo. | Phase 1 measured (60 trials, 5 sizes × 2 backends × 2 modes × 3 trials). Phase 2/3 measurement pending. | Phase 1 done. |

Phase 1 covered forkd's killer use case (live BRANCH from a long-running source, where
source downtime matters more than total API latency). Phases 2 and 3 close the gap for
the remaining workloads (multi-BRANCH source, total-API-latency-sensitive callers).
The combination keeps the trust story unchanged (still vanilla Firecracker).

**Out of scope for v0.3.** Live-fork via memfd + uffd_wp (deferred, see
[#101](https://github.com/deeplethe/forkd/issues/101)). Cross-host live branching
(needs RDMA or similar). Persistent fault-handler dump-and-replay. Fault-driven
prefetch policies. These are v0.4+ candidates.

## v0.3 candidates — speculative

These don't have firm ship dates; revisit at v0.4 retro.

- **Cross-host snapshot diffing** — ship a parent update as a binary
  diff against the previous tag instead of a full memory.bin. Big
  win for ~10 GiB ML-weight parents.
- **Branch GC policies** — auto-prune by age / count. Today every
  `branch_sandbox` call persists forever.
- **Merge-back / commit semantics** — `forkd merge --from <branch> --into <source>`
  to write a branch's diverged state back into the source's
  filesystem. Pairs with the "speculative destructive op" use case.
- **Multi-node scheduling** — break the "one daemon = one host"
  model. Probably depends on cross-host snapshot diffing landing
  first.
- **K8s Operator + CRDs** (`kind: ForkdSandbox`) — for downstream
  users with mature K8s platforms. Current `packaging/k8s/` starter
  manifest is the bridging step.

## Production-readiness gaps (across releases)

These are tracked separately from the v0.x feature line — they need
to land before v1.0:

- **Default-deny egress** on per-child netns. Today: shared
  MASQUERADE rule; allow-list policy = caller's responsibility.
- **`cpu.max` / `io.max` / `pids.max`** quotas beyond the existing
  `memory.max`.
- **Third-party security audit.**
- **Stable on-disk formats** — `snapshot.json` schema, `state.json`
  schema, audit log format all need a "v1.0 frozen" stamp.

## Recent shipped (v0.2 highlights)

- Sandbox branching: REST + CLI + Python SDK + volume inheritance +
  netns-allocator (#49 #50 #51 #52)
- forkd-mcp 0.1.0 on PyPI — MCP server for Claude Desktop / Code /
  Cursor / Cline
- K8s starter manifest verified end-to-end on k3s
- `postgres-fixture` recipe end-to-end verified
- 7-system bench refresh (CubeSandbox slow path → fast path,
  1.06 s / N=100 / 100 % success)
- Filed two upstream PRs to TencentCloud/CubeSandbox; #236 merged
