docs(sp14): spec — aux→Q wire + Earned Gradient Flow pearl

Designs the SP14 chain on top of SP13 Layer B (HEAD 6657e5626):

1. Sub-project A — stability fixes (3 small bugs found in Smoke A)
   - C51 atom-probability floor (ISV-driven from SP4 atom_pos_p99)
   - aux_w setter clamp lift [0.05, 0.3] → [0.15, 1.5]
   - Stagnation warmup gate at fold boundary

2. Sub-project B — the architectural piece (THIS spec)
   - Forward wire: aux_softmax_diff per-bar into direction Q-head input
     concat (in_dim+1, fingerprint bump, zero-init new column)
   - Earned Gradient Flow pearl — adaptive ISV-driven gradient gating:
     * Gate 1 (aux competence) — Schmitt-trigger hysteresis
     * Gate 2 (Q-head disagreement) — NEW signal, EMA per-step argmax
       mismatch
     * ISV-adaptive sigmoid steepness (variance-driven k_aux, k_q)
     * Per-epoch warmup ramp
     * Anti-gradient-hacking circuit breaker (mesa-opt defense)
   - 11 new ISV slots, 3 new GPU kernels, ~1060 LOC total
   - HEALTH_DIAG pearl_egf_diag observability line

3. Sub-project C — Adaptive LR (deferred until A+B effects measured)

Motivation from Smoke A diagnostic:
- aux_dir_acc reached 0.61 (signal extraction works)
- val_win_rate stuck 45-48% (no path to action selection)
- WR-flat-while-aux-varies = Q-head directional weights frozen
- 1109 GRAD_CLIP_OUTLIER events (chronic; not noise)

Three parallel diagnostic agents triangulated three interlocking root
causes:
- Slot 375 has zero readers (the wire was scoped but never built)
- C51 raw grad reaches 9.5e6, saturates SP7 budget controller
- aux_w controller muzzled by SP11-era [0.05, 0.3] clamp

The Earned Gradient Flow pearl is a new application of the codebase's
pearl pattern: ISV-driven adaptive controller, but applied to backward-
pass gradient flow instead of forward-pass features. The wire is one-
way (stop-gradient) by default; co-training is earned by both:
(a) aux head demonstrating label competence, AND
(b) Q-head showing it's actually fighting aux signal (informative
    disagreement above baseline).

Stability additions hardened against:
- Oscillation around target (Schmitt hysteresis)
- Numerical sigmoid saturation (argument clipping ±30)
- Stale variance EMAs across folds (state-reset-registry)
- Discontinuous warmup transitions (linear ramp)
- Mesa-optimization (gradient-hacking circuit breaker)
- Cold-start sentinel-state spurious gate openings (Pearl-A bootstrap)

Awaiting user review before invoking superpowers:writing-plans for
sub-project B (and a separate small plan for sub-project A).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-05-05 16:59:26 +02:00
parent 6657e56265
commit eaf4adcb98

View File

@@ -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<f32>` 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).