Shared persistent state across all subconscious agents

Moved persistent_state from per-agent to a single shared BTreeMap on
Subconscious. All agents read/write the same state — surface's walked
keys are visible to observe and reflect, etc.

- Subconscious.state: shared BTreeMap<String, String>
- walked() derives from state["walked"] instead of separate Vec
- subconscious-state.json is now a flat key-value map
- All agent outputs merge into the shared state on completion
- Loaded on startup, saved after any agent completes

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-04-07 19:16:01 -04:00
parent 578be807e7
commit 27ca3c058d
8 changed files with 60 additions and 80 deletions

View file

@ -102,7 +102,11 @@ impl Backend {
}
/// Resolve {{placeholder}} templates in subconscious agent prompts.
fn resolve_prompt(template: &str, memory_keys: &[String], walked: &[String]) -> String {
fn resolve_prompt(
template: &str,
memory_keys: &[String],
state: &std::collections::BTreeMap<String, String>,
) -> String {
let cfg = crate::config::get();
let template = template.replace("{assistant_name}", &cfg.assistant_name);
let mut result = String::with_capacity(template.len());
@ -112,14 +116,17 @@ fn resolve_prompt(template: &str, memory_keys: &[String], walked: &[String]) ->
let after = &rest[start + 2..];
if let Some(end) = after.find("}}") {
let name = after[..end].trim();
let replacement = match name {
"seen_current" => format_key_list(memory_keys),
"walked" => format_key_list(walked),
_ => {
result.push_str("{{");
result.push_str(&after[..end + 2]);
rest = &after[end + 2..];
continue;
let replacement = if let Some(key) = name.strip_prefix("state:") {
state.get(key).cloned().unwrap_or_else(|| "(not set)".to_string())
} else {
match name {
"seen_current" => format_key_list(memory_keys),
_ => {
result.push_str("{{");
result.push_str(&after[..end + 2]);
rest = &after[end + 2..];
continue;
}
}
};
result.push_str(&replacement);
@ -133,6 +140,8 @@ fn resolve_prompt(template: &str, memory_keys: &[String], walked: &[String]) ->
result
}
fn format_key_list(keys: &[String]) -> String {
if keys.is_empty() { "(none)".to_string() }
else { keys.iter().map(|k| format!("- {}", k)).collect::<Vec<_>>().join("\n") }
@ -177,10 +186,10 @@ impl AutoAgent {
&mut self,
agent: &std::sync::Arc<tokio::sync::Mutex<Agent>>,
memory_keys: &[String],
walked: &[String],
state: &std::collections::BTreeMap<String, String>,
) -> Result<String, String> {
let resolved_steps: Vec<AutoStep> = self.steps.iter().map(|s| AutoStep {
prompt: resolve_prompt(&s.prompt, memory_keys, walked),
prompt: resolve_prompt(&s.prompt, memory_keys, state),
phase: s.phase.clone(),
}).collect();
let orig_steps = std::mem::replace(&mut self.steps, resolved_steps);