> ## Documentation Index
> Fetch the complete documentation index at: https://agents.nanonets.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Execute Tool Batch

> Run a prepared list of tool calls as one step, in order or with bounded concurrency.

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

| Field             | Type    | Required | Description                                                                                                                                                                                                                                |
| ----------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `calls`           | array   | yes      | The calls to run, in order. At most 200. If an earlier step already produced the list, pass a reference to it (`"${VAR_1}.calls"`) instead of repeating every entry — see [Passing the list by reference](#passing-the-list-by-reference). |
| `calls[].tool`    | string  | yes      | Tool name, exactly as it appears in the agent's tool list (a configured display name or a base tool).                                                                                                                                      |
| `calls[].args`    | object  | no       | Arguments for that tool, exactly as on a direct call.                                                                                                                                                                                      |
| `calls[].key`     | string  | no       | Stable identity for the call. A keyed call runs **at most once per task**.                                                                                                                                                                 |
| `mode`            | string  | no       | `sequential` (default) or `parallel`.                                                                                                                                                                                                      |
| `max_concurrency` | integer | no       | Parallel mode only. Default 4, maximum 16.                                                                                                                                                                                                 |
| `serialize_on`    | string  | no       | Parallel mode only. Dotted path into each call's args.                                                                                                                                                                                     |
| `stop_on_error`   | boolean | no       | Stop starting further calls after the first failure. Default `true`.                                                                                                                                                                       |
| `include_results` | boolean | no       | Inline each call's result payload in this tool's own result. Default `true`.                                                                                                                                                               |

### 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:

```json theme={null}
{ "calls": "${VAR_1}.calls" }
```

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:

| What was passed                                                                                  | Why it is rejected                                                                                       |
| ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- |
| A reference embedded in a larger string (`"the plan: ${VAR_1}.calls"`)                           | Only a sole-content reference is inlined as JSON; embedded in a sentence it stays quoted text.           |
| A reference to a step output that does not exist on this task                                    | The unresolved `${VAR_N}` token is reported as a missing step output, not as a syntax error in the list. |
| The container object rather than the list (`"${VAR_1}"` where that output is `{"calls": [...]}`) | Pass the list itself — add the accessor.                                                                 |
| An empty string                                                                                  | There is no list to run.                                                                                 |

### 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:

```json theme={null}
{
  "mode": "parallel",
  "serialize_on": "reference_sd_document",
  "max_concurrency": 4,
  "calls": [
    { "tool": "create_sales_order", "key": "PO-4600008975-1", "args": { "reference_sd_document": "40040343", "...": "..." } },
    { "tool": "create_sales_order", "key": "PO-4600008975-2", "args": { "reference_sd_document": "40040343", "...": "..." } },
    { "tool": "create_sales_order", "key": "PO-4600009012-1", "args": { "reference_sd_document": "40041002", "...": "..." } }
  ]
}
```

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:

```json theme={null}
{
  "status": "partial",
  "total": 3, "succeeded": 2, "failed": 1, "skipped": 0, "not_run": 0,
  "results": [
    { "index": 0, "tool": "create_sales_order", "key": "PO-...-1", "status": "completed", "step_id": "…", "duration_ms": 8991, "result": { } },
    { "index": 1, "tool": "create_sales_order", "key": "PO-...-2", "status": "failed", "error": "sap api error (status 400): …" },
    { "index": 2, "tool": "create_sales_order", "key": "PO-...-1", "status": "skipped", "step_id": "…" }
  ]
}
```

`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**:

|                                                | Repeats at most                                                     |
| ---------------------------------------------- | ------------------------------------------------------------------- |
| `sequential`, keys set                         | **1**                                                               |
| `parallel` with `max_concurrency: N`, keys set | **N**                                                               |
| No keys, either mode                           | **everything already done** — the batch restarts from the beginning |

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.

<Note>
  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.
</Note>

### 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

| Condition                                                                                                              | Behaviour                                                                                                                                                                                                                                                                                            |
| ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| More than 200 calls                                                                                                    | Rejected before anything runs — split the work.                                                                                                                                                                                                                                                      |
| `calls` empty, duplicate `key`, unknown `mode`                                                                         | Rejected before anything runs.                                                                                                                                                                                                                                                                       |
| `serialize_on` given with `mode: "sequential"`                                                                         | Rejected: it would silently do nothing.                                                                                                                                                                                                                                                              |
| A pausing tool (`await_*`, `ask_user`, `task_complete`, `delegate_tasks`, `web_automation`)                            | That call fails. A tool that pauses or ends the agent has to run at the agent loop; nested in a batch it would register an await nothing resumes. Checked on the resolved base tool, so an alias cannot route around it.                                                                             |
| A call that **pauses for review** — extraction or review tools can stop for human validation depending on the document | That call is reported as `awaiting` with the review's id, and the batch **stops**, whatever `stop_on_error` says. The call itself completed; what the batch cannot do is wait for a review, so it does not run the remaining calls past one. Run these from the agent loop when a pause is expected. |
| `execute_tool_batch` inside a batch                                                                                    | That call fails. A batch of batches multiplies without bound, so neither the 200-call limit nor the concurrency width would hold.                                                                                                                                                                    |
| A native-loop tool (`run_code`, `continue`, `task_metadata`, `milestone_*`)                                            | That call fails — these are mechanisms of the agent loop itself, not callable work.                                                                                                                                                                                                                  |
| One call fails                                                                                                         | Recorded as a failed child step and reported in `results`. With `stop_on_error` (the default) no further calls start; in-flight ones finish.                                                                                                                                                         |
| The batch itself                                                                                                       | Does **not** return `isError` when calls fail. The step succeeded; its value is the per-call breakdown. Failing the step would send the whole batch back through step retry and repeat the calls that did land.                                                                                      |
| The step is cancelled or the worker is shut down                                                                       | No further calls start. Calls already made stand; re-issue the batch with the same keys to resume.                                                                                                                                                                                                   |
