rollout_o0 hardcoded `<ov>_o0b.c` as the whale object and listed SC07_006/007/010/011 as
OUT-OF-SCOPE. They were never out of scope structurally — the suffix letter just drifts:
_o0b across the fleet, _o0c in SC07_010, _o0d in SC07_006/007/011. The invariant that
actually identifies the object is that it ENDS at the whale and therefore includes
shared/func_80144B9C.h.
whale_file_of() resolves on that content, and refuses (never guesses) when two -O0 files in
one overlay match, or none do.
NEGATIVE CONTROL (R39) over the already-succeeding population: across all 134 overlays that
bank through the legacy `_o0b.c` path, content resolution returns EXACTLY that file — 134
agree, 0 disagree. The four SC07 overlays resolve to their _o0c/_o0d objects, and overlays
with no -O0 object at all (ov_MAIN_012, ov_SC02_037, ov_SC03_107 — the U3 carve group) are
still refused with `no-o0b`.
Result: func_801457A4 BANKED in all four, each proven by `make build BINARY=<ov>` matching
config/check.<ov>.sha. 4 functions / 316 instructions, no splat change, no drafting.
Also: build_ok() now takes `.run/auto/gate.<bin>.lock`, the same per-binary lock the gater,
sweep_parallel and the maintenance sweep take. This driver was written to be run by hand
between waves; with six lanes live, two processes in one build/<ov> tree would produce a
verdict about neither.
MEASURED across the harvested notes: 13 citations to sections that do not exist — §2329
(x5), §13446, §13474, §1914, §19189, §2392, §3478 — against a corpus that stops at §273.
Every one is a grep -n LINE NUMBER cited as a section number, and each resolves to a real
section that says what the note claimed: line 2329 is inside §28 (the stranded/pure-extern
class), 13446/13474 inside §164, 1914 inside §20 > §21.
The cost is compounding: the next agent greps "§2329", finds nothing, and re-derives a
lever we already own — and a distill reviewer scores the note "covered by §2329" as covered
by a section nobody ever wrote. Both directions of the flywheel corrupt.
Not a warning in the brief (R33): grep now returns the right answer. Every hit in
matching-cookbook.md is prefixed with its containing heading, nested where the corpus nests
(`§164 > §16Xy | docs/...:13446: ...`), so the number in front of the model is the one to
cite. Index is built once per process and cached by mtime; 555 headings resolve.
Takes effect on the next shard — api_agent is spawned fresh per draft, no restart needed.
Found by the S60 distill reviewers reading 218 harvested notes.
One clean whole-EXE rebuild verified the batch (gate_main), and main re-checked
BYTE-IDENTICAL against config/check.us.sha before anything was credited.
SYS_OBJ_2DD8
_clr
func_8005EA68
A marker is a CLAIM on work, not a record of it. axbm.json sat in .run/distill_ready
for 10.5 hours AFTER its waves were distilled into cookbook §269 — the reviewer landed
the sections, updated the mined state, and never removed the marker — and distill_scan's
one-pending-marker-at-a-time rule (correct, it stopped eight overlapping batches) then
refused to raise anything while 18 waves / 315 novel candidates accumulated behind it.
The marker's own waves are checkable against the mined state, so check them: a marker
whose every wave is already mined clears itself and says so. Negative control (R39): a
marker naming any still-unmined wave survives untouched.
Same family as R47 — a stage that consumes work must also consume the token that
represents it.
.run/maintenance.sh (what runs) and tools/lanes/maintenance.sh (a pre-S59 copy) had
diverged. The 150->50 threshold tune landed on the stale copy and was then copied over
the live one, silently reverting five S59 fixes:
* the R47 shape filter (staging fell back to status=='AGREE' alone — the exact defect
that staged 82 hopeless drafts every 45 minutes)
* the R48 (binary, fn) keying (bare-fn keys collide across overlays)
* reloc --fix MISMATCH auto-repair (measured 4/4 repaired to AGREE)
* rtu_second_chance (re-judges standalone COMPILE-FAILs against the real TU)
* fix_tu_ret_decls (the return-type half of the stale-decl wall)
Rebuilt from the S59 lineage with the 150->50 threshold and the periodic fleet R22
re-applied, both paths now byte-identical, `bash -n` clean, and the two-path hazard
documented in the header so the next edit cannot repeat it.
Also: relaunch_drafter_shell.sh 30s -> 5s ready-marker poll; regenerated backlog and
fleet progress artifacts.
150 was tuned for a lane that only re-swept an unchanged sibling pool and banked
nothing. The lane now consumes every verdict layer in the A-prop pipeline, carries the
free pre-gate reject recovery, and runs the periodic fleet R22 — a pass is worth
running on a much smaller refill.
Two binaries sat RED for hours today and nothing noticed: ov_SC07_010 from a
maintenance commit whose final tree state was provably never built, and ov_SC07_002
from a stale 2-table jtbl pad spec written at wave bp. Every lane only ever checks the
binary it is currently touching, so a byte-gate — a correctness oracle — was silent
about everything it did not build. They were found by accident, by an agent's scoped
R22 sweeping 141 binaries.
Every 4th maintenance pass (~3h), skipped while any gate is in flight (check-all
rebuilds stale objects and must not race a gate), it runs the fleet check and writes
any REDs to .run/fleet_red.txt with a loud log line. It FIXES NOTHING: a wrong repair
to a pad spec or a config is exactly how a silent byte shift gets committed, and the
two we fixed today each needed a different, evidence-led remedy.
OpenRouter returns a provider throttle as HTTP 200 whose body has no 'choices' and an
error of {"message": "Provider returned error", "code": 429}. That never reached the
429 handler, which keys on HTTPError, so it fell through to the 'no choices' raise and
ended the agent at turn 1 with no draft, no submit, $0.00 spent.
Measured, and it is not marginal:
wave cb: 169 of 260 shards hit a soft 429
wave cc: 115 of 260
wave cd: 187 of 260 <- 260 shards 'finished cleanly', 72 drafts produced
wave ce: 119 of 258
That is the draft-completion collapse. I had attributed 28-60% completion (against
84-89% before) to the straggler grace and raised it to 700s; the grace was never the
cause. The shard logs said 'finished cleanly' because the agent DID exit normally —
after being killed by an unretried rate limit on its first API call.
Now treated like every other transient: back off, retry, and log it as SOFT-BODY so
the rate telemetry stops under-counting 429s. Takes effect on the next wave's shards —
api_agent is spawned fresh per shard, so no lane restart is needed.
R40 again: the fleet looked like it was giving up, and the harness was hanging up on it.
Measured 23:20: the maintenance lane held .run/auto/draw.lock for a multi-minute
sweep, the pre-draw buffer happened to be empty, and the drafting fleet — the one
clock-limited resource — sat at 13 agents and 3 req/min printing 'gate holds the draw
lock and nothing is pre-drawn — waiting 30s' every thirty seconds.
That wait dates from when drawing during a gate was genuinely unsafe: corpus.stubs()
misreports for a binary whose sources carry a substituted draft (R35), so the draw
refused outright. Since 15:23 build_wave_atlas excludes exactly the binaries whose
per-binary gate lock is held and draws from the rest, so the hazard is handled at the
right granularity and the blanket wait now protects nothing.
Same lesson as the draw's own refusal earlier today: a guard scoped more broadly than
the hazard gets routed around or, worse, quietly starves the thing it sits in front of.
1. decl_for searched the DESTINATION for the SEED's name: a RENAMED symbol's
destination spelling can only exist under the MEMBER's name, so the
destination preference silently never applied to renamed data symbols
(D_801B9DF8 adopted the seed's 'short' against the TU's file-scope 's32').
dest/fleet tiers now search target_sym; seed tiers keep the seed name.
2. dest_scope is FILE-SCOPE-ONLY (brace-masked): the TUs are full of
block-scope externs inside banked bodies, and a flat regex adopted one of
those as 'the destination spelling' over the DEFINE macro's true file-scope
decl.
3. A body-embedded block extern that diverges from the destination file scope
is rewritten to the destination spelling when every use is ADDRESS-ONLY
(type is codegen-irrelevant for &sym); valued uses keep the seed spelling.
+ fix_tu_ret_decls.draft_ret now parses K&R definitions (its first run SKIPped
30 of 32 because the param decls sit between ')' and '{').
reloc_identity --fix's rowmap keyed rows by fn alone, so three same-named
func_8013BCDC rows across binaries all received ONE binary's draft — 2 of 4
'no textual occurrence' refusals were the tool editing the wrong file. Keyed
by (binary, fn): 4/4 repaired to AGREE/MATCH (byte-checked by re-run).
maintenance.sh: stage on status in {AGREE,UNRESOLVED} AND shape==MATCH keyed
(binary,fn); run reloc --fix on MISMATCH rows and re-check so repairs stage the
same pass; add rtu_second_chance for standalone COMPILE-FAILs (7/27 measured
TU-byte-MATCH, previously dropped unjudged).
aprop_autodraft decl_for gains the BORROW tier: when both home TU and seed are
silent, adopt a sibling TU's extern spelling under the same body-compatibility
guards — 'which TU may conflict' (home only) and 'where a guess may come from'
(anywhere) are different questions; refusing outright left 125 members undrafted.
Baseline measured on the 21:04 pass (117 staged, 0 banked): 82 near real-diff /
19 standalone compile-fail / 16 near-0 TU-integration; zero drafts invisible to
the gate (the 'drafts: 0' probe was the triage harness racing itself — a shared
per-fn probe dir rm -rf'd by a concurrent triage run, not lane plumbing).
1. K&R definitions (aprop_autodraft.kr_definition): the near-0 class is the TU's
own '(void)' decls + empty K&R call sites rejecting the draft's ANSI def
('too few arguments' after the arity pre-pass relaxes the decls). K&R the def
when every param is promotion-safe; byte-proven MATCH on func_80162CCC's real
TU (rtu_match) where the ANSI form CC1-failed.
2. decl_for scope: the destination is the HOME TU ONLY, macro-expanded
(dest_scope) — the whole-binary concat adopted spellings the home TU never
declares; and a dest spelling is adopted only when the seed body can compile
against it (void-return-value guard + call-arity guard; measured 42 and 15
fresh drafts died on each before the guards).
3. Ordinal IMM pairing is now a CANDIDATE SET (family_remap._ordinal_candidates):
the single-guess form paired the first spelling's occurrences only and shipped
35 of 117 drafts with value/offset swapped ('*(p+3)=2' for '*(p+2)=3',
closeness-2 forever); candidates are adjudicated with match_one at draft time.
4. Staging filter (maintenance.sh): stage only AGREE + shape==MATCH, keyed by
(binary, fn) — status-only staging burned 82 whole-binary builds per pass on
drafts match_one had already refuted, every 45 minutes.
+ tools/rtu_second_chance.py: standalone COMPILE-FAIL is the wrong oracle for a
TU-destined draft; re-judge those against the real TU (rtu_match) and stage
the MATCHes.
MY BUG, SAME DAY, SAME CLASS. recover_rejects staged by the binary field of
aprop_symfix's output slate — and aprop_symfix tags its REBASE VARIANTS into that
field (ov_MAIN_012-cn, -cn-cast, -cn-cast-rc, -s2in, -s2in-uni). Staged verbatim,
each variant became its own directory and sweep_parallel was handed 273 directories
naming binaries THAT DO NOT EXIST: 553 drafts that could never be gated against
anything, while the sweep reported 'over 331 binaries'. It now resolves the variant
tag back to a real binary, keeps ONE variant per target, and COUNTS what it cannot
resolve (R43). The 290 bogus staging dirs are cleaned; 118 real drafts remain.
aprop_autodraft, per Drew:
* decl_for prefers the DESTINATION TU's own spelling and falls back to the seed's
only when the destination is silent (wave law 2 — the destination is
authoritative; 45 of 188 staged drafts declared a conflicting type).
* it REFUSES a seed whose body is a verbatim __asm__ block. Those transcribe
instructions rather than decompiling them, trivially 'MATCH' the local oracle
because they ARE the target's bytes, and would count as matched functions in every
progress number while nothing was decompiled. 26 of 188 were this shape. §265 is a
deliberate human escape hatch for hand-written asm, not something an unattended
lane propagates across a family.
Measured before changing the drafting prompt instead: model waves bw/bx/by/bz produced
ZERO whole-body asm across 530 drafts, so this is a tool behaviour, not a model one. A
blanket 'no asm' rule on every card would have cost us the sanctioned one-line levers
(§5a fences, §17 register pins) that appear in 32-47 drafts per wave.
PROVEN: from 14:57:01 to 18:43:23 today HEAD built main to 307aa45d… against the
expected 143dbb89…, with NO draft substituted (measured under gate.main.lock, no
gate_main alive). Auto-commit commit:2693 had adopted a mid-flight gate_main
substitution — its carve-out reverted main's TUs, gate_main re-wrote them, and
`git add -A src/` swept the unverified bodies in (a TOCTOU race, 14 s after a
bisect chunk banked). Every main batch after it was doomed before its first
draft was judged: m00–m03 card cycles drafted ~737, slated 160, banked 0, and
burned ~50 clean rebuilds bisecting innocent slates. commit:2712 restored the
green content by accident (it swept this investigation's diagnostic checkout).
gate_main: on any batch failure, ONE try_batch([]) control runs first — if HEAD
itself is red it prints BASELINE RED, leaves the slate reusable, exits 3 (R40).
clean_build no longer reports a linked-but-mismatched build as "no binary" (the
build target embeds the SHA check), the compile-conflict shortcut fires only on
error-shaped lines naming a symbol some draft in the slate actually uses (the
baseline's own func_800143AC implicit-decl WARNING was matching — every m04
chunk died with "drafts declaring it: []"), reverts narrow to top-level src/*.c
(main_tus) so a main gate can never destroy overlay lanes' in-flight work, and
--assert-baseline is a first-class mode.
main_lane: every cycle opens with gate_main --assert-baseline and REFUSES to
draft or gate against a red baseline (R43) — BaselineRed parks nothing, burns
no tries, writes .run/main_lane.BASELINE_RED, re-checks every 30 min.
Adopters (ox_campaign ×3, maintenance.sh, gate_stage, gate_lane, idiom_serial):
main's TUs (top-level src/*.c) are never staged and never reverted by an
overlay/maintenance lane — one writer (gate_main), one committer (main_lane,
after the whole-EXE SHA re-checks green). Unstage-after-add is race-free where
the old revert-then-add was the losing half of the TOCTOU.
Diagnosis, evidence and the full timeline: docs/tool-designs/main-lane-fix-s59.md
STATUS BLINDNESS (Drew): status checks kept reporting the overlay drafter and the
gater — the lanes whose logs scroll — while the main, maintenance and distill lanes
went unmentioned for hours. A lane you do not report is a lane you do not notice
failing: the main lane spent an afternoon on an old config and bisected a whole batch
to zero banks without that ever reaching a status line. tools/campaign_status.py
prints every lane with ITS OWN metrics, read from artefacts rather than memory.
STRAGGLER GRACE 120 -> 700, tied to HTTP_TIMEOUT so they cannot drift. collect_drafts
queues a wave once 95% of shards finish, then waits this long for the rest — and 120s
is shorter than a single turn (~530s for a 16k generation at ~30 tok/s). So raising
the token budget converted truncated turns into agents guillotined mid-thought with NO
draft: overlay draft completion fell from 84-89% at 8k to 41% (bt) and 69% (bu).
DISTILL DEDUPE: each pass re-offers everything unmined, so the lane wrote a fresh
overlapping marker every five minutes — eight queued, each a superset of the last, and
a reviewer cannot tell which one is the work. One pending marker at a time.
Same problem as the drafter: an env/arg change (MAXTOK, HTTP_TIMEOUT) only reaches a
fresh shell, and the main lane is usually either drafting or gating. This waits for
the one safe window — no main-lane agents alive and no gate_main running, i.e.
between the gate and the next draw — then restarts. Mid-draft would discard drafted
work; mid-gate would abort a batch (safe, since gate_main reverts its own
substitution, but wasteful).
Probed ox-alpha directly on a real MIPS derivation:
no reasoning cap 265.2s finish=stop completion=8,067 reasoning=0 30 tok/s
reasoning cap 2000 22.3s finish=stop completion= 672 reasoning=0
reasoning cap 6000 41.4s finish=stop completion= 618 reasoning=0
Three findings. (1) ox reports reasoning_tokens=0 — its thinking is IN the content
stream, so the output cap was capping the reasoning; that is exactly why turns ended
in 'no tool call (finish=length)'. (2) The uncapped hard prompt wanted 8,067 tokens —
it was finishing precisely where the old 8k cap cut it off. (3) It generates at ~30
tok/s, not the ~54 I estimated from turn gaps, so a full 16k generation needs ~530s
and the 420s socket would have killed the very turns the bigger budget exists to
allow. A timeout wastes the whole turn; truncation at least leaves a partial.
HTTP_TIMEOUT=700 on both drafting lanes. The ordering that must hold is generation <
HTTP_TIMEOUT (700) < stallguard's wedged-agent kill (1200s). 420 was itself deliberate
— 1800 once parked a hung agent for thirty minutes — and 700 keeps a hang under 12
minutes without strangling legitimate deep reasoning.
Also recorded: a reasoning cap DOES work on ox, but it shortens the ANSWER too (618-672
total tokens), so it is a quality knob, not a fix for truncation.
THE OUTPUT CAP WAS EATING THE TURN BUDGET. Wave bk's shard logs: 240 of 244
turn-finishes were 'no tool call (finish=length) — NUDGE n/6'. The model was
exhausting its 8,000-token output budget BEFORE emitting a tool call, so the turn did
no work; an agent gets six nudges before giving up. That is why MATCHes average 2.8
oracle calls against a 24-turn budget — the turns are going to truncation, not
iteration. ox is free, so a bigger output budget costs latency and nothing else.
Measured alongside it, and worth recording because it redirects the obvious fix: turn
caps are NOT binding on the default lane. Across 1,166 agent completions, non-MATCH
runs used a median of 4 oracle calls and a p90 of 12, and exactly 1 of 194 reached 20
of the 24 available. Agents are not running out of turns; they are giving up early
after truncated turns. (The tells lane WAS cap-bound — 98 of 270 — which is why it
already has 40 turns.)
Plus tools/recover_rejects.py, wired into the maintenance lane: rebase the pre-gate
rejects whose body already matches and only the symbols are wrong (§171), stage them
for the lane's existing free gate. Zero model tokens; it only stages, so a bad
recovery can waste a build but never a bank.
Everything that reaches the GATE and fails gets a backlog row with closeness, class
and best draft. A draft the reloc pre-filter drops never reaches the gate, so it was
recorded nowhere and just sat on disk: 569 of 1,261 drafts across the last eight waves
— 45%.
They are not all garbage. 13% of the MISMATCH? rejects have a body that ALREADY
MATCHES and only the symbol names wrong, which is the deterministic aprop_symfix
stale-symbol class that banked 4 of 4 earlier this session. Roughly 6 recoverable
drafts per wave were being thrown away because no index existed to find them.
Now appended to .run/reloc_rejects.jsonl with the verdict, the shape (MATCH here means
right body, wrong symbols) and the first mismatches, so a recovery pass can work them
without re-drafting. Wrapped so telemetry can never break a gate.
The tells slot became redundant when build_wave_atlas started reserving 60 tell-lever
cards inside every ordinary wave: a dedicated tells wave draws 70-87 cards, a quarter
of a default wave, for a full 40-minute slot.
The 120-2000 slot is worse than redundant. Bank rate by size, measured: 57% under 50
instructions, 30% at 50-80, 22% at 80-120, 3% at 120-200, 6% above. Wave br drew 69
cards on that band — roughly 3 banks for a slot that a full-band wave turns into ~150.
Large functions are not abandoned: the full band contains them and the draw takes
mass-first within each gate group.
Takes effect at the next wave boundary via relaunch_drafter_shell.sh.
corpus.stubs() misreports for a binary while a gate has draft bodies substituted into
its sources (R35) — but only for THAT binary. The blanket refusal cost far more than
it saved: the gater runs almost continuously, so nearly every fresh draw was refused
and the drafter fell back to PRE-DRAWN waves. Measured at 15:20 — wave br refused,
wave bj (drawn hours earlier) drafted instead.
That is worse than idle time: a pre-drawn wave carries the OLD draw-time defaults, so
every feature landed today — the tells quota, the jtbl quota, the -O0 filter, the
oversize filter — was silently not reaching the fleet, while the logs showed healthy
410-shard waves.
Which binaries are mid-gate is not a guess: gate_stage and gate_main hold
.run/auto/gate.<bin>.lock for exactly that window, so a non-blocking test-lock answers
it per binary. Those are added to EXCLUDE for that draw; the refusal survives only for
--only-bins draws where every requested binary is busy.
Verified live against a running gate: the draw that would have been refused now
returns 40 cards.
A bisect can run many levels; deferring the commit left byte-proven functions sitting
uncommitted in src/ for the whole descent — precisely the window in which any other
tool's blind revert destroys them (61 banked functions died that way once). The tree
is verified byte-identical on the line where the credit is granted; that is both when
it is safe to commit and when it must be.
Three defects found by running it, all of the same family — a check that is true about
the wrong thing:
1. FALSE BANKS. The first accounting asked corpus.stubs('main') whether each name was
still a stub; that returns {addr: Stub(symbol=...)} — a dict keyed by INT. Comparing
a NAME against a set of ints is always True, so the lane reported '12 banked of 12'
from a gate that banked nothing and committed nothing. Credit now requires BOTH the
INCLUDE_ASM line gone from the working tree AND main re-checked byte-identical.
2. INNOCENT DRAFTS DYING WITH A DECL CLASH. gate_main deliberately refuses to bisect a
COMPILE conflict (right for a human caller, wrong for an unattended lane): the first
live batch hit a conflict on a symbol that was in the TU and in NO draft, so there
was nothing to drop and 40 innocent drafts died with it. The lane now halves the
slate — a main rebuild measures ~15 s, so bisecting is cheaper than discarding.
3. THE DRAW GUARD REFUSED EVERYTHING. build_wave_atlas refused to draw whenever any
gate was in flight (R35: corpus.stubs misreports mid-gate) — but only for the
binaries being gated. Main's sources are touched by gate_main alone, so a main-only
draw now watches gate_main and every other draw keeps the blanket refusal.
Failed drafts are parked with a try count instead of discarded (a failed draft is
evidence), capped at 2 so an unbankable body cannot spin the lane.
Live: 13 main functions banked, main byte-identical at 143dbb89, stubs 1713 -> 1700.
main is excluded from every wave draw for a good reason — its gate is a clean
whole-EXE rebuild that bisects, and on the overlay critical path it cost three
measured stalls (39 min unfinished on 29 drafts, 25 on 8, 65+ on 8). The consequence
was that main sat outside the loop entirely: 1,713 open stubs, no lane, no cadence,
while the overlay lane ran at ~a quarter of the API ceiling because CARD SUPPLY, not
throughput, is its constraint. Two populations, one idle half of a rate limit.
draw (main only) -> draft -> reloc pre-filter -> ONE gate_main batch -> commit.
Never sweep_parallel or gate_stage (both build incrementally; main's extract rewrites
the linker script, so an incremental build yields a FALSE diff — that is what banked
0 of 105 main cards in wave ab). Batches because one clean rebuild verifies the whole
slate; the reloc pre-filter is what keeps a batch from bisecting. Commits the moment a
batch is green (R42) since gate_main deliberately does not.
Parked drafts first: 170 main drafts sit in .run/main_queue from before the exclusion —
already drafted, never gated, free.
TELLS QUOTA (Drew approved): a dedicated tells wave drew only 70-87 cards — a full
40-minute drafting slot at a quarter of a default wave — because the 5-80 size cap and
the tells pool cannot fill more. Tells now ride inside ordinary waves with a 60-card
quota, same as jtbl. The size cap moved into the draw itself: tell-lever members above
--tells-max-ins (80) are not drawn at all, because the measured bank rate is 27-40%
at 5-80, 10% at 81-120, 1% at 121-200 and 0% above — those 383 members / 51,941 ins
are idiom_serial's work, and the skip counter names it (R45).
A QUOTA IS A FLOOR UNLESS IT IS ALSO A CEILING. First test: putting the tell levers in
the default list let them win the ranked fill too, and a 300-card wave came back 122
tells (41%). The size cap held; the mix did not. Tells now enter through the quota or
not at all.
GATE JOBS 24. The quotas deliberately pull cards from binaries outside the ranked gate
groups, so a measured draw went from ~24 groups to 63 — 63 whole-binary rebuilds per
wave, five serial batches at 12 jobs. The box is 32 cores at ~6% (load 3.1) with 39 GB
free. Lands via restart_gater_when_idle.sh so no sweep is killed mid-flight.
Correction to the record: the live draw already passed --max-bins 24 (plus
--one-per-gid and --exclude-bins main). An earlier measurement of mine used the tool's
default of 12 without those flags and read as 'max-bins is the cap' — it is not;
--one-per-gid is, and deliberately: it defers same-skeleton siblings to the free
deterministic remap instead of paying an agent twice.
Balance $2.56 and falling ~$1.43/h over the last three waves ($4.56 at 11:54 ->
$2.56 at 13:18) — about 23 minutes from --credit-floor 2.0. That floor does NOT pause
the paid lane: it breaks the whole drafting loop, and the shell then restarts a python
that breaks again, so the clock-limited resource dies on a check about money.
ox-alpha is free for the rest of this window, so drafting continues on ox alone at zero
burn, and the floor drops to 0.25 because with a free model the balance stops being a
proxy for 'can we draft'. deepseek was 280 of 2,000 workers — its value was an
independent 429 ceiling, not throughput.
Takes effect at the next wave boundary via relaunch_drafter_shell.sh, so wave bp's
in-flight drafts are not lost. Restoring it after a top-up is two edits, named in the
script's header.
Drew, 2026-08-24: distillation is judgement over an existing corpus (read harvested
notes, decide covered / addendum / new against 760+ sections), not a new wall class.
Fable is for the walls — an unsolved tooling problem, an adversarial design review, a
residual no documented lever reaches. I routed a distill batch to Fable; recorded here
so the next session reads the tier off the lane rather than guessing it.
A re-gated wave rewrites its candidate file with NEW rows under the SAME tag, so
"have I seen this tag" answers the wrong question. Two instances in one hour:
* wave `at` was re-gated hours after its first harvest, so an mtime-keyed seed
called it new and 52 mostly-re-derived rows went to a reviewer;
* my own hand-edit of the state folded "queued for review" into "reviewed", which
marked `ax` and `bm` — 82 candidates, gated minutes earlier — as mined by nobody.
Caught only because their files were newer than the edit.
The scan now compares COUNTS: a tag re-opens the moment its file grows past what was
mined from it. Extracted to tools/distill_scan.py so the logic is testable rather than
living inside a heredoc inside a lane loop (the heredoc-in-heredoc edit is also what
produced a syntax-broken lane script a minute earlier).
Verified: the lane now raises exactly the true pending batch — ax + bm, 82 novel.
Two discovery-gap defects the distill review measured, both costing drafters real
compiles:
1. LINE NUMBERS CITED AS SECTIONS. Index rows end with a <sub>L1234</sub> anchor = the
section's line in the cookbook. Drafters read it as a section id and cite it: this
batch alone carries §1907, §12479, §2965, §11383, §8892, §5583, §1832, §2429,
§1755, §2609 — line numbers, every one, and a grep for any of them returns nothing.
The number is real and the reading is wrong, which is the worst kind of dead end
because it looks like a citation. The index now publishes an L→§ table, and
answers it directly (verified: §12479 → §3-The,
§1907 → §21).
2. THE INDEX WAS NEVER NAMED AS THE ENTRY POINT. One drafter wrote 'no numbered
section I could find by grep' about a lever whose section title literally contains
the words it searched — it grepped the 25k-line cookbook, not the symptom-keyed
index. api_agent's SYS now says to start at the index, and how to resolve a
line-number citation.
The harness re-invokes the main loop when a background command exits, so a blocking
wait IS the self-reminder; a sleep-then-tail poll loop would just burn tokens for the
same information.
The gater already harvests every wave before the next draw (Drew's 2026-08-23 rule),
but that is EXTRACTION: it writes .run/idiom_candidates.<tag>.md and stops. What
changes the next wave's behaviour is the COOKBOOK, because that is what the drafting
agents grep — and distillation was batched per session, so the ore piled up: 165
novel candidates across 4 waves within three hours of the last cookbook update.
The lane does the zero-token half — watch, count novel rows, and raise a READY marker
naming the waves when a batch is worth a reviewer's turn (>=30 candidates or >=2
waves). It writes no cookbook, no src/, no config/: a bad harvest cannot pollute the
knowledge base on its own, and landing stays a reviewed step (us + a subagent).
It never blocks a draw. Wave N's ore is distilled while wave N+1 drafts, so wave N+2
is the first that can grep it — stopping the drafter to think cost 139 of 162 idle
minutes on 2026-08-23.
State seeded so it does not re-mine what S58 already landed as §233-§259: the "done"
list holds the 17 candidate files written before commit commit:2637.
Adding the lever alone was inert: a jtbl card is one-per-binary by construction, so
jtbl cards are maximally UN-concentrated, and the gate-group ranking exists precisely
to pack many drafts behind one rebuild. Measured — a mixed draw offered 71 jtbl
candidates and selected ZERO. It would have done that every wave, forever, while
looking enabled.
--jtbl-quota (default 6) seeds the wave with the largest eligible jtbl cards before
the ranked fill, so the lane advances inside ordinary waves instead of needing a
dedicated one (a jtbl-only draw is ~12 cards and would idle a 2,000-agent fleet).
Cost is exactly 6 extra whole-binary rebuilds per wave.
Verified on a live-shaped draw: 6 jtbl-carve cards in a 200-card wave, 13,470 ins
across 7 gate groups (was 11,451 ins in 1 group), with the probe filter refusing what
the gate cannot reach — main 29, island-blocked 10, island-pads 6, one-per-binary 71.
Its entire code subseg is the -O0 run and the .c is stub-only, so this is the boot
precedent: a whole-object CC1FLAGS override, no splat change, no carve, and none of
the 18-P29 re-disassembly risk. Proven byte-neutral by a CLEAN per-binary rebuild —
build dirs deleted, re-extract, rebuild:
sha1 80731bac0ddd6b3e354f43b2c179582b12590752 == config/check.md_MAIN_011.sha
Landed with the coupling fix it requires, or the 21 would have stayed invisible.
Three tools decided -O0-ness from the subseg NAME ('_o0' in it, or 'boot'); this
object keeps its plain name, so match_one would have warned 'cannot bank' about
functions that now bank and the wave draw would have kept refusing to draw them. All
three now ask corpus.o0_subseg(), which derives the answer from the Makefile itself
(R33: a name is a convention, the Makefile is ground truth).
Verified end to end: match_one compiles md_MAIN_011 targets at -O0 with the
cannot-bank warning correctly gone, the wave draw emits cards for them, and
test_o0_detect still passes 167/167 coverage with 0 false positives.
Three measured harness defects, all fixed:
1. THE CARD NAMED A WORD THE COOKBOOK DOES NOT CONTAIN. api_agent stated the lever
as a bare label and nothing else; grep 'extend-tell' / 'swaprepeat' / 's16-div-tell'
over the 750-section cookbook returns ZERO. 108 failure transcripts grepped
extend-tell and 28 grepped swaprepeat against nothing while the knowledge sat at
172a/172b under different words. Fixed both ends: a LANE ALIASES grep-bait block at
172b, and LEVER_CRIB on the card — what the tell means, the section to grep, and
the byte-proven C spellings.
2. ONE GLOBAL AGENT BUDGET FOR CARDS OF VERY DIFFERENT SIZE. tells cards are 2.4x the
default lane's (median 89-95 ins vs 37-39) and stack 3-5 idioms; 98 of 270 final
attempts ended AT the 24-turn cap. LANE_BUDGET gives tells 40 turns / /bin/bash.40, and
logs the choice so it is auditable rather than invisible.
3. Two new SYS laws: grep the section your crib cites before drafting, and stop when
the residual class says [permuter]; plus 263 (an invented argument changes
scheduling — check arity before reaching for a fence the permuter cannot help with).
Cookbook 264 records the four recipes the tells agent drove to MATCH: the inline (s16)
in a call argument, the save-order/bb0 anti-dependence law (new), the opaque-bound
local assigned late (new), and 172b-1's multi-def mirror variable made concrete.
The carve stays INSIDE the byte-gate (harvest_verify._jtbl_prep_one, the §61b-proven
order); everything new routes work to it:
* jtbl_carve: island_probe (read-only classifier: tail/covered/island-end/island-blocked/
island-pads/main-manual), --island-split (the one-line §260 insert, end-adjacent only),
and apply() now recognizes a completed island split as a no-op success instead of the
historical refusal.
* harvest_verify: _ISLAND_WALLS branch — on the §154-A refusal the gate isolates (body
still spliced), inserts the split line, re-extracts, re-carves. Snapshot-restore covers it.
* jtbl_lane.py (new): probe → draft (--draft-dir or api_agent) → gate via the exact
sweep_parallel worker call, HOLDING the campaign draw lock across gate+commit → commit
named per-binary paths. One jtbl target per gate invocation (§61c).
* build_wave_atlas: probe filter + one-jtbl-card-per-binary cap (inert unless
--levers jtbl-carve). idiom_serial: refuses the jtbl-carve lever (R43; its pre-carve ran
the refuted order — S58: 8 attempts, 0 banks).
Proven end to end with the live campaign running (banks committed separately):
commit:2661 ov_SC03_014/func_8017DCC0 tail: §8b adjacent merge + §8e pad recovery,
jr_8017AE2C.o .rodata 0x14→0x28 TIGHT, sha d84b01a2 green
commit:2663 md_SC03_076/func_801F218C covered: §260 STAGE 2 — sha 9a165e36 identical
with the table COMPILER-EMITTED; the first md_* jr bank ever
commit:2664 md_SC03_135/func_801E5358 island-end: the FULL split done BY THE GATE on a
virgin module (~1.1s, R40-checked), sha b901fda5, md.o 0x27c→0x268 + jr.o 0x14
Census (245/245 members probed, R32): 181 members / 26,445 ins reachable unattended;
main 47 parked (gate_main cadence), island-blocked 10 (stack order), island-pads 6
(needs §8e pads for modules.mk), no-jtbl 1 (atlas mislabel).
Design + failure semantics + campaign hook: docs/tool-designs/jtbl-automation-s59.md;
cookbook §260-A.
The build decides the opt level per OBJECT and the Makefile's -O0 rules cover boot,
ov_SC01_077_o0 and src/ov_*/ov_*_o0?.c — nothing else, src/md_*/ least of all. A
function whose target bytes carry the -O0 frame-pointer prologue while its subseg is
compiled -O2 is unbankable however good the draft (R43, cookbook 261).
Measured before the filter existed: 11 such functions had been drawn 79 times across
19 waves — 6 per wave recently — under head-crack/UNKNOWN/tells/len-vein/redraft
labels, because the atlas o0-lane label catches only a fraction of the real -O0
population. Not one could ever have banked. A live draw now reports
'o0-in-an-O2-object': 54 in the skip census.
The first version of the helper was itself the defect it exists to prevent: it took
corpus.asm_path() to be a directory when it is the .s FILE path, so every open()
raised, it returned False for every card, and it filtered nothing while appearing to
run. Its unit test passed because the test fed it the directory the code expected
instead of the value the caller passes.
The ready queue is two-sided — the gater consumes markers while the drafter produces
them — so 'the count went up' is not 'a wave queued'. The first version latched
BEFORE=1, the gater consumed that marker, and the next wave queuing took the count
back to 1, which is not greater than 1: it would have waited out its full 90-minute
deadline while the event it waits for happened. True about a number, false about the
world.
jtbl and o0 are held out of the default lane because a draft cannot BANK without
tooling first (a rodata carve; an -O0 object, and no Makefile glob matches md_* at
all). 'cc1' is not that: it records that some earlier draft failed to compile. A
compiling draft banks through the normal splice->build->SHA path regardless of its
history, and a non-compiling one fails fast at cc1 for the price of one build.
Holding 14 groups / 105 instances / 6,511 ins out of every wave bought nothing.
Effective on the next wave draw (build_wave_atlas is a fresh subprocess per draw —
no lane restart needed).
--o0 has existed since P29 and NOTHING ever passed it: api_draft.match_one(), the
oracle every wave agent iterates against, builds a fixed argv without it. Every agent
handed an -O0 target was shown an -O2 compile of its own C and a mismatch on every
instruction — feedback that cannot converge, for a reason invisible in the diff, and
it hit even the 51 functions already sitting in -O0 objects.
match_one now decides from the target (R33), with two oracles because neither alone
is sufficient (R34): the -O0 frame-pointer prologue in the target's own bytes, OR the
subseg being an object the Makefile compiles -O0. boot/start.s is built -O0 with no
ordinary prologue; an -O0 function stranded in an -O2 subseg has the prologue but
cannot bank — and match_one now says so instead of letting an agent chase it.
The prologue scan is anchored at glabel, not the top of the file: two md_MAIN_011
.s files open with a migrated jump table / .asciz blob and a naive scan read table
words as the prologue.
tools/test_o0_detect.py: 14,400 .s scanned, 167/167 signature files covered, 0 false
positives outside -O0 objects, both rodata-first files detected.
Census correction in the design doc: the handoff's refutation of md_MAIN_003/011 is
itself wrong (16 and 21 -O0 functions, byte-verified), and the 311 '$fp mentions'
figure over-counts because $fp is $s8, allocatable at -O2. True population: 167
files, of which 116 / 14,148 ins are -O0 functions the build compiles -O2 — double
the atlas estimate, with x2/x3/x4 sibling replication.
Joined the campaign ledger to each wave's cards and to the functions its own commit
banked (removed INCLUDE_ASM lines), pooled over bb/bg (tells) vs bc/bf (default):
nins default tells
0-50 303/528 57% 27/ 67 40%
50-80 43/145 30% 20/ 73 27%
80-120 9/ 41 22% 10/100 10%
120-200 1/ 30 3% 1/ 68 1%
200+ 2/ 35 6% 0/ 30 0%
At equal size the lanes are close below 80 instructions and both collapse above it.
What separated them is the card size MIX: default's cards are median 37-39 ins, the
tells pool median 89-95 (2.4x), so 'the tells lane is broken' measured the population,
not the lever. Tells now draws 5-80.
This also refutes the S58 handoff's one live hypothesis for tells (235, the phantom
symbol). Checked the recorded reloc_identity verdicts first (R38): among MISMATCH?
rows, the fraction whose instruction SHAPE already matched — the symbol-only class 235
describes — is 25/87, 16/79, 17/66, 13/57 on default waves but 6/66, 4/81, 5/47 on
tells. Tells drafts fail because the BODY is wrong, not the symbols, which is what a
2.4x larger median predicts.
Also adds tools/lanes/relaunch_drafter_shell.sh. bash parses a while...done body in
full before running it, so a lane-ARG change is invisible to the running shell and a
python bounce re-runs the OLD command line — measured at 11:02, when the bounced
python came back on the pre-S59 lane list 18 minutes after the file changed.
jr_inventory asserts every committed .rodata piece resolves to exactly one banked
owner (R32) — true for ov_*, where every .rodata piece was created by jtbl_carve.
On md_* the 154-A leading island - [0x0, .rodata, <alias>] is the module's own
rodata blob (INCLUDE_RODATA data + every stub's still-migrated table), has no single
owner, and made the check abort: md_SC03_076 reported UNOWNED 0x801ef468 and could
not be isolated at all.
rodata_carves() now takes the alias and skips that one piece. The discriminator was
verified across all 213 splat configs: a .rodata piece at offset 0 whose subseg is
the binary's own alias exists in exactly the 42 md_* configs and in none of the
others, so this is a provable no-op for every ov_*/main config.
md_SC03_076 --only func_801F218C now plans cleanly: 1 jr, 2 region .c files, no
carve repoints.
Ships a lane-arg change without losing drafts: the running python still carries the
pre-S59 args, and killing it mid-wave discards everything the fleet has drafted for
that wave. Waits for .run/ready/<wave>.json, then pkills only the python — the
drafter SHELL, the lane that must never stop, relaunches it from .run/drafter.sh.
90-minute deadline so it never lingers.
main is the one binary splat writes to asm/nonmatchings/800/ and asm/data/ rather
than asm/<alias>/...; the hardcoded asm/<ov>/ in nine places made every main lookup
miss a directory that does not exist and report it as 'already spliced AND no stale
copy' — a true-sounding message about the wrong tree (R43).
With the path, the base and parse_config all fixed, main now reaches the real
analysis: func_8001A114 refuses because subseg 800 would host non-contiguous
.rodata carves (0x63238 = the existing LZSS table, 0x6327c = the new one), i.e.
isolate first — the same architectural refusal an overlay gets.
The island guard also moved AHEAD of jtbl_range, so an island table no longer
reports 'not found in the raw data asm — already carved / stale asm?' (true and
useless: an island table was never in the data asm). It now prints the exact line
the review prescribes: - [0x268, .rodata, md_SC03_076_jr_801F218C].
Review doc gets an S59 addendum with the measured class facts and where the two
examples now stop.
parse_config's docstring said 'the trailing run of {data,.rodata} pieces after
the last c piece'; its code took data_pieces[0], the first such piece anywhere in
the file. Those agree on 171 configs and disagree on 42: the md_* modules open
with the 154-A leading island - [0x0, .rodata, md_XXX] BEFORE their c piece, so
apply()'s splice lines[:lo] + region + lines[hi:] deleted the c line and wrote the
yaml to disk before the tool errored out for unrelated reasons.
Fixed by deriving the region from the last c piece, plus an R43 guard that refuses
outright if any c piece lands inside the window apply() rewrites wholesale.
Also, correcting what S58's blanket refusal had lumped together:
* md_* (42 configs) was the whole corruption class.
* main was never in it - its pieces are already [all c ..., data, .rodata, data].
main's real defects were the config PATH (there is no config/splat.main.yaml,
it is splat.us.exe.yaml) and the FILE BASE: the EXE has a 0x800 header, so the
delta is 0x8000F800, not the yaml's first vram: 0x80010000, and with the naive
value payload_word silently read 0x800 early. Both fixed; the base comes from
the single derivation in family_remap.vram_of (R33).
The cfg_path class refusal is lifted and replaced by an operation-level one: a carve
whose table lies below the data region is in the leading island, which a tail carve
cannot reach, so build_carve refuses and names the island-split lane (R43).
tools/test_jtbl_parse_config.py proves all three, read-only:
NC-1 regression 171/171 configs byte-unchanged by the fix
NC-2 defect 42/42 md_* lose a c line under the historical derivation; 42/42
keep every c line under the fix
NC-3 main cfg=splat.us.exe.yaml base=0x8000F800, region starts after the last c
The S58 removal cited four waves (as/aw/az/bd) and blamed the lane. All four ran
at band 120-2000. The campaign ledger splits the population by band instead:
tells @ 120-2000 228 drafts -> 18 banked = 7.9%
tells @ full band 655 drafts -> 161 banked = 24.6%
default @ full 2,996 drafts -> 1,335 banked = 44.6%
Of drafts that actually reach a gate the lanes are indistinguishable (tells 54.4%,
default 56.4%) — the whole loss is reloc_identity discarding phantom-symbol drafts,
which is cookbook 235 and a BRIEF fix, not a lane deletion (R40).
Rotation now pins lane index%4 against band index%4 so slot 1 (tells) always draws
the full band and slot 3 (the large band) is always default; asserted over 200 waves.
Takes effect on the next drafter-python bounce; the running process still carries the
old args and the drafter SHELL is never stopped to ship a change.
Closes the gap that made main red for nine hours. R42 ('commit a dirty tree rather than
revert it') is correct for a per-binary gate that leaves PROVEN banks uncommitted, and WRONG
for gate_main, whose substitution is unverified by construction until the SHA matches.
Two guards, defense in depth:
1. gate_main installs atexit + SIGTERM/SIGINT/SIGHUP handlers that revert its own substitution
unless a bank actually succeeded. Killed mid-run, it now cleans up after itself.
2. ox_campaign's dirty-tree commit REFUSES top-level src/*.c (main's sources), reverting those
and committing the rest. Verified: src/800c.c and src/800.c refused, src/ov_*/... and
src/shared/engine_core.h still commit.
Also versions the autonomous lane scripts under tools/lanes/ — they lived only in gitignored
.run/, so a fresh clone had no drafter, gater, maintenance or stallguard at all.
The P31 S58 main probe ran 38 minutes on 8 drafts and never produced a verdict. Two
independent non-termination bugs, both fixed and negative-controlled:
1. THE BISECT COULD NOT TERMINATE. On a failing multi-element chunk it did 'lo = head + lo',
restoring lo to exactly its prior value, so the next iteration recomputed the same head and
failed identically — forever. Replaced with an explicit-stack bisect that SPLITS a failing
chunk and pushes both halves, so work strictly decreases and termination is structural.
GATE_MAIN_MAX_STEPS (24) is a loud backstop, not the mechanism.
Verified: 8 drafts with one poisoned -> 7 banked, 1 rejected, 7 rebuilds.
2. THE TYPEDEF HOIST WAS NOT IDEMPOTENT. The block is inserted AT the anchor, so anything
hoisted previously still started after it and was re-hoisted every call, stacking a fresh
marker comment each time ('hoisted 2 typedef(s)' x150; the tree held a duplicated marker).
Now tracks the already-hoisted region and reuses the existing marker.
Verified: 3 consecutive passes hoist [Foo,Bar], [], [] with exactly 1 marker.
Together these unblock main: 170 parked drafts and ~1,041 open stubs.
The main probe (8 drafts) ran 38 minutes without a verdict. Two defects, neither about the
drafts:
1. FIXED — the compile-error shortcut matched only 'previous declaration of', but gcc printed
'previous implicit declaration of func_80017930'. So a batch whose culprit gcc had already
named fell through to bisection, which costs a full clean EXE rebuild per step. The matcher
now accepts the implicit and conflicting-types forms too. (resolve_conflicts is separately
blind to this class: an implicit decl comes from a call site with no prototype.)
2. NOT FIXED, documented — the typedef-hoist repair is not idempotent. It emitted 'hoisted 2
typedef(s)' 150 times and left a duplicated marker comment; it re-hoists, rebuilds, fails
identically and repeats, so it cannot converge. Make it idempotent and bound the bisect
before gating main again.
Also: ox_campaign pre-draws the next wave AFTER launching shards (doing it before left the
fleet at 8 agents while a card job ran), collect_drafts grants stragglers a grace period
instead of letting 2 of 220 shards idle the fleet for 34 minutes, and drafter bands are now
mostly full-range (the 400-2000 band drew 9 cards for a 2,000-worker fleet).
Two independent reviews (ox design study + Fable validation) found parse_config does not
implement its own documented contract on md_*/main: it can DELETE the c config line and
corrupt the yaml on disk before failing. main also has no config/splat.main.yaml (it is
splat.us.exe.yaml), which is the FileNotFoundError seen on every main jtbl target.
Refuse loudly rather than corrupt quietly (R43). Lift only after parse_config is hardened
and proven on two examples.
Also: gate() stages into a per-run dir (a killed gate's 3,186 stale drafts survived into the
next gate of the same tag and poisoned every group); reloc_filter no longer treats
NOT-A-STUB as a pass (it means ALREADY BANKED — wave an staged 480 of them over source that
already byte-matches).
Designs recorded: docs/tool-designs/jtbl-island-split.md (ox) and -review.md (Fable):
14 CONFIRMED / 8 WRONG / 1 UNVERIFIABLE, verdict GO-WITH-CHANGES, and the correct fix is
SMALLER than proposed — one inserted .rodata carve line + jr_isolate_all.py --only, no _pre
piece and no ld_interleave leading mode.
CURRENT_PHASE.md gains a CRASH-RECOVERY checkpoint (not a fresh-session handoff): what is
running, restart order, the measured fleet/scaling facts, the fixes that must not regress,
and the ordered work queue.
Lanes: drafter (never stop it), gater (restartable), maintenance (free A-prop sibling lane),
stallguard (60s auto-repair). Drafting holds no lock; one narrow draw-vs-gate lock exists
because build_wave_atlas reads corpus.stubs and misreads substituted drafts mid-gate.
main is off the wave critical path — 157 drafts parked to .run/main_queue/ rather than
stalling the gater for another hour on a bisecting whole-EXE rebuild.
api_agent: 5xx retried like 429 (a 502 was abandoning functions at near-19), HTTP_TIMEOUT
420s not 1800 (a hung request parked an agent 30 min), EXTRA_READABLE for tooling briefs,
and bare-directory paths no longer refused against their own granted root.