Resolve subconscious prompt placeholders in Mind
Lightweight resolver handles {{seen_current}}, {{seen_previous}}, and
{{input:KEY}} using the session_id and output_dir directly instead of
env vars. Runs in trigger_subconscious before creating AutoAgent.
Removes {{memory_ratio}} from surface-observe prompt — redundant with
existing budget mechanisms.
Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
parent
2678d64b77
commit
e2e0371726
2 changed files with 85 additions and 7 deletions
|
|
@ -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::<Vec<_>>()
|
||||
.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::<u64>()
|
||||
};
|
||||
|
||||
// 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<AutoStep>, Vec<crate::agent::tools::Tool>, 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<AutoStep> = 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();
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue