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

# Delegation

> The first of a team's two coordination mechanisms: a point-to-point hand-off through built-in tools, each member running in its own isolated session.

Delegation is one of the [two ways a team coordinates](/docs/team/overview#two-ways-a-team-coordinates) — the point-to-point one. The coordinator does not call members over the network or embed them in a prompt. It delegates through **built-in tools** that Vetta injects into every team session, and each delegated call opens a new, isolated [session](/docs/concepts/sessions) for the member.

The other mechanism, the durable one, is the [company board](/docs/team/board). They compose: a card is the hand-over, a message is a nudge — see [A card, or a message?](#a-card-or-a-message) below.

## Which thread gets which tool

| Tool                               | Coordinator                 | Member                                                |
| ---------------------------------- | --------------------------- | ----------------------------------------------------- |
| `send_to_agent` with `wait: true`  | Yes                         | **Refused** (`no_delegation`)                         |
| `send_to_agent` with `wait: false` | Yes                         | Yes                                                   |
| `wait_for_agents`                  | Yes                         | **No**                                                |
| `list_agents`                      | Yes                         | Yes                                                   |
| `board_read` / `board_write`       | Yes, when its `board: true` | Yes, when seated on the coordinator's board by roster |

All of the first four follow the agent's [`handoffs`](/docs/capabilities/tools#handoffs): `false` and none is offered. A member that asks for `wait: true` is refused as a tool result — it cannot delegate and cannot be talked into delegating — which is how the one-level depth bound is enforced at runtime; the [save-time check](#bounds) is the other half. A member may still *hand work on* with `wait: false`, which starts a top-level session on the target's own budget rather than a thread of this team.

<Warning>
  **One line of the table reads differently on `claude_code` and `hermes`.** Those two run a CLI as a
  process in a micro-VM, on that CLI's own toolset, and reach these three tools over a session-scoped
  tool endpoint the machine is given rather than through the toolset Vetta assembles for a turn. They
  all work — a coordinator on either harness delegates, reads and writes the board normally — with one
  caveat: **`wait_for_agents` does not pause the turn** there. The coordinator parks once its turn
  goes idle and the delegated threads open correctly, so the cost is a few extra model calls between
  the fan-out and the park, not a wrong answer. See
  [Harness capabilities](/docs/concepts/harness-capabilities#two-limits-on-the-second-road).
</Warning>

## The delegation tools

<ResponseField name="send_to_agent" type="tool">
  Send one agent one message. **It returns immediately** — with the new session's id, `status`, and the `wait` it ran under — so a coordinator can start several and then wait once, rather than serialising them. With `wait: true` the member's answer arrives later, when the coordinator wakes; with `wait: false` nothing comes back, and the target's session is its own.

  <Expandable title="arguments">
    <ParamField path="agent" type="string" required>
      Which agent to send this to, by name. Under `handoffs: "team"` or a list it is a closed enum of those names, so the model cannot name someone outside it; under `true` it is any name in the organization, and an unknown one is refused as `not_found`.
    </ParamField>

    <ParamField path="message" type="string" required>
      Everything they need to do the work, up to 20,000 characters. This — and only this — becomes the target's opening context: they cannot see your conversation, your files, or your tools' results.
    </ParamField>

    <ParamField path="wait" type="boolean" required>
      `true` — **delegate**: the target runs as your teammate in a thread of this session, on your budget, at its roster-pinned version if it is on your roster, and `wait_for_agents` brings its answer back. `false` — **hand off**: the target starts a normal top-level session of its own, on its own budget, marked `metadata.handoff`, and you carry on. See [Handoffs](/docs/capabilities/tools#handoffs) for the depth, quota and key rules.
    </ParamField>

    <ParamField path="output_schema" type="object">
      `wait: true` only. A JSON Schema the member's final result must satisfy. Only valid for an agent `list_agents` reports as `typed`; see [Typed results](#typed-results). With `wait: false` it is refused as `wait_required`.
    </ParamField>

    <ParamField path="handoff_key" type="string">
      `wait: false` only. A stable name for this piece of work (up to 128 characters); a repeat under the same key while that session is still running answers `already_running` with the running session instead of starting a second one. With `wait: true` it is refused as `handoff_only`.
    </ParamField>

    <ParamField path="budget_micro_usd" type="integer">
      `wait: false` only. Caps the new session below the target's own budget. With `wait: true` it is refused as `handoff_only` — a teammate runs on your budget.
    </ParamField>
  </Expandable>
</ResponseField>

<ResponseField name="wait_for_agents" type="tool">
  Park until the delegations you name are done. The coordinator's turn ends here and resumes with the results. A session yielding here reports the [`awaiting_delegation`](/docs/concepts/sessions#stop-reasons) stop reason and nothing else — `awaiting_input` would claim a person can unblock it and `end_turn` would claim the task is finished, and a caller acts on both.

  <Expandable title="arguments">
    <ParamField path="members" type="string[]">
      Wait only for these teammates (at most 20). Omit to wait for everything you have delegated.
    </ParamField>

    <ParamField path="mode" type="string">
      `all` (the default) resumes when every one is done; `any` resumes on the first.
    </ParamField>
  </Expandable>

  With nothing outstanding it is an immediate no-op — `{ parked: false, outstanding: 0 }` — rather than a park, because parking a coordinator that has delegated nothing is a session that never wakes.

  The park is the turn's, not the call's. `send_to_agent` returns at once, so a coordinator can start four and wait once; the whole fan-out is dispatched when the turn ends, and this is what ends it. Calling it in a turn that delegated reports `awaiting_delegation`. Calling it in a turn that delegated *nothing* — you were resumed by one teammate and another is still running — parks the session `idle` instead, and the wake gate resumes you on the last one. Either way the turn stops here: it does not run on and it does not poll.
</ResponseField>

<ResponseField name="list_agents" type="tool">
  Who this agent may send work to, and how much of the concurrency budget is spent. Takes an optional `query` that narrows by a word in the name or description. Each agent comes back with its `name`, `description` (truncated to 160 characters), `project`, `team` — whether it is on this agent's roster, so `wait: true` runs its pinned version — and `typed` — whether its harness can honour an `output_schema` at all. Alongside them: `concurrency: { running, limit }`.
</ResponseField>

<Note>
  `typed` is derived for the model rather than left for it to infer from `harness`, because the model does not carry the harness catalogue and this is the one fact that decides whether a `send_to_agent` call may carry `output_schema`.
</Note>

## Nothing throws — a refusal is a tool result

Every way a delegation can fail to happen comes back as a tool **result** the model can read and act on, never as an exception that would end the turn.

| Refusal                   | What happened                                                                                                                                                                                                                                                                                                                                                        |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `unknown_agent`           | No agent by that name within this agent's `handoffs`.                                                                                                                                                                                                                                                                                                                |
| `no_structured_output`    | The call carried an `output_schema` and that agent's harness cannot produce one. Permanent — retrying will not help.                                                                                                                                                                                                                                                 |
| `at_capacity`             | Too many teammates are already running. The result names the running count and the limit, so the model knows to wait rather than guess.                                                                                                                                                                                                                              |
| `no_delegation`           | `wait: true` from a member, or on a deployment with no delegation seam wired at all.                                                                                                                                                                                                                                                                                 |
| `handoff_only`            | `handoff_key` or `budget_micro_usd` on a `wait: true` call.                                                                                                                                                                                                                                                                                                          |
| `wait_required`           | `output_schema` on a `wait: false` call.                                                                                                                                                                                                                                                                                                                             |
| `forbidden` / `not_found` | A `wait: false` call the [handoff door](/docs/capabilities/tools#handoffs) refused: the target is outside `handoffs` or is this agent itself, the chain is 4 deep, this session has started 8 handoffs already — or no agent of that name exists. A repeat under a running `handoff_key` is not a refusal: it answers `status: "already_running"` with that session's id. |

## Delegation is about context isolation

The point of delegation is **not** parallelism first and foremost — it is **context isolation and cost**.

When the coordinator calls `send_to_agent`, the member's session starts *fresh*: its opening context is only the `message`, not the coordinator's (possibly enormous) transcript. That has two consequences:

* **Focus.** The member reasons over a small, relevant context instead of wading through unrelated history.
* **Cost.** Every model call in that session carries far fewer tokens, so the same work is materially cheaper than expanding the coordinator's own context. See [Context & budgets](/docs/team/context-and-budgets).

<Tip>
  If you find yourself pasting large context into a single agent's prompt, that is often a signal to delegate: split the work so each member holds only the slice it needs.
</Tip>

## Isolated sessions, and what carries between them

Every member runs in its **own** session with its **own** conversation history. Two sessions never share conversation context: a member cannot see the coordinator's transcript or any sibling's, only the `brief` it was handed and its own turns.

That isolation is the point, but it means **the message is the whole hand-off**. There is no argument that continues an earlier member session — a delegation is one-shot. If a member needs to know what another member already did, that has to reach it some other way, and the way is the [board](/docs/team/board): a card's notes are readable by every thread on the team, which a transcript is not.

```text theme={"system"}
Coordinator ──send_to_agent(web-researcher, "find sources on X", wait: true)──▶ web-researcher's own session
            ◀── result, when the coordinator next wakes ───────────
```

## A card, or a message?

`send_to_agent` and the board both hand work to a teammate, and they are not interchangeable.

* **A card is the hand-over.** It is durable, on the record, and it starts the teammate: assign a `todo` card with no open blocker to an agent by name, and the board's clock starts a **top-level session of that agent** on it — on the agent's own budget, with the card's notes and comments as its brief, expected to end by moving the card to `done` with a note or `blocked` with a comment. That session is a member of the **crew**: seated on the coordinator's board by roster, answerable to the board, and not a thread of anyone's session. The coordinator does not park on it; it hears back through the board's [digest](/docs/team/board#the-owner-is-told-what-changed).
* **A message is a nudge.** `send_to_agent({ wait: true })` starts a **member thread** of your own session, on your budget, and you park until it answers. Nothing about it is on the record but the thread itself. It is the right tool for a question whose answer you need inside this turn — a fact to check, a shape to fill — not for work that has an owner and a state.
* **`wait: false` is neither.** It hands a message to a normal top-level session of the target's own, marked `metadata.handoff`, and nothing comes back. Use it to hand work *off* your team; use a card to hand work *within* it, where the teammate's progress should be readable.

A member thread and a crew session are both seated on the board, so either can read a card and comment on it. What a card gives a coordinator that a message cannot is the record: who holds it, whether it is `doing` or stuck and on what, and the note the holder left when it finished — none of which survives a transcript. The [coordinator page](/docs/team/coordinator#crew-not-member) sets the two side by side.

<Note>
  A coordinator that has delegated and parked wakes when its threads finish, not on a teammate's board write. Cards that reach `done` or `blocked` reach the coordinator as one digest session per board, on the next tick after the change.
</Note>

## Typed results

By default a member returns free-form text. Pass an `output_schema` on `send_to_agent` and the hand-off becomes a **typed** contract instead: the member's session must go [idle](/docs/concepts/sessions#lifecycle) producing a `structured_output` object that validates against the schema.

Not every harness can do this. `list_agents` reports `typed` per member, and a call carrying `output_schema` for a member that cannot is refused up front as `no_structured_output` — named, rather than quietly served as prose the coordinator's merge code then has to parse. The refusal is deliberately at *call* time and not at save time: whether any delegation will carry a schema is unknowable when the roster is written, and banning a text-only teammate outright would also ban the prose work it serves well.

```jsonc theme={"system"}
// what the coordinator emits, conceptually:
send_to_agent({
  agent: "doc_summarizer",
  message: "Summarize this contract. doc_id=c_4821",
  wait: true,
  output_schema: {
    type: "object",
    properties: {
      doc_id:   { type: "string" },
      summary:  { type: "string" },
      risk:     { type: "string", enum: ["low", "medium", "high"] }
    },
    required: ["doc_id", "summary", "risk"]
  }
})
```

This is what makes fan-out composable: when every member returns the same shape, the coordinator merges results deterministically rather than re-reading a pile of transcripts.

<Warning>
  **One catalogue entry is currently optimistic.** The `vetta` harness declares `structured_output` and is reported as `typed`, but its loop has no way to submit one — so a typed delegation to a `vetta` member is admitted by every gate and can then only fail at the turn. Until that lands, delegate to a `vetta` member without an `output_schema` and ask for the shape you want in the brief. The `pi` harness serves typed results today.
</Warning>

## Fan-out: several members at once

The coordinator may have several members running at the same time — each in its own session with its own isolated history — and then park once on `wait_for_agents`. Give each the same `output_schema` and merging the batch is just collecting typed objects.

```bash CLI theme={"system"}
vetta session run --agent release-manager --message "Summarize each of these 8 contracts, one per doc_summarizer copy, returning { doc_id, summary, risk }."
```

The coordinator's session events carry the fan-out: `thread.created` when a member session is spawned (with its `agent_id` and `member` name), and `thread.idle` as each result folds back. See [Context & budgets](/docs/team/context-and-budgets#events).

Each member is billed independently and quoted against the org balance before it runs.

## Bounds

Delegation is deliberately shallow and bounded to keep teams reproducible and cost-safe.

| Bound                       | Limit       | Notes                                                                                                                                                                                                                                       |
| --------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Unique agents per roster    | **20**      | Distinct members; `self` counts as one.                                                                                                                                                                                                     |
| Concurrent members per team | Capped      | A fixed platform limit, deliberately not published as a number here — it is enforced as a live count and no number is promised. The model is told the real figure by `list_agents` and by the `at_capacity` refusal, which is who needs it. |
| Delegation depth            | **1 level** | A member that itself declares a `multiagent` roster **fails validation** when added, and no member is ever handed the tool.                                                                                                                 |

<Warning>
  **One level only.** You cannot build a tree of coordinators. If you add a roster member whose config is itself a coordinator, the save is rejected. Use `self` for homogeneous recursion within a single level — a `self` copy runs the coordinator's base behavior on its own brief.
</Warning>

<Card title="Next: the team board" icon="kanban" href="/docs/team/board">
  The other mechanism — durable, broadcast, and the one that outlives the run.
</Card>
