diff --git a/docs/superpowers/specs/2026-05-05-sp14-aux-q-wire-earned-gradient-flow.md b/docs/superpowers/specs/2026-05-05-sp14-aux-q-wire-earned-gradient-flow.md new file mode 100644 index 000000000..fdae0fb40 --- /dev/null +++ b/docs/superpowers/specs/2026-05-05-sp14-aux-q-wire-earned-gradient-flow.md @@ -0,0 +1,337 @@ +# SP14 — Aux→Q Wire + Earned Gradient Flow Pearl + +**Status:** Design (awaiting user review) +**Date:** 2026-05-05 +**Author:** Brainstormed in session post-Smoke A +**Parent commit:** `6657e5626` (B1.1b — final piece of SP13 Layer B chain) +**Depends on:** SP13 chain (B0 → B0.1 → B1.0 → B1.1a → B1.1b) all landed +**Branch target:** `sp11-reward-as-controlled-subsystem` + +--- + +## Goal + +Connect the SP13 supervised aux signal to the Q-head's directional decision-making, and fix three structural stability issues that Smoke A surfaced. After SP14 lands, the K=2 softmax CE aux head's directional confidence should actually reach the action-selection path, gradient clipping should drop to baseline frequency, and the aux_w controller should reach its designed dynamic range. + +## Motivation + +Smoke A on `6657e5626` (multi_fold_convergence × 3 folds × 5 epochs) revealed a striking diagnostic pattern: + +- `aux_dir_acc` reached **0.61** in fold 1 — supervised signal extraction works, the data has directional signal, the K=2 softmax CE head finds it +- `val_win_rate` stayed glued at **45-48%** across all 15 epochs and all 3 folds, even when aux_dir_acc varied 0.16 → 0.61 +- `val_dir_dist` was uniform `[short=0.24, hold=0.26, long=0.26, flat=0.24]` — Hold-pricing eliminated the prior Hold dominance ✓ +- `val_sharpe` oscillated 9-16 — sizing/timing branches respond to fold variation +- `GRAD_CLIP_OUTLIER` fired **1109 times** (vs 5 in pre-SP13 baseline) — chronic gradient throttling + +The "WR-flat-while-aux-varies" pattern is a structural tell: directional weights are *frozen* (no path to action selection) while size/timing branches respond normally. Three parallel diagnostic agents triangulated three interlocking root causes, documented in the next section. + +The user's intuition was correct on all three counts: longer runs won't fix chronic clipping (gradient information is being lost at every step); the model isn't adapting because the aux signal can't reach the Q-head; and adaptive LR is the right next adaptation lever *but only after* these structural bugs are fixed. + +## Smoke A diagnostic — three interlocking bugs + +| # | Bug | Evidence | Bug class | +|---|---|---|---| +| 1 | **Aux→Q wire absent.** Slot 375 (`AUX_DIR_PREDICTION_INDEX`) has zero readers in any Q-head pipeline. The deferred Layer B Task B.T3 Step 2 (Q-head input concat) was scoped but never built. | Exhaustive grep across 120 `.cu` files returns no consumers. Self-aware comments at `gpu_dqn_trainer.rs:13385-13387` and `training_loop.rs:4878-4880` admit "Phase 0b wires the consumers — deferred." | Architectural | +| 2 | **C51 distributional Q-loss gradient blow-up.** Raw c51 grad reaches `9.5×10⁶` in fold 2; SP7 budget controller saturates at `1e-4` minimum but post-scale grad is still `~10³`, dominating IQN by 50-500×. | HEALTH_DIAG `grad_split_bwd c51=9522607` at fold 0 ep 1; `c51_budget` collapses to `1e-4` and pins. Pre-SP13 baseline had c51 grad 0.006-0.5. | Numerical | +| 3 | **aux_w controller muzzled by stale clamp.** P0b's deficit+stagnation controller computes raw aux_w in `[0.15, 1.5]`, but `set_aux_weight` at `gpu_dqn_trainer.rs:14722` re-clamps with the SP11-era `[0.05, 0.3]` cap. Deficit-amplification term is silently dead. Fold 2 stagnation drops aux_w 45% below working value. | Trace at line 14720-14723. Aux_w reaches floor 0.164 in fold 2 vs ceiling 0.3 in folds 0/1 (clamp masks reality). | Configuration | + +Plus two related issues: +- **Stagnation gate fires inappropriately at fold boundary** because Pearl-A first-observation bootstrap makes short-EMA = long-EMA at fold reset, giving `improvement = 0` by construction. +- **Producer kernel writes batch-mean scalar** to slot 375 instead of per-bar tile (Plan B.T3 sketched per-bar). For per-step Q-head decisions the batch-mean shape is structurally wrong, but the trainer-side `aux_nb_softmax_buf` is already `[B, K]` per-bar — so the wire reads from the source buffer directly, sidestepping the slot-375 shape question. + +## Decomposition into three sub-projects + +| Sub-project | Scope | Size | Brainstorm needed? | +|---|---|---|---| +| **A — Stability fixes** | Bugs 2, 3, plus stagnation warmup gate | ~3 files, ~50 LOC | No — direct to writing-plans | +| **B — Wire + Earned Gradient Flow pearl** | Bug 1 + the new gradient-gating pearl | 12-15 files, ~1200 LOC | Yes — this spec | +| **C — Adaptive LR (per-loss-group)** | Bug 6 (the user's open question) | TBD | Deferred until A+B's effect is measured | + +A and B are independent and parallel-safe. A goes straight to a plan; B is the focus of this spec. C is intentionally deferred — only revisit if the post-SP14 30-epoch validation shows WR not climbing past 50%. + +## Sub-project A — stability fixes (this spec defers to plan) + +A is small enough that brainstorming would be theatrical. The plan will land: + +1. **C51 atom-probability floor** in `c51_grad_kernel.cu`: `p_atom = fmaxf(p_atom, ATOM_FLOOR)` before the `−log(p)/p · ∂p/∂z` divide. Floor source: ISV-driven from existing SP4 `atom_pos_p99` per-branch producer. Per `pearl_isv_for_adaptive_bounds`, the floor must be ISV-driven not hardcoded. +2. **`set_aux_weight` clamp lift** at `gpu_dqn_trainer.rs:14722`: change `clamp(0.05, 0.3)` to `clamp(AUX_W_BASE * AUX_W_HARD_FLOOR_RATIO, AUX_W_BASE * AUX_W_HARD_CEIL_RATIO)` = `clamp(0.15, 1.5)`, importing constants from `sp13_isv_slots.rs`. +3. **Stagnation warmup gate** in `compute_aux_w_p0b` at `training_loop.rs:96-135`: skip stagnation logic when `epochs_in_fold < 1`. Pearl-A first-observation makes short=long at fold reset, so `improvement = 0` by construction in the first epoch — the stagnation gate fires on a non-stagnation. Add `epochs_in_fold > 0` guard. + +Validation: Smoke A re-run on commit landing A should show GRAD_CLIP_OUTLIER count drop from 1109 → ~50 or less, and aux_w trace should show values in `[0.15, 1.5]` range during deficit-amplification phases. + +A and B can land in either order. If A lands first (simpler), B's smoke validation has cleaner gradient signals. If B lands first, A's smoke shows whether the wire alone moves WR. Recommendation in plan: **A first** as a cleanup commit, then B as the main feature work. + +## Sub-project B — Aux→Q wire + Earned Gradient Flow pearl + +This is the architectural piece. Two coupled changes that must land atomically per `feedback_no_partial_refactor`: + +1. **Forward wire**: direction Q-head input concat gains 1 column from `aux_softmax_diff = aux_nb_softmax_buf[b, 1] - aux_nb_softmax_buf[b, 0]` ∈ [−1, +1] +2. **Backward gating pearl**: gradient flow back through that wire is gated by an adaptive scalar `α_grad ∈ [0, 1]` driven by ISV signals + +### B.1 — Forward wire design + +**Input shape change:** direction Q-head's input layer was `[h_s2]` (trunk features, shape `[B, SH2]`). Becomes `[h_s2 || aux_softmax_diff]` shape `[B, SH2 + 1]`. The new column is per-bar, one float per batch row. + +**Source of per-bar signal:** read directly from `aux_nb_softmax_buf` (existing `CudaSlice` shape `[B, K=2]`, populated by `aux_next_bar_forward` post-B1.1a). The diff `softmax[:, 1] - softmax[:, 0]` is bounded structurally in `[−1, +1]` because softmax components are non-negative and sum to 1. Per `pearl_bounded_modifier_outputs_require_structural_activation`, this is the right structural bound — no runtime clamp needed. + +**Slot 375 fate:** kept as-is (batch-mean scalar) for HEALTH_DIAG observability. The existing `aux_pred_to_isv_tanh_kernel` continues to write `mean(softmax[1] - softmax[0])` for diagnostic purposes. The wire bypasses slot 375 and reads `aux_nb_softmax_buf` directly per-bar — slot 375 becomes pure diagnostic, not a critical data path. + +**Q-head SGEMM weight matrix:** `direction Q-head W` grows from `[SH2, branch_0_size]` to `[SH2 + 1, branch_0_size]`. The new row of weights is initialized to **zero** at trainer construction. Rationale: model starts ignoring the new input (no perturbation to existing learned behavior), then learns to use it through gradient descent. Xavier-init would inject noise on day 0 that the trunk would have to learn to denoise. + +**Param tensor layout:** the direction Q-head weight tensor is one of the indices in `compute_param_sizes()` — implementer to identify exactly which index against current code at HEAD `6657e5626` per `feedback_trust_code_not_docs`. Its size grows by `branch_0_size`. Update `compute_param_sizes()` and the corresponding `layout_fingerprint_seed` entry. Per `pearl_build_rs_rerun_if_env_changed` and Invariant 8, fingerprint must bump (rename the entry suffix to indicate the new shape, e.g., `PARAM_W_DIR_QHEAD → PARAM_W_DIR_QHEAD_AUX1` or similar — implementer chooses convention matching the codebase). + +**Forward kernel modifications:** the direction Q-head's forward SGEMM (likely in `gpu_dqn_trainer.rs` near the per-branch forward block) needs its `K` (input dim) bumped by 1 and an extra concat operation that copies the per-bar `softmax_diff` into the input vector. Implementer to choose between (a) a new fused concat-SGEMM kernel, or (b) a separate concat into a scratch buffer followed by the existing SGEMM. (a) is more efficient but more invasive; (b) reuses existing paths. + +**Symmetry decision:** does the magnitude / order / urgency Q-head also see `softmax_diff`? **No, v1 keeps it direction-only.** The aux signal is directional — adding it to mag/ord/urg branches dilutes the signal and adds compute without clear benefit. If post-validation magnitude-conditional-on-direction shows benefit, that's a v2 lever. + +### B.2 — Earned Gradient Flow pearl + +The forward wire is one-way information flow by default. Backward gradient flow through the wire is **earned** by the aux head demonstrating label competence and the Q-head demonstrating it's not already aligned with aux. This is the new pearl — adaptive ISV-driven gating on backward-pass gradient flow, a new application of the codebase's existing Pearl pattern (which previously gated forward-pass features only). + +#### B.2.1 — Mathematical form + +``` +α_grad = σ(k_aux · sgn_open_1 · (aux_dir_acc_short_ema − threshold_1)) + × σ(k_q · (q_disagreement_short_ema − q_disagreement_baseline)) + × post_warmup_gate + +where: + σ(x) = 1 / (1 + exp(-clip(x, -30, 30))) # numerically safe sigmoid + k_aux, k_q = ISV-adaptive sigmoid steepness + sgn_open_1, threshold_1 = state-dependent (Schmitt trigger; see B.2.4) + post_warmup_gate = linear ramp over first epoch of each fold +``` + +`α_grad` is a single batch-mean scalar in `[0, 1]`. It scales the backward gradient column at the wire position (i.e., the new column in the direction Q-head's weight gradient that corresponds to the `softmax_diff` input). When `α_grad = 0`, the aux head receives no gradient from Q-loss (pure stop-gradient). When `α_grad = 1`, full Q-loss gradient flows back through the wire to the aux softmax outputs, then through aux head backward to the trunk. + +#### B.2.2 — Gate 1: aux competence + +**Driver signals:** `aux_dir_acc_short_ema` (ISV[373], existing) and `target_dir_acc` (ISV[372], existing). + +**Behavior:** opens when aux head has demonstrated label accuracy above target. Closes when aux head's accuracy drops back below target. + +**Threshold:** in v1, `threshold_1 = target_dir_acc` (so default behavior is to open at exactly target). The Schmitt-trigger (B.2.4) modifies this dynamically. + +**Why this gate exists:** prevents Q-loss from hijacking aux training before aux has earned its label. The aux head's primary job is supervised directional prediction; co-training is a privilege earned by performing that primary job. + +#### B.2.3 — Gate 2: Q-head disagreement (NEW signal) + +**Driver signals:** `q_disagreement_short_ema` (NEW ISV slot) and `q_disagreement_baseline` (NEW ISV slot, long-EMA of disagreement to provide adaptive baseline). + +**Per-step disagreement signal:** +``` +q_disagreement[t] = (argmax(Q_dir_logits[t]) != argmax(aux_softmax[t])) ? 1.0 : 0.0 +``` +EMA'd over batch, then over steps via standard Pearl-A bootstrap pattern. + +**Behavior:** opens when persistent disagreement is meaningfully above baseline. Closed when alignment is high (no co-training needed; aux signal already being consumed). Specifically: +- High disagreement → aux signal is informative (Q-head WANTS to fight aux) → co-training would help align them +- Low disagreement → Q-head already aligned with aux → no benefit from gradient flow + +**Argmax source:** use the deterministic `argmax(Q_dir_logits)` at both training and eval time — i.e., the Q-head's preferred direction *before* Thompson sampling. This tracks the Q-head's underlying directional belief; the disagreement signal should not be polluted by exploration noise. Thompson-sampled action selection diverges from argmax stochastically (especially early in training when Q values are flat), so using the post-Thompson selection would conflate two signals: directional belief and exploration. We want the belief. + +**Why this gate exists:** open Gate 1 alone would unlock co-training even when Q-head is already aligned with aux — wasted gradient flow. Gate 2 ensures co-training happens only when the system shows it's productive. + +#### B.2.4 — Schmitt-trigger hysteresis on Gate 1 + +Single-threshold sigmoid oscillates around target by ±50% if `aux_dir_acc` jitters around target by training noise. Hysteresis prevents this: + +``` +Two thresholds: + threshold_1_open = target_dir_acc + 0.03 # cross above this from below to OPEN + threshold_1_close = target_dir_acc - 0.03 # cross below this from above to CLOSE + +State persistence: + gate1_open_state ∈ {0, 1} (NEW ISV slot) + +Logic: + if gate1_open_state == 0 and aux_dir_acc_short_ema > threshold_1_open: + gate1_open_state = 1 + elif gate1_open_state == 1 and aux_dir_acc_short_ema < threshold_1_close: + gate1_open_state = 0 + +Sigmoid argument: + if gate1_open_state == 1: + arg_1 = aux_dir_acc_short_ema − threshold_1_close + else: + arg_1 = aux_dir_acc_short_ema − threshold_1_open +``` + +Once open, gate stays open until aux drops 0.06 below the open-threshold (i.e., 0.03 below target). Once closed, stays closed until aux rises 0.06 above the close-threshold. The ±0.03 band is structural (numerical-stability anchor; not adaptive). + +**State reset:** `gate1_open_state` resets to 0 at fold boundary via state-reset-registry. Otherwise stale gate state from fold N pollutes fold N+1. + +#### B.2.5 — ISV-adaptive sigmoid steepness + +Both `k_aux` and `k_q` self-adapt based on the variance of their respective driver signals: + +``` +k_x = clip(k_base / (1 + variance_ema_x / variance_ref), [k_min, k_max]) +``` + +- **Welford EMA variance** of the driver signal computed per-step +- High variance → noisy signal → low k → smoother sigmoid → less reactive to noise +- Low variance → stable signal → high k → sharper sigmoid → more decisive + +**Bounds:** `k_min = 1.0`, `k_max = 50.0`. These are structural numerical-stability anchors (Invariant 1 territory). The variance bound `variance_ref` is structural per-signal: e.g., `variance_ref_aux = 0.01` (1% deviation), `variance_ref_q = 0.05`. + +**Per-fold reset:** variance EMAs reset to a sentinel that gives `k = k_base` at fold boundary (e.g., `var_ema = variance_ref` so initial `k = k_base / 2`). Without reset, fold N's variance carries into fold N+1's sigmoid steepness during cold start. + +#### B.2.6 — Per-epoch warmup ramp + +``` +post_warmup_gate = min(steps_in_fold / WARMUP_STEPS, 1.0) +``` + +where `WARMUP_STEPS` is one epoch's worth of training steps (structural constant, derived from `config.steps_per_epoch`). This ramps the gate from 0 → 1 linearly over the first epoch of each fold, avoiding Δα discontinuity at the warmup boundary. + +**State source:** `steps_in_fold` is already tracked by the trainer for other purposes; reuse it. + +#### B.2.7 — Anti-gradient-hacking circuit breaker + +The pathological dynamic to defend against: Q-head learns that disagreeing with aux unlocks aux co-training, which lets Q-loss pull aux toward Q-favored features, which makes aux less label-aligned, which → Q-head disagrees more → cycle. This is mesa-optimization risk. + +**Detection:** track `aux_dir_acc_post_open_min` (NEW ISV slot — lowest aux_dir_acc since gate 1 opened in current fold). Reset when gate 1 closes. + +**Trigger:** if `aux_dir_acc_post_open_min < threshold_1_open - 0.05` AND `q_disagreement_short_ema > q_disagreement_baseline + 0.10` simultaneously → suspected gradient hacking → force gate 1 closed for `LOCKOUT_EPOCHS` (structural constant; 2 epochs). + +**Implementation:** `gradient_hack_lockout_remaining` (NEW ISV slot, integer epoch count). When zero, normal gate behavior. When >0, force `gate1_open_state = 0` and decrement at each epoch boundary. + +This is the "smart toddler with circuit breaker" pattern: the model can't game the wire if doing so triggers a forced reset. + +### B.3 — ISV slot allocations + +11 new ISV slots required: + +| Slot name | Purpose | Reset at fold? | +|---|---|---| +| `Q_DISAGREEMENT_SHORT_EMA_INDEX` | Fast EMA of per-step disagreement | Yes (sentinel 0.5) | +| `Q_DISAGREEMENT_LONG_EMA_INDEX` | Slow EMA, baseline source | Yes (sentinel 0.5) | +| `Q_DISAGREEMENT_BASELINE_INDEX` | Adaptive baseline (= long-EMA after warmup) | Yes (structural 0.5 sentinel) | +| `K_AUX_ADAPTIVE_INDEX` | Current `k_aux` value | Yes (sentinel `k_base`) | +| `K_Q_ADAPTIVE_INDEX` | Current `k_q` value | Yes (sentinel `k_base`) | +| `AUX_DIR_ACC_VARIANCE_EMA_INDEX` | Welford variance of aux_dir_acc | Yes (sentinel `variance_ref`) | +| `Q_DISAGREEMENT_VARIANCE_EMA_INDEX` | Welford variance of disagreement | Yes (sentinel `variance_ref`) | +| `GATE1_OPEN_STATE_INDEX` | Schmitt-trigger persistent state | Yes (0 — closed) | +| `ALPHA_GRAD_INDEX` | Computed α_grad value (HEALTH_DIAG visibility) | Yes (0) | +| `AUX_DIR_ACC_POST_OPEN_MIN_INDEX` | Anti-gradient-hacking detection | Yes (1.0 — no min observed) | +| `GRADIENT_HACK_LOCKOUT_REMAINING_INDEX` | Circuit-breaker epoch counter | Yes (0 — no lockout) | + +All slots register in `state_reset_registry.rs` with appropriate sentinels. Slot indices to be allocated in `sp14_isv_slots.rs` (NEW file; mirrors `sp13_isv_slots.rs` pattern). + +### B.4 — Kernels + +Three new GPU kernels: + +1. **`q_disagreement_update_kernel`** — per-step compute argmax mismatch from `Q_dir_logits` and `aux_nb_softmax_buf`, EMA update for short and long, Welford variance update. Block tree-reduce per `feedback_no_atomicadd`. ~80 LOC. +2. **`alpha_grad_compute_kernel`** — read all driver signals, evaluate Schmitt-trigger logic for Gate 1, compute both sigmoids, multiply with warmup gate, write `ALPHA_GRAD_INDEX`. Includes adaptive k_aux/k_q computation from variance EMAs. ~60 LOC. +3. **`gradient_hack_detect_kernel`** — read aux_dir_acc, gate1_open_state, q_disagreement, decide lockout state, write to `GRADIENT_HACK_LOCKOUT_REMAINING_INDEX` and force-clear `GATE1_OPEN_STATE_INDEX` if triggered. ~40 LOC. + +Plus modifications to: +- **Direction Q-head forward kernel** (or new fused concat-SGEMM): in_dim+1, read `softmax_diff` per-bar from `aux_nb_softmax_buf`. ~50-100 LOC. +- **Direction Q-head backward kernel**: scale the gradient column at the wire position by `α_grad` before SAXPY into aux head's softmax gradient. ~30 LOC. +- **`compute_param_sizes()`** and **`layout_fingerprint_seed()`**: bump direction Q-head weight tensor size by `branch_0_size`, rename layout entry. ~10 LOC. +- **Trainer construction**: zero-initialize the new weight column. ~5 LOC. + +### B.5 — HEALTH_DIAG observability + +New per-epoch `pearl_egf_diag` line: +``` +pearl_egf_diag α=0.45 gate1=0.92 gate2=0.49 k_aux=15.3 k_q=8.7 + q_dis_short=0.523 q_dis_long=0.498 q_dis_baseline=0.50 + gate1_state=open aux_post_open_min=0.55 lockout_remaining=0 + warmup=1.0 +``` + +Without these emit fields, diagnosing α_grad behavior in the smoke logs is forensic-only. All 11 ISV slots emit per epoch. + +### B.6 — Hard rules upheld + +- `feedback_no_partial_refactor` — wire (forward) and pearl (backward) land atomically. Cannot ship one without the other. +- `feedback_no_atomicadd` — all three new kernels use block tree-reduce. Welford variance specifically benefits from this pattern (Welford's online algorithm is associative and parallelizable). +- `feedback_cpu_is_read_only` — all signals computed GPU-side. CPU only reads ISV for HEALTH_DIAG emit. +- `feedback_no_stubs` — every new ISV slot has a real producer and a real consumer in this commit. No zero-stub. +- `feedback_isv_for_adaptive_bounds` — adaptive bounds (k_aux, k_q, q_disagreement_baseline) live in ISV. Numerical-stability anchors (k_min, k_max, variance_ref, hysteresis ±0.03 band, lockout epochs) are structural constants per Invariant 1. +- `feedback_trust_code_not_docs` — implementer verifies all line numbers and structures against current code at HEAD `6657e5626` before editing. +- `feedback_no_legacy_aliases` — fingerprint entry rename has no `_DEPRECATED` shim. +- `pearl_first_observation_bootstrap` — Pearl-A bootstrap applied to all new EMAs (q_disagreement, variances, post_open_min). +- `pearl_bounded_modifier_outputs_require_structural_activation` — softmax IS the structural activation; α_grad's sigmoid composition produces structurally-bounded `[0, 1]`. +- `pearl_zscore_normalization_for_magnitude_asymmetric_signals` — variance-driven k adjustment is the appropriate analog. +- `pearl_event_driven_reward_density_alignment` — N/A; this is a backward-pass gradient gate, not a per-step reward shaping. +- `feedback_no_quickfixes` — the pearl is the canonical answer to "stop-gradient vs full-gradient" tradeoff, not a hack to make Q-head train. + +### B.7 — Validation criteria + +**Smoke A2 (5-epoch L40S, post-SP14):** +- `α_grad` emits per epoch in HEALTH_DIAG; observable across all folds +- `gate1_open_state` opens (=1) at least once during fold 1 (when aux_dir_acc reached 0.61 in pre-SP14 smoke) +- `q_disagreement_short_ema` non-zero (at random init, expect ~0.5; with training, expect drift toward agreement or persistent disagreement) +- `aux_dir_acc` reaches target (0.55) in fold 1 — confirms aux head still trains on its label even with new wire +- `val_dir_dist` remains balanced (Hold-pricing still works) +- `GRAD_CLIP_OUTLIER` count < 100 (down from 1109 — assumes A's C51 floor lands first) +- No new pathological behaviors: aux_dir_acc doesn't drop catastrophically; circuit breaker `lockout_remaining` stays at 0 in normal operation + +**30-epoch validation A (post-Smoke-A2-pass):** +- `val_win_rate` rises above 0.50 by ep 20-25 — confirms the user's hypothesis that aux signal can move WR when wired +- If WR stays at 45-48% across 30 epochs even with α_grad demonstrably opening: hypothesis falsified at this layer; next intervention is on the aux signal richness (sub-project C — adaptive LR, OR longer prediction horizons, OR microstructure feature additions) +- `val_sharpe` recovers toward B1.0 baseline (~30) or stabilizes positive — assesses whether the new wire contention with Q-loss is destabilizing + +**Multi-seed validation (post-30-epoch-pass):** +- 3-seed × 30-epoch reproducibility check +- Validates the WR climb is signal, not seed-luck + +### B.8 — Implementation cost + +| Component | LOC | +|---|---| +| 11 new ISV slots + constants in `sp14_isv_slots.rs` | ~80 | +| `q_disagreement_update_kernel` (NEW) | ~80 | +| `alpha_grad_compute_kernel` (NEW) | ~60 | +| `gradient_hack_detect_kernel` (NEW) | ~40 | +| Direction Q-head forward modification (in_dim+1, concat) | ~100 | +| Direction Q-head backward gradient scaling | ~30 | +| `compute_param_sizes` + `layout_fingerprint_seed` updates | ~20 | +| `state_reset_registry` entries (11 new) | ~50 | +| HEALTH_DIAG emit | ~30 | +| Trainer construction (zero-init new weight column) | ~10 | +| Three orchestrator launchers (kernel launches + arg threading) | ~80 | +| Audit doc B section | ~80 | +| **Production code total** | **~660** | +| GPU oracle unit tests (12-15 tests) | ~400 | +| **Grand total** | **~1060** | + +3-5 days of focused work for a fresh implementer with clear brief. Comparable in scope to SP13 B1.1a (which was 175 tool uses for the implementer agent — at the upper bound of what fits in a single session). + +**Decomposition recommendation:** B should land as a single atomic commit per `feedback_no_partial_refactor`. The forward wire and the backward pearl are coupled (gating a non-existent wire is meaningless; an ungated wire bypasses the safety design). However, the spec writer (this doc) anticipates that the implementer may need to decompose internally into sub-commits the way B1 split into B1.0/B1.1a/B1.1b — the structure of "the wire alone gives degraded but valid behavior" is similar. + +## Sub-project C — Adaptive LR (deferred) + +Not in scope for this spec. Addressed only if post-SP14 30-epoch shows WR stuck at 45-48% despite `α_grad` opening and `gate1_open_state` showing healthy behavior. In that case, the next lever is per-loss-group LR (per `pearl_adam_normalizes_loss_weights` — the lever that escapes Adam normalization). Spec'd separately if needed. + +## Open questions for implementer + +These are decisions the implementer should make by reading current code (not pre-decided in this spec): + +1. **Forward concat strategy** — fused concat-SGEMM kernel vs. separate concat-then-SGEMM vs. extending an existing concat kernel like `mag_concat_qdir`. Implementer to evaluate compute cost vs. code clarity. +2. **Argmax source for q_disagreement** — Thompson selector output buffer vs. recomputing argmax inline in `q_disagreement_update_kernel`. Implementer to choose the cleaner integration with existing action-selection path. +3. **Per-bar α vs batch-mean α** — spec ships batch-mean for v1. If profiling shows the gradient column scaling has high variance per-row, per-row α may be needed. Implementer flags this if observable. +4. **Sub-decomposition** — if implementer agent capacity is at risk, may split into "wire only with stop-gradient hardcoded" + "pearl on top" as B1.1a/B1.1b style. Decide based on dispatch session size. + +## References + +- **Smoke A:** `smoke-test-qfxjw` on commit `6657e5626`, 22m 59s, 3-fold × 5-epoch +- **Three-agent diagnostic findings:** in conversation 2026-05-05 between Smoke A completion and this spec +- **Memory pearls referenced:** + - `pearl_first_observation_bootstrap` — sentinel = 0; first observation replaces directly + - `pearl_zscore_normalization_for_magnitude_asymmetric_signals` — variance-driven adaptive bounds + - `pearl_bounded_modifier_outputs_require_structural_activation` — sigmoid/softmax as structural bounds + - `pearl_event_driven_reward_density_alignment` — ruled out; backward-pass gating is not reward shaping + - `pearl_adam_normalizes_loss_weights` — drives the C deferral decision + - `feedback_isv_for_adaptive_bounds` — adaptive in ISV, structural anchors as constants + - `feedback_no_partial_refactor` — atomic commit constraint +- **Plan reference:** `docs/superpowers/plans/2026-05-04-sp13-redefine-success-for-predictive-skill.md` §B.T3 — the deferred Q-head input concat sketch (this spec implements that, with the EGF pearl as new addition) +- **Prior SP13 chain commits:** `f934ea171` (P0a) → `bdc5cb8bb` (P0b) → `62ab8ed85` (B0) → `6a869ad36` (B0.1) → `75e94858c` (B1.0) → `7d10ea8b3` (B1.1a) → `6657e5626` (B1.1b) + +## Next step + +Spec self-review (placeholder scan, internal consistency, ambiguity check), then user review. After user approval, invoke `superpowers:writing-plans` to produce the implementation plan for sub-project B (and a smaller plan for sub-project A if A is to land first).