> ## 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 [team board](/docs/team/board). They compose, and the pattern that runs in production uses both.

## Which thread gets which tool

| Tool                         | Coordinator             | Member                  |
| ---------------------------- | ----------------------- | ----------------------- |
| `send_to_agent`              | Yes                     | **No**                  |
| `wait_for_agents`            | Yes                     | Yes                     |
| `list_agents`                | Yes                     | Yes                     |
| `board_read` / `board_write` | Yes, when `board: true` | Yes, when `board: true` |

`send_to_agent` is not merely refused for a member — it is **never constructed**, so a member cannot delegate, cannot be talked into delegating, and does not pay the tokens its schema would cost on every turn. That is how the one-level depth bound is enforced at runtime; the [save-time check](#bounds) is the other half.

<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">
  Hand one teammate one brief. **It returns immediately** — with the new thread's id and `status: "running"` — so a coordinator can start several and then wait once, rather than serialising them. The member's answer arrives later, when the coordinator wakes.

  <Expandable title="arguments">
    <ParamField path="member" type="string" required>
      Which teammate to hand this to. It is a closed enum built from the roster, so the model cannot name someone who is not on the team.
    </ParamField>

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

    <ParamField path="output_schema" type="object">
      A JSON Schema the member's final result must satisfy. Only valid for a member `list_agents` reports as `typed`; see [Typed results](#typed-results).
    </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 is on the team, and how much of the concurrency budget is spent. Each teammate comes back with its `name`, `id`, pinned `version`, `description` (truncated to 160 characters), `harness`, and `typed` — whether that 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_member`       | No teammate by that name.                                                                                                               |
| `no_structured_output` | The call carried an `output_schema` and that member'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`        | This deployment has no delegation seam wired at all.                                                                                    |

## 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 `brief`, 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 brief 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")──▶ web-researcher's own session
            ◀── result, when the coordinator next wakes ───────────
```

<Note>
  A member's **board write also wakes a parked coordinator**, even before that member's session finishes. So a member that discovers something the coordinator should act on now does not have to finish first — it comments on a card, and the coordinator gets a turn. That is the clearest case of the two mechanisms composing.
</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({
  member: "doc_summarizer",
  brief: "Summarize this contract. doc_id=c_4821",
  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>
