# Forward-Port Main onto ML Crate Split Branch — Implementation Plan

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

**Goal:** Port 24 commits of GPU performance work from `main` onto `feature/ml-crate-split-phase2`, adapting file paths to the new sub-crate layout.

**Architecture:** Cherry-pick + path-rewrite in 8 phases. Commits that only touch sub-crates (`ml-core`, `ml-dqn`, etc.) or infra files apply directly. Commits that touch `crates/ml/` files which were moved to sub-crates need path-rewritten patches. Deleted files (parquet, smoke_tests) are triaged. Compiler-driven fixup after each phase.

**Tech Stack:** Rust workspace, git format-patch/apply, sed path rewriting, `SQLX_OFFLINE=true cargo check --workspace`

**Worktree:** `.worktrees/ml-split-p2` on branch `feature/ml-crate-split-phase2`

**Build command:** `SQLX_OFFLINE=true cargo check --workspace`
**Test command:** `SQLX_OFFLINE=true cargo test -p <crate> --lib`
**Clippy:** `SQLX_OFFLINE=true cargo clippy --workspace -- -D warnings`

---

## File Move Mapping (Branch Reality)

These files were moved from `crates/ml/src/` to sub-crates on the branch. Main modified them at their OLD paths. Patches must target the NEW paths.

```
OLD PATH (on main)                              → NEW PATH (on branch)
─────────────────────────────────────────────────────────────────────────
crates/ml/src/cuda_pipeline/*                   → crates/ml-core/src/cuda_pipeline/*
crates/ml/src/features/extraction.rs            → crates/ml-features/src/extraction.rs
crates/ml/src/features/production_adapter.rs    → crates/ml-features/src/production_adapter.rs
crates/ml/src/features/unified.rs               → crates/ml-features/src/unified.rs
crates/ml/src/features/mod.rs                   → crates/ml-features/src/lib.rs (merged into)
crates/ml/src/trainers/dqn/trainer.rs           → crates/ml-dqn/src/trainer/trainer.rs
crates/ml/src/trainers/dqn/config.rs            → crates/ml-dqn/src/trainer/config.rs
crates/ml/src/trainers/dqn/data_loading.rs      → crates/ml-dqn/src/trainer/data_loading.rs
crates/ml/src/trainers/dqn/features.rs          → crates/ml-dqn/src/trainer/features.rs
crates/ml/src/trainers/dqn/mod.rs               → crates/ml-dqn/src/trainer/mod.rs
crates/ml/src/trainers/dqn/monitoring.rs        → crates/ml-dqn/src/trainer/monitoring.rs
crates/ml/src/trainers/ppo.rs                   → crates/ml-ppo/src/trainer.rs
crates/ml/src/hyperopt/adapters/dqn.rs          → crates/ml-hyperopt/src/adapters/dqn.rs
crates/ml/src/hyperopt/adapters/ppo.rs          → crates/ml-hyperopt/src/adapters/ppo.rs
crates/ml/src/hyperopt/adapters/continuous_ppo.rs → crates/ml-hyperopt/src/adapters/continuous_ppo.rs
crates/ml/src/hyperopt/adapters/mamba2.rs       → crates/ml-hyperopt/src/adapters/mamba2.rs
crates/ml/src/hyperopt/shared_data.rs           → crates/ml-hyperopt/src/shared_data.rs
crates/ml/src/ensemble/model_adapter.rs         → crates/ml-ensemble/src/model_adapter.rs

STILL IN crates/ml/ (no rewrite needed):
  src/data_loaders/*, src/walk_forward.rs, src/hyperopt/adapters/tft.rs,
  examples/*, tests/*, Cargo.toml

DELETED on branch (main's changes are OBSOLETE):
  src/data_loaders/parquet_utils.rs   — branch intentionally removed all parquet code
  src/feature_cache.rs                — branch deleted (superseded by ml-features)
  src/trainers/tft_parquet.rs         — branch deleted (parquet removal)
  src/trainers/dqn/smoke_tests/*      — branch deleted entire directory (5 files, ~900 LOC)
```

---

## Phase 0: Preparation

### Task 0a: Create backup tag

**Step 1:** Tag current branch state

```bash
cd /home/jgrusewski/Work/foxhunt/.worktrees/ml-split-p2
git tag backup/ml-split-p2-before-forward-port
```

**Step 2:** Verify clean working tree

```bash
git status
```

Expected: nothing to commit, working tree clean

**Step 3:** Record baseline test counts

