Files
foxhunt/docs/superpowers/plans/2026-06-02-regime-invariance-implementation.md
jgrusewski 629ebd667c feat(ml-alpha): deterministic same-seed training + Tier 1.5 fast-dev-cycle
Two same-seed runs now produce bit-equal eval_summary.json, alpha_rl_train_summary.json,
and diag.jsonl (modulo wall-clock elapsed_s). The 5-phase falsification chain landed:

  Phase 2   PER tree-rebuild: __threadfence is NOT a grid-wide barrier; multiple blocks
            raced across sum-tree levels. Fix: Grid=(1) Block=(1024) + __syncthreads
            in rl_per_tree_rebuild.cu.

  Phase 2.3 cuBLAS GEMM_DFALT + TF32 default-math allowed split-K non-deterministic
            accumulation at 3 sites. New crates/ml-alpha/src/cublas_determinism.rs
            applies CUBLAS_PEDANTIC_MATH via FOXHUNT_DETERMINISTIC env toggle
            (0=TF32 prod, 1=PEDANTIC dev default, 2=DEFAULT_MATH control).

  Phase 2.6 Two bugs surfaced sequentially in the backward kernel chain:
            (1) rl_iqn_tau_cos_features had a multi-block r/w race on prng_state[batch]
                — all N_TAU=32 blocks read seed; only tau_idx==0 wrote back; no
                inter-block barrier. Fix: split into READ-ONLY rl_iqn_tau_cos_features
                + new sibling rl_iqn_advance_prng_state launched on same stream
                (kernel-launch ordering = grid-wide barrier).
            (2) OutcomeHead::new called near_zero_xavier without scoped_init_seed,
                falling back to time+thread-id RNG. Stayed dormant until first done
                event activated non-sentinel labels and divergent weights flowed via
                grad_h_t_outcome into encoder gradient. Fix: add seed param + install
                scoped_init_seed(dqn_seed.wrapping_add(0x0CE0)) guard.

Validation (./scripts/determinism-check.sh --quick, RTX 3050, b=128, 200+50 steps):
  - All 200 rows of checksums.* leaves match (rel-tol 1e-5, abs-tol 1e-7)
  - eval_summary.json, alpha_rl_train_summary.json byte-equal between runs
  - diag.jsonl byte-equal modulo elapsed_s
  - Eval pnl identical run-A vs run-B at seed 42

Pre-fix baseline (Phase 2.5 measurement): same-seed eval pnl spread $450k
($187k vs -$261k). Post-fix: $0 spread.

Speed cost: ~1.5ms/step amortised; ~10-15% slower than TF32 production
(PEDANTIC tax — acceptable in dev, toggle to FOXHUNT_DETERMINISTIC=0 for prod).

Mapped-pinned discipline: all 11 NEW memcpy_dtoh sites in diagnostic dump methods
+ per-step checksum readback use a new pub(crate) helper
read_slice_d_into<T: Copy>(stream, src, dst) — MappedRecordBuffer + raw
memcpy_dtod_async + raw_stream_sync + volatile read. Generic over T (f32, f64,
i32, u32, u8). Satisfies feedback_no_htod_htoh_only_mapped_pinned + hook guard.

Bundled Tier 1.5 fast-dev-cycle infrastructure (spec
docs/superpowers/specs/2026-06-02-fast-dev-cycle.md):
  - scripts/local-mid-smoke.sh        b=128, 2000+500, ~10min on RTX 3050
  - scripts/determinism-check.sh      runs mid-smoke twice, diffs checksums
  - scripts/tier1_5_verdict.py        behavioral kill verdict
  - AdamW checkpoint save/load (crates/ml-alpha/src/trainer/optim.rs)
  - IntegratedTrainer checkpoint save/load (resume from checkpoint)
  - 15 Phase 1 checksum leaves in build_diag_value
  - Env-gated dump methods (FOXHUNT_DETERMINISM_DEBUG_PER/MAMBA2/RL/BACKWARD)
    for future divergence-chasing — never run in production

