ruvector_sparse_inference/precision/
policy.rs1use super::lanes::{PrecisionLane, LaneConfig};
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Default, Serialize, Deserialize)]
10pub struct GraduationMetrics {
11 pub novelty: f32,
13
14 pub drift: f32,
16
17 pub drift_steps: usize,
19
20 pub confidence: f32,
22
23 pub stability: f32,
25
26 pub stable_steps: usize,
28
29 pub velocity: f32,
31
32 pub active_set_size: usize,
34
35 pub uncertainty: f32,
37
38 pub cost_usage: f32,
40
41 pub action_needed: bool,
43}
44
45impl GraduationMetrics {
46 pub fn new() -> Self {
48 Self::default()
49 }
50
51 pub fn update(&mut self, observation: &ObservationMetrics, ema_alpha: f32) {
53 self.novelty = ema_alpha * observation.novelty + (1.0 - ema_alpha) * self.novelty;
55 self.drift = ema_alpha * observation.drift + (1.0 - ema_alpha) * self.drift;
56 self.confidence = ema_alpha * observation.confidence + (1.0 - ema_alpha) * self.confidence;
57 self.stability = ema_alpha * observation.stability + (1.0 - ema_alpha) * self.stability;
58 self.velocity = ema_alpha * observation.velocity + (1.0 - ema_alpha) * self.velocity;
59 self.uncertainty = ema_alpha * observation.uncertainty + (1.0 - ema_alpha) * self.uncertainty;
60
61 self.active_set_size = observation.active_set_size;
62 self.action_needed = observation.action_needed;
63
64 if observation.drift > 0.1 {
66 self.drift_steps += 1;
67 } else {
68 self.drift_steps = 0;
69 }
70
71 if observation.stability > 0.8 {
73 self.stable_steps += 1;
74 } else {
75 self.stable_steps = 0;
76 }
77 }
78}
79
80#[derive(Debug, Clone, Default)]
82pub struct ObservationMetrics {
83 pub novelty: f32,
84 pub drift: f32,
85 pub confidence: f32,
86 pub stability: f32,
87 pub velocity: f32,
88 pub uncertainty: f32,
89 pub active_set_size: usize,
90 pub action_needed: bool,
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub enum GraduationDecision {
96 Stay,
98 Escalate(PrecisionLane),
100 Demote(PrecisionLane),
102}
103
104#[derive(Debug, Clone)]
106pub struct GraduationPolicy {
107 pub current_lane: PrecisionLane,
109 pub config: LaneConfig,
111 pub metrics: GraduationMetrics,
113 pub ema_alpha: f32,
115}
116
117impl GraduationPolicy {
118 pub fn new(initial_lane: PrecisionLane, config: LaneConfig) -> Self {
120 Self {
121 current_lane: initial_lane,
122 config,
123 metrics: GraduationMetrics::new(),
124 ema_alpha: 0.3,
125 }
126 }
127
128 pub fn evaluate(&mut self, observation: &ObservationMetrics) -> GraduationDecision {
130 self.metrics.update(observation, self.ema_alpha);
132
133 if self.should_escalate() {
135 if let Some(next_lane) = self.next_higher_lane() {
136 return GraduationDecision::Escalate(next_lane);
137 }
138 }
139
140 if self.should_demote() {
142 if let Some(prev_lane) = self.next_lower_lane() {
143 return GraduationDecision::Demote(prev_lane);
144 }
145 }
146
147 GraduationDecision::Stay
148 }
149
150 pub fn apply_decision(&mut self, decision: GraduationDecision) {
152 match decision {
153 GraduationDecision::Stay => {}
154 GraduationDecision::Escalate(lane) | GraduationDecision::Demote(lane) => {
155 self.current_lane = lane;
156 self.metrics.stable_steps = 0;
158 self.metrics.drift_steps = 0;
159 }
160 }
161 }
162
163 fn should_escalate(&self) -> bool {
165 let novelty_trigger = self.metrics.novelty > self.config.novelty_threshold;
168
169 let drift_trigger = self.metrics.drift_steps >= self.config.drift_persistence_threshold;
171
172 let quality_pass = self.metrics.confidence >= self.config.confidence_threshold
174 && self.metrics.stability >= 0.5;
175
176 let budget_allows = self.metrics.cost_usage < self.config.escalation_budget;
178
179 (novelty_trigger || drift_trigger) && quality_pass && budget_allows
181 }
182
183 fn should_demote(&self) -> bool {
185 let stability_returned = self.metrics.stable_steps >= self.config.min_stability_steps;
188
189 let velocity_stalled = self.metrics.velocity.abs() < 0.01;
191
192 let active_set_shrunk = self.metrics.active_set_size < 10;
194
195 let uncertain_idle = self.metrics.uncertainty > 0.7 && !self.metrics.action_needed;
197
198 stability_returned && (velocity_stalled || active_set_shrunk || uncertain_idle)
200 }
201
202 fn next_higher_lane(&self) -> Option<PrecisionLane> {
204 match self.current_lane {
205 PrecisionLane::Bit3 => Some(PrecisionLane::Bit5),
206 PrecisionLane::Bit5 => Some(PrecisionLane::Bit7),
207 PrecisionLane::Bit7 => Some(PrecisionLane::Float32),
208 PrecisionLane::Float32 => None,
209 }
210 }
211
212 fn next_lower_lane(&self) -> Option<PrecisionLane> {
214 match self.current_lane {
215 PrecisionLane::Bit3 => None,
216 PrecisionLane::Bit5 => Some(PrecisionLane::Bit3),
217 PrecisionLane::Bit7 => Some(PrecisionLane::Bit5),
218 PrecisionLane::Float32 => Some(PrecisionLane::Bit7),
219 }
220 }
221}
222
223pub struct LanedEventProcessor {
225 policy: GraduationPolicy,
227 event_count: usize,
229}
230
231impl LanedEventProcessor {
232 pub fn new(config: LaneConfig) -> Self {
234 Self {
235 policy: GraduationPolicy::new(config.default_lane, config),
236 event_count: 0,
237 }
238 }
239
240 pub fn process_event(&mut self, event: &Event) -> ProcessResult {
242 self.event_count += 1;
243
244 let reflex_result = self.reflex_3bit(event);
246 if !reflex_result.boundary_crossed {
247 return ProcessResult::Reflexed(reflex_result);
248 }
249
250 let embed_result = self.embed_5bit(event);
252
253 let observation = self.compute_observation(&reflex_result, &embed_result);
255 let decision = self.policy.evaluate(&observation);
256
257 if matches!(decision, GraduationDecision::Escalate(PrecisionLane::Bit7))
258 || self.policy.current_lane == PrecisionLane::Bit7
259 {
260 let reason_result = self.reason_7bit(event, &embed_result);
262 self.policy.apply_decision(decision);
263 return ProcessResult::Reasoned(reason_result);
264 }
265
266 self.policy.apply_decision(decision);
267 ProcessResult::Embedded(embed_result)
268 }
269
270 fn reflex_3bit(&self, _event: &Event) -> ReflexResult {
271 ReflexResult {
273 boundary_crossed: true, health_ok: true,
275 anomaly_detected: false,
276 }
277 }
278
279 fn embed_5bit(&self, _event: &Event) -> EmbedResult {
280 EmbedResult {
282 embedding_delta: vec![0.0; 64],
283 drift_detected: false,
284 }
285 }
286
287 fn reason_7bit(&self, _event: &Event, _embed: &EmbedResult) -> ReasonResult {
288 ReasonResult {
290 should_write_memory: false,
291 summary: String::new(),
292 actions: Vec::new(),
293 }
294 }
295
296 fn compute_observation(&self, _reflex: &ReflexResult, _embed: &EmbedResult) -> ObservationMetrics {
297 ObservationMetrics::default()
298 }
299
300 pub fn current_lane(&self) -> PrecisionLane {
302 self.policy.current_lane
303 }
304}
305
306#[derive(Debug, Clone)]
308pub struct Event {
309 pub data: Vec<f32>,
310 pub timestamp: u64,
311}
312
313#[derive(Debug, Clone)]
315pub struct ReflexResult {
316 pub boundary_crossed: bool,
317 pub health_ok: bool,
318 pub anomaly_detected: bool,
319}
320
321#[derive(Debug, Clone)]
323pub struct EmbedResult {
324 pub embedding_delta: Vec<f32>,
325 pub drift_detected: bool,
326}
327
328#[derive(Debug, Clone)]
330pub struct ReasonResult {
331 pub should_write_memory: bool,
332 pub summary: String,
333 pub actions: Vec<String>,
334}
335
336#[derive(Debug)]
338pub enum ProcessResult {
339 Reflexed(ReflexResult),
340 Embedded(EmbedResult),
341 Reasoned(ReasonResult),
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347
348 #[test]
349 fn test_graduation_policy_creation() {
350 let config = LaneConfig::default();
351 let policy = GraduationPolicy::new(PrecisionLane::Bit5, config);
352
353 assert_eq!(policy.current_lane, PrecisionLane::Bit5);
354 }
355
356 #[test]
357 fn test_escalation_on_novelty() {
358 let config = LaneConfig {
359 novelty_threshold: 0.3,
360 confidence_threshold: 0.5,
361 ..Default::default()
362 };
363 let mut policy = GraduationPolicy::new(PrecisionLane::Bit5, config);
364 policy.ema_alpha = 1.0;
366
367 let observation = ObservationMetrics {
369 novelty: 0.9,
370 confidence: 0.9,
371 stability: 0.6,
372 ..Default::default()
373 };
374
375 let decision = policy.evaluate(&observation);
376 assert!(matches!(decision, GraduationDecision::Escalate(PrecisionLane::Bit7)));
377 }
378
379 #[test]
380 fn test_demotion_on_stability() {
381 let mut config = LaneConfig::default();
382 config.min_stability_steps = 2;
383
384 let mut policy = GraduationPolicy::new(PrecisionLane::Bit7, config);
385
386 for _ in 0..5 {
388 let observation = ObservationMetrics {
389 stability: 0.9,
390 velocity: 0.001,
391 active_set_size: 5,
392 ..Default::default()
393 };
394 policy.evaluate(&observation);
395 }
396
397 let observation = ObservationMetrics {
398 stability: 0.9,
399 velocity: 0.001,
400 active_set_size: 5,
401 ..Default::default()
402 };
403
404 let decision = policy.evaluate(&observation);
405 assert!(matches!(decision, GraduationDecision::Demote(PrecisionLane::Bit5)));
406 }
407}