agent-zero/tools/parallel.py
Alessandro f68792496b
Some checks failed
Build And Publish Docker Images / plan (push) Has been cancelled
Build And Publish Docker Images / build (push) Has been cancelled
Fix parallel await timeouts
Keep running parallel jobs alive when an await call reaches its timeout so agents can await the same job ids again instead of cancelling child work. Distinguish direct background tool workers from call_subordinate child chats so nested subordinate chats can use parallel normally while true worker recursion remains blocked. Update the parallel prompt, DOX notes, and regressions for non-destructive timeout and non-blocking collect semantics.
2026-06-13 18:36:41 +02:00

91 lines
3.4 KiB
Python

from helpers.tool import Tool, Response
from helpers import parallel_tools
from helpers.strings import sanitize_string
class ParallelTool(Tool):
async def before_execution(self, **kwargs):
self.log = None
async def after_execution(self, response: Response, **kwargs):
text = sanitize_string(response.message.strip())
self.agent.hist_add_tool_result(
self.name,
text,
**(response.additional or {}),
)
async def execute(self, **kwargs) -> Response:
args = {**self.args, **kwargs}
action = str(args.get("action") or "").strip().lower()
try:
timeout = parallel_tools.coerce_timeout(args.get("timeout"))
job_ids = parallel_tools.normalize_job_ids(args.get("job_ids"))
if action == "cancel":
results = await parallel_tools.cancel_parallel_jobs(self.agent, job_ids)
return Response(
message=parallel_tools.format_parallel_results(results),
break_loop=False,
)
raw_calls = parallel_tools.extract_tool_calls(args)
started_jobs = []
if raw_calls is not None:
calls = parallel_tools.normalize_parallel_tool_calls(raw_calls)
started_jobs = await parallel_tools.start_parallel_jobs(self.agent, calls)
started_job_ids = [job.id for job in started_jobs]
all_job_ids = [*job_ids, *started_job_ids]
if not all_job_ids:
return Response(
message=(
"Error: provide `tool_calls` to start parallel jobs, "
"or `job_ids` to await/cancel existing jobs."
),
break_loop=False,
)
wait_default = action not in {"start", "background", "collect"}
wait = parallel_tools.coerce_bool(args.get("wait"), wait_default)
if action in {"await", "wait"}:
wait = True
if not wait:
if not started_jobs and not job_ids:
return Response(
message="Error: `wait: false` requires `tool_calls` to start new jobs.",
break_loop=False,
)
if not job_ids:
return Response(
message=parallel_tools.format_started_jobs(started_jobs),
break_loop=False,
)
results = await parallel_tools.await_parallel_jobs(
self.agent,
all_job_ids,
timeout=timeout,
collect=True,
wait=False,
)
return Response(
message=parallel_tools.format_parallel_results(results),
break_loop=False,
)
results = await parallel_tools.await_parallel_jobs(
self.agent,
all_job_ids,
timeout=timeout,
collect=True,
wait=True,
)
return Response(
message=parallel_tools.format_parallel_results(results),
break_loop=False,
)
except ValueError as exc:
return Response(message=f"Error: {exc}", break_loop=False)