Documentation:
  - docs/superpowers/specs/2026-06-02-determinism-foundation.md
  - docs/superpowers/specs/2026-06-02-fast-dev-cycle.md
  - docs/superpowers/plans/2026-06-02-determinism-foundation-implementation.md
  - docs/superpowers/notes/2026-06-02-determinism-phase{1,2,2.2,2.5,2.6}-*.md
  - Adjacent specs/plans/notes from the analytical chain that surfaced determinism
    as the load-bearing blocker (eval-summary, eval-boundary, regime-observer,
    multi-head policy, regime-invariance, Phase 3 IQN-complement post-mortem)

Unlocks: every controller / architecture / reward-shaping A/B from this commit
onward attributes outcome differences to the change, not random-init kernel-race
drift cascading through training x eval LOB-sim trajectories. The eval-collapse
investigation (pearl_reward_signal_anti_aligned_with_pnl, multi-head spec,
regime-invariance spec) is now testable with trustworthy verdicts.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 17:56:00 +02:00

21 KiB
Raw Blame History

Regime-Invariance — Implementation Plan for the Four Interventions

Date: 2026-06-02 Status: Plan (task list only; no implementation) Branch: ml-alpha-regime-observer (HEAD 63fc16f17) Source spec: 2026-06-02-regime-invariance-four-interventions.md

Notes for the executor

  1. Interventions ship in series, one cluster smoke per intervention. Do NOT bundle.
  2. Intervention #1 (R-multiple) is the foundational rip-and-replace. The other three build on top.
  3. Per feedback_no_feature_flags: the surfer-scaffold infra is deleted in the same commit that lands R-multiple. No parallel paths.
  4. Per feedback_single_source_of_truth_no_duplicates: no v2_/version suffixes anywhere.
  5. Per feedback_local_smoke_before_cluster: every .cu / trait / buffer change runs the 1k-step local smoke on RTX 3050 Ti before any Argo submit.
  6. Spec inaccuracy correction: §1.1 of the spec asserts "Read ATR from existing risk-stack signals (already computed for Kelly position sizing)". Verified false against crates/ml-alpha/cuda/rl_kelly_fraction_controller.cu: Kelly consumes win_rate × avg_win/avg_loss in USD, not ATR. There is no existing ATR EMA slot. Intervention #1 must therefore allocate and produce a fresh ATR EMA. The plan reflects this; the spec §7 open question #1 is settled by this finding.

Intervention #1 — R-multiple reward (ships first, single commit)

Phase A — ISV layout (RL bus surgery)

Task 1.1 — Retire surfer-scaffold slots 753-756

  • File: crates/ml-alpha/src/rl/isv_slots.rs
  • Action: Delete RL_SURFER_SCAFFOLD_WEIGHT_INDEX (753), RL_SURFER_BREAK_EVEN_WR_INDEX (754), RL_SURFER_K_SHARPNESS_INDEX (755), RL_SURFER_WARMUP_TRADES_INDEX (756). Remove the §"Adaptive Surfer-Scaffold Shaping" header block (lines 1740-1779). Renumber RL_SLOTS_END from 757 → 753.
  • LOC: 45

Task 1.2 — Allocate R-multiple slots

  • File: crates/ml-alpha/src/rl/isv_slots.rs
  • Action: Reuse the freed 753-756 range with new semantics (no renumbering needed beyond RL_SLOTS_END). Allocate:
    • RL_ATR_EMA_INDEX = 753 — per-batch ATR EMA in USD over closed-trade |Δrealized_pnl| (Wiener-α blend, half-life ~50 trades).
    • RL_ATR_EMA_ALPHA_INDEX = 754 — config, default 0.02.
    • RL_ATR_FLOOR_INDEX = 755 — config; floor in USD ≈ 1 ES tick × multiplier × min_size = $12.50.
    • RL_R_MULTIPLE_CLAMP_INDEX = 756 — config; symmetric clamp on R-multiple reward to ±N×ATR, default 5.0.
  • Set RL_SLOTS_END = 757. Add a §"R-multiple reward" header documenting the design + linkage to spec.
  • LOC: +50

