ruvector_sona/loops/
coordinator.rs1use 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
13pub struct LoopCoordinator {
15 config: SonaConfig,
17 instant: InstantLoop,
19 background: BackgroundLoop,
21 reasoning_bank: Arc<RwLock<ReasoningBank>>,
23 ewc: Arc<RwLock<EwcPlusPlus>>,
24 base_lora: Arc<RwLock<BaseLoRA>>,
25 instant_enabled: bool,
27 background_enabled: bool,
28}
29
30impl LoopCoordinator {
31 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 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, )));
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 pub fn on_inference(&self, trajectory: QueryTrajectory) {
82 if self.instant_enabled {
83 self.instant.on_trajectory(trajectory);
84 }
85 }
86
87 pub fn next_trajectory_id(&self) -> u64 {
89 self.instant.next_id()
90 }
91
92 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 pub fn force_background(&self) -> BackgroundResult {
110 let trajectories = self.instant.drain_trajectories();
111 self.background.run_cycle(trajectories)
112 }
113
114 pub fn flush_instant(&self) {
116 self.instant.flush();
117 }
118
119 pub fn micro_lora(&self) -> &Arc<RwLock<MicroLoRA>> {
121 self.instant.micro_lora()
122 }
123
124 pub fn base_lora(&self) -> &Arc<RwLock<BaseLoRA>> {
126 &self.base_lora
127 }
128
129 pub fn reasoning_bank(&self) -> &Arc<RwLock<ReasoningBank>> {
131 &self.reasoning_bank
132 }
133
134 pub fn ewc(&self) -> &Arc<RwLock<EwcPlusPlus>> {
136 &self.ewc
137 }
138
139 pub fn set_instant_enabled(&mut self, enabled: bool) {
141 self.instant_enabled = enabled;
142 }
143
144 pub fn set_background_enabled(&mut self, enabled: bool) {
146 self.background_enabled = enabled;
147 }
148
149 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#[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}