# Plan D — Phase 3: Long Verification + Direction-Branch Dead-Code Cleanup Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Close out the Thompson-sampling rollout. Run L4 (long smoke, 1 seed × 6 folds × 30 epoch) and L5 (full validation matrix per Plan 5 Task 5) to verify Thompson holds up over many epochs. Then delete unreachable direction-only `eps_dir` adaptive-boost and Boltzmann tau-floor code paths inside `experience_action_select`. Magnitude/order/urgency code paths stay 100 % intact.

**Architecture:** No new mechanisms. Two verification layers (L4, L5) plus a tightly-scoped cleanup pass on `experience_kernels.cu`. The cleanup is mandated by `feedback_no_partial_refactor.md`: Phase 2 changed the shared "action-select-direction" contract from `eps-greedy + Boltzmann` to `Thompson + argmax`; every consumer of the old contract must migrate in the same logical change. Leaving direction-only `eps_dir` clamps + tau floors compiled but unreachable is the partial-refactor anti-pattern.

**Tech Stack:** Same as Plans A+B+C. New tooling: `argo-train.sh --multi-seed`, `argo-train.sh --folds`, `scripts/validation/check_all_tiers.py`, nsys profile harness from Plan 5 Task 3.

**Prerequisite:** Plans A + B + C complete. All 16 unit tests (6 Phase 0 + 6 Phase 1 + 4 Phase 2) passing. Plan C's L3 short smoke pass with `train_active_frac > 0.40` across all epochs.

**Spec reference:** Phase 3 of `docs/superpowers/specs/2026-04-26-distributional-rl-aggregation-design.md` (lines 445-460), 5-Layer Verification Protocol (lines 464-489), Risk Register R3+R8 (lines 507-553).

---

## File Structure

| Path | Status | Responsibility |
|---|---|---|
| `crates/ml/src/cuda_pipeline/experience_kernels.cu` | Modify | Delete direction-only `eps_dir` adaptive-boost + EPS_FLOOR clamp + tau-floor lines; remove `eps_dir` from kernel signature if unused elsewhere |
| `crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs` | Modify | Drop the `eps_dir`-related kernel-arg if signature changed |
| `crates/ml/src/cuda_pipeline/gpu_backtest_evaluator.rs` | Modify | Same as above |
| `crates/ml/src/trainers/dqn/distributional_q_tests.rs` | Modify | Add Phase 3 regression test 3.A: load converged checkpoint, replay val-Flat-collapse setup, assert Thompson keeps `train_active_frac > 0.40` |
| `docs/dqn-v2-final-validation.md` | Create | Final L5 validation report (one row per Tier; same template as Plan 5 Task 5 Step 5.6) |
| `docs/dqn-wire-up-audit.md` | Modify | Mark direction-branch eps/tau rows as REMOVED with commit SHA reference |
| `~/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/MEMORY.md` | Modify | Reference Plan D close-out commit SHA in pearl entry created in Plan C |
| Argo deploy logs | Read | L4 + L5 evidence |

---

### Task 1: Phase 3 Test 3.A — Regression test against the original bug

**Files:**
- Modify: `crates/ml/src/trainers/dqn/distributional_q_tests.rs`

**Why this test:** All 16 prior unit tests verify the new Thompson mechanism. None replays the *original failure*. If a future refactor accidentally reverts to scalar-aggregate-then-Boltzmann selection, no existing test catches it. Test 3.A is the regression anchor: it replays the post-Kelly val-Flat-collapse on the converged checkpoint and asserts Thompson reverses it.

- [ ] **Step 1: Write the failing test**

Append to `crates/ml/src/trainers/dqn/distributional_q_tests.rs`:

