Making of Fleet Dispatch¶
LIVING DOCUMENT — update this file whenever a ticket lands a change worth a beat. Plain
version: docs/plain/making-of.md. Beats below are drawn from git log
and the BUILD-REPORT-*.md files this repo keeps per major feature; nothing here is invented.
Beat 1 — the core: a signed cross-PC command queue (09f3ce6)¶
Fleet Dispatch started as exactly what its name says: an HMAC-signed queue for running a command
on another house PC, with a usage-governor gate so a dispatched job could not run during a RED
or BLACKOUT tier. enqueue wrote a signed job to a shared OneDrive folder; a listener drained
it, verified the signature, and ran it as claude -p or a PowerShell command.
Beat 2 — the LAN peer path (1675390)¶
enqueue/listen alone meant every dispatch round-tripped through OneDrive sync latency. The
persistent listener (serve) added a direct HTTP path (send → /dispatch) with its own
security review pass — bearer-token pre-check before HMAC, replay guards, and the loopback +
LAN-only bind posture that is still in place today.
Beat 3 — CI hardening (3bd6fc6 .. a3bd9ee)¶
A run of small, individually unglamorous CI fixes: pinning the .NET SDK install into a
runner-writable directory (the self-hosted runner account can't write
C:\Program Files\dotnet), moving DOTNET_INSTALL_DIR to step level (job-level runner.*
context isn't valid), discovering the runners have no pwsh and switching to Windows PowerShell,
and fixing the CLI's AssemblyName so dotnet publish actually emits FleetDispatch.exe instead
of a name mismatch. Small fixes, but every one of them was a prerequisite for CI being trustworthy
at all — a green CI that silently didn't build the right artifact is worse than a red one.
Beat 4 — LAN address selection, three times (48f4dbf, 7b3ff18, 8bcd609, 1345d7c, ff0dd1e, FLT-84)¶
The fleet sits behind wireless NETGEAR extenders that each run their own DHCP — a lease renewal or
an SSID roam can silently change a box's 10.0.0.x address and orphan the pinned host, the URL
reservation, and the peer registry entry. This took several passes to get right: first stop
virtual-switch NICs (Hyper-V/WSL) from winning the "preferred LAN NIC" pick, then switch to
preferring the Tailscale 100.64.0.0/10 address entirely (stable across the extenders), then fix
the tailnet CIDR's netmask encoding (Windows stores it differently than expected), then scope the
tailnet firewall rule to the Tailscale interface specifically (a RemoteAddress-only filter is a
spoofable source-IP check, not a real boundary), then finally fix repair-fleet-listener.ps1 to
actually run on Windows PowerShell 5.1 (it had been written assuming pwsh).
Beat 5 — the harness kind registry (62ac09c, 9cede5b, polyharness-parity P2)¶
Before this, "what kind of job can run" was two independently hardcoded claude|pwsh checks — one
in the queue, one in the CLI — that could drift apart. HarnessRegistry replaced both with one
source of truth and added four new kinds in the same pass: codex, agy, grok, ollama, plus
odysseus (an HTTP kind executed listener-side against a fixed loopback port). The follow-up
council review (9cede5b) caught and fixed an SSRF path in the odysseus kind, a type-confusion
bug, and a resource leak — the kind of finding a second-pass review is specifically for.
Beat 6 — run_agent, the uniform agent contract (c85884c .. 04f7ee7, FLT-2)¶
A new kind, run_agent, gave the dispatcher a way to run a full agent session (worktree creation,
timeout, retry, diff capture, validation command) rather than a bare CLI invocation. Landed with
its own council review pass that caught status spoofing, a ref-injection path, and unbounded I/O;
follow-ups fixed a codex agent_message schema mismatch, reaped descendant processes orphaned by
an exited shell (via a Windows job object), and pinned agy to its worktree with --add-dir.
Beat 7 — cancel and steer lanes (b5dc6a8, 696e9ef, 49576ae, d2ec1ab, 24bc55c, FLT-70/71/101/103)¶
696e9ef is the FLT-70/71 incident fix that shows up throughout this codebase's comments today:
schema-version skew was being reported as a generic HMAC failure, and — separately — a listener
that failed to bind was still advertising itself as live in the peer registry, so a dead box
looked healthy. Both got fixed with the same philosophy that's now load-bearing everywhere in this
repo: name the failure precisely, and never claim success before the thing you're claiming is
actually true.
The cancel lane (b5dc6a8) added a signed marker file that could kill an in-flight job — checked
before both the availability and governor gates, since cancel is a control action. The steer
lane started as a "kill and resubmit with new context" mechanism (49576ae, FLT-101 phase 1),
then upgraded twice: first to a live, steerable Claude session via the stream-json protocol
(d2ec1ab, FLT-103 phase 2) so a steer message reaches an actual in-flight conversation instead
of restarting it, then to the same capability for Codex via its app-server protocol's turn/steer
(24bc55c, FLT-103 cycle 2) — the strongest steering path in the fleet as of this writing.
Beat 8 — the RAM reaper (a3ca967, 783b7b5, FLT-55)¶
A dry-run, three-bucket process-attribution reaper for reclaiming RAM across the fleet, hardened in a follow-up council pass for attribution safety (making sure a reap decision correctly attributes a process to the bucket that's actually responsible for it before acting on it).
Beat 9 — drift-aware config deployment (86a620b, FLT-51)¶
Hash-verified, drift-aware deployment of tracked skills and peer config across the fleet — landed right before the two features below, giving the fleet a general "push config safely" mechanism independent of the job-dispatch path.
Beat 10 — the repo registry scanner (b40d522, FLT-126)¶
The fleet-wide repo inventory that Mission Control's RepoRegistryReader consumes needed a
per-PC producer. RepoRegistryScanner scans approved roots only (never a full-disk walk),
classifies main checkouts vs. linked worktrees via git rev-parse --git-dir/--git-common-dir,
folds every worktree into its main checkout's entry via git worktree list --porcelain (so a
worktree living on a completely different drive, e.g. C:\wt\..., still attaches correctly
instead of leaking out as its own fake top-level repo), and signs the output with a byte-for-byte
mirror of fleet-mission-control's own RepoRegistryReader.CanonicalString/Sign — matching
JsonSerializerOptions, field order, and join format exactly, verified against a literal test
copy of the reader's logic (ReaderMirror.cs) since there's no project reference across the two
repos. Wired into serve as a boot-plus-daily-timer background loop; deliberately given no
dedicated HarnessKind for on-demand triggering — it rides the existing pwsh kind instead,
since a first-class kind would have meant touching three more subsystems for a command with no
payload and no steering need.
Beat 11 — unavailable-mode: the hard gate, the detector, and jobClass (79d336e, FLT-128/129)¶
The newest major feature (and the day this doc set was written): the fleet had no way to distinguish "safe to run a heavy job on this PC right now" from "Andrew is mid-game or mid-stream on it." Built as two independently-verifiable pieces:
AvailabilityGate.Evaluate— a pure, memory-free function checked fresh on every single job, immediately before execution, with absolute authority the governor cannot override.AvailabilityDetector— a separate always-on ~4s poll loop applying re-availability hysteresis (90s gaming / 120s streaming) purely for the published heartbeat, so Mission Control's view doesn't flicker, while the hard gate stays deliberately blind to that hysteresis.
The jobClass field this gate reads had to become part of the signed canonical, not an
optional extra — an unsigned jobClass would have let anyone with queue-write (not the fleet key)
relabel a heavy job as control to dodge the gate while the PC was gaming, which is a live
security bypass, not a compatibility nicety. That's why this feature is wire-schema v2: adding
jobClass to the canonical is exactly the kind of breaking change DispatchJob.SchemaVersion
exists to make nameable, and this is the first time that mechanism has actually been exercised for
real since it was built as a safety net back in Beat 7. 436 tests passing (60 new), 0 build
warnings.
What's next¶
- Mission Control's builder side of FLT-128/129 (reading
availabilityoff the heartbeat, writing the manual override file) — tracked on Mission Control's own board, not here. - The documented-but-not-built gaps: fullscreen+GPU detection for an unlisted game, pause-in-flight for a job that starts before a gaming/streaming trip, gating the repo-scan timer loop, and an admin UI for the game allowlist.
- Promoting the Stryker mutation score from report-only to an enforcing gate once the self-hosted runner's VsTest-socket-collapse-as-killed-mutant inflation issue is resolved.