Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/agent/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,16 @@ impl Agent {
/// Returns `AgenticLoopResult::Response` on completion, or
/// `AgenticLoopResult::NeedApproval` if a tool requires user approval.
///
/// When `resume_after_tool` is true the loop already knows a tool was
/// executed earlier in this turn (e.g. an approved tool), so it won't
/// force the LLM to use tools if it responds with text.
pub(super) async fn run_agentic_loop(
&self,
message: &IncomingMessage,
session: Arc<Mutex<Session>>,
thread_id: Uuid,
initial_messages: Vec<ChatMessage>,
resume_after_tool: bool,
) -> Result<AgenticLoopResult, Error> {
// Load workspace system prompt (identity files: AGENTS.md, SOUL.md, etc.)
let system_prompt = if let Some(ws) = self.workspace() {
Expand Down Expand Up @@ -110,6 +114,8 @@ impl Agent {

const MAX_TOOL_ITERATIONS: usize = 10;
let mut iteration = 0;
let mut tools_executed = resume_after_tool;

loop {
iteration += 1;
if iteration > MAX_TOOL_ITERATIONS {
Expand Down Expand Up @@ -193,12 +199,30 @@ impl Agent {

match output.result {
RespondResult::Text(text) => {
// If no tools have been executed yet, prompt the LLM to use tools
// This handles the case where the model explains what it will do
// instead of actually calling tools
if !tools_executed && iteration < 3 {
tracing::debug!(
"No tools executed yet (iteration {}), prompting for tool use",
iteration
);
context_messages.push(ChatMessage::assistant(&text));
context_messages.push(ChatMessage::user(
"Please proceed and use the available tools to complete this task.",
));
continue;
}

// Tools have been executed or we've tried multiple times, return response
return Ok(AgenticLoopResult::Response(text));
}
RespondResult::ToolCalls {
tool_calls,
content,
} => {
tools_executed = true;

// Add the assistant message with tool_calls to context.
// OpenAI protocol requires this before tool-result messages.
context_messages.push(ChatMessage::assistant_with_tool_calls(
Expand Down
4 changes: 2 additions & 2 deletions src/agent/thread_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ impl Agent {

// Run the agentic tool execution loop
let result = self
.run_agentic_loop(message, session.clone(), thread_id, turn_messages)
.run_agentic_loop(message, session.clone(), thread_id, turn_messages, false)
.await;

// Re-acquire lock and check if interrupted
Expand Down Expand Up @@ -1057,7 +1057,7 @@ impl Agent {

// Continue the agentic loop (a tool was already executed this turn)
let result = self
.run_agentic_loop(message, session.clone(), thread_id, context_messages)
.run_agentic_loop(message, session.clone(), thread_id, context_messages, true)
.await;

// Handle the result
Expand Down
Loading