Commit Graph

17 Commits

Author SHA1 Message Date
Drew T 9df4ae32f6 fix(api_agent): never warm-start a pack from the target's own assembly
Third door of one defect, and the one that mattered. A §265 verbatim body is
stored as <fn>.c like any draft, so prior_draft offered it under 'a previous
attempt left this body behind, keep what matches' — an invitation to
resubmit it. match_one then says MATCH, the gate goes green, nothing is
decompiled.

Measured today: gate_main banked 9 such bodies with progress.py moving by
exactly zero; harvest_verify had no guard at all; and with BOTH gates fixed,
two relaunched agents (func_8005E79C, func_8005EAC8) STILL returned verbatim,
because the pack handed it to them and they reasonably reported 'the prior
draft is already MATCH closeness 0'. It is — that is the problem. Fixing the
consumers is not the same as fixing the supply.

Verified on func_8005EAC8: 2 verbatim candidates now rejected with a named
reason (R32, never a silent drop) and the warm start falls back to a real C
body from wave_m05/shard31. Shared by claude_wave_packs, so every future
Claude wave gets it too.
2026-09-03 14:20:44 -06:00
Drew T 8edb918480 feat(cards): size-filter the same-address lead (§238 homonym) + bank the S74 lever set
THE CARD USED TO HAND AGENTS A WRONG TWIN ABOUT ONCE IN FIVE. `⭐ func X IS BANKED AT THIS ADDRESS`
never checked that the two functions were the same SIZE, and overlays share addresses between
unrelated functions as readily as they share code. Measured over this session's ~60 cards: about a
dozen agents reported discarding the lead themselves, and one card advertised a 72-instruction
namesake — with journal history claiming "already MATCH closeness 0" — to a 241-instruction target.
A confidently wrong lead costs more than no lead, because the agent believes it.

corpus.sig already carries `nins` and `h_seq`, so the fix is free: `_same_addr_banked` now returns
(binary, nins, h_seq); the card keeps a lead only at a MATCHING instruction count, marks it strong
when the mnemonic skeleton matches too, and prints an explicit `⚠ IGNORE` naming the binaries where
that address holds something else, with both sizes.

VERIFIED IN BOTH DIRECTIONS against known-true cases before being believed (never trust a filter you
have not tried to fool):
  * the trap: ov_SC03_105:func_801806F8 (241) vs ov_SC03_013 (72) -> `⚠ IGNORE`.
  * the positive: ov_SC02_003:func_80187B40 (158) -> strong lead to ov_SC02_000 (158, same h_seq,
    banked this session) AND, in the same card, warned off ov_SC04_011's 138-ins homonym at that
    same address. That is precisely the pair a wave agent sorted out by hand hours earlier.

Cookbook §438 (the law: a lead is fuel only if it carries the cheapest fact that can refute it —
size refutes a homonym for free and nobody had asked) and §439, the S74 lever set: MEM_IN_STRUCT_P
as a two-way alias-oracle dial (four agents converged on it independently); `goto`-into-a-shared-tail
vs longhand as a REGALLOC dial because gcc-2.7.2 cross-jumps after allocation; `for` -> do/while as a
length-changing scheduling dial; allocno PRIORITY via a non-volatile asm at a loop head, with the
measurement that register pins are actively harmful for that class; the -O0 global-RMW rule
(`x++` emits the copy-back quartet, `x = x+1` does not); why `sll 16; srl 16` survives only across a
CALL; `sltiu N` without `addiu -1` proving an empty `case 0` is mandatory; block-scoped temps in
duplicated bodies; two `register asm` vars cannot share a hard reg; and `x*32` vs `x<<5` emitting
lh vs lhu — which match_one's %lo mask HIDES, so it must be checked with objdump.
2026-09-02 19:38:56 -06:00
Drew T 8cf0104386 fix(cards): defect 5 — an expired BASELINE-RED claim, without discarding any measurement
The S70 patch was refused by its own adversarial review for sorting rows by recency:
a pair's ledger rows are several PROBES about one draft, alternating between
`closeness 4` and `won't compile standalone`, so max(ts) serves whichever probe ran
last — often the least informative. This form keeps both.

