pipelines/AUTONOMOUS_RUN_LOOP.md
What this is. The exact loop a Claude Code session executes with no human present, so that work
advances correctly while Josh is asleep, driving, or playing. It is a procedure with gates, not an
aspiration. Every step below is either a command that exists today or a tooth that fails the tree.
The target it serves. Josh, verbatim, 2026-08-01: *"We built the entire fucking spine and region
pages and all the thousands of hours of architecture so you can build 79 chapters autonomously. I've
already made all the decisions I should be needing to make. You have what you need so stop fucking
going in blind."* The loop's whole job is to make that true mechanically — the session reviews
nothing with him except creative art and quest dialogue.
The failure it ends. A cold-start trace walked the repo's own prescribed orientation chain on
2026-08-02: six forwarding layers, nine named destination docs, 19,774 words to reach the
chain's designated terminus — and the answer there was *wrong* ("the next node is Ch 13"; the spine
has been 79/79 and v3-rewritten since 2026-07-15). Full obedience to every pointer the chain issued
came to ~118,761 words. No document in the repo stated the next action at any word count. The
two artifacts that actually were current-truth were a row 20,981 words into docs/DOC_MAP.md and a
cell in a CSV nothing pointed at.
So the loop's first law is that "what next" is one file and one command, and the answer is
identical for any two sessions on the same tree:
python harness/queue.py next
---
SESSION START
0.1 git status ; git log --oneline -5
0.2 python harness/run_gates.py # UNPIPED. Exit 0 or the loop HALTS (§6.1)
0.3 python harness/queue.py recover # session-death recovery (§4.3)
0.4 python harness/queue.py intake # Josh's review comments -> queue items (§5)
PER ITEM (repeat until `next` exits 4)
1 python harness/queue.py next # THE pick. Deterministic. exit 4 = empty (§6.4)
2 python harness/queue.py claim <ID> --session <tok>
3 python harness/route.py <kind> --chapter CH_NN --region <r> # READ THE READ-SET, IN ORDER
4 produce # canon is WHAT; the proposal is only HOW (§2.2)
5 python harness/run_gates.py # the item's named gates must be green
6 the adversarial critic # mandatory for authored prose/images (§2.4)
6b the comparator rubric # if the item touched a fun-bearing domain (§2.4b)
7 git add <your paths> ; git commit # never `git add -A`; never push
8 python harness/queue.py land <ID> --commit <sha> --critic GO
9 git add docs/work_queue.json docs/archive/WORK_QUEUE_HISTORY.md docs/START_HERE.md ; git commit
# claim/land REWRITE the map's Ranked-next in place (§4.4) — commit map + queue together
SESSION END
the queue file IS the handoff. There is nothing else to write. (§4)
Everything below is that loop, specified.
---
docs/work_queue.json. It replaces reading docs/START_HERE.md ▶ Ranked next, the
▶ PROGRESS LEDGER in docs/PRE_5090_BUILD_PLAN.md:219, the head of
docs/spine/DECISIONS_PENDING_JOSH.md, STATUS.md, and the cascade runbook — and guessing which of
the five is current. Those five stay as the record; none of them is a work order any more.
It is machine-readable because a human-readable queue is a queue that drifts. queue.py next reads
it; a session never picks by eye.
Every field is an FK into something that already exists, so the queue mints no vocabulary of its own:
| Field | Meaning | Bound to |
|---|---|---|
work_kind | which of the 21 kinds of work this is | docs/task_router.json — so every item resolves to a read-set via harness/route.py |
target | {chapter, region, artifact_id, path} | the node the work is scoped to |
derivation | the canon this item realizes | docs/spine/ · _source/01_… · _source/02_… · registries/ — a docs/proposals/ path here is a FAIL |
gates | the gates that prove it | keys in harness/gates_config.json (42 gates) |
critic | the adversarial reviewer that must clear it | normalize-doc, the four image critics |
authority | loop or josh | §3 |
state | ready · in_flight · blocked · awaiting_josh | closed enum; there is no done (§4.2) |
priority · attempts · blocked_by | the ordering and the dependency graph | other item ids |
An item is claimable iff all four hold, and queue.py next applies exactly this test:
1. state == "ready", and
2. authority == "loop" — an item that is Josh's is never handed to the loop, and
3. every id in blocked_by is blocked or gone (landed items leave the file), and
4. attempts < 3.
Order is (priority asc, attempts asc, id asc) — total, deterministic, no coin flips. attempts
sits in the sort key on purpose: an item that just failed sorts behind its siblings instead of being
retried at 3am until the cap parks it. Two sessions on the same tree get the same item.
The queue as landed carries seven items derived from docs/START_HERE.md § Ranked next, the
awaiting_pick row in docs/art_boards/MANIFEST.csv, the single-entry artifacts list in
docs/canon_derivation.json, the ratchet in docs/ledger_baseline.json, and the 64 unbuilt
T0_Region_Index rows. One of them (WQ_0002) is a defect the seeding itself found: the map's
third-ranked open item is a decision Josh ruled on 2026-07-19
(docs/spine/DECISIONS_PENDING_JOSH.md:2919) whose ⚠ NOT ruled banner still sits nineteen lines
above its own ✅ RULED line. That is precisely the class of rot a single machine-checked queue
exists to stop.
---
python harness/route.py <work_kind> --chapter CH_NN --region <region_id>
Read what it prints, in the order it prints it. The read-order law is
docs/TASK_ROUTER.md § *The read-order law*: the spine node first, then the region page at section
grain, then the T0 registries the [POPULATE→ …] tags route to, and **the program doc LAST and
subordinate**.
This is the step whose absence cost a whole session. docs/spine/CH_02.md:226 ## Asset anchors
already listed the Flores board's entire contents — every CR_ id, the architecture set dressing,
the material palette, the realm object's art brief — each tagged with its destination registry. The
lane built the board from docs/proposals/CONCEPT_ART_PROGRAM.md instead, invented a "pick one of
three readings of Flores" frame for content the spine had settled, and shipped zero images. **It
passed a structural gate and four adversarial critics.**
The queue now refuses that item before a word is written: gate 42's T-DERIVATION fails any
node-scoped item whose derivation list is empty, unresolvable, or points into docs/proposals/.
Canon is WHAT; a proposal is only HOW (order of operations, lane routing, licence posture,
board shape). If a proposal asks you to re-decide something the node already settled, the proposal is
the defect — docs/DOC_MAP.md § 0 rank 6.
Fan-out rules, when the item warrants lanes: paste docs/SUBAGENT_CONTRACT.md §1 verbatim into every
lane prompt, and set model explicitly on every lane — nothing inherits the session model
(standing global rule; one 8-agent fan-out once burned ~20% of a weekly limit).
python harness/run_gates.py, read unpiped — a pipe masks the exit code. The item names its own
gates; the suite must be green as a whole. The per-kind assignment below is not invented here — it is
docs/task_router.json's gate column, reduced to the roster keys each entry names. An item may
name *more* gates than its kind's floor (WQ_0005 adds ws_value_form and slice_coherence to a
region page); it may never name fewer.
| Work kind | Its gates |
|---|---|
spine-chapter | lint · anchors + the entry enrolled in BOTH target lists · normalize-doc critic |
region-page | region_pages (+ check_region_page.py --self-test) |
concept-art-region · concept-art-class | boards · canon_derivation |
art-bible | boards (the bible header is AWAITING PICK or RATIFIED, nothing between) |
quest | seed_housing · no_dead_end · spine_graph · promise_ledger |
music · voice | grand_sage_silence · generation_lifecycle |
sfx | sfx_coverage |
terrain | region_pages · build_space |
assetgen-3d | generation_lifecycle (no binary enters this repo) |
cinematic | reveal_discipline |
boss | boss_axes · counter_class_coverage · no_dead_end · rarity_firewall |
registry | fidelity (--emit-baseline in the SAME commit) · registry_extensions · stress · scan |
range-review | the RR file enrolled in lint + anchors; tag spine/rr-NN |
decision | ledger — the ledger stays current-truth: a ruling COLLAPSES its options |
realm | realm_ruleset |
marketing-web | boards (T-MARKETING · T-PANELA-PREVIZ · T-MARKETING-COMPUTED) |
program-doc | registered in docs/DOC_MAP.md in the SAME commit |
gate | must-fire and must-not-fire fixtures; registered in gates_config.json |
ue-wiring | the game repo's own build/PIE verification; canon gates unaffected |
Mandatory for every kind that produces authored prose or images —
spine-chapter, region-page, concept-art-region, concept-art-class, art-bible, quest,
cinematic, boss, realm, marketing-web, program-doc. It is the critic_required set in the
queue contract, and queue.py land refuses an item of one of those kinds without a recorded
verdict of GO or GO_WITH_FIXES.
The critic loads canon cold (normalize-doc; the four image critics for boards) and it replaces
per-item first-line review, never Josh's authority — docs/CASCADE_RUNBOOK.md §10, ratified
2026-07-04.
Mechanical kinds (registry, gate, ue-wiring, terrain, assetgen-3d, sfx, voice,
decision) take their deterministic gate instead. That is not a lower bar: a registry item is
edited with the csv module only and re-baselined in the same commit, and a gate item ships a
failing fixture per tooth or does not ship.
Josh, 2026-08-05: *"Remember those critic lenses we created — all those gates based on the
comparable games I mentioned. Are you running everything against those?"* The honest answer was no.
The feel bands were comparator-anchored and fifteen teacher lenses ran on 2026-08-04 as one-shot
sweeps, but nothing made a later piece of work read them again, so a wave could build a fun-bearing
system, close, and never be scored against the teacher Josh named for it. This step is that gap
closed, and it is the same shape as §2.4: the work does not certify itself.
The rule. If the item touched any of the twelve fun-bearing domains — movement ·
combat-melee · combat-ranged-cast · magic-fantasy · loot · discovery · collection ·
raising · building · vehicles · minigames-secrets · systems-social — open that domain's
rubric at harness/qa/comparator_rubrics/<domain>.json, run its checks against what the item
actually built (each check names the instrument it reads — a feel band, the FG-11 chain, a canon
gate — or declares it has none), answer the exceeding_bar question in prose, and record the
verdict on the landing:
COMPARATOR VERDICT - <domain_id> vs <teacher(s)>: EXCEEDS|MEETS|BELOW|UNMEASURED - <evidence> COMPARATOR VERDICT - <domain_id>: NOT_APPLICABLE - <reason> # the considered escape
MEETS and BELOW and UNMEASURED are legal answers and are the point. UNMEASURED says the
instrument for that mechanism does not exist yet and carries what it would take — which is a
finding, not a failure. A rubric that can only return EXCEEDS is flattery, and a verdict written
without opening its instrument is the self-certifying pattern §2.4 exists to stop.
The tooth. Gate 51 comparator_rubric reads the wave-close entries in
docs/FACTORY_CONTINUATION.md and FINDS any that triggered a fun-bearing domain and carries no
verdict line for it. It never asks whether a verdict is *right* — that is the rubric's prose
question and a human or a critic answers it. Entries dated on or before 2026-08-05 are DECLARED
NOT-ARMED and can never be findings: no retroactive reds.
Commit your own paths explicitly — never git add -A (lanes share this tree). **Commit, do not
push; the director pushes. A new doc is not done until it has a docs/DOC_MAP.md row in the same
commit**.
Explicit is not the same as YOURS (added 2026-08-04, from the R7 swallow). An explicit pathspec
is only half the rule: the paths you name must be paths *your own item wrote*. Commit 2eb4b8b1
staged by name a file another lane had authored and left in the shared index
(T1_Personal_Dimension_Spec [ACTIVE v1.1].md) plus a whole unrelated gate apparatus, so a commit
titled for the healing-model ruling became the sole carrier of two other lanes — and a revert of the
ruling would now take a tier doc and a gate with it. **git add <another lane's path> is the same
defect as git add -A.** Before committing, read git status --short and challenge every path
outside your declared write-set; scope the commit itself (git commit -m ... -- <your paths>), never
a bare git commit. If you do carry another lane's work anyway, say so in the commit body and
land an empty provenance commit naming the carrier — the record is the repair, because rewriting
pushed history is worse than the misattribution.
python harness/queue.py land <ID> --commit <sha> --critic GO
The item leaves docs/work_queue.json and its line lands in
docs/archive/WORK_QUEUE_HISTORY.md. The queue update belongs in the same commit as the work: a
landed artifact with a stale queue is the drift this whole system exists to prevent.
---
| Class | It is Josh's because | Grounded at |
|---|---|---|
cardinal_canon | chapter/Component assignments, numerical canon, naming, hard lines, §17, the pillars, the vision | CLAUDE.md § Cardinal rule (the LOCKED list) · docs/CASCADE_RUNBOOK.md §10 |
spend | money leaves an account | CLAUDE.md § Book acquisition (the ~158 MUST-ACQUIRE buys) |
credentials | any account, key, or token | docs/spine/DECISIONS_PENDING_JOSH.md item 3, 2026-07-31 — the standing work-accounts boundary |
creative_approval | the pick on art; the sign-off on character lines that ship | docs/proposals/CONCEPT_ART_PROGRAM.md:1809 ("Josh touches the pick line and nothing else") · CASCADE_RUNBOOK.md §10 batch sign-off |
Nothing else is his. authority: josh with any fifth class fails T-AUTHORITY.
Run E1→E5 and stop at the first hit.
numerical-canon count (77+2 nodes, 22 threads, 72 weapons, 10 trades, 12 tiers, 33 species, 22/33
familiar slots, 185 abilities), a name, a §17 hard line, a pillar, the vision.
→ JOSH, cardinal_canon.
*Filling in* locked structure is not changing it. Only contradiction escalates.
spend or credentials.
→ JOSH at the approval step only, creative_approval. Producing the candidates, running the
four critics, and publishing the board are all the loop's. He sees one pick line.
docs/spine/DECISIONS_PENDING_JOSH.md, docs/BATCH1_RULINGS.md, docs/CANON_AUDIT.md § Josh's rulings,
docs/proposals/systems/RULINGS.md. → LOOP: apply the ruling. A ruling is a work order, not
an open question, and his rulings are the FLOOR — generalize the principle and sweep the whole
class, never just the named instance.
CLAUDE.md's four-step protocol (steelmannedalternatives → deep dive → adversarial review → recommendation + strongest objection), **apply the
recommendation, and show the path.** Do not ask.
Data-hygiene renames · docs/DOC_MAP.md rows · file moves and archive retirements · gate teeth and
fixtures · scorecard and baseline re-emissions · fixing a proposal that disagrees with canon ·
collapsing a superseded marker beside its own ruling · choosing which unbuilt region page is next ·
schema and column additions inside a declared extension contract · which model tier a lane runs at.
Surfacing one of these is the failure the loop was built to end, in the opposite direction from going
in blind: it costs Josh a decision he already delegated.
authority: josh requires a brief, and gate 42 checks its shape where the shape lives:
cardinal_canon, spend, credentials) — the brief file must carry *alternatives*, a *recommendation*, and the *strongest objection* (T-BRIEF-SHAPE). This is
CLAUDE.md's standing protocol, mechanized.
one marked recommendation per panel, armed by gate boards' T-ONE-REC. So the tooth requires
the item to route through boards. One rule, two surfaces, never a second vocabulary.
An escalation never stops the loop. An awaiting_josh item is invisible to queue.py next by
construction — the loop takes the next claimable item and keeps going. Josh being asleep is not a
blocker; it is the normal operating condition.
---
No handoff doc, no session-close narrative, no "next session" paragraph. Those are what produced a
six-layer chain in the first place. A session hands over by having landed its work and updated
docs/work_queue.json — which the next session reads with one command.
There is no done state. A landed item is deleted from the queue and its line appended to
docs/archive/WORK_QUEUE_HISTORY.md; T-NO-DONE fails the tree if one lingers. This is the same
split docs/START_HERE.md and docs/archive/START_HERE_HISTORY.md got on 2026-08-02, applied to the
worklist before it can rot the same way.
Sessions die without warning — an account-auth event killed two live sessions at once on 2026-07-17.
So:
is the memory, not the chat.
in_flight is a crash marker, not a lock. queue.py recover, run at session start, returns every in-flight item to ready, counts the attempt, and records which session died holding it.
The third death parks the item as blocked with the reason *"it is mis-specified, split it"*,
because three deaths on one item is a fact about the item.
python harness/queue.py view emits the entire ## Ranked next section, and **every successful
queue mutation rewrites that section of docs/START_HERE.md in place** (sync_map, called after
the save survives the teeth) — so the loop cannot leave the map behind by omission. Commit
docs/START_HERE.md alongside docs/work_queue.json; queue.py prints the reminder. The backstop
against HAND edits is check_orientation.py tooth T-QUEUE-VIEW: whole-section equality against a
fresh queue.py view, duplicate ## Ranked next headings fire, an unavailable view fires, and the
generated section's lines are exempt from T-NO-DATED/T-PATHS (gate queue owns that content). The
view is bounded at 8 numbered items with a generated overflow line, so the map's 120-line cap and
this tooth can never become jointly unsatisfiable.
---
He reviews two things: creative art and quest dialogue, as he plays. Both re-enter
mechanically.
python harness/queue.py intake reads docs/art_boards/MANIFEST.csv and applies the state machine
that docs/proposals/CONCEPT_ART_PROGRAM.md:1780 already defines:
| Row state | Item created | Regeneration state |
|---|---|---|
approval_state=awaiting_pick | loop item, ready: the DIRECTOR's pick, gates boards — RULED 2026-08-04 (the no-veto law, composed with the thirty-third sitting's picks-delegated ruling). It used to mint awaiting_josh + creative_approval, which is precisely the veto item Josh told the loop twice to stop bringing him. Josh comments by exception on the rendered board instead | — |
approval_state=picked, writeback_status=pending | loop item: run the deterministic applier onto the owning registry's art hooks | — |
pick_amendment_a/b non-empty | loop item: regenerate against the amendment, gates boards + canon_derivation, four image critics | approval_state → regenerating, regeneration_count + 1 |
The amendment rides as a note on the realization. The derivation does not move: an amendment is
never a re-decision of the canon node, and treating it as one is how the Flores board went wrong.
The picked → applier item is not bookkeeping — harness/read_boards.py fails a picked board
whose writeback_status stays pending past one commit, so this item is what keeps that gate green.
docs/JOSH_REVIEW_INBOX.md, one comment per line:
- [ ] CH_05 — Ana wouldn't say "obviously", she'd say nothing
intake resolves the target (BRD_dddd → board · CH_NN → chapter, derived from
docs/spine/CH_NN.md · a repo path → that file), attaches the gates and the critic, mints the item,
and ticks the line to - [x] WQ_00NN in place — so intake is idempotent and every line carries the
id that owns it.
**A target the resolver does not recognise becomes a BLOCKED item with reason unresolved_target.
It is never dropped.** A silently dropped Josh comment is the worst failure this file can have, so
the failure mode is loud and parked.
---
the history and makes the next session's baseline a lie.
run_gates.py is red *before* the loop producesanything, the red belongs to someone else's landing. HALT. Do not start an item. Write the
finding as a queue item (work_kind: gate, priority: 0) and stop the session. A loop that builds
on top of an inherited red gate multiplies the debugging surface for whoever fixes it.
queue.py block <ID> --reason "gate <name> red: <tooth>". That counts the attempt, parks the item, and next hands you the following one. The tree stays
green because nothing was committed.
that.
Return the item to ready with the attempt counted (block then unblock, or let recover do it
if the session died outright). Commit nothing partial — a half-authored spine node that lands is
worse than no node, because the next session cannot tell it is half. Then take the next item: most
kinds do not need the tier that was unavailable. Deterministic work — gates, scripts, git, index
rebuilds, baseline re-emissions — never waits on model budget at all, and the queue is deliberately
stocked with some of it so a token-starved session still advances the tree.
A NO-GO is a finding, not a block.
GO_WITH_FIXES → apply the fixes, re-run the critic, land.NO_GO → the item returns to ready with the findings recorded in note and the attempt counted. Fix and re-run. queue.py land refuses the item until the verdict is GO or GO_WITH_FIXES, so a
NO-GO cannot be argued past.
blockedand is split. Escalate to Josh only if the NO-GO is about canon — a §17 risk, a cultural-care
flag at a high-care chapter, or a canonical-substrate gap — which is the immediate-escalation set
docs/CASCADE_RUNBOOK.md §10 already names.
queue.py next exits 4 and says so loudly. An empty queue is a state, never a finish — the
one thing it must never do is look like success. The refill procedure, in order:
1. queue.py intake — the board state machine and the inbox may have items waiting.
2. Un-park what can be un-parked: queue.py list --state blocked, resolve the reasons.
3. Scan the four standing backlog surfaces, each of which is a measured number, not an opinion:
docs/ledger_baseline.json — the orphan / island / reachability ratchet;T0_Region_Index rows against 12 built pages;docs/canon_derivation.json artifacts — every kind not enrolled is a kind nothing checks;docs/TASK_ROUTER.md's enforcement ledger — every work kind whose gate column reads NONE.4. Propose the next N items as a program-doc queue item, run through the four-step protocol,
applied not asked. The loop refilling its own queue is E5 work.
A queue that empties with awaiting_josh items outstanding is reported as exactly that: the loop is
done, Josh is the constraint, and the session says so instead of inventing work.
The loop stops — fully, and says why — in exactly four cases: an inherited red gate (§6.1); every
remaining item is awaiting_josh or blocked (§6.4); a cardinal_canon escalation whose brief
cannot be written without an answer only Josh has; and gate 42 itself firing, which means the queue
is untrustworthy and must be repaired before it is obeyed.
---
queueharness/queue.py --check, enrolled in harness/gates_config.json as queue. Numbering: 37
boards, 38 ledger, 39 canon_derivation are taken; router and orientation landed unnumbered
and hold 40/41 by landing order, so the queue gate takes 42 — which is also the roster count
after this lands.
| Tooth | What becomes unrepresentable |
|---|---|
T-VACUITY | a missing or unparseable queue reading exactly like a clean one |
T-CONTRACT | an item legalizing itself by widening the vocabulary in the file it lives in |
T-ID | duplicate or malformed item ids |
T-STATE · T-NO-DONE | a landed item lingering — the queue rotting into an append-only log |
T-KIND | an item no router entry can produce a read-set for (going in blind, in queue form) |
T-GATE | an item no gate proves |
T-NODE | a node-reading work kind that never says which node |
T-DERIVATION | a node-scoped item with no canon, unresolvable canon, or a docs/proposals/ path standing in for canon |
T-DERIVE-MATCH | an item citing a spine node that is not its own target |
T-AUTHORITY | a loop item dressed as Josh's, or a Josh item with a fifth class or no brief |
T-BRIEF-SHAPE | a bare question — no alternatives / recommendation / objection, or a creative approval routed off boards |
T-JOSH-STATE | authority and state disagreeing |
T-BLOCK-REASON | a blocked item with no reason; an in-flight item with no claimant to recover |
T-CRITIC | authored prose or images with no critic named |
T-ATTEMPTS | the loop head-banging past the cap instead of parking the item |
T-DEPS · T-CYCLE | an item that can never run, or a queue that deadlocks silently |
Eighteen teeth, 50 self-test fixtures (the land-flow, generated-view, and dependency-clear fixtures
joined 2026-08-02), run on every real invocation — a rotted ruler exits 3, never 0. Each
fixture asserts the named tooth fired and that no other tooth fired alongside it; a fixture that
fails for two reasons proves nothing about the tooth it was built to arm. --check also refuses to
mutate a queue that is already red, and re-validates after every mutation, so no command can leave
the queue in a state the gate rejects.
---
and the first several refills will be the loop's own coverage work.
WQ_0003 landed 7eb9fe7b: boards,bibles, and emitted consumption manifests). Gate 42 checks that an item *names* its canon; gate 39
checks that the produced artifact *realizes* it. Every other kind still relies on
docs/SUBAGENT_CONTRACT.md §1 clause 5, which is author-side and uncheckable.
in_flight is a crash marker, not a mutex; twoconcurrent sessions on this tree would both be handed the same item. That matches the standing
one-lane-per-project session model and is a real constraint, not an oversight.