# Per-Horizon CfC Inference Architecture — Design

**Date**: 2026-05-21
**Branch**: `ml-alpha-phase-a`
**Author**: brainstorming session synthesis
**Status**: design awaiting plan
**Supersedes**: `2026-05-21-crt-train-intervention-b-multi-timescale-readout.md` (MTER, premise falsified)

---

## 1. Problem statement and verified mechanism

### 1.1 The bottleneck

`pearl_training_smoothness_does_not_transfer_to_inference` documented an empirical observation: the K-window output-smoothness regularizer (intervention A) produced real training-time horizon stratification (jitter ratio 0.40 between h30 and h6000 outputs) but ZERO inference-time `mean_run_len` differentiation (ratio 0.93). The original WIN gate (`mean_run_len[h6000] / mean_run_len[h30] ≥ 10×`, h6000 mean_run_len ≥ 100 events) was not approachable.

`pearl_random_sampling_sufficient_for_per_horizon_training_auc` (recorded 2026-05-21 during the Phase-1 diagnostic) further established that random batch sampling achieves identical peak per-horizon validation AUC to stateful sequential sampling (both ~0.745 at h6000). This refuted MTER's premise that training/inference EMA-distribution alignment was the bottleneck.

The remaining hypothesis: the bottleneck is on the inference-side architecture itself — specifically the CfC layer's per-channel τ dynamics under the K=32 training window.

### 1.2 The mechanism (math-derived, user-verified)

From `crates/ml-alpha/cuda/cfc_step.cu:57-58`:

```c
const float decay = expf(-dt_s / fmaxf(tau[i], 1e-6f));
h_new[i] = h_old[i] * decay + (1.0f - decay) * tanhf(pre[i]);
```

CfC `τ` is per-channel (`Vec<f32>` of length `n_hid=HIDDEN_DIM`), initialized log-uniform over `[0.01, 1000]` per `crates/ml-alpha/src/cfc/trunk.rs:204-209`. `τ` is also trained.

For a channel with `τ_i = 6000` and `dt = 1` (event-rate inference, per `forward_step_into` perception.rs:3517):

| Quantity | Value |
|---|---|
| Per-step decay | `exp(-1/6000) ≈ 0.99983` |
| Per-step input contribution `(1 − decay)` | `≈ 0.00017` |
| Cumulative h after K=32 training steps from h=0 | `≈ 32 × 0.00017 × tanh(pre) ≈ 0.005 × tanh(pre)` |
| Effective state magnitude during training | near zero |

For a channel with `τ_i = 1`:

| Quantity | Value |
|---|---|
| Per-step decay | `exp(-1) ≈ 0.37` |
| Cumulative h after a few K=32 training steps | `≈ tanh(pre[last])` |
| Effective state magnitude during training | full range |

Consequences:

