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

# Coordinator

> Declare a team by adding a multiagent coordinator object and a roster of members to an agent's configuration.

A team is an [agent](/docs/concepts/agents) whose configuration includes a `multiagent` object with `type: "coordinator"` and an `agents` roster. The coordinator owns the top-level task and coordinates its roster the [two ways a team can](/docs/team/overview#two-ways-a-team-coordinates). It is an ordinary agent in every other respect: it has a model, a budget, a system prompt, and it runs inside a [session](/docs/concepts/sessions) you drive directly.

## Making one

The roster lives on the coordinator's own config, so a team is made in two steps — create an agent, then give it a roster. There are no roster flags on `vetta agent create`.

<CodeGroup>
  ```bash CLI theme={"system"}
  vetta agent create --name release-manager --model zai-org/GLM-5.2-FP8 --harness pi --budget-usd 100 --max-task-usd 10 --budget-period month --window immediate --system "You coordinate a software release. Delegate bounded briefs."

  vetta team set release-manager --member changelog-writer@4 --member release-notes-qa@2 --member self --board
  ```

  ```typescript TypeScript theme={"system"}
  const coordinator = await vetta.agents.create({
    name: "release-manager",
    model: "zai-org/GLM-5.2-FP8",
    harness: "pi",
    budget: { capUsd: 100, maxTaskUsd: 10, period: "month" },
    window: "immediate",
    system: "You coordinate a software release. Delegate bounded briefs.",
  });

  await vetta.agents.update(coordinator.id, {
    expected_version: coordinator.current_version,
    multiagent: {
      type: "coordinator",
      agents: [
        { type: "agent", id: "agt_9f2c…", version: 4 },
        { type: "agent", id: "agt_4a71…", version: 2 },
        { type: "self" },
      ],
      board: true,
    },
  });
  ```

  ```json Agent config theme={"system"}
  {
    "name": "release-manager",
    "model": "zai-org/GLM-5.2-FP8",
    "harness": "pi",
    "budget": { "cap_usd": 100, "max_task_usd": 10, "period": "month" },
    "window": "immediate",
    "system": "You coordinate a software release. Delegate bounded briefs.",
    "multiagent": {
      "type": "coordinator",
      "agents": [
        { "type": "agent", "id": "agt_9f2c…", "version": 4 },
        { "type": "agent", "id": "agt_4a71…", "version": 2 },
        { "type": "self" }
      ],
      "board": true
    }
  }
  ```
</CodeGroup>

The response echoes your configuration and, like any agent, adds `id`, `version`, `created_at`, and `updated_at`. Adding, removing, or repinning members produces a **new coordinator version** — the roster is part of the agent's config.

<Note>
  **Any published harness can be the coordinator's.** `send_to_agent` and `board_write` reach the
  coordinator's model either in the toolset Vetta assembles for the turn, or — on `claude_code` and
  `hermes`, which run a CLI inside a micro-VM on that CLI's own toolset — over a session-scoped tool
  endpoint the machine is given. One caveat on those two: `wait_for_agents` does not pause the turn,
  so the coordinator parks once the turn goes idle instead of mid-call. It costs a few extra model
  calls, not a wrong answer. See
  [Harness capabilities](/docs/concepts/harness-capabilities#two-limits-on-the-second-road).
</Note>

<Note>
  **On the wire a roster member is an `agt_` id.** `web-researcher@3` is a CLI spelling: [`vetta team`](/docs/cli/team) resolves the name against your org's agents and writes the id. The API rejects anything that is not an `agt_` id.
</Note>

## The coordinator's half of the board

With `board: true` the coordinator owns the team's one [company board](/docs/team/board), and every agent on its current roster is seated on it. The coordinator's job on that board is to **put the work on cards and hand them out** — not to do the work, and not to work a teammate's card for them.

* **A card is the hand-over; a message is a nudge.** A card carries a title, what "done" looks like in the notes, and `blocked_by` for anything that must finish first. It is durable, it is readable by every teammate and by you, and it is what starts a teammate: `board_write({ op: "assign", card_id, assignee: "writer" })` on a `todo` card with no open blocker, and a session of `writer` is started on it on the next tick of the board's clock. A `send_to_agent` message is private, is not on the record, and is the right tool for a question — not for the work.
* **Never move a teammate's card.** Claiming (`doing`) is the assignee's move, and closing (`done`, with a note) is the assignee's too. The coordinator reads the card, comments on it, reassigns it or unblocks it — and moves only its own. The board refuses the rest anyway (`already_claimed`), but a coordinator that respects the rule leaves a board a person can read: whoever holds a card is whoever moved it.
* **The board tells the coordinator what changed.** When a card reaches `done` or `blocked`, the coordinator gets one session per board with the [digest](/docs/team/board#the-owner-is-told-what-changed) of the cards that changed. That is its cue to read them, reassign or unblock what is stuck, and assign what comes next. A woken teammate that stopped without finishing does not vanish: its card is parked as `blocked` with a system comment for the coordinator to act on.

### Crew, not member

A card starts a **top-level session of the rostered agent** — a member of the crew, on that agent's own budget and its own concurrency, answerable to the board. A `send_to_agent({ wait: true })` starts a **member thread** of the coordinator's session — on the coordinator's budget, parking the coordinator until it answers. They are different tools for different work:

|                     | Card-woken **crew** session                                                        | Delegated **member** thread                                 |
| ------------------- | ---------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| Started by          | The board's clock, on `assign`                                                     | The coordinator's `send_to_agent`                           |
| Runs as             | The agent's own top-level session                                                  | A thread of the coordinator's session                       |
| Budget              | The assignee's own                                                                 | The coordinator's                                           |
| Coordinator's state | Untouched — it does not park                                                       | Parks on `awaiting_delegation`                              |
| The brief           | The card's notes and comments                                                      | The `message`                                               |
| The result          | On the card — `done` with a note, or `blocked` with a comment                      | Folded into the coordinator's turn                          |
| Reach for it when   | The work has an owner and a state, and you do not need the answer inside this turn | You need an answer to fold back into what you are doing now |

Both are seated on the board — a member thread reads and comments on the cards like anyone else on the roster — and neither delegates: a rostered agent carries no roster of its own, so `wait: true` is the coordinator's alone. See [Delegation](/docs/team/delegation#a-card-or-a-message) for the other side of this.

## Roster entry types

Each entry in `agents` is discriminated by a `type` field. A bare `"agt_…"` string is also accepted and normalises to the `agent` form at parse, so exactly one shape reaches storage.

### `agent` — reference another agent

```json theme={"system"}
{ "type": "agent", "id": "agt_9f2c…", "version": 3 }
```

<Note>
  Referenced members are fully independent agents with their own model, harness, tools, skills, and system prompt. Session-level configuration overrides applied to the coordinator's session do **not** reach id-referenced members — they run exactly as their pinned version defines.
</Note>

Every referenced member is checked **when you save the roster**, not when it is first delegated to. See [Mixing harnesses](/docs/team/rosters-and-versioning#mixing-harnesses) for what is checked and why a save can be refused.

### `self` — spawn copies of the coordinator

```json theme={"system"}
{ "type": "self" }
```

The coordinator can delegate to **copies of itself**. Each copy is a fresh session running the coordinator's own configuration — useful for recursively decomposing homogeneous work. Unlike id-referenced members, session-level configuration overrides applied at the coordinator's session **do** apply to `self` copies (and to the coordinator itself), because they share the coordinator's configuration.

<Warning>
  `self` does not create a nested team. A `self` copy runs the coordinator's *base* behavior on its own brief; it is a member, and a member's `send_to_agent` refuses `wait: true`. Only **one level** of delegation is permitted (see [Delegation](/docs/team/delegation#bounds)).
</Warning>

### `advisor` — parses, but is not served

```json theme={"system"}
{ "type": "advisor", "model": "zai-org/GLM-5.2-FP8" }
```

An advisor is intended as a consult-only voice for the coordinator's own session — a bare model with no agent behind it, so it names a `model` instead of an `id`.

<Warning>
  **It is not served today.** A roster containing an `advisor` entry is **refused at save**, with `advisor roster entries are not yet served`. The shape is on the wire so that a roster written for it still parses, and the CLI still accepts the `advisor:<model>` spelling, but nothing runs it. Do not design a team around it.
</Warning>

## Roster entry types at a glance

| Entry            | Shape                                   | Served today?            | Inherits session overrides? |
| ---------------- | --------------------------------------- | ------------------------ | --------------------------- |
| Referenced agent | `{ "type": "agent", "id", "version"? }` | Yes                      | No                          |
| Self copy        | `{ "type": "self" }`                    | Yes                      | Yes                         |
| Advisor          | `{ "type": "advisor", "model" }`        | **No — refused at save** | n/a                         |

## Configuration reference

### The `multiagent` field

It accepts an object, a boolean, or `null`, and all three arms mean something:

<ParamField path="multiagent" type="object | boolean | null">
  An **object** declares the team. `false` and `null` mean the agent is not a coordinator — `null` is how an existing roster is *cleared* on a `PATCH`, which `false` cannot say because `false` is also the create-time default. `true` is read as a coordinator with an empty roster and no board, which has nobody to delegate to; it is a degenerate case, not a shortcut.

  <Expandable title="multiagent">
    <ParamField path="type" type="string" required>
      Must be `"coordinator"`. The only supported team type.
    </ParamField>

    <ParamField path="agents" type="object[]" required>
      The roster: **1 to 20** entries, each discriminated by its own `type`. At most one `self` and at most one `advisor`.
    </ParamField>

    <ParamField path="board" type="boolean">
      Own the shared [company board](/docs/team/board). Defaults to false. When true, this agent's board exists and every agent on the current roster is seated on it — given `board_read` and `board_write`, and addressable as an `assignee` by name. Its columns are fixed, so there is nothing else to configure.
    </ParamField>
  </Expandable>
</ParamField>

### `agent` entry

<ParamField path="id" type="string" required>
  The `agt_` id of another agent in your organization to delegate to.
</ParamField>

<ParamField path="version" type="integer">
  The [version](/docs/concepts/agents#versioning) of the referenced agent to pin. **Omit** the version and Vetta pins the member's *current* version at the moment the coordinator is saved — the stored roster always carries an explicit pin, and it does not drift. See [Rosters & versioning](/docs/team/rosters-and-versioning).
</ParamField>

<Card title="Next: delegation" icon="share-nodes" href="/docs/team/delegation">
  How the coordinator invokes members, and the isolated sessions they run in.
</Card>