* the ts-newest verdict is still selected (file order made the per-binary bulk ledger
  always win regardless of age: 25 pairs mis-selected),
* AND the best measurement ever taken on the pair rides alongside it, so a later
  uninformative probe can no longer erase an earlier residual: 981 of 2,605 pairs
  gain a line they were previously denied.
* BASELINE-RED is a fact about a binary at a moment (R51), frozen into an append-only
  ledger and replayed forever — 2,676 rows all stamped 2026-08-26. gate_feedback now
  reads the same live red union gate_stage consults, so a pack and the next gate run
  cannot disagree: 173 expired claims retired, 0 binaries currently red.

R39 control 3/3 (expired-when-green, harness-line-when-red, measurement-survives).
2026-09-02 00:32:20 -06:00
Drew T e532033c5e fix(cards): the SYMBOL MISMATCHES block gated on shape, not aligned — served void advice
gate_feedback selected the newest reloc_rejects row with shape=='MATCH' and printed
its mismatches under "your instruction stream already matched; ONLY these names were
wrong". But reloc_identity's binding condition is `aligned` (shape=='MATCH' AND equal
relocation-stream lengths); when that fails it downgrades status to "MISMATCH?" and
stamps the row ADVISORY. Gating on `shape` alone therefore republished ADVISORY rows
as binding per-index instructions — and when the streams are not index-aligned, draft
index i is compared to target index i of a DIFFERENT stream, so every "the target
references 0x..." line is arithmetic on the wrong word.

MEASURED (agent-run, not predicted):
  * 15 of 130 S70 targets were served this block; 15 of 15 were aligned=False, i.e.
    100% carried reloc_identity's own "verdicts are ADVISORY" caveat while the pack
    text told the agent the opposite.
  * 55 of the 66 printed lines (83%) name a value that is not an address at all
    (0x82020084, 0x880801C0, ...).
  * Of the 4 whose .s is on disk, 4 of 4 named symbols the target never relocates.
  * Whole index: 182 servable (binary, fn) rows, 149 aligned=False; 140 of those 149
    print >=1 non-address vs 1 of the 33 aligned=True.
