ceylon_next/memory/advanced/
manager.rs

1//! Advanced Memory Manager - Main orchestrator
2
3use super::{
4    consolidation::{Consolidator, ConsolidationTask},
5    episodic::{EpisodicMemory, TimeRange},
6    semantic::SemanticMemory,
7    summarization::{Summarizer, SummaryStrategy},
8    working::WorkingMemory,
9    EnhancedMemoryEntry, ImportanceLevel, MemoryConfig, MemoryType,
10};
11use crate::memory::{Memory, MemoryEntry};
12use async_trait::async_trait;
13use std::sync::Arc;
14
15/// Advanced Memory Manager
16///
17/// Orchestrates all memory subsystems:
18/// - Working Memory: Recent context
19/// - Episodic Memory: Past conversations
20/// - Semantic Memory: Facts and knowledge
21/// - Summarization: Automatic compression
22/// - Consolidation: Background processing
23pub struct AdvancedMemoryManager {
24    /// Backend storage
25    backend: Arc<dyn Memory>,
26    /// Working memory
27    working: WorkingMemory,
28    /// Episodic memory
29    episodic: EpisodicMemory,
30    /// Semantic memory
31    semantic: SemanticMemory,
32    /// Summarizer
33    summarizer: Summarizer,
34    /// Consolidator
35    consolidator: Consolidator,
36    /// Configuration
37    config: MemoryConfig,
38}
39
40impl AdvancedMemoryManager {
41    /// Create a new advanced memory manager
42    pub async fn new(
43        backend: Arc<dyn Memory>,
44        config: MemoryConfig,
45    ) -> Result<Self, String> {
46        let working = WorkingMemory::new(config.clone());
47        let episodic = EpisodicMemory::new(backend.clone(), config.clone()).await?;
48        let semantic = SemanticMemory::new();
49        let summarizer = Summarizer::new();
50        let consolidator = Consolidator::new(config.clone());
51
52        Ok(Self {
53            backend,
54            working,
55            episodic,
56            semantic,
57            summarizer,
58            consolidator,
59            config,
60        })
61    }
62
63    // ============================================
64    // Core Memory Operations
65    // ============================================
66
67    /// Store a new conversation
68    pub async fn store_conversation(&self, entry: MemoryEntry) -> Result<String, String> {
69        // Create enhanced entry
70        let mut enhanced = EnhancedMemoryEntry::new(entry, MemoryType::Working);
71
72        // Summarize if needed
73        if self.config.auto_summarize && enhanced.entry.messages.len() >= self.config.min_summary_length {
74            let summary = self
75                .summarizer
76                .summarize(&enhanced, SummaryStrategy::Hybrid)
77                .await?;
78
79            enhanced.summary = Some(summary.summary);
80            enhanced.key_points = summary.key_points;
81            enhanced.importance = summary.importance;
82
83            // Extract entities to string names
84            enhanced.entities = summary.entities.iter().map(|e| e.name.clone()).collect();
85
86            // Add entities to semantic memory
87            for entity in summary.entities {
88                self.semantic
89                    .add_entity(entity.name, entity.entity_type, enhanced.entry.id.clone())
90                    .await;
91            }
92
93            // Add facts to semantic memory
94            for fact in summary.facts {
95                self.semantic
96                    .add_fact(fact.subject, fact.predicate, fact.object, enhanced.entry.id.clone())
97                    .await;
98            }
99        }
100
101        // Add to working memory
102        self.working.add(enhanced.clone()).await?;
103
104        // Store in episodic memory
105        let id = self.episodic.store(enhanced).await?;
106
107        Ok(id)
108    }
109
110    /// Retrieve a memory by ID
111    pub async fn get(&self, id: &str) -> Result<Option<EnhancedMemoryEntry>, String> {
112        self.episodic.get(id).await
113    }
114
115    /// Get recent working memory
116    pub async fn get_working_memory(&self, max_tokens: Option<usize>) -> Vec<EnhancedMemoryEntry> {
117        if let Some(limit) = max_tokens {
118            self.working.get_recent_within_limit(limit).await
119        } else {
120            self.working.get_all().await
121        }
122    }
123
124    /// Get episodic memories by time range
125    pub async fn get_episodic_by_time(
126        &self,
127        agent_id: &str,
128        range: TimeRange,
129    ) -> Result<Vec<EnhancedMemoryEntry>, String> {
130        self.episodic.get_by_time_range(agent_id, range).await
131    }
132
133    /// Get relevant memories by score
134    pub async fn get_relevant_memories(
135        &self,
136        agent_id: &str,
137        min_score: f32,
138        limit: usize,
139    ) -> Result<Vec<EnhancedMemoryEntry>, String> {
140        self.episodic.get_by_relevance(agent_id, min_score, limit).await
141    }
142
143    /// Search memories
144    pub async fn search(&self, agent_id: &str, query: &str) -> Result<Vec<EnhancedMemoryEntry>, String> {
145        self.episodic.search(agent_id, query).await
146    }
147
148    // ============================================
149    // Semantic Memory Operations
150    // ============================================
151
152    /// Get knowledge about an entity
153    pub async fn get_knowledge(&self, entity_name: &str) -> Option<super::semantic::KnowledgeSummary> {
154        self.semantic.get_knowledge_summary(entity_name).await
155    }
156
157    /// Get all facts about a subject
158    pub async fn get_facts_about(&self, subject: &str) -> Vec<super::semantic::Fact> {
159        self.semantic.get_facts_about(subject).await
160    }
161
162    /// Get semantic memory statistics
163    pub async fn get_semantic_stats(&self) -> super::semantic::SemanticMemoryStats {
164        self.semantic.get_statistics().await
165    }
166
167    // ============================================
168    // Context Generation
169    // ============================================
170
171    /// Create a comprehensive context message for the agent
172    pub async fn create_agent_context(
173        &self,
174        agent_id: &str,
175        max_tokens: Option<usize>,
176        include_semantic: bool,
177    ) -> Result<String, String> {
178        let mut context = String::new();
179
180        // 1. Working Memory (recent context)
181        let working_context = self.working.create_context(max_tokens).await;
182        if !working_context.is_empty() {
183            context.push_str(&working_context);
184            context.push_str("\n");
185        }
186
187        // 2. Relevant Episodic Memories (based on importance)
188        let important_memories = self
189            .episodic
190            .get_by_importance(agent_id, ImportanceLevel::High)
191            .await?;
192
193        if !important_memories.is_empty() {
194            context.push_str("IMPORTANT PAST CONVERSATIONS:\n");
195            for (i, memory) in important_memories.iter().take(3).enumerate() {
196                if let Some(summary) = &memory.summary {
197                    context.push_str(&format!("{}. {}\n", i + 1, summary));
198                }
199            }
200            context.push_str("\n");
201        }
202
203        // 3. Semantic Knowledge (if requested)
204        if include_semantic {
205            let stats = self.semantic.get_statistics().await;
206            if stats.total_entities > 0 || stats.total_facts > 0 {
207                context.push_str("LEARNED KNOWLEDGE:\n");
208                context.push_str(&format!("- {} entities tracked\n", stats.total_entities));
209                context.push_str(&format!("- {} facts stored\n", stats.total_facts));
210
211                // Include high-confidence facts
212                let high_conf_facts = self.semantic.get_high_confidence_facts(0.8).await;
213                if !high_conf_facts.is_empty() {
214                    context.push_str("Key facts:\n");
215                    for fact in high_conf_facts.iter().take(5) {
216                        context.push_str(&format!(
217                            "  - {} {} {}\n",
218                            fact.subject, fact.predicate, fact.object
219                        ));
220                    }
221                }
222                context.push_str("\n");
223            }
224        }
225
226        Ok(context)
227    }
228
229    // ============================================
230    // Maintenance Operations
231    // ============================================
232
233    /// Run consolidation tasks
234    pub async fn consolidate(&self, task: ConsolidationTask) -> Result<super::consolidation::ConsolidationResult, String> {
235        // Get all memories from episodic
236        let memories = self.episodic.get_by_time_range("", TimeRange::All).await?;
237
238        // Convert to HashMap for consolidator
239        let mut mem_map = std::collections::HashMap::new();
240        for memory in memories {
241            mem_map.insert(memory.entry.id.clone(), memory);
242        }
243
244        self.consolidator.set_memories(mem_map).await;
245
246        // Run consolidation
247        let result = self.consolidator.consolidate(task).await?;
248
249        // Update episodic memory with consolidated memories
250        // (In a real implementation, we'd persist these changes)
251
252        Ok(result)
253    }
254
255    /// Start background consolidation
256    pub async fn start_background_processing(&self) {
257        if self.config.auto_consolidate {
258            self.consolidator.start_background_consolidation().await;
259        }
260    }
261
262    /// Stop background consolidation
263    pub async fn stop_background_processing(&self) {
264        self.consolidator.stop_background_consolidation().await;
265    }
266
267    /// Get statistics about memory usage
268    pub async fn get_statistics(&self) -> MemoryStatistics {
269        let semantic_stats = self.semantic.get_statistics().await;
270
271        MemoryStatistics {
272            working_memory_count: self.working.memory_count().await,
273            working_memory_tokens: self.working.token_count().await,
274            semantic_entities: semantic_stats.total_entities,
275            semantic_facts: semantic_stats.total_facts,
276            semantic_relationships: semantic_stats.total_relationships,
277        }
278    }
279
280    /// Clear all memory for an agent
281    pub async fn clear_agent_memory(&self, agent_id: &str) -> Result<(), String> {
282        self.working.clear().await;
283        self.episodic.clear(agent_id).await?;
284        Ok(())
285    }
286}
287
288/// Implement the base Memory trait for compatibility
289#[async_trait]
290impl Memory for AdvancedMemoryManager {
291    async fn store(&self, entry: MemoryEntry) -> Result<String, String> {
292        self.store_conversation(entry).await
293    }
294
295    async fn get(&self, id: &str) -> Result<Option<MemoryEntry>, String> {
296        if let Some(enhanced) = self.get(id).await? {
297            Ok(Some(enhanced.entry))
298        } else {
299            Ok(None)
300        }
301    }
302
303    async fn get_agent_history(&self, agent_id: &str) -> Result<Vec<MemoryEntry>, String> {
304        let enhanced_entries = self
305            .episodic
306            .get_by_time_range(agent_id, TimeRange::All)
307            .await?;
308
309        Ok(enhanced_entries.into_iter().map(|e| e.entry).collect())
310    }
311
312    async fn get_recent(&self, agent_id: &str, limit: usize) -> Result<Vec<MemoryEntry>, String> {
313        let enhanced_entries = self
314            .episodic
315            .get_by_relevance(agent_id, 0.0, limit)
316            .await?;
317
318        Ok(enhanced_entries.into_iter().map(|e| e.entry).collect())
319    }
320
321    async fn search(&self, agent_id: &str, query: &str) -> Result<Vec<MemoryEntry>, String> {
322        let enhanced_entries = self.search(agent_id, query).await?;
323        Ok(enhanced_entries.into_iter().map(|e| e.entry).collect())
324    }
325
326    async fn clear_agent_memory(&self, agent_id: &str) -> Result<(), String> {
327        self.clear_agent_memory(agent_id).await
328    }
329}
330
331/// Memory usage statistics
332#[derive(Debug, Clone)]
333pub struct MemoryStatistics {
334    pub working_memory_count: usize,
335    pub working_memory_tokens: usize,
336    pub semantic_entities: usize,
337    pub semantic_facts: usize,
338    pub semantic_relationships: usize,
339}
340
341#[cfg(test)]
342mod tests {
343    use super::*;
344    use crate::memory::InMemoryStore;
345    use crate::llm::types::Message;
346
347    #[tokio::test]
348    async fn test_advanced_memory_manager_basic() {
349        let backend = Arc::new(InMemoryStore::new());
350        let config = MemoryConfig {
351            auto_consolidate: false, // Disable to prevent background task from blocking test
352            ..Default::default()
353        };
354        let manager = AdvancedMemoryManager::new(backend, config).await.unwrap();
355
356        // Store a conversation
357        let messages = vec![
358            Message {
359                role: "user".to_string(),
360                content: "Hello, tell me about Rust.".to_string(),
361            },
362            Message {
363                role: "assistant".to_string(),
364                content: "Rust is a systems programming language.".to_string(),
365            },
366        ];
367
368        let entry = MemoryEntry::new("agent-1".to_string(), "task-1".to_string(), messages);
369        let id = manager.store_conversation(entry).await.unwrap();
370
371        // Retrieve it
372        let retrieved = manager.get(&id).await.unwrap();
373        assert!(retrieved.is_some());
374    }
375
376    #[tokio::test]
377    async fn test_context_generation() {
378        let backend = Arc::new(InMemoryStore::new());
379        let config = MemoryConfig {
380            auto_consolidate: false, // Disable to prevent background task from blocking test
381            ..Default::default()
382        };
383        let manager = AdvancedMemoryManager::new(backend, config).await.unwrap();
384
385        // Store some conversations
386        for i in 0..3 {
387            let messages = vec![Message {
388                role: "user".to_string(),
389                content: format!("Message {}", i),
390            }];
391
392            let entry = MemoryEntry::new(
393                "agent-1".to_string(),
394                format!("task-{}", i),
395                messages,
396            );
397            manager.store_conversation(entry).await.unwrap();
398        }
399
400        // Generate context
401        let context = manager
402            .create_agent_context("agent-1", None, true)
403            .await
404            .unwrap();
405
406        assert!(!context.is_empty());
407    }
408}