```rust
/// Phase 3 Test 3.A — Regression anchor against C51 expected-Q Hold/Flat
/// bias. Setup mirrors the original val-Flat-collapse pathology:
///   - Synthetic distributions: Flat = δ(0); Long/Short = small negative
///     mean with non-trivial σ (≥ 0.05).
/// Acceptance:
///   - Argmax(E[Q]) picks Flat for ≥ 95 % of states (replays the bug).
///   - Thompson sampling on the SAME distributions picks Long+Short for
///     ≥ 40 % of states (verifies the fix).
///   - Run BOTH on the production `experience_action_select` kernel,
///     not the standalone test kernel — production is what would regress.
///
/// This test is the durable artefact of the design lesson; if it ever
/// fails after a future refactor, that refactor reintroduced the bias
/// class and must be reverted.
#[test]
#[ignore]
fn test_3a_regression_anchor_c51_flat_bias() {
    let stream = make_test_stream();
    let n_states = 512;
    let n_atoms = 51;
    let v_min = -0.5_f32;
    let v_max = 0.5_f32;
    let dz = (v_max - v_min) / (n_atoms as f32 - 1.0);
    let atom_values: Vec<f32> = (0..n_atoms).map(|a| v_min + a as f32 * dz).collect();

    // Synth C51: Flat is δ(0); Short/Long/Hold = Gaussian centred at -0.005,
    // σ = 0.05 (one-tx-cost expected loss with realistic uncertainty).
    let mut c51_probs = vec![0.0_f32; n_states * 4 * n_atoms];
    let mut iqn_quantiles = vec![0.0_f32; n_states * 4 * 5];
    let directional_atom_pdf = gaussian_atom_pdf(&atom_values, -0.005, 0.05);
    let flat_atom_pdf = delta_zero_atom_pdf(&atom_values);
    for s in 0..n_states {
        for d in 0..4 {
            let pdf = if d == 3 { &flat_atom_pdf } else { &directional_atom_pdf };
            let off = (s * 4 + d) * n_atoms;
            c51_probs[off..off + n_atoms].copy_from_slice(pdf);
            // IQN: same shape, expressed as five equispaced quantiles.
            let q_off = (s * 4 + d) * 5;
            let q = if d == 3 {
                [0.0_f32; 5]
            } else {
                [-0.082, -0.039, -0.005, 0.029, 0.072]
            };
            iqn_quantiles[q_off..q_off + 5].copy_from_slice(&q);
        }
    }

    // Push to GPU and call the production kernel twice: once eval (argmax),
    // once train (Thompson).
    let argmax_picks = launch_production_action_select(&stream, &c51_probs, &iqn_quantiles, &atom_values, /*eval_mode=*/true);
    let thompson_picks = launch_production_action_select(&stream, &c51_probs, &iqn_quantiles, &atom_values, /*eval_mode=*/false);

    let argmax_flat_frac = (argmax_picks.iter().filter(|&&d| d == 3).count() as f32) / n_states as f32;
    let thompson_active_frac = (thompson_picks.iter().filter(|&&d| d == 0 || d == 2).count() as f32) / n_states as f32;

    assert!(argmax_flat_frac >= 0.95, "argmax should still demonstrate the bias on this synth setup, got {argmax_flat_frac}");
    assert!(thompson_active_frac >= 0.40, "Thompson must reverse the bias, got active_frac={thompson_active_frac}");
}

fn gaussian_atom_pdf(atom_values: &[f32], mean: f32, sigma: f32) -> Vec<f32> {
    let raw: Vec<f32> = atom_values.iter().map(|&v| {
        let z = (v - mean) / sigma;
        (-0.5 * z * z).exp()
    }).collect();
    let total: f32 = raw.iter().sum();
    raw.into_iter().map(|p| p / total).collect()
}

fn delta_zero_atom_pdf(atom_values: &[f32]) -> Vec<f32> {
    let mut p = vec![0.0_f32; atom_values.len()];
    let zero_idx = atom_values.iter().position(|&v| v.abs() < 1e-6).expect("atom grid must contain zero");
    p[zero_idx] = 1.0;
    p
}
```

`launch_production_action_select` is a thin Rust wrapper that fills the same kernel inputs `experience_action_select` would receive in production, then reads back the resulting `actions` GPU buffer and decodes `dir_idx = action / 27`. Reuse the buffer-wiring pattern established in Plan C Tasks 3-4.

- [ ] **Step 2: Run test to verify it fails before checkpoint loaded**

Run: `cargo test -p ml --lib distributional_q_tests::test_3a_regression_anchor_c51_flat_bias -- --ignored --nocapture`

Expected before completing helpers: FAIL with "function not defined" or `launch_production_action_select` panic.

- [ ] **Step 3: Implement helpers + run test to PASS**

Implement `launch_production_action_select` (~ 60 LOC). Run again; expect PASS — argmax_flat_frac ≥ 0.95, thompson_active_frac ≥ 0.40.

- [ ] **Step 4: Commit**

```bash
git add crates/ml/src/trainers/dqn/distributional_q_tests.rs
git commit -m "test(dqn): Phase 3 Test 3.A — regression anchor against C51 expected-Q Flat bias

Replays the original val-Flat-collapse setup on the production
experience_action_select kernel: argmax(E[Q]) picks Flat ≥ 95% (bug
reproduces); Thompson picks Long+Short ≥ 40% (fix verified). Durable
guard against future refactors reintroducing the aggregation-bias
class."
```

---

### Task 2: L4 long smoke (1 seed × 6 folds × 30 epoch)

**Files:**
- Read: Argo logs at `argo-server.foxhunt.internal/workflows/foxhunt/...`
- Read: HEALTH_DIAG output

**Why L4:** L3 (Plan C exit gate) was 5 epochs, three folds, primary gate `train_active_frac > 0.40` only. L4 verifies Phase 3 Risk R8 (long-term edge discovery): does val_sharpe trend non-negative over 30 epochs? Does val_active_frac evolve as the model finds edge? If after 30 epochs val_sharpe stays flat AND val_active_frac stays low, the data has no edge — that is correct model behaviour, not a bug.

