Files
BFM-decomp/docs/wave-metrics.md
T

11 KiB
Raw Blame History

Crack-wave performance metrics

Evolvable reference (docs/ layer). Started 2026-08-04 (Phase 30, sessions S33–S37). Append one row per wave. The point of this file is that the wave harness has knobs — the prompt, the concurrency shape, the model tier — and their effects are only visible if the numbers are recorded in one place. Two of them were already measured to matter a lot (§ below).

Sources, all derivable — do not hand-transcribe (R33):

  • counts/tokens/duration: the Workflow completion notification (agent_count, subagent_tokens, duration_ms)
  • per-agent wall-clock + parallelism: min/max(timestamp) per agent-*.jsonl under ~/.claude/projects/.../subagents/workflows/<runId>/
  • banked: the whole-binary gate log (.run/s*_gate.log) — the gate is the arbiter, never the agents' claim (G3/P9)

The table

wave script targets templ ins agents tokens wall parallelism median / slowest agent claimed banked +members
1 (S33) s33w.js 17 26,227 25 6.87M 169 min 4.8× 33 / 63 min 15/17 15 (13+2 rec) 65
2 (S34) s34w.js 13 37,943 19 4.51M 208 min 2.7× 20 / 79 min 11/13 10 (9+1 rec) 18
3 (S35) s35w.js 13 17,644 14 2.73M 140 min 2.1× 9 / 51 min 13/13 13 (12+1 rec) 21
4 (S36) s36w.js 14 16,844 17 2.99M 136 min 2.5× 14 / 48 min 14/14 14 (13+1 rec) 26
5 (S37) s37w.js 16 16,884 19 2.71M 82 min 3.8× 9 / 50 min 16/16 16 (16+0 rec) 26
6 (S38) w6.js 16 50,596 24 5.20M 71 min 7.4× 25 / 40 min 14/16 7 25

All waves: Sonnet drafters with an Opus escalation rung (§136i), whole-binary byte-gate as the sole arbiter, R22 clean-fleet 140/140 after every banked batch.

Finding 1 — the PROMPT is the lever, and the agents write it

Bank rate 76% → 77% → 100% → 100% → 100%, with the models and the gate held constant. The only variable was the prompt's extra block.

The jump came from STEP 0: grep -rn "<MAGIC>" src/ using a distinctive literal from the target .s, placed ahead of engine_core.h in §136c's search order — because §136c's first two steps are same-TU/shared-header scoped and structurally cannot reach a banked twin in a different overlay's TU, which is where the big template classes live.

That step came from a wave-2 agent's index_gap report. The wave schema asks every agent for index_hit / index_gap; harvesting those into the next wave's prompt is what compounds. By wave 4 most agents cited step 0 by name and reported index_gap: none.

Honesty caveat: wave 3–5 targets also trended easier (smaller families, more banked twins available), so the prompt is not solely responsible for 100%. The claim that holds without qualification is the direction and the mechanism, not the exact percentage.

Finding 2 — pipeline() vs batched parallel(): 40% faster on 14% more targets

Waves 1–4 split each wave into two parallel() batches, which is a hard barrier — batch 2 cannot start until batch 1's slowest agent finishes. Visible directly in the launch timestamps as 37–50 minute dead gaps at each boundary.

wave 4 parallel() wave 5 pipeline()
targets 14 16
wall-clock 136 min 82 min
parallelism 2.5× 3.8×
median agent 14 min 9 min

The batching existed to dodge S10's 30-wide server throttle, but the harness already caps workflow agents at min(16, cores-2) and these waves are 13–16 targets — so it bought nothing and cost a barrier. Use .run/s37w.js's execution block for every future wave.

The remaining floor is real: the slowest single agent is still ~50 min at 200–300 turns. That is genuine match_one iteration (one agent tested 470+ statement orderings on a 793-instruction function). Wall-clock cannot go below the slowest chain, so the lever there is target selection, not concurrency.

Finding 3 — economics

Roughly 170k–300k tokens per banked head across waves 3–5 (the stable regime). But a head is not the unit of value: the unit is the head plus its propagated members, and the sweep rate is a property of the FAMILY, not the wave — wave 3 swept 21/21 while wave 2 swept 18/165, because wave 2's big families are per-location variants that do not template (settled by probe: remapped member is BUILD OK and byte-different ⇒ genuine per-member codegen). Rank targets by open templatable instructions derived from corpus.stubs, and expect the sweep yield to be bimodal, not average.

Finding 4 — a perfect gate is a signal that the prompt rules landed

Wave 5 is the first 16/16 banked with ZERO reconcile. Waves 1–4 each needed 1–2 declaration reconciles after the gate; wave 5 needed none. The difference is that by then the prompt carried both §138 rules (the (macro-shape, TU-shape) pair, and the reconcile-direction line-number check) plus the wave-4 lesson that two targets sharing a TU can create each other's conflicts. The reconcile lane is the fallback, not the plan — when it goes quiet, the prompt is doing its job. Lifetime reconcile record across the session: 21/22.

How to add a row

After a wave's gate + sweep land, before the commit:

