From 9c9618d0341c2fb5d0166da49c736b09075c4dc6 Mon Sep 17 00:00:00 2001 From: Kent Overstreet Date: Wed, 8 Apr 2026 16:41:14 -0400 Subject: [PATCH] WIP: ActiveTools wrapper type, removing SharedActiveTools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New ActiveTools struct with proper methods: push, remove, take_finished, take_foreground, iter, len. Turn loop uses helpers instead of manual index iteration. Removing SharedActiveTools (Arc>) — active tools live directly in AgentState. A few UI callers still need updating. Co-Authored-By: Proof of Concept --- src/agent/mod.rs | 50 ++++++++-------------------------------- src/agent/tools/mod.rs | 52 +++++++++++++++++++++++++++++++++++------- src/user/mod.rs | 6 ++--- 3 files changed, 56 insertions(+), 52 deletions(-) diff --git a/src/agent/mod.rs b/src/agent/mod.rs index a5fe19c..2f41ad0 100644 --- a/src/agent/mod.rs +++ b/src/agent/mod.rs @@ -154,7 +154,7 @@ pub struct AgentState { pub conversation_log: Option, pub generation: u64, pub memory_scoring_in_flight: bool, - pub active_tools: tools::SharedActiveTools, + pub active_tools: tools::ActiveTools, pub changed: Arc, } @@ -166,7 +166,7 @@ impl Agent { app_config: crate::config::AppConfig, prompt_file: String, conversation_log: Option, - active_tools: tools::SharedActiveTools, + active_tools: tools::ActiveTools, ) -> Arc { let mut context = ContextState::new(); context.push(Section::System, AstNode::system_msg(&system_prompt)); @@ -248,7 +248,7 @@ impl Agent { conversation_log: None, generation: 0, memory_scoring_in_flight: false, - active_tools: tools::shared_active_tools(), + active_tools: tools::ActiveTools::new(), changed: Arc::new(tokio::sync::Notify::new()), }), }) @@ -279,35 +279,16 @@ impl Agent { pub async fn turn( agent: Arc, ) -> Result { - let active_tools = { - agent.state.lock().await.active_tools.clone() - }; - // Collect finished background tools { - let mut finished = Vec::new(); - { - let mut tools = active_tools.lock().unwrap(); - let mut i = 0; - while i < tools.len() { - if tools[i].handle.is_finished() { - finished.push(tools.remove(i)); - } else { - i += 1; - } - } - } + let finished = agent.state.lock().await.active_tools.take_finished(); if !finished.is_empty() { - let mut results = Vec::new(); + let mut bg_ds = DispatchState::new(); for entry in finished { if let Ok((call, output)) = entry.handle.await { - results.push((call, output)); + Agent::apply_tool_result(&agent, &call, output, &mut bg_ds).await; } } - let mut bg_ds = DispatchState::new(); - for (call, output) in results { - Agent::apply_tool_result(&agent, &call, output, &mut bg_ds).await; - } } } @@ -355,7 +336,7 @@ impl Agent { ).await; (call_clone, output) }); - active_tools.lock().unwrap().push(tools::ActiveToolCall { + agent.state.lock().await.active_tools.push(tools::ActiveToolCall { id: call.id.clone(), name: call.name.clone(), detail: call.arguments.clone(), @@ -404,20 +385,7 @@ impl Agent { if !pending_calls.is_empty() { ds.had_tool_calls = true; - // Collect non-background tool handles - let mut handles = Vec::new(); - { - let mut tools_guard = active_tools.lock().unwrap(); - let mut i = 0; - while i < tools_guard.len() { - if !tools_guard[i].background { - handles.push(tools_guard.remove(i)); - } else { - i += 1; - } - } - } - + let handles = agent.state.lock().await.active_tools.take_foreground(); for entry in handles { if let Ok((call, output)) = entry.handle.await { Agent::apply_tool_result(&agent, &call, output, &mut ds).await; @@ -465,7 +433,7 @@ impl Agent { ds.tool_errors += 1; } - agent.state.lock().await.active_tools.lock().unwrap().retain(|t| t.id != call.id); + agent.state.lock().await.active_tools.remove(&call.id); if call.name == "memory_render" && !output.starts_with("Error:") { let args: serde_json::Value = diff --git a/src/agent/tools/mod.rs b/src/agent/tools/mod.rs index acb8ae8..24b2876 100644 --- a/src/agent/tools/mod.rs +++ b/src/agent/tools/mod.rs @@ -56,10 +56,6 @@ impl Tool { } } - - -/// A tool call in flight — metadata for TUI + JoinHandle for -/// result collection and cancellation. pub struct ActiveToolCall { pub id: String, pub name: String, @@ -69,11 +65,51 @@ pub struct ActiveToolCall { pub handle: tokio::task::JoinHandle<(super::context::PendingToolCall, String)>, } -/// Shared active tool calls — agent spawns, TUI reads metadata / aborts. -pub type SharedActiveTools = Arc>>; +pub struct ActiveTools(Vec); -pub fn shared_active_tools() -> SharedActiveTools { - Arc::new(std::sync::Mutex::new(Vec::new())) +impl ActiveTools { + pub fn new() -> Self { Self(Vec::new()) } + + pub fn push(&mut self, call: ActiveToolCall) { + self.0.push(call); + } + + pub fn remove(&mut self, id: &str) { + self.0.retain(|t| t.id != id); + } + + pub fn take_finished(&mut self) -> Vec { + let mut finished = Vec::new(); + let mut i = 0; + while i < self.0.len() { + if self.0[i].handle.is_finished() { + finished.push(self.0.remove(i)); + } else { + i += 1; + } + } + finished + } + + pub fn take_foreground(&mut self) -> Vec { + let mut fg = Vec::new(); + let mut i = 0; + while i < self.0.len() { + if !self.0[i].background { + fg.push(self.0.remove(i)); + } else { + i += 1; + } + } + fg + } + + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + + pub fn len(&self) -> usize { self.0.len() } + pub fn is_empty(&self) -> bool { self.0.is_empty() } } /// Truncate output if it exceeds max length, appending a truncation notice. diff --git a/src/user/mod.rs b/src/user/mod.rs index e374f25..b24aa2f 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -103,7 +103,7 @@ struct App { temperature: f32, top_p: f32, top_k: u32, - active_tools: crate::agent::tools::SharedActiveTools, + agent: std::sync::Arc, should_quit: bool, context_info: Option, agent_state: Vec, @@ -113,7 +113,7 @@ struct App { } impl App { - fn new(model: String, active_tools: crate::agent::tools::SharedActiveTools) -> Self { + fn new(model: String, agent: std::sync::Arc) -> Self { Self { status: StatusInfo { dmn_state: "resting".into(), dmn_turns: 0, dmn_max_turns: 20, @@ -126,7 +126,7 @@ impl App { temperature: 0.6, top_p: 0.95, top_k: 20, - active_tools, + agent: mind.agent.clone(), should_quit: false, context_info: None, agent_state: Vec::new(),