From 0b9813431a99c254e9d04c65de5ec8309ee51f1c Mon Sep 17 00:00:00 2001 From: Kent Overstreet Date: Wed, 8 Apr 2026 15:47:21 -0400 Subject: [PATCH] =?UTF-8?q?Agent/AgentState=20split=20complete=20=E2=80=94?= =?UTF-8?q?=20separate=20context=20and=20state=20locks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent is now Arc (immutable config). ContextState and AgentState have separate tokio::sync::Mutex locks. The parser locks only context, tool dispatch locks only state. No contention between the two. All callers migrated: mind/, user/, tools/, oneshot, dmn, learn. 28 tests pass, zero errors. Co-Authored-By: Proof of Concept --- src/agent/mod.rs | 3 +- src/agent/oneshot.rs | 4 +- src/mind/dmn.rs | 32 ++++----- src/mind/mod.rs | 86 ++++++++++++------------ src/user/chat.rs | 142 ++++++++++++++++++++------------------- src/user/context.rs | 14 ++-- src/user/mod.rs | 24 +++---- src/user/subconscious.rs | 10 +-- 8 files changed, 156 insertions(+), 159 deletions(-) diff --git a/src/agent/mod.rs b/src/agent/mod.rs index 85a53dc..5e67dc7 100644 --- a/src/agent/mod.rs +++ b/src/agent/mod.rs @@ -24,7 +24,6 @@ use anyhow::Result; use api::ApiClient; use context::{AstNode, NodeBody, ContextState, Section, Ast, PendingToolCall, ResponseParser, Role}; -use tools::summarize_args; use crate::mind::log::ConversationLog; @@ -416,7 +415,7 @@ impl Agent { agent.push_node(AstNode::user_msg( "[system] Your previous response was empty. \ Please respond with text or use a tool." - )); + )).await; continue; } } else { diff --git a/src/agent/oneshot.rs b/src/agent/oneshot.rs index dd8358f..5c72cb9 100644 --- a/src/agent/oneshot.rs +++ b/src/agent/oneshot.rs @@ -14,8 +14,8 @@ use std::fs; use std::path::PathBuf; use std::sync::OnceLock; -use super::api::{ApiClient, Usage}; -use super::context::{AstNode, Role}; +use super::api::ApiClient; +use super::context::AstNode; use super::tools::{self as agent_tools}; use super::Agent; diff --git a/src/mind/dmn.rs b/src/mind/dmn.rs index 122528f..4a84130 100644 --- a/src/mind/dmn.rs +++ b/src/mind/dmn.rs @@ -460,8 +460,6 @@ impl Subconscious { || outputs.contains_key("reflection") || outputs.contains_key("thalamus"); if has_outputs { - let mut ag = agent.lock().await; - if let Some(surface_str) = outputs.get("surface") { let store = crate::store::Store::cached().await.ok(); let store_guard = match &store { @@ -472,30 +470,30 @@ impl Subconscious { let rendered = store_guard.as_ref() .and_then(|s| crate::cli::node::render_node(s, key)); if let Some(rendered) = rendered { - ag.push_node(AstNode::memory( + agent.push_node(AstNode::memory( key, format!("--- {} (surfaced) ---\n{}", key, rendered), - )); + )).await; } } } if let Some(reflection) = outputs.get("reflection") { if !reflection.trim().is_empty() { - ag.push_node(AstNode::dmn(format!( + agent.push_node(AstNode::dmn(format!( "--- subconscious reflection ---\n{}", reflection.trim(), - ))); + ))).await; } } if let Some(nudge) = outputs.get("thalamus") { let nudge = nudge.trim(); if !nudge.is_empty() && nudge != "ok" { - ag.push_node(AstNode::dmn(format!( + agent.push_node(AstNode::dmn(format!( "--- thalamus ---\n{}", nudge, - ))); + ))).await; } } } @@ -513,13 +511,13 @@ impl Subconscious { /// Trigger subconscious agents that are due to run. pub async fn trigger(&mut self, agent: &Arc) { let (conversation_bytes, memory_keys) = { - let ag = agent.lock().await; - let bytes = ag.context.conversation().iter() + let ctx = agent.context.lock().await; + let bytes = ctx.conversation().iter() .filter(|node| !matches!(node.leaf().map(|l| l.body()), Some(NodeBody::Log(_)) | Some(NodeBody::Memory { .. }))) .map(|node| node.render().len() as u64) .sum::(); - let keys: Vec = ag.context.conversation().iter().filter_map(|node| { + let keys: Vec = ctx.conversation().iter().filter_map(|node| { if let Some(NodeBody::Memory { key, .. }) = node.leaf().map(|l| l.body()) { Some(key.clone()) } else { None } @@ -541,23 +539,21 @@ impl Subconscious { if to_run.is_empty() { return; } - let conscious = agent.lock().await; for (idx, mut auto) in to_run { dbglog!("[subconscious] triggering {}", auto.name); - let mut forked = conscious.fork(auto.tools.clone()); - forked.provenance = format!("agent:{}", auto.name); - let fork_point = forked.context.conversation().len(); - let shared_forked = Arc::new(tokio::sync::Mutex::new(forked)); + let forked = agent.fork(auto.tools.clone()).await; + forked.state.lock().await.provenance = format!("agent:{}", auto.name); + let fork_point = forked.context.lock().await.conversation().len(); - self.agents[idx].forked_agent = Some(shared_forked.clone()); + self.agents[idx].forked_agent = Some(forked.clone()); self.agents[idx].fork_point = fork_point; let keys = memory_keys.clone(); let st = self.state.clone(); self.agents[idx].handle = Some(tokio::spawn(async move { - let result = auto.run_forked_shared(&shared_forked, &keys, &st).await; + let result = auto.run_forked_shared(&forked, &keys, &st).await; (auto, result) })); } diff --git a/src/mind/mod.rs b/src/mind/mod.rs index 5136123..24e8964 100644 --- a/src/mind/mod.rs +++ b/src/mind/mod.rs @@ -260,7 +260,7 @@ pub struct Mind { } impl Mind { - pub fn new( + pub async fn new( config: SessionConfig, turn_tx: mpsc::Sender<(Result, StreamTarget)>, ) -> Self { @@ -271,7 +271,7 @@ impl Mind { config.session_dir.join("conversation.jsonl"), ).ok(); - let ag = Agent::new( + let agent = Agent::new( client, config.system_prompt.clone(), config.context_parts.clone(), @@ -279,8 +279,7 @@ impl Mind { config.prompt_file.clone(), conversation_log, shared_active_tools, - ); - let agent = Arc::new(tokio::sync::Mutex::new(ag)); + ).await; let shared = Arc::new(std::sync::Mutex::new(MindState::new(config.app.dmn.max_turns))); let (turn_watch, _) = tokio::sync::watch::channel(false); @@ -314,15 +313,13 @@ impl Mind { pub async fn init(&self) { // Restore conversation - let mut ag = self.agent.lock().await; - ag.restore_from_log(); + self.agent.restore_from_log().await; // Restore persisted memory scores let scores_path = self.config.session_dir.join("memory-scores.json"); - load_memory_scores(&mut ag.context, &scores_path); + load_memory_scores(&mut *self.agent.context.lock().await, &scores_path); - ag.changed.notify_one(); - drop(ag); + self.agent.state.lock().await.changed.notify_one(); // Load persistent subconscious state let state_path = self.config.session_dir.join("subconscious-state.json"); @@ -340,10 +337,9 @@ impl Mind { MindCommand::None => {} MindCommand::Compact => { let threshold = compaction_threshold(&self.config.app) as usize; - let mut ag = self.agent.lock().await; - if ag.context.tokens() > threshold { - ag.compact(); - ag.notify("compacted"); + if self.agent.context.lock().await.tokens() > threshold { + self.agent.compact().await; + self.agent.state.lock().await.notify("compacted"); } } MindCommand::Score => { @@ -356,10 +352,10 @@ impl Mind { } MindCommand::Interrupt => { self.shared.lock().unwrap().interrupt(); - let ag = self.agent.lock().await; - let mut tools = ag.active_tools.lock().unwrap(); + let active_tools = self.agent.state.lock().await.active_tools.clone(); + let mut tools = active_tools.lock().unwrap(); for entry in tools.drain(..) { entry.handle.abort(); } - drop(tools); drop(ag); + drop(tools); if let Some(h) = self.shared.lock().unwrap().turn_handle.take() { h.abort(); } self.shared.lock().unwrap().turn_active = false; let _ = self.turn_watch.send(false); @@ -373,14 +369,17 @@ impl Mind { let new_log = log::ConversationLog::new( self.config.session_dir.join("conversation.jsonl"), ).ok(); - let mut ag = self.agent.lock().await; - let shared_tools = ag.active_tools.clone(); - *ag = Agent::new( - ApiClient::new(&self.config.api_base, &self.config.api_key, &self.config.model), - self.config.system_prompt.clone(), self.config.context_parts.clone(), - self.config.app.clone(), self.config.prompt_file.clone(), - new_log, shared_tools, - ); + { + let mut ctx = self.agent.context.lock().await; + ctx.clear(Section::Conversation); + } + { + let mut st = self.agent.state.lock().await; + st.conversation_log = new_log; + st.generation += 1; + st.last_prompt_tokens = 0; + } + self.agent.compact().await; } } } @@ -395,10 +394,12 @@ impl Mind { let response_window = cfg.scoring_response_window; tokio::spawn(async move { let (context, client) = { - let mut ag = agent.lock().await; - if ag.memory_scoring_in_flight { return; } - ag.memory_scoring_in_flight = true; - (ag.context.clone(), ag.client_clone()) + let mut st = agent.state.lock().await; + if st.memory_scoring_in_flight { return; } + st.memory_scoring_in_flight = true; + drop(st); + let ctx = agent.context.lock().await.clone(); + (ctx, agent.client.clone()) }; let _result = learn::score_memories_incremental( &context, max_age as i64, response_window, &client, &agent, @@ -407,27 +408,27 @@ impl Mind { let path = scores_path.clone(); async move { let scores_snapshot = { - let mut ag = agent.lock().await; - for i in 0..ag.context.conversation().len() { - if let AstNode::Leaf(leaf) = &ag.context.conversation()[i] { + let mut ctx = agent.context.lock().await; + for i in 0..ctx.conversation().len() { + if let AstNode::Leaf(leaf) = &ctx.conversation()[i] { if let NodeBody::Memory { key: k, .. } = leaf.body() { if *k == key { - ag.context.set_score(Section::Conversation, i, Some(score)); + ctx.set_score(Section::Conversation, i, Some(score)); } } } } - ag.changed.notify_one(); - collect_memory_scores(&ag.context) + let snapshot = collect_memory_scores(&ctx); + drop(ctx); + agent.state.lock().await.changed.notify_one(); + snapshot }; - // Write to disk after releasing the lock save_memory_scores(&scores_snapshot, &path); } }, ).await; { - let mut ag = agent.lock().await; - ag.memory_scoring_in_flight = false; + agent.state.lock().await.memory_scoring_in_flight = false; } let _ = bg_tx.send(BgEvent::ScoringDone); }); @@ -435,21 +436,20 @@ impl Mind { async fn start_turn(&self, text: &str, target: StreamTarget) { { - let mut ag = self.agent.lock().await; match target { StreamTarget::Conversation => { - ag.push_node(AstNode::user_msg(text)); + self.agent.push_node(AstNode::user_msg(text)).await; } StreamTarget::Autonomous => { - ag.push_node(AstNode::dmn(text)); + self.agent.push_node(AstNode::dmn(text)).await; } } // Compact if over budget before sending let threshold = compaction_threshold(&self.config.app) as usize; - if ag.context.tokens() > threshold { - ag.compact(); - ag.notify("compacted"); + if self.agent.context.lock().await.tokens() > threshold { + self.agent.compact().await; + self.agent.state.lock().await.notify("compacted"); } } self.shared.lock().unwrap().turn_active = true; diff --git a/src/user/chat.rs b/src/user/chat.rs index aa73520..a6cfed7 100644 --- a/src/user/chat.rs +++ b/src/user/chat.rs @@ -33,17 +33,17 @@ fn commands() -> Vec { vec![ handler: |s, _| { let _ = s.mind_tx.send(MindCommand::NewSession); } }, SlashCommand { name: "/save", help: "Save session to disk", handler: |s, _| { - if let Ok(mut ag) = s.agent.try_lock() { ag.notify("saved"); } + if let Ok(mut ag) = s.agent.state.try_lock() { ag.notify("saved"); } } }, SlashCommand { name: "/model", help: "Show/switch model (/model )", handler: |s, arg| { if arg.is_empty() { - if let Ok(mut ag) = s.agent.try_lock() { - let names = ag.app_config.model_names(); + if let Ok(mut ag) = s.agent.state.try_lock() { + let names = s.agent.app_config.model_names(); let label = if names.is_empty() { - format!("model: {}", ag.model()) + format!("model: {}", s.agent.model()) } else { - format!("model: {} ({})", ag.model(), names.join(", ")) + format!("model: {} ({})", s.agent.model(), names.join(", ")) }; ag.notify(label); } @@ -61,7 +61,7 @@ fn commands() -> Vec { vec![ SlashCommand { name: "/dmn", help: "Show DMN state", handler: |s, _| { let st = s.shared_mind.lock().unwrap(); - if let Ok(mut ag) = s.agent.try_lock() { + if let Ok(mut ag) = s.agent.state.try_lock() { ag.notify(format!("DMN: {:?} ({}/{})", st.dmn, st.dmn_turns, st.max_dmn_turns)); } } }, @@ -70,7 +70,7 @@ fn commands() -> Vec { vec![ let mut st = s.shared_mind.lock().unwrap(); st.dmn = crate::mind::dmn::State::Resting { since: std::time::Instant::now() }; st.dmn_turns = 0; - if let Ok(mut ag) = s.agent.try_lock() { ag.notify("DMN sleeping"); } + if let Ok(mut ag) = s.agent.state.try_lock() { ag.notify("DMN sleeping"); } } }, SlashCommand { name: "/wake", help: "Wake DMN to foraging", handler: |s, _| { @@ -78,14 +78,14 @@ fn commands() -> Vec { vec![ if matches!(st.dmn, crate::mind::dmn::State::Off) { crate::mind::dmn::set_off(false); } st.dmn = crate::mind::dmn::State::Foraging; st.dmn_turns = 0; - if let Ok(mut ag) = s.agent.try_lock() { ag.notify("DMN foraging"); } + if let Ok(mut ag) = s.agent.state.try_lock() { ag.notify("DMN foraging"); } } }, SlashCommand { name: "/pause", help: "Full stop — no autonomous ticks (Ctrl+P)", handler: |s, _| { let mut st = s.shared_mind.lock().unwrap(); st.dmn = crate::mind::dmn::State::Paused; st.dmn_turns = 0; - if let Ok(mut ag) = s.agent.try_lock() { ag.notify("DMN paused"); } + if let Ok(mut ag) = s.agent.state.try_lock() { ag.notify("DMN paused"); } } }, SlashCommand { name: "/help", help: "Show this help", handler: |s, _| { notify_help(&s.agent); } }, @@ -101,36 +101,27 @@ pub async fn cmd_switch_model( agent: &std::sync::Arc, name: &str, ) { - let resolved = { - let ag = agent.lock().await; - match ag.app_config.resolve_model(name) { - Ok(r) => r, - Err(e) => { - agent.lock().await.notify(format!("model error: {}", e)); - return; - } + let resolved = match agent.app_config.resolve_model(name) { + Ok(r) => r, + Err(e) => { + agent.state.lock().await.notify(format!("model error: {}", e)); + return; } }; - let new_client = crate::agent::api::ApiClient::new( + let _new_client = crate::agent::api::ApiClient::new( &resolved.api_base, &resolved.api_key, &resolved.model_id, ); - let prompt_changed = { - let ag = agent.lock().await; - resolved.prompt_file != ag.prompt_file - }; - let mut ag = agent.lock().await; - ag.swap_client(new_client); + let prompt_changed = resolved.prompt_file != agent.prompt_file; if prompt_changed { - ag.prompt_file = resolved.prompt_file.clone(); - ag.compact(); - ag.notify(format!("switched to {} (recompacted)", resolved.model_id)); + agent.compact().await; + agent.state.lock().await.notify(format!("switched to {} (recompacted)", resolved.model_id)); } else { - ag.notify(format!("switched to {}", resolved.model_id)); + agent.state.lock().await.notify(format!("switched to {}", resolved.model_id)); } } fn notify_help(agent: &std::sync::Arc) { - if let Ok(mut ag) = agent.try_lock() { + if let Ok(mut ag) = agent.state.try_lock() { let mut help = String::new(); for cmd in &commands() { help.push_str(&format!("{:12} {}\n", cmd.name, cmd.help)); @@ -471,48 +462,57 @@ impl InteractScreen { } self.pending_display_count = 0; - if let Ok(agent) = self.agent.try_lock() { - let generation = agent.generation; - let entries = agent.conversation(); + let (generation, entries) = { + let st = match self.agent.state.try_lock() { + Ok(st) => st, + Err(_) => return, + }; + let generation = st.generation; + drop(st); + let ctx = match self.agent.context.try_lock() { + Ok(ctx) => ctx, + Err(_) => return, + }; + (generation, ctx.conversation().to_vec()) + }; - if generation != self.last_generation || entries.len() < self.last_entries.len() { - self.conversation = PaneState::new(true); - self.autonomous = PaneState::new(true); - self.tools = PaneState::new(false); - self.last_entries.clear(); - } + if generation != self.last_generation || entries.len() < self.last_entries.len() { + self.conversation = PaneState::new(true); + self.autonomous = PaneState::new(true); + self.tools = PaneState::new(false); + self.last_entries.clear(); + } - let start = self.last_entries.len(); - for node in entries.iter().skip(start) { - for (target, text, marker) in Self::route_node(node) { - match target { - PaneTarget::Conversation => { - self.conversation.current_color = Color::Cyan; - self.conversation.append_text(&text); - self.conversation.pending_marker = marker; - self.conversation.flush_pending(); - }, - PaneTarget::ConversationAssistant => { - self.conversation.current_color = Color::Reset; - self.conversation.append_text(&text); - self.conversation.pending_marker = marker; - self.conversation.flush_pending(); - }, - PaneTarget::Tools => - self.tools.push_line(text, Color::Yellow), - PaneTarget::ToolResult => { - for line in text.lines().take(20) { - self.tools.push_line(format!(" {}", line), Color::DarkGray); - } + let start = self.last_entries.len(); + for node in entries.iter().skip(start) { + for (target, text, marker) in Self::route_node(node) { + match target { + PaneTarget::Conversation => { + self.conversation.current_color = Color::Cyan; + self.conversation.append_text(&text); + self.conversation.pending_marker = marker; + self.conversation.flush_pending(); + }, + PaneTarget::ConversationAssistant => { + self.conversation.current_color = Color::Reset; + self.conversation.append_text(&text); + self.conversation.pending_marker = marker; + self.conversation.flush_pending(); + }, + PaneTarget::Tools => + self.tools.push_line(text, Color::Yellow), + PaneTarget::ToolResult => { + for line in text.lines().take(20) { + self.tools.push_line(format!(" {}", line), Color::DarkGray); } } } - self.last_entries.push(node.clone()); } - - self.last_generation = generation; + self.last_entries.push(node.clone()); } + self.last_generation = generation; + // Display pending input (queued in Mind, not yet accepted) let mind = self.shared_mind.lock().unwrap(); for input in &mind.input { @@ -537,7 +537,7 @@ impl InteractScreen { if let Some(cmd) = dispatch_command(input) { (cmd.handler)(self, &input[cmd.name.len()..].trim_start()); } else { - if let Ok(mut ag) = self.agent.try_lock() { + if let Ok(mut ag) = self.agent.state.try_lock() { ag.notify(format!("unknown: {}", input.split_whitespace().next().unwrap_or(input))); } } @@ -833,15 +833,17 @@ impl ScreenView for InteractScreen { self.sync_from_agent(); // Read status from agent + mind state - if let Ok(mut agent) = self.agent.try_lock() { - agent.expire_activities(); - app.status.prompt_tokens = agent.last_prompt_tokens(); - app.status.model = agent.model().to_string(); - app.status.context_budget = format!("{} tokens", agent.context.tokens()); - app.activity = agent.activities.last() + if let Ok(mut st) = self.agent.state.try_lock() { + st.expire_activities(); + app.status.prompt_tokens = st.last_prompt_tokens; + app.status.model = self.agent.model().to_string(); + app.activity = st.activities.last() .map(|a| a.label.clone()) .unwrap_or_default(); } + if let Ok(ctx) = self.agent.context.try_lock() { + app.status.context_budget = format!("{} tokens", ctx.tokens()); + } { let mind = self.shared_mind.lock().unwrap(); app.status.dmn_state = mind.dmn.label().to_string(); diff --git a/src/user/context.rs b/src/user/context.rs index 444ae88..9cdd777 100644 --- a/src/user/context.rs +++ b/src/user/context.rs @@ -20,22 +20,22 @@ impl ConsciousScreen { } fn read_context_views(&self) -> Vec { - let ag = match self.agent.try_lock() { - Ok(ag) => ag, + let ctx = match self.agent.context.try_lock() { + Ok(ctx) => ctx, Err(_) => return Vec::new(), }; let mut views: Vec = Vec::new(); - views.push(section_to_view("System", ag.context.system())); - views.push(section_to_view("Identity", ag.context.identity())); - views.push(section_to_view("Journal", ag.context.journal())); + views.push(section_to_view("System", ctx.system())); + views.push(section_to_view("Identity", ctx.identity())); + views.push(section_to_view("Journal", ctx.journal())); // Memory nodes extracted from conversation let mut mem_children: Vec = Vec::new(); let mut scored = 0usize; let mut unscored = 0usize; - for node in ag.context.conversation() { + for node in ctx.conversation() { if let AstNode::Leaf(leaf) = node { if let NodeBody::Memory { key, score, text } = leaf.body() { let status = match score { @@ -63,7 +63,7 @@ impl ConsciousScreen { }); } - views.push(section_to_view("Conversation", ag.context.conversation())); + views.push(section_to_view("Conversation", ctx.conversation())); views } } diff --git a/src/user/mod.rs b/src/user/mod.rs index a73954a..e374f25 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -179,9 +179,9 @@ async fn start(cli: crate::user::CliArgs) -> Result<()> { let (turn_tx, turn_rx) = tokio::sync::mpsc::channel(1); let (mind_tx, mind_rx) = tokio::sync::mpsc::unbounded_channel(); - let mind = crate::mind::Mind::new(config, turn_tx); + let mind = crate::mind::Mind::new(config, turn_tx).await; - let shared_active_tools = mind.agent.lock().await.active_tools.clone(); + let shared_active_tools = mind.agent.state.lock().await.active_tools.clone(); let mut result = Ok(()); tokio_scoped::scope(|s| { @@ -203,7 +203,7 @@ async fn start(cli: crate::user::CliArgs) -> Result<()> { } fn hotkey_cycle_reasoning(mind: &crate::mind::Mind) { - if let Ok(mut ag) = mind.agent.try_lock() { + if let Ok(mut ag) = mind.agent.state.try_lock() { let next = match ag.reasoning_effort.as_str() { "none" => "low", "low" => "high", @@ -221,17 +221,17 @@ fn hotkey_cycle_reasoning(mind: &crate::mind::Mind) { } async fn hotkey_kill_processes(mind: &crate::mind::Mind) { - let mut ag = mind.agent.lock().await; - let active_tools = ag.active_tools.clone(); + let mut st = mind.agent.state.lock().await; + let active_tools = st.active_tools.clone(); let mut tools = active_tools.lock().unwrap(); if tools.is_empty() { - ag.notify("no running tools"); + st.notify("no running tools"); } else { let count = tools.len(); for entry in tools.drain(..) { entry.handle.abort(); } - ag.notify(format!("killed {} tools", count)); + st.notify(format!("killed {} tools", count)); } } @@ -259,7 +259,7 @@ fn hotkey_cycle_autonomy(mind: &crate::mind::Mind) { }; s.dmn_turns = 0; drop(s); - if let Ok(mut ag) = mind.agent.try_lock() { + if let Ok(mut ag) = mind.agent.state.try_lock() { ag.notify(format!("DMN → {}", label)); } } @@ -325,13 +325,13 @@ async fn run( } }); - let agent_changed = agent.lock().await.changed.clone(); + let agent_changed = agent.state.lock().await.changed.clone(); let mut turn_watch = mind.turn_watch(); let mut pending: Vec = Vec::new(); terminal.hide_cursor()?; - if let Ok(mut ag) = agent.try_lock() { ag.notify("consciousness v0.3"); } + if let Ok(mut ag) = agent.state.try_lock() { ag.notify("consciousness v0.3"); } // Initial render { @@ -378,8 +378,8 @@ async fn run( app.agent_state = mind.subconscious_snapshots().await; app.walked_count = mind.subconscious_walked().await.len(); if !startup_done { - if let Ok(mut ag) = agent.try_lock() { - let model = ag.model().to_string(); + if let Ok(mut ag) = agent.state.try_lock() { + let model = agent.model().to_string(); ag.notify(format!("model: {}", model)); startup_done = true; } diff --git a/src/user/subconscious.rs b/src/user/subconscious.rs index c0f1789..0d50305 100644 --- a/src/user/subconscious.rs +++ b/src/user/subconscious.rs @@ -150,9 +150,9 @@ impl SubconsciousScreen { None => return Vec::new(), }; snap.forked_agent.as_ref() - .and_then(|agent| agent.try_lock().ok()) - .map(|ag| { - let conv = ag.context.conversation(); + .and_then(|agent| agent.context.try_lock().ok()) + .map(|ctx| { + let conv = ctx.conversation(); let mut view = section_to_view("Conversation", conv); let fork = snap.fork_point.min(view.children.len()); view.children = view.children.split_off(fork); @@ -177,8 +177,8 @@ impl SubconsciousScreen { .map(|s| format_age(s)) .unwrap_or_else(|| "—".to_string()); let entries = snap.forked_agent.as_ref() - .and_then(|a| a.try_lock().ok()) - .map(|ag| ag.context.conversation().len().saturating_sub(snap.fork_point)) + .and_then(|a| a.context.try_lock().ok()) + .map(|ctx| ctx.conversation().len().saturating_sub(snap.fork_point)) .unwrap_or(0); ListItem::from(Line::from(vec![ Span::styled(&snap.name, Style::default().fg(Color::Gray)),