# tokens / agents / duration: from the Workflow completion notification
# parallelism + per-agent spread:
python3 - <<'PY'
import json, glob, datetime
d = "<runId dir under ~/.claude/projects/.../subagents/workflows/>"
T = lambda s: datetime.datetime.fromisoformat(s.replace("Z","+00:00"))
ag=[]
for f in glob.glob(f"{d}/agent-*.jsonl"):
    ts=[T(r["timestamp"]) for r in map(json.loads, open(f)) if r.get("timestamp")]
    if ts: ag.append(((max(ts)-min(ts)).total_seconds()/60, min(ts), max(ts)))
wall=(max(a[2] for a in ag)-min(a[1] for a in ag)).total_seconds()/60
tot=sum(a[0] for a in ag); ag.sort()
print(f"{len(ag)} agents · wall {wall:.0f}m · agent-time {tot:.0f}m · {tot/wall:.1f}x · "
      f"median {ag[len(ag)//2][0]:.0f}m · slowest {ag[-1][0]:.0f}m")
PY

Related: cookbook §138 (the reconcile/search rules these waves produced), memory wave-prompt-seed-step0-and-gaps (what to seed the next prompt with), docs/effort-map.md (Ultracode/breadth policy), docs/calibration.md (templatability measurements).

Finding 5 — rank waves by INSTRUCTIONS BANKED, not heads; and the knob that moved in wave 6

Wave 6 deliberately changed the difficulty knob: 50,596 templatable instructions offered vs ~16,800, median target 438 instructions vs 143, max 947 vs 397, and most targets in the has_mid_jr (jump-table-carve) class. Its bank rate — 7/16 heads — therefore is not comparable to waves 3–5's 100%, and should never be read next to them as if the knob were fixed.

By instructions, the bigger pool won:

wave 5 wave 6
pool offered 16,884 50,596
heads banked 16/16 7/16
ins in banked heads 2,753 2,204
propagated members 26 25
total instructions banked ~7,200 (est.) 10,616 (exact)
pool realised ~43% (est.) 21%

Wave 6's 10,616 is exact — make report moved 12,368,236 → 12,378,852, matching the hand-derivation to the instruction. Wave 5's figure is an estimate (its per-family member breakdown wasn't recorded — record it from now on).

So wave 6 banked ~45% more instructions while banking less than half as many heads, because a bigger head carries more instructions and its family propagates at the same cost per sibling — func_8017FEE0, one 299-instruction head, became 4,485 instructions across 15 siblings for ~0 agent tokens. Head count flatters small-target waves; instruction weight is what moves the fleet.

The metric to beat is POOL REALISATION (21%), not bank rate. A wave banking 16/16 of a 17k pool is worth less than one banking 7/16 of a 50k pool. And wave 6's 21% is a floor, not a ceiling: it lost an entire gate cycle to the _reload_corpus defect (§139), and 9 of its 16 targets are still open and routable rather than refuted.

Finding 6 — a wave's tally is a COVERAGE claim, and needs its own assertion

Wave 6's first gate printed BANKED 5 / FAILED 1 over 16 drafts. Ten produced no verdict at all because the child crashed and the driver only grepped stdout for two line-prefixes. Nine of the ten were claiming MATCH, and one of them (func_8017EA84, 579 ins) banks byte-identical under the fixed path — a real match reported as nothing.

Any row in this table is a coverage claim. Before recording one, confirm the gate accounted for every draft (banked + failed + no-verdict == drafts, now asserted in .run/s6f_gate.py). Full post-mortem: cookbook §139.


Wave S40-1 (2026-08-05) — 8 targets, the first wave ever aimed at the open-only h_norm clusters

Pool verified BEFORE the wave (R14). The frontier report's cluster pool was carried with an explicit "not verified" caveat, and its other headline claim (the whale open in 4 SC07 overlays) had already proved 3/4 wrong. Measured from the sigs instead:

clusters fns ins multiplier
claimed 1,689 5,956 326,261 2.7×
measured 1,677 5,795 319,755 3.68×

Within 2–4% on size, and the multiplier is better than claimed. Verify each claim separately: the same document was right here and wrong about the whale.

Result — and the two numbers say different things.

stage result
match_one close=0 8 / 8
whole-binary gate, first pass 5 / 8
after deterministic recovery 8 / 8 — ~0 agent tokens

All three first-pass failures were integration plumbing, each a different known lever, ZERO codegen walls:

fn gate error lever
func_801802EC redefinition of morph_lerp strip the §77 probe layer — the draft carried types + a static inline helper so match_one could compile standalone; the real TU already defines them. Scaffolding is not part of the bank.
func_8018B238 conflicting types for D_80115158 recover_giant — the draft declared it file-scope as a struct array while the TU declares u8[] block-scope inside other functions; block-scoping the draft's externs removes the collision.
func_8017EF54 conflicting types for func_8017EF54 §37/§124 def-side asm-label alias — TU declares void f(void) for no-arg callers, byte-true def takes s32 in $a0; no-prototype escape illegal once a param promotes, so the definition takes a private C identifier + __asm__("func_8017EF54").

The lesson for reading any future wave row: the gate number is not the close-rate. 5/8 measured integration, not matching. Run the recovery ladder before recording a wave's yield, or the table will under-report the drafters and send the next wave hunting compiler walls that are not there.

Cost: 1.31M subagent tokens, 8 agents, 0 errors. Idioms harvested: cookbook §144 (literal spelling picks the immediate encoding). Defect found: .run/ghidra_c/func_8017EF54.c is a stale decompile of the WRONG function — the prefetch cache is not trustworthy per-entry.