```bash
SQLX_OFFLINE=true cargo test --workspace --lib 2>&1 | grep "test result" | sort
```

Save output for comparison after all phases.

---

## Phase 1: Infrastructure-Only Commits (3 commits, clean cherry-pick)

These commits touch only `infra/` files — no `crates/` conflicts at all.

### Task 1a: Cherry-pick infra commits

**Commits:**
- `47d49595` fix(train): correct --output to --output-dir arg, add Prometheus annotations
- `9d6a04ab` fix(grafana): add 20 missing metric panels to training dashboard
- `356f1d29` fix(train): remove duplicate annotations block from rebase artifact

**Step 1:** Cherry-pick all three (oldest first)

```bash
cd /home/jgrusewski/Work/foxhunt/.worktrees/ml-split-p2
git cherry-pick 47d49595 9d6a04ab 356f1d29
```

Expected: clean apply, no conflicts.

**Step 2:** Verify

```bash
SQLX_OFFLINE=true cargo check --workspace
```

**Step 3:** If any conflict: resolve, `git add`, `git cherry-pick --continue`

---

## Phase 2: Sub-Crate-Only Commits (5 commits, clean cherry-pick)

These commits ONLY modify files inside `crates/ml-core/`, `crates/ml-dqn/`, `crates/ml-ppo/`, or `crates/common/` — files that exist at the same paths on both branches.

### Task 2a: Cherry-pick sub-crate performance commits

**Commits (chronological):**
- `0355fb17` perf(metrics): expose step-level DQN metrics to Prometheus gauges
- `db65eb56` perf(gpu): eliminate GPU→CPU sync barriers from training hot path
- `ee12f1d9` perf(gpu): zero-sync gradient clipping — eliminate all GPU→CPU barriers
- `379c0bee` fix(dqn): cast F32 constants to input dtype for BF16 mixed precision on H100
- `1b9f09fc` perf(dqn): eliminate GPU→CPU roundtrip in branching action decomposition

**Step 1:** Cherry-pick all five

```bash
git cherry-pick 0355fb17 db65eb56 ee12f1d9 379c0bee 1b9f09fc
```