Task 1.3 — Per-account ATR-at-entry CUDA buffer

  • File: crates/ml-alpha/src/trainer/integrated.rs
  • Action: Add unit_atr_at_entry_d: CudaSlice<f32> (sized b_size). Allocated in IntegratedRlTrainer::new, zero-init alongside unit_entry_step_d. Listed in struct, zeroed by reset_session_state per pearl_adaptive_carryover_discipline.
  • LOC: +20

Phase B — CUDA kernel changes

Task 1.4 — Delete rl_surfer_scaffold_controller.cu

  • File: crates/ml-alpha/cuda/rl_surfer_scaffold_controller.cu
  • Action: rm. Per feedback_no_feature_flags, the kernel + its 753-weight consumer go atomically.
  • LOC: 102

Task 1.5 — Rewrite Phase 5 of rl_fused_reward_pipeline.cu for R-multiple

  • File: crates/ml-alpha/cuda/rl_fused_reward_pipeline.cu
  • Action: Replace Phase 5 / 5b entirely (lines 217-272). New structure:
    • Capture unit_atr_at_entry[b] on flat→positioned transition. Bootstrap = max(isv[RL_ATR_EMA_INDEX], isv[RL_ATR_FLOOR_INDEX]).
    • At close (done > 0.5): r = clamp(realized_pnl_delta / max(unit_atr_at_entry[b], floor), ±R_MULTIPLE_CLAMP).
    • Drop entry-cost / short-hold-penalty / long-ride / per-step hold-bonus / inventory-penalty.
    • Drop RL_SURFER_SCAFFOLD_WEIGHT_INDEX read and all w * multipliers.
    • Drop #defines for RL_SURFER_SCAFFOLD_WEIGHT_INDEX, RL_ENTRY_COST_INDEX, RL_SHORT_HOLD_MIN_STEPS_INDEX, RL_SHORT_HOLD_PENALTY_INDEX, RL_HOLD_BONUS_INDEX, RL_INVENTORY_PENALTY_BETA_INDEX.
    • Add #defines for RL_ATR_EMA_INDEX, RL_ATR_FLOOR_INDEX, RL_R_MULTIPLE_CLAMP_INDEX.
    • Add new kernel arg float* unit_atr_at_entry (after unit_active).
  • LOC: 80 / +40 (net 40)

Task 1.6 — New ATR EMA producer kernel

  • File: crates/ml-alpha/cuda/rl_atr_ema_update.cu (NEW)
  • Action: Single-thread (1,1,1)/(1,1,1) launch. On done, read |realized_pnl_delta| from the per-batch close, average across the closed-trade batch (single-block tree reduce, no atomics per feedback_no_atomicadd), Wiener-α blend into RL_ATR_EMA_INDEX with RL_ATR_EMA_ALPHA_INDEX. Bootstraps from first-observation per pearl_first_observation_bootstrap.
  • LOC: +90

Phase C — Trainer wiring

Task 1.7 — Drop scaffold controller from trainer

  • File: crates/ml-alpha/src/trainer/integrated.rs
  • Action: Delete the four rl_surfer_scaffold_controller-related blocks:
    • Constant RL_SURFER_SCAFFOLD_CONTROLLER_CUBIN (line 299-300).
    • Struct fields _rl_surfer_scaffold_controller_module + rl_surfer_scaffold_controller_fn (839-840).
    • load_cubin / load_function blocks (1807-1812).
    • Field assignment in struct literal (2959-2960).
    • Method launch_rl_surfer_scaffold_controller (4261-4275).
    • Call site in step pipeline (7757-7762).
  • LOC: 60

Task 1.8 — Wire ATR EMA kernel + buffer into reward pipeline launch

  • File: crates/ml-alpha/src/trainer/integrated.rs
  • Action:
    • Add RL_ATR_EMA_UPDATE_CUBIN include + module/function load alongside other reward kernels.
    • Add unit_atr_at_entry_d to the rl_fused_reward_pipeline args block (after unit_active_d).
    • Add a launch_rl_atr_ema_update method.
    • Call it immediately AFTER the fused reward pipeline kernel (so the new ATR observation propagates the next time entry capture fires).
    • Update reset_session_state to zero unit_atr_at_entry_d and RL_ATR_EMA_INDEX per pearl_adaptive_carryover_discipline.
  • LOC: +90

