From c549e3590dd5d0eb4c033786dee8baf16bc0d309 Mon Sep 17 00:00:00 2001 From: "Strix (Claude Opus 4.6)" Date: Fri, 10 Apr 2026 13:26:31 +0000 Subject: [PATCH 1/2] fix: cycle detection prompts reflection instead of just stopping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When loop detection fires, the return message now suggests: 1. Reflect on what you were trying to accomplish 2. Try a completely different approach 3. Invoke 5 Whys if available The old prompt ("stop repeating, change strategy, finish safely") was purely defensive — it told the agent what NOT to do but gave no constructive path. This caused agents like Motley to just panic-retry harder because the circuit breaker said "stop" not "think." Co-Authored-By: Claude Opus 4.6 --- open_strix/tools.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/open_strix/tools.py b/open_strix/tools.py index 1640444..1f808a7 100644 --- a/open_strix/tools.py +++ b/open_strix/tools.py @@ -407,8 +407,10 @@ async def send_message( reacted=hard_stop_reacted, ) raise SendMessageCircuitBreakerStop( - "send_message hard stop: detected repeated near-duplicate loop. " - "Turn terminated at streak=10 for safety.", + "send_message hard stop: repeated near-duplicate loop at streak=10. " + "This turn is terminated for safety. Next turn: reflect on what went " + "wrong before resuming — consider using 5 Whys or a completely " + "different approach instead of retrying.", ) self.log_event( @@ -421,8 +423,14 @@ async def send_message( ) return ( "Loop detected in send_message calls. Message delivery is paused for this turn " - "to prevent an infinite output loop. Stop repeating similar messages immediately, " - "change strategy, and finish the turn safely." + "to prevent an infinite output loop.\n\n" + "Before retrying, reflect:\n" + "1. What were you trying to accomplish? Did the approach work?\n" + "2. What is a COMPLETELY DIFFERENT way to achieve this?\n" + "3. If you have a 5 Whys or root-cause analysis skill, use it on why " + "this approach failed before trying again.\n\n" + "Do not retry the same action. Think creatively about an alternative, " + "or finish the turn safely." ) sent, sent_message_id, sent_chunks = await self._send_channel_message( From e927448bd909e31a6d27477f5f012937c97af0f7 Mon Sep 17 00:00:00 2001 From: "Strix (Claude Opus 4.6)" Date: Fri, 10 Apr 2026 13:48:01 +0000 Subject: [PATCH 2/2] feat: add warning at streak 7 before hard stop at 10 Tim's feedback: warn with reflection guidance BEFORE the hard stop, not just at the point of termination. Now three tiers: - Streak 3-6: simple "loop detected, paused" message - Streak 7-9: WARNING with countdown + full reflection guidance - Streak 10: hard stop terminates the turn The reflection content (try something different, use 5 Whys) now appears at the warning tier where the agent can still act on it. Co-Authored-By: Claude Opus 4.6 --- open_strix/app.py | 2 ++ open_strix/tools.py | 32 ++++++++++++++++++++++++-------- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/open_strix/app.py b/open_strix/app.py index d97676b..bdadda7 100644 --- a/open_strix/app.py +++ b/open_strix/app.py @@ -61,6 +61,7 @@ SEND_MESSAGE_LOOP_HARD_LIMIT, SEND_MESSAGE_LOOP_SIMILARITY_THRESHOLD, SEND_MESSAGE_LOOP_SOFT_LIMIT, + SEND_MESSAGE_LOOP_WARN_LIMIT, SendMessageCircuitBreakerStop, ToolsMixin, ) @@ -362,6 +363,7 @@ def __init__(self, home: Path) -> None: self.worker_task: asyncio.Task[Any] | None = None self._current_turn_sent_messages: list[tuple[str, str]] | None = None self.send_message_loop_soft_limit = SEND_MESSAGE_LOOP_SOFT_LIMIT + self.send_message_loop_warn_limit = SEND_MESSAGE_LOOP_WARN_LIMIT self.send_message_loop_hard_limit = SEND_MESSAGE_LOOP_HARD_LIMIT self.send_message_loop_similarity_threshold = SEND_MESSAGE_LOOP_SIMILARITY_THRESHOLD self._send_message_last_text_normalized: str | None = None diff --git a/open_strix/tools.py b/open_strix/tools.py index 1f808a7..c045cc2 100644 --- a/open_strix/tools.py +++ b/open_strix/tools.py @@ -26,6 +26,7 @@ DEFAULT_TAVILY_SEARCH_URL = "https://api.tavily.com/search" SHELL_OUTPUT_LIMIT_CHARS = 12_000 SEND_MESSAGE_LOOP_SOFT_LIMIT = 3 +SEND_MESSAGE_LOOP_WARN_LIMIT = 7 SEND_MESSAGE_LOOP_HARD_LIMIT = 10 SEND_MESSAGE_LOOP_SIMILARITY_THRESHOLD = 0.98 @@ -413,6 +414,28 @@ async def send_message( "different approach instead of retrying.", ) + if streak >= self.send_message_loop_warn_limit: + self.log_event( + "send_message_loop_warning", + tool="send_message", + channel_id=target_channel_id, + streak=streak, + similarity_ratio=round(similarity_ratio, 6), + reacted_warning=warning_reacted, + ) + return ( + f"WARNING: You have sent {streak} near-duplicate messages. " + f"Hard stop at {self.send_message_loop_hard_limit} — after that, " + "this turn will be terminated.\n\n" + "Before that happens, stop and reflect:\n" + "1. What were you trying to accomplish? Did the approach work?\n" + "2. What is a COMPLETELY DIFFERENT way to achieve this?\n" + "3. If you have a 5 Whys or root-cause analysis skill, use it on why " + "this approach failed before trying again.\n\n" + "Do not retry the same action. Think creatively about an alternative, " + "or finish the turn safely." + ) + self.log_event( "send_message_loop_detected", tool="send_message", @@ -423,14 +446,7 @@ async def send_message( ) return ( "Loop detected in send_message calls. Message delivery is paused for this turn " - "to prevent an infinite output loop.\n\n" - "Before retrying, reflect:\n" - "1. What were you trying to accomplish? Did the approach work?\n" - "2. What is a COMPLETELY DIFFERENT way to achieve this?\n" - "3. If you have a 5 Whys or root-cause analysis skill, use it on why " - "this approach failed before trying again.\n\n" - "Do not retry the same action. Think creatively about an alternative, " - "or finish the turn safely." + "to prevent an infinite output loop." ) sent, sent_message_id, sent_chunks = await self._send_channel_message(