use super::config::MemoryConfig;
use super::conversation::{generate_session_id, ConversationMemoryStore};
use super::episodic::EpisodicMemory;
use super::semantic::SemanticMemory;
use super::shared::SharedKnowledgeBase;
use super::working::WorkingMemory;
use crate::error::RragResult;
use crate::storage::Memory;
use rexis_llm::ChatMessage; use std::sync::Arc;
pub struct AgentMemoryManager {
storage: Arc<dyn Memory>,
agent_id: String,
session_id: String,
conversation: ConversationMemoryStore,
working: Option<WorkingMemory>,
semantic: Option<SemanticMemory>,
episodic: Option<EpisodicMemory>,
shared: Option<SharedKnowledgeBase>,
config: MemoryConfig,
}
impl AgentMemoryManager {
pub fn new(mut config: MemoryConfig) -> Self {
if config.session_id.is_none() && config.auto_generate_session_id {
config.session_id = Some(generate_session_id());
}
let session_id = config
.session_id
.clone()
.unwrap_or_else(|| "default".to_string());
let conversation = ConversationMemoryStore::new(
config.backend.clone(),
session_id.clone(),
config.max_conversation_length,
config.persist_conversations,
);
Self {
storage: config.backend.clone(),
agent_id: config.agent_id.clone(),
session_id,
conversation,
working: None,
semantic: None,
episodic: None,
shared: None,
config,
}
}
pub fn working(&mut self) -> &mut WorkingMemory {
if self.working.is_none() {
self.working = Some(WorkingMemory::new(
self.storage.clone(),
self.session_id.clone(),
));
}
self.working.as_mut().unwrap()
}
pub fn semantic(&mut self) -> &mut SemanticMemory {
if self.semantic.is_none() {
self.semantic = Some(SemanticMemory::new(
self.storage.clone(),
self.agent_id.clone(),
));
}
self.semantic.as_mut().unwrap()
}
pub fn episodic(&mut self) -> &mut EpisodicMemory {
if self.episodic.is_none() {
self.episodic = Some(EpisodicMemory::new(
self.storage.clone(),
self.agent_id.clone(),
));
}
self.episodic.as_mut().unwrap()
}
pub fn shared(&mut self) -> &mut SharedKnowledgeBase {
if self.shared.is_none() {
self.shared = Some(SharedKnowledgeBase::new(
self.storage.clone(),
self.agent_id.clone(),
));
}
self.shared.as_mut().unwrap()
}
pub fn agent_id(&self) -> &str {
&self.agent_id
}
pub fn session_id(&self) -> &str {
&self.session_id
}
pub fn conversation(&self) -> &ConversationMemoryStore {
&self.conversation
}
pub async fn add_conversation_message(&self, message: ChatMessage) -> RragResult<()> {
self.conversation.add_message(message).await
}
pub async fn get_conversation_messages(&self) -> RragResult<Vec<ChatMessage>> {
self.conversation.get_messages().await
}
pub async fn clear_conversation(&self) -> RragResult<()> {
self.conversation.clear().await
}
pub fn storage(&self) -> Arc<dyn Memory> {
self.storage.clone()
}
pub fn agent_key(&self, key: &str) -> String {
format!("agent::{}::{}", self.agent_id, key)
}
pub fn session_key(&self, key: &str) -> String {
format!("session::{}::{}", self.session_id, key)
}
pub fn global_key(key: &str) -> String {
format!("global::{}", key)
}
pub async fn set_agent_memory(
&self,
key: &str,
value: impl Into<crate::storage::MemoryValue>,
) -> RragResult<()> {
let full_key = self.agent_key(key);
self.storage.set(&full_key, value.into()).await
}
pub async fn get_agent_memory(
&self,
key: &str,
) -> RragResult<Option<crate::storage::MemoryValue>> {
let full_key = self.agent_key(key);
self.storage.get(&full_key).await
}
pub async fn set_session_memory(
&self,
key: &str,
value: impl Into<crate::storage::MemoryValue>,
) -> RragResult<()> {
let full_key = self.session_key(key);
self.storage.set(&full_key, value.into()).await
}
pub async fn get_session_memory(
&self,
key: &str,
) -> RragResult<Option<crate::storage::MemoryValue>> {
let full_key = self.session_key(key);
self.storage.get(&full_key).await
}
pub async fn set_global_memory(
&self,
key: &str,
value: impl Into<crate::storage::MemoryValue>,
) -> RragResult<()> {
let full_key = Self::global_key(key);
self.storage.set(&full_key, value.into()).await
}
pub async fn get_global_memory(
&self,
key: &str,
) -> RragResult<Option<crate::storage::MemoryValue>> {
let full_key = Self::global_key(key);
self.storage.get(&full_key).await
}
pub fn config(&self) -> &MemoryConfig {
&self.config
}
}
impl Clone for AgentMemoryManager {
fn clone(&self) -> Self {
Self {
storage: self.storage.clone(),
agent_id: self.agent_id.clone(),
session_id: self.session_id.clone(),
conversation: ConversationMemoryStore::new(
self.storage.clone(),
self.session_id.clone(),
self.config.max_conversation_length,
self.config.persist_conversations,
),
working: None, semantic: None,
episodic: None,
shared: None,
config: self.config.clone(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::{InMemoryStorage, MemoryValue};
#[tokio::test]
async fn test_memory_manager_namespacing() {
let storage = Arc::new(InMemoryStorage::new());
let config = MemoryConfig::new(storage, "test-agent").with_session_id("test-session");
let manager = AgentMemoryManager::new(config);
assert_eq!(
manager.agent_key("preferences"),
"agent::test-agent::preferences"
);
assert_eq!(
manager.session_key("working_data"),
"session::test-session::working_data"
);
assert_eq!(
AgentMemoryManager::global_key("shared_config"),
"global::shared_config"
);
}
#[tokio::test]
async fn test_memory_manager_scoped_storage() {
let storage = Arc::new(InMemoryStorage::new());
let config = MemoryConfig::new(storage.clone(), "test-agent");
let manager = AgentMemoryManager::new(config);
manager
.set_agent_memory("profile::name", MemoryValue::from("Alice"))
.await
.unwrap();
manager
.set_session_memory("temp::data", MemoryValue::from(42i64))
.await
.unwrap();
manager
.set_global_memory("config::setting", MemoryValue::from(true))
.await
.unwrap();
let name = manager.get_agent_memory("profile::name").await.unwrap();
assert_eq!(name.unwrap().as_string(), Some("Alice"));
let data = manager.get_session_memory("temp::data").await.unwrap();
assert_eq!(data.unwrap().as_integer(), Some(42));
let setting = manager.get_global_memory("config::setting").await.unwrap();
assert_eq!(setting.unwrap().as_boolean(), Some(true));
}
#[tokio::test]
async fn test_conversation_integration() {
let storage = Arc::new(InMemoryStorage::new());
let config = MemoryConfig::new(storage, "test-agent").with_persistence(true);
let manager = AgentMemoryManager::new(config);
manager
.add_conversation_message(ChatMessage::system("System prompt"))
.await
.unwrap();
manager
.add_conversation_message(ChatMessage::user("Hello"))
.await
.unwrap();
let messages = manager.get_conversation_messages().await.unwrap();
assert_eq!(messages.len(), 2);
}
}