Research log Small Model Experimentation
GitHub

Qwen3.5-4B: Can Confidence Replace the Verifier in the Banking Flywheel?

Confidence can rank answers but can't clean training

The one idea you need

This small model teaches itself: it writes many candidate programs, keeps the ones it is surest are right, and retrains on those. Like a cook who tastes to serve tonight's best plate (fine), but writes the cookbook by taste alone — subtly-wrong recipes slip in and the apprentice learns the flaws.

The question

If a small model grades its own generated code by how confident it feels instead of actually running it, can it still build a training set and teach itself to improve?

What we found

No. When training on answers checked by actually running the code lifted single-shot accuracy from 8% to 24%, confidence-filtered data — fifteen times purer than a random grab of the model's own outputs — landed right on the no-filter floor at 12%, adding nothing. Self-training tolerates almost no wrong examples, and the confident mistakes a filter lets through are plausible near-misses that teach the worst habits.

Why it matters

In a self-training loop, keep a real code-runner cleaning the training data — confidence leaks plausible-but-wrong examples that poison learning at small data sizes. Use confidence only to rank candidates, filtering by rank within difficulty tiers, never by a fixed score cutoff.

Accuracy from execution-checked training data8% → 24%depth-2 single-shot, thinking mode — the only filter that moved the needle
Accuracy from confidence-filtered data8% → 12%identical to an unfiltered random sample — a statistically insignificant nudge, not a real gain
Purity edge that bought nothing43% vs 3% correctabout 15x cleaner than random, yet trained the model no better
Self-grading right from wrong on one taskcoin flip → reliableblind until the model was allowed to think before scoring, which fixed it
On this page
  1. Results at a glance
  2. Overview
  3. Report
    1. Summary
    2. Research Program Fit
    3. Method
    4. Results
    5. Controls
    6. Oracle Versus Deployable Evidence
    7. Interpretation
    8. Next Experiments
    9. Artifact Manifest
  4. Experiment log
  5. Figures
  6. Data files
  7. Reproduce
  8. Related

Results at a glance 4

Only execution-checked training data improved the model; confidence-picked matched no filter

How to read

One bar per training filter, showing depth-2 single-shot accuracy in thinking mode after fine-tuning on 70 self-generated solutions drawn from one shared pool: no training, execution-verified (all correct), confidence-picked (43% correct), and unfiltered random (3% correct). Taller is better.

0%10%20%30%base (no training)base (no training)8%execution-verified (purity 1.00)execution-verified (purity 1.00)24%confidence-picked (purity 0.43)confidence-picked (purity 0.43)12%unfiltered random (purity 0.03)unfiltered random (purity 0.03)12%

Takeaway → Only the fully-verified bar rises, 8% to 24%; the confidence-picked bar sits at 12%, dead level with random despite being fifteen times cleaner — near-purity bought nothing.

Data table
conditiondepth-2 single-shot accuracy, thinking mode (n=25, frozen paired eval)
base (no training)8%
execution-verified (purity 1.00)24%
confidence-picked (purity 0.43)12%
unfiltered random (purity 0.03)12%

Numbers from runs/verdict.json (think.greedy_full.d2.POSTHOC), runs/eval_*_think.json

Technical framing

Fine-tuning on self-picked solutions: only execution-verified data teaches; confidence-picked = unfiltered — All arms fine-tune on 70 same-size pairs from the SAME candidate pool; only the filter differs. The ~15x purity advantage of the confidence filter buys nothing over no filter (trichotomy verdict: conf~rand; post-hoc cell, exec-vs-base CI [+0.04,+0.32], p=0.011). Banking tolerates almost no confidently-wrong data.

The one-token self-score is blind unless the model thinks first

How to read

Two judging methods side by side: an instant one-token self-score versus one given room to reason first. Dark bars measure how reliably it separates right from wrong answers on the SAME task (higher is better); light bars are a flattering score inflated by mixing easy and hard tasks together.

00.250.50.751instant (no-think) judgeinstant (no-think) judge0.4710.749thinking judge (512-token budget)thinking judge (512-token budget)0.8450.961

Takeaway → Instant scoring sits at a coin flip within a task; thinking first lifts it to reliable. The flattering pooled bar hides this failure — always check the within-task bar.

Data table
conditionwithin-task AUROC (right vs wrong, same task)pooled AUROC (inflated by task difficulty)
instant (no-think) judge0.4710.749
thinking judge (512-token budget)0.8450.961

Numbers from data/pool.jsonl recompute (no-think series; documented in reports/report.md), runs/arms_summary.json gate (think series), runs/judge_think_diag.json (forced-close/budget note)

Technical framing

The one-token self-score is blind here unless the model thinks before scoring — On this substrate correctness must be mentally EXECUTED, not read off a docstring -- the C46 single-token law is substrate-scoped, and C44's serial-compute law governs the judge seat: 99% of thinking judgments hit the 512-token budget and the rescue still holds. Pooled AUROC conceals the failure: gate on within-task.

Does self-training break the judge? Ranking holds, scores creep up

How to read

Three models each grade the identical set of 777 candidates: untrained, trained on verified data, and trained on its OWN approvals. Dark bars: how well it separates right from wrong answers (higher is better, near-flat across all three). Light bars: confidence wrongly given to incorrect answers (lower is better, creeping up after training).

