use anyhow::Result;
use forget::Forget;
use memory::Memory as Store;
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use recall::Recall;
use remember::Remember;
use runtime::Hook;
use std::{path::PathBuf, sync::Arc};
use wcore::{
MemoryConfig, ToolDispatch, ToolFuture,
agent::AsTool,
model::{HistoryEntry, Tool},
storage::Storage,
};
mod forget;
mod recall;
mod remember;
pub type SharedStore = Arc<RwLock<Store>>;
pub const DEFAULT_SOUL: &str = include_str!("../../../prompts/crab.md");
const MEMORY_PROMPT: &str = include_str!("../../../prompts/memory.md");
pub struct Memory {
pub(super) inner: SharedStore,
}
impl Memory {
pub fn open(db_path: PathBuf) -> Result<Self> {
let store = Store::open(&db_path)?;
Ok(Self {
inner: Arc::new(RwLock::new(store)),
})
}
pub fn shared(&self) -> SharedStore {
self.inner.clone()
}
pub(super) fn store_read(&self) -> RwLockReadGuard<'_, Store> {
self.inner.read()
}
pub(super) fn store_write(&self) -> RwLockWriteGuard<'_, Store> {
self.inner.write()
}
}
pub struct MemoryHook {
pub(super) memory: Arc<Memory>,
storage: Arc<dyn Storage>,
}
impl MemoryHook {
pub fn new(memory: Arc<Memory>, storage: Arc<dyn Storage>) -> Self {
Self { memory, storage }
}
pub fn recall_limit(&self, agent: &str) -> usize {
match self.storage.load_agent_by_name(agent) {
Ok(Some(cfg)) => cfg.hooks.memory.recall_limit,
Ok(None) => MemoryConfig::default().recall_limit,
Err(e) => {
tracing::error!(%agent, error = %e, "failed to load memory config — falling back to defaults");
MemoryConfig::default().recall_limit
}
}
}
}
impl Hook for MemoryHook {
fn schema(&self) -> Vec<Tool> {
vec![Recall::as_tool(), Remember::as_tool(), Forget::as_tool()]
}
fn system_prompt(&self) -> Option<String> {
Some(format!("\n\n{MEMORY_PROMPT}"))
}
fn on_before_run(
&self,
agent: &str,
_conversation_id: u64,
history: &[HistoryEntry],
) -> Vec<HistoryEntry> {
self.memory.before_run(history, self.recall_limit(agent))
}
fn dispatch<'a>(&'a self, name: &'a str, call: ToolDispatch) -> Option<ToolFuture<'a>> {
match name {
"recall" => Some(Box::pin(self.handle_recall(call))),
"remember" => Some(Box::pin(self.handle_remember(call))),
"forget" => Some(Box::pin(self.handle_forget(call))),
_ => None,
}
}
}