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 1640444..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 @@ -407,8 +408,32 @@ 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.", + ) + + 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( @@ -421,8 +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. Stop repeating similar messages immediately, " - "change strategy, and finish the turn safely." + "to prevent an infinite output loop." ) sent, sent_message_id, sent_chunks = await self._send_channel_message(