Twinned from https://github.com/cfsh/ninefold/issues/746 by tools/beads/import_github.py's reconcile pass.
Piece 2 of design 005's work breakdown (§8), under #686. Covers S2. Stacks on #739 (pieces 1+3) — same file.
Measured on nf-dev-sg 2026-08-07, 8 probes (tonapse-home focus-ring fit-notes cold-start cujs-gaps focus-glide guide-cursor chit-budget), warm cache, idle box:
| | wall clock |
|---|---|
| serial | 70 s |
| 8-way parallel, one xvfb-run -a each | 32 s |
2.19×, against the doc's 1.63× at 4-way. The difference is the box: §2.2 measured 4-way because nproc read 4, and this box now reports 8 (piece 3 / S16 covers that — notes/machines.md had 2, the doc measured 4, it is 8, so the pool must read nproc at runtime, never a written-down number).
⚠️ This is now Phase 1's actual cost win. S1's 1.8× import-cache speedup is retracted (#738, and the comment on #686) — it is a correctness fix, not a cost one. Parallelism is what is left.
outDirprobe_all already rewrites outDir per probe, so screenshots are safe. These two are not, and neither follows outDir:
| path | source |
|---|---|
| <clone>/probe_out/userdata — the whole user:// redirect | shared/VisualProbe.cs:101 |
| <clone>/probe_out/playtest — the playtest journal | shared/PlaytestJournal.cs:217 |
Both are wiped/seeded at startup, so N workers in one clone stamp on each other. Design 003 Open Q2 called this *"a race that happened not to fire"*. It fires at 8-way, and both failure modes showed up:
outDir also shared (running probe.py directly), cold-start failed: no focusable control matching '✳ New song using Shablam Demo Song'. It is the probe that sets ColdStartRun and depends on the fresh-install user:// state, so it is the most exposed.outDir — i.e. exactly what probe_all does — round 2 was clean, and in round 3 cold-start hung. Not failed: stalled after [playtest] journal armed and was still stuck ~9 minutes later, when I killed it.cold-start passes serially every time (3/3 immediately afterwards), so this is parallelism, not a broken probe.
Filed separately as a finding, because it is not only a parallelism problem. budget_ms warns and never fails (VisualProbe.cs:301), and there is no step deadline anywhere in the harness — so a probe that stalls waits forever. Under merge_pr.py that is a merge gate that never returns rather than one that goes red, and probe_gate cannot report what never finished.
A parallel sweep must not ship without a deadline, or one stuck worker converts a 32-second sweep into an unbounded hang holding a merge open.
probe_all.py, sized from nproc at runtime, one xvfb-run -a per worker. ⚠️ The sweep currently re-execs itself once under xvfb-run so all 34 children share one display (nflib.godot.ensure_display); a pool needs one server per worker instead, so that handshake changes.user://. UserDataRoot is a static derived from res://. Deriving it from the probe's existing outDir — probe_out/<name>/userdata — needs no new probe-JSON key, which matters because VisualProbe.cs enforces a strict root-key allow-list (§2.7). shared/UserPathGuardTests.cs and UserPathsTests.cs guard this area and must stay green.nproc, and on a shared box adding workers cannot recover what contention takes.⚠️ Ordering: the user:// and journal isolation must land before or with the pool. Shipping the pool first makes the gate flaky in a way that looks like #514 and gets diagnosed as flake.
None.
No comments.