Skip to content

feat(controller): Phase 7.1 — canonical mode field on BRANCH - #204

Merged
WaylandYang merged 1 commit into
mainfrom
feat/v0.4-phase7.1-mode-field
May 30, 2026
Merged

WaylandYang merged 1 commit into
mainfrom
feat/v0.4-phase7.1-mode-field

Conversation

@WaylandYang

Copy link
Copy Markdown
Contributor

Land the public-surface `mode` field from `DESIGN-v0.4-USER-API.md`. Internal request path is unchanged; this is the wire-level layer that lets external callers stop touching the unstable `live: true` / `diff: true` booleans.

Changes

  • `api.rs`: add `BranchMode { Full, Diff, Live }` (lowercase serde) and an optional `mode` field on `BranchSandboxRequest`.
  • `http.rs`: at the top of `branch_sandbox`, resolve `mode` → `effective_diff` / `effective_live`. Reject any request that sets both `mode` and either legacy bool (single source of truth). Downstream code unchanged — it still reads the boolean pair.

The legacy bools are still accepted for v0.3.x clients and for the existing diff/measure_diff bench harness. Doc-comments now point at `mode` as canonical.

What's out of scope (follow-up PRs)

Test plan

  • `cargo check --workspace` — clean
  • `cargo clippy --workspace --all-targets -- -D warnings` — clean
  • `cargo fmt --check --all` — clean
  • `cargo test -p forkd-controller --lib` — 50 passed, 0 failed (44 pre-existing + 6 new)

New tests:

  • `branch_rejects_mode_plus_legacy_bools` — every combo of `mode` + (`diff` or `live`) returns 400 (4 combos)
  • `branch_mode_full_for_missing_sandbox_returns_404_not_400` — `mode: "full"` passes resolution
  • `branch_mode_diff_alone_validates`
  • `branch_mode_live_validates`
  • `branch_mode_live_with_wait_false_validates` — legit combo
  • `branch_rejects_mode_full_with_wait_false` — wait=false needs live

🤖 Generated with Claude Code

Land the public-surface `mode` field from DESIGN-v0.4-USER-API.md.
Internal request path is unchanged; this is the wire-level
layer that lets external callers stop touching the unstable
`live: true` / `diff: true` booleans.

  - api.rs: add `BranchMode { Full, Diff, Live }` (lowercase
    serde) and an optional `mode` field on BranchSandboxRequest.
  - http.rs: at the top of `branch_sandbox`, resolve `mode` →
    `effective_diff` / `effective_live`. Reject any request that
    sets both `mode` and either legacy bool (single source of
    truth). Downstream code unchanged — it still reads the
    boolean pair.

The legacy bools are still accepted for v0.3.x clients and for
the existing diff/measure_diff bench harness. They are now
internal in spirit; doc-comments updated to point at `mode`.

Test coverage (6 new, on top of 44 pre-existing):
  - mode + legacy bool → 400 (4 combos)
  - mode=full / mode=diff / mode=live alone → 404 missing-sandbox
    (validates they pass mode-resolution)
  - mode=live + wait=false → 404 (legit combo)
  - mode=full + wait=false → 400 (wait=false needs live)

Doesn't yet ship Phase 7's CLI / SDK / `mode: "live-async"` enum
variant — those are follow-ups. This PR's scope is just the
REST canonical field so the surface stops being "set this
unstable boolean".

