From bfea4affd836f2b723b8521f850b0ed98bf3c80e Mon Sep 17 00:00:00 2001 From: Drew T <50529377+Druthulu@users.noreply.github.com> Date: Wed, 2 Sep 2026 01:29:59 -0600 Subject: [PATCH] feat(draw): route the model tier off the prior RESIDUAL CLASS, not nins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Measured on S71's own wave: wall-clock tracks iteration count, and iteration count tracks the residual class, not size. A 26-instruction function took 18 min / 31 tool calls (regalloc, finished NEAR); a 122-instruction one took 80 s / 10. The 20-33 min runs were all compiler-internal residuals — scheduling ties, birthing boost, register colouring, LUID order — where every hypothesis costs a compile-and-measure cycle. arm_for keys on nins alone, so a 47-instruction regalloc wall could not be drawn at the higher tier and nothing escalates mid-run. arm_from_history() now reads the function's own journal notes at draw time and returns fable when they name one of those classes; it never downgrades the size ladder's choice. R39 control over 3,147 functions with history x 3 bands = 9,441 decisions: 4,020 upgrades (43%), 0 downgrades. The control's FIRST form passed over an empty set — it keyed on journal rows carrying a binary, and there are none: the agent verdict schema never had that field, so every historical note is name-keyed and the same name is a different function in another overlay (§238). claude_wave_draft.js's VERDICT now requires `binary`, so new rows are exact. Cookbook §413. --- docs/cookbook-index.md | 7 ++-- docs/matching-cookbook.md | 37 +++++++++++++++++++++ tools/draw_waves.py | 48 +++++++++++++++++++++++++++- tools/workflows/claude_wave_draft.js | 13 +++++--- 4 files changed, 98 insertions(+), 7 deletions(-) diff --git a/docs/cookbook-index.md b/docs/cookbook-index.md index 066e891626..6d67ee6e45 100644 --- a/docs/cookbook-index.md +++ b/docs/cookbook-index.md @@ -2,7 +2,7 @@ > **Generated by `tools/cookbook_index.py` — do not hand-edit** (R33). Regenerate after adding a cookbook section. > -> `docs/matching-cookbook.md` is ~716 KB / 1080 sections. Grepping it blind is how three P30 wave-1 agents each "discovered" an idiom that was already written down. **Start here, then read the section.** A section appears under every symptom it addresses. +> `docs/matching-cookbook.md` is ~716 KB / 1081 sections. Grepping it blind is how three P30 wave-1 agents each "discovered" an idiom that was already written down. **Start here, then read the section.** A section appears under every symptom it addresses. **How to use:** name what you SEE in the diff (a stolen delay slot, an extra `la`, a swapped register pair, a `conflicting types` error), find that symptom below, read those sections first. If nothing fits, THEN grind — and add a section when you win. @@ -1336,7 +1336,7 @@ - **§408** — ★★★ — §406 REFUTED AS A SWEEP: THE SHAPE IS THE FAMILY, THE DISAGREEMENT IS THE DEFECT (P31 S71; 0 MATCH / 14 applied, 0 / 210) L33209 - **§411** — ★★★ — THE PACK MUST CARRY THAT FUNCTION'S OWN HISTORY (P31 S71; measured 38/39 vs 124/131) L33386 -### (unbucketed — title matched no symptom vocabulary) (323) +### (unbucketed — title matched no symptom vocabulary) (324) - **§3-How** — to use this L30 - **§1** — Idiom catalog (asm pattern → C that produces it) L39 @@ -1661,6 +1661,7 @@ - **§407** — ★★ — LATE-WAVE ADDENDA TO §405 (the last agents in) L33180 - **§409** — ★★★ — THE S71 JOURNAL-FUELLED WAVE: 100% FIRST-PASS MATCH, AND THE NINE LAWS IT BROUGHT BACK (P31 S71) L33261 - **§3-The** — nine laws this wave produced L33288 +- **§413** — ★★★ — DIFFICULTY IS THE RESIDUAL CLASS, NOT `nins` — ROUTE THE MODEL TIER OFF HISTORY (P31 S71, Drew) L33473 ## All sections, in order @@ -2745,6 +2746,7 @@ - **§410** — ★★★ — COPY THEN ACCUMULATE ON THE COPY: resolving the birthing-boost vs register-allocation dilemma (P31 S71; byte-proven `ov_SC04_015/func_8017EB78`, 98 ins) L33353 - **§411** — ★★★ — THE PACK MUST CARRY THAT FUNCTION'S OWN HISTORY (P31 S71; measured 38/39 vs 124/131) L33386 - **§412** — ★★★ — §323 CARVE BLOCKER 2 WAS A REGEX THAT COULD NOT SEE PAST `__attribute__` (P31 S71) L33424 +- **§413** — ★★★ — DIFFICULTY IS THE RESIDUAL CLASS, NOT `nins` — ROUTE THE MODEL TIER OFF HISTORY (P31 S71, Drew) L33473 --- @@ -3837,3 +3839,4 @@ Notes routinely quote that as a section id. This table resolves it. Grep bait: ` | L33353 | §410 | ★★★ — COPY THEN ACCUMULATE ON THE COPY: resolving the birthing-boost vs register-allocatio | | L33386 | §411 | ★★★ — THE PACK MUST CARRY THAT FUNCTION'S OWN HISTORY (P31 S71; measured 38/39 vs 124/131) | | L33424 | §412 | ★★★ — §323 CARVE BLOCKER 2 WAS A REGEX THAT COULD NOT SEE PAST `__attribute__` (P31 S71) | +| L33473 | §413 | ★★★ — DIFFICULTY IS THE RESIDUAL CLASS, NOT `nins` — ROUTE THE MODEL TIER OFF HISTORY (P31 | diff --git a/docs/matching-cookbook.md b/docs/matching-cookbook.md index 4d9d21e601..0b8dc17a96 100644 --- a/docs/matching-cookbook.md +++ b/docs/matching-cookbook.md @@ -33469,3 +33469,40 @@ position relative to a brace or a keyword must strip `__attribute__((…))` firs appear between `}` and the name, after the name, and after the parameter list. This is the §134 class (a scanner that cannot start where the C grammar actually puts things), and it is now the seventh tool in this project to hit it. + +## §413 ★★★ — DIFFICULTY IS THE RESIDUAL CLASS, NOT `nins` — ROUTE THE MODEL TIER OFF HISTORY (P31 S71, Drew) + +**The observation.** S71's wave ran one agent per function and logged wall-clock and tool-call counts. +Duration tracks **iteration count**, and iteration count tracks the **residual class** — not size: + +| function | ins | arm | wall | tool calls | what it was | +|---|---|---|---|---|---| +| `func_80181294` | **26** | opus | 18 min | 31 | REGALLOC-PERM, finished NEAR/2 | +| `func_80185D44` | **47** | opus | 21 min | 48 | LUID contradiction (read cc1's `-dS` trace) | +| `func_80185F4C` | **60** | opus | 22 min | 33 | sched1 birthing-boost tie | +| `func_800D24D0` | 141 | opus | 33 min | 51 | `memrefs_conflict_p` PLUS-vs-LO_SUM | +| `func_8017DB98` | 122 | opus | **80 s** | 10 | body recovered from a prior attempt | +| `func_80182CBC` | 28 | opus | 115 s | 12 | §193-A twin remap | + +A 26-instruction function took 18 minutes; a 122-instruction one took 80 seconds. **Size predicted +neither.** What separates the two groups is whether the residual is a compiler-internal one +(scheduling ties, birthing boost, register colouring, LUID order) where every hypothesis costs a +compile-and-measure cycle — or an ordinary body question that a twin or a neighbour answers at once. + +**Why they never escalated.** `draw_waves.arm_for` keys the model tier on `nins` alone, so a +47-instruction regalloc wall is STRUCTURALLY unable to be drawn at the higher tier, and nothing +escalates mid-run: each agent runs to its own budget and no watcher re-hands the target. + +**The fix, available only because the packs now carry history (§411).** `arm_from_history()` reads the +function's own journal notes at draw time and returns `fable` when they name a compiler-internal +residual; it never downgrades what the size ladder chose. Negative-controlled over all 3,147 functions +with history × 3 size bands = 9,441 decisions: **4,020 upgrades (43%), 0 downgrades.** + +**A defect the control caught, worth as much as the lever.** The first negative control iterated only +journal keys carrying a binary and reported `0 decisions / 0 downgrades` — a clean pass over an EMPTY +set. The agent verdict schema never had a `binary` field, so **every historical note is name-keyed**, +and the same name is a different function in another overlay (§238). Two consequences: the S71 +past-attempt fuel can serve one overlay's history to another's target, and any future join on these +rows inherits it. `claude_wave_draft.js`'s `VERDICT` schema now requires `binary`, so new rows are +exact; the historical corpus stays name-keyed and should be read with that caveat. +`check-against-a-known-true-case`, again: the instrument passed because it measured nothing. diff --git a/tools/draw_waves.py b/tools/draw_waves.py index c11558e925..f245576b32 100644 --- a/tools/draw_waves.py +++ b/tools/draw_waves.py @@ -25,6 +25,7 @@ Then, per wave: t5_cards.py -> claude_wave_packs.py -> wave_args.py, and launch tools/workflows/claude_wave_draft.js with the args wave_args.py printed (never hand-typed). """ import argparse, collections, glob, json, os, sys +import re REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) os.chdir(REPO) @@ -69,6 +70,51 @@ def arm_for(n): return 'opus' if n <= 150 else 'fable' +# THE RESIDUAL CLASS PREDICTS DIFFICULTY BETTER THAN `nins` DOES (Drew, 2026-09-02, S71). +# Measured over the S71 wave's own agent runs: wall-clock and iteration count track the RESIDUAL, +# not the size. A 26-instruction function took 18 minutes and 31 tool calls (`func_80181294`, +# still NEAR); a 122-instruction one took 80 seconds and 10 (`func_8017DB98`). The 20-30 minute +# runs were all compiler-internal residuals — scheduling ties, birthing boost, register colouring — +# where each hypothesis costs a compile-and-measure cycle: +# +# func_80185D44 47 ins opus 21 min 48 tool calls (LUID contradiction, read cc1 -dS) +# func_80185F4C 60 ins opus 22 min 33 +# func_800D24D0 141 ins opus 33 min 51 +# +# `arm_for` keys on size alone, so a 47-instruction regalloc wall was STRUCTURALLY unable to be +# drawn at the higher tier, and nothing escalates mid-run. Now that every pack carries the +# function's own history (§411), the prior residual class is known AT DRAW TIME — and 1,352 of the +# 3,147 functions with history (43%) have a note naming one of these classes. +_WALL_RE = re.compile( + r'permuter|regalloc|register (?:alloc|colou?ring|pressure)|schedule[- ]reorder|SCHEDULE-' + r'|birthing|LUID|sched1|sched2|scheduler-internal|cross_?jump|delay[- ]slot|colou?ring', + re.I) + + +def arm_from_history(binary, fn, n, _cache={}): + """`fable` when this function's own journal history names a compiler-internal residual. + + Escalating SOONER is the standing finding (see arm_for); this applies it to the axis that + actually predicts cost. Falls back to the size ladder when there is no history, and never + DOWNGRADES what the size ladder chose.""" + if not _cache: + try: + sys.path.insert(0, os.path.join(REPO, 'tools')) + import journal_notes + _cache['idx'] = journal_notes.load() + _cache['mod'] = journal_notes + except Exception: + _cache['idx'] = None + base = arm_for(n) + idx = _cache.get('idx') + if not idx or base == 'fable': + return base + rows = _cache['mod'].notes_for(idx, binary, fn) + if rows and _WALL_RE.search(" ".join(r.get('note') or '' for r in rows)): + return 'fable' + return base + + def main(): ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) ap.add_argument('--prefix', required=True, help='wave dir prefix; waves are 1, 2, ...') @@ -128,7 +174,7 @@ def main(): continue pool.append(dict(name=s.symbol, addr='0x%08x' % s.addr, nins=n, binary=b, sub=s.asm_dir, asm=s.asm_path, tu=s.path, - cls='FRONTIER', arm=arm_for(n), **{'from': 'draw_waves'})) + cls='FRONTIER', arm=arm_from_history(b, s.symbol, n), **{'from': 'draw_waves'})) pool.sort(key=lambda t: (t['nins'], t['binary'], t['name'])) print('population: %d open stub(s) in [%d,%d] ins, undrawn, over %d binaries (%d oracle refusals: %s)' diff --git a/tools/workflows/claude_wave_draft.js b/tools/workflows/claude_wave_draft.js index 1b17ea7441..192250775e 100644 --- a/tools/workflows/claude_wave_draft.js +++ b/tools/workflows/claude_wave_draft.js @@ -14,6 +14,11 @@ const VERDICT = { type: 'object', properties: { fn: { type: 'string' }, + // BINARY IS PART OF A FUNCTION'S IDENTITY (R48/§238). Without it every journal note is + // name-keyed, and the same name is a DIFFERENT function in another overlay — so the S71 + // past-attempt fuel (§411) could serve one overlay's history to another's target. Every + // historical journal row lacks this; stamping it now makes future ones exact. + binary: { type: 'string' }, arm: { type: 'string' }, status: { type: 'string', enum: ['MATCH', 'NEAR', 'FAIL', 'NO-DRAFT'] }, closeness: { type: ['integer', 'null'] }, @@ -21,7 +26,7 @@ const VERDICT = { draft_path: { type: 'string' }, note: { type: 'string' }, }, - required: ['fn', 'arm', 'status', 'closeness', 'compiles', 'draft_path', 'note'], + required: ['fn', 'binary', 'arm', 'status', 'closeness', 'compiles', 'draft_path', 'note'], } phase('Draft') log(`wave ${WAVE}: ${TARGETS.length} targets (${TARGETS.filter(t => t.arm === 'sonnet').length} sonnet / ${TARGETS.filter(t => t.arm === 'opus').length} opus)`) @@ -41,10 +46,10 @@ CLI equivalents (run from ${REPO}): submit -> write your FINAL draft to ${REPO}/${WAVE}/${t.arm}/${t.name}.c (mkdir -p the dir) and return the JSON verdict. HARD RULES: never modify anything under src/, config/, include/, asm/, build/ or run make; never touch other agents' files; write only to ${WAVE}/${t.arm}/${t.name}.c and scratch under ${WAVE}/${t.arm}/scratch_${t.name}/. Budget: up to ~24 compile/match_one iterations, then stop honestly. -Your final answer is the JSON verdict only: fn, arm="${t.arm}", status (MATCH if match_one says MATCH; NEAR if it compiles with closeness>0; FAIL if it never compiled; NO-DRAFT if you wrote nothing), closeness (integer or null), compiles, draft_path, note (one line: what blocked you, or which cookbook § unlocked it).`, +Your final answer is the JSON verdict only: fn, binary="${t.binary}", arm="${t.arm}", status (MATCH if match_one says MATCH; NEAR if it compiles with closeness>0; FAIL if it never compiled; NO-DRAFT if you wrote nothing), closeness (integer or null), compiles, draft_path, note (one line: what blocked you, or which cookbook § unlocked it).`, { label: `${t.arm}:${t.name}`, phase: 'Draft', model: t.arm, schema: VERDICT } -).then(v => v || { fn: t.name, arm: t.arm, status: 'NO-DRAFT', closeness: null, compiles: false, draft_path: '', note: 'agent returned null' }) - .catch(e => ({ fn: t.name, arm: t.arm, status: 'NO-DRAFT', closeness: null, compiles: false, draft_path: '', note: 'agent error: ' + String(e).slice(0, 120) })))) +).then(v => v || { fn: t.name, binary: t.binary, arm: t.arm, status: 'NO-DRAFT', closeness: null, compiles: false, draft_path: '', note: 'agent returned null' }) + .catch(e => ({ fn: t.name, binary: t.binary, arm: t.arm, status: 'NO-DRAFT', closeness: null, compiles: false, draft_path: '', note: 'agent error: ' + String(e).slice(0, 120) })))) const byArm = {} for (const r of results.filter(Boolean)) { (byArm[r.arm] = byArm[r.arm] || []).push(r) } for (const arm of Object.keys(byArm)) {