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

# Compound tools

> Compose multi-step capabilities from your existing tools in .vendo/capabilities.json and run each step through guard with its own approval and audit.

A compound tool bundles a short sequence of your existing host and connector
tools into one capability the agent can call by name. Each step still runs
through `guard.bind`, so approvals, grants, breakers, and audit apply per
step — not to the compound as a whole.

Use compound tools when a task always spans the same few primitive calls
(fetch, transform, write) and you want the agent to reach for one intent name
instead of chaining several. Extraction never writes this file — you author it.

## When to use a compound

Reach for a compound when all three are true:

* The steps are stable and belong together as a single user intent.
* Each step is already a `route` or `openapi` tool in `.vendo/tools.json`, a
  connector tool, or a Vendo capability.
* You want the agent to see one descriptor instead of several.

If any step needs branching a JSONata expression cannot express, or the flow
is long-running or event-driven, use an automation instead.

## The capabilities file

Compounds live in `.vendo/capabilities.json`. A compound entry inside
`tools.json` is rejected at load with a pointer to this file.

```json theme={null}
{
  "format": "vendo/capabilities@1",
  "tools": [
    {
      "name": "host_invoice_send_reminder",
      "description": "Fetch an overdue invoice and email the customer a reminder.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "invoiceId": { "type": "string" }
        },
        "required": ["invoiceId"]
      },
      "risk": "write",
      "binding": {
        "kind": "compound",
        "steps": [
          {
            "id": "load",
            "tool": "host_invoices_get",
            "args": { "id": "{{ args.invoiceId }}" }
          },
          {
            "id": "notify",
            "if": "{{ load.status = 'overdue' }}",
            "tool": "host_email_send",
            "args": {
              "to": "{{ load.customerEmail }}",
              "template": "invoice-reminder",
              "vars": { "invoiceId": "{{ load.id }}", "amount": "{{ load.amount }}" }
            }
          }
        ]
      }
    }
  ]
}
```

A binding accepts 1–50 steps with unique `id` values. Each step targets one
existing tool by name. `args`, `if`, and `forEach` are JSONata expressions
evaluated against the root `args` and prior step results by id.

### Steps

Every step runs `if → forEach → args → invoke` in that order.

* `if` — skip the step when the expression is falsy.
* `forEach` — invoke the step once per item, up to 1000 items. The item is
  available to `args` as the step's context. Non-array values fail loudly.
* `args` — the JSON object passed to the target tool after evaluation.

Prior step results are available by step `id`. Root inputs are available as
`args`.

### Risk

The compound's declared `risk` must equal the maximum risk of its steps after
overrides apply. Under- or over-declaring risk quarantines the entry.

## Overrides

`.vendo/overrides.json` applies to compounds by name, field by field, the same
way it applies to extracted tools. Use overrides to tighten risk, mark a
compound `critical`, or disable it without editing the capabilities file.

```json theme={null}
{
  "format": "vendo/overrides@1",
  "tools": {
    "host_invoice_send_reminder": { "critical": true }
  }
}
```

## Per-step approvals

Every step crosses `guard.bind` on its own descriptor.

* A step that trips policy `ask` parks the whole compound with that step's
  approval card. The user sees the real step name, arguments, and risk.
* A standing grant for the step tool runs the walk under `decidedBy: "grant"`
  with the grant id in audit.
* `critical` on a step forces an ask on every run, even under a standing grant.
* The `maxWritesPerRun` breaker counts each write step and can trip mid-walk.
* Policy `block` on any step halts the compound before that step runs.

Approving or granting the compound name never exempts its steps. Away runs
follow the same rule: a compound-level grant leaves each step parked until it
has its own step-level grant, and `actAs` only ever receives step grants.

## Resume

When a step parks, the compound resumes from that step after the user decides.
Completed steps do not re-run — Vendo re-issues the parked step call verbatim
so the approval matches the original request.

Resume state is in-memory, bounded to 1000 entries per registry with a 60
minute TTL. A process restart re-walks the compound from step 0 on the next
call. With per-step approvals, a compound whose own policy decision is "ask"
re-asks on each resume.

## Quarantine

Vendo validates every compound at load. Entries that fail validation are
quarantined: the name is reserved, the compound never appears in descriptors
or dispatch, and boot succeeds. Common causes:

* A step targets an unknown tool, a disabled tool, or another compound.
* A step targets a `fn:` or registry-added tool.
* The declared `risk` is not the maximum of the step risks.
* Two capability sources declare the same name — this throws `conflict`
  instead of quarantining.

Fix the entry and it becomes available on the next load.
