ruvector_sona/loops/
coordinator.rs

1//! Loop Coordinator - Orchestrates all learning loops
2
3use crate::ewc::{EwcConfig, EwcPlusPlus};
4use crate::lora::{BaseLoRA, MicroLoRA};
5use crate::loops::background::{BackgroundLoop, BackgroundLoopConfig, BackgroundResult};
6use crate::loops::instant::{InstantLoop, InstantLoopConfig};
7use crate::reasoning_bank::{PatternConfig, ReasoningBank};
8use crate::types::{QueryTrajectory, SonaConfig};
9use parking_lot::RwLock;
10use std::sync::Arc;
11use std::time::Instant;
12
13/// Loop coordinator managing all learning loops
14pub struct LoopCoordinator {
15    /// Configuration
16    config: SonaConfig,
17    /// Instant loop (Loop A)
18    instant: InstantLoop,
19    /// Background loop (Loop B)
20    background: BackgroundLoop,
21    /// Shared components
22    reasoning_bank: Arc<RwLock<ReasoningBank>>,
23    ewc: Arc<RwLock<EwcPlusPlus>>,
24    base_lora: Arc<RwLock<BaseLoRA>>,
25    /// Enabled flags
26    instant_enabled: bool,
27    background_enabled: bool,
28}
29
30impl LoopCoordinator {
31    /// Create new coordinator with default config
32    pub fn new(hidden_dim: usize) -> Self {
33        Self::with_config(SonaConfig {
34            hidden_dim,
35            embedding_dim: hidden_dim,
36            ..Default::default()
37        })
38    }
39
40    /// Create with custom config
41    pub fn with_config(config: SonaConfig) -> Self {
42        let reasoning_bank = Arc::new(RwLock::new(ReasoningBank::new(PatternConfig {
43            embedding_dim: config.embedding_dim,
44            k_clusters: config.pattern_clusters,
45            ..Default::default()
46        })));
47
48        let ewc = Arc::new(RwLock::new(EwcPlusPlus::new(EwcConfig {
49            param_count: config.hidden_dim * config.base_lora_rank * 2,
50            initial_lambda: config.ewc_lambda,
51            ..Default::default()
52        })));
53
54        let base_lora = Arc::new(RwLock::new(BaseLoRA::new(
55            config.hidden_dim,
56            config.base_lora_rank,
57            12, // Default number of layers
58        )));
59
60        let instant = InstantLoop::from_sona_config(&config);
61        let background = BackgroundLoop::new(
62            BackgroundLoopConfig::from(&config),
63            reasoning_bank.clone(),
64            ewc.clone(),
65            base_lora.clone(),
66        );
67
68        Self {
69            config,
70            instant,
71            background,
72            reasoning_bank,
73            ewc,
74            base_lora,
75            instant_enabled: true,
76            background_enabled: true,
77        }
78    }
79
80    /// Process inference trajectory (Loop A)
81    pub fn on_inference(&self, trajectory: QueryTrajectory) {
82        if self.instant_enabled {
83            self.instant.on_trajectory(trajectory);
84        }
85    }
86
87    /// Generate next trajectory ID
88    pub fn next_trajectory_id(&self) -> u64 {
89        self.instant.next_id()
90    }
91
92    /// Run background cycle if needed (Loop B)
93    pub fn maybe_run_background(&self) -> Option<BackgroundResult> {
94        if !self.background_enabled {
95            return None;
96        }
97
98        if self.background.should_run() {
99            let trajectories = self.instant.drain_trajectories();
100            if !trajectories.is_empty() {
101                return Some(self.background.run_cycle(trajectories));
102            }
103        }
104
105        None
106    }
107
108    /// Force background cycle
109    pub fn force_background(&self) -> BackgroundResult {
110        let trajectories = self.instant.drain_trajectories();
111        self.background.run_cycle(trajectories)
112    }
113
114    /// Flush instant loop updates
115    pub fn flush_instant(&self) {
116        self.instant.flush();
117    }
118
119    /// Get micro-LoRA for inference
120    pub fn micro_lora(&self) -> &Arc<RwLock<MicroLoRA>> {
121        self.instant.micro_lora()
122    }
123
124    /// Get base-LoRA for inference
125    pub fn base_lora(&self) -> &Arc<RwLock<BaseLoRA>> {
126        &self.base_lora
127    }
128
129    /// Get reasoning bank
130    pub fn reasoning_bank(&self) -> &Arc<RwLock<ReasoningBank>> {
131        &self.reasoning_bank
132    }
133
134    /// Get EWC++
135    pub fn ewc(&self) -> &Arc<RwLock<EwcPlusPlus>> {
136        &self.ewc
137    }
138
139    /// Enable/disable instant loop
140    pub fn set_instant_enabled(&mut self, enabled: bool) {
141        self.instant_enabled = enabled;
142    }
143
144    /// Enable/disable background loop
145    pub fn set_background_enabled(&mut self, enabled: bool) {
146        self.background_enabled = enabled;
147    }
148
149    /// Get statistics
150    pub fn stats(&self) -> CoordinatorStats {
151        let (buffer_len, dropped, success_rate) = self.instant.buffer_stats();
152
153        CoordinatorStats {
154            trajectories_buffered: buffer_len,
155            trajectories_dropped: dropped,
156            buffer_success_rate: success_rate,
157            patterns_stored: self.reasoning_bank.read().pattern_count(),
158            ewc_tasks: self.ewc.read().task_count(),
159            instant_enabled: self.instant_enabled,
160            background_enabled: self.background_enabled,
161        }
162    }
163}
164
165/// Coordinator statistics
166#[derive(Debug, Clone)]
167#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
168pub struct CoordinatorStats {
169    pub trajectories_buffered: usize,
170    pub trajectories_dropped: u64,
171    pub buffer_success_rate: f64,
172    pub patterns_stored: usize,
173    pub ewc_tasks: usize,
174    pub instant_enabled: bool,
175    pub background_enabled: bool,
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181    use crate::types::TrajectoryStep;
182
183    fn make_trajectory(id: u64) -> QueryTrajectory {
184        let mut t = QueryTrajectory::new(id, vec![0.1; 256]);
185        t.add_step(TrajectoryStep::new(vec![0.5; 256], vec![], 0.8, 0));
186        t.finalize(0.8, 1000);
187        t
188    }
189
190    #[test]
191    fn test_coordinator_creation() {
192        let coord = LoopCoordinator::new(256);
193        let stats = coord.stats();
194        assert_eq!(stats.trajectories_buffered, 0);
195    }
196
197    #[test]
198    fn test_inference_processing() {
199        let coord = LoopCoordinator::new(256);
200
201        for i in 0..10 {
202            let t = make_trajectory(coord.next_trajectory_id());
203            coord.on_inference(t);
204        }
205
206        let stats = coord.stats();
207        assert_eq!(stats.trajectories_buffered, 10);
208    }
209
210    #[test]
211    fn test_force_background() {
212        let coord = LoopCoordinator::new(256);
213
214        for i in 0..150 {
215            let t = make_trajectory(coord.next_trajectory_id());
216            coord.on_inference(t);
217        }
218
219        let result = coord.force_background();
220        assert_eq!(result.trajectories_processed, 150);
221        assert!(result.patterns_extracted > 0);
222    }
223}