The matrix
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 — 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, 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, and an answer to a question the agent asked with ask_operator. A harness with no can run neither, and the session is refused at start 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, and can serve a typed delegation.
streaming_deltas — the loop emits incremental message.delta 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
This is theinjected_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_agentsfor delegation, andboard_read/board_writefor the board. - MCP servers declared on the agent.
- Connections — the third-party integrations an agent acts through.
- Social publishing and your apps, including the tools an app declares for itself.
- Vetta’s own built-ins — web search and fetch, image and video generation, skill disclosure,
publish_file, the managed browser. These reachpiandvettaonly (see the second card).
pi and vetta — the loop is ours
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.claude_code and hermes — the loop is a CLI
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.
tools policy 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 fromapprovals: false, and both are published rather than discovered at a turn.
Coordinators and members
A coordinator coordinates by callingsend_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. 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 a400 naming harness — the half of the pair you can change:
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.Reading it at runtime
The catalogue is served, so a picker never has to hard-code this page: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 and client.harnesses.
Back to harnesses
What each loop is, and how changing the field versions the agent.