1. Long-τ CfC channels produce vanishingly small `h` values during K=32 training.
2. `heads_w_skip[head, i]` (head's per-channel projection) receives gradient `grad_logit × h[i] ≈ 0` for those channels. **The per-horizon head weights through long-τ channels are effectively frozen at initialization.**
3. `τ[i]` itself receives gradient `d_decay × decay × dt / τ_eff²` where `d_decay = grad_h_new × (h_old − tanh(pre))`. With both h_old and the gradient signal near zero, **training does not push long-τ channels toward shorter τ either.**
4. At inference (sequential `forward_step_into` over thousands of events), long-τ channels finally integrate and produce meaningful state values. But the heads never learned to use them, so those channels contribute essentially DC noise that is the same across all horizon heads.
5. Per-horizon head outputs share dynamics dictated by the short-τ channels that DID train. Inference-time `mean_run_len` is therefore roughly the same across all horizons. **No differentiation.**

This mechanism is consistent with all observed phenomena: training-time AUC differentiation (heads' weights to short-τ channels do differ per horizon), training-time output stratification (intervention A nudges training-time outputs but the underlying state is shared), and the inference-time `mean_run_len` collapse.

### 1.3 The fix family

For inference-time `mean_run_len` to differ per horizon, each head's output must read from state with structurally different time constants. Three families of fix exist:

| Family | Mechanism | Verdict |
|---|---|---|
| A: shorten τ to fit K=32 | All CfC channels become fast; heads project from uniform short-τ state | **Fails the WIN gate** — does not produce multi-scale dynamics; only fixes dead-channel waste |
| B: K-window inference reset | Training and inference geometries match | **Fails the WIN gate** — geometries match but all heads still share dynamics |
| **C: route per-horizon heads via channels sorted by `CfC.tau`** | The existing per-channel CfC τ vector (`crates/ml-alpha/src/cfc/trunk.rs:138`, init log-uniform [0.01, 1000] per line 204) IS the per-channel multi-scale signal; sort by it, bucket, route heads | **Approach taken** |

**Bucketing source: CfC.tau, not Mamba2 A.** Verified against code (`feedback_trust_code_not_docs`):
- `crates/ml-alpha/src/cfc/trunk.rs:138` — `tau_d: CudaSlice<f32>` is per-hidden-channel of size `HIDDEN_DIM=128`. **Per-channel, trained, multi-scale** (log-uniform init spans 5 decades).
- `crates/ml-alpha/cuda/mamba2_alpha_kernel.cu:57-89` — Mamba2's `w_a` is shape `[hidden_dim, state_dim ≤ 32]` and the per-step gate is `sigmoid(a_proj[i,t,s])` with `a_proj = x @ W_a.T + b_a`. **The gate is input-dependent, recomputed every sample × every step. No per-channel intrinsic A_diag exists.**

CfC.tau is the natural bucketing source because the §1.2 decay math literally derives from its per-channel values. The mechanism that breaks differentiation (long-τ channels are dead in K=32 training) is the same mechanism that gives us the bucketing signal (τ-spectrum is multi-scale by construction).

Empirical anchor: `pearl_state_amplifies_short_horizon_into_long_horizon` showed Mamba2 SSM state lifts AUC 0.50→0.66 at K=6000. CfC's contribution (0.66 → 0.745) is a per-channel-τ-filtered smoother on top — confirming CfC.tau-based bucketing is reading the right signal.

---

## 2. Architecture

### 2.1 Forward flow

```
snap_feature → vsn → mamba2_l1 → ln_a → mamba2_l2 → ln_b → attn_pool
                                                              │
                                                              ▼  [B, HIDDEN_DIM]
                          ┌───────────────────────────────────┴───────────────────────────────────┐
                          │  Phase 1: WARMUP (epochs 0..W, W decided by ISV A-stability EMA)      │
                          │     - All channels routed identically (no bucketing)                  │
                          │     - Single CfC body trains W_in/W_rec/b/τ as before                 │
                          │     - heads_w_skip/w1/w2/etc. train with full-dim reads               │
                          └───────────────────────────────────┬───────────────────────────────────┘
                                                              ▼
                          ┌───────────────────────────────────┴───────────────────────────────────┐
                          │  Phase 1→2 TRANSITION (one batch, ISV-triggered, ALL ON DEVICE):      │
                          │     - GPU bitonic-sort cfc.tau_d → sorted_channel_indices_d           │
                          │     - GPU kernel: bucket_id_per_channel from static quintile bounds   │
                          │     - GPU kernel: tau_all_d reorder; bucket IQRs for Controller B     │
                          │     - GPU kernel: heads_w_skip_d ragged-compact reorder               │
                          │     - Freeze: bucket-channel-map; recapture CUDA graph                │
                          └───────────────────────────────────┬───────────────────────────────────┘
                                                              ▼
                          ┌───────────────────────────────────┴───────────────────────────────────┐
                          │  Phase 2: ROUTED (epochs W..end):                                      │
                          │                                                                        │
                          │     attn_pool out  ────────►  bucket_split (5 buckets by channel)     │
                          │                                       │                                │
                          │                                       ├─►  CfC[shared W,b] τ=τ_0 ──►  head_h30    ─►  logit_h30
                          │                                       ├─►  CfC[shared W,b] τ=τ_1 ──►  head_h100   ─►  logit_h100
                          │                                       ├─►  CfC[shared W,b] τ=τ_2 ──►  head_h300   ─►  logit_h300
                          │                                       ├─►  CfC[shared W,b] τ=τ_3 ──►  head_h1000  ─►  logit_h1000
                          │                                       └─►  CfC[shared W,b] τ=τ_4 ──►  head_h6000  ─►  logit_h6000
                          │                                                                        │
                          │     Each branch carries its own h_state across forward_step_into calls │
                          │     Heads are block-diagonal: head_h_k reads ONLY bucket_k channels    │
                          └────────────────────────────────────────────────────────────────────────┘
```

### 2.2 Shapes and parameters

- `HIDDEN_DIM = 128` (current value, no widening). Confirmed at `crates/ml-alpha/src/heads.rs:20`.
- `N_HORIZONS = 5` (matches existing per-horizon heads).
- **Bucket-channel-count vector**: `bucket_dim_k = [25, 25, 25, 25, 28]` (sum = 128 = HIDDEN_DIM). Last bucket absorbs the remainder because HIDDEN_DIM is not divisible by 5.
- **MAX_BUCKET_DIM = 28** (used for uniform CUDA block shape; idle threads in shorter buckets handled by uniform predicate; see §5.4 point 1).
- CfC body weights (`W_in: [HIDDEN_DIM, HIDDEN_DIM]`, `W_rec: [HIDDEN_DIM, HIDDEN_DIM]`, `b: [HIDDEN_DIM]`) are **shared across all 5 branches**.
- `tau_all_d: CudaSlice<f32>` total size `sum(bucket_dim_k) = 128`; addressed via `bucket_channel_offset[k]..bucket_channel_offset[k] + bucket_dim_k[k]`.
- Per-branch h_state: `h_state_all_d: CudaSlice<f32>` of size `n_batch × sum(bucket_dim_k) = n_batch × 128`, same addressing pattern.
- `heads_w_skip_d`: compact ragged storage of size `sum(bucket_dim_k) = 128` (each horizon reads only its bucket; total bytes = `128 × sizeof(f32) = 512` per ragged column, NOT `[5 × 128 = 640]`). Storage is **5× reduced** from the original block-diagonal-with-zeros layout.
- `heads_w_skip_offset: [N_HORIZONS + 1]` — start+end indices of each horizon's compact weights.
- `bucket_channel_offset: [N_HORIZONS + 1] -> u32` — start+end indices into HIDDEN_DIM-ordered channel space. Device-resident, frozen at transition. Last entry = HIDDEN_DIM for end-cap.
- `bucket_id_per_channel: [HIDDEN_DIM] -> u8` — inverse map, device-resident, also frozen.

Total new device-resident metadata: `(N_HORIZONS + 1) * 4 + HIDDEN_DIM = 24 + 128 = 152` bytes — negligible.

### 2.3 Two-phase training

**Phase 1 (warmup, epochs 0..W):**

Identical to the current architecture: single CfC body operating on full HIDDEN_DIM state, full HIDDEN_DIM-wide heads, all backward gradients flow as today. Controller A (Section 3.1) monitors `CfC.tau` drift to detect when the trunk has stabilized enough to derive the bucketing.

**Phase 1→2 transition (one batch boundary, ISV-triggered, ALL ON DEVICE):**

Per `feedback_no_htod_htoh_only_mapped_pinned` and `feedback_cpu_is_read_only`, the entire transition happens on the GPU. No CPU sort, no DtoH bulk read of τ. The host only OBSERVES Controller A's scalar trigger signal (single float via mapped-pinned, per the standard host-decision pattern).

Quintile boundaries are KNOWN constants given `HIDDEN_DIM=128` and `bucket_dim_k=[25,25,25,25,28]`: cumulative-sum offsets are `[0, 25, 50, 75, 100, 128]`. Static device constants; no per-step or per-transition computation needed.

Device kernels launched at transition (between epochs, OUTSIDE captured graph):

1. **`tau_sort_kernel`** — sort `cfc.tau_d` ascending. Bitonic-merge sort, single block of 32 threads, 4 passes for HIDDEN_DIM=128 (uniform-block, no host branching). Output: `sorted_channel_indices_d: [HIDDEN_DIM]` device tensor (channel index in sorted-by-τ order).

2. **`bucket_assign_kernel`** — for each `sorted_position p ∈ [0, HIDDEN_DIM)`: lookup which bucket via static `[0, 25, 50, 75, 100, 128]` boundaries; write `bucket_id_per_channel[sorted_channel_indices_d[p]] = bucket_id`. Single block, HIDDEN_DIM threads.

3. **`bucket_iqr_kernel`** — for each bucket k, compute median + IQR of τ values within bucket. Block-per-bucket, BUCKET_SIZE threads, warp-shuffle reductions per `feedback_no_atomicadd`. Output: `bucket_tau_median_d: [N_HORIZONS]`, `bucket_tau_iqr_lo_d: [N_HORIZONS]`, `bucket_tau_iqr_hi_d: [N_HORIZONS]`. Used by Controller B as the IQR-widened center.

4. **`tau_reorder_kernel`** — copy `cfc.tau_d` values to `tau_all_d` in bucket-grouped order. Per the static `bucket_channel_offset = [0, 25, 50, 75, 100, 128]`, channel `c` in bucket `k` at within-bucket-position `p` writes `tau_all_d[bucket_channel_offset[k] + p] = cfc.tau_d[c]`. Preserves per-channel-within-bucket variation; median is metadata, not collapsed value.

5. **`heads_compact_kernel`** — reorder `heads_w_skip` into ragged-compact `heads_w_skip_d: [HIDDEN_DIM]`. For each bucket k, head_h_k's compact weights read from `existing_heads_w_skip[k * HIDDEN_DIM + channel_indices_in_bucket_k]`. Per the static `bucket_channel_offset`, output offsets are known constants.

6. **Constants populated on device** (no host-side bulk memcpy): `bucket_channel_offset_d: [N_HORIZONS+1] = [0, 25, 50, 75, 100, 128]`, `bucket_dim_k_d: [N_HORIZONS] = [25, 25, 25, 25, 28]`, `heads_w_skip_offset_d: [N_HORIZONS+1] = [0, 25, 50, 75, 100, 128]`. Two acceptable implementations:
   - (a) **Compile-time embedded**: kernels reference these as `__constant__` device-side arrays linked into the cubin; no runtime memcpy needed.
   - (b) **One-time init kernel**: a single device kernel writes the constants into device tensors at program start (before training). Single kernel launch, no host data transfer.
   Both implementations are equally valid; choice deferred to plan-writing (small detail).

7. **Seed-scoping** per `pearl_scoped_init_seed_for_reproducibility`: any reallocation of `tau_all_d` / `heads_w_skip_d` device buffers wraps the GPU-side init in `scoped_init_seed(cfg.seed)`. Per-allocation determinism preserved.

8. **Emit `kernel-step-trace` event** marking transition: the device-side `bucket_iqr_kernel` writes per-bucket median τ values into the existing GPU log ring buffer (mapped-pinned, per the GPU log ring spec). Host drains the ring asynchronously; no synchronous DtoH.

9. **Recapture the training CUDA Graph** per `pearl_no_host_branches_in_captured_graph` + `pearl_cudarc_disable_event_tracking_for_graph_capture` (bracket capture with disable/enable, no host-malloc, no scalar arg changes after capture).

The total device-only work at transition: 5 kernel launches + 1 graph recapture. Estimated wall time < 5ms.

**Phase 2 (routed, epochs W..end):**

Per-branch τ trains independently. Heads' `w_skip` operates on its bucket's BUCKET_DIM channels. Controllers B, C, D active. All training behavior otherwise identical (random sampling, K=32, intervention-A regularizer per-bucket).

### 2.4 Inference (`forward_step_into`)

Phase 2 only — production checkpoints have already-frozen routing.

```
1. Existing pipeline up to attn_pool — unchanged.
2. attn_pool output [B, HIDDEN_DIM] is conceptually viewed as N_HORIZONS contiguous buckets via bucket_channel_offset.
3. Fused per-branch CfC step kernel (single launch, see Section 5.4 point 1):
   - Grid: (B, N_HORIZONS, 1)
   - Block: (MAX_BUCKET_DIM = 28, 1, 1) — uniform block shape; idle threads in shorter buckets (uniform predicate, no warp divergence)
   - Each block reads its bucket's start/size from `bucket_channel_offset[blockIdx.y]` and `bucket_dim_k[blockIdx.y]`
4. Per-branch h_new values write back to h_state_all_d for next step.
5. Heads forward (existing kernel, modified to read compact w_skip): produces [B, N_HORIZONS] logits.
6. Sigmoid → probs → device-resident alpha_probs_d as today.
```

### 2.5 Atomic refactor: MTER scaffolding removal

The following are deleted in the same commit chain as the per-horizon CfC implementation:

- `LoaderMode::Sequential` enum variant
- `next_sequence_sequential` function
- `sequential_file_idx`, `sequential_anchor_idx`, `last_call_was_file_boundary` cursor fields in MultiHorizonLoader
- `notify_file_boundary` method on PerceptionTrainer
- `last_seen_file_boundary` field
- `train_graph_boundary_state` field and its recapture-on-boundary-change logic
- `--loader-mode` CLI flag
- `loader-mode` Argo template parameter

The transition-boundary CUDA Graph recapture infrastructure (`train_graph_boundary_state` mechanism) is **redirected** to the Phase 1→2 transition rather than file boundaries. Pattern preserved; trigger condition changed.

---

## 3. ISV-driven controllers

Per `pearl_controller_anchors_isv_driven` and `feedback_isv_for_adaptive_bounds`, every adaptive threshold or bound in the control loop derives from a signal. No hardcoded constants.

### 3.1 Controller A — Warmup-completion detector

Decides when CfC.tau has stabilized enough to derive the bucketing.

**Memory contract**: per `feedback_cpu_is_read_only` and `feedback_no_htod_htoh_only_mapped_pinned`, all signal computation happens on device. The host's role is to read a SINGLE FLOAT scalar via mapped-pinned (the `tau_change_ema` value at end of step) and compare against `noise_floor / 4` (a host-local scalar derived from the first-observation read). This is the standard host-decision pattern; no bulk data moves CPU↔GPU.

```
Per-step signal (all on device):
  tau_change[t] = ‖cfc.tau_t − cfc.tau_{t-1}‖_F²            // single GPU kernel: vector diff + Frobenius reduce → single float on device
  tau_change_ema = wiener_α_ema(tau_change[t])               // device-side EMA update; result is a single float on device
  α = diff_var / (diff_var + sample_var + ε)                 // Wiener-α, all device-side
  α = max(α, 0.4)                                            // floor for non-stationary, per pearl_wiener_alpha_floor_for_nonstationary

Anchor (first-observation bootstrap, per pearl_first_observation_bootstrap):
  At t=0: device kernel reads tau_change[0] and writes noise_floor[k=0] to a mapped-pinned scalar slot.
  Host reads the mapped-pinned slot ONCE at t=0 to keep noise_floor as a host-local f32 (read-only).
  Subsequent steps NEVER re-read τ from device; only tau_change_ema scalar is read.

Trigger condition (host-side scalar compare):
  Per step, host reads tau_change_ema scalar from mapped-pinned device-visible slot (no DtoH; mapped-pinned IS the device-visible buffer).
  if tau_change_ema < max(noise_floor / 4, noise_floor / 16):
    trigger Phase 1→2 transition.
  // SNR margin derivation: 4 = standard SNR margin for "signal has decayed below detection" (4× below initial noise floor matches SP16 controller idiom for stabilized signals); 16 = 4² safety floor preventing degenerate near-zero noise_floor from collapsing the threshold to 0. Both factors anchored to the same first-obs noise_floor — they encode "4× and 16× below observed initial dispersion", not arbitrary constants.
  // max-with-floor per pearl_blend_formulas_must_have_permanent_floor

Safety cap (ISV-anchored, derived from observed dispersion):
  half_life = first step at which tau_change drops below noise_floor / 2 (observed online; first-obs bootstrap if no crossing within first 100 steps).
  hard_cap_warmup_steps = half_life × (1 + MAD(tau_change in first 100 steps) / median(tau_change in first 100 steps))
    // ISV-anchored cap: more dispersion in observed signal → larger cap multiplier
    // Replaces hardcoded 1.5× with a signal-derived dispersion factor
  Computed on device, tracked via device-side step counter; host reads single counter scalar via mapped-pinned for the safety check.
```

When triggered → execute Phase 1→2 transition (Section 2.3, all-on-device).

**Controllers B, C, D, E follow the same memory contract**: signals computed on device, single-scalar mapped-pinned reads for host-side trigger decisions only. Controllers' state (EMAs, accumulators) is device-resident throughout training; no bulk DtoH ever occurs.

### 3.2 Controller B — Per-bucket τ bound (post-Adam projection)

Keeps each branch's τ within its bucket's empirical range during Phase 2.

**Why not loss-weight modulation**: per `pearl_adam_normalizes_loss_weights`, adding `loss += λ * τ_drift` would be a no-op — `tau_all_d` is its own parameter group and Adam normalizes m/sqrt(v). A K× loss-weight lift produces K× m, K²× v, identical step ratio.

**Mechanism**: hard projection AFTER each Adam step on the τ slice. This bypasses Adam's normalization entirely.

```
At transition time, the all-on-device kernels (§2.3 step 3) produce:
  bucket_tau_q1_d[k], bucket_tau_q3_d[k]                                // per-bucket IQR, device-resident
  slack_factor_d[k] = derived per-bucket from observed Q3/Q1 ratio:
                       slack_factor_k = sqrt(Q3_k / Q1_k)
                       // ISV-anchored: wider IQR gets less additional slack; narrow IQR gets more
                       // Avoids hardcoded 1.5; the slack is proportional to bucket's natural log-spread
  IQR_widened_lo_d[k] = bucket_tau_q1_d[k] / slack_factor_d[k]
  IQR_widened_hi_d[k] = bucket_tau_q3_d[k] * slack_factor_d[k]

After every Adam step that updates tau_all_d (single fused clamp kernel, device-resident bounds):
  for each channel c ∈ [0, HIDDEN_DIM):
    k = bucket_id_per_channel[c]
    tau_all_d[c] = clamp(tau_all_d[c], IQR_widened_lo_d[k], IQR_widened_hi_d[k])
```

Hard projection on a single param group. Gradient signal can still drift τ within the widened IQR each step. Outside the IQR, Adam's m/v continue to accumulate (state preserved for when gradient pushes back inward) but the projection clips the visible parameter.

If a bucket's τ values consistently want to drift outside the IQR for >`tau_change_half_life` consecutive steps, that signals a Controller D dead-bucket condition or a Mamba2-state-shift; logged via kernel-step-trace, treated as a soft warning (not panic).

### 3.3 Controller C — Per-bucket smoothness (per-bucket LR multiplier)

Extends intervention A's existing single-λ ISV controller to 5 independent per-bucket regulators.

**Why not loss-weight modulation**: same Adam-normalization no-op critique as Controller B. The smoothness loss for bucket k backpropagates into `head_h_k` weight slice — its own parameter group. λ-weight on the loss term cancels under Adam's normalization.

**Mechanism**: per-bucket LR multiplier on the head_h_k parameter group's Adam step.

```
Per bucket k, per training step:
  jitter_k = K-window position-variance of head_h_k output     // existing intervention-A signal, scoped to bucket
  jitter_ema_k = wiener_α_ema(jitter_k)
  α = max(α, 0.4)                                              // floor per pearl_wiener_alpha_floor_for_nonstationary (this IS a co-adapting closed loop)

  target_jitter_k = sqrt(realised_label_variance_k)             // ISV anchor from per-horizon label distribution; sentinel-bootstrap at step 0 per pearl_first_observation_bootstrap

  smoothness_ratio_k = max(0, jitter_ema_k − target_jitter_k) / max(target_jitter_k, ε_floor)
                                                                // signal-driven, max-with-floor, ε_floor = target_jitter_k_at_first_observation / 16

  // LR multiplier per head_h_k parameter group:
  lr_multiplier_h_k = 1.0 / (1.0 + smoothness_ratio_k)          // multiplicative attenuation when jitter exceeds target
                                                                // Bounded in (0, 1]: when jitter is in spec, lr_mult=1; when far over, lr_mult→0

  // Smoothness loss term WITHOUT λ-weight (just unit weight, because the LR multiplier provides the regulation):
  loss += smoothness_loss_k
```

Where `smoothness_loss_k` is the existing K-window output-smoothness loss from intervention A, evaluated on horizon-k bucket's head output. The kernel is `output_smoothness.cu` called 5 times with different (bucket-id, head-output-slice) arguments. Each loss term contributes its gradient at unit weight; the per-bucket LR multiplier on the Adam step regulates the response amplitude — escaping Adam's m/sqrt(v) normalization.

Each loss term has exactly one unbounded multiplicand per `pearl_one_unbounded_signal_per_reward`.

### 3.4 Controller D — Dead-bucket detector (recovery-first, inheritance-last)

Post-warmup safety: detect if any bucket's CfC state never integrates. Per `feedback_no_functionality_removal`, recovery is attempted BEFORE the bucket is collapsed to a neighbor's dynamics.

```
Per bucket k, every step in Phase 2:
  h_mag_k = mean(|h_state_branch_k|) across batch and bucket_dim_k channels
  h_mag_k_ema = wiener_α_ema(h_mag_k)
  α = max(α, 0.4)                                              // co-adapting closed loop, floor per pearl_wiener_alpha_floor_for_nonstationary

First-observation bootstrap (NOT deferred):
  At Phase 2 first step: first_observation_floor[k] = h_mag_k[t=transition+1]
  Replace directly per pearl_first_observation_bootstrap; no waiting period.
  Note on bucket ordering: buckets are sorted ascending-by-τ (bucket 0 = shortest τ, bucket 4 = longest τ). Per §1.2 mechanism, short-τ channels integrate fast → larger h_mag at first observation; long-τ channels integrate slowly → smaller h_mag at first observation. So first_observation_floor is monotonically decreasing with bucket index k.

Threshold derivation (ISV-anchored, no inter-bucket coupling):
  For each bucket k INDEPENDENTLY:
    dead_threshold[k] = first_observation_floor[k] × decay_relative_floor
    decay_relative_floor = exp(-1) ≈ 0.368   // 1/e of first-observation magnitude is the standard half-life floor; derived from CfC's own decay formula, not a hardcoded fraction
  // No inter-bucket comparison; "dead" means bucket-k's own state has shrunk to 1/e of its initial value over the dead_window. This is the natural CfC time-constant criterion.

Detection window (ISV-anchored):
  dead_window_k = ceil(2 × half_life of h_mag_k_ema under current Wiener-α)
  (replaces the hardcoded "50 consecutive steps")

Dead-bucket condition:
  h_mag_k_ema < dead_threshold[k]   for ≥ dead_window_k consecutive steps

On dead detection — RECOVERY-FIRST cascade:
  1. RECOVERY ATTEMPT 1: relax Controller B's slack factor for this bucket by ×2:
     slack_factor_d[k] := 2 × slack_factor_d[k]   // doubles the τ envelope; if τ is being pinched, this releases it
     // The factor 2 is the natural log-doubling step; previously the slack was sqrt(Q3/Q1) — doubling captures one full log-spread step beyond the bucket's natural range
     Emit kernel-step-trace event: "bucket_k_recovery_widen"
     Re-check dead condition for next dead_window_k steps.

  2. RECOVERY ATTEMPT 2 (if Attempt 1 fails): reassign N_swap channels from the most-active neighbor
     bucket into this dead bucket (channels with highest h_mag move).
     N_swap = ceil(bucket_dim_k * (1 - h_mag_k_ema / first_observation_floor[k]))   // ISV-derived from how-far-below-floor the bucket has decayed
                                                                                      // 0 channels swap if EMA still at floor; bucket_dim_k channels swap if EMA = 0
                                                                                      // Replaces hardcoded "5 channels"
     N_swap = clamp(N_swap, 1, bucket_dim_k - 1)  // ensure at least 1 swap, at most bucket_dim_k-1 (preserve target bucket structure)
     Recompute bucket_channel_offset and bucket_id_per_channel; recapture CUDA graph.
     Emit kernel-step-trace event: "bucket_k_recovery_channel_swap" with N_swap value
     Re-check dead condition for next dead_window_k steps.

  3. INHERITANCE FALLBACK (only after both recoveries fail):
     Inherit τ from neighbor (k-1) or (k+1) — pick the neighbor with closer label_variance to bucket k.
     tau_all_d[k slice] := tau_all_d[neighbor slice]
     Emit kernel-step-trace event: "bucket_k_inheritance_fallback" (this is a degraded outcome, logged distinctly).
     Continue training.

  After any of the 3 paths, reset Controller D state for bucket k (avoid repeated firing).
```

Recovery-first satisfies `feedback_no_functionality_removal`: the bucket's architectural slot is preserved through two corrective attempts before degraded inheritance is permitted.

If inheritance is reached, the trading layer's `WeightedByRealizedSharpe` ensemble will detect correlated leaf Sharpe between the dead bucket and its neighbor and downweight the redundant one — graceful degradation, NOT silent collapse.

### 3.5 Controller E — Inference-time bucket validation (post-checkpoint)

Diagnostic only; runs in `crt-diag` tool, not in production hot path.

```
At inference (offline diagnostic only), per bucket k:
  output_run_len_k_ema = wiener_α_ema(run length of sign(head_h_k_logit) being constant)
    // head_h_k emits a scalar logit per step; "run length" = number of consecutive steps with sign(logit_k - 0.5) unchanged
    // This is the metric CRT.diag already computes (harness.rs:374 `sum_run_length[b * n_horizons + h]`)
  τ_assigned_k = median tau across branch k's bucket_dim_k channels

  Cross-check:
    Spearman rank correlation of (τ_assigned_k for k in 0..5) and (output_run_len_k_ema for k in 0..5)

Threshold (statistically anchored, not hardcoded):
  Use Spearman ρ, not Pearson — we only require monotonic ordering, not linear scaling.
  For N=5 ordered pairs, the threshold for "strictly increasing within sampling noise" is
  ρ ≥ (N−1)/N = 0.8  // exactly one out-of-order adjacent pair is the natural single-sample slack
  This derives from the discrete rank-correlation structure for N=5, not a hardcoded constant.

  If ρ < 0.8:
    Warning logged; treated as a soft failure of the inference-differentiation gate.
```

---

## 4. Validation gates

The design is validated against three smokes, in order. Each smoke gates the next.

### 4.1 Smoke 1: training-time stability (~20 min L40S)

Argo run, random sampling, 20 epochs, no early-stop, default seed 16962.

| Quantity | Gate | Source |
|---|---|---|
| `val_loss` stays within 1.5× of Phase 1 peak across all 20 epochs | required | rules out catastrophic divergence like the sequential failure (val_loss 3 → 18) |
| Best per-horizon `val_AUC` (h6000) | ≥ `chance + (random_baseline_h6000_auc − chance) × (1 − regression_tolerance)` where `regression_tolerance` derives from per-seed variance of random baseline (run a 3-seed baseline sweep BEFORE this smoke to anchor) | invariant-based per `pearl_tests_must_prove_not_lock_observations` — assert that the architecture didn't materially regress, not a fixed 0.72 threshold |
| Controller A transition triggered | within ISV-derived hard cap (Section 3.1) | if cap hit before signal-driven trigger, surfaces as "soft" outcome; Smoke 2 may still pass with imperfect routing |
| Controller D dead-bucket detector fires | 0 occurrences ideal; recovery-attempt-1 firing acceptable; recovery-attempt-2 firing soft warning; inheritance-fallback distinct outcome (logged, counted) | recovery cascade in §3.4 means each level is a different soft-outcome classification |
| Controller B post-Adam projection clips | bounded; per-bucket fraction of params clipped per step < 50% sustained | confirms IQR widening matches training data — if half the params want to live outside the IQR sustained, that's a signal the bucketing source was wrong |

### 4.2 Smoke 2: inference-time differentiation (~10 min, CRT.diag on Smoke 1 checkpoint)

The actual WIN gate. Uses existing `crt-diag` tooling with the Phase 2 checkpoint.

| Quantity | Gate | Source |
|---|---|---|
| `mean_run_len[h6000] / mean_run_len[h30]` | **≥ 10×** | original WIN criterion |
| `mean_run_len[h6000]` absolute | **≥ 100 events** | original WIN criterion |
| Monotonic ordering: `mean_run_len[h30] < h100 < h300 < h1000 < h6000` | strictly increasing | confirms 5 distinct dynamics |
| Controller E Spearman ρ: assigned τ ↔ observed run-length | ≥ 0.8 (Spearman, threshold derived from N=5 single-pair slack) | confirms multi-scale dynamics flow through to outputs |

### 4.3 Smoke 3: trading-sim end-to-end (~5-10 min)

Run `fxt-backtest` with the Phase 2 checkpoint against existing test scenarios.

| Quantity | Gate | Source |
|---|---|---|
| `fxt-backtest` completes without panic / NaN / inf-cascade | required | basic robustness check |
| Per-leaf max position size | ≤ existing leaf bound | confirms Kelly sizing doesn't blow under new dynamics |
| `WeightedByRealizedSharpe` ensemble weights at end | non-degenerate (no leaf at 100%) | confirms 5 useful signals, not 1+4 redundant |
| Total PnL vs current-architecture baseline | ≥ 0.9× baseline (soft); stretch goal ≥ baseline | rules out regression in trading performance |
| Per-leaf trade frequency: h30 leaf trades >> h6000 leaf trades | qualitative | confirms differentiation flows through to trading behavior |

### 4.4 Failure outcomes

Each row below corresponds to a distinct mechanism with a distinct next-step (per `feedback_kill_runs_on_anomaly_quickly`, gates must discriminate mechanisms).

| Outcome | Distinct mechanism | Next step (distinct) |
|---|---|---|
| Smoke 1 ✓ Smoke 2 ✓ Smoke 3 ✓ | DESIGN VALIDATED | Document pearl `pearl_per_horizon_channel_routing_via_cfc_tau`. Set up walk-forward and production training. |
| Smoke 1 ✓ Smoke 2 fails ratio gate AND Controller A triggered cleanly | Bucketing source produces τ ordering BUT outputs don't differentiate | Dump per-bucket head_h_k output histograms. Likely cause: shared CfC body (per §5.8 out-of-scope item) is too restrictive — per-bucket independent W_in/W_rec/b becomes the next-spec scope. |
| Smoke 1 ✓ Smoke 2 fails ratio gate AND Controller A hit hard cap | CfC.tau distribution hasn't separated into 5 distinct quintiles | Inspect CfC.tau histogram at hard cap. If most channels clustered in narrow band: longer Phase 1 needed (extend hard cap derivation) or τ-init was wrong (revisit log-uniform range). |
| Smoke 1 ✓ Smoke 2 ✓ Smoke 3 fails PnL gate | Trading economic objective diverges from WIN criterion | SERIOUS finding requiring follow-up design. NOT code rollback; trade-off must be made explicit; user decision required. |
| Smoke 1 fails val_AUC invariant gate | Bucket split + block-diagonal head mask costs AUC at training | Investigate: per-bucket head_h_k can't see enough channels to learn. Either widen bucket coverage (heads read OWN bucket + neighbors with soft mask) or revert to single-CfC. |
| Smoke 1 fails val_loss stability (divergence cascade) | Controllers B/C/D are interacting destructively | Disable Controllers B and C (use plain Adam); re-run. If stable: re-design the per-bucket regulators. If not: deeper bug. |
| Controller D recovery-attempt-1 fires (IQR widen) | Single bucket's τ values converging outside initial IQR | Likely benign: bucketing was conservative. Soft success if Smoke 2 + Smoke 3 still pass. |
| Controller D recovery-attempt-2 fires (channel swap) | One bucket's CfC.tau-sorted channels weren't actually multi-scale | Bucketing source partially failed at the chosen bucket; investigate that quintile's channels post-hoc. |
| Controller D inheritance-fallback fires | Both recoveries failed for a bucket | Logged as degraded outcome. Smoke 3 may still pass with ensemble rebalancing. If >1 bucket hits inheritance: bucketing source is fundamentally wrong — re-brainstorm.|

---

## 5. Implementation scope and performance

Per `feedback_no_partial_refactor` and `pearl_no_deferrals_for_complementary_fixes`, the per-horizon CfC architecture lands in one commit chain with MTER scaffolding atomically removed.

### 5.1 Files added (4)

- `crates/ml-alpha/src/cfc/bucket_routing.rs` — Controller A logic, freeze metadata, transition orchestration (kernel dispatch + graph recapture)
- `crates/ml-alpha/cuda/cfc_step_per_branch.cu` — **single fused kernel** covering all 5 branches × n_batch in one launch
- `crates/ml-alpha/cuda/heads_block_diagonal_fwd.cu` — heads forward with compact block-diagonal `w_skip` storage and bucket offset lookup
- `crates/ml-alpha/cuda/bucket_transition_kernels.cu` — **5 device kernels for the Phase 1→2 transition** (all-on-device per `feedback_no_htod_htoh_only_mapped_pinned`): `tau_sort_kernel` (bitonic-merge, 1 block × 32 threads × 4 passes for HIDDEN_DIM=128), `bucket_assign_kernel` (HIDDEN_DIM threads, write `bucket_id_per_channel`), `bucket_iqr_kernel` (block-per-bucket median + IQR via warp-shuffle reduction), `tau_reorder_kernel` (channel-reorder tau into bucket-grouped `tau_all_d`), `heads_compact_kernel` (ragged-compact reorder of `heads_w_skip`)

### 5.2 Files modified (8)

- `crates/ml-alpha/src/cfc/trunk.rs` — `tau_d` replaced by `tau_all_d: CudaSlice<f32>` of size `sum(bucket_dim_k) = HIDDEN_DIM = 128`; `bucket_channel_offset: [N_HORIZONS+1]` (last entry = HIDDEN_DIM for end-cap); `bucket_dim_k: [N_HORIZONS]` (sizes per bucket — [25, 25, 25, 25, 28]). Checkpoint envelope adds these three vectors + per-channel τ values laid out compactly. Bump Checkpoint envelope (greenfield, no backward compat).
   - **Checkpoint envelope consumer audit (3 sites, all migrated atomically in this commit chain)**:
     1. `crates/ml-alpha/src/cfc/trunk.rs:30,334,398` — `Checkpoint` struct definition + save_checkpoint + load_checkpoint (the producer)
     2. `crates/ml-alpha/src/trainer/perception.rs:3826` — `CfcTrunk::load_checkpoint` consumer in `from_checkpoint`
     3. `crates/ml-backtesting/tests/checkpoint_smoke.rs:34-44` — smoke test that exercises load_checkpoint
   - No additional consumers found via `grep -rn "load_checkpoint\|CfcTrunk" --include="*.rs"`. Audit complete.
- `crates/ml-alpha/src/cfc/step.rs` — binding for the per-branch fused kernel
- `crates/ml-alpha/src/heads.rs` — block-diagonal mask on `heads_w_skip`; storage compacted from `[N_HORIZONS × HIDDEN_DIM]` to ragged `[sum(bucket_dim_k) = HIDDEN_DIM]` via `heads_w_skip_offset`; optimizer Adam moments shrink correspondingly
- `crates/ml-alpha/src/trainer/perception.rs` — two-phase loop (warmup → transition → routed); Controllers A/B/C/D wired into `step_batched`; `forward_step_into` 5-branch fused CfC call + heads with compact w_skip
- `crates/ml-alpha/src/data/loader.rs` — REMOVE `LoaderMode::Sequential`, `next_sequence_sequential`, `sequential_file_idx`, `sequential_anchor_idx`, `last_call_was_file_boundary`. `LoaderMode` reduces to a single-variant enum that may be deleted.
- `crates/ml-alpha/examples/alpha_train.rs` — REMOVE `--loader-mode` CLI flag and `notify_file_boundary()` call. Add `--bucket-warmup-cap-steps` flag (default: ISV-derived from first 100 steps; CLI override for diagnostic purposes only).
- `infra/k8s/argo/alpha-perception-template.yaml` — REMOVE `loader-mode` workflow param. Optionally add `bucket-warmup-cap-steps` param (default unset → ISV-derived).
- `crates/ml-alpha/cuda/output_smoothness.cu` — intervention A regularizer scoped per-bucket. Same kernel, called 5× with different (bucket-id, head-output-slice) arguments, contributing to per-bucket jitter signal for Controller C.

### 5.3 Files deleted (0)

The existing MTER spec and plan documents (`docs/superpowers/specs/2026-05-21-crt-train-intervention-b-multi-timescale-readout.md` and corresponding plan) remain as historical record. They are not in the build path.

### 5.4 Performance optimizations (in scope from day one)

NVIDIA-grade kernel design is part of correctness here, not a separate optimization pass.

1. **Single fused CfC kernel for all 5 branches** (uniform-block-shape pattern)
   - `HIDDEN_DIM = 128` is not divisible by 5; bucket sizes are `[25, 25, 25, 25, 28]`. To keep a single fused launch with a uniform block shape:
   - Launch: `grid = (n_batch, N_HORIZONS, 1)`, `block = (MAX_BUCKET_DIM = 28, 1, 1)`
   - Each block reads its bucket's start offset from `bucket_channel_offset[blockIdx.y]` and its size from `bucket_dim_k[blockIdx.y]`
   - Threads with `threadIdx.x >= bucket_dim_k[blockIdx.y]` early-return (3 idle threads in 4 of 5 buckets — negligible). No branch divergence within a warp since the early-return is a uniform comparison against a per-block constant.
   - Each block addresses `tau_all_d[bucket_channel_offset[blockIdx.y] + threadIdx.x]`
   - Shared `W_in`/`W_rec`/`b` cooperative-staged into shared memory once per block, per `pearl_cooperative_staging_eliminates_redundant_reads`

2. **Compact block-diagonal heads storage** (ragged layout for uneven bucket sizes)
   - `heads_w_skip` was `[N_HORIZONS × HIDDEN_DIM]` = `[5 × 128]` = 640 floats with off-bucket entries forced to zero.
   - Becomes a ragged compact buffer of size `sum(bucket_dim_k) = 128` floats total — each head_h_k owns only its bucket's contiguous slice.
   - Storage **5× reduced** (640 → 128 floats); memory bandwidth proportionally reduced.
   - GRN heads kernel takes `heads_w_skip_offset[N_HORIZONS+1]` device array (start+end), reads from offset.
   - Zero data movement at hot path — bucket assignment is purely addressing metadata.

3. **No tensor reshuffling at bucket-split**
   - `bucket_id_per_channel: [HIDDEN_DIM] -> u8` and `bucket_channel_offset: [N_HORIZONS] -> u32` precomputed at transition, frozen, ~256 bytes device-resident
   - Kernels use the metadata for addressing — zero data shuffling on the hot path
   - Per-branch h_state laid out as `[n_batch × sum(bucket_dim_k) = n_batch × HIDDEN_DIM]` single contiguous allocation; per-branch slice via `bucket_channel_offset[k]..bucket_channel_offset[k+1]`, no allocator pressure

4. **CUDA Graph capture preserved**
   - Phase 1→2 transition happens OUTSIDE the captured graph (between epochs)
   - Phase 2 captured graph is monomorphic — no host branches, no scalar-arg changes per step
   - One-time graph recapture at transition — same boundary-aware pattern as the (now-removed) sequential mode's file boundary recapture, redirected to phase transition
   - Per `pearl_no_host_branches_in_captured_graph` and `pearl_cudarc_disable_event_tracking_for_graph_capture`

5. **Coalesced writes in heads forward**
   - Heads' per-horizon logit outputs already shape `[n_batch × N_HORIZONS]` from existing kernel
   - Confirm via inspection under new compact w_skip layout; if non-coalesced, swap thread role per `pearl_coalesce_via_thread_role_swap`

6. **Per-branch backward respects mask without divergence**
   - Backward CfC kernel: same per-branch fused pattern as forward, single launch
   - Backward heads: gradient on `heads_w_skip` only fills compact `[sum(bucket_dim_k) = 128]` positions; off-bucket positions DO NOT EXIST in the storage (it's compact, not block-zero-padded). Adam moments live only over the compact storage.
   - No branch divergence: bucket addressing is uniform within a block

7. **Adam step kernel reuses existing path + per-bucket LR multipliers (Controllers B and C)**
   - Per-branch τ values are contiguous slices of single `tau_all_d` device tensor (size HIDDEN_DIM = 128, unchanged from pre-bucketing layout) — same Adam-update kernel as today
   - Adam moment buffers (`m`, `v`) for `tau_all_d` are single contiguous allocations matched to the param shape
   - **Per-bucket LR multiplier** (Controller C mechanism): the Adam update kernel takes a per-parameter-group LR multiplier vector. For `head_h_k` weight slices, the multiplier is `lr_multiplier_h_k` from Controller C. For `tau_all_d`, multiplier stays 1.0 (Controller B operates via post-Adam projection, not LR).
   - **Post-Adam projection** (Controller B mechanism): after `tau_all_d` is updated by Adam, a single clamp kernel applies `tau_all_d[c] = clamp(tau_all_d[c], IQR_widened[bucket_id_per_channel[c]].lo, IQR_widened[bucket_id_per_channel[c]].hi)`. Single launch, threadIdx covers all 128 channels.
   - **Seed-scoping** (per `pearl_scoped_init_seed_for_reproducibility`): the Phase 1→2 transition's `tau_all_d` and `heads_w_skip_d` reallocations MUST be wrapped in `scoped_init_seed(cfg.seed)`. Any xavier-init or random-init helpers within the transition use this scope.

8. **`fxt-backtest` inference reuses the same kernels**
   - `forward_step_into` at deployment uses the SAME fused per-branch step kernel as training
   - 5 independent h_state buffers (sum = HIDDEN_DIM floats per batch slot) — identical total to the current single-h_state memory footprint
   - Layout: `h_state_all_d: [n_batch × HIDDEN_DIM]` contiguous; per-branch slices addressed via `bucket_channel_offset`

9. **Launch-order assertion (per `pearl_canary_input_freshness_launch_order`)**
   - During Phase 1, NO kernel reads `bucket_id_per_channel`, `bucket_channel_offset`, or any Phase-2-only tensor. Phase 1 captured graph and Phase 2 captured graph are DISJOINT — different kernel pointer sets, different metadata tensors. The transition (between epochs) recaptures from scratch.
   - During Phase 2, the bucket metadata tensors are read by every per-branch CfC kernel; they're written ONLY at the transition by the device kernels in §2.3 (no HtoD; static constants like `bucket_channel_offset=[0,25,50,75,100,128]` can be either compile-time embedded or initialized once at program start). `bucket_id_per_channel` is the only metadata that depends on actual τ values; it's computed by `bucket_assign_kernel` from `sorted_channel_indices_d` (also device-side).

### 5.5 Trading-layer integration (no code changes, validation only)

The trading layer is already structured around per-horizon probs:

- `ml-backtesting/policy/mod.rs`: 5 leaf strategies indexed by `horizon_idx: u8`; each runs per-horizon ISV-Kelly
- `ml-backtesting/sim/mod.rs`: `alpha_probs_d` is `[N_HORIZONS]` device slice — interface preserved
- `ml-backtesting/harness.rs`: hardcoded horizon labels `[30, 100, 300, 1000, 6000]` — unchanged
- `harness.rs` CRT.diag: already computes `flip_count`, `mean_run_len`, `run_length_hist` per (batch, horizon)
- Policy's `WeightedByRealizedSharpe`: adaptive ensemble across the 5 leaves — auto-rebalances when leaf signals genuinely differentiate (which the new architecture provides)

**Integration risk** is operational, not interface-level: NaN propagation, exposure bound behavior under suddenly-differentiated leaf signals, capital allocation responsiveness. Smoke 3 covers these.

### 5.6 Memory updates (4 entries in same commit chain)

- New: `pearl_per_horizon_channel_routing_via_cfc_tau.md`
  > "Horizon-conditioned channel routing where per-horizon heads read state channels sorted by `CfC.tau` (the existing per-channel CfC time-constant vector). The multi-tau state already exists in CfC; this design structurally binds each horizon's head to a quintile of the tau spectrum, producing inference-time temporal differentiation. Verified: the bucketing source must be a real per-channel signal in the codebase — Mamba2's `A` is input-dependent, not a per-channel rate, so it's NOT the bucketing source despite being a tempting candidate."
- Update: `pearl_training_smoothness_does_not_transfer_to_inference.md` — mark mechanism understood, link to this design
- Update: `project_mter_commit1_smoke_outcome.md` — link to successor design; mark verdict closed
- New: `pearl_trading_layer_designed_for_differentiated_per_horizon_dynamics.md`
  > "5-horizon trading ensembles with WeightedByRealizedSharpe can absorb genuinely-differentiated per-horizon signals without code change. The bottleneck for inference-time horizon differentiation was always upstream in the alpha generation, not in the trading layer."

### 5.7 Verification matrix (commit chain complete only when ALL pass)

- `cargo check --workspace --all-targets` clean
- `SQLX_OFFLINE=true cargo test -p ml-alpha --lib` passes
- `SQLX_OFFLINE=true cargo test -p ml-backtesting --lib` passes (downstream consumers still green)
- Local CUDA smoke on RTX 3050 (required, per `feedback_nvidia_grade_perf_for_kernels` — local sm_86 smoke catches UB before cluster deploy). With bucket sizes [25,25,25,25,28] and HIDDEN_DIM=128 total, memory footprint is trivially below 4GB.
- Argo Smoke 1 (training-time stability)
- Argo Smoke 2 (CRT.diag inference-time differentiation)
- Argo Smoke 3 (fxt-backtest trading sim)

### 5.8 Explicitly out of scope

These were considered during brainstorming and deferred. Each would require its own design and is NOT to be included in the per-horizon CfC commit chain.

- Per-bucket independent W_in/W_rec/b CfC bodies (shared body validated first; future spec if shared body underperforms)
- Mamba2 A initialization constraints (let Mamba2 learn whatever it learns; if Phase 1→2 transition reveals A clustering, future spec)
- Production walk-forward training of the new architecture (after design validation, separate spec)
- H100 training (smokes are on L40S per `feedback_default_to_l40s_pool`; if production needs H100, separate spec)

---

## 6. References

### Memory entries this design depends on

- `pearl_training_smoothness_does_not_transfer_to_inference` — predecessor pearl; documents the bottleneck
- `pearl_random_sampling_sufficient_for_per_horizon_training_auc` — refutes MTER's premise
- `pearl_state_amplifies_short_horizon_into_long_horizon` — empirical anchor for Mamba2 multi-scale capacity
- `pearl_controller_anchors_isv_driven` — every controller anchor ISV-driven
- `pearl_first_observation_bootstrap` — sentinel-bootstrap pattern
- `pearl_wiener_optimal_adaptive_alpha` — α derivation
- `pearl_wiener_alpha_floor_for_nonstationary` — α floor 0.4 in control loops where target drifts
- `pearl_blend_formulas_must_have_permanent_floor` — max-with-floor not blend
- `pearl_one_unbounded_signal_per_reward` — controller term hygiene
- `pearl_no_host_branches_in_captured_graph` — CUDA Graph constraint
- `pearl_cooperative_staging_eliminates_redundant_reads` — shared body weights staging
- `pearl_coalesce_via_thread_role_swap` — heads kernel output coalescing
- `feedback_no_partial_refactor` — atomic migration of consumers
- `feedback_isv_for_adaptive_bounds` — bounds derive from ISV
- `feedback_default_to_l40s_pool` — smoke on L40S
- `project_mter_commit1_smoke_outcome` — context for why MTER is abandoned

### Code references (verified during spec authorship)

- `crates/ml-alpha/cuda/cfc_step.cu:57-58` — CfC math `decay = exp(-dt/τ); h_new = h_old·decay + (1-decay)·tanh(pre)`
- `crates/ml-alpha/src/cfc/trunk.rs:138` — `tau_d: CudaSlice<f32>` of size HIDDEN_DIM=128 — **the bucketing source**
- `crates/ml-alpha/src/cfc/trunk.rs:204-209` — current log-uniform τ init over [10ms, 1000s]
- `crates/ml-alpha/src/cfc/trunk.rs:30,334,398` — Checkpoint struct, save_checkpoint, load_checkpoint (envelope producer + consumer)
- `crates/ml-alpha/src/trainer/perception.rs:3826` — load_checkpoint consumer in `from_checkpoint`
- `crates/ml-backtesting/tests/checkpoint_smoke.rs:34-44` — smoke test exercising load_checkpoint (3rd consumer)
- `crates/ml-alpha/cuda/mamba2_alpha_kernel.cu:57-89` — verified Mamba2's input-dependent selective scan gate (NOT a per-channel A_diag)
- `crates/ml-alpha/src/mamba2_block.rs:551-554,631-633` — verified `w_a` shape `[hidden_dim, state_dim≤32]`
- `crates/ml-alpha/src/trainer/perception.rs:3319` — `forward_step_into` inference path
- `crates/ml-alpha/src/trainer/perception.rs:3517` — `dt_s = 1.0` event-rate per CfC step
- `crates/ml-alpha/src/heads.rs:20` — `HIDDEN_DIM = 128`
- `crates/ml-backtesting/src/policy/mod.rs:23` — per-horizon Kelly leaf structure
- `crates/ml-backtesting/src/harness.rs:147` — horizons `[30, 100, 300, 1000, 6000]`

### Diagnostic Argo workflows

- `alpha-perception-8gz72` — original MTER commit-1 smoke (sequential, patience=5, false-fail)
- `alpha-perception-diag-seq-120533` — 20-epoch sequential no-early-stop diagnostic (revealed catastrophic divergence at epoch 7+, peak h6000=0.745 at epoch 6)
- `alpha-perception-diag-rnd-120534` — 20-epoch random control no-early-stop (stable throughout, peak h6000=0.745 at epoch 5)

---

## 7. Open questions deferred to plan-writing

These are detail-level choices that the implementation plan (writing-plans skill) should pin down before Task 1. Q1 (originally about Mamba2's A_diag) is RESOLVED — bucketing source is `CfC.tau`, not Mamba2 A. The remaining questions:

- **Q-label-var**: Exact form of label-variance signal for Controller C's `target_jitter_k` — likely existing per-horizon BCE label distribution; need to verify the per-bucket scoping is sound and that `sentinel-bootstrap at step 0` lands on a non-degenerate value
- **Q-ckpt-tests**: Existing `checkpoint_smoke_tests::save_load_roundtrip_preserves_all_weights` (`cfc/trunk.rs:485`) currently asserts bit-equality of `tau_d`; needs migration to assert bit-equality of `tau_all_d` + `bucket_channel_offset` + `bucket_dim_k` + `bucket_id_per_channel`. Plan must include this test migration.
- **Q-smoothness-api**: The existing `output_smoothness.cu` kernel API may or may not support per-bucket invocation natively — need to inspect the kernel signature at plan-writing time and decide between (a) per-bucket wrapper, (b) extending the kernel with a `bucket_offset` argument
- **Q-3-seed-baseline**: Before Smoke 1, run a 3-seed baseline sweep on the CURRENT architecture (HEAD f22f3f948) to establish `random_baseline_h6000_auc` mean ± stddev. This anchors the Smoke 1 invariant gate's `regression_tolerance`. ~30 min total wall.
- **(RESOLVED in §3.4)**: Controller D Recovery-Attempt-2's channel swap stride is now ISV-derived from `bucket_dim_k * (1 - h_mag_k_ema / first_observation_floor)`. No longer an open question.
- **Q-recapture-cost**: CUDA graph recapture cost at Phase 1→2 transition — empirically measure during plan execution. If > 100ms, consider keeping bucket metadata as a graph-input rather than recompiling.
