cleanup: simplify MemoryNode, deduplicate tool dispatch

- Removed write/search/mark_used static methods from MemoryNode —
  those are store ops, not MemoryNode concerns
- Removed SearchResult duplicate — use query::engine::SearchResult
- Simplified Link to (String, f32) tuple — inline detection moved
  to render()
- Collapsed tool definitions to one-liners
- Consolidated store-mutation tools into with_store() helper
- Supersede uses store directly instead of MemoryNode round-trip

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
ProofOfConcept 2026-03-25 01:59:13 -04:00
parent 10932cb67e
commit 164a603c8e
2 changed files with 102 additions and 326 deletions

View file

@ -2,7 +2,7 @@
//
// MemoryNode is a lightweight representation of a loaded node:
// key, content, links, version, weight. Used by the agent for
// context tracking and by tools for direct store access.
// context tracking and by the CLI for rendering.
use super::store::Store;
@ -11,24 +11,13 @@ use super::store::Store;
pub struct MemoryNode {
pub key: String,
pub content: String,
pub links: Vec<Link>,
/// Version from the store — used for change detection.
pub links: Vec<(String, f32)>, // (target_key, strength)
pub version: u32,
/// Weight in the graph.
pub weight: f32,
}
/// A link to a neighbor node.
#[derive(Debug, Clone)]
pub struct Link {
pub target: String,
pub strength: f32,
/// Whether this link target is already referenced inline in the content.
pub inline: bool,
}
impl MemoryNode {
/// Load a node from the store by key. Returns None if not found.
/// Load a node from the store by key.
pub fn load(key: &str) -> Option<Self> {
let store = Store::load().ok()?;
Self::from_store(&store, key)
@ -38,7 +27,6 @@ impl MemoryNode {
pub fn from_store(store: &Store, key: &str) -> Option<Self> {
let node = store.nodes.get(key)?;
// Collect neighbor strengths
let mut neighbors: std::collections::HashMap<&str, f32> = std::collections::HashMap::new();
for r in &store.relations {
if r.deleted { continue; }
@ -51,14 +39,10 @@ impl MemoryNode {
}
}
let mut links: Vec<Link> = neighbors.into_iter()
.map(|(target, strength)| Link {
inline: node.content.contains(target),
target: target.to_string(),
strength,
})
let mut links: Vec<(String, f32)> = neighbors.into_iter()
.map(|(k, s)| (k.to_string(), s))
.collect();
links.sort_by(|a, b| b.strength.total_cmp(&a.strength));
links.sort_by(|a, b| b.1.total_cmp(&a.1));
Some(MemoryNode {
key: key.to_string(),
@ -74,16 +58,15 @@ impl MemoryNode {
let mut out = self.content.clone();
// Footer: links not already referenced inline
let footer_links: Vec<&Link> = self.links.iter()
.filter(|l| !l.inline)
let footer: Vec<&(String, f32)> = self.links.iter()
.filter(|(target, _)| !self.content.contains(target.as_str()))
.collect();
if !footer_links.is_empty() {
let total = footer_links.len();
if !footer.is_empty() {
let total = footer.len();
out.push_str("\n\n---\nLinks:");
for link in footer_links.iter().take(15) {
out.push_str(&format!("\n ({:.2}) `poc-memory render {}`",
link.strength, link.target));
for (target, strength) in footer.iter().take(15) {
out.push_str(&format!("\n ({:.2}) `poc-memory render {}`", strength, target));
}
if total > 15 {
out.push_str(&format!("\n ... and {} more (`poc-memory graph link {}`)",
@ -92,53 +75,4 @@ impl MemoryNode {
}
out
}
/// Write content to the store and return an updated MemoryNode.
pub fn write(key: &str, content: &str, provenance: Option<&str>) -> Result<Self, String> {
let prov = provenance.unwrap_or("manual");
let mut store = Store::load()?;
store.upsert_provenance(key, content, prov)?;
store.save()?;
Self::from_store(&store, key)
.ok_or_else(|| format!("wrote {} but failed to load back", key))
}
/// Search for nodes matching a query. Returns lightweight results.
pub fn search(query: &str) -> Result<Vec<SearchResult>, String> {
let store = Store::load()?;
let results = super::query::engine::search(query, &store);
Ok(results.into_iter().take(20).map(|hit| SearchResult {
key: hit.key.clone(),
score: hit.activation as f32,
snippet: hit.snippet.unwrap_or_default(),
}).collect())
}
/// Mark a node as used (boosts weight).
pub fn mark_used(key: &str) -> Result<String, String> {
let mut store = Store::load()?;
if !store.nodes.contains_key(key) {
return Err(format!("node not found: {}", key));
}
store.mark_used(key);
store.save()?;
Ok(format!("marked {} as used", key))
}
}
/// A search result — lightweight, not a full node load.
#[derive(Debug, Clone)]
pub struct SearchResult {
pub key: String,
pub score: f32,
pub snippet: String,
}
impl SearchResult {
/// Format for display.
pub fn render(&self) -> String {
format!("({:.2}) {}{}", self.score, self.key, self.snippet)
}
}