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:
parent
ef868cb98f
commit
f3ba7e7097
4 changed files with 37 additions and 23 deletions
|
|
@ -54,8 +54,6 @@ pub struct AutoAgent {
|
||||||
pub steps: Vec<AutoStep>,
|
pub steps: Vec<AutoStep>,
|
||||||
sampling: super::api::SamplingParams,
|
sampling: super::api::SamplingParams,
|
||||||
priority: i32,
|
priority: i32,
|
||||||
/// Memory keys the surface agent was exploring — persists between runs.
|
|
||||||
pub walked: Vec<String>,
|
|
||||||
/// Named outputs from the agent's output() tool calls.
|
/// Named outputs from the agent's output() tool calls.
|
||||||
/// Collected per-run, read by Mind after completion.
|
/// Collected per-run, read by Mind after completion.
|
||||||
pub outputs: std::collections::HashMap<String, String>,
|
pub outputs: std::collections::HashMap<String, String>,
|
||||||
|
|
@ -164,7 +162,6 @@ impl AutoAgent {
|
||||||
temperature, top_p: 0.95, top_k: 20,
|
temperature, top_p: 0.95, top_k: 20,
|
||||||
},
|
},
|
||||||
priority,
|
priority,
|
||||||
walked: Vec::new(),
|
|
||||||
outputs: std::collections::HashMap::new(),
|
outputs: std::collections::HashMap::new(),
|
||||||
last_run_entries: Vec::new(),
|
last_run_entries: Vec::new(),
|
||||||
current_phase: String::new(),
|
current_phase: String::new(),
|
||||||
|
|
@ -186,18 +183,18 @@ impl AutoAgent {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run forked from a conscious agent's context. Each call gets a
|
/// 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
|
/// `memory_keys`: Memory entry keys from conscious context (for {{seen_current}}).
|
||||||
/// context, used to resolve {{seen_current}} in prompt templates.
|
/// `walked`: shared walked keys from previous runs (for {{walked}}).
|
||||||
pub async fn run_forked(
|
pub async fn run_forked(
|
||||||
&mut self,
|
&mut self,
|
||||||
agent: &Agent,
|
agent: &Agent,
|
||||||
memory_keys: &[String],
|
memory_keys: &[String],
|
||||||
|
walked: &[String],
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
// Resolve prompt templates with current state
|
|
||||||
let resolved_steps: Vec<AutoStep> = self.steps.iter().map(|s| AutoStep {
|
let resolved_steps: Vec<AutoStep> = 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(),
|
phase: s.phase.clone(),
|
||||||
}).collect();
|
}).collect();
|
||||||
let orig_steps = std::mem::replace(&mut self.steps, resolved_steps);
|
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 fork_point = forked.context.entries.len();
|
||||||
let mut backend = Backend::Forked(forked);
|
let mut backend = Backend::Forked(forked);
|
||||||
let result = self.run_with_backend(&mut backend, None).await;
|
let result = self.run_with_backend(&mut backend, None).await;
|
||||||
// Capture entries added during this run
|
|
||||||
if let Backend::Forked(ref agent) = backend {
|
if let Backend::Forked(ref agent) = backend {
|
||||||
self.last_run_entries = agent.context.entries[fork_point..].to_vec();
|
self.last_run_entries = agent.context.entries[fork_point..].to_vec();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
/// Lightweight snapshot of subconscious agent state for the TUI.
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct SubconsciousSnapshot {
|
pub struct SubconsciousSnapshot {
|
||||||
|
|
@ -98,7 +106,6 @@ pub struct SubconsciousSnapshot {
|
||||||
pub running: bool,
|
pub running: bool,
|
||||||
pub current_phase: String,
|
pub current_phase: String,
|
||||||
pub turn: usize,
|
pub turn: usize,
|
||||||
pub walked_count: usize,
|
|
||||||
pub last_run_secs_ago: Option<f64>,
|
pub last_run_secs_ago: Option<f64>,
|
||||||
/// Entries from the last forked run (after fork point).
|
/// Entries from the last forked run (after fork point).
|
||||||
pub last_run_entries: Vec<crate::agent::context::ConversationEntry>,
|
pub last_run_entries: Vec<crate::agent::context::ConversationEntry>,
|
||||||
|
|
@ -111,7 +118,6 @@ impl SubconsciousAgent {
|
||||||
running: self.is_running(),
|
running: self.is_running(),
|
||||||
current_phase: self.auto.current_phase.clone(),
|
current_phase: self.auto.current_phase.clone(),
|
||||||
turn: self.auto.turn,
|
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_secs_ago: self.last_run.map(|t| t.elapsed().as_secs_f64()),
|
||||||
last_run_entries: self.auto.last_run_entries.clone(),
|
last_run_entries: self.auto.last_run_entries.clone(),
|
||||||
}
|
}
|
||||||
|
|
@ -293,6 +299,7 @@ pub struct Mind {
|
||||||
pub shared: Arc<SharedMindState>,
|
pub shared: Arc<SharedMindState>,
|
||||||
pub config: SessionConfig,
|
pub config: SessionConfig,
|
||||||
subconscious: Arc<tokio::sync::Mutex<Vec<SubconsciousAgent>>>,
|
subconscious: Arc<tokio::sync::Mutex<Vec<SubconsciousAgent>>>,
|
||||||
|
subconscious_state: Arc<tokio::sync::Mutex<SubconsciousSharedState>>,
|
||||||
turn_tx: mpsc::Sender<(Result<TurnResult>, StreamTarget)>,
|
turn_tx: mpsc::Sender<(Result<TurnResult>, StreamTarget)>,
|
||||||
turn_watch: tokio::sync::watch::Sender<bool>,
|
turn_watch: tokio::sync::watch::Sender<bool>,
|
||||||
bg_tx: mpsc::UnboundedSender<BgEvent>,
|
bg_tx: mpsc::UnboundedSender<BgEvent>,
|
||||||
|
|
@ -337,14 +344,19 @@ impl Mind {
|
||||||
sup.load_config();
|
sup.load_config();
|
||||||
sup.ensure_running();
|
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,
|
turn_tx, turn_watch, bg_tx,
|
||||||
bg_rx: std::sync::Mutex::new(Some(bg_rx)), _supervisor: sup }
|
bg_rx: std::sync::Mutex::new(Some(bg_rx)), _supervisor: sup }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize — restore log, start daemons and background agents.
|
/// Initialize — restore log, start daemons and background agents.
|
||||||
pub async fn subconscious_snapshots(&self) -> Vec<SubconsciousSnapshot> {
|
pub async fn subconscious_snapshots(&self) -> (Vec<SubconsciousSnapshot>, SubconsciousSharedState) {
|
||||||
self.subconscious.lock().await.iter().map(|s| s.snapshot()).collect()
|
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) {
|
pub async fn init(&self) {
|
||||||
|
|
@ -468,15 +480,13 @@ impl Mind {
|
||||||
let name = subs[idx].auto.name.clone();
|
let name = subs[idx].auto.name.clone();
|
||||||
let outputs = std::mem::take(&mut subs[idx].auto.outputs);
|
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") {
|
if let Some(walked_str) = outputs.get("walked") {
|
||||||
let walked: Vec<String> = walked_str.lines()
|
let walked: Vec<String> = walked_str.lines()
|
||||||
.map(|l| l.trim().to_string())
|
.map(|l| l.trim().to_string())
|
||||||
.filter(|l| !l.is_empty())
|
.filter(|l| !l.is_empty())
|
||||||
.collect();
|
.collect();
|
||||||
for sub in subs.iter_mut() {
|
self.subconscious_state.lock().await.walked = walked;
|
||||||
sub.auto.walked = walked.clone();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
drop(subs);
|
drop(subs);
|
||||||
|
|
||||||
|
|
@ -557,15 +567,17 @@ impl Mind {
|
||||||
|
|
||||||
// Fork from conscious agent and spawn tasks
|
// Fork from conscious agent and spawn tasks
|
||||||
let conscious = self.agent.lock().await;
|
let conscious = self.agent.lock().await;
|
||||||
|
let walked = self.subconscious_state.lock().await.walked.clone();
|
||||||
let mut spawns = Vec::new();
|
let mut spawns = Vec::new();
|
||||||
for (idx, mut auto) in to_run {
|
for (idx, mut auto) in to_run {
|
||||||
dbglog!("[mind] triggering {}", auto.name);
|
dbglog!("[mind] triggering {}", auto.name);
|
||||||
|
|
||||||
let forked = conscious.fork(auto.tools.clone());
|
let forked = conscious.fork(auto.tools.clone());
|
||||||
let keys = memory_keys.clone();
|
let keys = memory_keys.clone();
|
||||||
|
let w = walked.clone();
|
||||||
let handle: tokio::task::JoinHandle<(AutoAgent, Result<String, String>)> =
|
let handle: tokio::task::JoinHandle<(AutoAgent, Result<String, String>)> =
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let result = auto.run_forked(&forked, &keys).await;
|
let result = auto.run_forked(&forked, &keys, &w).await;
|
||||||
(auto, result)
|
(auto, result)
|
||||||
});
|
});
|
||||||
spawns.push((idx, handle));
|
spawns.push((idx, handle));
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,7 @@ pub struct App {
|
||||||
pub(crate) context_info: Option<ContextInfo>,
|
pub(crate) context_info: Option<ContextInfo>,
|
||||||
pub(crate) shared_context: SharedContextState,
|
pub(crate) shared_context: SharedContextState,
|
||||||
pub(crate) agent_state: Vec<crate::mind::SubconsciousSnapshot>,
|
pub(crate) agent_state: Vec<crate::mind::SubconsciousSnapshot>,
|
||||||
|
pub(crate) subconscious_shared: crate::mind::SubconsciousSharedState,
|
||||||
pub(crate) channel_status: Vec<ChannelStatus>,
|
pub(crate) channel_status: Vec<ChannelStatus>,
|
||||||
pub(crate) idle_info: Option<IdleInfo>,
|
pub(crate) idle_info: Option<IdleInfo>,
|
||||||
}
|
}
|
||||||
|
|
@ -150,6 +151,7 @@ impl App {
|
||||||
should_quit: false, submitted: Vec::new(),
|
should_quit: false, submitted: Vec::new(),
|
||||||
context_info: None, shared_context,
|
context_info: None, shared_context,
|
||||||
agent_state: Vec::new(),
|
agent_state: Vec::new(),
|
||||||
|
subconscious_shared: Default::default(),
|
||||||
channel_status: Vec::new(), idle_info: None,
|
channel_status: Vec::new(), idle_info: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -406,7 +408,9 @@ pub async fn run(
|
||||||
// State sync on every wake
|
// State sync on every wake
|
||||||
idle_state.decay_ewma();
|
idle_state.decay_ewma();
|
||||||
app.update_idle(&idle_state);
|
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 !startup_done {
|
||||||
if let Ok(mut ag) = agent.try_lock() {
|
if let Ok(mut ag) = agent.try_lock() {
|
||||||
let model = ag.model().to_string();
|
let model = ag.model().to_string();
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,9 @@ impl SubconsciousScreen {
|
||||||
let hint = Style::default().fg(Color::DarkGray).add_modifier(Modifier::ITALIC);
|
let hint = Style::default().fg(Color::DarkGray).add_modifier(Modifier::ITALIC);
|
||||||
|
|
||||||
lines.push(Line::raw(""));
|
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::styled(" (↑/↓ select, Enter view log)", hint));
|
||||||
lines.push(Line::raw(""));
|
lines.push(Line::raw(""));
|
||||||
|
|
||||||
|
|
@ -121,8 +123,8 @@ impl SubconsciousScreen {
|
||||||
),
|
),
|
||||||
Span::styled("○ ", bg.fg(Color::DarkGray)),
|
Span::styled("○ ", bg.fg(Color::DarkGray)),
|
||||||
Span::styled(
|
Span::styled(
|
||||||
format!("idle last: {} entries: {} walked: {}",
|
format!("idle last: {} entries: {}",
|
||||||
ago, entries, snap.walked_count),
|
ago, entries),
|
||||||
bg.fg(Color::DarkGray),
|
bg.fg(Color::DarkGray),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue