fix(response): reject malformed response arguments
Raise RepairableException when response lacks a top-level text or message string. The existing recovery path now warns the model to correct the call instead of exposing a KeyError, while preserving legacy message arguments.
Alessandro committed
Jul 23, 2026 at 09:05 UTC
d3a81ac65bbe9e50bf938a71bafa2e64485a946a
3 files changed
+55
-2
tests/test_response_tool_validation.py
new
+43
@@ -0,0 +1,43 @@
1
+import sys
2
+from pathlib import Path
3
+
4
+import pytest
5
+
6
+
7
+PROJECT_ROOT = Path(__file__).resolve().parents[1]
8
+if str(PROJECT_ROOT) not in sys.path:
9
+ sys.path.insert(0, str(PROJECT_ROOT))
10
+
11
+from helpers.errors import RepairableException
12
+from tools.response import ResponseTool
13
+
14
+
15
+@pytest.mark.asyncio
16
+@pytest.mark.parametrize("args", [{"text": "ok"}, {"message": "ok"}])
17
+async def test_response_tool_accepts_text_or_message(args) -> None:
18
+ tool = ResponseTool(None, "response", None, args, "", None)
19
+
20
+ response = await tool.execute()
21
+
22
+ assert response.message == "ok"
23
+ assert response.break_loop is True
24
+
25
+
26
+@pytest.mark.asyncio
27
+async def test_response_tool_rejects_nested_response_args() -> None:
28
+ tool = ResponseTool(
29
+ None,
30
+ "response",
31
+ None,
32
+ {
33
+ "thoughts": [],
34
+ "headline": "Providing exact payload",
35
+ "tool_name": "response",
36
+ "tool_args": {"text": "nested"},
37
+ },
38
+ "",
39
+ None,
40
+ )
41
+
42
+ with pytest.raises(RepairableException, match="top-level text or message"):
43
+ await tool.execute()
tools/response.py
+8
-1
@@ -1,10 +1,17 @@
1
+from helpers.errors import RepairableException
2
from helpers.tool import Tool, Response
3
4
5
class ResponseTool(Tool):
6
7
async def execute(self, **kwargs):
7
- return Response(message=self.args["text"] if "text" in self.args else self.args["message"], break_loop=True)
8
+ for key in ("text", "message"):
9
+ message = self.args.get(key)
10
+ if isinstance(message, str):
11
+ return Response(message=message, break_loop=True)
12
+ raise RepairableException(
13
+ "response tool requires a top-level text or message string argument"
14
+ )
15
16
async def before_execution(self, **kwargs):
17
# self.log = self.agent.context.log.log(type="response", heading=f"{self.agent.agent_name}: Responding", content=self.args.get("text", ""))
tools/response.py.dox.md
+4
-1
@@ -22,7 +22,10 @@
22
- Update this file whenever tool arguments, output shape, `break_loop` behavior, intervention handling, prompt instructions, or side effects change.
23
- `ResponseTool` is a `Tool`.
24
- `ResponseTool` defines `execute(...)`.
25
-- Imported dependency areas include: `helpers.tool`.
25
+- `ResponseTool` requires a top-level string `text` or legacy `message` argument.
26
+ Invalid arguments raise `RepairableException` so the agent can surface a correction
27
+ warning and retry rather than crash.
28
+- Imported dependency areas include: `helpers.errors`, `helpers.tool`.
29
30
## Key Concepts
31