rexis_rag/agent/memory/
manager.rs1use super::config::MemoryConfig;
4use super::conversation::{generate_session_id, ConversationMemoryStore};
5use super::episodic::EpisodicMemory;
6use super::semantic::SemanticMemory;
7use super::shared::SharedKnowledgeBase;
8use super::working::WorkingMemory;
9use crate::error::RragResult;
10use crate::storage::Memory;
11use rexis_llm::ChatMessage; use std::sync::Arc;
13
14pub struct AgentMemoryManager {
16 storage: Arc<dyn Memory>,
18
19 agent_id: String,
21
22 session_id: String,
24
25 conversation: ConversationMemoryStore,
27
28 working: Option<WorkingMemory>,
30
31 semantic: Option<SemanticMemory>,
33
34 episodic: Option<EpisodicMemory>,
36
37 shared: Option<SharedKnowledgeBase>,
39
40 config: MemoryConfig,
42}
43
44impl AgentMemoryManager {
45 pub fn new(mut config: MemoryConfig) -> Self {
47 if config.session_id.is_none() && config.auto_generate_session_id {
49 config.session_id = Some(generate_session_id());
50 }
51
52 let session_id = config
53 .session_id
54 .clone()
55 .unwrap_or_else(|| "default".to_string());
56
57 let conversation = ConversationMemoryStore::new(
58 config.backend.clone(),
59 session_id.clone(),
60 config.max_conversation_length,
61 config.persist_conversations,
62 );
63
64 Self {
65 storage: config.backend.clone(),
66 agent_id: config.agent_id.clone(),
67 session_id,
68 conversation,
69 working: None,
70 semantic: None,
71 episodic: None,
72 shared: None,
73 config,
74 }
75 }
76
77 pub fn working(&mut self) -> &mut WorkingMemory {
79 if self.working.is_none() {
80 self.working = Some(WorkingMemory::new(
81 self.storage.clone(),
82 self.session_id.clone(),
83 ));
84 }
85 self.working.as_mut().unwrap()
86 }
87
88 pub fn semantic(&mut self) -> &mut SemanticMemory {
90 if self.semantic.is_none() {
91 self.semantic = Some(SemanticMemory::new(
92 self.storage.clone(),
93 self.agent_id.clone(),
94 ));
95 }
96 self.semantic.as_mut().unwrap()
97 }
98
99 pub fn episodic(&mut self) -> &mut EpisodicMemory {
101 if self.episodic.is_none() {
102 self.episodic = Some(EpisodicMemory::new(
103 self.storage.clone(),
104 self.agent_id.clone(),
105 ));
106 }
107 self.episodic.as_mut().unwrap()
108 }
109
110 pub fn shared(&mut self) -> &mut SharedKnowledgeBase {
112 if self.shared.is_none() {
113 self.shared = Some(SharedKnowledgeBase::new(
114 self.storage.clone(),
115 self.agent_id.clone(),
116 ));
117 }
118 self.shared.as_mut().unwrap()
119 }
120
121 pub fn agent_id(&self) -> &str {
123 &self.agent_id
124 }
125
126 pub fn session_id(&self) -> &str {
128 &self.session_id
129 }
130
131 pub fn conversation(&self) -> &ConversationMemoryStore {
133 &self.conversation
134 }
135
136 pub async fn add_conversation_message(&self, message: ChatMessage) -> RragResult<()> {
138 self.conversation.add_message(message).await
139 }
140
141 pub async fn get_conversation_messages(&self) -> RragResult<Vec<ChatMessage>> {
143 self.conversation.get_messages().await
144 }
145
146 pub async fn clear_conversation(&self) -> RragResult<()> {
148 self.conversation.clear().await
149 }
150
151 pub fn storage(&self) -> Arc<dyn Memory> {
153 self.storage.clone()
154 }
155
156 pub fn agent_key(&self, key: &str) -> String {
158 format!("agent::{}::{}", self.agent_id, key)
159 }
160
161 pub fn session_key(&self, key: &str) -> String {
163 format!("session::{}::{}", self.session_id, key)
164 }
165
166 pub fn global_key(key: &str) -> String {
168 format!("global::{}", key)
169 }
170
171 pub async fn set_agent_memory(
173 &self,
174 key: &str,
175 value: impl Into<crate::storage::MemoryValue>,
176 ) -> RragResult<()> {
177 let full_key = self.agent_key(key);
178 self.storage.set(&full_key, value.into()).await
179 }
180
181 pub async fn get_agent_memory(
183 &self,
184 key: &str,
185 ) -> RragResult<Option<crate::storage::MemoryValue>> {
186 let full_key = self.agent_key(key);
187 self.storage.get(&full_key).await
188 }
189
190 pub async fn set_session_memory(
192 &self,
193 key: &str,
194 value: impl Into<crate::storage::MemoryValue>,
195 ) -> RragResult<()> {
196 let full_key = self.session_key(key);
197 self.storage.set(&full_key, value.into()).await
198 }
199
200 pub async fn get_session_memory(
202 &self,
203 key: &str,
204 ) -> RragResult<Option<crate::storage::MemoryValue>> {
205 let full_key = self.session_key(key);
206 self.storage.get(&full_key).await
207 }
208
209 pub async fn set_global_memory(
211 &self,
212 key: &str,
213 value: impl Into<crate::storage::MemoryValue>,
214 ) -> RragResult<()> {
215 let full_key = Self::global_key(key);
216 self.storage.set(&full_key, value.into()).await
217 }
218
219 pub async fn get_global_memory(
221 &self,
222 key: &str,
223 ) -> RragResult<Option<crate::storage::MemoryValue>> {
224 let full_key = Self::global_key(key);
225 self.storage.get(&full_key).await
226 }
227
228 pub fn config(&self) -> &MemoryConfig {
230 &self.config
231 }
232}
233
234impl Clone for AgentMemoryManager {
235 fn clone(&self) -> Self {
236 Self {
237 storage: self.storage.clone(),
238 agent_id: self.agent_id.clone(),
239 session_id: self.session_id.clone(),
240 conversation: ConversationMemoryStore::new(
241 self.storage.clone(),
242 self.session_id.clone(),
243 self.config.max_conversation_length,
244 self.config.persist_conversations,
245 ),
246 working: None, semantic: None,
248 episodic: None,
249 shared: None,
250 config: self.config.clone(),
251 }
252 }
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258 use crate::storage::{InMemoryStorage, MemoryValue};
259
260 #[tokio::test]
261 async fn test_memory_manager_namespacing() {
262 let storage = Arc::new(InMemoryStorage::new());
263 let config = MemoryConfig::new(storage, "test-agent").with_session_id("test-session");
264
265 let manager = AgentMemoryManager::new(config);
266
267 assert_eq!(
269 manager.agent_key("preferences"),
270 "agent::test-agent::preferences"
271 );
272 assert_eq!(
273 manager.session_key("working_data"),
274 "session::test-session::working_data"
275 );
276 assert_eq!(
277 AgentMemoryManager::global_key("shared_config"),
278 "global::shared_config"
279 );
280 }
281
282 #[tokio::test]
283 async fn test_memory_manager_scoped_storage() {
284 let storage = Arc::new(InMemoryStorage::new());
285 let config = MemoryConfig::new(storage.clone(), "test-agent");
286
287 let manager = AgentMemoryManager::new(config);
288
289 manager
291 .set_agent_memory("profile::name", MemoryValue::from("Alice"))
292 .await
293 .unwrap();
294
295 manager
296 .set_session_memory("temp::data", MemoryValue::from(42i64))
297 .await
298 .unwrap();
299
300 manager
301 .set_global_memory("config::setting", MemoryValue::from(true))
302 .await
303 .unwrap();
304
305 let name = manager.get_agent_memory("profile::name").await.unwrap();
307 assert_eq!(name.unwrap().as_string(), Some("Alice"));
308
309 let data = manager.get_session_memory("temp::data").await.unwrap();
310 assert_eq!(data.unwrap().as_integer(), Some(42));
311
312 let setting = manager.get_global_memory("config::setting").await.unwrap();
313 assert_eq!(setting.unwrap().as_boolean(), Some(true));
314 }
315
316 #[tokio::test]
317 async fn test_conversation_integration() {
318 let storage = Arc::new(InMemoryStorage::new());
319 let config = MemoryConfig::new(storage, "test-agent").with_persistence(true);
320
321 let manager = AgentMemoryManager::new(config);
322
323 manager
325 .add_conversation_message(ChatMessage::system("System prompt"))
326 .await
327 .unwrap();
328
329 manager
330 .add_conversation_message(ChatMessage::user("Hello"))
331 .await
332 .unwrap();
333
334 let messages = manager.get_conversation_messages().await.unwrap();
336 assert_eq!(messages.len(), 2);
337 }
338}