Skip to main content

sparrow/tools/
memory.rs

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}