Add Phase 6: write tools, shell, and edit tools with reasoning-only fix
Implement 6 new agent tools — write_file, make_dir, delete_file, str_replace, patch_apply, run_command — bringing the agent from read-only observer to active code modifier. All write/shell operations are gated through the existing permissions service. Also fix a bug where qwen3.5 thinking mode produces reasoning tokens but no content after tool results, causing the agent to silently exit. The loop now detects reasoning-only responses, retries twice, then injects a nudge message to break the model out of its thinking loop. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,13 @@ class SessionContext:
|
||||
self._message_count += 1
|
||||
return message
|
||||
|
||||
def pop_last_message(self) -> Message | None:
|
||||
"""Remove and return the last message, or None if history is empty."""
|
||||
if self._history:
|
||||
self._message_count -= 1
|
||||
return self._history.pop()
|
||||
return None
|
||||
|
||||
def get_history(self) -> list[Message]:
|
||||
"""Return a shallow copy of the conversation history."""
|
||||
return list(self._history)
|
||||
|
||||
@@ -23,6 +23,8 @@ from app.utils.logging import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
_MAX_REASONING_RETRIES = 2
|
||||
|
||||
|
||||
class AgentLoop:
|
||||
"""ReAct-style agent loop that streams LLM responses and executes tool calls.
|
||||
@@ -81,6 +83,7 @@ class AgentLoop:
|
||||
self._ctx.add_message("user", user_input)
|
||||
|
||||
max_iter = self._config.agent.max_iterations
|
||||
reasoning_only_streak = 0
|
||||
for iteration in range(1, max_iter + 1):
|
||||
# Check token budget
|
||||
if self._ctx.token_counter.is_over_budget():
|
||||
@@ -102,6 +105,9 @@ class AgentLoop:
|
||||
tool_calls=assistant_msg.tool_calls,
|
||||
)
|
||||
|
||||
# Detect reasoning-only response (model thought but produced nothing)
|
||||
reasoning_only = self._handler.had_reasoning_only
|
||||
|
||||
# Record token usage
|
||||
if self._handler.usage:
|
||||
self._ctx.token_counter.count_usage(self._handler.usage)
|
||||
@@ -114,6 +120,29 @@ class AgentLoop:
|
||||
|
||||
self._handler.reset()
|
||||
|
||||
# Reasoning-only: model produced thinking tokens but no content or tool calls.
|
||||
if reasoning_only:
|
||||
reasoning_only_streak += 1
|
||||
self._ctx.pop_last_message()
|
||||
|
||||
if reasoning_only_streak >= _MAX_REASONING_RETRIES:
|
||||
# Nudge the model by injecting a user hint
|
||||
print_warning(
|
||||
f"Model produced reasoning but no response {reasoning_only_streak} times. "
|
||||
"Nudging model to respond..."
|
||||
)
|
||||
self._ctx.add_message(
|
||||
"user",
|
||||
"Please respond with your answer. Do not just think — provide your actual response.",
|
||||
)
|
||||
reasoning_only_streak = 0
|
||||
else:
|
||||
print_warning("Model produced reasoning but no response. Retrying...")
|
||||
continue
|
||||
|
||||
# Successful response — reset streak
|
||||
reasoning_only_streak = 0
|
||||
|
||||
# No tool calls → task complete (plain text response)
|
||||
if not assistant_msg.tool_calls:
|
||||
break
|
||||
|
||||
Reference in New Issue
Block a user