Skip to main content
A team is only reproducible if its members can’t shift under it. Vetta guarantees that by snapshotting the whole coordinator config — including the roster — at create and update time, and pinning each member to a specific version.

Version pinning

Each id-referenced roster member is stored with a version:
  • Provide version to pin a specific one.
  • Omit it and Vetta pins the member’s current version at the moment you save the coordinator. The version is resolved server-side and written into the stored roster, so what comes back always carries an explicit pin. A member never floats.
  • Once pinned, a member does not auto-adopt later edits to that agent. To use a newer version, update the coordinator’s roster — an explicit, reviewable act.
This is what makes a team behave the same today and next month: editing a member in isolation cannot silently change how the coordinator runs.

What a save checks

Saving a roster is not a bookkeeping write. Every id-referenced member is loaded and checked, and a failure is refused at save, naming the entry and the reason — never left to surface as a broken delegation an hour into a run. One more check reads this agent rather than a member: a coordinator whose own harness declares injected_tools: false is refused, naming multiagent, because the team tools would never reach its model. No published harness declares false, so it stands as a guard rather than a rule you meet. See Mixing harnesses.

Mixing harnesses

Members may mix harnesses freely. A member’s harness is its own — fixed when that agent was created — and the coordinator’s harness does not have to match. A pi coordinator with a vetta member is an ordinary team, and so is the reverse. Any published harness can be a coordinator. Delegation is a tool: send_to_agent reaches the coordinator’s model either as part of the toolset Vetta assembles for the turn, or — where the agent is a CLI process in a machine of its own — over the session-scoped tool endpoint that machine is given. One caveat is worth knowing before you pick: on the second kind, wait_for_agents does not pause the turn in flight. The coordinator parks once the turn goes idle and the delegated threads open correctly; it costs a few extra model calls, not a wrong answer. A harness declaring injected_tools: false would still be refused as a coordinator, naming multiagent at save and harness at session start, but none is published. A member on the same harness is fine, and that distinction is the point: a member is delegated to. send_to_agent runs in the coordinator’s loop, and what reaches the member is a brief through its own ordinary session. It never needs a team tool of its own. What a member’s harness must satisfy is that it can serve its own configuration, and the check is at save:
  • Runnable on this deployment. A harness whose agent is a process in a machine needs a base image configured for it in that environment. Staging may have one where production does not, so this is a per-deployment fact, not a property of the harness — the refusal reads runs the … harness, which is not runnable on this deployment. Delegating to such a member would otherwise buy nothing but a retry ladder against a failure knowable at save time.
  • Typed results. If the member’s own config demands a structured output, its harness must declare structured_output. See Harness capabilities for which do — and for the one that declares it and cannot yet serve it.
  • Approvals. If the member’s toolset carries permission: "ask" anywhere, its harness must be able to hold a tool call for a decision. A harness without that seam does not prompt — it silently denies — so admitting the pair would drop an approval policy without a trace.
Which members can return a typed result is also visible to the coordinator at runtime: list_agents reports typed per member, and a send_to_agent carrying an output_schema for one that cannot is refused by name.
What a mixed team then costs while it runs is decided by each member’s harness independently — see What a member holds while it runs.

Managing the roster

Each --member is name@version, a bare name, or an agt_ id — plus self. The name spelling is resolved by the CLI against your org’s agents; the wire only ever carries { type: "agent", id, version? }. set replaces the roster outright and is the only one that takes --board; add and remove keep the rest of the roster as it stands, including its board setting. Removing the last member clears the roster to null, because an empty coordinator is not a team. Because the roster lives on the coordinator’s config, a roster change is an ordinary agent update: it creates a new coordinator version, under the same optimistic-concurrency guard. Every one of these commands is a read-modify-write under expected_version, so two people editing a roster from two terminals cannot silently overwrite each other.

Data residency

Vetta does not yet expose a per-agent data-region or residency control — there is no data_region field on an agent or a coordinator today, so there is nothing for a team to reconcile across its members. Everything runs in Vetta’s default region.
Configurable inference regions and a residency posture have not shipped. When region pinning does, a team will be required to keep a single region across the coordinator and every member (or leave it unset everywhere) so work is never split across regions by accident. Until then, do not assume any residency guarantee.

Limits

Next: context & budgets

What sessions share, what a member holds while it runs, and how spend flows across a team.