Split unusable response loop control
Handle empty model responses and repeated response content in separate message-loop result extensions. Move the repeat retry log text into a framework prompt. Cover empty-response logging and repeated content with reasoning.
linkliti committed
Aug 24, 2026 at 22:07 UTC
dbbdd26cc54a1e433f6290ffe8420b77ea73d608
4 files changed
+66
-16
extensions/python/message_loop_result/_20_empty_response.py
renamed
+7
-14
@@ -1,4 +1,4 @@
1
-"""Handle completed model turns that should retry instead of dispatching tools."""
1
+"""Retry completed model turns with neither response nor reasoning."""
2
3
from __future__ import annotations
4
@@ -8,7 +8,7 @@ from helpers.extension import Extension
8
from helpers.print_style import PrintStyle
9
10
11
-class LoopControl(Extension):
11
+class EmptyResponse(Extension):
12
def execute(self, result_data: dict[str, Any] | None = None, **kwargs: Any) -> None:
13
if not self.agent or not isinstance(result_data, dict):
14
return
@@ -18,16 +18,10 @@ class LoopControl(Extension):
18
reasoning = getattr(llm_result, "reasoning", "")
19
if not isinstance(response, str) or not isinstance(reasoning, str):
20
return
21
-
22
- if not response.strip():
23
- if reasoning.strip():
24
- return
25
- warning = self.agent.read_prompt("fw.msg_empty_response.md")
26
- elif response != self.agent.loop_data.last_response:
21
+ if response.strip() or reasoning.strip():
22
return
28
- else:
29
- warning = self.agent.read_prompt("fw.msg_repeat.md")
23
24
+ warning = self.agent.read_prompt("fw.msg_empty_response.md")
25
log_item = self.agent.loop_data.params_temporary.get("log_item_generating")
26
assistant_message = self.agent.hist_add_ai_response(
27
response,
@@ -37,10 +31,9 @@ class LoopControl(Extension):
31
self.agent._remember_llm_result_state(llm_result, assistant_message)
32
warning_message = self.agent.hist_add_warning(message=warning)
33
PrintStyle(font_color="orange", padding=True).print(warning)
40
- log_content = warning
41
- if response:
42
- log_content = f"{self.agent.agent_name}: Repeated response detected. Retrying."
34
self.agent.context.log.log(
44
- type="warning", content=log_content, id=warning_message.id
35
+ type="warning",
36
+ content=f"{self.agent.agent_name}: {warning}",
37
+ id=warning_message.id,
38
)
39
result_data["skip_default_processing"] = True
extensions/python/message_loop_result/_30_repeat_response.py
new
+38
@@ -0,0 +1,38 @@
1
+"""Retry completed model turns that repeat the prior response."""
2
+
3
+from __future__ import annotations
4
+
5
+from typing import Any
6
+
7
+from helpers.extension import Extension
8
+from helpers.print_style import PrintStyle
9
+
10
+
11
+class RepeatResponse(Extension):
12
+ def execute(self, result_data: dict[str, Any] | None = None, **kwargs: Any) -> None:
13
+ if not self.agent or not isinstance(result_data, dict):
14
+ return
15
+
16
+ llm_result = result_data.get("llm_result")
17
+ response = getattr(llm_result, "response", "")
18
+ if not isinstance(response, str):
19
+ return
20
+ if not response.strip() or response != self.agent.loop_data.last_response:
21
+ return
22
+
23
+ warning = self.agent.read_prompt("fw.msg_repeat.md")
24
+ log_item = self.agent.loop_data.params_temporary.get("log_item_generating")
25
+ assistant_message = self.agent.hist_add_ai_response(
26
+ response,
27
+ id=log_item.id if log_item else "",
28
+ llm_result=llm_result,
29
+ )
30
+ self.agent._remember_llm_result_state(llm_result, assistant_message)
31
+ warning_message = self.agent.hist_add_warning(message=warning)
32
+ PrintStyle(font_color="orange", padding=True).print(warning)
33
+ self.agent.context.log.log(
34
+ type="warning",
35
+ content=f"{self.agent.agent_name}: {self.agent.read_prompt('fw.msg_repeat_response.md')}",
36
+ id=warning_message.id,
37
+ )
38
+ result_data["skip_default_processing"] = True
prompts/fw.msg_repeat_response.md
new
+1
@@ -0,0 +1 @@
1
+Repeated response detected. Retrying.
tests/test_message_loop_result.py
+20
-2
@@ -1,6 +1,7 @@
1
from types import SimpleNamespace
2
3
-from extensions.python.message_loop_result._20_loop_control import LoopControl
3
+from extensions.python.message_loop_result._20_empty_response import EmptyResponse
4
+from extensions.python.message_loop_result._30_repeat_response import RepeatResponse
5
6
7
class FakeAgent:
@@ -23,6 +24,7 @@ class FakeAgent:
24
return {
25
"fw.msg_empty_response.md": "empty",
26
"fw.msg_repeat.md": "repeat",
27
+ "fw.msg_repeat_response.md": "Repeated response detected. Retrying.",
28
}[name]
29
30
def hist_add_ai_response(self, response, **kwargs):
@@ -41,7 +43,8 @@ def _run(agent):
43
result_data = {
44
"llm_result": SimpleNamespace(response=agent.response, reasoning=agent.reasoning)
45
}
44
- LoopControl(agent).execute(result_data)
46
+ EmptyResponse(agent).execute(result_data)
47
+ RepeatResponse(agent).execute(result_data)
48
return result_data
49
50
@@ -51,6 +54,13 @@ def test_empty_result_skips_default_processing():
54
assert _run(agent)["skip_default_processing"] is True
55
assert agent.history == [""]
56
assert agent.warnings == ["empty"]
57
+ assert agent.logs == [
58
+ {
59
+ "type": "warning",
60
+ "content": "A0: empty",
61
+ "id": "warning",
62
+ }
63
+ ]
64
65
66
def test_repeat_skips_default_processing():
@@ -67,6 +77,14 @@ def test_repeat_skips_default_processing():
77
]
78
79
80
+def test_repeat_ignores_reasoning():
81
+ response = '{"tool_name":"response"}'
82
+ agent = FakeAgent(response, reasoning="thinking", last_response=response)
83
+
84
+ assert _run(agent)["skip_default_processing"] is True
85
+ assert agent.warnings == ["repeat"]
86
+
87
+
88
def test_result_with_reasoning_uses_default_processing():
89
agent = FakeAgent("", reasoning="thinking")
90