cargo {check, clippy, fmt} clean. 50 controller-lib tests passing.
@WaylandYang
WaylandYang merged commit 3653ffa into main May 30, 2026
1 of 2 checks passed
@WaylandYang
WaylandYang deleted the feat/v0.4-phase7.1-mode-field branch May 30, 2026 10:27
WaylandYang added a commit that referenced this pull request May 31, 2026
…rce (#210)

Replaces the "pause_ms TBD" disclaimer in v0.4 docs with measured
numbers from a clean Hub-pulled `python-numpy` source (1.5 GiB,
sha256-verified). The previous attempt at this measurement used
`coding-agent-fork-prewarm-v1`, which had 17 baked-in guest Oopses
contaminating the timing — fixed by switching source.

Methodology (`bench/live-fork-pause-window/bench-live-fork.py`,
based on `scripts/dev/e2e-live-branch.py` Phase 6 E2E harness):

- One memfd-backed source sandbox spawned with `live_fork: true`
- 10 iterations × 4 modes ({live-sync, live-async, diff, full}),
  interleaved so cold-cache effects average across modes
- Each iteration: POST .../branch, record `pause_ms` and HTTP RT,
  DELETE the result snapshot to bound disk usage
- Async iterations also record `poll_until_ready_ms`

Results (Intel i7-12700, 30 GiB RAM, Linux 6.14, ext4 on **HDD**):

| mode         | pause p50 | pause p90 | RT p50    |
|--------------|----------:|----------:|----------:|
| live-sync    |  **56 ms**|     64 ms | 13 730 ms |
| live-async   |     54 ms |    241 ms | **69 ms** |
| diff         |    202 ms |    418 ms | 13 461 ms |
| full         |  13 550 ms |  14 268 ms | 13 559 ms |

Key ratios at p50:

- live vs diff: **3.6× faster pause** (202 / 56)
- live vs full: **242× faster pause** (13550 / 56)
- async RT vs sync RT: **198× faster return** (13730 / 69)

The "on HDD" point is a feature, not a bug for the writeup:
Live's pause is disk-independent (memory copy runs after resume,
not during), so the Live / Diff gap *widens* on slow storage rather
than shrinking. NVMe would speed up Diff but not Live, making the
ratio narrower — but Live is always bounded by CPU work (vmstate
dump + UFFD_WP arming), never by disk throughput.

Files:

- `bench/live-fork-pause-window/bench-live-fork.py` — runnable
  harness, parameterized on source-tag and iterations
- `bench/live-fork-pause-window/bench-live-fork.csv` — 40-row raw
  data (one per BRANCH iteration)
