~/steve.net — topics/ai/fleet-explainers · theme: Synthwave

Fleet Explainers

Five animated diagrams of how the fleet builds software: the task queue, one worker's life, a wave over a dependency graph, the reconcile barrier, and the whole machine end to end. They were built as talk visuals for the Fleet Series, and they're grounded in the real system — where a plate simplifies, the notes under it say so.

Each plate is a single self-contained page: no frameworks, no network, just SVG and CSS. Nothing autoplays — press play, or scrub the beat rail and step with the arrow keys. Every frame is a complete picture, so you can freeze anywhere. Plates follow your light/dark preference and respect reduced-motion. The ⚡ scenario tabs inject failures: gates going red, conflicts, escalations — the failure paths are the point.

A full text transcript of every plate's narration is included below each one.

Plate 1 — The task & the queue

Plate 1: The task & the queue (preview)

▶ Open the animated plate

State is a color. Claimable is a three-part AND — Ready ∧ auto ∧ unblocked — and blocking fails closed.

Four swim-lanes (Spec Needed → Ready → In Progress → Done), a handful of task chips, and the three-check claim gate. A task is claimable only when it is Ready and tagged for autonomous work and unblocked — and blocking fails closed: a task with a dependency that can't be resolved stays locked for the whole story.

Depiction notes: The three checks light up one at a time for teaching; in the real system they're a single predicate evaluated at claim time.

Plate 1 transcript

  1. Four states, four lanes. Every task lives in exactly one — and the only door between Ready and In Progress is the claim gate.
  2. Specs get written; tasks become Ready. Ready is necessary — not sufficient.
  3. A padlock means blocked: auth-api depends on auth-model, and a dep that isn't Done blocks its dependent.
  4. flag-cleanup points at a task that doesn't exist. Unresolvable? Still blocks. The gate fails closed — bad refs stop the fleet, they don't slip past it.
  5. The claim gate asks three questions, ANDed — re-checked against a fresh read at the moment of claim.
  6. auth-model: Ready ✓ · auto ✓ · unblocked ✓ — claimed. In Progress.
  7. auth-api: Ready ✓ · auto ✓ · deps all Done ✗ — it stays put. No wave will ever include it while the padlock is on.
  8. docs-pass is Manual — and an unset mode counts as Manual: Ready ✓ · auto ✗. The fleet never claims it. A human will.
  9. auth-model survives its gauntlet (plate 2) and lands: Done.
  10. The dep is satisfied — the padlock comes off. Nobody recomputed a plan. The blocked flag just flipped, and the frontier moved by itself.
  11. Re-check auth-api: Ready ✓ · auto ✓ · unblocked ✓ — claimed.
  12. auth-api lands. Done.
  13. End state: two landed; one waits for a human; one blocks until someone fixes the ref. Claimable = Ready ∧ auto ∧ unblocked. That AND is the whole queue discipline.

Plate 2 — One worker's life

Plate 2: One worker's life (preview)

▶ Open the animated plate

Edit in a sandbox, verify through an ordered gauntlet, commit on the host — and on any red gate, revert: green or it never happened.

One worker, one task, the whole gauntlet: clean working copy on the host, then edit → scope → build → lint → tests inside a sandbox, then commit back on the host. The rule that matters: revert-on-fail — green or it never happened. Two failure scenarios: a gate goes red, and a worker gets stuck and escalates to a human.

Depiction notes: Some machinery isn't depicted (skip markers, retry details, sandbox preflight ordering). The stuck scenario merges the no-progress guard with the attempt cap — at a cap of two they coincide.

Plate 2 transcript

Scenario: green run

  1. One Ready leaf, one worker. Everything in the dashed box runs inside an isolated sandbox container — the repo itself lives on the host, on the rail below.
  2. First check, on the host: is the working copy clean? The worker refuses to edit over uncommitted work.
  3. Clean ✓ — the task is marked In Progress. Now, and only now, does the LLM get to type.
  4. Into the container. The model edits — opencode run, with the repo's durable lessons prepended — and produces a changeset.
  5. Gate 1 — scope: 3 files ≤ max_files (5). A sprawling diff is a failure, not a style.
  6. Gate 2 — build: the static check. This one runs always, whatever the task says.
  7. Gate 3 — lint (when the task requires tests): autofix, then re-check.
  8. Gate 4 — tests: the full suite, still inside the sandbox.
  9. Green across the board. The changeset leaves the sandbox and the host commits it — auto: fix retry logic, addressed by a jj change id, not a mutable sha.
  10. Only now — after the commit exists — does the status flip: Done.
  11. The invariant: green or it never happened. The task board never claims what the branch can't show.

