From 1d1285de460bca4630db355916710cbce2391e8f Mon Sep 17 00:00:00 2001 From: h Date: Tue, 22 Sep 2026 20:53:05 +0200 Subject: [PATCH] fix(agents): only models that send progress updates read thinking as progress --- src/beaver_gateway/agents/claude.py | 10 +++++++--- tests/test_conversations.py | 24 +++++++++++++++++++++--- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/beaver_gateway/agents/claude.py b/src/beaver_gateway/agents/claude.py index a276c56..4a4bf28 100644 --- a/src/beaver_gateway/agents/claude.py +++ b/src/beaver_gateway/agents/claude.py @@ -17,7 +17,10 @@ from beaver_gateway.agents.policy import PolicyRule # noqa: TC001 from beaver_gateway.agents.prompts import PromptSource # noqa: TC001 from beaver_gateway.conversations.kinds import KINDS, Kind -__all__ = ["ClaudeAgent", "ClaudeOptions", "Prompts", "SkillSets"] +__all__ = ["PROGRESS_MODELS", "ClaudeAgent", "ClaudeOptions", "Prompts", "SkillSets"] + +PROGRESS_MODELS = frozenset({"claude-opus-5-5", "claude-fable-5-1"}) +"""Models whose non-empty thinking is the text written between tool calls.""" class ClaudeOptions(BaseModel): @@ -123,8 +126,9 @@ class ClaudeAgent(BaseAgent): @property def thinking_is_progress(self) -> bool: - """Reasoning arrives empty unless ``display`` is ``summarized``.""" - return (self.options.thinking or {}).get("display") != "summarized" + """A progress model, unless ``display: summarized`` brings reasoning.""" + summarized = (self.options.thinking or {}).get("display") == "summarized" + return self.model in PROGRESS_MODELS and not summarized def prompt_for(self, kind: Kind) -> tuple[PromptSource, ...] | None: return self.prompts.for_kind(kind) diff --git a/tests/test_conversations.py b/tests/test_conversations.py index 6cc8497..859bcba 100644 --- a/tests/test_conversations.py +++ b/tests/test_conversations.py @@ -767,9 +767,28 @@ def test_read_conversation_keeps_progress_and_drops_reasoning() -> None: ) -def test_only_claude_agents_read_their_thinking_as_progress(tmp_path: Path) -> None: +@pytest.mark.parametrize( + ("model", "expected"), + [ + ("claude-opus-5-5", True), + ("claude-fable-5-1", True), + ("claude-opus-5", False), + ("claude-sonnet-5", False), + ("claude-haiku-4-5-20251001", False), + ("claude-next-unknown", False), + ], +) +def test_only_progress_models_read_their_thinking_as_progress( + tmp_path: Path, model: str, expected: bool +) -> None: + assert ( + ClaudeAgent(name="c", model=model, cwd=tmp_path).thinking_is_progress + is expected + ) + + +def test_summarized_display_and_raycast_are_never_progress(tmp_path: Path) -> None: raycast = RaycastAgent(name="r", model="google-gemini-3.1-pro", system_prompt="") - claude = ClaudeAgent(name="c", model="claude-opus-5-5", cwd=tmp_path) summarized = ClaudeAgent( name="s", model="claude-opus-5-5", @@ -777,7 +796,6 @@ def test_only_claude_agents_read_their_thinking_as_progress(tmp_path: Path) -> N options=ClaudeOptions(thinking={"type": "adaptive", "display": "summarized"}), ) assert not raycast.thinking_is_progress - assert claude.thinking_is_progress assert not summarized.thinking_is_progress