WIP: ActiveTools wrapper type, removing SharedActiveTools

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<Mutex<Vec>>) — active tools
live directly in AgentState. A few UI callers still need
updating.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-04-08 16:41:14 -04:00
parent 14fd8c9b90
commit 9c9618d034
3 changed files with 56 additions and 52 deletions

View file

@ -154,7 +154,7 @@ pub struct AgentState {
pub conversation_log: Option<ConversationLog>,
pub generation: u64,
pub memory_scoring_in_flight: bool,
pub active_tools: tools::SharedActiveTools,
pub active_tools: tools::ActiveTools,
pub changed: Arc<tokio::sync::Notify>,
}
@ -166,7 +166,7 @@ impl Agent {
app_config: crate::config::AppConfig,
prompt_file: String,
conversation_log: Option<ConversationLog>,
active_tools: tools::SharedActiveTools,
active_tools: tools::ActiveTools,
) -> Arc<Self> {
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<Agent>,
) -> Result<TurnResult> {
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 =

View file

@ -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<std::sync::Mutex<Vec<ActiveToolCall>>>;
pub struct ActiveTools(Vec<ActiveToolCall>);
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<ActiveToolCall> {
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<ActiveToolCall> {
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<Item = &ActiveToolCall> {
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.