From 7d49f29fde3da1ccdd57d43273020a89c4c5d568 Mon Sep 17 00:00:00 2001 From: Kent Overstreet Date: Mon, 13 Apr 2026 18:50:21 -0400 Subject: [PATCH] store: remove dead code and move params to config Remove: - score_weight() - never called - position field on Node - never read (was for export) - Provenance enum - inline helper for capnp migration - migrate_transcript_progress + CLI command - init_from_markdown, import_file, ingest_units - export command and export_to_markdown - RetrievalEvent, GapRecord types - classify_filename, new_transcript_segment Move spreading activation params to Config: - default_node_weight, edge_decay, max_hops, min_activation - Remove Params struct and StoreView::params() Simplify cmd_init to just seed identity via upsert(). Simplify cmd_import to use parse_units + upsert directly. -576 lines Co-Authored-By: Proof of Concept --- src/cli/admin.rs | 73 +++------ src/config.rs | 18 +++ src/hippocampus/query/engine.rs | 8 +- src/hippocampus/store/mod.rs | 270 +------------------------------ src/hippocampus/store/ops.rs | 16 -- src/hippocampus/store/parse.rs | 11 -- src/hippocampus/store/persist.rs | 62 ------- src/hippocampus/store/types.rs | 158 +++--------------- src/hippocampus/store/view.rs | 10 +- src/main.rs | 19 --- 10 files changed, 69 insertions(+), 576 deletions(-) diff --git a/src/cli/admin.rs b/src/cli/admin.rs index 3a7bbe5..6a7af53 100644 --- a/src/cli/admin.rs +++ b/src/cli/admin.rs @@ -1,6 +1,6 @@ // cli/admin.rs — admin subcommand handlers -use anyhow::{Context, Result}; +use anyhow::Result; use crate::hippocampus as memory; use crate::hippocampus::store; @@ -25,19 +25,16 @@ pub async fn cmd_init() -> Result<()> { install_default_file(&cfg.data_dir, "on-consciousness.md", include_str!("../../defaults/on-consciousness.md"))?; - // Initialize store and seed default identity node if empty + // Seed identity node if empty let arc = memory::access_local()?; let mut store = arc.lock().await; - let count = store.init_from_markdown().map_err(|e| anyhow::anyhow!("{}", e))?; - for key in &cfg.core_nodes { - if !store.nodes.contains_key(key) && key == "identity" { - let default = include_str!("../../defaults/identity.md"); - store.upsert(key, default).map_err(|e| anyhow::anyhow!("{}", e))?; - println!("Seeded {} in store", key); - } + if !store.nodes.contains_key("identity") { + let default = include_str!("../../defaults/identity.md"); + store.upsert("identity", default)?; + println!("Seeded identity in store"); } - store.save().map_err(|e| anyhow::anyhow!("{}", e))?; - println!("Indexed {} memory units", count); + store.save()?; + println!("Initialized with {} nodes", store.nodes.len()); // Create config if none exists let config_path = std::env::var("POC_MEMORY_CONFIG") @@ -325,8 +322,7 @@ pub async fn cmd_import(files: &[String]) -> Result<()> { let arc = memory::access_local()?; let mut store = arc.lock().await; - let mut total_new = 0; - let mut total_updated = 0; + let mut count = 0; for arg in files { let path = std::path::PathBuf::from(arg); @@ -340,52 +336,21 @@ pub async fn cmd_import(files: &[String]) -> Result<()> { } mem_path }; - let (n, u) = store.import_file(&resolved)?; - total_new += n; - total_updated += u; - } - if total_new > 0 || total_updated > 0 { - store.save()?; - } - println!("Import: {} new, {} updated", total_new, total_updated); - Ok(()) -} + let filename = resolved.file_name().unwrap().to_string_lossy().to_string(); + let content = std::fs::read_to_string(&resolved)?; + let units = store::parse_units(&filename, &content); -pub async fn cmd_export(files: &[String], export_all: bool) -> Result<()> { - let arc = memory::access_local()?; - let store = arc.lock().await; - - let targets: Vec = if export_all { - let mut files: Vec = store.nodes.keys() - .filter(|k| !k.contains('#')) - .cloned() - .collect(); - files.sort(); - files - } else if files.is_empty() { - anyhow::bail!("export requires file keys or --all"); - } else { - files.iter().map(|a| { - a.strip_suffix(".md").unwrap_or(a).to_string() - }).collect() - }; - - let mem_dir = store::memory_dir(); - - for file_key in &targets { - match store.export_to_markdown(file_key) { - Some(content) => { - let out_path = mem_dir.join(format!("{}.md", file_key)); - std::fs::write(&out_path, &content) - .with_context(|| format!("write {}", out_path.display()))?; - let section_count = content.matches("\n", marker_parts.join(" "))); - } - output.push_str(&node.content); - if !node.content.ends_with('\n') { - output.push('\n'); - } - output.push('\n'); - } - - Some(output.trim_end().to_string()) - } - - /// Find the episodic node that best matches the given entry text. - pub fn find_journal_node(&self, entry_text: &str) -> Option { - if entry_text.is_empty() { - return None; - } - - let words: Vec<&str> = entry_text.split_whitespace() - .filter(|w| w.len() > 5) - .take(5) - .collect(); - - let mut best_key = None; - let mut best_score = 0; - - for (key, node) in &self.nodes { - if node.node_type != NodeType::EpisodicSession { - continue; - } - let content_lower = node.content.to_lowercase(); - let score: usize = words.iter() - .filter(|w| content_lower.contains(&w.to_lowercase())) - .count(); - if score > best_score { - best_score = score; - best_key = Some(key.clone()); - } - } - - best_key - } } diff --git a/src/hippocampus/store/ops.rs b/src/hippocampus/store/ops.rs index 6fe212f..b60524e 100644 --- a/src/hippocampus/store/ops.rs +++ b/src/hippocampus/store/ops.rs @@ -268,22 +268,6 @@ impl Store { Ok((old, weight)) } - /// Update a node's weight with a new score and record the scoring - /// timestamp. Uses asymmetric smoothing: responds quickly to high - /// scores (alpha=0.5) but decays slowly on low scores (alpha=0.1). - /// This keeps memories surfaced even if they're only useful 1 in 4 times. - /// Returns (old_weight, new_weight). - pub fn score_weight(&mut self, key: &str, score: f64) -> Result<(f32, f32)> { - let node = self.nodes.get_mut(key) - .ok_or_else(|| anyhow!("node not found: {}", key))?; - let old = node.weight; - let alpha = if score > old as f64 { 0.5 } else { 0.1 }; - let new = (alpha * score + (1.0 - alpha) * old as f64) as f32; - node.weight = new.clamp(0.01, 1.0); - node.last_scored = chrono::Utc::now().timestamp(); - Ok((old, node.weight)) - } - /// Set the strength of a link between two nodes. Deduplicates if /// multiple links exist. Returns the old strength, or error if no link. pub fn set_link_strength(&mut self, source: &str, target: &str, strength: f32) -> Result { diff --git a/src/hippocampus/store/parse.rs b/src/hippocampus/store/parse.rs index b172a57..0436bb8 100644 --- a/src/hippocampus/store/parse.rs +++ b/src/hippocampus/store/parse.rs @@ -5,8 +5,6 @@ // becomes the file-level unit. Links and causal edges are extracted from // both marker attributes and inline markdown links. -use super::NodeType; - use regex::Regex; use std::collections::HashMap; @@ -23,15 +21,6 @@ pub struct MemoryUnit { pub source_ref: Option, } -pub(super) fn classify_filename(filename: &str) -> NodeType { - let bare = filename.strip_suffix(".md").unwrap_or(filename); - if bare.starts_with("daily-") { NodeType::EpisodicDaily } - else if bare.starts_with("weekly-") { NodeType::EpisodicWeekly } - else if bare.starts_with("monthly-") { NodeType::EpisodicMonthly } - else if bare == "journal" { NodeType::EpisodicSession } - else { NodeType::Semantic } -} - pub fn parse_units(raw_filename: &str, content: &str) -> Vec { let filename = raw_filename.strip_suffix(".md").unwrap_or(raw_filename); static MARKER_RE: OnceLock = OnceLock::new(); diff --git a/src/hippocampus/store/persist.rs b/src/hippocampus/store/persist.rs index 801b615..626f07c 100644 --- a/src/hippocampus/store/persist.rs +++ b/src/hippocampus/store/persist.rs @@ -478,68 +478,6 @@ impl Store { Ok(()) } - /// Migrate old stub-node transcript markers into the new progress log. - /// Reads _observed-transcripts-f-*, _mined-transcripts#f-*, and _facts-* keys, - /// extracts transcript_id and segment_index, writes to transcript-progress.capnp, - /// then deletes the stub nodes. - pub fn migrate_transcript_progress(&mut self) -> Result { - let mut segments = Vec::new(); - - for key in self.nodes.keys() { - // _observed-transcripts-f-{UUID}.{segment} - if let Some(rest) = key.strip_prefix("_observed-transcripts-f-") { - if let Some((uuid, seg_str)) = rest.rsplit_once('.') - && let Ok(seg) = seg_str.parse::() { - segments.push(new_transcript_segment(uuid, seg, "observation")); - } - } - // _mined-transcripts#f-{UUID}.{segment} - else if let Some(rest) = key.strip_prefix("_mined-transcripts#f-") { - if let Some((uuid, seg_str)) = rest.rsplit_once('.') - && let Ok(seg) = seg_str.parse::() { - segments.push(new_transcript_segment(uuid, seg, "experience")); - } - } - // _mined-transcripts-f-{UUID}.{segment} - else if let Some(rest) = key.strip_prefix("_mined-transcripts-f-") { - if let Some((uuid, seg_str)) = rest.rsplit_once('.') - && let Ok(seg) = seg_str.parse::() { - segments.push(new_transcript_segment(uuid, seg, "experience")); - } - } - // _facts-{UUID} (whole-file, segment 0) - else if let Some(uuid) = key.strip_prefix("_facts-") { - if !uuid.contains('-') || uuid.len() < 30 { continue; } // skip non-UUID - segments.push(new_transcript_segment(uuid, 0, "fact")); - } - } - - let count = segments.len(); - if count > 0 { - self.append_transcript_progress(&segments)?; - } - - // Soft-delete the old stub nodes - let keys_to_delete: Vec = self.nodes.keys() - .filter(|k| k.starts_with("_observed-transcripts-") - || k.starts_with("_mined-transcripts") - || (k.starts_with("_facts-") && !k.contains("fact_mine"))) - .cloned() - .collect(); - - for key in &keys_to_delete { - if let Some(node) = self.nodes.get_mut(key) { - node.deleted = true; - } - } - - if !keys_to_delete.is_empty() { - self.save()?; - } - - Ok(count) - } - /// Record visits for a batch of node keys from a successful agent run. pub fn record_agent_visits(&mut self, node_keys: &[String], agent: &str) -> Result<()> { let visits: Vec = node_keys.iter() diff --git a/src/hippocampus/store/types.rs b/src/hippocampus/store/types.rs index 8e13f66..3e9a135 100644 --- a/src/hippocampus/store/types.rs +++ b/src/hippocampus/store/types.rs @@ -204,10 +204,6 @@ pub struct Node { pub last_replayed: i64, pub spaced_repetition_interval: u32, - // Position within file (section index, for export ordering) - #[serde(default)] - pub position: u32, - // Stable creation timestamp (unix epoch seconds). Set once at creation; // never updated on rename or content update. Zero for legacy nodes. #[serde(default)] @@ -250,70 +246,6 @@ pub enum NodeType { EpisodicMonthly, } -#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] -pub enum Provenance { - Manual, - Journal, - Agent, // legacy catch-all, prefer specific variants below - Dream, - Derived, - AgentExperienceMine, - AgentKnowledgeObservation, - AgentKnowledgePattern, - AgentKnowledgeConnector, - AgentKnowledgeChallenger, - AgentConsolidate, - AgentDigest, - AgentFactMine, - AgentDecay, -} - -impl Provenance { - /// Parse from POC_PROVENANCE env var. Returns None if unset. - pub fn from_env() -> Option { - std::env::var("POC_PROVENANCE").ok().and_then(|s| Self::from_label(&s)) - } - - pub fn from_label(s: &str) -> Option { - Some(match s { - "manual" => Self::Manual, - "journal" => Self::Journal, - "agent" => Self::Agent, - "dream" => Self::Dream, - "derived" => Self::Derived, - "agent:experience-mine" => Self::AgentExperienceMine, - "agent:knowledge-observation"=> Self::AgentKnowledgeObservation, - "agent:knowledge-pattern" => Self::AgentKnowledgePattern, - "agent:knowledge-connector" => Self::AgentKnowledgeConnector, - "agent:knowledge-challenger" => Self::AgentKnowledgeChallenger, - "agent:consolidate" => Self::AgentConsolidate, - "agent:digest" => Self::AgentDigest, - "agent:fact-mine" => Self::AgentFactMine, - "agent:decay" => Self::AgentDecay, - _ => return None, - }) - } - - pub fn label(&self) -> &'static str { - match self { - Self::Manual => "manual", - Self::Journal => "journal", - Self::Agent => "agent", - Self::Dream => "dream", - Self::Derived => "derived", - Self::AgentExperienceMine => "agent:experience-mine", - Self::AgentKnowledgeObservation => "agent:knowledge-observation", - Self::AgentKnowledgePattern => "agent:knowledge-pattern", - Self::AgentKnowledgeConnector => "agent:knowledge-connector", - Self::AgentKnowledgeChallenger => "agent:knowledge-challenger", - Self::AgentConsolidate => "agent:consolidate", - Self::AgentDigest => "agent:digest", - Self::AgentFactMine => "agent:fact-mine", - Self::AgentDecay => "agent:decay", - } - } -} - #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] pub enum RelationType { Link, @@ -324,13 +256,6 @@ pub enum RelationType { capnp_enum!(NodeType, memory_capnp::NodeType, [EpisodicSession, EpisodicDaily, EpisodicWeekly, Semantic, EpisodicMonthly]); -capnp_enum!(Provenance, memory_capnp::Provenance, - [Manual, Journal, Agent, Dream, Derived, - AgentExperienceMine, AgentKnowledgeObservation, AgentKnowledgePattern, - AgentKnowledgeConnector, AgentKnowledgeChallenger, AgentConsolidate, - AgentDigest, AgentFactMine, AgentDecay]); - - capnp_enum!(RelationType, memory_capnp::RelationType, [Link, Causal, Auto]); @@ -341,11 +266,32 @@ capnp_message!(Node, uuid: [uuid], prim: [version, timestamp, weight, emotion, deleted, retrievals, uses, wrongs, last_replayed, - spaced_repetition_interval, position, created_at, last_scored], + spaced_repetition_interval, created_at, last_scored], enm: [node_type: NodeType], skip: [community_id, clustering_coefficient, degree], ); +/// Convert legacy capnp provenance enum to string label. +fn legacy_provenance_label(p: memory_capnp::Provenance) -> &'static str { + use memory_capnp::Provenance::*; + match p { + Manual => "manual", + Journal => "journal", + Agent => "agent", + Dream => "dream", + Derived => "derived", + AgentExperienceMine => "agent:experience-mine", + AgentKnowledgeObservation => "agent:knowledge-observation", + AgentKnowledgePattern => "agent:knowledge-pattern", + AgentKnowledgeConnector => "agent:knowledge-connector", + AgentKnowledgeChallenger => "agent:knowledge-challenger", + AgentConsolidate => "agent:consolidate", + AgentDigest => "agent:digest", + AgentFactMine => "agent:fact-mine", + AgentDecay => "agent:decay", + } +} + impl Node { /// Read from capnp with migration: if the new provenance text field /// is empty (old record), fall back to the deprecated provenanceOld enum. @@ -353,7 +299,7 @@ impl Node { let mut node = Self::from_capnp(r)?; if node.provenance.is_empty() && let Ok(old) = r.get_provenance_old() { - node.provenance = Provenance::from_capnp(old).label().to_string(); + node.provenance = legacy_provenance_label(old).to_string(); } // Sanitize timestamps: old capnp records have raw offsets instead // of unix epoch. Anything past year 2100 (~4102444800) is bogus. @@ -383,52 +329,12 @@ impl Relation { let mut rel = Self::from_capnp(r)?; if rel.provenance.is_empty() && let Ok(old) = r.get_provenance_old() { - rel.provenance = Provenance::from_capnp(old).label().to_string(); + rel.provenance = legacy_provenance_label(old).to_string(); } Ok(rel) } } -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RetrievalEvent { - pub query: String, - pub timestamp: String, - pub results: Vec, - pub used: Option>, -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize)] -pub struct Params { - pub default_weight: f64, - pub decay_factor: f64, - pub use_boost: f64, - pub prune_threshold: f64, - pub edge_decay: f64, - pub max_hops: u32, - pub min_activation: f64, -} - -impl Default for Params { - fn default() -> Self { - Params { - default_weight: 0.7, - decay_factor: 0.95, - use_boost: 0.15, - prune_threshold: 0.1, - edge_decay: 0.3, - max_hops: 3, - min_activation: 0.05, - } - } -} - -// Gap record — something we looked for but didn't find -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct GapRecord { - pub description: String, - pub timestamp: String, -} - /// Per-node agent visit index: node_key → (agent_type → last_visit_timestamp) pub(super) type VisitIndex = HashMap>; @@ -437,9 +343,6 @@ pub struct Store { pub nodes: HashMap, // key → latest node pub uuid_to_key: HashMap<[u8; 16], String>, // uuid → key (rebuilt from nodes) pub relations: Vec, // all active relations - pub retrieval_log: Vec, - pub gaps: Vec, - pub params: Params, /// Agent visit tracking: node_key → (agent_type → last_visit_epoch) pub visits: VisitIndex, /// Transcript mining progress: (transcript_id, segment_index) → set of agents that processed it @@ -457,9 +360,6 @@ impl Default for Store { nodes: HashMap::new(), uuid_to_key: HashMap::new(), relations: Vec::new(), - retrieval_log: Vec::new(), - gaps: Vec::new(), - params: Params::default(), visits: HashMap::new(), transcript_progress: HashMap::new(), loaded_nodes_size: 0, @@ -510,7 +410,6 @@ pub fn new_node(key: &str, content: &str) -> Node { state_tag: String::new(), last_replayed: 0, spaced_repetition_interval: 1, - position: 0, created_at: now_epoch(), last_scored: 0, community_id: None, @@ -570,15 +469,6 @@ capnp_message!(TranscriptSegment, skip: [], ); -pub(super) fn new_transcript_segment(transcript_id: &str, segment_index: u32, agent: &str) -> TranscriptSegment { - TranscriptSegment { - transcript_id: transcript_id.to_string(), - segment_index, - agent: agent.to_string(), - timestamp: now_epoch(), - } -} - pub(crate) fn transcript_progress_path() -> PathBuf { memory_dir().join("transcript-progress.capnp") } /// Create a new relation. diff --git a/src/hippocampus/store/view.rs b/src/hippocampus/store/view.rs index 738c078..d51a389 100644 --- a/src/hippocampus/store/view.rs +++ b/src/hippocampus/store/view.rs @@ -21,9 +21,6 @@ pub trait StoreView { /// Node content by key. fn node_content(&self, key: &str) -> Option<&str>; - - /// Search/graph parameters. - fn params(&self) -> Params; } impl StoreView for Store { @@ -47,14 +44,11 @@ impl StoreView for Store { } fn node_weight(&self, key: &str) -> f64 { - self.nodes.get(key).map(|n| n.weight as f64).unwrap_or(self.params.default_weight) + let cfg = crate::config::get(); + self.nodes.get(key).map(|n| n.weight as f64).unwrap_or(cfg.default_node_weight) } fn node_content(&self, key: &str) -> Option<&str> { self.nodes.get(key).map(|n| n.content.as_str()) } - - fn params(&self) -> Params { - self.params - } } diff --git a/src/main.rs b/src/main.rs index da066ac..3bd68ab 100644 --- a/src/main.rs +++ b/src/main.rs @@ -323,14 +323,6 @@ enum AdminCmd { /// File paths files: Vec, }, - /// Export store nodes to markdown file(s) - Export { - /// File keys to export (or --all) - files: Vec, - /// Export all file-level nodes - #[arg(long)] - all: bool, - }, /// Output session-start context from the store #[command(name = "load-context")] LoadContext { @@ -338,9 +330,6 @@ enum AdminCmd { #[arg(long)] stats: bool, }, - /// Migrate transcript stub nodes to progress log - #[command(name = "migrate-transcript-progress")] - MigrateTranscriptProgress, } /// Print help with subcommands expanded to show nested commands. @@ -464,15 +453,7 @@ impl Run for AdminCmd { Self::Dedup { apply } => cli::admin::cmd_dedup(apply).await, Self::DailyCheck => cli::admin::cmd_daily_check().await, Self::Import { files } => cli::admin::cmd_import(&files).await, - Self::Export { files, all } => cli::admin::cmd_export(&files, all).await, Self::LoadContext { stats } => cli::node::cmd_load_context(stats).await, - Self::MigrateTranscriptProgress => { - let arc = hippocampus::access_local()?; - let mut store = arc.lock().await; - let count = store.migrate_transcript_progress()?; - println!("Migrated {} transcript segment markers", count); - Ok(()) - } } } }