Task 1.9 — ISV bootstrap entries

  • File: crates/ml-alpha/src/trainer/integrated.rs
  • Action: In the init isv_seed table (~line 3919), remove the 4 surfer entries and add R-multiple bootstrap entries:
    • (RL_ATR_EMA_INDEX, 0.0_f32) (sentinel — first-observation bootstrap)
    • (RL_ATR_EMA_ALPHA_INDEX, 0.02_f32)
    • (RL_ATR_FLOOR_INDEX, 12.50_f32) (1 ES tick = $12.50)
    • (RL_R_MULTIPLE_CLAMP_INDEX, 5.0_f32)
  • Also remove the 4 surfer entries from reset_session_state's seed list (~line 3646 area).
  • LOC: +12 / 10

Task 1.10 — Drop scaffold diag emission

  • File: crates/ml-alpha/src/trainer/integrated.rs
  • Action: Replace the "surfer_scaffold_weight" JSONL field (~line 10053) with "atr_ema_usd" reading isv[RL_ATR_EMA_INDEX] and "r_multiple_last" (per-step max abs R-multiple if cheap; otherwise just ATR).
  • LOC: +5 / 5

Phase D — Build registration

Task 1.11 — Update build.rs kernel list

  • File: crates/ml-alpha/build.rs
  • Action: Remove "rl_surfer_scaffold_controller" (line 118). Add "rl_atr_ema_update" in its place.
  • LOC: 0 (1 line swap)

Phase E — Tests + cubin sanity

Task 1.12 — GPU-oracle test for the new fused reward pipeline

  • File: crates/ml-alpha/tests/reward_alignment_invariants.rs (extend existing)
  • Action: Add a deterministic-seed sub-test asserting:
    • After 1000 sim steps, Pearson(rewards.sum, Δpnl) ≥ 0.7 per pearl_reward_signal_anti_aligned_with_pnl.
    • On every close event, observed reward == clamp(Δpnl / max(atr_at_entry, floor), ±5).
    • unit_atr_at_entry_d[b] is overwritten on every flat→positioned transition and unchanged otherwise.
  • Per feedback_no_cpu_test_fallbacks: oracle is GPU-resident; test reads back via mapped-pinned only.
  • LOC: +120

Task 1.13 — ISV invariants test

  • File: crates/ml-alpha/tests/risk_stack_invariants.rs (extend)
  • Action: Assert RL_SLOTS_END == 757 and the four R-multiple slots resolve to 753/754/755/756.
  • LOC: +15

Task 1.14 — Audit-wiring + audit-isv dry run

  • Files: scripts scripts/audit-wiring.sh, scripts/audit-isv.sh
  • Action: Run both; verify no orphan references to surfer_scaffold_* survive (consumers, configs, JSONL keys). No new orphans introduced by ATR slots.
  • LOC: 0 (script run only)

Phase F — Local validation

Task 1.15 — Local 1k-step smoke (RTX 3050 Ti)

  • Command:
    FOXHUNT_TEST_DATA=test_data/futures-baseline \
      SQLX_OFFLINE=true \
      cargo test -p ml-alpha --lib --release -- smoke_tests --ignored --nocapture
    
  • Pass gate: zero kernel asserts, no NaNs in diag JSONL, atr_ema_usd > 0 after the first close event, Pearson(reward, Δpnl) from reward_alignment_invariants test ≥ 0.7.
  • LOC: 0

Task 1.16 — Local 5k-step extended smoke

  • Command: same as 1.15 but with --n-steps 5000. Validates fold-1 train wr ≥ 0.30 trend before cluster commit.
  • LOC: 0

Phase G — Cluster smoke

