Skip to main content

aagt_core/agent/
namespaced_memory.rs

1use std::sync::Arc;
2use std::time::Duration;
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use crate::agent::memory::{MemoryManager, Memory};
6use crate::error::Result;
7
8/// Metadata for namespaced memory entries
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct MemoryEntry {
11    /// The actual value stored
12    pub value: String,
13    /// When this entry was created
14    pub created_at: DateTime<Utc>,
15    /// When this entry expires (None = never)
16    pub expires_at: Option<DateTime<Utc>>,
17    /// Namespace this entry belongs to
18    pub namespace: String,
19    /// Optional author/source
20    pub author: Option<String>,
21}
22
23impl MemoryEntry {
24    /// Check if this entry has expired
25    pub fn is_expired(&self) -> bool {
26        if let Some(expires) = self.expires_at {
27            Utc::now() > expires
28        } else {
29            false
30        }
31    }
32}
33
34/// Namespaced memory wrapper providing isolation and TTL support
35/// 
36/// # Architecture
37/// 
38/// ```text
39/// ┌─────────────────────────────────────────┐
40/// │      NamespacedMemory                   │
41/// ├─────────────────────────────────────────┤
42/// │  Namespace: "market"                    │
43/// │    - btc_price → $43,200 (TTL: 5m)     │
44/// │    - eth_price → $2,300  (TTL: 5m)     │
45/// ├─────────────────────────────────────────┤
46/// │  Namespace: "news"                      │
47/// │    - latest → "Fed announces..." (TTL: 1h) │
48/// ├─────────────────────────────────────────┤
49/// │  Namespace: "analysis"                  │
50/// │    - btc_signal → "BUY" (TTL: 30m)     │
51/// └─────────────────────────────────────────┘
52/// ```
53/// 
54/// # Benefits
55/// 
56/// - **Isolation**: Each namespace is independent
57/// - **TTL**: Automatic expiration of stale data
58/// - **Performance**: Shared data avoids redundant computation
59/// - **Security**: Namespaces prevent cross-contamination
60pub struct NamespacedMemory {
61    memory: Arc<MemoryManager>,
62}
63
64impl NamespacedMemory {
65    /// Create a new namespaced memory wrapper
66    pub fn new(memory: Arc<MemoryManager>) -> Self {
67        Self { memory }
68    }
69
70    /// Store a value in a specific namespace with optional TTL
71    /// 
72    /// # Arguments
73    /// 
74    /// * `namespace` - Namespace for isolation (e.g., "market", "news", "analysis")
75    /// * `key` - Unique key within the namespace
76    /// * `value` - Value to store
77    /// * `ttl` - Optional time-to-live duration
78    /// * `author` - Optional author/source identifier
79    /// 
80    /// # Example
81    /// 
82    /// ```ignore
83    /// // Store market price with 5-minute TTL
84    /// memory.store(
85    ///     "market",
86    ///     "btc_price",
87    ///     "$43,200",
88    ///     Some(Duration::from_secs(300)),
89    ///     Some("PriceAPI")
90    /// ).await?;
91    /// ```
92    pub async fn store(
93        &self,
94        namespace: &str,
95        key: &str,
96        value: &str,
97        ttl: Option<Duration>,
98        author: Option<String>,
99    ) -> Result<()> {
100        let full_key = format!("{}::{}", namespace, key);
101        
102        let entry = MemoryEntry {
103            value: value.to_string(),
104            created_at: Utc::now(),
105            expires_at: ttl.map(|d| Utc::now() + chrono::Duration::from_std(d).unwrap()),
106            namespace: namespace.to_string(),
107            author,
108        };
109
110        let serialized = serde_json::to_string(&entry)
111            .map_err(|e| crate::error::Error::Internal(format!("Failed to serialize entry: {}", e)))?;
112
113        self.memory.store_knowledge("system", None, &full_key, &serialized, "namespaced_memory").await
114    }
115
116    /// Read a value from a specific namespace
117    /// 
118    /// Returns `None` if:
119    /// - Key doesn't exist
120    /// - Entry has expired
121    /// 
122    /// # Example
123    /// 
124    /// ```ignore
125    /// if let Some(price) = memory.read("market", "btc_price").await? {
126    ///     println!("BTC price: {}", price);
127    /// } else {
128    ///     println!("Price not available or expired");
129    /// }
130    /// ```
131    pub async fn read(&self, namespace: &str, key: &str) -> Result<Option<String>> {
132        let full_key = format!("{}::{}", namespace, key);
133        
134        let results = self.memory.search("system", None, &full_key, 1).await?;
135        
136        if results.is_empty() {
137            return Ok(None);
138        }
139
140        // Get the first (most recent) result
141        let content = &results[0].content;
142        
143        let entry: MemoryEntry = serde_json::from_str(content)
144            .map_err(|e| crate::error::Error::Internal(format!("Failed to deserialize entry: {}", e)))?;
145
146        // Check expiration
147        if entry.is_expired() {
148            return Ok(None);
149        }
150
151        Ok(Some(entry.value))
152    }
153
154    /// Read with metadata (including timestamp, author, etc.)
155    pub async fn read_with_metadata(&self, namespace: &str, key: &str) -> Result<Option<MemoryEntry>> {
156        let full_key = format!("{}::{}", namespace, key);
157        
158        let results = self.memory.search("system", None, &full_key, 1).await?;
159        
160        if results.is_empty() {
161            return Ok(None);
162        }
163
164        let content = &results[0].content;
165        
166        let entry: MemoryEntry = serde_json::from_str(content)
167            .map_err(|e| crate::error::Error::Internal(format!("Failed to deserialize entry: {}", e)))?;
168
169        if entry.is_expired() {
170            return Ok(None);
171        }
172
173        Ok(Some(entry))
174    }
175
176    /// List all keys in a namespace
177    pub async fn list_keys(&self, namespace: &str) -> Result<Vec<String>> {
178        let prefix = format!("{}::", namespace);
179        let results = self.memory.search("system", None, &prefix, 100).await?;
180        
181        let mut keys = Vec::new();
182        for result in results {
183            if let Ok(entry) = serde_json::from_str::<MemoryEntry>(&result.content) {
184                if !entry.is_expired() {
185                    // Extract key from full_key (remove namespace prefix)
186                    let key = result.title.strip_prefix(&prefix)
187                        .unwrap_or(&result.title)
188                        .to_string();
189                    keys.push(key);
190                }
191            }
192        }
193        
194        Ok(keys)
195    }
196
197    /// Delete a key from a namespace
198    pub async fn delete(&self, namespace: &str, key: &str) -> Result<()> {
199        // For now, we "delete" by storing an expired entry.
200        // The Memory trait should eventually include a delete method.
201        self.store(namespace, key, "", Some(Duration::from_secs(0)), None).await
202    }
203
204    /// Clear all entries in a namespace
205    pub async fn clear_namespace(&self, namespace: &str) -> Result<()> {
206        let keys = self.list_keys(namespace).await?;
207        for key in keys {
208            self.delete(namespace, &key).await?;
209        }
210        Ok(())
211    }
212}
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217
218    #[tokio::test]
219    async fn test_store_and_read() {
220        // This test would require a real MemoryManager instance
221        // Skipped for now
222    }
223
224    #[test]
225    fn test_entry_expiration() {
226        let entry = MemoryEntry {
227            value: "test".to_string(),
228            created_at: Utc::now(),
229            expires_at: Some(Utc::now() - chrono::Duration::hours(1)),
230            namespace: "test".to_string(),
231            author: None,
232        };
233
234        assert!(entry.is_expired());
235    }
236}