Scenario: ⚡ fail a gate (failure injection)

  1. One Ready leaf, one worker. Everything in the dashed box runs inside an isolated sandbox container — the repo itself lives on the host, on the rail below.
  2. First check, on the host: is the working copy clean? The worker refuses to edit over uncommitted work.
  3. Clean ✓ — the task is marked In Progress. Now, and only now, does the LLM get to type.
  4. Into the container. The model edits — opencode run, with the repo's durable lessons prepended — and produces a changeset.
  5. Gate 1 — scope: 3 files ≤ max_files (5). A sprawling diff is a failure, not a style.
  6. Gate 2 — build: the static check. This one runs always, whatever the task says.
  7. Gate 3 — lint (when the task requires tests): autofix, then re-check.
  8. Gate 4 — tests: the full suite, still inside the sandbox.
  9. Tests: RED.
  10. Any red gate, same move: the edit is thrown away on the host — revert — and the task drops back to Ready. Nothing survives.
  11. No half-work, no “mostly done”. The next attempt starts from a clean copy — and if it keeps failing the same way, a human gets it (third tab).

Scenario: ⚡ stuck → escalate (failure injection)

  1. Fast-forward: attempt 1 dies at the test gate and reverts. Its failure gets a signature — the reason, normalized: shas, paths, numbers stripped.
  2. Attempt 2 runs the whole gauntlet again… and dies with the identical signature. Two matching failures in a row = stuck.
  3. Stuck → escalate: Spec Needed + Manual. A valid terminal state, not a crash — the loop stops burning budget and hands the leaf to a person.
  4. The guard is deterministic code, before any model call: max 2 attempts per leaf per epic, and matching signatures short-circuit even that. Done resets the streak.

Plate 3 — A wave

Plate 3: A wave (preview)

▶ Open the animated plate

Claimable lights up, a wave of at most 4 is drawn, leaves land, locks pop — repeat until dry. Serial runs 1 at a time; concurrent runs up to 3 at once, and only disjoint-scope leaves co-dispatch.

A ten-leaf epic as a dependency graph, built in waves: everything currently unblocked lights up, a wave is drawn, leaves land, padlocks pop, repeat until the graph runs dry. Two scenarios run the same epic serially (one in flight) and concurrently (three in flight) — same end state, very different wall-clock.

Depiction notes: A couple of compressions: graph nodes flip to Done at the landing beat as a montage, and padlocks pop at the wave boundary. One subtlety the plate gets right (verified against the real scheduler): the concurrency cap staggers workers within a wave — only a file-scope collision defers a leaf to the next wave.

Plate 3 transcript

Scenario: serial (1 in flight)

  1. One epic, ten leaves, dependencies drawn as they are. There is no layer chart — nobody computed one. Just chips, edges, and a blocked flag.
  2. Claimable lights up: Ready ∧ auto ∧ unblocked. Four qualify. The padlocked ones wait on deps; docs is Manual; migrate has no spec yet.
  3. The wave: up to 4 of the claimable — auto+ first, then priority. Four qualify, four drawn. That's the cap, not a layer.
  4. Serial dispatch: one in flight. schema runs the plate-2 gauntlet against the main working copy, in the dx container.
  5. …green → the host commits. schema is Done. One leaf, one commit, no merge step — nothing ran in parallel.
  6. parser, same lane. Two down.
  7. store follows…
  8. …then bench. Four commits, strictly one at a time.
  9. Wave gate: the whole-project suite on the accumulated state — hard. The ensemble review — advisory; confirmed findings become new fix-up leaves.
  10. Locks pop: api, cli, metrics are unblocked — because Done flipped, not because anyone replanned. The frontier advanced by itself.
  11. Wave 2: three claimable, three drawn.
  12. One at a time again: apiclimetrics. Seven commits on the epic bookmark.
  13. Gate again — then ui unlocks (both of its deps are Done). Wave 3 is a single leaf. Waves come from the frontier, so they shrink and grow with it.
  14. ui lands. Eight commits. Nothing claimable remains…
  15. …and nothing ever will be: what's left is human-owned. The loop exits clean — waiting_on_human, not an error. The rhythm: light up → wave ≤4 → land → unlock → repeat until dry.

