1use async_trait::async_trait;
2use serde_json::json;
3use std::sync::Arc;
4
5use super::{Tool, ToolCtx, ToolResult};
6use crate::event::RiskLevel;
7use crate::memory::{Fact, Memory, MemoryDocKind};
8
9pub struct MemoryTool {
10 memory: Arc<dyn Memory>,
11}
12
13impl MemoryTool {
14 pub fn new(memory: Arc<dyn Memory>) -> Self {
15 Self { memory }
16 }
17}
18
19#[async_trait]
20impl Tool for MemoryTool {
21 fn name(&self) -> &str {
22 "memory"
23 }
24
25 fn description(&self) -> &str {
26 "Read and manage Sparrow persistent memory: facts, bounded MEMORY.md/USER.md docs, recall, and consolidation."
27 }
28
29 fn schema(&self) -> serde_json::Value {
30 json!({
31 "type": "object",
32 "properties": {
33 "action": {
34 "type": "string",
35 "enum": ["list", "recall", "add", "replace", "remove", "consolidate", "docs", "stats"]
36 },
37 "id": { "type": "string", "description": "Fact id for remove/replace" },
38 "key": { "type": "string", "description": "Fact key or doc kind (memory|user)" },
39 "value": { "type": "string", "description": "Fact value or doc content" },
40 "query": { "type": "string", "description": "Recall query" },
41 "limit": { "type": "integer", "description": "Maximum rows to return" }
42 },
43 "required": ["action"]
44 })
45 }
46
47 fn risk(&self) -> RiskLevel {
48 RiskLevel::Mutating
49 }
50
51 async fn call(&self, args: serde_json::Value, _ctx: &ToolCtx) -> anyhow::Result<ToolResult> {
52 let action = args["action"].as_str().unwrap_or("list");
53 let limit = args["limit"].as_u64().unwrap_or(10).min(50) as usize;
54 match action {
55 "list" => {
56 let facts = self.memory.all_facts();
57 if facts.is_empty() {
58 Ok(ToolResult::text("No stored facts."))
59 } else {
60 Ok(ToolResult::text(
61 facts
62 .iter()
63 .take(limit)
64 .map(|fact| format!("{} {}: {}", fact.id, fact.key, fact.value))
65 .collect::<Vec<_>>()
66 .join("\n"),
67 ))
68 }
69 }
70 "recall" => {
71 let query = args["query"].as_str().unwrap_or("");
72 if query.trim().is_empty() {
73 return Ok(ToolResult::error("memory recall requires query"));
74 }
75 let facts = self.memory.recall(query, limit);
76 if facts.is_empty() {
77 Ok(ToolResult::text("No matching facts."))
78 } else {
79 Ok(ToolResult::text(
80 facts
81 .iter()
82 .map(|fact| format!("{} {}: {}", fact.id, fact.key, fact.value))
83 .collect::<Vec<_>>()
84 .join("\n"),
85 ))
86 }
87 }
88 "add" | "replace" => {
89 let key = args["key"].as_str().unwrap_or("").trim();
90 let value = args["value"].as_str().unwrap_or("").trim();
91 if key.is_empty() || value.is_empty() {
92 return Ok(ToolResult::error(
93 "memory add/replace requires key and value",
94 ));
95 }
96 if let Some(kind) = MemoryDocKind::parse(key) {
97 self.memory.upsert_memory_doc(kind, value)?;
98 return Ok(ToolResult::text(format!("Updated {}", kind.as_str())));
99 }
100 let id = if action == "replace" {
101 args["id"]
102 .as_str()
103 .filter(|id| !id.trim().is_empty())
104 .map(|id| id.to_string())
105 .or_else(|| {
106 self.memory
107 .all_facts()
108 .into_iter()
109 .find(|f| f.key == key)
110 .map(|f| f.id)
111 })
112 } else {
113 Some(uuid::Uuid::new_v4().to_string())
114 };
115 let id = id.unwrap_or_else(|| key.to_string());
116 self.memory.remember(Fact {
117 id: id.clone(),
118 key: key.to_string(),
119 value: value.to_string(),
120 created_at: chrono::Utc::now().to_rfc3339(),
121 updated_at: chrono::Utc::now().to_rfc3339(),
122 })?;
123 Ok(ToolResult::text(format!("Stored fact {}", id)))
124 }
125 "remove" => {
126 let id = args["id"].as_str().unwrap_or("").trim();
127 let key = args["key"].as_str().unwrap_or("").trim();
128 if let Some(kind) = MemoryDocKind::parse(key) {
129 self.memory.remove_memory_doc(kind)?;
130 return Ok(ToolResult::text(format!("Removed {}", kind.as_str())));
131 }
132 if id.is_empty() {
133 return Ok(ToolResult::error("memory remove requires id"));
134 }
135 self.memory.forget(id)?;
136 Ok(ToolResult::text(format!("Removed fact {}", id)))
137 }
138 "consolidate" => {
139 self.memory.consolidate_memory()?;
140 Ok(ToolResult::text("Memory consolidated into bounded docs."))
141 }
142 "docs" => {
143 let mut out = Vec::new();
144 for kind in [MemoryDocKind::Memory, MemoryDocKind::User] {
145 if let Some(doc) = self.memory.memory_doc(kind) {
146 out.push(format!("## {}\n{}", kind.as_str(), doc.content));
147 }
148 }
149 if out.is_empty() {
150 Ok(ToolResult::text("No MEMORY.md/USER.md docs stored."))
151 } else {
152 Ok(ToolResult::text(out.join("\n\n")))
153 }
154 }
155 "stats" => {
156 let stats = self.memory.memory_stats();
157 Ok(ToolResult::text(format!(
158 "facts={} MEMORY.md={}/{} chars USER.md={}/{} chars",
159 stats.facts,
160 stats.memory_chars,
161 stats.memory_limit,
162 stats.user_chars,
163 stats.user_limit
164 )))
165 }
166 _ => Ok(ToolResult::error("unknown memory action")),
167 }
168 }
169}