00.250.50.751basebase0.8720.098trained on verified datatrained on verified data0.8730.149trained on its OWN approvalstrained on its OWN approvals0.8830.141

Takeaway → The judge's ranking survives even after training on its own approvals, but its confidence on wrong answers drifts upward — so filter by rank each round, never by a fixed score cutoff.

Data table
conditionwithin-task AUROC, fixed 777 candidates (thinking judge)P(True) given INCORRECT, fixed set
base0.8720.098
trained on verified data0.8730.149
trained on its OWN approvals0.8830.141

Numbers from runs/calib_think_*.json, runs/calib_self_think_*.json

Technical framing

Does the judge survive self-training? Ranking intact, scores inflate on its own mistakes — Every model judges the IDENTICAL 777 candidates, so any change is a change in the judge. Discrimination survives even for the arm trained on its own approvals; on its OWN new candidates the score on wrong answers doubles (0.091 -> 0.204). Flywheel rule: filter by rank each round, never by a fixed score threshold.

Side effect: instant-answer variety collapses onto the trained answer-family

How to read

Bars show depth-1 answer variety in instant no-think mode after each fine-tune — the share of tasks where at least one of 16 tries is correct (higher is better). Left to right: base, execution-verified, confidence-stratified, confidence-global, and unfiltered random.

0%20%40%60%80%basebase72%execution-verifiedexecution-verified36%confidence-stratifiedconfidence-stratified76%confidence-globalconfidence-global32%unfiltered randomunfiltered random40%

Takeaway → Most trained arms roughly halve base variety (72% down to 32-40%) as correct answers get crammed onto one banked program family; the light-dose stratified arm (76%) is spared, and reasoning mode stays untouched.

Data table
conditiondepth-1 coverage@16, no-think (frozen paired eval)
base72%
execution-verified36%
confidence-stratified76%
confidence-global32%
unfiltered random40%

Numbers from runs/eval_*_nothink.json + per-task forensics in reports/report.md (Result 6)

Technical framing

Side effect: banking reallocates the no-think proposal distribution onto the banked op-family — Not capability loss: correct samples are conserved (126/115/130/106/108 per 400) but crammed onto the banked program family -- the dedup-op family drops from 9/10 tasks covered to 0/10. The stratified arm banks only 15 depth-1 pairs (sub-threshold dose) and is spared. Chain-of-thought re-derivation shields think-mode sampling, which is what a flywheel harvests.

In the author’s words from the Overview · “Results”

stats in runs/verdict.json; saga in experiment_log.md. Gate stop first (kept): the NO-think P(True) judge is at chance within-task on this substrate (0.471; pooled 0.749 = task difficulty). C46's within-problem law is substrate-scoped: it holds where correctness is semantically readable, not where it must be computed. Serial compute rescues the judge: CoT judging (judge_think, budget 512, 99% forced-close) lifts within-task AUROC to 0.845 (pooled 0.961), P(True) correct/incorrect 0.61/0.08 full-pool — C44's law governs the judge seat. The pivot re-ranked the conf arms on think-P(True); gate passed (purity gap 0.400). Trichotomy = conf~rand. No pre-registered cell cleared exec−base ≥ 0.10. … Read the full result →

Overview

Research Program

  • Program: posttraining_and_adaptationevidence_conditioned_selection)
  • Program question: the banking arc (C11C24) requires an executable verifier at every rung; the confidence arc (C40C46) built a calibrated verification-free judge. Can the second power the first — extending self-training to domains with no interpreter?
  • Prior anchors: C18 (qwen35_4b_coverage_banking — the harness and effect this replicates), C46 (qwen35_4b_code_confidence + HumanEval — P(True) is the program-level confidence), C11 (banking law), C29 (read-only verifier 2AFC 0.81), C24 (banking gain is diversity-driven).

Question

Does banking the model's own high-P(True) solutions — with NO execution anywhere in the training pipeline — recover the capability gain of banking execution-verified solutions? And does the calibration signal itself SURVIVE being trained on (flywheel viability), or does self-training inflate P(True) / collapse the judge?

Hypothesis

C46 says top-P(True) selection approaches visible-test execution per-candidate, so a top-fraction filter over a large harvest should yield a training set pure enough (~0.8+) for C18-style banking, whose gain is diversity- not purity-driven (C24). Falsifiers, each a distinct autopsy: (a) banking is less noise-tolerant than selection — confident-but-WRONG pairs teach the confidently-wrong modes; (b) the filter biases toward easy tasks, collapsing the diversity that drives banking; (c) training on own confident outputs inflates P(True) and destroys calibration (feedback collapse — kills round 2 even if round 1 works).

