exo_temporal/
lib.rs

1//! # exo-temporal: Temporal Memory Coordinator
2//!
3//! Causal memory coordination for the EXO-AI cognitive substrate.
4//!
5//! This crate implements temporal memory with:
6//! - Short-term volatile buffer
7//! - Long-term consolidated store
8//! - Causal graph tracking antecedent relationships
9//! - Memory consolidation with salience-based filtering
10//! - Predictive anticipation and pre-fetching
11//!
12//! ## Architecture
13//!
14//! ```text
15//! ┌─────────────────────────────────────────────────────────┐
16//! │                  TemporalMemory                         │
17//! ├─────────────────────────────────────────────────────────┤
18//! │ ┌─────────────┐  ┌─────────────┐  ┌─────────────┐      │
19//! │ │ Short-Term  │  │ Long-Term   │  │   Causal    │      │
20//! │ │   Buffer    │→ │    Store    │  │    Graph    │      │
21//! │ └─────────────┘  └─────────────┘  └─────────────┘      │
22//! │        ↓                ↑                 ↑             │
23//! │ ┌─────────────────────────────────────────────┐         │
24//! │ │          Consolidation Engine               │         │
25//! │ │  (Salience computation & filtering)         │         │
26//! │ └─────────────────────────────────────────────┘         │
27//! │        ↓                                                │
28//! │ ┌─────────────────────────────────────────────┐         │
29//! │ │       Anticipation & Prefetch               │         │
30//! │ └─────────────────────────────────────────────┘         │
31//! └─────────────────────────────────────────────────────────┘
32//! ```
33//!
34//! ## Example
35//!
36//! ```rust,ignore
37//! use exo_temporal::{TemporalMemory, TemporalConfig};
38//! use exo_core::Pattern;
39//!
40//! // Create temporal memory
41//! let memory = TemporalMemory::new(TemporalConfig::default());
42//!
43//! // Store pattern with causal context
44//! let pattern = Pattern::new(vec![1.0, 2.0, 3.0], metadata);
45//! let id = memory.store(pattern, &[]).unwrap();
46//!
47//! // Causal query
48//! let results = memory.causal_query(
49//!     &query,
50//!     reference_time,
51//!     CausalConeType::Past,
52//! );
53//!
54//! // Trigger consolidation
55//! memory.consolidate();
56//! ```
57
58pub mod anticipation;
59pub mod causal;
60pub mod consolidation;
61pub mod long_term;
62pub mod short_term;
63pub mod types;
64
65pub use anticipation::{
66    anticipate, AnticipationHint, PrefetchCache, SequentialPatternTracker, TemporalPhase,
67};
68pub use causal::{CausalConeType, CausalGraph, CausalGraphStats};
69pub use consolidation::{compute_salience, compute_salience_batch, consolidate, ConsolidationConfig, ConsolidationResult, ConsolidationStats};
70pub use long_term::{LongTermConfig, LongTermStats, LongTermStore};
71pub use short_term::{ShortTermBuffer, ShortTermConfig, ShortTermStats};
72pub use types::*;
73
74use thiserror::Error;
75
76/// Error type for temporal memory operations
77#[derive(Debug, Error)]
78pub enum TemporalError {
79    /// Pattern not found
80    #[error("Pattern not found: {0}")]
81    PatternNotFound(PatternId),
82
83    /// Invalid query
84    #[error("Invalid query: {0}")]
85    InvalidQuery(String),
86
87    /// Storage error
88    #[error("Storage error: {0}")]
89    StorageError(String),
90}
91
92/// Result type for temporal operations
93pub type Result<T> = std::result::Result<T, TemporalError>;
94
95/// Configuration for temporal memory
96#[derive(Debug, Clone)]
97pub struct TemporalConfig {
98    /// Short-term buffer configuration
99    pub short_term: ShortTermConfig,
100    /// Long-term store configuration
101    pub long_term: LongTermConfig,
102    /// Consolidation configuration
103    pub consolidation: ConsolidationConfig,
104    /// Prefetch cache capacity
105    pub prefetch_capacity: usize,
106    /// Auto-consolidation enabled
107    pub auto_consolidate: bool,
108}
109
110impl Default for TemporalConfig {
111    fn default() -> Self {
112        Self {
113            short_term: ShortTermConfig::default(),
114            long_term: LongTermConfig::default(),
115            consolidation: ConsolidationConfig::default(),
116            prefetch_capacity: 1000,
117            auto_consolidate: true,
118        }
119    }
120}
121
122/// Temporal memory coordinator
123pub struct TemporalMemory {
124    /// Short-term volatile memory
125    short_term: ShortTermBuffer,
126    /// Long-term consolidated memory
127    long_term: LongTermStore,
128    /// Causal graph tracking antecedent relationships
129    causal_graph: CausalGraph,
130    /// Prefetch cache for anticipated queries
131    prefetch_cache: PrefetchCache,
132    /// Sequential pattern tracker
133    sequential_tracker: SequentialPatternTracker,
134    /// Configuration
135    config: TemporalConfig,
136}
137
138impl TemporalMemory {
139    /// Create new temporal memory
140    pub fn new(config: TemporalConfig) -> Self {
141        Self {
142            short_term: ShortTermBuffer::new(config.short_term.clone()),
143            long_term: LongTermStore::new(config.long_term.clone()),
144            causal_graph: CausalGraph::new(),
145            prefetch_cache: PrefetchCache::new(config.prefetch_capacity),
146            sequential_tracker: SequentialPatternTracker::new(),
147            config,
148        }
149    }
150
151    /// Store pattern with causal context
152    pub fn store(&self, pattern: Pattern, antecedents: &[PatternId]) -> Result<PatternId> {
153        let id = pattern.id;
154        let timestamp = pattern.timestamp;
155
156        // Wrap in TemporalPattern
157        let temporal_pattern = TemporalPattern::new(pattern);
158
159        // Add to short-term buffer
160        self.short_term.insert(temporal_pattern);
161
162        // Record causal relationships
163        self.causal_graph.add_pattern(id, timestamp);
164        for &antecedent in antecedents {
165            self.causal_graph.add_edge(antecedent, id);
166        }
167
168        // Auto-consolidate if needed
169        if self.config.auto_consolidate && self.short_term.should_consolidate() {
170            self.consolidate();
171        }
172
173        Ok(id)
174    }
175
176    /// Retrieve pattern by ID
177    pub fn get(&self, id: &PatternId) -> Option<Pattern> {
178        // Check short-term first
179        if let Some(temporal_pattern) = self.short_term.get(id) {
180            return Some(temporal_pattern.pattern);
181        }
182
183        // Check long-term
184        self.long_term.get(id).map(|tp| tp.pattern)
185    }
186
187    /// Update pattern access tracking
188    pub fn mark_accessed(&self, id: &PatternId) {
189        // Update in short-term if present
190        self.short_term.get_mut(id, |p| p.mark_accessed());
191
192        // Update in long-term if present
193        if let Some(mut temporal_pattern) = self.long_term.get(id) {
194            temporal_pattern.mark_accessed();
195            self.long_term.update(temporal_pattern);
196        }
197    }
198
199    /// Causal cone query: retrieve within light-cone constraints
200    pub fn causal_query(
201        &self,
202        query: &Query,
203        reference_time: SubstrateTime,
204        cone_type: CausalConeType,
205    ) -> Vec<CausalResult> {
206        // Determine time range based on cone type
207        let time_range = match cone_type {
208            CausalConeType::Past => TimeRange::past(reference_time),
209            CausalConeType::Future => TimeRange::future(reference_time),
210            CausalConeType::LightCone { .. } => {
211                // Simplified: use full range for now
212                // In full implementation, would compute relativistic constraint
213                TimeRange::new(SubstrateTime::MIN, SubstrateTime::MAX)
214            }
215        };
216
217        // Search long-term with temporal filter
218        let search_results = self.long_term.search_with_time_range(query, time_range);
219
220        // Compute causal and temporal distances
221        let mut results = Vec::new();
222
223        for search_result in search_results {
224            let temporal_pattern = search_result.pattern;
225            let similarity = search_result.score;
226
227            // Causal distance
228            let causal_distance = if let Some(origin) = query.origin {
229                self.causal_graph.distance(origin, temporal_pattern.id())
230            } else {
231                None
232            };
233
234            // Temporal distance (in nanoseconds)
235            let time_diff = (reference_time - temporal_pattern.pattern.timestamp).abs();
236            let temporal_distance_ns = time_diff.0;
237
238            // Combined score (weighted combination)
239            const ALPHA: f32 = 0.5; // Similarity weight
240            const BETA: f32 = 0.25; // Temporal weight
241            const GAMMA: f32 = 0.25; // Causal weight
242
243            let temporal_score = 1.0 / (1.0 + (temporal_distance_ns / 1_000_000_000) as f32); // Convert to seconds
244            let causal_score = if let Some(dist) = causal_distance {
245                1.0 / (1.0 + dist as f32)
246            } else {
247                0.0
248            };
249
250            let combined_score = ALPHA * similarity + BETA * temporal_score + GAMMA * causal_score;
251
252            results.push(CausalResult {
253                pattern: temporal_pattern,
254                similarity,
255                causal_distance,
256                temporal_distance_ns,
257                combined_score,
258            });
259        }
260
261        // Sort by combined score
262        results.sort_by(|a, b| b.combined_score.partial_cmp(&a.combined_score).unwrap());
263
264        results
265    }
266
267    /// Anticipatory pre-fetch for predictive retrieval
268    pub fn anticipate(&self, hints: &[AnticipationHint]) {
269        anticipate(
270            hints,
271            &self.long_term,
272            &self.causal_graph,
273            &self.prefetch_cache,
274            &self.sequential_tracker,
275        );
276    }
277
278    /// Check prefetch cache for query
279    pub fn check_cache(&self, query: &Query) -> Option<Vec<SearchResult>> {
280        self.prefetch_cache.get(query.hash())
281    }
282
283    /// Memory consolidation: short-term -> long-term
284    pub fn consolidate(&self) -> ConsolidationResult {
285        consolidate(
286            &self.short_term,
287            &self.long_term,
288            &self.causal_graph,
289            &self.config.consolidation,
290        )
291    }
292
293    /// Strategic forgetting in long-term memory
294    pub fn forget(&self) {
295        self.long_term.decay_low_salience(self.config.long_term.decay_rate);
296    }
297
298    /// Get causal graph reference
299    pub fn causal_graph(&self) -> &CausalGraph {
300        &self.causal_graph
301    }
302
303    /// Get short-term buffer reference
304    pub fn short_term(&self) -> &ShortTermBuffer {
305        &self.short_term
306    }
307
308    /// Get long-term store reference
309    pub fn long_term(&self) -> &LongTermStore {
310        &self.long_term
311    }
312
313    /// Get statistics
314    pub fn stats(&self) -> TemporalStats {
315        TemporalStats {
316            short_term: self.short_term.stats(),
317            long_term: self.long_term.stats(),
318            causal_graph: self.causal_graph.stats(),
319            prefetch_cache_size: self.prefetch_cache.len(),
320        }
321    }
322}
323
324impl Default for TemporalMemory {
325    fn default() -> Self {
326        Self::new(TemporalConfig::default())
327    }
328}
329
330/// Temporal memory statistics
331#[derive(Debug, Clone)]
332pub struct TemporalStats {
333    /// Short-term buffer stats
334    pub short_term: ShortTermStats,
335    /// Long-term store stats
336    pub long_term: LongTermStats,
337    /// Causal graph stats
338    pub causal_graph: CausalGraphStats,
339    /// Prefetch cache size
340    pub prefetch_cache_size: usize,
341}
342
343#[cfg(test)]
344mod tests {
345    use super::*;
346
347    #[test]
348    fn test_temporal_memory() {
349        let memory = TemporalMemory::default();
350
351        let pattern = Pattern {
352            id: PatternId::new(),
353            embedding: vec![1.0, 2.0, 3.0],
354            metadata: Metadata::default(),
355            timestamp: SubstrateTime::now(),
356            antecedents: Vec::new(),
357            salience: 1.0,
358        };
359        let id = pattern.id;
360
361        memory.store(pattern, &[]).unwrap();
362
363        assert!(memory.get(&id).is_some());
364    }
365
366    #[test]
367    fn test_causal_query() {
368        // Use low salience threshold to ensure all patterns are consolidated
369        let config = TemporalConfig {
370            consolidation: ConsolidationConfig {
371                salience_threshold: 0.0, // Accept all patterns
372                ..Default::default()
373            },
374            ..Default::default()
375        };
376        let memory = TemporalMemory::new(config);
377
378        // Create causal chain: p1 -> p2 -> p3
379        let t1 = SubstrateTime::now();
380        let p1 = Pattern {
381            id: PatternId::new(),
382            embedding: vec![1.0, 0.0, 0.0],
383            metadata: Metadata::default(),
384            timestamp: t1,
385            antecedents: Vec::new(),
386            salience: 1.0,
387        };
388        let id1 = p1.id;
389        memory.store(p1, &[]).unwrap();
390
391        let p2 = Pattern {
392            id: PatternId::new(),
393            embedding: vec![0.9, 0.1, 0.0],
394            metadata: Metadata::default(),
395            timestamp: SubstrateTime::now(),
396            antecedents: Vec::new(),
397            salience: 1.0,
398        };
399        let id2 = p2.id;
400        memory.store(p2, &[id1]).unwrap();
401
402        let p3 = Pattern {
403            id: PatternId::new(),
404            embedding: vec![0.8, 0.2, 0.0],
405            metadata: Metadata::default(),
406            timestamp: SubstrateTime::now(),
407            antecedents: Vec::new(),
408            salience: 1.0,
409        };
410        memory.store(p3, &[id2]).unwrap();
411
412        // Consolidate to long-term
413        let result = memory.consolidate();
414        assert!(result.num_consolidated >= 3, "Should consolidate all patterns");
415
416        // Query with causal context - use p1's timestamp as reference for future cone
417        let query = Query::from_embedding(vec![1.0, 0.0, 0.0]).with_origin(id1);
418        let results = memory.causal_query(
419            &query,
420            t1, // Use p1's timestamp as reference, so p2 and p3 are in the future
421            CausalConeType::Future,
422        );
423
424        // Should find patterns in the causal future of p1
425        assert!(!results.is_empty(), "Should find causal descendants in future cone");
426    }
427}