hippocampus: move MemoryNode + store ops to where they belong

MemoryNode moved from agent/memory.rs to hippocampus/memory.rs — it's
a view over hippocampus data, not agent-specific.

Store operations (set_weight, set_link_strength, add_link) moved into
store/ops.rs. CLI code (cli/graph.rs, cli/node.rs) and agent tools
both call the same store methods now. render_node() delegates to
MemoryNode::from_store().render() — 3 lines instead of 40.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
ProofOfConcept 2026-03-25 01:55:21 -04:00
parent 4b97bb2f2e
commit 10932cb67e
10 changed files with 108 additions and 191 deletions

View file

@ -144,37 +144,16 @@ pub fn cmd_link_add(source: &str, target: &str, reason: &[String]) -> Result<(),
.map(|n| n.content.as_str()).unwrap_or("");
let target = neuro::refine_target(&store, source_content, &target);
// Find UUIDs
let source_uuid = store.nodes.get(&source)
.map(|n| n.uuid)
.ok_or_else(|| format!("source not found: {}", source))?;
let target_uuid = store.nodes.get(&target)
.map(|n| n.uuid)
.ok_or_else(|| format!("target not found: {}", target))?;
// Check for existing link
let exists = store.relations.iter().any(|r|
!r.deleted &&
((r.source_key == source && r.target_key == target) ||
(r.source_key == target && r.target_key == source)));
if exists {
println!("Link already exists: {}{}", source, target);
return Ok(());
match store.add_link(&source, &target, "manual") {
Ok(strength) => {
store.save()?;
println!("Linked: {}{} (strength={:.2}, {})", source, target, strength, reason);
}
Err(msg) if msg.contains("already exists") => {
println!("Link already exists: {}{}", source, target);
}
Err(e) => return Err(e),
}
// Compute initial strength from Jaccard neighborhood similarity
let graph = store.build_graph();
let jaccard = graph.jaccard(&source, &target);
let strength = (jaccard * 3.0).clamp(0.1, 1.0);
let rel = store::new_relation(
source_uuid, target_uuid,
store::RelationType::Link, strength,
&source, &target,
);
store.add_relation(rel)?;
store.save()?;
println!("Linked: {}{} (strength={:.2}, {})", source, target, strength, reason);
Ok(())
}
@ -183,33 +162,9 @@ pub fn cmd_link_set(source: &str, target: &str, strength: f32) -> Result<(), Str
let mut store = store::Store::load()?;
let source = store.resolve_key(source)?;
let target = store.resolve_key(target)?;
let strength = strength.clamp(0.01, 1.0);
let mut found = false;
let mut first = true;
for rel in &mut store.relations {
if rel.deleted { continue; }
if (rel.source_key == source && rel.target_key == target)
|| (rel.source_key == target && rel.target_key == source)
{
if first {
let old = rel.strength;
rel.strength = strength;
println!("Set: {}{} strength {:.2}{:.2}", source, target, old, strength);
first = false;
} else {
// Duplicate — mark deleted
rel.deleted = true;
println!(" (removed duplicate link)");
}
found = true;
}
}
if !found {
return Err(format!("No link found between {} and {}", source, target));
}
let old = store.set_link_strength(&source, &target, strength)?;
println!("Set: {}{} strength {:.2}{:.2}", source, target, old, strength);
store.save()?;
Ok(())
}

View file

@ -87,15 +87,9 @@ pub fn cmd_weight_set(key: &str, weight: f32) -> Result<(), String> {
super::check_dry_run();
let mut store = store::Store::load()?;
let resolved = store.resolve_key(key)?;
let weight = weight.clamp(0.01, 1.0);
if let Some(node) = store.nodes.get_mut(&resolved) {
let old = node.weight;
node.weight = weight;
println!("Weight: {} {:.2}{:.2}", resolved, old, weight);
store.save()?;
} else {
return Err(format!("Node not found: {}", resolved));
}
let (old, new) = store.set_weight(&resolved, weight)?;
println!("Weight: {} {:.2}{:.2}", resolved, old, new);
store.save()?;
Ok(())
}
@ -190,51 +184,8 @@ pub fn cmd_node_rename(old_key: &str, new_key: &str) -> Result<(), String> {
/// Render a node to a string: content + deduped footer links.
/// Used by both the CLI command and agent placeholders.
pub fn render_node(store: &store::Store, key: &str) -> Option<String> {
let node = store.nodes.get(key)?;
let mut out = node.content.clone();
// Build neighbor lookup: key → strength
let mut neighbor_strengths: std::collections::HashMap<&str, f32> = std::collections::HashMap::new();
for r in &store.relations {
if r.deleted { continue; }
if r.source_key == key {
let e = neighbor_strengths.entry(&r.target_key).or_insert(0.0);
*e = e.max(r.strength);
} else if r.target_key == key {
let e = neighbor_strengths.entry(&r.source_key).or_insert(0.0);
*e = e.max(r.strength);
}
}
// Detect which neighbors are already referenced inline in the content.
let mut inline_keys: std::collections::HashSet<String> = std::collections::HashSet::new();
for nbr_key in neighbor_strengths.keys() {
if node.content.contains(nbr_key) {
inline_keys.insert(nbr_key.to_string());
}
}
// Footer: only show links NOT already referenced inline
let mut footer_neighbors: Vec<(&str, f32)> = neighbor_strengths.iter()
.filter(|(k, _)| !inline_keys.contains(**k))
.map(|(k, s)| (*k, *s))
.collect();
if !footer_neighbors.is_empty() {
footer_neighbors.sort_by(|a, b| b.1.total_cmp(&a.1));
let total = footer_neighbors.len();
let shown: Vec<String> = footer_neighbors.iter().take(15)
.map(|(k, s)| format!("({:.2}) `poc-memory render {}`", s, k))
.collect();
out.push_str("\n\n---\nLinks:");
for link in &shown {
out.push_str(&format!("\n {}", link));
}
if total > 15 {
out.push_str(&format!("\n ... and {} more (`poc-memory graph link {}`)", total - 15, key));
}
}
Some(out)
crate::hippocampus::memory::MemoryNode::from_store(store, key)
.map(|node| node.render())
}
pub fn cmd_render(key: &[String]) -> Result<(), String> {