Setup

  • Model: Qwen3.5-4B, QLoRA r32/α64 (C18-identical: epochs 3, lr 2e-4, no-think prompt→code pairs).
  • Dataset/task source: contamination-free procedural list-DSL identification tasks (families.py), 90 train tasks at depths 1/2/3 (C18 schedule), K=40 think-mode samples each.
  • Train/eval split: frozen held-out eval set (eval_ladder.py), behavioral func-sig + op-composition dedup vs ALL train tasks, 0 leakage, paired across arms.
  • Baseline: base model (no training) and rand (same-size, draw-frequency-weighted sample of the same pool — what "bank with no filter" actually means; uniform-over-unique would deflate the floor).
  • Arms (all filter the SAME candidate pool; matched size is enforced HARD = matched optimizer steps; identical training recipe; the ONLY variable is the keep-test): exec (execution-verified, C18-identical — the ceiling), conf_strat (PRIMARY: depth-stratified top think-P(True), per-depth quotas ∝ pool judge-score MASS — attempt-2 pivot: candidate-count quotas provably allocate slots where wrong candidates explode; verifier-free, isolates the keep-test from cross-depth slot allocation), conf_global (ablation: naive global top-P(True), the fully-deployable policy; its expected depth-collapse is reported, not conflated), rand.
  • Gate (pre-registered, run.py hard-stops otherwise): pool P(True)-vs-full_pass AUROC ≥ 0.65 AND conf_strat−rand purity gap ≥ 0.10 AND matched n ≥ 60. Below the gate the finding is "the judge does not transfer to this substrate" and training is not burned. (The gate reads oracle purity — an experiment-level run/stop decision, documented like the matched-n scalar.)
  • Primary metric & decision rule (pre-registered): per cell (mode × metric × depth, incl. pooled) where exec−base ≥ 0.10, classify by paired-bootstrap CIs into a TRICHOTOMY — conf~rand (conf_strat-vs-rand CI ≤ 0: filter adds nothing), intermediate (beats rand, below exec), conf~exec (beats rand, CI vs exec includes 0: verifier replaceable). The joint-bootstrap recovery ratio (conf−base)/(exec−base) is reported with CI as a magnitude, not a cutoff (n=25/depth cannot resolve 0.8 vs 1.0).
  • Secondary: calibration survival — (a) fixed judge set (base's no-think eval-task samples; every model judges the SAME candidates → judge-change isolated), inflation headline = P(True)-on-INCORRECT drift (overall mean drift confounds inflation with real ability); (b) self-distribution pass (each model judges its OWN think-mode eval-task candidates — the actual round-2 flywheel number).
  • Oracle-only metrics: full_pass grades all evals, builds the exec arm, and gates the run; in the conf arms' selection it never appears (post-hoc purity reporting only).
  • Hidden-label boundary: conf-arm selection reads nothing but the model's own P(True) logit (no-think A/B judge, P(A) after "Answer: ") + generator metadata (task depth, draw frequency for tiebreaks). Matching size to the exec arm leaks one scalar (documented design constant).
  • Known limits (pre-registered): single LoRA seed per arm (~30 optimizer steps — arm deltas include seed noise); no depth-4 cell (C18 comparability cell dropped for time); conf_global gets no think-mode eval (ablation, nothink only).

Run

Smoke (~10 min end-to-end, all five stages):

python scripts/run.py --smoke

Full (~12 h on the RTX 4090: harvest ~2 h, pool think-judge ~50 min, 4 trains ~12 min, 9 pipeline evals ~5 h + 1 held-back conf_global think eval post-pipeline, 10 calib passes ~4 h — the think-judge and self-distribution passes dominate):

python scripts/run.py            # idempotent; safe to re-run after interruption
python scripts/analyze.py

Results

Full narrative in reports/report.md; stats in runs/verdict.json; saga in experiment_log.md.

  1. Gate stop first (kept): the NO-think P(True) judge is at chance within-task on this substrate (0.471; pooled 0.749 = task difficulty). C46's within-problem law is substrate-scoped: it holds where correctness is semantically readable, not where it must be computed.
  2. Serial compute rescues the judge: CoT judging (judge_think, budget 512, 99% forced-close) lifts within-task AUROC to 0.845 (pooled 0.961), P(True) correct/incorrect 0.61/0.08 full-pool — C44's law governs the judge seat. The pivot re-ranked the conf arms on think-P(True); gate passed (purity gap 0.400).
  3. Trichotomy = conf~rand. No pre-registered cell cleared exec−base ≥ 0.10. In the post-hoc cell where exec moved (d2 think greedy 0.08 → 0.24, CI [+0.04,+0.32], p=0.011), conf_strat +0.04 = rand +0.04 exactly, despite ~15× purity (0.429 vs 0.029). Recovery 0.25, CI [−1,+1]. Banking is far less noise-tolerant than selection.
  4. The exec ceiling is dose-limited: first matched-dose C18 re-run under the strict frozen paired eval → d2 think coverage flat (0.28 → 0.28). With the lineage audit (C18: p=0.082, 3/20 d2 eval tasks leaked under the canonical probe-set func-sig; C22/C23 replications at 3.5× dose; C24 dose law), C18's headline was a low-dose overestimate, not a false effect.
  5. Calibration survives banking as a RANKER: fixed-set think-judge within-AUROC 0.872 → 0.873 (exec) / 0.883 (conf_strat). But P(True) on own INCORRECT candidates doubles on the self-distribution (0.091 → 0.204). Rank filters survive round 2; fixed thresholds silently degrade. Own pass-rate 0.120 → 0.093 (no capability gain, consistent with 3).
  6. No-think SFT reallocates the no-think proposal prior; CoT shields think: d1 no-think cov@16 collapses 0.72 → 0.32–0.40 for exec/conf_global/rand (conserved correct mass crammed onto the banked op-family; dedup-family tasks 9/10 → 0/10) but conf_strat's d3-heavy quota is sub-threshold (0.76); think-mode diversity untouched. Registered prediction for the held-back conf_global think eval: d1 cov_any ≈ 0.80–0.88, losses on dedup tasks — result: 0.88, uniq 8.6, all 3 residual losses in the dedup/unique family (3/3 confirmed).

Interpretation

The verifier is load-bearing at the TRAINING seat, not the JUDGING seat. As a selector (C41/C46), P(True) approaches execution; as a training filter it fails — not because the judge ranks badly (0.845) but because banking at this dose tolerates almost no confidently-wrong data, and the wrongs a confidence filter admits are precisely the plausible ones. The judge itself is robust to self-training (ranks survive, scores inflate on the self-distribution), so the flywheel's binding constraint is not feedback collapse but dose (C24): scale the harvest so the top-rank slice is pure AND diverse, or keep the executable verifier. Deployment rules: think-judge (never no-think) on computational substrates; filter by rank within depth strata (score-mass quotas), never by a fixed threshold.

Knowledgebase Update

  • Program evidence updated: posttraining_and_adaptation × evidence_conditioned_selection
  • Claim ledger updated: C47 (this experiment); C18 annotated with the eval audit + dose-law framing

Artifacts

  • src/ — shared banking substrate (families list-DSL, gen_lib with the P(True) judge, code_env)
  • scripts/harvest_pool.py (shared pool + oracle + P(True) annotations, AUROC canary), build_arms.py (matched-size exec/conf/rand sets), train_lora.py (C18-identical), eval_ladder.py (frozen paired eval, C24 version), calib_eval.py (fixed-judge-set calibration survival), run.py, analyze.py
  • data/ — train_tasks, pool, per-arm training sets, frozen eval, judge set
  • runs/ — adapters (moved external before commit), eval/calib JSONs
  • reports/ — design_review.md, report.md, artifact_manifest.yaml

Report

Rendered from reports/report.md

Summary

Split verdict, and the split is the finding. At the capability seat: no — in the one eval cell where execution-verified banking produced a real gain (depth-2 think greedy 0.08 → 0.24, CI [+0.04, +0.32]; a post-hoc cell — no pre-registered cell cleared the bar), the pre-registered trichotomy RULE classifies confidence-filtered banking exactly on the no-filter floor (both +0.04): conf~rand, despite the confidence arm being ~15× purer than random (0.43 vs 0.03). At the judge seat: yes, with a caveat — think-judge discrimination fully survives being trained on its own approvals (fixed-set within-task AUROC 0.872 → 0.883), but P(True) scores inflate on the model's own post-banking distribution (P(True) on own incorrect candidates 0.091 → 0.204, doubled). A flywheel that filters by rank keeps its filter at round 2; one that filters by a fixed threshold silently degrades. Two upstream findings condition everything: the no-think P(True) judge is at chance within-task on this substrate (C46's law is substrate-scoped), and CoT judging rescues it (0.47 → 0.85 within-task) — verification of computational correctness is itself a serial computation (C44's law reaches the judge seat).

Research Program Fit

posttraining_and_adaptation × evidence_conditioned_selection: first experiment to connect the banking arc (C11C24: self-training needs an executable verifier at every rung) to the confidence arc (C40C46: a calibrated verification-free judge exists). The connection fails at the training seat and succeeds at the judging seat, which cleanly relocates where the verifier is load-bearing.

Method

Shared candidate pool: 90 procedural list-DSL identification tasks (depths 1/2/3 = 20/35/35), K=40 think-mode samples each → 2,130 unique candidates, oracle-graded (unique-candidate pass rate 0.033) and judged twice: no-think P(True) at harvest, think-P(True) (judge_pool_think.py, budget 512) after the gate-forced pivot. Four matched-size arms (n=70 pairs, hard trim; identical C18 QLoRA recipe r32/α64, 3 epochs, lr 2e-4, no-think prompt→code; the ONLY variable is the keep-test): exec (execution-verified, C18-identical keep rule — the ceiling, purity 1.0, depth mix 43/20/7), conf_strat (PRIMARY verifier-free arm: depth-stratified top think-P(True), per-depth quotas ∝ judge-score MASS, purity 0.429, mix 15/30/25), conf_global (ablation: naive global top-score, purity 0.614, mix 36/29/5), rand (draw-frequency-weighted floor, purity 0.029). Frozen paired eval (75 held-out tasks, behavioral func-sig + op-composition dedup, 0 leakage), 25/depth, K=16, greedy@1 + coverage. Pre-registered run/stop gate and trichotomy decision rule; paired-bootstrap CIs on every delta; calibration survival on a fixed judge set (777 identical base-generated candidates judged by every arm) plus a self-distribution pass (each model think-judges its OWN K=8 eval-task candidates — the round-2 flywheel condition).

Results

1. The no-think judge reads difficulty, not correctness, on this substrate (gate stop). Pooled no-think P(True) AUROC 0.749 but within-task 0.471 = chance (mean P(True) by depth 0.38/0.25/0.20 merely tracks solvability). C46's within-problem discrimination (0.74 MBPP, 0.78 HumanEval) does not transfer to a substrate where correctness is only computable (mentally execute the candidate on 8 examples), not semantically readable from a docstring.

2. Serial compute rescues the judge. judge_think (CoT before the same A/B verdict token) lifts within-task AUROC 0.471 → 0.845 on the full pool (pooled 0.961; diagnostic subset 0.49 → 0.802), P(True) correct/incorrect 0.61/0.08 full-pool (0.58/0.10 on the diagnostic subset) — with thinking budget-truncated at 512 tokens in 99% of judgments. C44's serial-compute law governs the judge seat: verifying a computational candidate IS a serial computation. This is what made the experiment runnable verifier-free: the conf arms rank on think-P(True); the gate passed (pool AUROC 0.961, purity gap 0.400).

3. MAIN — trichotomy verdict: conf~rand; purity is not the binding constraint. No pre-registered cell cleared the exec-gain ≥ 0.10 bar (see Result 4). The only significant pre-registered exec effect is depth-2 think cov_frac +0.035 (CI [+0.003, +0.072]) — concentration of sample mass on already-covered tasks, not expansion. In the post-hoc cell (labeled .POSTHOC in runs/verdict.json; added after seeing raw evals) exec moved hard — depth-2 think greedy 0.08 → 0.24 (CI [+0.04, +0.32], p=0.011):

armΔ vs base (d2 think greedy)purityrole
exec+0.160 (CI [+0.04, +0.32])1.000ceiling
conf_strat+0.040 (n.s.)0.429conf~rand
rand+0.040 (n.s.)0.029floor

Recovery ratio (conf−base)/(exec−base) = 0.25, joint-bootstrap CI [−1.0, +1.0] (uninformative at n=25). The ~15× purity advantage bought zero deployable gain — only the 100%-pure arm moved. Banking is far less noise-tolerant than selection (C41/C46's seat): 40% confidently-wrong pairs — plausible near-misses, by construction of the filter — erase what 100%-pure data teaches. Suggestive (multiple-comparison caveat): conf_strat is the only arm that did NOT collapse depth-1 no-think coverage (Result 6) and shows small d1 think cov_frac wins over both rand (+0.110, CI [+0.018, +0.207]) and exec (+0.090, CI [+0.005, +0.185]).

4. The exec ceiling itself is dose-limited: C18's headline was a low-dose overestimate. The exec arm is the first matched-dose re-run of the C18 recipe (20 d2 pairs / 8 distinct d2 functions vs C18's 24 / 9) under the strict C24-standard eval — and C18's depth-2 coverage expansion (0.15 → 0.45) is flat here (0.28 → 0.28). Lineage audit: (a) C18's headline was 3/20 vs 9/20 (Fisher p=0.082) on a leak-permissive eval — exact-ops dedup only; 3/20 of its d2 eval tasks were train-identical under the canonical probe-set func-sig (the same criterion the frozen eval dedups on), leak-excluded 2/17 vs 7/17 (p=0.118); (b) the expansion effect is real but was re-established at 3.5× the d2 dose — C22 0.225 → 0.425 (n=40), C23 0.175 → 0.50 (n=40, p=0.004, 83 pairs / 26 distinct functions); (c) under C24's diversity dose law, 8–9 distinct d2 functions sit at the bottom of the curve where a null at n=25 is expected. Consequence: at matched dose the exec ceiling is intrinsically small, so the trichotomy had little headroom — conf~rand holds in the one cell that cleared the bar, with n=25 power caveats.

5. Calibration survives banking as a RANKER; scores inflate on the self-distribution. Fixed judge set (777 identical candidates, 47 correct; think judge = the flywheel's filter):

modelwithin-AUROCpooledP(True)\|correctP(True)\|incorrect
base0.8720.9610.6130.098
exec-banked0.8730.9620.7080.149
conf_strat-banked0.8830.9570.6820.141

Self-distribution (own think-mode K=8 eval-task candidates — the round-2 harvest):

modelwithin-AUROCpooledP(True)\|incorrectown pass-rate
base0.8420.9300.0910.120
conf_strat-banked0.8780.9230.2040.093

Discrimination is untouched — even for the arm trained on its own judge approvals; the self-reinforcement collapse hypothesis (falsifier c) is dead. But P(True) on own incorrect candidates doubles (fixed-set drift only +0.043 → the inflation is distribution-specific).

6. Banking reallocates the no-think proposal distribution; CoT shields the think one. Banked arms collapse depth-1 no-think coverage@16 (base 0.72 → exec 0.36 / conf_global 0.32 / rand 0.40; conf_strat 0.76 does NOT collapse) while greedy stays flat. Per-task paired forensics: zero-sum mode reallocation, not capability loss — correct d1 samples per 400 are conserved across arms (126/115/130/106/108) while covered-task count crashes 18 → 9/8/10; the same correct mass gets crammed onto the banked program family. All four banks' d1 pairs come exclusively from the arithmetic-map/reorder/slice families and 0% from dedup_adjacent/unique_stable; exactly that family goes from 9/10 tasks covered (66 correct samples) at base to 0/10 (0) under exec/conf_global. Dose-dependent (exec banks 43 d1 pairs → collapse; conf_strat only 15 via its d3-heavy score-mass quotas → sub-threshold, 7/10 dedup tasks survive) and correctness-dependent (rand collapses by the other route: mass shifts toward wrong programs, diversity retained). Training pairs are NO-THINK, so the sharpening lives in the immediate-answer prior; CoT re-derives the answer and shields the think distribution (think-mode unique-programs unchanged, 7.7 → 7.8–9.0). Registered prediction (logged before running): the missing conf_global think eval should show d1 cov_any ≈ 0.80–0.88 with residual losses on dedup tasks, vs its 0.32 no-think collapse. Test result: 3/3 — d1 cov_any 0.88 (top of the predicted band), uniq 8.6 (predicted 7–9), and all three residual lost-vs-base tasks are exactly the dedup/unique family (unique_stable, dedup_adjacent, unique_stable). The out-of-sample test confirms the mechanism.

Controls

  • rand is draw-frequency-weighted (what "bank with no filter" means); uniform-over-unique would deflate the floor. Matched size is a hard trim (C23 count-confound), matched optimizer steps.
  • Score-mass quotas for conf_strat exist because candidate-count quotas provably allocate slots where wrong candidates explode (d2: 915 wrong / 20 correct; d3: 1066 / 7) — the attempt-2 lesson.
  • The fixed judge set isolates judge-change from candidate-change (every arm judges the identical 777 candidates); the inflation headline is P(True)-on-INCORRECT drift, since overall mean drift confounds inflation with real ability gain.
  • No-think judge secondary control: its within-task AUROC improves after banking (0.679 → 0.71–0.82: exec 0.815, rand 0.800, conf_strat 0.792, conf_global 0.713) in every arm including rand (97% wrong pairs) — a generic task-domain-SFT effect on the readout, not learning of correctness. (It also shows the no-think judge is not uniformly at chance: on the cruder no-think error distribution it is partially informative, 0.679; on the plausible think-harvest near-misses it is chance, 0.471.)

Oracle Versus Deployable Evidence

Oracle (full_pass) appears ONLY in: exec's keep-test (the ceiling arm by design), the experiment-level gate, and post-hoc purity/calibration REPORTING. The conf arms' selection reads nothing but the model's own think-P(True) logit + generator metadata (depth, draw frequency); matching size to exec leaks one scalar (documented design constant). The deployable recipe this experiment licenses: think-judge rank filtering (no oracle) — but for training data, its 0.43 purity was not enough to matter at this dose.

Interpretation

The verifier is load-bearing at the TRAINING seat, not the JUDGING seat. Confidence transfers across seats asymmetrically: as a selector (C41/C46) P(True) approaches execution; as a training filter it fails not because the judge ranks badly (0.845 within-task) but because banking tolerates almost no confidently-wrong data at this dose — and the wrongs a confidence filter admits are precisely the plausible ones. Meanwhile the judge itself is robust to self-training (ranks survive, scores inflate), so the flywheel's failure mode is not feedback collapse but simple dose: at 70 pairs the exec ceiling is already at the bottom of C24's diversity dose curve, and a 0.43-pure filter recovers none of it. Three laws sharpen: C46 is substrate-scoped (single-token readouts work where verification is shallow; computational correctness needs serial compute — C44 governs the judge seat too); C24's dose law now has a matched-dose null confirming its bottom end (and retro-explains C18 as a low-dose overestimate on a leak-permissive eval); and the banking arc gains a distribution rule — no-think SFT reallocates the no-think proposal prior toward banked families while CoT shields the think distribution, so flywheel harvests (think-mode) survive banking that would look catastrophic no-think.

Next Experiments

  1. Scale the harvest, not the filter: K=40 → K=160+ on more tasks, then take the top-rank slice at conf_strat's quota — does purity×diversity at larger dose cross the banking threshold verifier-free? (The direct test of "dose is the binding constraint".)
  2. Threshold-vs-rank flywheel round 2 in anger: run one actual round-2 harvest+train with rank filtering (predicted fine) vs fixed-threshold filtering (predicted purity decay via the +0.113 inflation).
  3. Semantically-readable substrate (natural-language task descriptions, C37-style): does the NO-think judge's within-task discrimination return, making the cheap filter viable there?
  4. Judge-budget dose-response: think-judge scores fell by depth (0.36/0.10/0.07 mean by depth) at budget 512, 99% forced-close — does budget 1024/2048 lift deep-candidate ranking?

Artifact Manifest

runs/verdict.json (full stats incl. .POSTHOC labels), runs/arms_summary.json (gate + arm composition), runs/judge_think_diag.json (Result 2 diagnostic), runs/calib*.json (Result 5: fixed / think / self-think), runs/eval_*.json (10 arm×mode evals, 25 tasks/depth), analysis/ verifier_free_banking.png, experiment_log.md (full saga incl. two failed attempts and the session crash), reports/design_review.md (pre-registration). Adapters (5 × 182 MB) moved out of the repo — external paths + regenerate commands in reports/artifact_manifest.yaml.

Experiment log 10

Show the running log (10 entries, 2026-07-07 → 08)

2026-07-07 design + review

Scaffolded from the C18 harness (harvest/train_lora) + C24 frozen-paired eval; the P(True) judge (judge_nothink) was already present, unused, in every banking experiment's gen_lib.py. Adversarial design review (reports/design_review.md): sound_with_fixes; all seven must-fixes applied (smoke/full artifact separation, hard gate, stratified-conf primary arm, draw-weighted rand, matched-size hard trim, self-distribution calibration, trichotomy decision rule). Two smoke passes green, second covering adapter-loaded eval/calib and analyze.

2026-07-08 attempt 1: CUDA OOM (two stacked environment bugs)

Full harvest OOM'd at batch 48 fifteen minutes in. Causes: (1) launched without PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (mandatory per docs/compute_environment.md — C18 ran this exact config WITH it); (2) torch 2.12 raises generation OOM as torch.AcceleratorError, which gen_lib's six batch-halving catch sites (except torch.cuda.OutOfMemoryError) do not catch — the designed graceful degradation became a hard crash. A third bug hid the failure: cmd | grep | tee | tail reports the LAST pipe stage's exit code — the dead run looked like exit 0. Fixes: run.py sets the alloc env var itself; gen_lib catches OOM_ERRORS = (OutOfMemoryError, AcceleratorError); relaunches use pipefail.

2026-07-08 attempt 2: GATE STOP — the no-think judge reads difficulty, not correctness

Harvest succeeded (90 tasks × K=40 → 2,130 unique candidates). The pool is needle-in-haystack, unlike MBPP: unique-candidate purity 0.033 (draws ~0.21) — wrong programs EXPLODE combinatorially at depths 2–3 (~1,000 distinct wrong each vs 20/7 correct), while correct answers concentrate + duplicate at depth 1.

The pre-registered gate stopped the run before training, and its numbers contain the experiment's first finding: pooled P(True) AUROC 0.749 but WITHIN-task AUROC 0.471 — chance. All apparent signal is between-task difficulty (mean P(True) by depth 0.38/0.25/0.20 tracks solvability). The C46 within-problem discrimination (0.74 on MBPP) does NOT transfer to this substrate: here correctness is only COMPUTABLE (mentally execute the candidate on 8 examples), not semantically readable from a docstring. Also caught: pool-candidate-count quotas for conf_strat allocate slots exactly where wrong candidates are most numerous (perfect-ranker purity ceiling ≈ 0.44; actual 0.10); conf_global still concentrated purity 8× over random (0.243 vs 0.029) on the between-task component alone.

2026-07-08 diagnostic: serial compute RESCUES the judge (0.49 → 0.80 within-task)

Hypothesis from C44 (induction is a serial-compute limit) applied to the JUDGE seat: verifying a candidate transform requires serially executing it; a single no-think forward pass cannot. Test: judge_think (CoT before the A/B verdict) on 524 pool candidates (all 70 correct + 6 wrong/task). RESULT: within-task AUROC 0.49 → 0.802; pooled 0.719 → 0.924; mean P(True) correct/incorrect 0.48/0.26 → 0.58/0.10. Verification of computational correctness is itself a serial computation — C44's law governs metacognition too. Notable: thinking was budget-truncated (forced-close) in 99% of judgments and still reached 0.80 — the first 512 thinking tokens carry the decisive execution steps. (Tooling: judge_think returns (p_values, forced_flags) — a tuple, not a list.)

2026-07-08 attempt 3: pipeline relaunched on the think-P(True) filter

Pivot codified: new stage judge_pool_think.py (think-judge the full pool); build_arms.py ranks conf arms on p_true_think, and conf_strat quotas switch from candidate-count-proportional to JUDGE-SCORE-MASS-proportional (the count version provably allocates slots where wrong candidates explode); calibration survival now measured on the THINK judge (fixed set for base/exec/conf_strat; self-distribution for base/conf_strat) since that is the filter the flywheel would actually use. Same gate, same trichotomy. Gate PASSED: pool think-AUROC 0.961 pooled, purity gap conf_strat-rand 0.400; arms at matched n=70: exec purity 1.0 {d1:43,d2:20,d3:7}, conf_strat 0.429 {15,30,25}, conf_global 0.614 {36,29,5}, rand 0.029.

2026-07-08 session crash mid-pipeline (harness, not experiment)

The CLI session died with repeated EACCES while the pipeline ran; the orphaned run.py SURVIVED (trains + evals kept writing). No permission error appears anywhere in the run logs -- the crash was the harness's own session files. Recovery: verify PID alive, re-arm a log monitor from the new session, continue. Lesson: launch long pipelines so they don't die with the session (they did not -- subprocess detach was sufficient), and make every stage idempotent so a relaunch resumes from disk (it is).

2026-07-08 eval grid complete: trichotomy = conf~rand in the only cell exec moved

NO pre-registered cell clears the exec-gain >= 0.10 bar. The only significant pre-registered exec effect is d2 think cov_frac +0.035 CI[+0.003,+0.072] (concentration on already-covered tasks). POST-HOC cell (labeled .POSTHOC in verdict.json -- added after seeing raw evals): d2 think greedy base 0.08 -> exec 0.24 (CI[+0.04,+0.32], p=0.011); there conf_strat +0.04 = rand +0.04 exactly -> trichotomy conf~rand. Purity 0.43 (~15x rand) bought zero deployable gain; only the 100%-pure exec arm moved anything. Suggestive (multiple-comparison caveat): conf_strat is the only arm not collapsing d1 no-think coverage and shows small d1 cov_frac wins over BOTH rand (+0.110) and exec (+0.090) in think mode.

2026-07-08 forensics (read-only agents, while calibration runs)

d1 no-think coverage collapse (0.72 -> 0.36 exec / 0.32 conf_global / 0.40 rand, but 0.76 conf_strat) is zero-sum mode reallocation, not capability loss. Correct d1 samples/400 are CONSERVED across arms (126/115/130/106/108) while covered tasks crash 18 -> 9/8/10; correct mass gets crammed onto the banked program family. All four banks' d1 pairs come exclusively from the map/reorder/slice families (square/negate/drop_k/ rotate/sort/reverse/mul_k/abs) and 0% dedup_adjacent/unique_stable -- and the dedup/unique family goes 9/10 tasks covered (66 correct samples) at base to 0/10 (0) under exec/conf_global. Dose x correctness: exec banks 43 d1 pairs (collapse), conf_strat only 15 (sub-threshold, 7/10 dedup tasks survive), rand collapses by the other route (97% wrong pairs shift mass off-target with diversity retained). Training pairs are NO-THINK, so the sharpening lives in the immediate-answer prior; CoT re-derives and shields the think distribution (think uniq unchanged 7.7 -> 7.8-9.0). Flywheel-relevant: round-2 harvest is think-mode, i.e. on the SHIELDED distribution. REGISTERED PREDICTION before running the missing cell: conf_global_think d1 cov_any ~0.80-0.88, uniq ~7-9, residual losses concentrated on dedup/unique tasks (vs its 0.32 no-think collapse). Will run post-pipeline when the GPU frees.

"C18 fails to replicate" is the WRONG frame -- it is C24's dose law. Audit of the lineage: C18's 0.15->0.45 was 3/20 vs 9/20 (Fisher p=0.082, n=20) on a leak-permissive eval (exact-ops dedup only; 3/20 d2 eval tasks train-identical under the canonical probe-set func-sig; leak-excluded 2/17 -> 7/17 p=0.118). The d2 expansion DID survive frozen leak-proof evals -- C22 0.225->0.425 (n=40) and decisively C23 0.175->0.50 (n=40, p=0.004) -- but at 3.5x the d2 dose (83 pairs / 26 distinct functions vs C18's 24 / 9). Our exec arm is the first MATCHED-DOSE re-run (20 pairs / 8 distinct d2 functions) under the strict eval: flat cov_any 0.28->0.28 is the bottom of the C24 dose curve, not an anomaly. Correct statement: C18's effect size was a low-dose overestimate; cite against C23 + C24, not as a standalone replication failure. Consequence for THIS experiment: at matched dose the exec ceiling is intrinsically small, so the trichotomy had little headroom -- the conf~rand verdict holds in the one cell that cleared the bar, with n=25 power caveats.

2026-07-08 calibration complete: the judge survives banking as a RANKER

Fixed judge set (777 identical base no-think candidates, 47 correct): think-judge within-AUROC base 0.872 -> exec 0.873 / conf_strat 0.883 -- discrimination untouched, even for the arm trained on its own approvals (falsifier c dead). Scores inflate mildly on the fixed set (P(True)|incorrect 0.098 -> 0.149/0.141) and HARD on the self-distribution (base 0.091 -> conf_strat 0.204, doubled; own pass-rate 0.120 -> 0.093, no capability gain). Flywheel rule: rank filters survive round 2, fixed thresholds silently rot. Secondary: the NO-think judge's within-task AUROC improves after banking in EVERY arm including rand (0.679 -> 0.71-0.82) -- a generic task-domain-SFT readout effect, not correctness learning; and it is not uniformly chance (0.679 on crude no-think errors vs 0.471 on plausible think-harvest near-misses -- the failure is distribution-dependent).

2026-07-08 registered prediction test: 3/3 CONFIRMED

Post-pipeline, ran the held-back conf_global think eval against the pre-logged prediction (d1 cov_any ~0.80-0.88, uniq ~7-9, residual losses on dedup/unique tasks): measured cov_any 0.88, uniq 8.6, and ALL THREE residual lost-vs-base tasks are the dedup/unique family (unique_stable, dedup_adjacent, unique_stable). The mode-reallocation + CoT-shield mechanism survives an out-of-sample test. Final analyze covers 10 eval tags / 20 paired cells; verdict.json + figure final. Claim C47 filed; C18 annotated with the eval audit; playbook updated; adapters (5 x 182MB) moved to scratchpad/verifier_free_banking_artifacts/ per reports/artifact_manifest.yaml.

Figures 1

verifier free banking
verifier free banking · analysis/

Data files 14

Result tables and metrics copied from the experiment folder — preview inline or open the raw file.

Reproduce

Smoke test

python scripts/run.py --smoke

Full run

python scripts/run.py && python scripts/eval_ladder.py --tag conf_global_think --K 16 --n-per-depth 25 --depths 1 2 3 --adapter runs/lora_conf_global --think && python scripts/analyze.py

Run steps are documented inside the experiment folder (README and scripts).

Browse the experiment folder on GitHub ↗