From 7c04c755ae2943e65c11ffd3502e01882de02c36 Mon Sep 17 00:00:00 2001 From: ciregenz Date: Thu, 20 Aug 2026 12:32:14 -0700 Subject: [PATCH] [eric] agents: an invisible thinking pill no longer breaks the repeat-card dedup Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_014wtspwSFzZmjCx9UNPAorQ --- .../agents/manager/run/handle_run_error.py | 9 +++++- .../streaming/handle_assistant_message.py | 5 ++- backend/tests/test_error_card_dedup.py | 31 +++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/backend/apps/agents/manager/run/handle_run_error.py b/backend/apps/agents/manager/run/handle_run_error.py index a3438ebd..fdcd76c6 100644 --- a/backend/apps/agents/manager/run/handle_run_error.py +++ b/backend/apps/agents/manager/run/handle_run_error.py @@ -34,6 +34,12 @@ logger = logging.getLogger(__name__) +@typechecked +def p_renders_as_nothing(msg: Message) -> bool: + """An empty thinking pill occupies the tail while showing the user nothing, which silently broke the dedup below (live drill 2026-08-20: three identical cards).""" + return msg.role == "thinking" and not str(msg.content or "").strip() + + @typechecked def absorb_repeat_card(session: AgentSession, error_msg: Message) -> None: """Append the error card, unless the branch tail is already the IDENTICAL card with nothing @@ -47,7 +53,8 @@ def absorb_repeat_card(session: AgentSession, error_msg: Message) -> None: because each retry's hidden prompt had displaced the previous card from the tail.""" p_tail = [m for m in session.messages if getattr(m, "branch_id", None) in (None, session.active_branch_id) - and not getattr(m, "hidden", False)] + and not getattr(m, "hidden", False) + and not p_renders_as_nothing(m)] if p_tail and p_tail[-1].role == "system" and p_tail[-1].content == error_msg.content: error_msg.id = p_tail[-1].id p_tail[-1].timestamp = error_msg.timestamp diff --git a/backend/apps/agents/manager/streaming/handle_assistant_message.py b/backend/apps/agents/manager/streaming/handle_assistant_message.py index db6fff0e..aa4ec238 100644 --- a/backend/apps/agents/manager/streaming/handle_assistant_message.py +++ b/backend/apps/agents/manager/streaming/handle_assistant_message.py @@ -220,7 +220,10 @@ async def handle_assistant_message( content=p_copy, branch_id=session.active_branch_id, ) - session.messages.append(p_card) + # A retry ladder re-failing the same way must bump one card, not stack clones; the live + # drill produced three before this line existed. + from backend.apps.agents.manager.run.handle_run_error import absorb_repeat_card + absorb_repeat_card(session, p_card) logger.warning( f"Agent {session_id}: provider returned {p_provider_error.kind} " f"(status={p_provider_error.status}, lane={p_provider_error.lane}) as assistant " diff --git a/backend/tests/test_error_card_dedup.py b/backend/tests/test_error_card_dedup.py index e964af6a..69cde3d2 100644 --- a/backend/tests/test_error_card_dedup.py +++ b/backend/tests/test_error_card_dedup.py @@ -78,3 +78,34 @@ def test_a_real_user_message_still_earns_a_fresh_card(): shown = [m for m in s.messages if m.role == "system"] assert len(shown) == 2, "each real ask gets its own honest answer" + + +def test_an_empty_thinking_pill_does_not_break_the_dedup(): + """Found by the live provider-error drill 2026-08-20, invisible to every unit test here. + + Each retry leaves a non-hidden `thinking` message with empty content sitting in the tail. It + renders as nothing, but it displaced the previous card from the tail scan, so a re-failing + ladder stacked three identical cards on screen while this suite stayed green. + """ + s = AgentSession(name="t", model="sonnet") + first = p_card("Lost the connection to the model.") + absorb_repeat_card(s, first) + s.messages.append(Message(role="thinking", content="", branch_id="main")) + repeat = p_card("Lost the connection to the model.") + absorb_repeat_card(s, repeat) + p_cards = [m for m in s.messages if m.role == "system"] + assert len(p_cards) == 1, "an invisible pill must not earn the user a duplicate card" + assert repeat.id == first.id + + +def test_a_thinking_pill_with_real_content_still_breaks_the_dedup(): + """NEGATIVE CONTROL. Only the EMPTY pill is invisible; real thinking is content the user saw, + so a card after it is genuinely new and must not be absorbed into the older one.""" + s = AgentSession(name="t", model="sonnet") + first = p_card("Lost the connection to the model.") + absorb_repeat_card(s, first) + s.messages.append(Message(role="thinking", content="Let me try that again.", branch_id="main")) + repeat = p_card("Lost the connection to the model.") + absorb_repeat_card(s, repeat) + p_cards = [m for m in s.messages if m.role == "system"] + assert len(p_cards) == 2, "visible thinking separates the two failures"