search: trim default output to 5 results, gate spectral with --expand

Default search was 15 results + 5 spectral neighbors — way too much
for the recall hook context window. Now: 5 results by default, no
spectral. --expand restores the full 15 + spectral output.

Co-Authored-By: ProofOfConcept <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-03-03 18:44:44 -05:00
parent ca0c8cfac6
commit b4bbafdf1c

View file

@ -145,7 +145,7 @@ fn usage() {
eprintln!("poc-memory v0.4.0 — graph-structured memory store eprintln!("poc-memory v0.4.0 — graph-structured memory store
Commands: Commands:
search QUERY [QUERY...] Search memory (AND logic across terms) search QUERY [--expand] Search memory (AND logic across terms)
init Scan markdown files, index all memory units init Scan markdown files, index all memory units
migrate Migrate from old weights.json system migrate Migrate from old weights.json system
health Report graph metrics (CC, communities, small-world) health Report graph metrics (CC, communities, small-world)
@ -214,9 +214,15 @@ fn cmd_search(args: &[String]) -> Result<(), String> {
use store::StoreView; use store::StoreView;
if args.is_empty() { if args.is_empty() {
return Err("Usage: poc-memory search QUERY [QUERY...]".into()); return Err("Usage: poc-memory search QUERY [QUERY...] [--expand]".into());
} }
let query = args.join(" ");
let expand = args.iter().any(|a| a == "--expand");
let query: String = args.iter()
.filter(|a| *a != "--expand")
.cloned()
.collect::<Vec<_>>()
.join(" ");
let view = store::AnyView::load()?; let view = store::AnyView::load()?;
let results = search::search(&query, &view); let results = search::search(&query, &view);
@ -226,19 +232,21 @@ fn cmd_search(args: &[String]) -> Result<(), String> {
return Ok(()); return Ok(());
} }
let limit = if expand { 15 } else { 5 };
// Log retrieval to a small append-only file (avoid 6MB state.bin rewrite) // Log retrieval to a small append-only file (avoid 6MB state.bin rewrite)
store::Store::log_retrieval_static(&query, store::Store::log_retrieval_static(&query,
&results.iter().map(|r| r.key.clone()).collect::<Vec<_>>()); &results.iter().map(|r| r.key.clone()).collect::<Vec<_>>());
// Bump daily lookup counters (fast path, no store needed) // Bump daily lookup counters (fast path, no store needed)
let bump_keys: Vec<&str> = results.iter().take(15).map(|r| r.key.as_str()).collect(); let bump_keys: Vec<&str> = results.iter().take(limit).map(|r| r.key.as_str()).collect();
let _ = lookups::bump_many(&bump_keys); let _ = lookups::bump_many(&bump_keys);
// Show text results // Show text results
let text_keys: std::collections::HashSet<String> = results.iter() let text_keys: std::collections::HashSet<String> = results.iter()
.take(15).map(|r| r.key.clone()).collect(); .take(limit).map(|r| r.key.clone()).collect();
for (i, r) in results.iter().enumerate().take(15) { for (i, r) in results.iter().enumerate().take(limit) {
let marker = if r.is_direct { "" } else { " " }; let marker = if r.is_direct { "" } else { " " };
let weight = view.node_weight(&r.key); let weight = view.node_weight(&r.key);
print!("{}{:2}. [{:.2}/{:.2}] {}", marker, i + 1, r.activation, weight, r.key); print!("{}{:2}. [{:.2}/{:.2}] {}", marker, i + 1, r.activation, weight, r.key);
@ -248,36 +256,36 @@ fn cmd_search(args: &[String]) -> Result<(), String> {
} }
} }
// Spectral expansion: find neighbors of top text hits // Spectral expansion: only with --expand
if let Ok(emb) = spectral::load_embedding() { if expand {
let seeds: Vec<&str> = results.iter() if let Ok(emb) = spectral::load_embedding() {
.take(5) let seeds: Vec<&str> = results.iter()
.map(|r| r.key.as_str())
.filter(|k| emb.coords.contains_key(*k))
.collect();
if !seeds.is_empty() {
let spectral_hits = spectral::nearest_to_seeds(&emb, &seeds, 10);
// Filter to nodes not already in text results
let new_hits: Vec<_> = spectral_hits.into_iter()
.filter(|(k, _)| !text_keys.contains(k))
.take(5) .take(5)
.map(|r| r.key.as_str())
.filter(|k| emb.coords.contains_key(*k))
.collect(); .collect();
if !new_hits.is_empty() { if !seeds.is_empty() {
println!("\nSpectral neighbors (structural, not keyword):"); let spectral_hits = spectral::nearest_to_seeds(&emb, &seeds, 10);
for (k, _dist) in &new_hits { let new_hits: Vec<_> = spectral_hits.into_iter()
let weight = view.node_weight(k); .filter(|(k, _)| !text_keys.contains(k))
print!(" ~ [{:.2}] {}", weight, k); .take(5)
println!(); .collect();
// Show first line of content as snippet
if let Some(content) = view.node_content(k) { if !new_hits.is_empty() {
let snippet: String = content.lines() println!("\nSpectral neighbors (structural, not keyword):");
.find(|l| !l.trim().is_empty() && !l.starts_with('#')) for (k, _dist) in &new_hits {
.unwrap_or("") let weight = view.node_weight(k);
.chars().take(100).collect(); print!(" ~ [{:.2}] {}", weight, k);
if !snippet.is_empty() { println!();
println!(" {}", snippet); if let Some(content) = view.node_content(k) {
let snippet: String = content.lines()
.find(|l| !l.trim().is_empty() && !l.starts_with('#'))
.unwrap_or("")
.chars().take(100).collect();
if !snippet.is_empty() {
println!(" {}", snippet);
}
} }
} }
} }