- `bench/live-fork-pause-window/RESULTS-v0.4.md` — writeup with
  methodology, host config, per-mode interpretation of what
  pause_ms / RT measure, and honest caveats (single host, one
  source size, p90 outlier on async iter #8)

Docs updated:

- `README.md` headline: "BRANCH a live VM in 150 ms" → "in 56 ms
  (v0.4 live mode)". v0.4 preview block now leads with the
  measured 3.6× / 200× ratios and links to RESULTS-v0.4.md.
- `README-zh.md`: same headline + intro update.
- `CHANGELOG.md`: Unreleased's v0.4 section's "Bench in progress"
  disclaimer replaced with the actual numbers table.

Phase 7 (user surface for v0.4 live BRANCH) is complete with this
PR: REST (#204), CLI (#205), SDKs (#206), doctor (#207), docs
(#208), bench (this).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jrimmer pushed a commit to jrimmer/forkd that referenced this pull request Aug 11, 2026
…ethe#204)

Land the public-surface `mode` field from DESIGN-v0.4-USER-API.md.
Internal request path is unchanged; this is the wire-level
layer that lets external callers stop touching the unstable
`live: true` / `diff: true` booleans.

  - api.rs: add `BranchMode { Full, Diff, Live }` (lowercase
    serde) and an optional `mode` field on BranchSandboxRequest.
  - http.rs: at the top of `branch_sandbox`, resolve `mode` →
    `effective_diff` / `effective_live`. Reject any request that
    sets both `mode` and either legacy bool (single source of
    truth). Downstream code unchanged — it still reads the
    boolean pair.

The legacy bools are still accepted for v0.3.x clients and for
the existing diff/measure_diff bench harness. They are now
internal in spirit; doc-comments updated to point at `mode`.

Test coverage (6 new, on top of 44 pre-existing):
  - mode + legacy bool → 400 (4 combos)
  - mode=full / mode=diff / mode=live alone → 404 missing-sandbox
    (validates they pass mode-resolution)
  - mode=live + wait=false → 404 (legit combo)
  - mode=full + wait=false → 400 (wait=false needs live)

Doesn't yet ship Phase 7's CLI / SDK / `mode: "live-async"` enum
variant — those are follow-ups. This PR's scope is just the
REST canonical field so the surface stops being "set this
unstable boolean".

cargo {check, clippy, fmt} clean. 50 controller-lib tests passing.
jrimmer pushed a commit to jrimmer/forkd that referenced this pull request Aug 11, 2026
…rce (deeplethe#210)

Replaces the "pause_ms TBD" disclaimer in v0.4 docs with measured
numbers from a clean Hub-pulled `python-numpy` source (1.5 GiB,
sha256-verified). The previous attempt at this measurement used
`coding-agent-fork-prewarm-v1`, which had 17 baked-in guest Oopses
contaminating the timing — fixed by switching source.

Methodology (`bench/live-fork-pause-window/bench-live-fork.py`,
based on `scripts/dev/e2e-live-branch.py` Phase 6 E2E harness):

- One memfd-backed source sandbox spawned with `live_fork: true`
- 10 iterations × 4 modes ({live-sync, live-async, diff, full}),
  interleaved so cold-cache effects average across modes
- Each iteration: POST .../branch, record `pause_ms` and HTTP RT,
  DELETE the result snapshot to bound disk usage
- Async iterations also record `poll_until_ready_ms`

Results (Intel i7-12700, 30 GiB RAM, Linux 6.14, ext4 on **HDD**):

| mode         | pause p50 | pause p90 | RT p50    |
|--------------|----------:|----------:|----------:|
| live-sync    |  **56 ms**|     64 ms | 13 730 ms |
| live-async   |     54 ms |    241 ms | **69 ms** |
| diff         |    202 ms |    418 ms | 13 461 ms |
| full         |  13 550 ms |  14 268 ms | 13 559 ms |

Key ratios at p50:

- live vs diff: **3.6× faster pause** (202 / 56)
- live vs full: **242× faster pause** (13550 / 56)
- async RT vs sync RT: **198× faster return** (13730 / 69)

The "on HDD" point is a feature, not a bug for the writeup:
Live's pause is disk-independent (memory copy runs after resume,
not during), so the Live / Diff gap *widens* on slow storage rather
than shrinking. NVMe would speed up Diff but not Live, making the
ratio narrower — but Live is always bounded by CPU work (vmstate
dump + UFFD_WP arming), never by disk throughput.

Files:

- `bench/live-fork-pause-window/bench-live-fork.py` — runnable
  harness, parameterized on source-tag and iterations
- `bench/live-fork-pause-window/bench-live-fork.csv` — 40-row raw
  data (one per BRANCH iteration)
- `bench/live-fork-pause-window/RESULTS-v0.4.md` — writeup with
  methodology, host config, per-mode interpretation of what
  pause_ms / RT measure, and honest caveats (single host, one
  source size, p90 outlier on async iter deeplethe#8)

Docs updated:

- `README.md` headline: "BRANCH a live VM in 150 ms" → "in 56 ms
  (v0.4 live mode)". v0.4 preview block now leads with the
  measured 3.6× / 200× ratios and links to RESULTS-v0.4.md.
- `README-zh.md`: same headline + intro update.
- `CHANGELOG.md`: Unreleased's v0.4 section's "Bench in progress"
  disclaimer replaced with the actual numbers table.

Phase 7 (user surface for v0.4 live BRANCH) is complete with this
PR: REST (deeplethe#204), CLI (deeplethe#205), SDKs (deeplethe#206), doctor (deeplethe#207), docs
(deeplethe#208), bench (this).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant