feat(dqn-v2): A.1 state reset registry
Typed classification of every piece of training state by reset lifecycle: FoldReset, WindowReset, SoftReset(decay_bars), TrainingPersist, SchemaContract. Replaces the previous vibes-driven fold-boundary reset logic with an explicit registry. Adding new state requires adding an entry in the same commit (Invariant 2 Wire-It-Up). Consumer wiring (reset_fold_state, reset_window_state helpers that iterate the registry) follows in Task 3 of Plan 1. Tests: 3 unit tests covering category lookup, fold-reset filtering, unknown-name handling. Authority: docs/superpowers/specs/2026-04-24-dqn-v2-unified-design.md §4.A.1 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,8 @@ mod risk;
|
||||
mod statistics;
|
||||
mod trainer;
|
||||
mod smoke_tests;
|
||||
pub mod state_reset_registry;
|
||||
pub use state_reset_registry::{ResetCategory, RegistryEntry, StateResetRegistry};
|
||||
|
||||
// Re-export all public items for backward compatibility
|
||||
pub use config::{DQNAgentType, DQNHyperparameters};
|
||||
|
||||
223
crates/ml/src/trainers/dqn/state_reset_registry.rs
Normal file
223
crates/ml/src/trainers/dqn/state_reset_registry.rs
Normal file
@@ -0,0 +1,223 @@
|
||||
//! Formal classification of every piece of training-time state by reset lifecycle.
|
||||
//! See `docs/superpowers/specs/2026-04-24-dqn-v2-unified-design.md` §4.A.1.
|
||||
|
||||
/// Reset lifecycle category for a piece of training-time state.
|
||||
///
|
||||
/// See `docs/superpowers/specs/2026-04-24-dqn-v2-unified-design.md` §4.A.1.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ResetCategory {
|
||||
/// Cleared at fold boundary.
|
||||
FoldReset,
|
||||
/// Cleared at validation-window boundary.
|
||||
WindowReset,
|
||||
/// Anneals toward bootstrap value over `decay_bars` bars at fold boundary.
|
||||
SoftReset { decay_bars: u32 },
|
||||
/// Survives across folds (learned weights).
|
||||
TrainingPersist,
|
||||
/// Version-stable across runs; touched only by explicit migration.
|
||||
SchemaContract,
|
||||
}
|
||||
|
||||
/// One entry in the reset registry.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct RegistryEntry {
|
||||
pub name: &'static str,
|
||||
pub category: ResetCategory,
|
||||
/// Short description — what this state represents. Used in diagnostic dumps.
|
||||
pub description: &'static str,
|
||||
}
|
||||
|
||||
/// Typed registry classifying every piece of training-time state.
|
||||
///
|
||||
/// Populated at construction; read-only thereafter. Adding new state requires
|
||||
/// adding a new entry here in the same commit that introduces the state
|
||||
/// (per Invariant 2 Wire-It-Up).
|
||||
#[derive(Debug)]
|
||||
pub struct StateResetRegistry {
|
||||
entries: Vec<RegistryEntry>,
|
||||
}
|
||||
|
||||
impl StateResetRegistry {
|
||||
/// Build the registry with all currently-known training state.
|
||||
pub fn new() -> Self {
|
||||
let entries = vec![
|
||||
// ───── Fold-reset state ─────────────────────────────────────
|
||||
RegistryEntry {
|
||||
name: "kelly_stats",
|
||||
category: ResetCategory::FoldReset,
|
||||
description: "Per-window Kelly win/loss counters (ps[14..18])",
|
||||
},
|
||||
RegistryEntry {
|
||||
name: "plan_state",
|
||||
category: ResetCategory::FoldReset,
|
||||
description: "Val plan_state_buf [N, 7] — plan entry snapshots",
|
||||
},
|
||||
RegistryEntry {
|
||||
name: "eval_q_mean_ema",
|
||||
category: ResetCategory::FoldReset,
|
||||
description: "Per-branch Q-mean EMAs in update_eval_v_range",
|
||||
},
|
||||
RegistryEntry {
|
||||
name: "eval_q_std_ema",
|
||||
category: ResetCategory::FoldReset,
|
||||
description: "Per-branch Q-std EMAs in update_eval_v_range",
|
||||
},
|
||||
RegistryEntry {
|
||||
name: "isv_v_range_slots",
|
||||
category: ResetCategory::FoldReset,
|
||||
description: "ISV[23..31) — v_center/v_half per branch",
|
||||
},
|
||||
RegistryEntry {
|
||||
name: "isv_learning_health",
|
||||
category: ResetCategory::FoldReset,
|
||||
description: "ISV[12] — learning_health EMA",
|
||||
},
|
||||
RegistryEntry {
|
||||
name: "isv_sharpe_ema",
|
||||
category: ResetCategory::FoldReset,
|
||||
description: "ISV[22] — training Sharpe EMA",
|
||||
},
|
||||
RegistryEntry {
|
||||
name: "isv_q_means",
|
||||
category: ResetCategory::FoldReset,
|
||||
description: "ISV[13..22) — per-direction/magnitude Q-mean EMAs + |Q| refs",
|
||||
},
|
||||
// ───── Window-reset state ───────────────────────────────────
|
||||
RegistryEntry {
|
||||
name: "portfolio_state",
|
||||
category: ResetCategory::WindowReset,
|
||||
description: "Per-window portfolio (ps[0..8]) — reset at window start",
|
||||
},
|
||||
RegistryEntry {
|
||||
name: "hold_time",
|
||||
category: ResetCategory::WindowReset,
|
||||
description: "ps[5] / ps[10] — consecutive bars in position",
|
||||
},
|
||||
RegistryEntry {
|
||||
name: "entry_price",
|
||||
category: ResetCategory::WindowReset,
|
||||
description: "ps[3] — entry price of current trade",
|
||||
},
|
||||
// ───── Soft-reset state (anneals across folds) ──────────────
|
||||
RegistryEntry {
|
||||
name: "adaptive_gamma",
|
||||
category: ResetCategory::SoftReset { decay_bars: 500 },
|
||||
description: "training_loop.rs adaptive_gamma — anneals to 0.905 bootstrap",
|
||||
},
|
||||
RegistryEntry {
|
||||
name: "isv_grad_balance_targets",
|
||||
category: ResetCategory::SoftReset { decay_bars: 500 },
|
||||
description: "ISV[31..35) — per-branch grad-norm targets",
|
||||
},
|
||||
RegistryEntry {
|
||||
name: "isv_grad_scale_limit",
|
||||
category: ResetCategory::SoftReset { decay_bars: 500 },
|
||||
description: "ISV[35] — scale-clamp limit EMA",
|
||||
},
|
||||
// ───── Training-persist state ───────────────────────────────
|
||||
RegistryEntry {
|
||||
name: "network_params",
|
||||
category: ResetCategory::TrainingPersist,
|
||||
description: "Online network weights (all tensors)",
|
||||
},
|
||||
RegistryEntry {
|
||||
name: "target_network",
|
||||
category: ResetCategory::TrainingPersist,
|
||||
description: "Target network weights — Polyak EMA of online",
|
||||
},
|
||||
RegistryEntry {
|
||||
name: "adam_momentum",
|
||||
category: ResetCategory::TrainingPersist,
|
||||
description: "Adam optimizer first moment (m)",
|
||||
},
|
||||
RegistryEntry {
|
||||
name: "adam_variance",
|
||||
category: ResetCategory::TrainingPersist,
|
||||
description: "Adam optimizer second moment (v)",
|
||||
},
|
||||
RegistryEntry {
|
||||
name: "adam_step",
|
||||
category: ResetCategory::TrainingPersist,
|
||||
description: "Adam step counter t",
|
||||
},
|
||||
RegistryEntry {
|
||||
name: "replay_buffer",
|
||||
category: ResetCategory::TrainingPersist,
|
||||
description: "PER experience buffer — persists across folds",
|
||||
},
|
||||
// ───── Schema-contract state ────────────────────────────────
|
||||
RegistryEntry {
|
||||
name: "ISV_SCHEMA_VERSION",
|
||||
category: ResetCategory::SchemaContract,
|
||||
description: "ISV[0] — slot-layout schema version (migrated only)",
|
||||
},
|
||||
RegistryEntry {
|
||||
name: "isv_iql_branch_scale_floor",
|
||||
category: ResetCategory::SchemaContract,
|
||||
description: "ISV[36] — IQL branch_scales floor (safety bound, bootstrap-only)",
|
||||
},
|
||||
];
|
||||
Self { entries }
|
||||
}
|
||||
|
||||
/// Look up a state's category by name. Returns `None` for unknown names
|
||||
/// (caller should assert Some before use to catch typos).
|
||||
pub fn category(&self, name: &str) -> Option<ResetCategory> {
|
||||
self.entries.iter().find(|e| e.name == name).map(|e| e.category)
|
||||
}
|
||||
|
||||
/// Iterator over all entries with `FoldReset` category.
|
||||
/// Used by the fold-boundary reset logic.
|
||||
pub fn fold_reset_entries(&self) -> impl Iterator<Item = &RegistryEntry> {
|
||||
self.entries.iter().filter(|e| e.category == ResetCategory::FoldReset)
|
||||
}
|
||||
|
||||
/// Iterator over all entries with `WindowReset` category.
|
||||
pub fn window_reset_entries(&self) -> impl Iterator<Item = &RegistryEntry> {
|
||||
self.entries.iter().filter(|e| e.category == ResetCategory::WindowReset)
|
||||
}
|
||||
|
||||
/// Iterator over all entries with `SoftReset` category.
|
||||
pub fn soft_reset_entries(&self) -> impl Iterator<Item = &RegistryEntry> {
|
||||
self.entries.iter().filter(|e| matches!(e.category, ResetCategory::SoftReset { .. }))
|
||||
}
|
||||
|
||||
/// All entries (debug/diagnostic dump).
|
||||
pub fn all(&self) -> &[RegistryEntry] {
|
||||
&self.entries
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for StateResetRegistry {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn registry_classifies_known_state() {
|
||||
let r = StateResetRegistry::new();
|
||||
assert_eq!(r.category("kelly_stats"), Some(ResetCategory::FoldReset));
|
||||
assert_eq!(r.category("adaptive_gamma"), Some(ResetCategory::SoftReset { decay_bars: 500 }));
|
||||
assert_eq!(r.category("network_params"), Some(ResetCategory::TrainingPersist));
|
||||
assert_eq!(r.category("ISV_SCHEMA_VERSION"), Some(ResetCategory::SchemaContract));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registry_fold_reset_iterates_only_fold_reset_entries() {
|
||||
let r = StateResetRegistry::new();
|
||||
let names: Vec<&str> = r.fold_reset_entries().map(|e| e.name).collect();
|
||||
assert!(names.contains(&"kelly_stats"));
|
||||
assert!(!names.contains(&"network_params"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registry_unknown_name_returns_none() {
|
||||
let r = StateResetRegistry::new();
|
||||
assert_eq!(r.category("nonexistent_state"), None);
|
||||
}
|
||||
}
|
||||
@@ -13,3 +13,4 @@
|
||||
| Module / kernel | Consumer path | Classification | Notes |
|
||||
|---|---|---|---|
|
||||
| (populated during A.5 audit) | | | |
|
||||
| `crates/ml/src/trainers/dqn/state_reset_registry.rs` | consumers in Plan 1 Task 3+ (reset_fold_state call) | Wired (consumer added in same plan) | A.1 primary implementation |
|
||||
|
||||
Reference in New Issue
Block a user