Shared subconscious state — walked keys are Mind-level, not per-agent

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 <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-04-07 02:13:06 -04:00
parent ef868cb98f
commit f3ba7e7097
4 changed files with 37 additions and 23 deletions

View file

@ -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<String>,
}
/// 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<f64>,
/// Entries from the last forked run (after fork point).
pub last_run_entries: Vec<crate::agent::context::ConversationEntry>,
@ -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<SharedMindState>,
pub config: SessionConfig,
subconscious: Arc<tokio::sync::Mutex<Vec<SubconsciousAgent>>>,
subconscious_state: Arc<tokio::sync::Mutex<SubconsciousSharedState>>,
turn_tx: mpsc::Sender<(Result<TurnResult>, StreamTarget)>,
turn_watch: tokio::sync::watch::Sender<bool>,
bg_tx: mpsc::UnboundedSender<BgEvent>,
@ -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<SubconsciousSnapshot> {
self.subconscious.lock().await.iter().map(|s| s.snapshot()).collect()
pub async fn subconscious_snapshots(&self) -> (Vec<SubconsciousSnapshot>, 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<String> = 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<String, String>)> =
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));