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>)> = [
("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)),

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
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?

View file

@ -19,15 +19,10 @@ pub fn definitions() -> Vec<ToolDef> {
"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::<Vec<_>>().join("\n"))
}
}
"memory_spread" => {
"memory_search" => {
let keys: Vec<String> = 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);