- [ ] **Step 1: Push to origin/main**

Per Invariants: always `git push origin main` before deploy.

```bash
git push origin main
```

- [ ] **Step 2: Launch L4**

```bash
./scripts/argo-train.sh dqn \
    --baseline \
    --epochs 30 \
    --folds 6 \
    --commit "$(git rev-parse HEAD)" \
    --gpu-pool ci-training-l40s \
    --tag plan-d-l4-long-smoke
```

Wait ~1 hour. Monitor via Argo UI or:

```bash
argo list -l foxhunt-tag=plan-d-l4-long-smoke
argo wait @latest -l foxhunt-tag=plan-d-l4-long-smoke --timeout 90m
```

- [ ] **Step 3: Gather HEALTH_DIAG metrics**

```bash
./scripts/gather-multi-seed-metrics.sh plan-d-l4-long-smoke > /tmp/l4-metrics.json
```

- [ ] **Step 4: Verify L4 acceptance criteria**

Pass criteria from spec line 473:
- `val_sharpe` trend across 30 epochs is non-negative (linreg slope ≥ 0)
- `val_active_frac` evolves: not stuck at 0 across all 30 epochs
- `train_active_frac > 0.40` across every epoch (regression check vs L3)

Use:

```bash
python3 scripts/validation/check_l4_long_smoke.py /tmp/l4-metrics.json
```

If `check_l4_long_smoke.py` does not exist, write it (~ 50 LOC) computing slope of last-fold val_sharpe via numpy.polyfit and asserting the three criteria. Add it under `scripts/validation/` next to existing `check_tier1.py` etc.

Exit 0 required. If exit non-zero:
- val_sharpe slope < 0 → escalate per Risk R3 / R8: open follow-up task to investigate; this is NOT cause for L4 retry. Per `feedback_stop_on_anomaly.md`: re-running hoping for a different outcome is anti-pattern.
- train_active_frac < 0.40 in any epoch → Phase 2 Thompson regression; revisit Plan C kernel.

- [ ] **Step 5: Commit L4 evidence**

```bash
git add scripts/validation/check_l4_long_smoke.py
git commit -m "validation(dqn): Plan D L4 long smoke — train_active_frac ≥ 40%, val_sharpe non-negative slope, val_active_frac evolves"
```

---

### Task 3: L5 full validation matrix (per Plan 5 Task 5)

**Files:**
- Read: `docs/superpowers/plans/2026-04-24-dqn-v2-plan-5-validation.md` Task 5
- Run: `scripts/validation/check_all_tiers.py`
- Create: `docs/dqn-v2-final-validation.md`

L5 is exactly Plan 5 Task 5 re-executed against the post-Thompson commit. The Plan 5 Task 5 protocol is the source of truth for procedure; this task is a thin wrapper that links into it and adds Thompson-specific evidence rows.

- [ ] **Step 1: Pre-flight checks**

```bash
git status --porcelain | test -z "$(cat)"
git fetch origin main
test "$(git rev-parse main)" = "$(git rev-parse origin/main)"

# All Phase 0+1+2+3 tests pass locally
SQLX_OFFLINE=true FOXHUNT_TEST_DATA=test_data/futures-baseline \
    cargo test -p ml --lib distributional_q_tests -- --ignored --nocapture 2>&1 | tee /tmp/dist-tests.log
grep -q "test result: ok" /tmp/dist-tests.log

# Smoke tests still pass
SQLX_OFFLINE=true FOXHUNT_TEST_DATA=test_data/futures-baseline \
    cargo test -p ml --lib -- smoke_tests --ignored --nocapture 2>&1 | tee /tmp/smoke.log
grep -q "test result: ok" /tmp/smoke.log
```

- [ ] **Step 2: Launch L5 = Plan 5 Task 5 multi-seed × multi-fold matrix**

```bash
git push origin main
./scripts/argo-train.sh \
    --multi-seed 5 \
    --folds 6 \
    --commit "$(git rev-parse HEAD)" \
    --tag dqnv2-thompson-final
```

Wait ~ 6 hours. Monitor:

```bash
argo list -l foxhunt-tag=dqnv2-thompson-final
argo wait @latest -l foxhunt-tag=dqnv2-thompson-final --timeout 8h
```

- [ ] **Step 3: Gather L5 metrics**

```bash
./scripts/gather-multi-seed-metrics.sh dqnv2-thompson-final > /tmp/dqnv2-thompson-final-metrics.json
./scripts/aggregate-norm-stats.py \
    --input-dir /mnt/training-data/norm-stats/dqnv2-thompson-final \
    --output    /mnt/training-data/norm-stats/dqnv2-thompson-final-aggregate.json
```

- [ ] **Step 4: Run Tier 1 + Tier 2 + Tier 3 validation**

