use std::sync::Arc;
use std::time::SystemTime;
use async_trait::async_trait;
use foundation_db::traits::DocumentStore;
use crate::agentic::memory::MemoryHierarchy;
use crate::agentic::memory_store::MemoryStore;
use crate::agentic::tool_impl::{ToolArgs, ToolCallResult, ToolError, ToolImpl};
use crate::agentic::toolshed::MemoryAccess;
use crate::types::base_types::Args;
use crate::types::{MemoryFact, SessionRecord, Tool, ToolArguments, ToolDefinition};
const TOOL: &str = "memory";
fn exec_err(reason: impl Into<String>) -> ToolError {
ToolError::Execution {
tool: TOOL.into(),
reason: reason.into(),
}
}
fn fact_arg() -> foundation_jsonschema::ValidationOptions {
foundation_jsonschema::scheme::object()
.required("fact", foundation_jsonschema::scheme::string().min_len(1))
.build()
}
fn new_fact(text: String) -> MemoryFact {
MemoryFact {
fact: text,
asserted_at: SystemTime::now(),
source_message_id: foundation_compact::ids::new_scru128(),
confidence: 1.0,
}
}
pub struct MemoryTool {
hierarchy: Arc<dyn MemoryAccess>,
}
impl MemoryTool {
#[must_use]
pub fn new<M, D>(hierarchy: Arc<MemoryHierarchy<M, D>>) -> Self
where
M: MemoryStore + 'static,
D: DocumentStore + 'static,
{
Self { hierarchy }
}
#[must_use]
pub fn from_shared(memory: Arc<dyn MemoryAccess>) -> Self {
Self { hierarchy: memory }
}
async fn current_working(&self) -> Result<(Vec<MemoryFact>, u64), ToolError> {
let memory = self
.hierarchy
.hydrate()
.await
.map_err(|e| exec_err(format!("hydrate failed: {e}")))?;
Ok(match memory.working {
Some(SessionRecord::WorkingMemory { facts, version, .. }) => (facts, version),
_ => (Vec::new(), 0),
})
}
async fn write(&self, facts: Vec<MemoryFact>, version: u64) -> Result<(), ToolError> {
self.hierarchy
.update_working_memory(facts, version)
.await
.map_err(|e| exec_err(format!("write failed: {e}")))
}
async fn add(&self, args: ToolArgs<'_>) -> Result<ToolCallResult, ToolError> {
let fact = args.str("fact")?.to_string();
let (mut facts, version) = self.current_working().await?;
facts.push(new_fact(fact.clone()));
self.write(facts, version).await?;
Ok(ToolCallResult::text(format!("Remembered: {fact}")))
}
async fn remove(&self, args: ToolArgs<'_>) -> Result<ToolCallResult, ToolError> {
let fact = args.str("fact")?.to_string();
let (facts, version) = self.current_working().await?;
let before = facts.len();
let remaining: Vec<MemoryFact> = facts.into_iter().filter(|f| f.fact != fact).collect();
if remaining.len() == before {
return Err(exec_err(format!("no fact matching '{fact}' in working memory")));
}
self.write(remaining, version).await?;
Ok(ToolCallResult::text(format!("Forgot: {fact}")))
}
async fn replace(&self, args: ToolArgs<'_>) -> Result<ToolCallResult, ToolError> {
let old = args.str("old")?.to_string();
let new = args.str("new")?.to_string();
let (mut facts, version) = self.current_working().await?;
let mut replaced = false;
for f in &mut facts {
if f.fact == old {
f.fact = new.clone();
f.asserted_at = SystemTime::now();
replaced = true;
}
}
if !replaced {
return Err(exec_err(format!("no fact matching '{old}' in working memory")));
}
self.write(facts, version).await?;
Ok(ToolCallResult::text(format!(
"Updated memory: {old} → {new}"
)))
}
}
#[async_trait]
impl ToolImpl for MemoryTool {
fn definition(&self) -> Tool {
Tool::MultiCommands(
TOOL.to_string(),
vec![
ToolDefinition {
name: "add".into(),
category: TOOL.into(),
description: "Add a durable fact about the user or task. Args: fact.".into(),
arguments: Args::new(fact_arg()),
returns: None,
},
ToolDefinition {
name: "remove".into(),
category: TOOL.into(),
description: "Remove a fact by its exact text. Args: fact.".into(),
arguments: Args::new(fact_arg()),
returns: None,
},
ToolDefinition {
name: "replace".into(),
category: TOOL.into(),
description: "Replace a fact's text. Args: old (exact), new.".into(),
arguments: Args::new(
foundation_jsonschema::scheme::object()
.required("old", foundation_jsonschema::scheme::string().min_len(1))
.required("new", foundation_jsonschema::scheme::string().min_len(1))
.build(),
),
returns: None,
},
],
)
}
async fn execute(&self, arguments: ToolArguments) -> Result<ToolCallResult, ToolError> {
let args = ToolArgs::new(TOOL, &arguments);
let command = args.str("command")?;
match command {
"add" => self.add(args).await,
"remove" => self.remove(args).await,
"replace" => self.replace(args).await,
other => Err(ToolError::InvalidArguments {
tool: TOOL.into(),
reason: format!("unknown memory command '{other}' (add|remove|replace)"),
}),
}
}
}
pub fn register_memory_tool<M, D>(
manager: &crate::agentic::tool_impl::ToolCallManager,
hierarchy: Arc<MemoryHierarchy<M, D>>,
) where
M: MemoryStore + 'static,
D: DocumentStore + 'static,
{
manager.register(Arc::new(MemoryTool::new(hierarchy)));
}