Scenario: concurrent (3 in flight)

  1. Same epic, same frontier — now with dispatch_concurrency = 3.
  2. Wave planned: still 4 leaves. At most 3 run at once (dispatch_concurrency = 3) — and only leaves with disjoint file-scopes co-dispatch.
  3. Three fan out — disjoint scopes, each into its own jj workspace in an ephemeral run-once sandbox. bench steps back: its predicted files collide with a sibling, so pick_disjoint holds it — colliding leaves can't run together. Still Ready, next wave's problem.
  4. All three finish green — in isolation. Three workspaces, three commits, zero integration so far.
  5. The reconcile barrier: one door, one commit at a time, in dispatch order. Parallel work, serial truth. (Plate 4 is this, slowed down.)
  6. Wave gate on the combined state — suite hard, review advisory.
  7. Frontier advances — and Wave 2 is four again: api, cli, metrics, bench, in priority order.
  8. Again three co-dispatch; bench defers again — its scope still overlaps a sibling this wave. Deferred by file collision, not by rank: the concurrency cap staggers work inside a wave, it never pushes a leaf to the next one.
  9. Green in isolation → through the barrier, one at a time. Six commits.
  10. Gate → ui unlocks → Wave 3: just ui and bench — disjoint at last, so both co-dispatch. bench finally rides.
  11. Barrier → eight commits. Dry of auto work.
  12. Same end state as serial, same barrier-guarded truth — just less wall-clock. Concurrency is an optimization inside the wave, never a different correctness story. Exit: waiting_on_human.

Plate 4 — The reconcile barrier

Plate 4: The reconcile barrier (preview)

▶ Open the animated plate

Concurrent leaves finish green in isolation; a single serial door integrates them in dispatch order. Conflicts are data; losers are demoted; wave-mates keep going.

The centerpiece. Three workspaces finish in parallel, then integrate one at a time, in dispatch order, each rebasing onto the head the previous one produced: parallel work, serial truth. Conflict is a field you check, not an exception you catch — losers are abandoned and demoted, never allowed to hold their wave-mates hostage. The third scenario is the best one: every leaf green alone, the suite red combined, and a bisect walk finds the semantic conflict.

Depiction notes: This one tracks the real reconcile logic closely — including the deliberately linear bisect walk. A few rare error paths aren't depicted.

Plate 4 transcript

Scenario: clean integration

  1. Where plate 3 left off: three leaves ran in parallel, each in its own jj workspace, each through the full gauntlet — green in isolation, committed, marked Done.
  2. Now the only door. Integration is serial, in dispatch order, no matter how parallel the work was. The barrier admits one commit at a time.
  3. head = base. First in dispatch order: retry-core enters — alone. Its workspace commit was resolved to a change id first; shas go stale across rebases, change ids don't.
  4. jj rebase -r krxq -d head → conflict? no. Landed. head = krxq.
  5. retry-cli enters. It rebases onto the head the last leaf produced — a target that didn't exist until a moment ago. That is why the barrier is serial.
  6. Clean. head = wmpt.
  7. metrics, same door, same order. head = zsov.
  8. ≥1 leaf landed, so the main working copy repositions onto the final head. The wave gate (plate 3) now runs the whole suite on this combined state.
  9. Parallel work, serial truth. The fan-out was an optimization; this ordered line of commits is the product.