```bash
python3 scripts/validation/check_all_tiers.py /tmp/dqnv2-thompson-final-metrics.json --warmup-end 15
```

Exit 0 required on the FIRST run. If non-zero:
- DO NOT retry with different seeds or a different tag (per `feedback_stop_on_anomaly.md` and Plan 5 Step 5.4)
- Open the failing tier as a separate task for investigation
- Plan D does not land until Tier 1 + Tier 2 + Tier 3 all PASS on the first run

- [ ] **Step 5: nsys regression check (Phase H continuity)**

```bash
mc cp minio/foxhunt-training-artifacts/profiles/baseline-plan5/profile-seed0-fold0.nsys-rep /tmp/baseline.nsys-rep
mc cp minio/foxhunt-training-artifacts/profiles/$(git rev-parse --short HEAD)/profile-seed0-fold0.nsys-rep /tmp/current.nsys-rep
python3 scripts/compare-nsys-profiles.py /tmp/baseline.nsys-rep /tmp/current.nsys-rep
```

Exit 0 required. The Thompson kernel is ~ 50 µs per batch (per Risk R1 mitigation); regression must stay within Plan 5 Task 3 tolerance (typically ≤ 5 % step-time delta).

If nsys regression > tolerance, open as Plan H follow-up but DO NOT block Plan D — Phase 3 spec line 455 says "Phase H continues independently".

- [ ] **Step 6: Document final run**

Create `docs/dqn-v2-final-validation.md`:

```markdown
# DQN v2 — Final Validation Run (post-Thompson)

**Date:** <DATE>
**Commit SHA:** <SHA>
**Argo tag:** dqnv2-thompson-final
**Matrix:** 5 seeds × 6 folds = 30 training jobs
**GPU hours:** ~<H> hours L40S
**Thompson rollout:** Plans A+B+C+D landed at <SHA>

## Tier 1 — Convergence discipline

[paste check_tier1.py output]

## Tier 2 — Behavioural parity

[paste check_tier2.py output, including val_trades_per_bar, val_active_frac,
direction entropy, and the new train_active_frac line]

## Tier 3 — Profitability

[paste check_tier3.py output with val_sharpe, win_rate, profit_factor,
std across seeds]

## Thompson-specific evidence

| Metric | Value | Acceptance |
|---|---|---|
| train_active_frac mean across all (seed, fold, epoch) | <X> | ≥ 0.40 |
| val_active_frac final-epoch mean across seeds | <X> | reported honestly (no gate) |
| val_dir_dist == val_picked_dir_dist | ✓ / ✗ | Kelly-fix invariant retained |
| Phase 3 Test 3.A (regression anchor) | PASS / FAIL | PASS required |

## nsys regression

[paste compare-nsys-profiles.py output]

## HEALTH_DIAG sample lines (last 3 epochs, seed 0, fold 5)

[paste 3 lines showing reward_split, temporal_reward, attn, aux,
controller, train_active_frac, val_active_frac]

## Sign-off

Thompson sampling integrated into DQN v2 direction branch. C51 expected-Q
Hold/Flat aggregation bias resolved structurally — see
`docs/superpowers/specs/2026-04-26-distributional-rl-aggregation-design.md`.
All 9 invariants preserved across every commit in Plans A-D.
```

- [ ] **Step 7: Commit L5 evidence**

```bash
git add docs/dqn-v2-final-validation.md
git commit -m "validation(dqn): Plan D L5 final matrix — Tier 1+2+3 PASS first run"
```

---

### Task 4: Direction-branch dead-code cleanup — `eps_dir` adaptive boost + EPS_FLOOR clamp

**Files:**
- Modify: `crates/ml/src/cuda_pipeline/experience_kernels.cu` (lines 806-865 region)
- Modify: `crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs` (kernel-arg call site for `experience_action_select`)
- Modify: `crates/ml/src/cuda_pipeline/gpu_backtest_evaluator.rs` (same)

**Why this cleanup:** Phase 2 replaced direction-branch action selection wholesale (eps-greedy + Boltzmann → Thompson + argmax). The `eps_dir` clamp at line 816 and adaptive-boost block at lines 822-861 are now structurally unreachable for direction (the eps-greedy branch is gone). Per `feedback_no_partial_refactor.md`: every consumer of the changed contract must migrate in the same logical change. Dead-but-compiled code is the partial-refactor anti-pattern. Magnitude/order/urgency continue to use the EPS_FLOOR clamp + adaptive boost is direction-only so removing only the direction lines is a safe surgical edit.

- [ ] **Step 1: Read the current state**

```bash
sed -n '800,870p' crates/ml/src/cuda_pipeline/experience_kernels.cu
```

