From efe1cb00c88234ab4c81055a8aac07689a315508 Mon Sep 17 00:00:00 2001 From: happy5318 Date: Tue, 5 May 2026 04:39:51 -0700 Subject: [PATCH] fix: prevent stale reasoning from being reused across turns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The reasoning-box extraction loop in run_conversation() walked backwards through the entire message history looking for any assistant message with a non-empty 'reasoning' field. When the current turn produced no reasoning (e.g. the provider returned reasoning_content=null for a trivial response), the loop walked past the current turn and showed reasoning from a prior turn — stale text from minutes or hours ago displayed as if it belonged to the current reply. Fix: stop the walk at the user message that started the current turn. That picks the most recent reasoning WITHIN the turn (correct for tool-calling turns where reasoning lands on the tool-call step and the final-answer step has reasoning=None — common on Claude thinking, DeepSeek v4, Codex Responses), and returns None cleanly when the current turn genuinely had no reasoning. Co-authored-by: happy5318 --- run_agent.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/run_agent.py b/run_agent.py index 3554ff665d..46197eee76 100644 --- a/run_agent.py +++ b/run_agent.py @@ -13952,9 +13952,19 @@ class AIAgent: except Exception as exc: logger.warning("post_llm_call hook failed: %s", exc) - # Extract reasoning from the last assistant message (if any) + # Extract reasoning from the CURRENT turn only. Walk backwards + # but stop at the user message that started this turn — anything + # earlier is from a prior turn and must not leak into the reasoning + # box (confusing stale display; #17055). Within the current turn + # we still want the *most recent* non-empty reasoning: many + # providers (Claude thinking, DeepSeek v4, Codex Responses) emit + # reasoning on the tool-call step and leave the final-answer step + # with reasoning=None, so picking only the last assistant would + # silently drop legitimate same-turn reasoning. last_reasoning = None for msg in reversed(messages): + if msg.get("role") == "user": + break # turn boundary — don't cross into prior turns if msg.get("role") == "assistant" and msg.get("reasoning"): last_reasoning = msg["reasoning"] break