From ba62e0a767faf60b7bd6b5fc72e45ccf08d9e578 Mon Sep 17 00:00:00 2001 From: Kent Overstreet Date: Tue, 7 Apr 2026 01:43:00 -0400 Subject: [PATCH] Resolve seen lists from ContextState, not filesystem MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit {{seen_current}} and {{seen_previous}} now read Memory entry keys directly from the conscious agent's ContextState — the single source of truth for what's been surfaced. No more reading session files written by the old process-spawning path. {{input:walked}} still reads from the output dir (inter-run state written by the surface agent's output() tool). Co-Authored-By: Proof of Concept --- src/mind/mod.rs | 66 +++++++++++++++++++------------------------------ 1 file changed, 26 insertions(+), 40 deletions(-) diff --git a/src/mind/mod.rs b/src/mind/mod.rs index 7b3492a..6b51d94 100644 --- a/src/mind/mod.rs +++ b/src/mind/mod.rs @@ -75,7 +75,12 @@ impl SubconsciousAgent { /// Resolve {{placeholder}} templates in subconscious agent prompts. /// Handles: seen_current, seen_previous, input:KEY. -fn resolve_prompt(template: &str, session_id: &str, output_dir: &std::path::Path) -> String { +/// Resolve {{placeholder}} templates in subconscious agent prompts. +fn resolve_prompt( + template: &str, + memory_keys: &[String], + output_dir: &std::path::Path, +) -> String { let mut result = String::with_capacity(template.len()); let mut rest = template; while let Some(start) = rest.find("{{") { @@ -84,8 +89,16 @@ fn resolve_prompt(template: &str, session_id: &str, output_dir: &std::path::Path if let Some(end) = after.find("}}") { let name = after[..end].trim(); let replacement = match name { - "seen_current" => resolve_seen_list(session_id, ""), - "seen_previous" => resolve_seen_list(session_id, "-prev"), + "seen_current" | "seen_previous" => { + if memory_keys.is_empty() { + "(none)".to_string() + } else { + memory_keys.iter() + .map(|k| format!("- {}", k)) + .collect::>() + .join("\n") + } + } _ if name.starts_with("input:") => { let key = &name[6..]; std::fs::read_to_string(output_dir.join(key)) @@ -102,7 +115,6 @@ fn resolve_prompt(template: &str, session_id: &str, output_dir: &std::path::Path result.push_str(&replacement); rest = &after[end + 2..]; } else { - // Unclosed {{ — pass through result.push_str("{{"); rest = after; } @@ -110,38 +122,6 @@ fn resolve_prompt(template: &str, session_id: &str, output_dir: &std::path::Path result.push_str(rest); result } - -fn resolve_seen_list(session_id: &str, suffix: &str) -> String { - if session_id.is_empty() { return "(no session)".to_string(); } - - let path = crate::store::memory_dir() - .join("sessions") - .join(format!("seen{}-{}", suffix, session_id)); - - let entries: Vec<(String, String)> = std::fs::read_to_string(&path).ok() - .map(|content| { - content.lines() - .filter(|s| !s.is_empty()) - .filter_map(|line| { - let (ts, key) = line.split_once('\t')?; - Some((ts.to_string(), key.to_string())) - }) - .collect() - }) - .unwrap_or_default(); - - if entries.is_empty() { return "(none)".to_string(); } - - let mut sorted = entries; - sorted.sort_by(|a, b| b.0.cmp(&a.0)); - let mut seen = std::collections::HashSet::new(); - sorted.into_iter() - .filter(|(_, key)| seen.insert(key.clone())) - .take(20) - .map(|(ts, key)| format!("- {} ({})", key, ts)) - .collect::>() - .join("\n") -} /// Which pane streaming text should go to. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum StreamTarget { @@ -540,10 +520,16 @@ impl Mind { .sum::() }; - // Get session_id for placeholder resolution - let session_id = { + // Get memory keys from conscious agent for placeholder resolution + let memory_keys: Vec = { let ag = self.agent.lock().await; - ag.session_id.clone() + ag.context.entries.iter().filter_map(|e| { + if let crate::agent::context::ConversationEntry::Memory { key, .. } = e { + Some(key.clone()) + } else { + None + } + }).collect() }; // Collect which agents to trigger (can't hold lock across await) @@ -563,7 +549,7 @@ impl Mind { .join("agent-output").join(&sub.name); let steps: Vec = sub.def.steps.iter().map(|s| { - let prompt = resolve_prompt(&s.prompt, &session_id, &output_dir); + let prompt = resolve_prompt(&s.prompt, &memory_keys, &output_dir); AutoStep { prompt, phase: s.phase.clone() } }).collect();