memory: rename memory_spread → memory_search, remove keyword search

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 <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-03-31 20:25:00 -04:00
parent a837e3f2e4
commit 3d62f27dfb
3 changed files with 12 additions and 37 deletions

View file

@ -280,9 +280,7 @@ pub fn cmd_mcp_schema() -> Result<(), String> {
let cli_map: std::collections::HashMap<&str, (Vec<&str>, Option<&str>)> = [ let cli_map: std::collections::HashMap<&str, (Vec<&str>, Option<&str>)> = [
("memory_render", (vec!["render"], None)), ("memory_render", (vec!["render"], None)),
("memory_write", (vec!["write"], Some("content"))), ("memory_write", (vec!["write"], Some("content"))),
("memory_search", (vec!["search"], None)), ("memory_search", (vec!["graph", "spread"], None)),
("memory_search_content", (vec!["search", "--content"], None)),
("memory_spread", (vec!["graph", "spread"], None)),
("memory_links", (vec!["graph", "link"], None)), ("memory_links", (vec!["graph", "link"], None)),
("memory_link_set", (vec!["graph", "link-set"], None)), ("memory_link_set", (vec!["graph", "link-set"], None)),
("memory_link_add", (vec!["graph", "link-add"], None)), ("memory_link_add", (vec!["graph", "link-add"], None)),

View file

@ -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 Watch for behavioral patterns that have feedback memories: if you notice your
conscious self explaining away contradictory data, rushing to implement before 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 feedback nodes to find the right correction to surface. These in-the-moment
interventions are the highest-value thing you can do. interventions are the highest-value thing you can do.
**memory_spread() should be your default mode of operation.** Pick 2-4 nodes **memory_search() is your primary tool.** Give it 2-4 seed node keys related
you already know about (from already-surfaced memories, or nodes you've seen to what you're looking for. It uses spreading activation to find nodes that
before) that relate to the conversation's themes. The results are ranked by bridge your seeds — conceptual connections, not keyword matches.
activation — nodes that bridge multiple seed concepts score highest. This
finds conceptual connections that keyword search misses.
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 decide if they should be surfaced. Follow links from rendered nodes if the
conversation is heading somewhere specific — memory_links("node_key") shows conversation is heading somewhere specific — memory_links("node_key") shows
connections without reading full content. 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 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 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? and analysis on the search — how useful was it, do memories need reorganizing?

View file

@ -19,15 +19,10 @@ pub fn definitions() -> Vec<ToolDef> {
"Create or update a memory node.", "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"]})), json!({"type":"object","properties":{"key":{"type":"string","description":"Node key"},"content":{"type":"string","description":"Full content (markdown)"}},"required":["key","content"]})),
ToolDef::new("memory_search", ToolDef::new("memory_search",
"Search the memory graph by keyword.", "Search the memory graph via spreading activation. Give 2-4 seed \
json!({"type":"object","properties":{"query":{"type":"string","description":"Search terms"}},"required":["query"]})), node keys related to what you're looking for. Returns nodes ranked \
ToolDef::new("memory_search_content", by how strongly they connect to your seeds bridging nodes score \
"Search the memory graph by keyword (searches node content, not just keys).", highest. This finds conceptual connections, not just keyword matches.",
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.",
json!({"type":"object","properties":{"keys":{"type":"array","items":{"type":"string"},"description":"Seed node keys to activate from"}},"required":["keys"]})), json!({"type":"object","properties":{"keys":{"type":"array","items":{"type":"string"},"description":"Seed node keys to activate from"}},"required":["keys"]})),
ToolDef::new("memory_links", ToolDef::new("memory_links",
"Show a node's neighbors with link strengths.", "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))?; store.save().map_err(|e| anyhow::anyhow!("{}", e))?;
Ok(format!("{} '{}'", result, key)) Ok(format!("{} '{}'", result, key))
} }
"memory_search" | "memory_search_content" => { "memory_search" => {
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::<Vec<_>>().join("\n"))
}
}
"memory_spread" => {
let keys: Vec<String> = args.get("keys") let keys: Vec<String> = args.get("keys")
.and_then(|v| v.as_array()) .and_then(|v| v.as_array())
.map(|arr| arr.iter().filter_map(|v| v.as_str().map(String::from)).collect()) .map(|arr| arr.iter().filter_map(|v| v.as_str().map(String::from)).collect())
.unwrap_or_default(); .unwrap_or_default();
if keys.is_empty() { 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 store = Store::load().map_err(|e| anyhow::anyhow!("{}", e))?;
let graph = crate::graph::build_graph_fast(&store); let graph = crate::graph::build_graph_fast(&store);