# Build synthetic tools

> Source: https://elaichi.ai/docs/guides/toolboxes/synthetic-tools/

Combine several steps — sometimes across different products — into a single tool your agents can call.

**Where to find it:** **Toolboxes → Synthetic tools**

Example: look a customer up in the CRM, then open a support ticket for them. One decisive tool usually beats three chained calls the agent has to orchestrate itself.

Synthetic tools are gated by your plan’s **synthetic tools** feature (included on Gold and Black). Without it, the empty state explains they aren’t in your plan.

## Before you start

- A plan that includes synthetic tools
- `toolbox:create` (or equivalent access to build tools)
- At least one working connection to test against

## Design a synthetic tool

1. Open **Toolboxes → Synthetic tools** → **New synthetic tool**.
2. Name it and describe when an agent should reach for it (this becomes the advertised tool description).
3. Define the tool’s **input schema** (what the agent must provide).
4. **Add step** for each call. For every step set:

| Field | Purpose |
| --- | --- |
| **Step key** | Stable id other steps and templates refer to |
| **Connection** | Which account runs this step |
| **Tool** | Which tool on that connection to call |
| **Depends on** | Earlier steps that must finish first |
| **Repeat for each** (optional) | Run this step once per item of a list |
| **Advanced** (optional) | Parallel items, what to do if a call fails, and pages to read — see below |
| **Arguments template (JSONata)** | Build arguments from `{ input, steps, run }` |
| **Output template (JSONata)** (optional) | Shape what later steps see |

Independent steps (no dependency between them) **run in parallel**. Dependent steps wait. Cycles and unknown references are rejected when you save.

Step results are available as `steps.<key>` for later templates.

5. **Save changes**.

## Repeat a step for each item

Without **Repeat for each**, a step calls its tool once. Turn it on and give it a JSONata
expression producing a list, and the step calls its tool once per item instead — the agent
gets one tool, not one call per item to chain by hand.

**Worked example:** update every open card and leave a comment, in three steps instead of
3×N calls:

1. `find_cards` — no **Repeat for each**. Finds the open cards.
2. `update_card` — **Repeat for each** set to `steps.find_cards.result`. Its arguments
   template can now read `item` (the current card) and `index` (0-based), for example
   `{ "card_id": item.id, "status": "in_progress" }`.
3. `comment_card` — **Repeat for each** set to `steps.find_cards.result`, arguments
   `{ "card_id": item.id, "text": "Picked up" }`.

A `for_each` step's result becomes a **list**, in item order — `steps.update_card.result` is
every updated card's result, not one. A single item failing fails the step, named as
"item 3: …" so you know which one. **Repeating stops at 100 items** — split the input or
narrow the list first if it can run larger than that.

## Advanced step settings

Each step has an **Advanced** disclosure (collapsed unless you've already set something in it) with three controls:

- **Parallel items** — only shown when **Repeat for each** is on. How many items run at once (1–4, default 4). Lower it if the connection's tool rate-limits — running fewer items concurrently trades speed for staying under the vendor's limit.
- **If a call fails** — **Stop the run** (default) or **Keep going**. Keeping going leaves an empty slot for the failed item (or the failed step, on a step that doesn't repeat) and lists the errors instead of stopping everything for one bad record.
- **Pages to read** — for a list tool, how many pages to follow via the tool's own `next_cursor` (1–10, default 1; list tools only — any other tool is rejected on save) before the step's result is considered complete. A step that repeats over a list reports which items still had more pages (`truncated`). A single step that stops with pages left reports it as "1 cut short" and keeps the tool's `next_cursor`. When you use **Keep going** or read several pages, include the step's `errors`, `truncated` and `next_cursor` in the output template — an assistant that runs the tool over MCP sees only the output, so anything the template leaves out stays hidden. With no output template the raw steps are returned, and they carry all three.

## Only what changed

Every synthetic tool tracks `run.last_success_at` — the timestamp of the caller's last successful *real* run — and makes it available to every step's arguments template. A template that reads it can ask for just the new or changed records instead of the whole set every time, for example:

```jsonata
run.last_success_at ? { "updated_since": run.last_success_at } : {}
```

`run.last_success_at` is empty on the very first run, so the template leaves the filter out then and reads everything.

**A test run never moves this timestamp** — only a real run (through a toolbox, or the MCP tool itself with `test` left off) does, so you can test freely without losing your place. A real run also leaves the timestamp where it was when it did not finish the job: a step failed items under **Keep going**, or a step stopped with pages left unread. The next run then reads a little more, never less.

## Test run

After save, use the **Test run** panel:

1. Provide sample inputs matching your schema.
2. Choose **Run** against live connections.
3. Confirm **Test succeeded**, or fix the step where **Test stopped**.

Test execute runs the synthetic tool without needing it in a toolbox first. Results depend on real account data — use a safe connection when experimenting. A **Repeat for each** step shows how many times it ran (for example "Ran 12 times") next to its duration, plus how many failed if you set **If a call fails** to **Keep going**. If the run retried any calls, the result badge says how many times.

**The test run always uses the output template as currently written in the editor**, even if you haven't saved yet — change the template, hit **Run** again, and see the new shape before committing to it.

## Add to a toolbox

1. Open a template or a toolbox → **Add tools**.
2. Choose the **Synthetic tools** tab and pick your tool.
3. Tune the name and description. Defaults and schema overrides are
   rejected here — the synthetic tool defines its own schema — and frozen
   params do nothing on a synthetic entry, because each step supplies its
   own arguments.

It advertises its own input schema over MCP and executes its steps server-side through the same restriction and audit pipeline as normal tools.

## Good to know

- Each step runs with the access of the connection behind it.
- If a dependent step fails, the run stops there; independent parallel steps still follow the DAG.
- Restriction-blocked tools can’t be used as steps for callers who can’t reach them.
- Agents see one tool — they don’t manage the intermediate steps.

## Related

- [How toolboxes work](/guides/toolboxes/overview)
- [Templates](/guides/toolboxes/templates)
- [Toolboxes](/guides/toolboxes/toolboxes)
