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

# Harness capabilities

> Every harness, and exactly what it can be admitted for: approvals, typed results, streaming deltas, whether the platform's own tools reach the model, and what an idle session holds.

[Harnesses](/docs/concepts/harnesses) explains what each loop *is*. This page is the matrix: what each one **can actually do today**, read off the catalogue and the loops themselves rather than off the design. Read it before you pick a harness for a [team](/docs/team/overview) — one of the differences below decides whether an agent can be a coordinator at all.

<Warning>
  The values below are the compile-time catalogue. `runnable` is the one field that varies by
  environment, so [`GET /v1/harnesses`](/docs/api/harnesses) is authoritative for the deploy you are
  talking to. Everything else on this page is a property of the loop and is the same everywhere.
</Warning>

## The matrix

|                               |    `pi`    |               `vetta`               |  `claude_code` |    `hermes`    |
| ----------------------------- | :--------: | :---------------------------------: | :------------: | :------------: |
| Status                        |    `ga`    |              `preview`              |    `preview`   |    `preview`   |
| Execution                     |  `sandbox` |            **`isolate`**            |    `sandbox`   |    `sandbox`   |
| Idle session holds            | a micro-VM |               nothing               |   a micro-VM   |   a micro-VM   |
| Runnable                      |   always   |                always               | needs an image | needs an image |
| `approvals`                   |   **yes**  |                  no                 |       no       |       no       |
| `structured_output`           |   **yes**  | declared, [not served](#known-gaps) |       no       |       no       |
| `streaming_deltas`            |   **yes**  |                  no                 |       no       |       no       |
| `injected_tools`              |   **yes**  |               **yes**               |     **yes**    |     **yes**    |
| Can be a team **member**      |     yes    |                 yes                 |       yes      |       yes      |
| Can be a team **coordinator** |   **yes**  |               **yes**               |     **yes**    |     **yes**    |

All four are `location: "hosted"` and `billing: "usage"`. There is no local harness today; the field keeps a `"local"` value because the refusal that would catch one is already written.

All four capability rows are on the wire, under `capabilities` on [`GET /v1/harnesses`](/docs/api/harnesses) — nothing on this page has to be hard-coded by a picker.

## What each row means

**Execution** is what an *idle* session costs, and it is not the same question as who operates the loop. `isolate` runs the loop in the session object itself — its workspace is rows in that session's own storage, so a session between turns holds no machine and bills storage only. `sandbox` runs the agent as a process on a [micro-VM](/docs/computer/index), which the session holds across turns. For long-horizon work that spends most of its life waiting, this is usually the deciding field.

**Runnable** answers "will a session on this start, on this deploy". A harness whose agent is a process in a box also needs a base image built in that environment's provider account, and staging may have one where production does not. `pi` and `vetta` need no image and are runnable wherever the code is deployed.

**`approvals`** — the loop can hold a tool call open and wait for a person. That covers both parks: a decision on [tool confirmations](/docs/api/sessions), and an [answer](/docs/concepts/session-operations#awaiting-answer-loop) to a question the agent asked with `ask_operator`. A harness with `no` can run neither, and the session is [refused at start](#nothing-is-a-quiet-best-effort) rather than silently downgraded.

A denied tool at least fails loudly. An unheld question does not: the harness would have to answer the call before anybody had been asked, handing the model an empty answer it cannot tell from a real one — so `ask_operator` is refused on the same predicate, up front, before any spend.

**`structured_output`** — the loop can enforce [`structured_output_required`](/docs/capabilities/structured-outputs), and can serve a [typed delegation](/docs/team/delegation#typed-results).

**`streaming_deltas`** — the loop emits incremental `message.delta` [events](/docs/api/events). With `no` you still get `message.completed`: the answer arrives whole rather than as it is written.

**`injected_tools`** — the platform's tools reach the loop's model. Every published harness declares `yes`, by one of two roads, and the road a harness takes changes two things about how the tools behave. It has [its own section](#platform-injected-tools).

## Platform-injected tools

This is the `injected_tools` capability. Every published harness declares it, and this section is about *how* — because the two roads are not identical.

Vetta contributes these families of tool to a turn, chosen per turn rather than fixed when the agent was made:

* **Team tools** — `send_to_agent`, `wait_for_agents`, `list_agents` for [delegation](/docs/team/delegation), and `board_read` / `board_write` for the [board](/docs/team/board).
* **[MCP servers](/docs/api/mcp)** declared on the agent.
* **[Connections](/docs/api/connections)** — the third-party integrations an agent acts through.
* **Social publishing** and your [apps](/docs/api/apps), including the tools an app declares for itself.
* **Vetta's own built-ins** — [web search and fetch, image and video generation](/docs/capabilities/tools#built-in-tools), skill disclosure, `publish_file`, the managed browser. **These reach `pi` and `vetta` only** (see the second card).

<CardGroup cols={2}>
  <Card title="pi and vetta — the loop is ours" icon="circle-check">
    We assemble the turn, so one of our tools is simply another entry in the toolset the model is offered. Every family above arrives, built-ins included, and an `ask` can be held for a decision where the harness declares `approvals`.
  </Card>

  <Card title="claude_code and hermes — the loop is a CLI" icon="circle-check">
    The agent is a binary running as a process in a micro-VM, driven by its command line, carrying **its own** toolset. It is given a session-scoped tool endpoint at the start of every turn and calls our tools over it, alongside its own. You configure nothing. Every family above except the built-ins travels this road.
  </Card>
</CardGroup>

**Your [`tools` policy](/docs/capabilities/tools) governs both roads, by the same names.** `<server>.<tool>` for an MCP server, `<connector>.<tool>` for a connection, the plain name for a built-in. On the second road a `deny` is not merely refused when called — the tool is never put in the list the agent is given, so the model cannot ask for something the policy forbids.

### Two limits on the second road

Both follow from `approvals: false`, and both are published rather than discovered at a turn.

<Warning>
  **A tool whose permission is `ask` is left out entirely, not gated.** Nothing in such a machine can
  hold a call open while a person decides: the agent's own prompt is closed, and the call is a single
  request that has to be answered now. This is why a session whose toolset carries an `ask` is
  **refused when it starts** on a harness declaring `approvals: false`, naming `harness` — the guard
  is what keeps the omission fail-closed instead of silent. The one case you meet without writing
  `ask` yourself is **social publishing**: `social.post` defaults to `ask`, so on these two harnesses
  it is not offered unless you set it to `allow` explicitly.
</Warning>

<Warning>
  **`wait_for_agents` does not pause the turn** on these two. A coordinator that fans work out and
  then calls it is not suspended mid-turn the way it is on `pi`; it parks once the turn goes idle, and
  the delegated threads open correctly either way. The cost is a few extra model calls between the
  fan-out and the park — an inefficiency, not a wrong answer.
</Warning>

### Coordinators and members

A [coordinator](/docs/team/coordinator) coordinates by calling `send_to_agent` and `board_write`, so it needs the team tools. **Every published harness can now be one.** Members were always unaffected: a member is handed a brief and answers it, and is never given `send_to_agent` at all — [delegation is one level deep](/docs/team/delegation#bounds). **A team may mix harnesses freely, at either level.**

The refusals that used to enforce this are still in the code and still fail closed; no published harness trips them today. A base whose loop could neither be handed our tools nor call for them would be refused as a coordinator at the agent save, naming `multiagent`, and refused a non-empty `mcp_servers`, naming `mcp_servers`; a coordinator stored on such a base would be refused at session start, naming `harness`.

## Nothing is a quiet best effort

Where a capability is gated, it is gated up front. A session asking for something its harness does not declare is refused when it starts, with a `400` naming `harness` — the half of the pair you can change:

| You asked for                                             | On a harness declaring     | What happens                                                                       |
| --------------------------------------------------------- | -------------------------- | ---------------------------------------------------------------------------------- |
| A toolset with `permission: "ask"`                        | `approvals: false`         | `400` at session start, `param: "harness"`                                         |
| An enabled `ask_operator`                                 | `approvals: false`         | `400` at session start, `param: "harness"`                                         |
| `structured_output_required`                              | `structured_output: false` | `400` at session start, `param: "harness"`                                         |
| A session on a stored coordinator                         | `injected_tools: false`    | `400` at session start, `param: "harness"` — no published harness declares `false` |
| A coordinator (`multiagent`)                              | `injected_tools: false`    | `validation_failed` at **save**, `param: "multiagent"` — same                      |
| A non-empty `mcp_servers`                                 | `injected_tools: false`    | `validation_failed` at **save**, `param: "mcp_servers"` — same                     |
| A roster member whose harness cannot serve its own config | —                          | `validation_failed` at **save**, naming the entry                                  |
| A roster member whose harness has no image here           | —                          | `validation_failed` at save: *not runnable on this deployment*                     |
| A session on a harness with no image here                 | —                          | `501` naming `harness`                                                             |

The alternative was worse and is why these exist: an `ask` toolset that silently became `allow` would remove a human-in-the-loop gate with no event and no error to notice it by.

## Known gaps

Stated here rather than left to be discovered at a turn.

<Warning>
  **`vetta` declares `structured_output: true` and its loop cannot produce one.** The catalogue
  publishes the capability, `list_agents` reports the member as `typed`, and every gate — session
  start, roster save, `send_to_agent` — admits the pairing. The loop has no submit path, so the
  promise can only fail at the turn. Until it lands: run `structured_output_required` on `pi`, and
  delegate to a `vetta` member **without** an `output_schema`, asking for the shape you want in the
  brief instead.
</Warning>

<Warning>
  **Vetta's own built-ins stop at the first road.** Web search and fetch, image and video generation,
  skill disclosure, `publish_file` and the managed browser are handed to a loop we host and are not
  published over the session-scoped endpoint, so a `claude_code` or `hermes` agent is never offered
  them. It has its CLI's own web, file and search tools instead. Nothing errors — the tool is simply
  absent — so a prompt that instructs the agent to call `publish_file` will read as though the model
  ignored it. Use `pi` or `vetta` when a built-in is load-bearing.
</Warning>

<Warning>
  **A tool call made over the session-scoped endpoint is not in the event stream on `hermes`.** That
  harness re-emits one whole answer rather than a running transcript, so `tool.started` /
  `tool.completed` are not published for *any* tool it calls, ours included. `claude_code` publishes
  them normally. Cost is metered on both.
</Warning>

<Warning>
  **`advisor` roster entries parse and are refused at save.** `{ "type": "advisor", "model": "…" }`
  is a legal shape on the wire and the CLI accepts the `advisor:<model>` spelling, but saving a roster
  that contains one fails with `advisor roster entries are not yet served`. See
  [Roster entry types](/docs/team/coordinator#advisor--parses-but-is-not-served).
</Warning>

## Reading it at runtime

The catalogue is served, so a picker never has to hard-code this page:

```ts theme={"system"}
const harnesses = await client.harnesses.list();

for (const h of harnesses.data) {
  console.log(h.base, h.execution, h.capabilities, h.runnable ? "available" : "not on this deploy");
}
```

`capabilities` carries all four booleans. What this page adds is the values in one place, and what each `false` — and, for `injected_tools`, each road — costs.

Full field reference: [`GET /v1/harnesses`](/docs/api/harnesses) and [`client.harnesses`](/docs/sdk/harnesses).

<Card title="Back to harnesses" icon="layer-group" href="/docs/concepts/harnesses">
  What each loop is, and how changing the field versions the agent.
</Card>