Task 1.17 — Cluster smoke fold-1 b=1024 20k+5k

  • Command:
    ./scripts/argo-alpha-rl.sh \
      --sha <COMMIT_SHA> \
      --branch ml-alpha-regime-observer \
      --gpu-pool ci-training-l40s \
      --batch-size 1024 \
      --n-steps 20000 \
      --n-eval-steps 5000 \
      --n-folds 9 --fold-idx 0
    
  • Pass gate: spec §1.3 — train wr ≥ 0.30 by step 5k AND eval wr ≥ 0.28 AND eval pnl > 0.
  • Fail gate: eval pnl < $5M → kill and pivot to Intervention #3.
  • Note: per pearl_argo_branch_ref_trap_strikes_again, pin the exact SHA in the script invocation.
  • LOC: 0

Intervention #1 total LOC: ~+250 / 300 = net 50 (matches spec §1.1 "Net code REDUCTION").


Intervention #2 — Wire regime_observer features into policy network input

Defer until #1 result known. Spec §2 says best deployed AFTER or WITH #3.

Task 2.1 — Confirm encoder input dim path

  • File: crates/ml-alpha/src/trainer/integrated.rs (read-only audit)
  • Action: Locate the encoder input projection used by rl_encoder_context_broadcast (build.rs line 94). Identify the current K-dim count it broadcasts (encoder input [B, K, 56] per existing comment).
  • LOC: 0 (research)

Task 2.2 — Extend rl_encoder_context_broadcast.cu to inject 5 regime features

  • File: crates/ml-alpha/cuda/rl_encoder_context_broadcast.cu
  • Action: Add a 5-slot tail (cols 56-60) populated from ISV:
    • RL_REGIME_DEAD_ZONE_FLAG_INDEX (696)
    • RL_REGIME_SESSION_PNL_VARIANCE_EMA_INDEX (700)
    • RL_REGIME_RECOVERY_FACTOR_INDEX (699)
    • RL_REGIME_TAIL_EVENT_RECENCY_INDEX (701) (normalize by RL_IQN_TAU_TAIL_BOOST_N_WINDOW_INDEX = 712)
    • RL_EDGE_PH_MEAN_INDEX (750) (normalize against RL_EDGE_PH_THRESHOLD_INDEX = 748)
  • Each broadcast as a constant across the K-axis (same value all timesteps in the encoder window).
  • LOC: +40

Task 2.3 — Bump encoder input projection from 56 → 61 dims

  • File: crates/ml-alpha/src/trainer/integrated.rs + any HIDDEN_INPUT_DIM const
  • Action: Identify the constant carrying 56, replace with 61 (or named const ENCODER_INPUT_DIM). Per feedback_use_consts_not_literals_for_structural_dims, all consumers must reference the const not the literal.
  • LOC: +10 / 0

Task 2.4 — Expand projection weight matrix [in, hidden]

  • File: crates/ml-alpha/src/trainer/perception.rs (encoder init)
  • Action: Grow input-projection weight matrix from [56, hidden] to [61, hidden]. New columns Xavier-init small. Update checkpoint format version bump constant.
  • LOC: +25

Task 2.5 — Local 1k-step smoke + 5k extended smoke

  • Command: same as Task 1.15-1.16. Verifies the wider encoder runs cleanly and doesn't explode action entropy.
  • LOC: 0

Task 2.6 — Cluster smoke

  • Same as Task 1.17. Pass gate per spec §5 (eval pnl > 0). Compare action-entropy curve against #1 baseline.
  • LOC: 0

Intervention #2 total LOC: ~+75


Intervention #3 — Cross-fold training (domain randomization)

Task 3.1 — Audit walk-forward sampler

  • File: crates/ml-alpha/examples/alpha_rl_train.rs (lines 314-340)
  • Action: Current sampler splits n_files into n_folds blocks then picks train = 0..=fold_idx, eval = fold_idx+1. Map this to a per-batch fold-randomization variant.
  • LOC: 0 (research)