Scenario: ⚡ rebase conflict (failure injection)

  1. Where plate 3 left off: three leaves ran in parallel, each in its own jj workspace, each through the full gauntlet — green in isolation, committed, marked Done.
  2. Now the only door. Integration is serial, in dispatch order, no matter how parallel the work was. The barrier admits one commit at a time.
  3. head = base. First in dispatch order: retry-core enters — alone. Its workspace commit was resolved to a change id first; shas go stale across rebases, change ids don't.
  4. jj rebase -r krxq -d head → conflict? no. Landed. head = krxq.
  5. retry-cli enters — it touched the same backoff code the first leaf just rewrote.
  6. The rebase “succeeds” either way — jj records a conflict as data on the commit, not a thrown exception. Check the field: conflict: true.
  7. Conflicted → read the paths, jj abandon the commit, demote the leaf: Done → Ready. Journaled as a reconcile_demotionthis wave will not re-dispatch it; the replan owns the retry.
  8. metrics doesn't care. It enters — head is still krxq — and rebases clean onto it.
  9. Landed. head = zsov. The hole where wmpt would have been simply closed up.
  10. Two landed, one demoted — and the wave-mates were never hostage to the conflicted leaf. Main WC repositions onto the final head; the wave gate runs on what actually landed.
  11. Disjoint file scopes at dispatch time make conflicts rare. The barrier is what makes them safe. Optimization above, correctness floor below.

Scenario: ⚡ semantic conflict → bisect (failure injection)

  1. Where plate 3 left off: three leaves ran in parallel, each in its own jj workspace, each through the full gauntlet — green in isolation, committed, marked Done.
  2. This time all three integrate cleanly — conflict: false, three times. Chain: base → krxq → wmpt → zsov.
  3. Then the wave gate runs the whole suite on the combined state: RED. Every leaf was green alone — this failure exists only in combination, and nothing attributes it for free.
  4. Bisect-on-red (runs only when: suite red ∧ concurrency > 1 ∧ ≥2 landed). Walk the chain, re-running the suite at each point. At krxq: green.
  5. At krxq + wmpt: RED. First red leaf found: retry-cli.
  6. Back it out: rebase the rest off it (zsov moves down one), abandon wmpt, demote the leaf — same demotion, different trigger.
  7. Suite on what remains: green. The wave continues with what is true together.
  8. Two barriers, one law: what lands must be green together, in one serial order.

Plate 5 — The whole fleet

Plate 5: The whole fleet (preview)

▶ Open the animated plate

Goal → architect → ★approve★ → task tree → wave loop on a private epic bookmark → epic gate → ★human merges main★. The loop never touches main.

The whole machine on one map, bookended by the two human gates: a person approves the framing before any code exists, and a person merges to main at the end. Everything in between — architect, task tree, wave loop, epic gate — runs on a private branch, and main receives exactly one commit: the human merge.

Depiction notes: The wave loop runs three turns and then reads "dry"; real epics can span many runs. The three seals at the epic gate abstract a cross-family review quorum. Commit ids are stylized throughout.

Plate 5 transcript

  1. It starts as a sentence: “retire the legacy import path.” No tickets, no tree, no branch.
  2. The architect (strong tier) goes first: A0 inventory the repo, then A1 — a framing proposal. Scope, risks, approach. Still no code.
  3. ★ Human gate 1. A person reads the framing and approves it — or doesn't. Hard gate: nothing autonomous starts without this stamp.
  4. A2 decompose: epic → features → leaves, ≤3 deep, every leaf small enough for one worker. A3 emit to Forge — idempotently.
  5. A private epic bookmark forks. Everything that follows lives here. main is not part of this story yet.
  6. The wave loop turns — plan → dispatch (serial or ≤3 concurrent) → reconcile → gates → replan. Turn one: three leaves land.
  7. Every turn ends at the wave gates: whole-project suite (hard) + ensemble review (advisory).
  8. Advisory findings don't block — confirmed ones become new fix-up leaves. The tree grows mid-flight; the next wave will pick this one up.
  9. Turn two: four more land — including the fix-up.
  10. Turn three: the last two. The tree is dry — and an epic can span many runs; the per-run wave cap just parks it resumable.
  11. Epic gate: the whole epic diff gets a full-quorum, cross-family sign-off. Hard, fail-closed — three model families must all say yes.
  12. ★ Human gate 2. A person — and only a person — merges. main moves for the first time in this whole story.
  13. Two human gates bookend the autonomy. No code before an approved framing; nothing on main without a human merge. Everything in between is the loop you now know — plates 1 through 4.
theme: apple synthwave amber c64 arcade tek
steve.net · page: topics/ai/fleet-explainers
? keys try: curl steve.net or: ssh steve.net or: shell mode or: reading view