You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
π¬ Active β verdict converging. Across model scale (46Mβ476M) and data budget (6β100%), capacity-controlled LoRA FT matches the frozen probe but never beats it β the two scale together (same slope, converging to a tie). With the cost asymmetry (probe = one auto-tuned knob; FT needed a capacity sweep + ~8 knobs to reach parity), the frozen linear probe is the better default for Mendelian-missense VEP on this data budget. But broader data is a lever: a single FT trained on all consequences pooled beats the per-subset probe on the small/weak subsets (3β²/5β²UTR, ncRNA, distal) while losing on the strong ones β so to beat the probe, spend on more labels, not more FT hyperparameters. Detail: iter2, iter3; findings below.
Question
#341 showed a frozen-embedding linear probe beats the zero-shot LLR on Mendelian missense, by a margin that grows with scale (+0.195 per-chromosome-weighted AUPRC at 4B), because the missense likelihood readout degrades over training while the representation keeps the signal β a cheap linear head recovers it.
Does unfreezing the backbone recover more? Does LoRA supervised fine-tuning β the same labels, the same pooled feature, but the backbone now trainable end-to-end β beat the frozen linear probe on Mendelian-missense per-chromosome-weighted AUPRC? If yes, there is missense signal that is not linearly decodable from the frozen embedding but is reachable by adapting the backbone. If no, the frozen probe is the ceiling for this label budget and the lever is more labels, not more trainable parameters.
This is EDA β a bounded look at the readout landscape, no preregistered hypothesis. Output is insights (does FT beat the probe, does the answer depend on scale, is it an overfitting story), not a locked leaderboard. Train split only; the test split is held for a future pre-registered final pass.
Honest prior β expect this to be hard.#180 found supervised heads on pooled embeddings did not materially beat zero-shot, and #314 explicitly deferred fine-tuning ("MLP/XGBoost/LoRA β #180 found no lift; revisit only if linear plateaus"). A positive, scale-growing result would be the notable finding; a null almost certainly means we couldn't out-regularize the data scarcity (next section), not that the backbone lacks signal.
The central risk β overfitting is the whole investigation
With ~4,910 training missense SNVs (491 positives) after holding out chr1, and a LoRA-adapted 46Mβ4B backbone carrying 10Β³β10βΆ trainable parameters, the model will memorize the training set by default β the Phase-0 overfit-100 check expects train AUPRC β 1.0. The frozen probe (β few-thousand params, hard L2, StandardScaler) is a strong baseline precisely because it can't overfit. So this investigation has exactly one failure mode and one open question, and they are the same thing:
Can we regularize the fine-tune hard enough that it generalizes to the held-out chromosome better than the frozen probe β or does every recipe overfit past the probe's generalization?
Therefore the exploration is organized around regularization as the primary sweep axis and the train-vs-validation generalization gap as the primary instrument β logged at every step of every run, not just at the end.
The diagnostic is the trajectory vs the probe baseline line. For every run we log validation-chromosome AUPRC per training step against the constant frozen-probe baseline (the number to beat): does the trajectory climb above the probe before it inevitably turns down (overfitting), or plateau below it (nothing to gain)? Early stopping picks the peak; the height of that peak over the probe is the result.(We can optionally warm-start the head from the fitted probe so the curve departs exactly from the probe line β but the comparison does not depend on it; see Architecture.)
Anti-overfitting levers, ranked (what we actually sweep):
lever
mechanism
priority
Training length / early stopping
fewer steps = closer to the frozen init; the dominant regularizer here. One run's val-vs-step curve gives the whole axis for free.
SGD-noise-as-regularizer (smaller) vs cleaner early-stop signal (larger); genuinely unclear at this data scale
exploratory / secondary
Meta-risk β don't overfit the selection. The early-stop/hyperparameter signal is a single held-out chromosome (noisy β chr3 β 71 missense positives). Searching a large grid against one noisy val chrom overfits the selection. Mitigations: keep each sweep coarse and staged (vary one lever family at a time, not a full grid), rotate / inner-CV the val chrom, and treat any single-fold win as provisional until the full nested-LOCO confirms it.
Scope
In: LoRA supervised fine-tuning (missense only), the matched Mendelian train split, the scaling ladder (subset of the 8 rungs), per-chromosome-weighted AUPRC, single-fold dev + full nested-LOCO honest eval, baselines = frozen probe + zero-shot LLR.
Out (deferred): full-FT / non-LoRA PEFT; external / larger labeled sets; non-missense consequence classes; complex / SGE / QTL datasets; self-supervised readout-repair; test-split final pass; productionizing into evals_v2.
Approach
Baselines to beat (same rows, same metric, paired)
The frozen probe and LLR recompute per-fold directly from the existing S3 scores parquets β the scaling-v0.5-*/mendelian_traits.parquet files already carry emb_ref/emb_alt (from the #341 embed overlay) and llr_fwd/llr_rc. No GPU needed for the baselines.
size
frozen probe
zero-shot LLR
Ξ (probe β LLR)
46M
0.425
0.444
β0.019
76M
0.438
0.479
β0.041
128M
0.485
0.489
β0.004
255M
0.522
0.487
+0.035
476M
0.534
0.454
+0.080
1B
0.553
0.428
+0.125
2B
0.577
0.389
+0.188
4B
0.586
0.391
+0.195
(#341 numbers; per-chromosome-weighted AUPRC on missense, train split.) The frozen probe column is the number LoRA FT must beat at each scale.
5,800 SNVs / 580 positives (exactly 1:9; matched-pair β match groups pit a positive against 9 nearest-neighbor negatives on (consequence, chrom) strata + continuous TSS/exon-distance features, so this is an across-gene, not within-gene, task).
12 train chromosomes:{1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, X} (odd + X, by upstream split construction; even + Y = held-out test, never loaded). CV just groups by the chrom column (like traitgym_nested_oof's LeaveOneGroupOut) β there is no in-repo odd/even filter to replicate.
Leave-out-chr1 dev fold: train on ~4,910 missense (491 pos), eval on chr1's 890 missense / 89 positives β enough positives for a usable dev signal, few enough to keep iteration fast (but noisy β a single-fold point estimate, treat as directional).
Architecture (siamese β mirrors the probe's feature exactly)
A shared LoRA-adapted backbone processes the ref and alt 255 bp+BOS windows; each is entire-window mean-pooled (fp32, BOS excluded β the #341 pooling); the feature is [pool_ref, pool_alt β pool_ref] (the probe's concat_ref_delta); a single linear head β logit, trained with BCE. Same input construction as the frozen probe β the only difference is that the backbone (via LoRA) is now trainable. RC: reverse-complement as train-time augmentation (random strand per example β free regularization, biologically motivated), FWD+RC pooled-average at eval (matches the probe + pipeline's two-pass scoring).
Head init β fresh head; probe kept as an external baseline line (default). The probe head is calibrated to the FWD+RC-averaged embedding the pipeline manufactures β and these are causal, unidirectional models, so that "bidirectionality" is entirely the strand-average (+ mean-pool), not something a single forward produces. We therefore do not warm-start from the probe by default: we train a fresh linear head and keep the frozen probe as a constant baseline line to clear. Optional diagnostic: warm-starting the head (fold StandardScaler in; LoRA Ξ=0) makes the eval curve depart exactly from the probe line at step 0 β but that init is only honest if training also consumes the averaged feature (otherwise the probe head is fed single-strand inputs it wasn't calibrated for); since we keep RC as a train-time augmentation, the warm-start stays an add-on, not a dependency. Deferred representation arm: a causal model's more native per-window summary is the last-token hidden state (attended over the whole context) rather than the probe's mean-pool + strand-average β possibly a better FT target, but it breaks apples-to-apples with the probe, so not iter-1.
What we reuse vs build
Reuse (no rework):Genome window reads (src/marin_dna/data/genome.py), the variant-window + RC + SNV tokenization (transform_llr_clm / _get_variant_window, src/marin_dna/data/transforms.py), the HF load pattern AutoModelForCausalLM.from_pretrained(..., trust_remote_code=True) (src/marin_dna/pipelines/evals/inference.py), the entire-window pooling span [n_prefix, n_prefix+window_size) (src/marin_dna/model/scoring.py), the frozen-probe recompute traitgym_nested_oof / pair_feature_from_bundle (src/marin_dna/pipelines/evals/variant_probe.py), and the scorerper_chrom_weighted_ap / per_chrom_ap_table (src/marin_dna/pipelines/evals/metrics.py) verbatim.
Build (there is zero training/PEFT code in the repo today): add peft as a dep; a siamese LoRA classifier module + training loop + chromosome-grouped CV harness, all in a new testedsrc/marin_dna/pipelines/finetune/ (library home per Research Code Values). Run drivers + SkyPilot YAML in scripts/. This branch won't merge to main (research; cited via commit-pinned permalinks).
CV & leak-proofing (the discipline β hyperparameters stay exploratory)
Train split only. Even-chrom test never touched.
Dev fold (fast loop): hold out chr1 as the fold's test; hold out one more train chromosome (e.g. chr3) as a validation chrom for early stopping and hyperparameter selection; train on the other 10. Report AUPRC on chr1. chr1 is never peeked at for selection (mirrors nested-LOCO).
Honest final: full nested leave-one-chromosome-out over all 12 train chroms (inner val chrom per fold for early stopping), OOF predictions concatenated and scored once as per-chromosome-weighted AUPRC, with paired cluster-bootstrap deltas vs the frozen probe and LLR on identical rows.
Fixed defaults (everything not being swept)
AdamW; fresh linear head (frozen probe kept as an external baseline line, not the init); 10% warmup β cosine; bf16 (+ gradient checkpointing β₯1B); plain BCE (pos_weight a later knob); β₯3 seeds, report mean Β± SD (small-data FT is high-variance); RC augmentation in training, FWD+RC pooled-average at eval; per-step train + val-chrom AUPRC logged for every run (the main instrument). Starting points for the swept regularization levers (see the levers table above β seeds for the sweep, not settled values): LoRA r = 8 / Ξ± = 16 / dropout = 0.05, attention q/k/v/o, LoRA lr 1e-4 / head lr 1e-3, weight decay 0, early-stop patience 3 on val-chrom AUPRC with a generous max-epoch cap.
Metric
Per-chromosome-weighted AUPRC on missense (per_chrom_weighted_ap, the #341 / #331 / TraitGym primary), paired vs frozen probe and vs LLR (paired bootstrap resampling match_group on the full CV). Single-fold dev = AUPRC on chr1's missense (one number; report seed spread).
Plan (order of exploration)
Phase 0 β de-risk + instrument (46M, cheap). Confirm gradients flow through LoRA; overfit-100 sanity (train AUPRC β ~1.0). Wire the per-step train + val-chrom AUPRC logging that is the investigation's main instrument. Confirm the recomputed frozen-probe baseline line matches Scaling ladder Γ Mendelian VEP: linear probe vs zero-shot LLR through the official pipeline (#320/#331)Β #341 on the chr1 fold (the number to beat); if the optional warm-start is enabled, check it reproduces that AUPRC at step 0. Recompute the frozen-probe + LLR baselines per-fold from the S3 parquets (no GPU) β the exact numbers to beat.
Phase 1 β can we out-regularize the data scarcity? (255M, leave-out-chr1). 255M: probe clearly beats LLR (0.522 vs 0.487) yet iterates fast. Start with ONE warm-started run at defaults and read the val-AUPRC-vs-step trajectory β the single most informative run: does val ever exceed the frozen probe, and where does it peak vs. overfit? Then stage the regularization sweep to lift that peak above the probe, one lever-family at a time (avoid selection-overfitting), 3 seeds each: (1) training length β free from the trajectory; (2) LoRA capacity β rank β and top-K-layers-only; (3) weight decay + dropout β; (4) LR β. Select on the val chrom; report chr1 AUPRC vs probe. Gate: if no regularization lifts val above the probe, that's a clean "can't out-regularize the scarcity at this scale" β decide whether large scale (more probe-over-LLR headroom) is worth a test before calling it, and flag the deferred more-labels arm as the implied lever.
Phase 2 β scale trend (best recipe, leave-out-chr1). Run the Phase-1 winner across {46M, 128M, 255M, 1B, 4B} (light per-model LR retune). Overlay three curves β LLR / frozen probe / LoRA FT β and read whether the FT-vs-probe gap grows, shrinks, or vanishes with scale.
Phase 3 β honest full CV (winners only). Full nested LOCO (12 folds) on the 2β3 most informative scales; per-chrom-weighted AUPRC + paired bootstrap vs probe & LLR. Train split only.
Current findings
iter1 (255M, leave-out-chr1, missense, single seed). Baselines: frozen probe 0.523, zero-shot LLR 0.489 (LLR full-12 recompute = 0.487 = #341 β). The default LoRA (rank 8) overfits from ~epoch 5 (val-loss bottoms epoch 4, train-loss β 0.06 by epoch 11) and scores 0.500 β below the probe, at the LLR floor. The dominant lever is capacity: rank 8β2 or top-4-layers both lift it to 0.524 β the probe, while weight-decay (0.510 @ 0.1) / LR barely move it. β with capacity control the fine-tune matches the frozen probe at 255M but does not clearly beat it. Point-per-config table + methodology in the iter1 comment.
iter2 β scale + data-scaling (comment). FT vs probe vs LLR across 46Mβ476M (chr1 missense): 46M .379/.362/.367, 76M .422/.415/.442, 128M .480/.505/.474, 255M .524/.523/.489, 476M .545/.548/.472 β FT tracks the probe at every scale, no crossover (matches it; β« LLR at large scale). Data-scaling (255M, subsample): FT and probe rise together and converge β FT has no steeper slope, so more data along this axis won't let it overtake. β LoRA FT matches but never beats the frozen probe, across scale and data budget; the probe wins on simplicity/robustness (1 auto-tuned knob vs ~8). Remaining: pooled-all-subsets (broader data, evaluated per subset).
Open questions / next steps
Now: trace FT vs frozen-probe vs zero-shot-LLR across the scaling ladder (46M β 476M) with the capacity recipe (low rank), single seed β does the FT-vs-probe relationship (a tie at 255M) change where the probe-over-LLR gap is large? Small rungs included, to watch the pattern evolve; per-scale chr1 baselines computed alongside.
Compute: β€476M on the A10G (no 4B / A100 yet); torch.compile + gradient accumulation to hold the effective batch constant as batch shrinks on bigger rungs.
Status
π¬ Active β verdict converging. Across model scale (46Mβ476M) and data budget (6β100%), capacity-controlled LoRA FT matches the frozen probe but never beats it β the two scale together (same slope, converging to a tie). With the cost asymmetry (probe = one auto-tuned knob; FT needed a capacity sweep + ~8 knobs to reach parity), the frozen linear probe is the better default for Mendelian-missense VEP on this data budget. But broader data is a lever: a single FT trained on all consequences pooled beats the per-subset probe on the small/weak subsets (3β²/5β²UTR, ncRNA, distal) while losing on the strong ones β so to beat the probe, spend on more labels, not more FT hyperparameters. Detail: iter2, iter3; findings below.
Question
#341 showed a frozen-embedding linear probe beats the zero-shot LLR on Mendelian missense, by a margin that grows with scale (+0.195 per-chromosome-weighted AUPRC at 4B), because the missense likelihood readout degrades over training while the representation keeps the signal β a cheap linear head recovers it.
Does unfreezing the backbone recover more? Does LoRA supervised fine-tuning β the same labels, the same pooled feature, but the backbone now trainable end-to-end β beat the frozen linear probe on Mendelian-missense per-chromosome-weighted AUPRC? If yes, there is missense signal that is not linearly decodable from the frozen embedding but is reachable by adapting the backbone. If no, the frozen probe is the ceiling for this label budget and the lever is more labels, not more trainable parameters.
This is EDA β a bounded look at the readout landscape, no preregistered hypothesis. Output is insights (does FT beat the probe, does the answer depend on scale, is it an overfitting story), not a locked leaderboard. Train split only; the test split is held for a future pre-registered final pass.
Honest prior β expect this to be hard. #180 found supervised heads on pooled embeddings did not materially beat zero-shot, and #314 explicitly deferred fine-tuning ("MLP/XGBoost/LoRA β #180 found no lift; revisit only if linear plateaus"). A positive, scale-growing result would be the notable finding; a null almost certainly means we couldn't out-regularize the data scarcity (next section), not that the backbone lacks signal.
The central risk β overfitting is the whole investigation
With ~4,910 training missense SNVs (491 positives) after holding out chr1, and a LoRA-adapted 46Mβ4B backbone carrying 10Β³β10βΆ trainable parameters, the model will memorize the training set by default β the Phase-0 overfit-100 check expects train AUPRC β 1.0. The frozen probe (β few-thousand params, hard L2,
StandardScaler) is a strong baseline precisely because it can't overfit. So this investigation has exactly one failure mode and one open question, and they are the same thing:Therefore the exploration is organized around regularization as the primary sweep axis and the train-vs-validation generalization gap as the primary instrument β logged at every step of every run, not just at the end.
The diagnostic is the trajectory vs the probe baseline line. For every run we log validation-chromosome AUPRC per training step against the constant frozen-probe baseline (the number to beat): does the trajectory climb above the probe before it inevitably turns down (overfitting), or plateau below it (nothing to gain)? Early stopping picks the peak; the height of that peak over the probe is the result. (We can optionally warm-start the head from the fitted probe so the curve departs exactly from the probe line β but the comparison does not depend on it; see Architecture.)
Anti-overfitting levers, ranked (what we actually sweep):
Meta-risk β don't overfit the selection. The early-stop/hyperparameter signal is a single held-out chromosome (noisy β chr3 β 71 missense positives). Searching a large grid against one noisy val chrom overfits the selection. Mitigations: keep each sweep coarse and staged (vary one lever family at a time, not a full grid), rotate / inner-CV the val chrom, and treat any single-fold win as provisional until the full nested-LOCO confirms it.
Scope
In: LoRA supervised fine-tuning (missense only), the matched Mendelian train split, the scaling ladder (subset of the 8 rungs), per-chromosome-weighted AUPRC, single-fold dev + full nested-LOCO honest eval, baselines = frozen probe + zero-shot LLR.
Out (deferred): full-FT / non-LoRA PEFT; external / larger labeled sets; non-missense consequence classes; complex / SGE / QTL datasets; self-supervised readout-repair; test-split final pass; productionizing into evals_v2.
Approach
Baselines to beat (same rows, same metric, paired)
The frozen probe and LLR recompute per-fold directly from the existing S3 scores parquets β the
scaling-v0.5-*/mendelian_traits.parquetfiles already carryemb_ref/emb_alt(from the #341 embed overlay) andllr_fwd/llr_rc. No GPU needed for the baselines.(#341 numbers; per-chromosome-weighted AUPRC on missense, train split.) The frozen probe column is the number LoRA FT must beat at each scale.
Data
bolinas-dna/evals_mendelian_traits@4aed58e5(PR #194 k=9 rebuild), train split,missense_variantsubset only:(consequence, chrom)strata + continuous TSS/exon-distance features, so this is an across-gene, not within-gene, task).{1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, X}(odd + X, by upstream split construction; even + Y = held-out test, never loaded). CV just groups by thechromcolumn (liketraitgym_nested_oof'sLeaveOneGroupOut) β there is no in-repo odd/even filter to replicate.Architecture (siamese β mirrors the probe's feature exactly)
A shared LoRA-adapted backbone processes the ref and alt 255 bp+BOS windows; each is entire-window mean-pooled (fp32, BOS excluded β the #341 pooling); the feature is
[pool_ref, pool_alt β pool_ref](the probe'sconcat_ref_delta); a single linear head β logit, trained with BCE. Same input construction as the frozen probe β the only difference is that the backbone (via LoRA) is now trainable. RC: reverse-complement as train-time augmentation (random strand per example β free regularization, biologically motivated), FWD+RC pooled-average at eval (matches the probe + pipeline's two-pass scoring).Head init β fresh head; probe kept as an external baseline line (default). The probe head is calibrated to the FWD+RC-averaged embedding the pipeline manufactures β and these are causal, unidirectional models, so that "bidirectionality" is entirely the strand-average (+ mean-pool), not something a single forward produces. We therefore do not warm-start from the probe by default: we train a fresh linear head and keep the frozen probe as a constant baseline line to clear. Optional diagnostic: warm-starting the head (fold
StandardScalerin; LoRA Ξ=0) makes the eval curve depart exactly from the probe line at step 0 β but that init is only honest if training also consumes the averaged feature (otherwise the probe head is fed single-strand inputs it wasn't calibrated for); since we keep RC as a train-time augmentation, the warm-start stays an add-on, not a dependency. Deferred representation arm: a causal model's more native per-window summary is the last-token hidden state (attended over the whole context) rather than the probe's mean-pool + strand-average β possibly a better FT target, but it breaks apples-to-apples with the probe, so not iter-1.What we reuse vs build
Reuse (no rework):
Genomewindow reads (src/marin_dna/data/genome.py), the variant-window + RC + SNV tokenization (transform_llr_clm/_get_variant_window,src/marin_dna/data/transforms.py), the HF load patternAutoModelForCausalLM.from_pretrained(..., trust_remote_code=True)(src/marin_dna/pipelines/evals/inference.py), the entire-window pooling span[n_prefix, n_prefix+window_size)(src/marin_dna/model/scoring.py), the frozen-probe recomputetraitgym_nested_oof/pair_feature_from_bundle(src/marin_dna/pipelines/evals/variant_probe.py), and the scorerper_chrom_weighted_ap/per_chrom_ap_table(src/marin_dna/pipelines/evals/metrics.py) verbatim.Build (there is zero training/PEFT code in the repo today): add
peftas a dep; a siamese LoRA classifier module + training loop + chromosome-grouped CV harness, all in a new testedsrc/marin_dna/pipelines/finetune/(library home per Research Code Values). Run drivers + SkyPilot YAML inscripts/. This branch won't merge tomain(research; cited via commit-pinned permalinks).CV & leak-proofing (the discipline β hyperparameters stay exploratory)
Fixed defaults (everything not being swept)
AdamW; fresh linear head (frozen probe kept as an external baseline line, not the init); 10% warmup β cosine; bf16 (+ gradient checkpointing β₯1B); plain BCE (
pos_weighta later knob); β₯3 seeds, report mean Β± SD (small-data FT is high-variance); RC augmentation in training, FWD+RC pooled-average at eval; per-step train + val-chrom AUPRC logged for every run (the main instrument). Starting points for the swept regularization levers (see the levers table above β seeds for the sweep, not settled values): LoRA r = 8 / Ξ± = 16 / dropout = 0.05, attention q/k/v/o, LoRA lr 1e-4 / head lr 1e-3, weight decay 0, early-stop patience 3 on val-chrom AUPRC with a generous max-epoch cap.Metric
Per-chromosome-weighted AUPRC on missense (
per_chrom_weighted_ap, the #341 / #331 / TraitGym primary), paired vs frozen probe and vs LLR (paired bootstrap resamplingmatch_groupon the full CV). Single-fold dev = AUPRC on chr1's missense (one number; report seed spread).Plan (order of exploration)
Current findings
iter1 (255M, leave-out-chr1, missense, single seed). Baselines: frozen probe 0.523, zero-shot LLR 0.489 (LLR full-12 recompute = 0.487 = #341 β). The default LoRA (rank 8) overfits from ~epoch 5 (val-loss bottoms epoch 4, train-loss β 0.06 by epoch 11) and scores 0.500 β below the probe, at the LLR floor. The dominant lever is capacity: rank 8β2 or top-4-layers both lift it to 0.524 β the probe, while weight-decay (0.510 @ 0.1) / LR barely move it. β with capacity control the fine-tune matches the frozen probe at 255M but does not clearly beat it. Point-per-config table + methodology in the iter1 comment.
iter2 β scale + data-scaling (comment). FT vs probe vs LLR across 46Mβ476M (chr1 missense): 46M .379/.362/.367, 76M .422/.415/.442, 128M .480/.505/.474, 255M .524/.523/.489, 476M .545/.548/.472 β FT tracks the probe at every scale, no crossover (matches it; β« LLR at large scale). Data-scaling (255M, subsample): FT and probe rise together and converge β FT has no steeper slope, so more data along this axis won't let it overtake. β LoRA FT matches but never beats the frozen probe, across scale and data budget; the probe wins on simplicity/robustness (1 auto-tuned knob vs ~8). Remaining: pooled-all-subsets (broader data, evaluated per subset).
Open questions / next steps
torch.compile+ gradient accumulation to hold the effective batch constant as batch shrinks on bigger rungs.Tracking
Description = current state. Comments = append-only
π€iteration log with commit-pinned permalinks to code. Pipeline README (if any) = how-to-run only. References: #341 (motivator), #314 (frozen-probe protocol + the "defer FT" note), #180 (supervised-head null), #302 / #306 (the missense readout-degradation origin).π€ Opened by Claude Code as an agent-research EDA investigation, motivated by #341.