Skip to main content
This document covers execute_tool_batch, which runs a list of tool calls the agent has already prepared — typically the output of a payload-builder tool — as a single step. Use it when the same tool must be called many times with different arguments. Without it the agent has to emit one call per item, which costs either a decision round trip per call or an unordered parallel fan-out. A fan-out is unsafe whenever the downstream system locks on something the calls share: creating four sales orders against one reference contract 170 ms apart fails three of them with Sales document ... is currently being processed.

Authentication and enablement

The tool makes no outbound calls of its own — every credential belongs to the tool being called, which is resolved exactly as it would be on a direct step (configured-tool bindings → integration discovery → secrets). It is off by default. Enable it per agent in the tool list. A batch can only execute tools the agent itself may run: the registry the agent was built with is the allowlist.

Inputs

Passing the list by reference

A batch’s calls list is often built by an earlier step. Rather than copying every entry into the batch call, name the step output that holds it:
The reference is resolved before the tool runs and arrives as a real array, so a 200-entry list costs one short argument instead of a full re-emission of the plan. The reference must be the whole value of the field. These are rejected before anything runs, each naming what arrived:

Ordering

sequential runs one call at a time, in the order given. It is the default because a batch may contain writes and “one at a time” is the only ordering that is safe without knowing anything about the tools involved. parallel runs calls concurrently, at most max_concurrency at once. serialize_on refines parallel mode and is the reason to prefer it over a blanket sequential. It names a path into each call’s args; calls whose value at that path is equal run one at a time relative to each other, while calls with different values run concurrently. Set it to whatever the downstream system locks on:
The first two share a contract and run in order; the third runs alongside them. A call whose serialize_on path resolves to nothing, or to an object or array, is treated as unconstrained.

Keys and resuming

Give every call with a side effect a stable key. A keyed call is executed at most once per task: if the batch is re-run after a partial failure, already-completed keys are reported as skipped with the step id of the original call instead of being repeated. Keys must be unique within a batch. Omit the key to opt out — appropriate for read-only calls where repetition is harmless.

Outputs

structuredContent is a summary plus one entry per call:
status is completed when every call landed, failed when none did, and partial otherwise. Per-call status is completed, failed, skipped (already run under that key), not_run (the batch stopped before reaching it) or awaiting (it ran and paused for review — await_id names the review). A call’s result is inlined only if it is under 4 KB and the batch’s total inlined payload is under 256 KB; over either, result_omitted is true. Nothing is lost — every call’s full result is on its own step, which step_id names.

Side effects

Each call is recorded as its own child step of the batch step, with:
  • a row in steps (already completed, so it is never re-dispatched or reaped),
  • its own step_usage row, priced under the called tool’s name rather than folded onto the batch,
  • a tool_execution trace in agent_logs, and
  • a feed card nested under the batch’s card.
So a batched call is bookkept exactly like a direct tool step.

What happens if the run is interrupted

A batch can be interrupted mid-flight — a deploy, a pod eviction, a crashed worker. The platform re-runs the batch step, so the batch always finishes; what matters is whether a call that already ran is repeated. The rule: a call that finished is never repeated. A call that was still running may be. When the batch restarts, it looks at what already completed in this task and skips those calls. The only call it cannot reason about is one that was in flight at the moment of the interruption — the request had left, the answer never came back, and there is no way to tell from this side whether it landed. That call runs again. So the number of operations that can be repeated by one interruption is the number of calls that were running at that instant: This is the reason to set key on anything with side effects, and the reason to prefer sequential for them. Without keys the batch has no way to recognise its own earlier work, so an interruption near the end of a forty-call batch repeats all thirty-nine that succeeded.
If the task itself stops or fails, the batch is not resumed at all and the remaining calls never run. Resumption applies to a live task.

Verified behaviour

Measured by killing the worker mid-batch at five different points in an eight-call run. Every run finished with eight operations performed and none repeated, skipping between one and five calls depending on how far it had got. The same test without keys performed eleven operations for the same eight calls.

If repeats are unacceptable

For work where even one repeat is too many — creating an order, issuing a payment, sending a message — the durable answer is for the receiving system to recognise the repeat: a uniqueness rule on a reference you already send, or an idempotency key if its API accepts one. Nothing this tool can do on its own closes the gap, because it cannot learn the outcome of a request it never got an answer to.

Limits and expected errors