Confirm:
- Line ~806 declares `float eps_dir = ...`
- Line ~816 has `eps_dir = fmaxf(eps_dir, EPS_FLOOR);`
- Lines ~822-861 are an adaptive-boost block scaling `eps_dir` from ISV[71/72] passive_pressure
- Line ~861 ends with `eps_dir = fmaxf(eps_dir, adaptive_floor);`
- Line ~922 has `if (!eval_mode && philox_uniform(i, timestep, rng_ctr++) < eps_dir) {` (the eps-greedy direction-sample branch — replaced by Thompson in Plan C Task 2)

If any line numbers differ from the spec, that's expected — work from semantic location, not literal line numbers (Plan C will have shifted lines around the kernel).

- [ ] **Step 2: Identify safe deletion span**

After Plan C Task 2, the direction-branch action select reads:

```cuda
if (eval_mode) { ... argmax E[Q] ... } else { ... Thompson ... }
```

NEITHER path consumes `eps_dir`. Therefore:
- Delete the `float eps_dir = fminf(epsilon * eps_exp_mult, 1.0f);` declaration (line ~806).
- Delete `eps_dir = fmaxf(eps_dir, EPS_FLOOR);` (line ~816).
- Delete the entire adaptive-boost block (lines ~822-861) including its containing comment block.
- Delete the now-orphaned `if (!eval_mode && philox_uniform(...) < eps_dir) {...}` direction-eps-greedy branch (line ~922 region) — Plan C should have removed this already; verify.

KEEP unchanged:
- `eps_mag`, `eps_ord`, `eps_urg` declarations
- `eps_mag = fmaxf(eps_mag, EPS_FLOOR);` and the two sibling lines
- All uses of `eps_mag`, `eps_ord`, `eps_urg` in their respective magnitude/order/urgency branches

- [ ] **Step 3: Apply the edits**

Edit `crates/ml/src/cuda_pipeline/experience_kernels.cu` removing the direction-only lines listed in Step 2.

- [ ] **Step 4: Grep for residual `eps_dir` references**

```bash
grep -rn "eps_dir" crates/ml/
```

Expected: zero matches in `.cu` files. If any matches remain (e.g. in a comment, in a Rust `*.rs` file kernel-arg call, in `gpu_dqn_trainer.rs` or `gpu_backtest_evaluator.rs` where the C++ kernel signature is mirrored), follow up:

- If `experience_action_select` kernel signature still includes `eps_dir`/passive_pressure parameters, drop them. Update Rust call sites to match.
- If only ISV[71]/ISV[72] producer kernels remain (these write `passive_pressure` even though direction no longer reads it), DO NOT delete those producers — `feedback_isv_for_adaptive_bounds.md`: ISV signal bus values may be consumed by future modules. Adding a comment that no current consumer uses these is acceptable; deleting them is not.

- [ ] **Step 5: Build and run unit tests**

```bash
SQLX_OFFLINE=true cargo build --release -p ml 2>&1 | tee /tmp/build.log
grep -q "error\[" /tmp/build.log && { echo "BUILD FAILED"; exit 1; }

cargo test -p ml --lib distributional_q_tests -- --ignored --nocapture
```

Expected: build succeeds. All 17 tests (Phases 0+1+2+3) PASS — including Test 3.A regression anchor on the cleaned-up kernel.

- [ ] **Step 6: Re-run L3 short smoke as a regression check**

```bash
git push origin HEAD:main
./scripts/argo-train.sh dqn --baseline --epochs 5 --folds 3 \
    --commit "$(git rev-parse HEAD)" --gpu-pool ci-training-l40s \
    --tag plan-d-cleanup-l3-regression
argo wait @latest -l foxhunt-tag=plan-d-cleanup-l3-regression --timeout 30m
./scripts/gather-multi-seed-metrics.sh plan-d-cleanup-l3-regression > /tmp/l3-regression.json
python3 -c "
import json
m = json.load(open('/tmp/l3-regression.json'))
mins = [e['train_active_frac'] for e in m['epochs']]
assert min(mins) >= 0.40, f'cleanup regression: min train_active_frac={min(mins)}'
print('OK: cleanup preserves train_active_frac >= 0.40')
"
```

If `train_active_frac` regresses below 0.40 after cleanup, REVERT the cleanup commit immediately and open as a follow-up task. The tau-floor / eps_dir code may have been a hidden Thompson dependency that needs deeper investigation.

- [ ] **Step 7: Commit cleanup**

```bash
git add crates/ml/src/cuda_pipeline/experience_kernels.cu \
        crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs \
        crates/ml/src/cuda_pipeline/gpu_backtest_evaluator.rs
git commit -m "cleanup(dqn): Plan D — remove direction-only eps_dir adaptive boost + EPS_FLOOR clamp

Phase 2 replaced direction-branch action selection (eps-greedy +
Boltzmann → Thompson + argmax). The eps_dir adaptive boost (ISV[71/72]
passive_pressure → eps floor scaled to 0.5) and the EPS_FLOOR clamp
on eps_dir are now structurally unreachable for the direction branch.

Per feedback_no_partial_refactor.md: every consumer of a changed shared
contract must migrate in the same logical change.

Magnitude/order/urgency keep their EPS_FLOOR + eps_mag/ord/urg paths
intact — they were never reached by Thompson.

Verified: 17/17 distributional_q_tests pass, L3 regression smoke
maintains train_active_frac >= 0.40."
```

