From 8a4f3b6f88afac34f8501d7bc640defeb98f6650 Mon Sep 17 00:00:00 2001 From: "firat.sertgoz" Date: Fri, 20 Feb 2026 12:09:52 +0400 Subject: [PATCH] fix: remove auto-proceed fake user message injection from agent loop (#255) The agentic loop injected fake user messages ("Please proceed and use the available tools to complete this task.") when the LLM responded with text instead of tool calls. This caused hallucinated conversations during casual chat, 3x wasted LLM calls, and trust issues. Remove the `resume_after_tool` parameter and `tools_executed` tracking entirely. Text responses now return immediately, trusting the LLM to decide when tools are needed (consistent with ZeroClaw and OpenClaw). Closes #145 Co-authored-by: Claude Opus 4.6 --- src/agent/dispatcher.rs | 24 ------------------------ src/agent/thread_ops.rs | 4 ++-- 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/src/agent/dispatcher.rs b/src/agent/dispatcher.rs index 78d23c31..fcead248 100644 --- a/src/agent/dispatcher.rs +++ b/src/agent/dispatcher.rs @@ -33,16 +33,12 @@ 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>, thread_id: Uuid, initial_messages: Vec, - resume_after_tool: bool, ) -> Result { // Load workspace system prompt (identity files: AGENTS.md, SOUL.md, etc.) let system_prompt = if let Some(ws) = self.workspace() { @@ -114,8 +110,6 @@ 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 { @@ -199,30 +193,12 @@ 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( diff --git a/src/agent/thread_ops.rs b/src/agent/thread_ops.rs index 66f3723c..c1d6442f 100644 --- a/src/agent/thread_ops.rs +++ b/src/agent/thread_ops.rs @@ -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, false) + .run_agentic_loop(message, session.clone(), thread_id, turn_messages) .await; // Re-acquire lock and check if interrupted @@ -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, true) + .run_agentic_loop(message, session.clone(), thread_id, context_messages) .await; // Handle the result