diff --git a/src/mind/mod.rs b/src/mind/mod.rs index fb6fef1..7b3492a 100644 --- a/src/mind/mod.rs +++ b/src/mind/mod.rs @@ -72,6 +72,76 @@ impl SubconsciousAgent { conversation_bytes.saturating_sub(self.last_trigger_bytes) >= interval } } + +/// 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 { + let mut result = String::with_capacity(template.len()); + let mut rest = template; + while let Some(start) = rest.find("{{") { + result.push_str(&rest[..start]); + let after = &rest[start + 2..]; + 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"), + _ if name.starts_with("input:") => { + let key = &name[6..]; + std::fs::read_to_string(output_dir.join(key)) + .unwrap_or_default() + } + _ => { + // Unknown placeholder — leave as-is + result.push_str("{{"); + result.push_str(&after[..end + 2]); + rest = &after[end + 2..]; + continue; + } + }; + result.push_str(&replacement); + rest = &after[end + 2..]; + } else { + // Unclosed {{ — pass through + result.push_str("{{"); + rest = after; + } + } + 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 { @@ -470,6 +540,12 @@ impl Mind { .sum::() }; + // Get session_id for placeholder resolution + let session_id = { + let ag = self.agent.lock().await; + ag.session_id.clone() + }; + // Collect which agents to trigger (can't hold lock across await) let to_trigger: Vec<(usize, Vec, Vec, String, i32)> = { let mut subs = self.subconscious.lock().await; @@ -481,9 +557,14 @@ impl Mind { let sub = &mut subs[i]; sub.last_trigger_bytes = conversation_bytes; + // The output dir for this agent — used for input: placeholders + // and the output() tool at runtime + let output_dir = crate::store::memory_dir() + .join("agent-output").join(&sub.name); + let steps: Vec = sub.def.steps.iter().map(|s| { - // TODO: resolve remaining placeholders (seen_current, input:walked, etc.) - AutoStep { prompt: s.prompt.clone(), phase: s.phase.clone() } + let prompt = resolve_prompt(&s.prompt, &session_id, &output_dir); + AutoStep { prompt, phase: s.phase.clone() } }).collect(); let all_tools = crate::agent::tools::memory_and_journal_tools(); diff --git a/src/subconscious/agents/subconscious-surface-observe.agent b/src/subconscious/agents/subconscious-surface-observe.agent index 50debec..abd57a0 100644 --- a/src/subconscious/agents/subconscious-surface-observe.agent +++ b/src/subconscious/agents/subconscious-surface-observe.agent @@ -57,11 +57,8 @@ and analysis on the search — how useful was it, do memories need reorganizing? Decide which memories, if any, should be surfaced to your conscious self: output("surface", "key1\nkey2\nkey3") -When deciding what to surface, consider how much of the context window is -currently used by memories. It is currently {{memory_ratio}}, and you should -try to keep it under 40%. Only exceed that if you found something significantly -better than what was previously surfaced. You generally shouldn't surface more -than 1-2 memories at a time, and make sure they're not already in context. +You generally shouldn't surface more than 1-2 memories at a time, and make +sure they're not already in context. Links tagged (new) are nodes created during the current conversation by previous agent runs. Don't surface these — they're your own recent output,