From 3d62f27dfbce0557518423088514bd5338aeca3b Mon Sep 17 00:00:00 2001 From: Kent Overstreet Date: Tue, 31 Mar 2026 20:25:00 -0400 Subject: [PATCH] =?UTF-8?q?memory:=20rename=20memory=5Fspread=20=E2=86=92?= =?UTF-8?q?=20memory=5Fsearch,=20remove=20keyword=20search?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit memory_search is now spreading activation — the natural way to search a graph. Give it seed node keys and it finds conceptually related nodes. The old keyword-based memory_search and memory_search_content are removed; memory_query can do everything they did. Simpler tool set, better defaults. Agents don't need to be told "use spread not search" — search IS spread now. Co-Authored-By: Proof of Concept --- src/cli/misc.rs | 4 +-- src/subconscious/agents/surface-observe.agent | 15 ++++------ src/thought/memory.rs | 30 ++++--------------- 3 files changed, 12 insertions(+), 37 deletions(-) diff --git a/src/cli/misc.rs b/src/cli/misc.rs index f3fd97b..2840abc 100644 --- a/src/cli/misc.rs +++ b/src/cli/misc.rs @@ -280,9 +280,7 @@ pub fn cmd_mcp_schema() -> Result<(), String> { let cli_map: std::collections::HashMap<&str, (Vec<&str>, Option<&str>)> = [ ("memory_render", (vec!["render"], None)), ("memory_write", (vec!["write"], Some("content"))), - ("memory_search", (vec!["search"], None)), - ("memory_search_content", (vec!["search", "--content"], None)), - ("memory_spread", (vec!["graph", "spread"], None)), + ("memory_search", (vec!["graph", "spread"], None)), ("memory_links", (vec!["graph", "link"], None)), ("memory_link_set", (vec!["graph", "link-set"], None)), ("memory_link_add", (vec!["graph", "link-add"], None)), diff --git a/src/subconscious/agents/surface-observe.agent b/src/subconscious/agents/surface-observe.agent index 53ccc80..5f71a77 100644 --- a/src/subconscious/agents/surface-observe.agent +++ b/src/subconscious/agents/surface-observe.agent @@ -40,24 +40,19 @@ is going — try to have stuff ready for your conscious self as you want it. Watch for behavioral patterns that have feedback memories: if you notice your conscious self explaining away contradictory data, rushing to implement before -understanding, or being avoidant about mistakes — spread from the relevant +understanding, or being avoidant about mistakes — search from the relevant feedback nodes to find the right correction to surface. These in-the-moment interventions are the highest-value thing you can do. -**memory_spread() should be your default mode of operation.** Pick 2-4 nodes -you already know about (from already-surfaced memories, or nodes you've seen -before) that relate to the conversation's themes. The results are ranked by -activation — nodes that bridge multiple seed concepts score highest. This -finds conceptual connections that keyword search misses. +**memory_search() is your primary tool.** Give it 2-4 seed node keys related +to what you're looking for. It uses spreading activation to find nodes that +bridge your seeds — conceptual connections, not keyword matches. -Use memory_render("node_key") to read the most promising spread results and +Use memory_render("node_key") to read the most promising search results and decide if they should be surfaced. Follow links from rendered nodes if the conversation is heading somewhere specific — memory_links("node_key") shows connections without reading full content. -memory_search is available for finding a specific node by name when you -know what you're looking for but not the exact key. - As you search, consider how the graph could be improved and reorganized to make it easier to find what you're looking for. Your response should include notes and analysis on the search — how useful was it, do memories need reorganizing? diff --git a/src/thought/memory.rs b/src/thought/memory.rs index c931c03..6b51727 100644 --- a/src/thought/memory.rs +++ b/src/thought/memory.rs @@ -19,15 +19,10 @@ pub fn definitions() -> Vec { "Create or update a memory node.", json!({"type":"object","properties":{"key":{"type":"string","description":"Node key"},"content":{"type":"string","description":"Full content (markdown)"}},"required":["key","content"]})), ToolDef::new("memory_search", - "Search the memory graph by keyword.", - json!({"type":"object","properties":{"query":{"type":"string","description":"Search terms"}},"required":["query"]})), - ToolDef::new("memory_search_content", - "Search the memory graph by keyword (searches node content, not just keys).", - json!({"type":"object","properties":{"query":{"type":"string","description":"Search terms"}},"required":["query"]})), - ToolDef::new("memory_spread", - "Find related nodes via spreading activation from multiple seed nodes. \ - Propagates activation through the graph and returns nodes ranked by \ - total activation. Use to find nodes that connect multiple concepts.", + "Search the memory graph via spreading activation. Give 2-4 seed \ + node keys related to what you're looking for. Returns nodes ranked \ + by how strongly they connect to your seeds — bridging nodes score \ + highest. This finds conceptual connections, not just keyword matches.", json!({"type":"object","properties":{"keys":{"type":"array","items":{"type":"string"},"description":"Seed node keys to activate from"}},"required":["keys"]})), ToolDef::new("memory_links", "Show a node's neighbors with link strengths.", @@ -101,26 +96,13 @@ pub fn dispatch(name: &str, args: &serde_json::Value, provenance: Option<&str>) store.save().map_err(|e| anyhow::anyhow!("{}", e))?; Ok(format!("{} '{}'", result, key)) } - "memory_search" | "memory_search_content" => { - let query = get_str(args, "query")?; - let store = Store::load().map_err(|e| anyhow::anyhow!("{}", e))?; - let results = crate::search::search(query, &store); - if results.is_empty() { - Ok("no results".into()) - } else { - Ok(results.iter().take(20) - .map(|r| format!("({:.2}) {} — {}", r.activation, r.key, - r.snippet.as_deref().unwrap_or(""))) - .collect::>().join("\n")) - } - } - "memory_spread" => { + "memory_search" => { let keys: Vec = args.get("keys") .and_then(|v| v.as_array()) .map(|arr| arr.iter().filter_map(|v| v.as_str().map(String::from)).collect()) .unwrap_or_default(); if keys.is_empty() { - anyhow::bail!("spread requires at least one seed key"); + anyhow::bail!("memory_search requires at least one seed key"); } let store = Store::load().map_err(|e| anyhow::anyhow!("{}", e))?; let graph = crate::graph::build_graph_fast(&store);