---

### Task 5: Direction-branch dead-code cleanup — Boltzmann tau-floor

**Files:**
- Modify: `crates/ml/src/cuda_pipeline/experience_kernels.cu` (around lines 887-944)

**Why:** Same partial-refactor argument. The direction Boltzmann tau-floor (lines 935-944 region, where `q_dir_abs_ref` clamps `tau_d`) is unreachable now that direction uses Thompson (no Boltzmann). The mag/ord/urg branches keep their tau-floor logic verbatim.

- [ ] **Step 1: Read the current state**

```bash
sed -n '880,950p' crates/ml/src/cuda_pipeline/experience_kernels.cu
```

Confirm:
- Comment around line 887: "Branch 0: direction — Boltzmann softmax with ISV-adaptive tau floor."
- Around line 935-944: `q_dir_abs_ref` computation and `float tau_floor = fmaxf(q_dir_abs_ref, 0.01f); float tau_d = fmaxf(q_max_d - q_min_d, tau_floor);`

If Plan C Task 2 already removed these along with the direction Boltzmann path, this task may be a no-op. Verify by searching:

```bash
grep -n "q_dir_abs_ref\|tau_d\|tau_floor.*direction" crates/ml/src/cuda_pipeline/experience_kernels.cu
```

If grep returns no direction-only lines, skip to Step 5 ("commit no-op").

- [ ] **Step 2: Identify safe deletion span**

