Agent Zero AI framework
Block final responses from parallel calls
Keep response tool calls at the top level so they can end the parent message loop instead of forcing an unnecessary follow-up turn.\n\nDocument the parallel constraint and cover both runtime rejection and prompt guidance.
Alessandro committed
Jul 26, 2026 at 00:57 UTC
a76b0b5b7d5f0fd7ced8bdd0f942d87da4a424af
6 files changed
+10
-6
helpers/parallel_tools.py
+1
-1
@@ -31,7 +31,7 @@ CHILD_PARALLEL_TOOL_NAME_KEY = "parallel_tool_name"
31
DEFAULT_MAX_CALLS = 8
32
DEFAULT_TIMEOUT_SECONDS = 300
33
POLL_INTERVAL_SECONDS = 0.5
34
-DISALLOWED_PARALLEL_TOOLS = {"document_query"}
34
+DISALLOWED_PARALLEL_TOOLS = {"document_query", "response"}
35
36
TERMINAL_STATES = {"success", "error", "cancelled", "timeout"}
37
JobState = Literal["pending", "running", "success", "error", "cancelled", "timeout"]
helpers/parallel_tools.py.dox.md
+1
-1
@@ -25,7 +25,7 @@
25
- Wrapped tool-call items must use the same shape as normal tool calls: a tool name plus arguments.
26
- Normalization accepts full agent-reply-shaped objects when `tool_name` and `tool_args` are present; non-contract planning fields such as `thoughts` or `headline` are ignored.
27
- `tool_calls` should be an array, but normalization also accepts a valid JSON string encoding of that array to recover provider/model stringification.
28
-- Normalization rejects `document_query` inside `parallel` because document parsing and Q&A fan out into heavier worker/model paths that must run sequentially.
28
+- Normalization rejects `document_query` and `response` inside `parallel`: document parsing and Q&A must run sequentially, while `response` must remain top-level so it can end the message loop.
29
- `call_subordinate` jobs run in isolated child chat contexts tagged with parent-chat metadata; they must not be added to the scheduler task list and may use normal child-chat tools, including `parallel`.
30
- Direct tool jobs run in isolated background contexts and are blocked from recursively invoking `parallel`.
31
- Direct tool background context cleanup removes both the in-memory context and any transient chat folder left on disk.
prompts/agent.system.tool.parallel.md
+1
@@ -9,6 +9,7 @@ Rules:
9
- do not use for one simple call, dependent steps, ordered steps, shared mutable state, or state/tool-availability changes that must happen in the parent context
10
- never nest `parallel`
11
- Never include `document_query` in `tool_calls`; it is too heavy for parallel workers, so call it sequentially.
12
+- Call `response` only as a top-level tool so it ends the message loop; never wrap it inside `parallel.tool_calls`.
13
- `call_subordinate` inside `parallel` starts an isolated child chat under the parent chat, not a scheduler task
14
- use `wait: false` only when you will collect results later with `job_ids`
15
- if extras list running or ready parallel jobs, collect them before final synthesis
tests/test_parallel_tool.py
+5
-4
@@ -145,13 +145,14 @@ def test_normalize_parallel_tool_calls_rejects_nested_parallel() -> None:
145
)
146
147
148
-def test_normalize_parallel_tool_calls_rejects_document_query() -> None:
149
- with pytest.raises(ValueError, match="document_query.*parallel"):
148
+@pytest.mark.parametrize("tool_name", ["document_query", "response"])
149
+def test_normalize_parallel_tool_calls_rejects_disallowed_tools(tool_name: str) -> None:
150
+ with pytest.raises(ValueError, match=rf"{tool_name}.*parallel"):
151
parallel_tools.normalize_parallel_tool_calls(
152
[
153
{
153
- "tool_name": "document_query",
154
- "tool_args": {"document": "/tmp/report.pdf"},
154
+ "tool_name": tool_name,
155
+ "tool_args": {},
156
}
157
]
158
)
tests/test_tool_request_normalization.py
+1
@@ -174,3 +174,4 @@ def test_parallel_prompt_encourages_mixed_independent_batches() -> None:
174
assert "even when they use different tools" in prompt
175
assert "Do not split by tool type" in prompt
176
assert "Never include `document_query`" in prompt
177
+ assert "Call `response` only as a top-level tool" in prompt
tools/parallel.py.dox.md
+1
@@ -23,6 +23,7 @@
23
- The tool is intended for independent calls only; dependent operations remain sequential.
24
- Independent calls should share one batch even when they use different tools; split only for dependencies, ordering, shared mutable state, or parent-context state/tool-availability changes.
25
- `document_query` is intentionally excluded from wrapped calls because it is too heavy for parallel workers and must be called sequentially.
26
+- `response` is excluded because a wrapped response cannot end the parent message loop; final responses must be top-level calls.
27
- `action="start"` starts calls and optionally waits according to `wait`.
28
- `action="await"` waits for requested job IDs until completion or `timeout`; timeout returns running job handles without canceling them.
29
- `action="collect"` returns completed job results without waiting.