mimirs_sleeptime/
reflection.rs1use mimirs_core::{Goal, MemoryAgent, MimirError, RecallQuery};
7use mimirs_oracle::{Forseti, Ginnunga};
8use std::sync::Weak;
9use std::time::Duration;
10
11#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
13pub struct SleepConfig {
14 pub min_idle_duration: Duration,
16 pub max_memories_per_session: usize,
18 pub auto_consolidate: bool,
20}
21
22impl Default for SleepConfig {
23 fn default() -> Self {
24 Self {
25 min_idle_duration: Duration::from_secs(300), max_memories_per_session: 100,
27 auto_consolidate: true,
28 }
29 }
30}
31
32#[derive(Debug, Clone, Default)]
34pub struct GoalSystem {
35 pub active_goals: Vec<Goal>,
37}
38
39impl GoalSystem {
40 pub fn select_focus(&self) -> Option<&Goal> {
42 self.active_goals.iter().max_by(|a, b| {
43 let sa = a.priority * a.persistence;
44 let sb = b.priority * b.persistence;
45 debug_assert!(sa.is_finite() && sb.is_finite());
46 sa.partial_cmp(&sb).unwrap_or(std::cmp::Ordering::Equal)
47 })
48 }
49}
50
51pub struct Nott {
53 well_agent: Weak<dyn MemoryAgent>,
55 pub goals: GoalSystem,
57 pub ginnunga: Ginnunga,
59 pub forseti: Forseti,
61 _config: SleepConfig,
63 pub bus: Option<mimirs_ha::EventBus>,
65 pub agent_id: Option<mimirs_core::AgentId>,
67 pub distillation_config: crate::distillation::DistillationConfig,
69}
70
71impl Nott {
72 pub fn new(agent: Weak<dyn MemoryAgent>, config: SleepConfig) -> Self {
74 Self {
75 well_agent: agent,
76 goals: GoalSystem::default(),
77 ginnunga: Ginnunga::default(),
78 forseti: Forseti::default(),
79 _config: config,
80 bus: None,
81 agent_id: None,
82 distillation_config: crate::distillation::DistillationConfig::default(),
83 }
84 }
85
86 pub fn with_bus(mut self, bus: mimirs_ha::EventBus) -> Self {
88 self.bus = Some(bus);
89 self
90 }
91
92 pub fn with_agent_id(mut self, id: mimirs_core::AgentId) -> Self {
94 self.agent_id = Some(id);
95 self
96 }
97
98 pub async fn drauma(&self) -> Result<(), MimirError> {
101 let agent = self
102 .well_agent
103 .upgrade()
104 .ok_or_else(|| MimirError::Uninitialized("Agent dropped".to_string()))?;
105
106 let agent_id = self.agent_id.unwrap_or_default();
107
108 tracing::info!("Nott is starting the reflection loop (Drauma)");
109
110 let query = RecallQuery::new("")
112 .with_limit(self._config.max_memories_per_session)
113 .with_memory_class(mimirs_core::MemoryClass::Episodic);
114
115 let results = agent.recall(query).await?;
116 if results.is_empty() {
117 tracing::debug!("No episodic memories to reflect on");
118 return Ok(());
119 }
120
121 for (i, res_a) in results.iter().enumerate() {
123 for res_b in results.iter().skip(i + 1) {
124 let contradiction_score = self.ginnunga.greina(&res_a.memory, &res_b.memory);
125 if contradiction_score > self.ginnunga.contradiction_threshold {
126 tracing::warn!(
127 "Contradiction detected between {} and {}: score {}",
128 res_a.id,
129 res_b.id,
130 contradiction_score
131 );
132
133 if let Some(ref bus) = self.bus {
135 let _ = bus.broadcast(mimirs_ha::MemorySignal::ConflictDetected {
136 agent_id,
137 memory_a: res_a.id,
138 memory_b: res_b.id,
139 score: contradiction_score,
140 });
141 }
142 }
143 }
144
145 let mut current_memory = agent.get_memory(res_a.id).await?;
147 let new_stage = if let Some(ref rho) = current_memory.rho {
148 self.forseti.domur(rho)
149 } else {
150 current_memory.verifiability
151 };
152
153 if new_stage > current_memory.verifiability {
154 tracing::info!(
155 "Promoting memory {} from {:?} to {:?}",
156 res_a.id,
157 current_memory.verifiability,
158 new_stage
159 );
160
161 current_memory.verifiability = new_stage;
163 agent.update_memory(current_memory).await?;
164
165 if let Some(ref bus) = self.bus {
167 let _ = bus.broadcast(mimirs_ha::MemorySignal::VerifiabilityChanged {
168 agent_id,
169 memory_id: res_a.id,
170 new_stage,
171 });
172 }
173 }
174 }
175
176 tracing::info!("Nott is distilling episodic manifold into Drauma blocks");
178 let mut full_memories = Vec::new();
179 for r in results.iter() {
180 if let Ok(m) = agent.get_memory(r.id).await {
181 full_memories.push(m);
182 }
183 }
184
185 match crate::distillation::Drauma::distill(&full_memories, &self.distillation_config) {
186 Ok(drauma) => {
187 tracing::info!(
188 "Successfully distilled {} memories into block {}",
189 full_memories.len(),
190 drauma.id
191 );
192
193 agent.attach_block(drauma.block).await?;
195
196 if let Some(ref bus) = self.bus {
198 let _ = bus.broadcast(mimirs_ha::MemorySignal::ConsolidationCompleted {
199 agent_id,
200 memories_processed: full_memories.len(),
201 });
202 }
203 }
204 Err(e) => tracing::error!("Distillation failed: {}", e),
205 }
206
207 if let Some(focus) = self.goals.select_focus() {
209 tracing::info!("Current cognitive focus: {}", focus.id);
210 }
211
212 Ok(())
213 }
214}