Task 3.2 — Add --cross-fold-training CLI flag

  • File: crates/ml-alpha/examples/alpha_rl_train.rs
  • Action: Add boolean flag cross_fold_training: bool defaulting false. When set, each rollout batch samples a random training fold uniformly from 0..=fold_idx; eval stays on fold_idx + 1 (held-out).
  • LOC: +15

Task 3.3 — Per-batch-element fold sampling in rollout loop

  • File: crates/ml-alpha/examples/alpha_rl_train.rs + downstream sampler in services/ml_training_service
  • Action: When cross-fold mode is active, the per-batch GPU sample-and-gather path (gpu_sample_and_gather kernel) takes a randomly-chosen fold's file list per batch element. Verify gpu_sample_and_gather already supports per-batch file selection (it does — the dataset gather is per-snapshot-anchor). If not, extend.
  • LOC: +30

Task 3.4 — Verify all training folds load cleanly

  • File: deployment-time check (no code change)
  • Action: Per pearl_mbp10_data_is_structurally_broken correction (cluster PVC has 148GB coherent 24-month ES MBP-10), confirm 9 quarterly files load without size/shape mismatch. Add an assertion in alpha_rl_train.rs that all train_files parse before training begins (and abort early if not).
  • LOC: +10

Task 3.5 — Local 1k-step smoke

  • Command: same as Task 1.15 with --cross-fold-training flag.
  • Pass gate: trade rate doesn't crash to zero; reward Pearson ≥ 0.7 preserved.
  • LOC: 0

Task 3.6 — Cluster smoke

  • Same as Task 1.17 with --cross-fold-training and --n-folds 9 --fold-idx 7 (train on folds 0-6, eval on fold 7, held-out tail).
  • LOC: 0

Intervention #3 total LOC: ~+55


Intervention #4 — Mixture-of-experts gated by regime (deferred)

Spec §4.2 marks this as 2-day, 250-LOC effort, defer until #1-#3 validated. Plan stays at task-level only.

