idiom_serial passed `--cards .run/aprop_cards.json` to api_agent. That file is a FAMILY-card
file — rows are {family, members, seed, cls, reach}, with no per-function key — while
api_agent's --cards wants per-function wave cards and built its map with `c['fn']`. Result:
KeyError at line 647, the agent died before its first turn, and every target logged
"no-draft". The lane read as a model failure; its ledger holds 8 rows total and the tells
lane had, literally, "never yet run".
TWO FIXES, one on each side of the contract:
* api_agent REFUSES, never crashes (R43). It accepts 'fn' or 'name', skips rows with
neither, and says how many it skipped. The R32 warning two lines below — "ZERO matched —
wrong card file?" — existed to catch exactly this and was unreachable behind the crash. A
guard downstream of the failure is not a guard.
* idiom_serial passes no --cards at all. Its fuel is the --brief: the target plus every
idiom distilled so far in the run, which IS the compounding channel the 2,000-way fan-out
lacks. It never needed a wave card.
Verified: the lane now reaches `api_agent: stealth/ox-alpha -> 1 target(s), max 60 turns` and
drafts, instead of exiting in 0.1 s.
A tell-lever card named a lever and then made the agent go find its sites: it said
"extend-tell" and nothing about where or how many. atlas_features already counts the
detectors per function into .run/feat.<bin>.jsonl at atlas time, so this is a JOIN, not a
computation — one dict load per binary in the wave.
build_wave_atlas attaches 'tells' {extpair, dupselect, magic_div, sign_lh, sign_lb} to every
card that has a nonzero one — not just tell-lever cards, because a sll/sra pair site or a
repeated select is worth knowing whatever lever drew the card. Measured on a live draw: 85 of
185 cards carry counts.
api_agent._fuel renders them as a CHECKLIST rather than a hint, which is the point: the
counts come from the TARGET's own bytes, so a draft emitting fewer has provably missed sites
and should go looking before spending a turn elsewhere.
Verified: a card with {extpair 3, dupselect 2, sign_lh 1} renders all three with the zero
fields omitted; NEGATIVE CONTROL — a card with no tells renders no TELLS line at all.
Takes effect on the next draw + the next shard (api_agent is spawned per draft).
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.
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.
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.
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.
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.
R42: gate_main reverted 61 byte-proven overlay banks it could not distinguish from its own
substitution (sweep_parallel gates commit=False by design). Fixed by committing overlay banks
before the main batch, chunking main at 8 to bound bisect cost, and replacing every blind
'git checkout -- src/ config/' with commit-or-refuse in ox_campaign and idiom_serial.
R43: sweep_parallel had an explicit branch admitting main, which cannot be gated incrementally
— wave ab banked 0/105 main cards while its non-main cards banked 94/115 (82%), and the wave
read as a drafting failure. sweep_parallel now refuses main and names gate_main.py.
Also: validate_targets now prefers the card's own addr field (named symbols like SYS_OBJ_F00
were MALFORMED and discarded whole 220-card waves); ox_campaign deals model lanes by
smallest-ratio scheduling (a 73-card wave had put 73 shards on ox and 0 on deepseek);
docs/accelerators.md gains the four vacuous-check defects.
An external-model bake-off, not a banking session. Nothing was banked; that is
the next session's first task.
FINDINGS
- CARD FUEL is the biggest lever, bigger than model choice: the same 10 cards
went 4/10 -> 9/10 when seed_ref/tu_ref/decl_prior were injected. The
"60-instruction ceiling" was an artifact of withholding fuel.
- Sub-50 is near-free: 19/19 verified MATCH at $0.007/function, blind.
7,724 sub-50 open functions = 73.5% of the remaining set.
- A free model (stealth/ox-alpha) cracked a 611-ins function and a jtbl
exemplar, and distilled §206 — whose two negative results were
independently byte-confirmed before banking.
- §206 transfers WITHIN a family (40 turns -> 11) but NOT across (56 turns,
0 compiles). jtbl costs ~40 turns of learning per family, not per class.
TOOL FIXES (all negative-controlled)
- family_remap.gather_externs: took the first ALPHABETICAL extern across the
overlay's TUs, carrying two types swapped (sh/lh vs lbu/sb). Now prefers
the extracted unit's own file. Blocked a 4-member/2,444-ins family.
- atlas.member_lever: aprop_card was loaded and never read while a bare
ledger DIFF forced needs-autopsy. PURE now outranks it — rescues 32
members / 11 families / 3,810 ins.
- decl_prior._ASM_SYM: the %hi/%lo arm had never fired (0 of 1,210 over four
waves). jal 306->306 zero regressions, data 0->299.
- api_agent.py (new): --cards, --brief, --max-cost, nudge loop, 429
attribution + backoff, transport retry, non-fatal tool faults, and a
repeated-call guard.
RULES PROPOSED: R40 (exonerate the instrument before attributing a failure to
its subject — seven instances this session) and R41 (a cost/rate/yield number
ships with its denominator — I quoted $0.30 against a $6.31 bill).