use super::{memory_store::StoredMemoryTurn, MemoryStore, StoreError};
use crate::agents::memory::{Memory, MemoryTurn};
use potato_type::prompt::MessageNum;
use potato_util::create_uuid7;
use std::fmt::Debug;
use std::sync::Arc;
use tracing::warn;
pub struct PersistentMemory {
session_id: String,
app_name: String,
user_id: String,
invocation_id: String,
store: Arc<dyn MemoryStore>,
cache: Vec<MemoryTurn>,
loaded: bool,
max_turns: Option<usize>,
}
impl Debug for PersistentMemory {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PersistentMemory")
.field("session_id", &self.session_id)
.field("app_name", &self.app_name)
.field("user_id", &self.user_id)
.field("loaded", &self.loaded)
.field("cached_turns", &self.cache.len())
.field("max_turns", &self.max_turns)
.finish()
}
}
impl PersistentMemory {
pub fn new(
session_id: impl Into<String>,
app_name: impl Into<String>,
user_id: impl Into<String>,
store: Arc<dyn MemoryStore>,
) -> Self {
Self {
session_id: session_id.into(),
app_name: app_name.into(),
user_id: user_id.into(),
invocation_id: create_uuid7(),
store,
cache: Vec::new(),
loaded: false,
max_turns: None,
}
}
pub fn windowed(
session_id: impl Into<String>,
app_name: impl Into<String>,
user_id: impl Into<String>,
store: Arc<dyn MemoryStore>,
n: usize,
) -> Self {
Self {
session_id: session_id.into(),
app_name: app_name.into(),
user_id: user_id.into(),
invocation_id: create_uuid7(),
store,
cache: Vec::new(),
loaded: false,
max_turns: Some(n),
}
}
pub async fn hydrate(&mut self) -> Result<(), StoreError> {
if self.loaded {
return Ok(());
}
let stored = self
.store
.load_turns(&self.app_name, &self.user_id, &self.session_id)
.await?;
let turns: Vec<MemoryTurn> = stored.into_iter().map(|t| t.into_memory_turn()).collect();
self.cache = if let Some(n) = self.max_turns {
turns.into_iter().rev().take(n).rev().collect()
} else {
turns
};
self.loaded = true;
Ok(())
}
pub async fn push_turn_async(&mut self, turn: MemoryTurn) -> Result<(), StoreError> {
let stored = StoredMemoryTurn::new(
&self.session_id,
&self.app_name,
&self.user_id,
&self.invocation_id,
turn.user.clone(),
turn.assistant.clone(),
);
self.store.save_turn(&stored).await?;
self.cache.push(turn);
if let Some(n) = self.max_turns {
if self.cache.len() > n {
self.cache.remove(0);
}
}
Ok(())
}
pub async fn clear_store(&mut self) -> Result<(), StoreError> {
self.store
.clear(&self.app_name, &self.user_id, &self.session_id)
.await?;
self.cache.clear();
Ok(())
}
}
impl Memory for PersistentMemory {
fn as_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
Some(self)
}
fn push_turn(&mut self, turn: MemoryTurn) {
warn!("PersistentMemory::push_turn called synchronously — turn will not be persisted to the backing store. Use push_turn_async in async contexts.");
self.cache.push(turn);
if let Some(n) = self.max_turns {
if self.cache.len() > n {
self.cache.remove(0);
}
}
}
fn messages(&self) -> Vec<MessageNum> {
let mut msgs = Vec::with_capacity(self.cache.len() * 2);
for turn in &self.cache {
msgs.push(turn.user.clone());
msgs.push(turn.assistant.clone());
}
msgs
}
fn clear(&mut self) {
self.cache.clear();
}
fn len(&self) -> usize {
self.cache.len()
}
}