Task 4.1 — Expert architecture design doc

  • File: docs/superpowers/specs/2026-06-XX-moe-regime-gating.md (CREATE only if Intervention #1-#3 fails)
  • Action: Capture expert-A / expert-B / gating-net dimensions, per-expert reward shaping plan, training schedule.
  • LOC: ~150 markdown

Task 4.2 — Two-expert MLP heads (parallel to current PPO policy head)

  • File: crates/ml-alpha/src/rl/ppo.rs + new crates/ml-alpha/cuda/rl_moe_policy_head.cu
  • Action: 2 separate [hidden, N_ACTIONS] weight matrices, forward + backward kernels. Gating net is a 2-layer MLP on regime features feeding softmax(K=2).
  • LOC: +250

Task 4.3 — Per-expert reward shaping

  • File: crates/ml-alpha/cuda/rl_fused_reward_pipeline.cu
  • Action: Add per-expert reward path — expert A uses R-multiple-by-hold-duration, expert B uses R-multiple-of-inverse-hold (mean-revert specialty). Gated by expert_assignment[b] (sampled from gate softmax at trade open).
  • LOC: +40

Task 4.4 — Trainer wiring + 6 ISV slots

  • File: crates/ml-alpha/src/rl/isv_slots.rs, crates/ml-alpha/src/trainer/integrated.rs
  • Action: 6 new slots (gate temp, K=2 gate weights × 2 layers' hyperparams, per-expert KL trackers). Bumps RL_SLOTS_END from 757 → 763.
  • LOC: +80

Task 4.5 — Cluster smoke

  • Same as Task 1.17. Spec §4 falsification: eval pnl > 0.
  • LOC: 0

Intervention #4 total LOC: ~+370 (excluding spec doc)


Cross-cutting tasks (apply to all interventions)

Task X.1 — Run scripts/audit-wiring.sh and scripts/audit-isv.sh after each intervention

  • Action: Confirms no orphan kernel registrations, no dead ISV slots, no hardcoded literals where ISV reads should be used.
  • LOC: 0

Task X.2 — Memory pearl update on cluster verdict

  • File: /home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/
  • Action: After each cluster smoke, append a pearl-style entry to MEMORY.md topic file with the verdict (PASS / MARGINAL / FAIL with eval pnl, wr, action entropy). Per feedback_trust_code_not_docs, the cluster log is ground truth; pearl summarizes what was measured.
  • LOC: ~5 per intervention

Task X.3 — Pre-commit precommit zen + local smoke

  • Action: Run mcp__zen__precommit or equivalent + feedback_local_smoke_before_cluster 1k-step smoke before each cluster submission. No exceptions.
  • LOC: 0

Sequencing decision tree (recap from spec §5)

[Ship #1] ──→ cluster smoke ──→ eval pnl > 0? ──yes──→ DONE, deploy
                                       │
                                       no
                                       ↓
                          [Ship #3] ──→ cluster smoke ──→ eval pnl > 0? ──yes──→ DONE
                                                                    │
                                                                    no
                                                                    ↓
                                                       [Ship #2] ──→ cluster smoke ──→ eval pnl > 0? ──yes──→ DONE
                                                                                                │
                                                                                                no
                                                                                                ↓
                                                                                  [Ship #4] ──→ cluster smoke ──→ verdict
                                                                                                                  │
                                                                                                                  no
                                                                                                                  ↓
                                                                                                       Termination clause §5

Open questions resolved by this plan

  1. ATR source (spec §7 Q1): No existing Kelly ATR. Allocate fresh RL_ATR_EMA_INDEX slot 753 + new rl_atr_ema_update.cu producer kernel.
  2. ATR bootstrap (Q2): Per pearl_first_observation_bootstrap + sentinel-zero protocol. Use max(atr_ema, atr_floor=$12.50).
  3. Eval-boundary discipline (Q3): Per pearl_adaptive_carryover_discipline, zero RL_ATR_EMA_INDEX and unit_atr_at_entry_d in reset_session_state. R-multiple denominator falls back to atr_floor for the first eval trade — preferred over carrying train-end calibration into a regime-shifted eval window.
  4. Fold sampling weight (Q4): Equal-weight (uniform) in Intervention #3 v1. Active domain randomization (regime-novelty weighting) is a future #3.1 if Plain #3 underperforms.

Estimated total LOC by intervention

# Intervention Net LOC Files touched Cluster smokes
1 R-multiple reward 50 8 1
2 Regime features → encoder +75 3 1
3 Cross-fold training +55 2 1
4 Mixture-of-experts (deferred) +370 5 1+

Files this plan touches (canonical list)

Intervention #1:

  • crates/ml-alpha/src/rl/isv_slots.rs
  • crates/ml-alpha/cuda/rl_fused_reward_pipeline.cu
  • crates/ml-alpha/cuda/rl_surfer_scaffold_controller.cu (DELETE)
  • crates/ml-alpha/cuda/rl_atr_ema_update.cu (CREATE)
  • crates/ml-alpha/src/trainer/integrated.rs
  • crates/ml-alpha/build.rs
  • crates/ml-alpha/tests/reward_alignment_invariants.rs
  • crates/ml-alpha/tests/risk_stack_invariants.rs

Intervention #2:

  • crates/ml-alpha/cuda/rl_encoder_context_broadcast.cu
  • crates/ml-alpha/src/trainer/integrated.rs
  • crates/ml-alpha/src/trainer/perception.rs

Intervention #3:

  • crates/ml-alpha/examples/alpha_rl_train.rs
  • services/ml_training_service/src/k8s_dispatcher.rs (if any K8s arg surface needs --cross-fold-training)

Intervention #4 (deferred):

  • docs/superpowers/specs/2026-06-XX-moe-regime-gating.md (CREATE)
  • crates/ml-alpha/src/rl/ppo.rs
  • crates/ml-alpha/cuda/rl_moe_policy_head.cu (CREATE)
  • crates/ml-alpha/cuda/rl_fused_reward_pipeline.cu
  • crates/ml-alpha/src/rl/isv_slots.rs
  • crates/ml-alpha/src/trainer/integrated.rs