What changed, and why
The Glassbox repo already runs a fleet of Many Hands Engineering loops — read-only agents that map a class of defect and let a human fix what they agree with. This change adds three that push the bar past content-accuracy (which only guards whether the prose is true):
- lab-fidelity — is every lab actually driven by the lesson's engine, or does it fake the output it presents as computed? - collection-coherence — does each lesson hit the shared "complete lesson" bar, and stay consistent with its siblings? - visual-sanity — does the page render without layout breakage, measured across both themes at desktop and mobile?
The interesting part is that three new loops did not mean three new copies of the fan-out machinery. A shared scaffold, per-lesson.ts, is extracted first, and the existing content-accuracy loop is migrated onto it — so the per-lesson family is deduped, not triplicated. Then all three loops were run over every lesson; the three real gaps they surfaced are fixed in the second commit and shown at the end.
Blast radius: the loops live under agents/ (their own package, never in the app's dependency graph). The only app-source touched is the four-file content fix and a staleness fix to one audit script. Everything is read-only by construction.
lab-fidelity — the engine is the oracle
content-accuracy guards the prose; lab-fidelity guards the interactive half. Its outside reference is unusually concrete: engine/index.js, the pure, unit-tested logic that is the source of truth for what every lab should compute. A lab whose displayed values or step order disagree with the engine — or that bypasses the engine to hardcode a "result" — is a finding.
Because the scaffold carries the orchestration, the loop file is almost entirely its two prompts. The registration is this small:
The entire loop wiring: prompts + model + budget, handed to the scaffold.
agents/lab-fidelity.ts · 115 lines
⋯ 100 lines hidden (lines 1–100)
The system prompt does the real work — it defines the three buckets (fidelity defect / verified live / judgment) and the cite-or-omit discipline that keeps the agent honest: a finding with no quoted line from its own Read is a confabulation and must be suppressed.
The engine-vs-faked rule the agent grades against.
agents/lab-fidelity.ts · 115 lines
⋯ 53 lines hidden (lines 1–53)
⋯ 54 lines hidden (lines 62–115)
collection-coherence — each lesson against the whole
Every other content loop judges one lesson against the world. This one judges each lesson against the collection: does it hit the shared "anatomy of a complete lesson" bar, and is it consistent with its siblings? The rubric is checked into the repo as COLLECTION.md and mirrored in the loop's system prompt, so the document a human reads and the bar the agent grades against are the same five beats.
The pedagogy bar — the five beats every lesson must hit.
COLLECTION.md · 185 lines
⋯ 21 lines hidden (lines 1–21)
⋯ 148 lines hidden (lines 38–185)
The same five beats, encoded in the agent's system prompt.
agents/collection-coherence.ts · 116 lines
⋯ 51 lines hidden (lines 1–51)
⋯ 58 lines hidden (lines 59–116)
visual-sanity — measure, don't look
Layout integrity can only be judged on the rendered page, so this loop is self-contained: it builds the app, boots vite preview, runs a deterministic layout sweep, tears the server down, and hands the agent the per-render defect signals to classify.
Build → boot preview → measure → tear down, in a finally block.
agents/visual-sanity.ts · 217 lines
⋯ 67 lines hidden (lines 1–67)
⋯ 128 lines hidden (lines 90–217)
The sweep itself is plain Playwright measurement, not vision. For each lesson × theme × viewport it walks the DOM and records horizontal overflow (and the elements causing it), broken images, an empty main, and reveal-on-scroll blocks left stuck hidden.
The overflow oracle: which elements cross the viewport edge, and by how much.
scripts/render-audit.js · 159 lines
⋯ 93 lines hidden (lines 1–93)
⋯ 51 lines hidden (lines 109–159)
Proof the dedup is real — and a staleness bug fixed in passing
The claim "deduped, not triplicated" is only true if the existing loop actually moved onto the scaffold. It did: content-accuracy now imports from per-lesson.ts and ends in the same runPerLessonLoop call the new loops use — its ~200 lines of bespoke orchestration deleted, its two long prompts kept verbatim.
content-accuracy, after migration — the same one-call shape.
agents/content-accuracy.ts · 139 lines
⋯ 124 lines hidden (lines 1–124)
Wiring up the render sweep also turned up a real bug in a neighbouring script. contrast-audit.js had hardcoded an 18-lesson id list, so it had been silently skipping the five newest lessons every run. It now parses the catalog the way the scaffold does — the exact staleness class these loops exist to kill.
Lesson ids parsed from the catalog, so the sweep can't go stale.
scripts/contrast-audit.js · 113 lines
⋯ 29 lines hidden (lines 1–29)
⋯ 76 lines hidden (lines 38–113)
What the loops caught — and the fixes
All three loops were run over every one of the 23 lessons. lab-fidelity (0 defects) and visual-sanity (0 of 96 renders flagged) came back clean. collection-coherence surfaced three real gaps — all fixed in the second commit, and confirmed closed by re-running the loop on those lessons.
| Lesson | Gap the loop found | Fix |
|---|---|---|
| bloom-filters | Hero claims 7 interactive labs; only 6 render, and badges skip from Lab 05 to Lab 07 | Renumber to Lab 06, correct the Hero count to 6 |
| bloom-filters | Ch08 puts HyperLogLog at ~1.5 KB, contradicting the hyperloglog lesson's ~12 KB (stated 4×) | Reconcile to the owner's ~12 KB |
| b-trees | Coda asks why "filing 80" made the tree wider not taller — but 80 is never staged and the question is never posed | Retarget the callback to the split lab the reader actually used |
The b-trees one is the subtle one. The §IX coda was a rhetorical "the question from before" callback — but it referenced a key (80) the split lab never files and a question the lesson never asks (§V scripts filing 50 into a full root). The fix drops the fabricated setup and points the reflection at the lab the reader did use, keeping the pedagogy while making the callback resolve.
The retargeted coda — no fabricated key, references the real split lab.