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::{collections::BTreeMap, path::PathBuf, sync::Arc};
use wcore::{AgentConfig, MemoryConfig, ToolDispatch, ToolFuture, agent::AsTool, model::Tool};
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>,
configs: RwLock<BTreeMap<String, MemoryConfig>>,
}
impl MemoryHook {
pub fn new(memory: Arc<Memory>) -> Self {
Self {
memory,
configs: RwLock::new(BTreeMap::new()),
}
}
fn recall_limit(&self, agent: &str) -> usize {
self.configs
.read()
.get(agent)
.map(|c| c.recall_limit)
.unwrap_or_else(|| 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_register_agent(&self, name: &str, config: &AgentConfig) {
self.configs
.write()
.insert(name.to_owned(), config.hooks.memory.clone());
}
fn on_unregister_agent(&self, name: &str) {
self.configs.write().remove(name);
}
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,
}
}
}