From f3ba7e709766a1e44b3a6d1d2a0fcaf4f4f095e5 Mon Sep 17 00:00:00 2001 From: Kent Overstreet Date: Tue, 7 Apr 2026 02:13:06 -0400 Subject: [PATCH] =?UTF-8?q?Shared=20subconscious=20state=20=E2=80=94=20wal?= =?UTF-8?q?ked=20keys=20are=20Mind-level,=20not=20per-agent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SubconsciousSharedState holds walked keys shared between all subconscious agents. Enables splitting surface-observe into separate surface and observe agents that share the same walked state. Walked is passed to run_forked() at run time instead of living on AutoAgent. UI shows walked count in the subconscious screen header. Co-Authored-By: Proof of Concept --- src/agent/oneshot.rs | 14 +++++--------- src/mind/mod.rs | 32 ++++++++++++++++++++++---------- src/user/mod.rs | 6 +++++- src/user/subconscious.rs | 8 +++++--- 4 files changed, 37 insertions(+), 23 deletions(-) diff --git a/src/agent/oneshot.rs b/src/agent/oneshot.rs index 6a6dd58..28e436d 100644 --- a/src/agent/oneshot.rs +++ b/src/agent/oneshot.rs @@ -54,8 +54,6 @@ pub struct AutoAgent { pub steps: Vec, sampling: super::api::SamplingParams, priority: i32, - /// Memory keys the surface agent was exploring — persists between runs. - pub walked: Vec, /// Named outputs from the agent's output() tool calls. /// Collected per-run, read by Mind after completion. pub outputs: std::collections::HashMap, @@ -164,7 +162,6 @@ impl AutoAgent { temperature, top_p: 0.95, top_k: 20, }, priority, - walked: Vec::new(), outputs: std::collections::HashMap::new(), last_run_entries: Vec::new(), current_phase: String::new(), @@ -186,18 +183,18 @@ impl AutoAgent { } /// Run forked from a conscious agent's context. Each call gets a - /// fresh fork for KV cache sharing. Walked state persists between runs. + /// fresh fork for KV cache sharing. /// - /// `memory_keys`: keys of Memory entries in the conscious agent's - /// context, used to resolve {{seen_current}} in prompt templates. + /// `memory_keys`: Memory entry keys from conscious context (for {{seen_current}}). + /// `walked`: shared walked keys from previous runs (for {{walked}}). pub async fn run_forked( &mut self, agent: &Agent, memory_keys: &[String], + walked: &[String], ) -> Result { - // Resolve prompt templates with current state let resolved_steps: Vec = self.steps.iter().map(|s| AutoStep { - prompt: resolve_prompt(&s.prompt, memory_keys, &self.walked), + prompt: resolve_prompt(&s.prompt, memory_keys, walked), phase: s.phase.clone(), }).collect(); let orig_steps = std::mem::replace(&mut self.steps, resolved_steps); @@ -205,7 +202,6 @@ impl AutoAgent { let fork_point = forked.context.entries.len(); let mut backend = Backend::Forked(forked); let result = self.run_with_backend(&mut backend, None).await; - // Capture entries added during this run if let Backend::Forked(ref agent) = backend { self.last_run_entries = agent.context.entries[fork_point..].to_vec(); } diff --git a/src/mind/mod.rs b/src/mind/mod.rs index 6810ff0..3222869 100644 --- a/src/mind/mod.rs +++ b/src/mind/mod.rs @@ -91,6 +91,14 @@ impl SubconsciousAgent { } } +/// State shared between all subconscious agents. Lives on Mind, +/// passed to agents at run time. Enables splitting surface/observe +/// into separate agents that share walked keys. +#[derive(Clone, Default)] +pub struct SubconsciousSharedState { + pub walked: Vec, +} + /// Lightweight snapshot of subconscious agent state for the TUI. #[derive(Clone, Default)] pub struct SubconsciousSnapshot { @@ -98,7 +106,6 @@ pub struct SubconsciousSnapshot { pub running: bool, pub current_phase: String, pub turn: usize, - pub walked_count: usize, pub last_run_secs_ago: Option, /// Entries from the last forked run (after fork point). pub last_run_entries: Vec, @@ -111,7 +118,6 @@ impl SubconsciousAgent { running: self.is_running(), current_phase: self.auto.current_phase.clone(), turn: self.auto.turn, - walked_count: self.auto.walked.len(), last_run_secs_ago: self.last_run.map(|t| t.elapsed().as_secs_f64()), last_run_entries: self.auto.last_run_entries.clone(), } @@ -293,6 +299,7 @@ pub struct Mind { pub shared: Arc, pub config: SessionConfig, subconscious: Arc>>, + subconscious_state: Arc>, turn_tx: mpsc::Sender<(Result, StreamTarget)>, turn_watch: tokio::sync::watch::Sender, bg_tx: mpsc::UnboundedSender, @@ -337,14 +344,19 @@ impl Mind { sup.load_config(); sup.ensure_running(); - Self { agent, shared, config, subconscious: Arc::new(tokio::sync::Mutex::new(subconscious)), + let subconscious_state = Arc::new(tokio::sync::Mutex::new(SubconsciousSharedState::default())); + Self { agent, shared, config, + subconscious: Arc::new(tokio::sync::Mutex::new(subconscious)), + subconscious_state, turn_tx, turn_watch, bg_tx, bg_rx: std::sync::Mutex::new(Some(bg_rx)), _supervisor: sup } } /// Initialize — restore log, start daemons and background agents. - pub async fn subconscious_snapshots(&self) -> Vec { - self.subconscious.lock().await.iter().map(|s| s.snapshot()).collect() + pub async fn subconscious_snapshots(&self) -> (Vec, SubconsciousSharedState) { + let snaps = self.subconscious.lock().await.iter().map(|s| s.snapshot()).collect(); + let shared = self.subconscious_state.lock().await.clone(); + (snaps, shared) } pub async fn init(&self) { @@ -468,15 +480,13 @@ impl Mind { let name = subs[idx].auto.name.clone(); let outputs = std::mem::take(&mut subs[idx].auto.outputs); - // Walked keys — update all subconscious agents + // Walked keys — update shared state if let Some(walked_str) = outputs.get("walked") { let walked: Vec = walked_str.lines() .map(|l| l.trim().to_string()) .filter(|l| !l.is_empty()) .collect(); - for sub in subs.iter_mut() { - sub.auto.walked = walked.clone(); - } + self.subconscious_state.lock().await.walked = walked; } drop(subs); @@ -557,15 +567,17 @@ impl Mind { // Fork from conscious agent and spawn tasks let conscious = self.agent.lock().await; + let walked = self.subconscious_state.lock().await.walked.clone(); let mut spawns = Vec::new(); for (idx, mut auto) in to_run { dbglog!("[mind] triggering {}", auto.name); let forked = conscious.fork(auto.tools.clone()); let keys = memory_keys.clone(); + let w = walked.clone(); let handle: tokio::task::JoinHandle<(AutoAgent, Result)> = tokio::spawn(async move { - let result = auto.run_forked(&forked, &keys).await; + let result = auto.run_forked(&forked, &keys, &w).await; (auto, result) }); spawns.push((idx, handle)); diff --git a/src/user/mod.rs b/src/user/mod.rs index ea814d0..5cfb7f4 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -128,6 +128,7 @@ pub struct App { pub(crate) context_info: Option, pub(crate) shared_context: SharedContextState, pub(crate) agent_state: Vec, + pub(crate) subconscious_shared: crate::mind::SubconsciousSharedState, pub(crate) channel_status: Vec, pub(crate) idle_info: Option, } @@ -150,6 +151,7 @@ impl App { should_quit: false, submitted: Vec::new(), context_info: None, shared_context, agent_state: Vec::new(), + subconscious_shared: Default::default(), channel_status: Vec::new(), idle_info: None, } } @@ -406,7 +408,9 @@ pub async fn run( // State sync on every wake idle_state.decay_ewma(); app.update_idle(&idle_state); - app.agent_state = mind.subconscious_snapshots().await; + let (snaps, shared) = mind.subconscious_snapshots().await; + app.agent_state = snaps; + app.subconscious_shared = shared; if !startup_done { if let Ok(mut ag) = agent.try_lock() { let model = ag.model().to_string(); diff --git a/src/user/subconscious.rs b/src/user/subconscious.rs index cdd54db..ff49767 100644 --- a/src/user/subconscious.rs +++ b/src/user/subconscious.rs @@ -76,7 +76,9 @@ impl SubconsciousScreen { let hint = Style::default().fg(Color::DarkGray).add_modifier(Modifier::ITALIC); lines.push(Line::raw("")); - lines.push(Line::styled("── Subconscious Agents ──", section)); + let walked = app.subconscious_shared.walked.len(); + lines.push(Line::styled( + format!("── Subconscious Agents ── walked: {}", walked), section)); lines.push(Line::styled(" (↑/↓ select, Enter view log)", hint)); lines.push(Line::raw("")); @@ -121,8 +123,8 @@ impl SubconsciousScreen { ), Span::styled("○ ", bg.fg(Color::DarkGray)), Span::styled( - format!("idle last: {} entries: {} walked: {}", - ago, entries, snap.walked_count), + format!("idle last: {} entries: {}", + ago, entries), bg.fg(Color::DarkGray), ), ]