**Step 2:** If `1b9f09fc` fails (branching.rs may not exist yet — it's added by `89c3fb89` which comes in Phase 5): skip it for now, cherry-pick it after Phase 5.

```bash
git cherry-pick --abort  # if it failed
# We'll retry 1b9f09fc after Phase 5
```

**Step 3:** Verify

```bash
SQLX_OFFLINE=true cargo check --workspace
```

**Step 4:** Commit checkpoint message if needed

---

## Phase 3: Mixed Commits — Sub-Crate + Simple ML Path Rewrites (7 commits)

These commits touch sub-crate files (apply cleanly) PLUS `crates/ml/` files that were moved. Strategy: cherry-pick, let sub-crate parts apply, then manually fix the `crates/ml/` parts using path-rewritten patches.

### Task 3a: GPU PER hot path (`e35ead5f`)

**Main's changes:** ml-dqn/dqn.rs, ml-dqn/quantile_regression.rs (clean), + `ml/src/cuda_pipeline/gpu_experience_collector.rs` (→ ml-core), `ml/src/trainers/dqn/config.rs` (→ ml-dqn/trainer), `ml/src/trainers/dqn/trainer.rs` (→ ml-dqn/trainer)

**Step 1:** Generate and rewrite patch

```bash
cd /home/jgrusewski/Work/foxhunt
git format-patch -1 e35ead5f --stdout > /tmp/e35ead5f.patch

# Rewrite moved file paths
sed -i 's|a/crates/ml/src/cuda_pipeline/|a/crates/ml-core/src/cuda_pipeline/|g' /tmp/e35ead5f.patch
sed -i 's|b/crates/ml/src/cuda_pipeline/|b/crates/ml-core/src/cuda_pipeline/|g' /tmp/e35ead5f.patch
sed -i 's|a/crates/ml/src/trainers/dqn/|a/crates/ml-dqn/src/trainer/|g' /tmp/e35ead5f.patch
sed -i 's|b/crates/ml/src/trainers/dqn/|b/crates/ml-dqn/src/trainer/|g' /tmp/e35ead5f.patch
```

**Step 2:** Apply with reject mode

```bash
cd /home/jgrusewski/Work/foxhunt/.worktrees/ml-split-p2
git apply --reject /tmp/e35ead5f.patch
```

**Step 3:** Resolve any `.rej` files. Check for `use crate::` paths that need updating:
- In ml-core files: `use crate::` is already correct (they're in ml-core)
- In ml-dqn/trainer files: `use crate::cuda_pipeline::` → `use ml_core::cuda_pipeline::`

**Step 4:** Compiler-driven fixup

```bash
SQLX_OFFLINE=true cargo check -p ml-core -p ml-dqn
```

Fix any remaining import errors.

**Step 5:** Commit

```bash
git add -A
git commit -m "forward-port(e35ead5f): wire GPU PER into hot path (path-adapted)"
```

### Task 3b: IQN GPU PER weights + CUDA default (`b616d024`)

**Main's changes:** 10 sub-crate Cargo.tomls (cuda default feature) + `ml/Cargo.toml` + `ml/src/trainers/dqn/config.rs` (→ ml-dqn/trainer)

**Step 1:** Generate and rewrite patch

```bash
cd /home/jgrusewski/Work/foxhunt
git format-patch -1 b616d024 --stdout > /tmp/b616d024.patch
sed -i 's|a/crates/ml/src/trainers/dqn/|a/crates/ml-dqn/src/trainer/|g' /tmp/b616d024.patch
sed -i 's|b/crates/ml/src/trainers/dqn/|b/crates/ml-dqn/src/trainer/|g' /tmp/b616d024.patch
```

**Step 2:** Apply

```bash
cd /home/jgrusewski/Work/foxhunt/.worktrees/ml-split-p2
git apply --reject /tmp/b616d024.patch
```

**Step 3:** Cargo.toml conflicts are likely — the sub-crate Cargo.tomls have different dependency lists on the branch. For each `.rej` file in a Cargo.toml:
- Main added `default-features = ["cuda"]` — manually add this to the branch's version
- Main may have version bumps — adopt them

**Step 4:** Also add `default-features = ["cuda"]` to any NEW sub-crate Cargo.tomls the branch created (ml-data, ml-benchmark, ml-deployment) that main didn't know about.

**Step 5:** Verify + commit

```bash
SQLX_OFFLINE=true cargo check --workspace
git add -A
git commit -m "forward-port(b616d024): IQN GPU PER weights, CUDA default features (path-adapted)"
```

### Task 3c: Eliminate CPU tensor ops (`801781a4`)

**Main's changes:** ml-core/gradient_accumulation.rs, ml-dqn/dqn.rs, ml-dqn/gpu_replay_buffer.rs (clean) + `ml/src/trainers/dqn/config.rs` (→ ml-dqn/trainer)

**Step 1-5:** Same pattern as 3a — format-patch, sed trainer path, apply, fix, commit.

```bash
cd /home/jgrusewski/Work/foxhunt
git format-patch -1 801781a4 --stdout > /tmp/801781a4.patch
sed -i 's|a/crates/ml/src/trainers/dqn/|a/crates/ml-dqn/src/trainer/|g' /tmp/801781a4.patch
sed -i 's|b/crates/ml/src/trainers/dqn/|b/crates/ml-dqn/src/trainer/|g' /tmp/801781a4.patch
cd /home/jgrusewski/Work/foxhunt/.worktrees/ml-split-p2
git apply --reject /tmp/801781a4.patch
# Fix .rej files, cargo check, commit
```

### Task 3d: Replace remaining CPU tensor allocations (`5191fa02`)

**Main's changes:** ml-dqn/distributional.rs, ml-dqn/dqn.rs, ml-dqn/quantile_regression.rs, ml-ppo/ppo.rs — ALL sub-crate files, no path rewrite needed.

```bash
cd /home/jgrusewski/Work/foxhunt
git format-patch -1 5191fa02 --stdout > /tmp/5191fa02.patch
cd /home/jgrusewski/Work/foxhunt/.worktrees/ml-split-p2
git apply --reject /tmp/5191fa02.patch
# Should apply cleanly. Verify + commit.
```

### Task 3e: GPU searchsorted kernel (`0f45537e`)

**Main's changes:** ml-core/mixed_precision.rs, ml-dqn/gpu_replay_buffer.rs, ml-dqn/searchsorted_kernel.cu — all sub-crate.

```bash
cd /home/jgrusewski/Work/foxhunt
git format-patch -1 0f45537e --stdout > /tmp/0f45537e.patch
cd /home/jgrusewski/Work/foxhunt/.worktrees/ml-split-p2
git apply --reject /tmp/0f45537e.patch
```

**Note:** `searchsorted_kernel.cu` is a new file — verify it's created in `crates/ml-dqn/src/`.

### Task 3f: BF16 mixed precision default (`7f3066e6`)

**Main's changes:** ml-core/mixed_precision.rs, ml-dqn/curiosity.rs, ml-dqn/network.rs, ml-hyperopt/traits.rs (all clean) + `ml/src/hyperopt/adapters/dqn.rs` (→ ml-hyperopt/adapters)

```bash
cd /home/jgrusewski/Work/foxhunt
git format-patch -1 7f3066e6 --stdout > /tmp/7f3066e6.patch
sed -i 's|a/crates/ml/src/hyperopt/adapters/|a/crates/ml-hyperopt/src/adapters/|g' /tmp/7f3066e6.patch
sed -i 's|b/crates/ml/src/hyperopt/adapters/|b/crates/ml-hyperopt/src/adapters/|g' /tmp/7f3066e6.patch
cd /home/jgrusewski/Work/foxhunt/.worktrees/ml-split-p2
git apply --reject /tmp/7f3066e6.patch
```

### Task 3g: BF16 softmax overflow (`fb18e0f1`)

**Main's changes:** ml-dqn/distributional_dueling.rs, ml-dqn/rainbow_network.rs (clean) + `ml/src/trainers/dqn/config.rs` (→ ml-dqn/trainer)

```bash
cd /home/jgrusewski/Work/foxhunt
git format-patch -1 fb18e0f1 --stdout > /tmp/fb18e0f1.patch
sed -i 's|a/crates/ml/src/trainers/dqn/|a/crates/ml-dqn/src/trainer/|g' /tmp/fb18e0f1.patch
sed -i 's|b/crates/ml/src/trainers/dqn/|b/crates/ml-dqn/src/trainer/|g' /tmp/fb18e0f1.patch
cd /home/jgrusewski/Work/foxhunt/.worktrees/ml-split-p2
git apply --reject /tmp/fb18e0f1.patch
```

### Task 3-checkpoint: Verify Phase 3

```bash
SQLX_OFFLINE=true cargo check --workspace
SQLX_OFFLINE=true cargo clippy --workspace -- -D warnings 2>&1 | head -30
```

Commit any remaining fixups.

---

## Phase 4: The Monster Commit — GPU-Native Regime Classification (`c0c44a5f`)

This single commit touches **42 files** (+821/-514 lines) spanning cuda_pipeline, features, data_loaders, ensemble, hyperopt, trainers, and tests. It must be decomposed into per-destination patches.

### Task 4a: Extract and split the patch

**Step 1:** Generate the full patch

```bash
cd /home/jgrusewski/Work/foxhunt
git format-patch -1 c0c44a5f --stdout > /tmp/c0c44a5f-full.patch
```

**Step 2:** Create per-destination copies with path rewrites

```bash
# Copy A: cuda_pipeline changes → ml-core
cp /tmp/c0c44a5f-full.patch /tmp/c0c44a5f-cuda.patch
sed -i 's|a/crates/ml/src/cuda_pipeline/|a/crates/ml-core/src/cuda_pipeline/|g' /tmp/c0c44a5f-cuda.patch
sed -i 's|b/crates/ml/src/cuda_pipeline/|b/crates/ml-core/src/cuda_pipeline/|g' /tmp/c0c44a5f-cuda.patch

# Copy B: features changes → ml-features
cp /tmp/c0c44a5f-full.patch /tmp/c0c44a5f-features.patch
sed -i 's|a/crates/ml/src/features/|a/crates/ml-features/src/|g' /tmp/c0c44a5f-features.patch
sed -i 's|b/crates/ml/src/features/|b/crates/ml-features/src/|g' /tmp/c0c44a5f-features.patch

# Copy C: trainers/dqn changes → ml-dqn/trainer
cp /tmp/c0c44a5f-full.patch /tmp/c0c44a5f-trainer.patch
sed -i 's|a/crates/ml/src/trainers/dqn/|a/crates/ml-dqn/src/trainer/|g' /tmp/c0c44a5f-trainer.patch
sed -i 's|b/crates/ml/src/trainers/dqn/|b/crates/ml-dqn/src/trainer/|g' /tmp/c0c44a5f-trainer.patch

# Copy D: hyperopt/adapters changes → ml-hyperopt/adapters
cp /tmp/c0c44a5f-full.patch /tmp/c0c44a5f-hyperopt.patch
sed -i 's|a/crates/ml/src/hyperopt/adapters/|a/crates/ml-hyperopt/src/adapters/|g' /tmp/c0c44a5f-hyperopt.patch
sed -i 's|b/crates/ml/src/hyperopt/adapters/|b/crates/ml-hyperopt/src/adapters/|g' /tmp/c0c44a5f-hyperopt.patch

# Copy E: ensemble changes → ml-ensemble
cp /tmp/c0c44a5f-full.patch /tmp/c0c44a5f-ensemble.patch
sed -i 's|a/crates/ml/src/ensemble/|a/crates/ml-ensemble/src/|g' /tmp/c0c44a5f-ensemble.patch
sed -i 's|b/crates/ml/src/ensemble/|b/crates/ml-ensemble/src/|g' /tmp/c0c44a5f-ensemble.patch

# Copy F: trainers/ppo changes → ml-ppo/trainer
cp /tmp/c0c44a5f-full.patch /tmp/c0c44a5f-ppo.patch
sed -i 's|a/crates/ml/src/trainers/ppo.rs|a/crates/ml-ppo/src/trainer.rs|g' /tmp/c0c44a5f-ppo.patch
sed -i 's|b/crates/ml/src/trainers/ppo.rs|b/crates/ml-ppo/src/trainer.rs|g' /tmp/c0c44a5f-ppo.patch
```

### Task 4b: Apply each sub-patch

**Step 1:** Apply in dependency order. Use `--reject` throughout.

```bash
cd /home/jgrusewski/Work/foxhunt/.worktrees/ml-split-p2

# A: ml-core cuda_pipeline
git apply --reject /tmp/c0c44a5f-cuda.patch 2>&1 | grep -E "Applied|Rejected"

# B: ml-features
git apply --reject /tmp/c0c44a5f-features.patch 2>&1 | grep -E "Applied|Rejected"

# C: ml-dqn trainer
git apply --reject /tmp/c0c44a5f-trainer.patch 2>&1 | grep -E "Applied|Rejected"

# D: ml-hyperopt adapters
git apply --reject /tmp/c0c44a5f-hyperopt.patch 2>&1 | grep -E "Applied|Rejected"

# E: ml-ensemble
git apply --reject /tmp/c0c44a5f-ensemble.patch 2>&1 | grep -E "Applied|Rejected"

# F: ml-ppo
git apply --reject /tmp/c0c44a5f-ppo.patch 2>&1 | grep -E "Applied|Rejected"

# G: The original patch for files that DIDN'T move (data_loaders, tests, examples, etc.)
git apply --reject /tmp/c0c44a5f-full.patch 2>&1 | grep -E "Applied|Rejected"
```

**Step 2:** Each patch will partially apply and partially reject (since each copy has ALL hunks but only some paths match). Resolve `.rej` files one by one.

**Step 3:** Handle special cases:
- `features/mod.rs` on main → merged into `ml-features/src/lib.rs` on branch. Manually read the diff hunk and apply to lib.rs.
- `trainers/dqn/smoke_tests/*` — main CREATED these files; branch DELETED the directory. **Decision: Skip.** These smoke tests tested the monolithic trainer; they need to be rewritten for ml-dqn's trainer structure. Create a TODO in the commit message.
- `trainers/tft_parquet.rs` — DELETED on branch. Skip (parquet removal intentional).
- `data_loaders/parquet_utils.rs` — DELETED on branch. Skip.
- `feature_cache.rs` — DELETED on branch. Skip.
- `hyperopt/shared_data.rs` → `ml-hyperopt/src/shared_data.rs` — apply shared_data changes with path rewrite.

**Step 4:** Clean up reject files

```bash
find . -name "*.rej" -type f
# Review each, apply manually or confirm skip, then delete
find . -name "*.rej" -type f -delete
```

### Task 4c: Compiler-driven fixup for regime classification

This is the most intensive fixup step. The regime classification commit changed function signatures and data flow across crate boundaries.

**Step 1:** Run compiler

```bash
SQLX_OFFLINE=true cargo check --workspace 2>&1 | head -100
```

**Step 2:** Fix import paths. Common patterns:
```
# In ml-core/cuda_pipeline files:
use crate::mixed_precision::training_dtype  → already correct (in ml-core)

# In ml-dqn/trainer files:
use crate::cuda_pipeline::  → use ml_core::cuda_pipeline::
use crate::features::       → use ml_features::

# In ml-hyperopt/adapters files:
use crate::trainers::dqn::  → use ml_dqn::trainer::
use crate::features::       → use ml_features::

# In ml-ensemble files:
use crate::features::       → use ml_features::
```

**Step 3:** Add missing crate dependencies to Cargo.toml files if needed (e.g., if ml-hyperopt now needs ml-features).

**Step 4:** Iterate: `cargo check`, fix, `cargo check`, fix... until clean.

**Step 5:** Commit

```bash
git add -A
git commit -m "forward-port(c0c44a5f): GPU-native regime classification with 42-dim features (decomposed + path-adapted)

Skipped: smoke_tests/ (deleted on branch), parquet_utils.rs, feature_cache.rs, tft_parquet.rs
TODO: Recreate DQN trainer smoke tests in crates/ml-dqn/tests/"
```

---

## Phase 5: Heavy Commits (4 commits)

### Task 5a: GPU PER monitoring (`d12273e1`, 17 files, +1148/-115)

**Main's changes span:** cuda_pipeline (→ ml-core), trainers/dqn (→ ml-dqn/trainer), hyperopt/adapters/dqn (→ ml-hyperopt), smoke_tests (→ DELETED), + ml-dqn sub-crate files, ml-core files.

**Step 1:** Full rewrite-and-apply

```bash
cd /home/jgrusewski/Work/foxhunt
git format-patch -1 d12273e1 --stdout > /tmp/d12273e1.patch

# Apply all path rewrites
sed -i 's|a/crates/ml/src/cuda_pipeline/|a/crates/ml-core/src/cuda_pipeline/|g' /tmp/d12273e1.patch
sed -i 's|b/crates/ml/src/cuda_pipeline/|b/crates/ml-core/src/cuda_pipeline/|g' /tmp/d12273e1.patch
sed -i 's|a/crates/ml/src/trainers/dqn/|a/crates/ml-dqn/src/trainer/|g' /tmp/d12273e1.patch
sed -i 's|b/crates/ml/src/trainers/dqn/|b/crates/ml-dqn/src/trainer/|g' /tmp/d12273e1.patch
sed -i 's|a/crates/ml/src/hyperopt/adapters/|a/crates/ml-hyperopt/src/adapters/|g' /tmp/d12273e1.patch
sed -i 's|b/crates/ml/src/hyperopt/adapters/|b/crates/ml-hyperopt/src/adapters/|g' /tmp/d12273e1.patch

cd /home/jgrusewski/Work/foxhunt/.worktrees/ml-split-p2
git apply --reject /tmp/d12273e1.patch
```

**Step 2:** The `smoke_tests/` hunks will fail (directory doesn't exist on branch). This is expected — these 5 files (~900 LOC) were deleted on the branch. The `.rej` files for smoke_tests can be safely deleted.

**Step 3:** Fix imports, cargo check, commit.

```bash
find . -name "*.rej" -delete
SQLX_OFFLINE=true cargo check --workspace
git add -A
git commit -m "forward-port(d12273e1): GPU PER monitoring fix (path-adapted, smoke_tests skipped)"
```

### Task 5b: Branching DQN with GPU Rainbow parity (`89c3fb89`, 10 files, +3207/-125)

**Main's changes:** ml-core/fill_simulator.rs, ml-core/lib.rs, ml-dqn/branching.rs (NEW), ml-dqn/dqn.rs, ml-dqn/lib.rs (all sub-crate — clean) + `ml/src/hyperopt/adapters/dqn.rs` (→ ml-hyperopt), `ml/src/trainers/dqn/config.rs` (→ ml-dqn/trainer), `ml/src/trainers/dqn/trainer.rs` (→ ml-dqn/trainer), infra/scripts/train.sh, docs/

```bash
cd /home/jgrusewski/Work/foxhunt
git format-patch -1 89c3fb89 --stdout > /tmp/89c3fb89.patch
sed -i 's|a/crates/ml/src/trainers/dqn/|a/crates/ml-dqn/src/trainer/|g' /tmp/89c3fb89.patch
sed -i 's|b/crates/ml/src/trainers/dqn/|b/crates/ml-dqn/src/trainer/|g' /tmp/89c3fb89.patch
sed -i 's|a/crates/ml/src/hyperopt/adapters/|a/crates/ml-hyperopt/src/adapters/|g' /tmp/89c3fb89.patch
sed -i 's|b/crates/ml/src/hyperopt/adapters/|b/crates/ml-hyperopt/src/adapters/|g' /tmp/89c3fb89.patch

cd /home/jgrusewski/Work/foxhunt/.worktrees/ml-split-p2
git apply --reject /tmp/89c3fb89.patch
```

**Note:** `branching.rs` is a NEW file that goes into `ml-dqn/src/` — should apply cleanly. Verify `ml-dqn/src/lib.rs` includes `pub mod branching;`.

Fix imports, cargo check, commit.

### Task 5c: Eliminate OOM in hyperopt (`fe223b38`, 9 files, +477/-129)

**Main's changes:** ml-dqn/dqn.rs, ml-dqn/noisy_layers.rs, ml-hyperopt/traits.rs (clean) + `ml/src/cuda_pipeline/gpu_experience_collector.rs` (→ ml-core), `ml/src/hyperopt/adapters/dqn.rs` (→ ml-hyperopt), `ml/src/trainers/dqn/config.rs` (→ ml-dqn/trainer), `ml/src/trainers/dqn/trainer.rs` (→ ml-dqn/trainer), ml/tests/* (stay in ml)

```bash
cd /home/jgrusewski/Work/foxhunt
git format-patch -1 fe223b38 --stdout > /tmp/fe223b38.patch
sed -i 's|a/crates/ml/src/cuda_pipeline/|a/crates/ml-core/src/cuda_pipeline/|g' /tmp/fe223b38.patch
sed -i 's|b/crates/ml/src/cuda_pipeline/|b/crates/ml-core/src/cuda_pipeline/|g' /tmp/fe223b38.patch
sed -i 's|a/crates/ml/src/trainers/dqn/|a/crates/ml-dqn/src/trainer/|g' /tmp/fe223b38.patch
sed -i 's|b/crates/ml/src/trainers/dqn/|b/crates/ml-dqn/src/trainer/|g' /tmp/fe223b38.patch
sed -i 's|a/crates/ml/src/hyperopt/adapters/|a/crates/ml-hyperopt/src/adapters/|g' /tmp/fe223b38.patch
sed -i 's|b/crates/ml/src/hyperopt/adapters/|b/crates/ml-hyperopt/src/adapters/|g' /tmp/fe223b38.patch

cd /home/jgrusewski/Work/foxhunt/.worktrees/ml-split-p2
git apply --reject /tmp/fe223b38.patch
```

Fix, check, commit.

### Task 5d: Retry branching action decomposition (`1b9f09fc`)

If this was skipped in Phase 2 because `branching.rs` didn't exist yet:

```bash
git cherry-pick 1b9f09fc
```

Should apply cleanly now that `89c3fb89` added `branching.rs`.

---

## Phase 6: CI/Metrics Mixed Commits (2 commits)

### Task 6a: Unblock CPU service builds (`7751f761`)

**Main's changes:** Cargo.toml (root), ml-dqn/regime_conditional.rs (clean), `ml/src/trainers/dqn/trainer.rs` (→ ml-dqn/trainer), infra/*

```bash
cd /home/jgrusewski/Work/foxhunt
git format-patch -1 7751f761 --stdout > /tmp/7751f761.patch
sed -i 's|a/crates/ml/src/trainers/dqn/|a/crates/ml-dqn/src/trainer/|g' /tmp/7751f761.patch
sed -i 's|b/crates/ml/src/trainers/dqn/|b/crates/ml-dqn/src/trainer/|g' /tmp/7751f761.patch

cd /home/jgrusewski/Work/foxhunt/.worktrees/ml-split-p2
git apply --reject /tmp/7751f761.patch
```

### Task 6b: Wire training pod scraping (`e89fbc2b`)

**Main's changes:** `ml/src/trainers/dqn/trainer.rs` (→ ml-dqn/trainer), `ml/src/trainers/ppo.rs` (→ ml-ppo/trainer.rs), infra/*

```bash
cd /home/jgrusewski/Work/foxhunt
git format-patch -1 e89fbc2b --stdout > /tmp/e89fbc2b.patch
sed -i 's|a/crates/ml/src/trainers/dqn/|a/crates/ml-dqn/src/trainer/|g' /tmp/e89fbc2b.patch
sed -i 's|b/crates/ml/src/trainers/dqn/|b/crates/ml-dqn/src/trainer/|g' /tmp/e89fbc2b.patch
sed -i 's|a/crates/ml/src/trainers/ppo.rs|a/crates/ml-ppo/src/trainer.rs|g' /tmp/e89fbc2b.patch
sed -i 's|b/crates/ml/src/trainers/ppo.rs|b/crates/ml-ppo/src/trainer.rs|g' /tmp/e89fbc2b.patch

cd /home/jgrusewski/Work/foxhunt/.worktrees/ml-split-p2
git apply --reject /tmp/e89fbc2b.patch
```

Fix, check, commit.

---

## Phase 7: Visibility & Import Fixup Pass

After all patches are applied, some items may have `pub(crate)` visibility that's now insufficient across crate boundaries.

### Task 7a: Full compiler sweep

```bash
cd /home/jgrusewski/Work/foxhunt/.worktrees/ml-split-p2
SQLX_OFFLINE=true cargo check --workspace 2>&1 | tee /tmp/forward-port-errors.log
grep "error\[" /tmp/forward-port-errors.log | sort -u | head -50
```

### Task 7b: Fix visibility errors

Common patterns:
- `pub(crate) fn foo()` in ml-core that ml-dqn now calls → change to `pub fn foo()`
- `pub(crate) struct Bar` in ml-core used by ml-hyperopt → change to `pub struct Bar`

### Task 7c: Fix missing dependency declarations

If ml-hyperopt now imports ml-features but it's not in Cargo.toml:
```toml
[dependencies]
ml-features = { workspace = true }
```

### Task 7d: Clippy clean

```bash
SQLX_OFFLINE=true cargo clippy --workspace -- -D warnings 2>&1 | head -50
```

Fix any new clippy warnings introduced by forward-ported code.

### Task 7e: Commit fixup

```bash
git add -A
git commit -m "fix: resolve visibility and import issues from forward-port"
```

---

## Phase 8: Final Verification

### Task 8a: Full workspace build

```bash
SQLX_OFFLINE=true cargo check --workspace
```

Expected: success, 0 errors.

### Task 8b: Full clippy

```bash
SQLX_OFFLINE=true cargo clippy --workspace -- -D warnings
```

Expected: 0 errors, 0 warnings (excluding known ml-dqn dead field warnings from branch).

### Task 8c: Test count comparison

```bash
# Run all ml-* tests
for crate in crates/ml-*/; do
  name=$(basename "$crate")
  echo -n "$name: "
  SQLX_OFFLINE=true cargo test -p "$name" --lib 2>&1 | grep "test result" | tail -1
done

# Run ml orchestration tests
echo -n "ml: "
SQLX_OFFLINE=true cargo test -p ml --lib 2>&1 | grep "test result" | tail -1
```

Compare totals with Phase 0 baseline. Total should be ≥ baseline (main added tests).

### Task 8d: Squash or keep history

**Option A (recommended):** Keep granular forward-port commits for traceability:
```
forward-port(e35ead5f): wire GPU PER into hot path (path-adapted)
forward-port(b616d024): IQN GPU PER weights, CUDA default features (path-adapted)
...
```

**Option B:** Squash all forward-port commits into one:
```bash
git rebase -i backup/ml-split-p2-before-forward-port
# Mark all forward-port commits as "squash"
```

### Task 8e: Update plan document

Add a completion note to `docs/plans/2026-03-08-ml-crate-split-phase2.md` noting that main's GPU changes have been forward-ported.

---

## Skip List (Merge Commits)

These are merge commits on main — skip them entirely, their content is included in the non-merge commits:

- `06d0a8d6` Merge branch 'feature/dqn-branching'
- `3a37454e` Merge branch 'feature/dqn-branching'
- `3c3812d9` Merge branch 'worktree-gpu-hotpath-audit'

---

## Deleted File Triage Summary

| File | Decision | Reason |
|------|----------|--------|
| `parquet_utils.rs` | Skip | Branch intentionally deleted all parquet code (-3K LOC) |
| `feature_cache.rs` | Skip | Superseded by ml-features architecture |
| `tft_parquet.rs` | Skip | Parquet removal |
| `smoke_tests/feature_coverage.rs` | TODO | 156 LOC — recreate in ml-dqn/tests/ |
| `smoke_tests/gpu_residency.rs` | TODO | 272 LOC — recreate in ml-dqn/tests/ |
| `smoke_tests/helpers.rs` | TODO | 85 LOC — recreate in ml-dqn/tests/ |
| `smoke_tests/training_stability.rs` | TODO | 254 LOC — recreate in ml-dqn/tests/ |
| `smoke_tests/performance.rs` | TODO | 146 LOC — recreate in ml-dqn/tests/ |

Smoke tests total ~900 LOC. They tested the monolithic DQN trainer. After the forward-port, create a follow-up task to rewrite them as integration tests in `crates/ml-dqn/tests/`.

---

## Rollback

If anything goes catastrophically wrong:

```bash
cd /home/jgrusewski/Work/foxhunt/.worktrees/ml-split-p2
git reset --hard backup/ml-split-p2-before-forward-port
```