Delete:
- The "Branch 0: direction — Boltzmann softmax with ISV-adaptive tau floor" header comment.
- The `q_dir_abs_ref` computation (the EMA from ISV slot — but only if it's used ONLY for `tau_floor` for direction; if mag branch reads it too, KEEP the producer + mark it unused-by-direction in a one-line comment).
- The direction-only `tau_floor`, `tau_d`, and the direction Boltzmann softmax block (replaced by Thompson in Plan C Task 2 — this verifies Plan C didn't leave residue).

KEEP unchanged:
- All `tau_mag`, `tau_ord`, `tau_urg` declarations and their `fmaxf(_, 0.01f)` clamps.
- All `q_mag_abs_ref`, `q_ord_abs_ref`, `q_urg_abs_ref` ISV producers and consumers.

- [ ] **Step 3: Apply the edits**

Edit `experience_kernels.cu` removing the direction-only Boltzmann tau-floor lines.

- [ ] **Step 4: Build, run unit tests, regression smoke**

```bash
SQLX_OFFLINE=true cargo build --release -p ml 2>&1 | tee /tmp/build.log
grep -q "error\[" /tmp/build.log && { echo "BUILD FAILED"; exit 1; }
cargo test -p ml --lib distributional_q_tests -- --ignored --nocapture
```

Expected: 17/17 pass.

If the deletion span was non-trivial (anything beyond a comment), re-run the L3 regression smoke as in Task 4 Step 6.

- [ ] **Step 5: Commit cleanup (or no-op)**

If real edits made:

```bash
git add crates/ml/src/cuda_pipeline/experience_kernels.cu
git commit -m "cleanup(dqn): Plan D — remove direction-only Boltzmann tau-floor

Phase 2 Thompson replaces direction Boltzmann; the q_dir_abs_ref-driven
tau_floor for the direction branch is now structurally unreachable.

Magnitude/order/urgency keep tau_floor + Boltzmann intact.

Per feedback_no_partial_refactor.md: every consumer of the changed
shared contract must migrate in the same logical change."
```

If no-op (Plan C already cleaned this):

```bash
git commit --allow-empty -m "cleanup(dqn): Plan D Task 5 — verified Plan C removed direction tau-floor; no residual code"
```

---

### Task 6: Audit-doc + memory-pearl cross-reference

**Files:**
- Modify: `docs/dqn-wire-up-audit.md`
- Modify: `~/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/MEMORY.md`

- [ ] **Step 1: Audit-doc update**

In `docs/dqn-wire-up-audit.md`, find the rows added in Plan C Task 10 (Distributional Q-head Aggregation Contract) and the rows for `eps_dir` adaptive boost / direction tau-floor (if present). Update:

- Append to the Aggregation Contract table description: "Phase 3 (Plan D, commit `<SHA>`) cleaned up direction-only `eps_dir` adaptive-boost and Boltzmann tau-floor code; magnitude/order/urgency paths intact."
- If there's a per-component "wire-up status" row for `eps_dir` adaptive boost, mark it: `REMOVED in <SHA> — direction-branch unreachable after Thompson migration; magnitude/order/urgency EPS_FLOOR + eps_mag/ord/urg paths preserved.`
- Same for direction Boltzmann tau-floor row.

- [ ] **Step 2: Memory-pearl cross-reference**

In `~/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/MEMORY.md`, find the line added in Plan C Task 10 referencing `pearl_thompson_for_distributional_action_selection.md`. Append:

```
- [pearl_thompson_for_distributional_action_selection.md](pearl_thompson_for_distributional_action_selection.md) — distributional Q-heads must use sampling-based action selection. Plans A-D landed: Phase 0/1 verification, Phase 2 production integration (commit `<SHA>`), Phase 3 long verification + direction-branch dead-code cleanup (commit `<SHA>`).
```

(Replace existing line in-place; do not duplicate.)

- [ ] **Step 3: Commit**

```bash
git add docs/dqn-wire-up-audit.md
git commit -m "docs(dqn): Plan D close-out — Aggregation Contract notes cleanup; eps_dir + direction tau-floor rows REMOVED with Plan D SHA reference"
```

(Memory file is local; no commit needed.)

---

### Task 7: Final spec status footer

**Files:**
- Modify: `docs/superpowers/specs/2026-04-26-distributional-rl-aggregation-design.md`

- [ ] **Step 1: Append status footer**

Append at end of file:

```markdown
---

## Status (post-implementation)

**LANDED:** <DATE>
**Final commit SHA:** <SHA>
**Plans:** A (`<SHA>`), B (`<SHA>`), C (`<SHA>`), D (`<SHA>`)

**Verification matrix:**
- L1 (unit tests): 17/17 PASS — 6 Phase 0 + 6 Phase 1 + 4 Phase 2 + 1 Phase 3 regression
- L2 (GPU integration on converged checkpoint): PASS — Tests 0.F + 2.B + 2.D + 3.A
- L3 (3-fold × 5-epoch L40S): train_active_frac > 0.40 across all epochs
- L4 (1-seed × 6-fold × 30-epoch L40S): val_sharpe non-negative slope; val_active_frac evolves
- L5 (5-seed × 6-fold matrix): Tier 1 + Tier 2 + Tier 3 PASS first run; nsys within tolerance

**Cleanup**: direction-only `eps_dir` adaptive-boost (`d54b49efc`) and direction Boltzmann tau-floor (`7a3d88646`) deleted as unreachable code. Magnitude/order/urgency paths preserved. Per `feedback_no_partial_refactor.md`.

**v2 enhancements deferred:** triple-source Thompson, persistent Thompson, CVaR-eval, IDS, hierarchical Thompson, curiosity coupling — each a follow-up spec.

**Aggregation Contract enforced:** any future distributional Q-head must expose sampleable interface and wire into Thompson (training) + argmax (eval). Pre-commit hook (Invariant 7) refuses non-compliant merges.
```

- [ ] **Step 2: Commit**

```bash
git add docs/superpowers/specs/2026-04-26-distributional-rl-aggregation-design.md
git commit -m "spec(dqn): Plan D close-out — Thompson sampling LANDED; status footer added"
```

---

### Task 8: Phase 3 exit gate

- [ ] **Step 1: All unit tests pass**

```bash
cargo test -p ml --lib distributional_q_tests -- --ignored --nocapture
```

Expected: 17/17 pass (Phases 0+1+2+3).

- [ ] **Step 2: All smoke tests pass**

```bash
SQLX_OFFLINE=true FOXHUNT_TEST_DATA=test_data/futures-baseline \
    cargo test -p ml --lib -- smoke_tests --ignored --nocapture
```

Expected: all PASS.

- [ ] **Step 3: L4 evidence file present**

```bash
test -f /tmp/l4-metrics.json && echo "L4 metrics gathered"
```

- [ ] **Step 4: L5 final validation doc committed**

```bash
test -f docs/dqn-v2-final-validation.md
git log --format=%H -- docs/dqn-v2-final-validation.md | head -1
```

- [ ] **Step 5: Cleanup commits landed**

```bash
git log --grep="cleanup(dqn): Plan D" --oneline
```

Expected: at least one commit (Task 4); possibly two (Task 5 if non-no-op).

- [ ] **Step 6: nsys regression within tolerance**

Already verified in Task 3 Step 5. Confirm by re-reading `scripts/compare-nsys-profiles.py` exit-0 status from that step.

- [ ] **Step 7: Final close-out commit**

```bash
git push origin main
git commit --allow-empty -m "phase3(dqn): Plan D complete — Thompson sampling rollout LANDED

L1: 17/17 unit tests pass (Phases 0+1+2+3 incl. Test 3.A regression anchor)
L3: train_active_frac > 0.40 across all epochs (re-verified post-cleanup)
L4: 30-epoch val_sharpe non-negative slope; val_active_frac evolves
L5: Tier 1 + Tier 2 + Tier 3 PASS first run; nsys within tolerance
Cleanup: direction-only eps_dir adaptive-boost + Boltzmann tau-floor removed.

Spec docs/superpowers/specs/2026-04-26-distributional-rl-aggregation-design.md
status footer updated. Aggregation Contract pre-commit hook enforced.

C51 expected-Q Hold/Flat aggregation bias structurally resolved."
git push origin main
```

---

## Self-Review

**Spec coverage check (against spec lines 445-460 + 464-489):**

| Spec deliverable | Task |
|---|---|
| L4 multi-fold deploy (1 seed × 6 folds × 30 epoch) | Task 2 |
| L5 full validation matrix per Plan 5 Task 5 (Tier 1/2/3 PASS) | Task 3 |
| Direction-branch eps_dir adaptive-boost cleanup | Task 4 |
| Direction-branch Boltzmann tau-floor cleanup | Task 5 |
| nsys regression check | Task 3 Step 5 |
| Final architecture doc updates | Tasks 6 + 7 |
| L1 (unit tests) gate | Task 8 Step 1 |
| L2 (integration tests) gate | Test 3.A in Task 1 covers this for Phase 3 |
| Spec line 454 detail: `experience_kernels.cu` lines 814-865 cleanup | Task 4 explicit |
| KEEP Kelly cap fix (`0c9d1ee39`), KEEP train return + sharpe annualization fixes (non-tau parts of `7a3d88646`) | Implicit (no removal task touches these) |
| Aggregation Contract pre-commit hook continues to enforce | Task 6 references it; the hook itself ships in Plan C Task 10 |

All deliverables covered.

**Placeholder scan:**

- No "TBD"/"TODO"/"implement later" strings.
- Test 3.A includes complete code for the kernel-input synthesis and assertions; only the `launch_production_action_select` wrapper is described not coded — it reuses the buffer-wiring pattern landed in Plan C Tasks 3-4 (referenced explicitly), which is the appropriate cross-plan reference rather than copy-paste.
- L4 acceptance script `check_l4_long_smoke.py` is sized (~ 50 LOC) and pinned to numpy.polyfit; this is a complete instruction.

**Type/symbol consistency:**

- `train_active_frac` HEALTH_DIAG metric introduced in Plan C Task 5; consumed in Task 2 Step 4 and Task 4 Step 6 here. Same name throughout.
- `experience_action_select` kernel name consistent with Plan C.
- `EPS_FLOOR` constant referenced consistently with the kernel source (`experience_kernels.cu` line 815).
- `dqnv2-thompson-final` Argo tag matches the L5 metric-gather command and the validation doc.
- `pearl_thompson_for_distributional_action_selection.md` filename matches Plan C Task 10 Step 2.
- `docs/dqn-v2-final-validation.md` matches Plan 5 Task 5 Step 5.6 template.

**Sequencing check:**

- Test 3.A (Task 1) lands BEFORE the cleanup tasks (4, 5) — gives a regression anchor that survives the cleanup.
- L4 (Task 2) lands BEFORE L5 (Task 3) — short before long, per the L3→L4→L5 spec progression.
- Cleanup (Tasks 4, 5) lands AFTER L5 (Task 3) — verifies the production system is healthy before doing surgical removals.
- Wait — the spec says cleanup is "+ 0.5 day" on top of Plan 5 budget but doesn't strictly mandate sequencing. Re-reading spec line 460: cleanup is treated as part of Phase 3 alongside L4/L5. Acceptable to do cleanup either before or after L5; doing it AFTER L5 is safer (we know the production code passes L5 first; cleanup is then a low-risk surgical pass). Keep current order.
- Audit doc + spec footer (Tasks 6, 7) reference commit SHAs from earlier tasks — must come last. Correct.
- Final close-out (Task 8) is the empty-commit gate — must come last. Correct.

**Risk-coverage check (against spec Risk Register R1-R8):**

- R1 (Thompson cost): nsys regression in Task 3 Step 5 catches.
- R2 (PRNG quality): Test 3.A re-exercises Phase 0 Test 0.B's PRNG path on production kernel.
- R3 (Thompson at training, argmax at eval — wasted exploration): explicitly accepted as by-design tradeoff per spec; no code-level mitigation, but L5 Tier 3 catches if it becomes a profitability problem.
- R8 (slow edge discovery): L4 30 epochs targets this directly per spec line 543.

All risks covered or explicitly accepted.

**Self-review complete. No fixes needed.**

---

## Execution Handoff

After Plans A + B + C + D all written, the writing-plans skill workflow concludes by offering execution choice:

**1. Subagent-Driven (recommended)** — fresh subagent per task, two-stage review between tasks.

**2. Inline Execution** — execute tasks in this session using executing-plans, batch with checkpoints.

Plan D is the last plan; once it's accepted, execution can begin from Plan A.
