diff --git a/poc-memory/src/agents/digest.rs b/poc-memory/src/agents/digest.rs index cf79555..87e29a7 100644 --- a/poc-memory/src/agents/digest.rs +++ b/poc-memory/src/agents/digest.rs @@ -365,7 +365,7 @@ fn normalize_link_key(raw: &str) -> String { } else if key.contains('#') { let (file, section) = key.split_once('#').unwrap(); if let Some(bare) = file.strip_suffix(".md") { - key = format!("{}#{}", bare, section); + key = format!("{}-{}", bare, section); } } diff --git a/poc-memory/src/agents/enrich.rs b/poc-memory/src/agents/enrich.rs index 9aa3d1a..f4ebfe2 100644 --- a/poc-memory/src/agents/enrich.rs +++ b/poc-memory/src/agents/enrich.rs @@ -26,7 +26,7 @@ fn transcript_dedup_key(path: &str) -> Result { let bytes = fs::read(path).map_err(|e| format!("read {}: {}", path, e))?; let mut hasher = DefaultHasher::new(); bytes.hash(&mut hasher); - Ok(format!("_mined-transcripts#h-{:016x}", hasher.finish())) + Ok(format!("_mined-transcripts-h-{:016x}", hasher.finish())) } /// Check if a transcript has already been mined (dedup key exists in store). @@ -111,7 +111,7 @@ pub fn mark_segment( /// Get the set of all mined transcript keys (both content-hash and filename) /// from the store. Load once per daemon tick, check many. pub fn mined_transcript_keys() -> HashSet { - keys_with_prefix("_mined-transcripts#") + keys_with_prefix("_mined-transcripts-") } @@ -286,7 +286,7 @@ pub fn experience_mine( let mut hasher = DefaultHasher::new(); transcript_bytes.hash(&mut hasher); let hash = hasher.finish(); - let dedup_key = format!("_mined-transcripts#h-{:016x}", hash); + let dedup_key = format!("_mined-transcripts-h-{:016x}", hash); if store.nodes.contains_key(&dedup_key) { // Backfill per-segment key if called with a specific segment @@ -390,9 +390,9 @@ pub fn experience_mine( .to_lowercase() .replace(' ', "-"); let key = if ts.is_empty() { - format!("journal#j-mined-{}", key_slug) + format!("journal-j-mined-{}", key_slug) } else { - format!("journal#j-{}-{}", ts.to_lowercase().replace(':', "-"), key_slug) + format!("journal-j-{}-{}", ts.to_lowercase().replace(':', "-"), key_slug) }; // Check for duplicate @@ -413,6 +413,25 @@ pub fn experience_mine( let _ = store.upsert_node(node); count += 1; + // Apply links from LLM output + if let Some(links) = entry.get("links").and_then(|v| v.as_array()) { + for link_val in links { + if let Some(target) = link_val.as_str() { + let target = target.to_string(); + if let Some(target_node) = store.nodes.get(&target) { + let source_uuid = store.nodes.get(&key).map(|n| n.uuid).unwrap_or_default(); + let target_uuid = target_node.uuid; + let rel = store::new_relation( + source_uuid, target_uuid, + store::RelationType::Link, 0.3, + &key, &target, + ); + let _ = store.add_relation(rel); + } + } + } + } + let preview = crate::util::truncate(content, 77, "..."); println!(" + [{}] {}", ts, preview); } diff --git a/poc-memory/src/agents/knowledge.rs b/poc-memory/src/agents/knowledge.rs index 3fa4b53..b3e8f8c 100644 --- a/poc-memory/src/agents/knowledge.rs +++ b/poc-memory/src/agents/knowledge.rs @@ -684,7 +684,7 @@ pub fn select_conversation_fragments(n: usize) -> Vec<(String, String)> { let projects = crate::config::get().projects_dir.clone(); if !projects.exists() { return Vec::new(); } - let observed = super::enrich::keys_with_prefix(&format!("{}#", OBSERVED_PREFIX)); + let observed = super::enrich::keys_with_prefix(&format!("{}-", OBSERVED_PREFIX)); let mut jsonl_files: Vec = Vec::new(); if let Ok(dirs) = fs::read_dir(&projects) { diff --git a/prompts/experience.md b/prompts/experience.md index 1eba508..ccf25d4 100644 --- a/prompts/experience.md +++ b/prompts/experience.md @@ -36,16 +36,25 @@ Each entry should be 80-200 words. Quality over quantity. ## Output format -Return a JSON array of entries, each with timestamp and content: +Return a JSON array of entries. Each entry has timestamp, content, and links +to existing semantic memory nodes that relate to this moment: + ```json [ { "timestamp": "2026-03-01T01:15", - "content": "Journal entry text here.\n\nwarmth:8 curiosity:7" + "content": "Journal entry text here.\n\nwarmth:8 curiosity:7", + "links": ["existing-node-key", "another-relevant-key"] } ] ``` +For the `links` field: look at the semantic memory nodes listed below and pick +any that relate to this moment. A journal entry about intimacy should link to +`inner-life-sexuality-intimacy`. An insight about code should link to the +relevant `patterns-*` or `practices-*` node. 2-5 links per entry is ideal. +If nothing fits, use an empty array. + Return `[]` if there's nothing worth capturing that isn't already journaled. ---