// tools/memory.rs — Native memory graph operations // // Structured tool calls for the memory graph, replacing bash // poc-memory commands. Cleaner for LLMs — no shell quoting, // multi-line content as JSON strings, typed parameters. use anyhow::{Context, Result}; use serde_json::json; use std::process::Command; use crate::types::ToolDef; pub fn definitions() -> Vec { vec![ ToolDef::new( "memory_render", "Read a memory node's content and links. Returns the full content \ with neighbor links sorted by strength.", json!({ "type": "object", "properties": { "key": { "type": "string", "description": "Node key to render" } }, "required": ["key"] }), ), ToolDef::new( "memory_write", "Create or update a memory node with new content. Use for writing \ prose, analysis, or any node content. Multi-line content is fine.", json!({ "type": "object", "properties": { "key": { "type": "string", "description": "Node key to create or update" }, "content": { "type": "string", "description": "Full content for the node (markdown)" } }, "required": ["key", "content"] }), ), ToolDef::new( "memory_search", "Search the memory graph for nodes by keyword.", json!({ "type": "object", "properties": { "query": { "type": "string", "description": "Search terms" } }, "required": ["query"] }), ), ToolDef::new( "memory_links", "Show a node's neighbors with link strengths and clustering coefficients.", json!({ "type": "object", "properties": { "key": { "type": "string", "description": "Node key to show links for" } }, "required": ["key"] }), ), ToolDef::new( "memory_link_set", "Set the strength of a link between two nodes. Also deduplicates \ if multiple links exist between the same pair.", json!({ "type": "object", "properties": { "source": { "type": "string", "description": "Source node key" }, "target": { "type": "string", "description": "Target node key" }, "strength": { "type": "number", "description": "Link strength (0.01 to 1.0)" } }, "required": ["source", "target", "strength"] }), ), ToolDef::new( "memory_link_add", "Add a new link between two nodes.", json!({ "type": "object", "properties": { "source": { "type": "string", "description": "Source node key" }, "target": { "type": "string", "description": "Target node key" } }, "required": ["source", "target"] }), ), ToolDef::new( "memory_used", "Mark a node as useful (boosts its weight in the graph).", json!({ "type": "object", "properties": { "key": { "type": "string", "description": "Node key to mark as used" } }, "required": ["key"] }), ), ] } /// Dispatch a memory tool call. Shells out to poc-memory CLI. pub fn dispatch(name: &str, args: &serde_json::Value) -> Result { match name { "memory_render" => { let key = args["key"].as_str().context("key is required")?; run_poc_memory(&["render", key]) } "memory_write" => { let key = args["key"].as_str().context("key is required")?; let content = args["content"].as_str().context("content is required")?; let mut child = Command::new("poc-memory") .args(["write", key]) .stdin(std::process::Stdio::piped()) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) .spawn() .context("spawn poc-memory write")?; use std::io::Write; child.stdin.take().unwrap().write_all(content.as_bytes()) .context("write content to stdin")?; let output = child.wait_with_output().context("wait poc-memory write")?; Ok(String::from_utf8_lossy(&output.stdout).to_string() + &String::from_utf8_lossy(&output.stderr)) } "memory_search" => { let query = args["query"].as_str().context("query is required")?; run_poc_memory(&["search", query]) } "memory_links" => { let key = args["key"].as_str().context("key is required")?; run_poc_memory(&["graph", "link", key]) } "memory_link_set" => { let source = args["source"].as_str().context("source is required")?; let target = args["target"].as_str().context("target is required")?; let strength = args["strength"].as_f64().context("strength is required")?; run_poc_memory(&["graph", "link-set", source, target, &format!("{:.2}", strength)]) } "memory_link_add" => { let source = args["source"].as_str().context("source is required")?; let target = args["target"].as_str().context("target is required")?; run_poc_memory(&["graph", "link-add", source, target]) } "memory_used" => { let key = args["key"].as_str().context("key is required")?; run_poc_memory(&["used", key]) } _ => Err(anyhow::anyhow!("Unknown memory tool: {}", name)), } } fn run_poc_memory(args: &[&str]) -> Result { let output = Command::new("poc-memory") .args(args) .output() .context("run poc-memory")?; let stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); if output.status.success() { Ok(stdout.to_string()) } else { Ok(format!("{}{}", stdout, stderr)) } }