This reproduces both S70 agent reports verbatim (ov_SC02_035:func_8017D3F4 "cross-
overlay contamination"; ov_SC06_020:func_8017D918 "those symbols are absent from
this .s").

Root cause has a second half, still OPEN upstream: ox_campaign.reloc_filter stamps the
row's binary from `binof = {c["fn"]: c["binary"]}` — a BARE-NAME dict (R48). Wave `el`
carried 44 names in >=2 binaries, so func_8017D918's row was stamped ov_SC06_020 while
the draft it checked belonged to ov_SC01_074. A correct read key cannot repair a wrong
write-side stamp, which is why the fix validates against the TARGET'S OWN bytes.

Adversarially reviewed (sound=True) and controlled here: the known-true aligned=True
case ov_SC07_011:func_8016AB6C is STILL SERVED; ov_SC02_035:func_8017D3F4 is withheld
with a loud reason. The reviewer's own first attempt validated draft_symbol against the
.s and rejected that good block — a false positive caught only by a known-true case.
2026-09-01 23:44:37 -06:00
Drew T 97cbaf0408 fix(cards): never assert "NO banked twin" without the cross-overlay address check
MEASURED over the 130 S70 targets: 110 cards printed "This card has NO banked twin
— derive the structure from the .s", and **75 of them (68%) had that function
already BANKED at the same address in a sibling overlay.**

seed_ref joins on signature hashes and is blind to indexed-global relocs (§389), so
a reloc-only twin of an already-banked body hashes differently and reads as a
singleton. Overlays share code at the same VRAM, so "is this address banked
elsewhere?" is a one-line question the card never asked. An S70 agent found its
answer at src/ov_SC02_000/ov_SC02_000_jr_8018173C.c:4827 and reported the card was
simply wrong: "a cross-overlay same-address grep as step 0 would have returned this
for ~0 tokens" — which is exactly what the wave playbook prescribes and what nothing
was supplying.

_same_addr_banked() derives it from the corpus invariant (R33: banked == in sig and
not an INCLUDE_ASM stub), memoized once per process. The card now names the binaries
and tells the agent to READ IT FIRST, while warning that a same-address function in
another overlay is usually — not always — the same function (verify per law 1c).

Controls: positive ov_SC02_003:func_80185840 -> ['ov_SC02_000', 'ov_SC03_091'] (the
first is the very binary the agent found by hand); bogus address -> []; a named
symbol -> []. Failure returns [] so this only ever ADDS fuel.
2026-09-01 23:18:44 -06:00
Drew T c16fd3cf53 fix(warm-start): ANY foreign symbol disqualifies a same-named prior draft (law 1c)
api_agent.prior_draft's law-1c guard had two holes, both measured live in the S70
wave where FOUR independent agents reported discarding the warm-start as "a
different function entirely":

  * `len(syms) >= 2` exempted every body referencing 0 or 1 symbols — exactly the
    small-function case. func_80182438 (21 ins, ONE symbol) sailed through carrying
    ov_SC02_028's body for the SAME ADDRESS, and its agent reported that as the
    reason its PRIOR attempt failed outright.
  * requiring a strict majority foreign let a body sharing half its symbols pass.

A correct draft can only reference what the target's .s actually relocates, so ANY
foreign symbol disqualifies.

NEGATIVE CONTROL over all 50 S70 targets: 46 admitted -> 42, and the 4 rejected are
exactly the bodies the agents flagged (func_80182438 foreign func_801330E0,
func_800D0664, func_801831D0 foreign func_80182570, func_80185F4C). No collateral.

Cost of the hole: every agent reading a poisoned warm-start burns compiles
discarding it, and a weaker model follows it instead. R48 again — never key by bare
function name.
2026-09-01 22:35:56 -06:00
Drew T 5962bb70ed fix(api_agent): prior_draft accepts a warm-start body only when its symbols overlap the target .s (law 1c) — 22/60 T4 agents had received another overlay's same-named function; fleet ledger regenerated (2,380 stubs) (P31 S62) 2026-08-26 19:02:07 -06:00
Drew T 0ff05ce200 feat(drafting): retries carry the gate's verdict — class-accurate failure feedback + named reloc mismatches in the warm start; harness-fault classes flagged as not-the-draft's-fault (P31 S61) 2026-08-26 01:58:00 -06:00
Drew T 09b7f71a19 fix(serial lane): it was dead at turn 0 — a family-card file fed to a per-function consumer
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.
2026-08-25 15:00:31 -06:00
Drew T e7353d63de feat(tells 6.3): put the TELL COUNTS on the card and in the prompt
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).
2026-08-25 14:35:36 -06:00
Drew T 3a7f67187c fix(agent): cookbook grep hits come back with the SECTION they live in
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.
2026-08-25 01:09:25 -06:00
Drew T 3960b3e28a fix(agents): retry the SOFT 429 — a 200 body carrying a rate-limit error was killing agents outright
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.
2026-08-24 23:38:18 -06:00
Drew T 99f096c591 fix(flywheel): the index is the entry point, and a cited number that fails to grep is a LINE
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.
2026-08-24 13:36:58 -06:00
Drew T 6ae2c55804 feat(tells): land the crib, the grep aliases and per-lane budgets
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.
2026-08-24 12:41:41 -06:00
Drew T e05dc116be chore(phase-31): S58 crash-recovery checkpoint + autonomous lane architecture
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.
2026-08-24 00:24:11 -06:00
Drew T e7ebb1cd45 docs+rules(S58): R42 commit-banked-work-immediately, R43 refuse-unsupported-input
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.
2026-08-23 12:59:59 -06:00
Drew T cb65a62cad feat(phase-31): S57 external-model bake-off — 4 tool fixes, §205/§206, api_agent harness
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).
2026-08-23 00:12:07 -06:00