---
title: "Harnesses, events and channels"
description: "Look up the fields for harnesses, events and channels."
canonical_url: "https://docs.oz0.ai/reference/plugin-manifest/runtimes"
---
# Harnesses, events and channels

> Look up the fields for harnesses, events and channels.

For: Plugin authors looking up manifest fields.

Look up the fields for harnesses, events and channels.

## `harnesses[]`

A harness runs one session of an agent harness per job. Agents use it with `runtime: harness:<name>`. See [Harnesses](https://docs.oz0.ai/build/parts/harnesses).

<field-group>
<field name="name" type="string" :required="true">

The harness's name, as agents refer to it.

</field>

<field name="command" type="string[]" :required="true">

The command that starts one session, resolved like a tool's `mcp` command. It speaks the harness protocol in JSON lines over stdin and stdout.

</field>

<field name="permissions" type="boolean">

The harness asks before each tool call, so the job's hooks and approvals decide it. See [Write a harness for another tool](https://docs.oz0.ai/build/parts/harnesses/custom-harnesses#write-a-harness-for-another-tool).

</field>

<field name="tools" type="string[]">

The tools a session gets when its agent lists no `harness.tools`. For an ACP harness, ACP tool kinds; its default is every kind but `switch_mode`.

</field>

<field name="formats" type="string[]">

The model APIs a JSON-lines harness's sessions speak, preferred first: `anthropic-messages`, `openai-chat`, `openai-responses`, `google-generate-content`. Its agents may then ask for an alias (`model.use`): the edge resolves it only to a provider record that takes one of them, whatever the provider is called, and the session request carries the record's format in `formats`. An ACP harness lists them under `acp.formats` instead; a remote harness has none. `harness-claude` declares `[anthropic-messages]`.

</field>

<field name="effort" type="boolean">

The JSON-lines harness honours the session request's `effort`: the [reasoning effort](https://docs.oz0.ai/spend/models-and-keys/effort-and-cascades#reasoning-effort) the job's resolution decided for the session's model, one of `low`, `medium`, `high`, `xhigh` and `max`, or empty for the model's own. Without it, `plugin lint` warns that an agent's `model.effort` does nothing on the harness. An ACP harness needs no flag: the runtime sets an agent's thought-level option when it offers one. `harness-claude` sets it and passes the level to the Agent SDK's effort option.

</field>

<field name="protocol" type="string">

`acp` for an agent that speaks the [Agent Client Protocol](https://docs.oz0.ai/build/parts/harnesses/acp#agents-that-speak-acp), with the node runtime as its client, or `remote` for a [remote agent](https://docs.oz0.ai/build/apps/remote-agents)'s provider adapter, which agents use with `runtime: remote:<name>`. Empty means the JSON lines of the harness protocol.

</field>

<field name="package" type="string">

An existing program the harness runs, pinned to one version: `npm:<name>@<x.y.z>` or `pypi:<name>==<version>`. `command` then names its bin and arguments.

</field>

<field name="binaries" type="map">

A program's archive for each platform, such as `linux/amd64: {url: https://..., sha256: ...}`: `.tar.gz`, `.tgz`, `.tar.bz2` or `.zip`, checked against its SHA-256 when the node installs the plugin. `command[0]` is the program's path inside the archive.

</field>

<field name="acp" type="map">

Settings for an ACP harness's sessions:

- `formats`: the model APIs the agent speaks. Required.
- `env`: variables with `${model}`, `${base_url}`, `${api_key}` and the other placeholders filled in.
- `auth`: the authentication method to select.
- `mode`: the session mode to set.
- `needs`: client capabilities the agent cannot work without, `fs` and `terminal`. These are offered only in sandbox tier 1.
- `asks`: tool kinds the agent asks about before running them with these settings, or `*`. A routine's fire or shadow run needs every kind its session may use listed here.
- `cannot_ask`: why the agent cannot promise to ask about more. A refused fire or shadow run quotes this reason.
- `skills`: the folder its tool loads skills from, under the session's `${home}` or `${state}`, such as `${home}/.agents/skills`. The runtime links a read-only copy of the agent's [skills](https://docs.oz0.ai/build/parts/skills) there. Without it, the session gets `oz.skill` through its MCP server.

`env` cannot set `OZ0_*` or loader variables (`LD_*`, `DYLD_*`).

</field>
</field-group>

## `events[]`

See [Event sources](https://docs.oz0.ai/build/parts/event-sources).

<field-group>
<field name="name" type="string" :required="true">

The source's name in the tenant, in its URL: lowercase letters, digits and dashes, starting with a letter, at most 31.

</field>

<field name="description" type="string">

What the source delivers, for people reading `oz0 event sources`.

</field>

<field name="call" type="string" :required="true">

The function that normalises a delivery, `<tool>.<function>`, of one of this plugin's tools.

</field>

<field name="secret" type="string" :required="true">

The tenant secret deliveries are signed with or carry. The edge reads it; with scheme `plugin`, list it in `requires.secrets` too.

</field>

<field name="signature" type="map" :required="true">

`scheme` (`hmac-sha256`, `ed25519`, `jwt`, `token` or `plugin`), `header`, `prefix` and, for `hmac-sha256` and `ed25519`, `encoding` (`hex` or `base64`), `message` (what is signed, of `{body}` and `{timestamp}`), `timestamp` (the header with the signing time) and `max_age_s` (300). For `jwt`: `openid_url` or `jwks_url` (HTTPS), `key_hosts`, `issuers`, `algorithms` (`RS256` unless set), `max_age_s` (the clock skew on `exp` and `nbf`, at most 600), `claims` and `key_fields` (what the function gets in `delivery.verified`); the source's `secret` holds the audience. See [Bearer tokens](https://docs.oz0.ai/build/parts/event-sources#bearer-tokens-jwt).

</field>

<field name="delivery_header" type="string">

The header with the sender's ID for one delivery, such as `X-GitHub-Delivery`, kept with its record. Without it: `Idempotency-Key`, `X-Request-Id`. It never names the delivery: the body's SHA-256, which the signature covers, does.

</field>

<field name="types" type="string[]">

The event types the function gives.

</field>

<field name="max_body_kb" type="integer">

The largest delivery taken, at most and by default 1024.

</field>

<field name="replies" type="map[]">

What the edge answers at once: `when` (CEL over `body` and `headers`), `reply` (CEL), `status`, `route`. See [Answer at once](https://docs.oz0.ai/build/parts/event-sources/delivery-binding#answer-at-once).

</field>

<field name="bind" type="map">

What the edge checks of the function's events against the delivery: for `type`, `action`, `subject`, `text`, `attributes.<name>` or a chat message's `message.kind`, `workspace`, `room`, `thread`, `id`, `author.id`, `action`, `agent`, `private` and `direct`, a CEL expression over `body`, `headers`, `verified` and `sealed` that gives the field's value, or a list of the values of the delivery's events in the same order (one each; an empty list: no event); at most 32, each at most 4096 bytes.

Each event must be a row of its own, so one delivery gives no more events than its rows; a chat channel's delivery is one message, read as one row.

Routines, flows and waits match only bound fields, and a channel's source binds the message's fields, its thread and its text. With scheme `plugin` nothing is verified. See [Bind events to the delivery](https://docs.oz0.ai/build/parts/event-sources/delivery-binding#bind-events-to-the-delivery).

</field>

<field name="seal" type="object">

What the edge vouches for across a round trip through the other system, such as a form a click opened: `when` (CEL: which deliveries' facts it seals), `facts` (by name, CEL giving a string each; at most 16) and `from` (CEL: the seal a later delivery carries back). The function gets the seal as `delivery.seal`, and the bindings read a carried seal's facts as `sealed`; a seal opens for one hour, for its source. Not with scheme `plugin`. See [Carry a click through a form](https://docs.oz0.ai/build/parts/event-sources/delivery-binding#carry-a-click-through-a-form).

</field>
</field-group>

## `channels[]`

See [Chat channels](https://docs.oz0.ai/build/apps/chat-channels/build#build-a-channel).

<field-group>
<field name="name" type="string">

The channel's name in the tenant, which bindings name. The source's name unless set.

</field>

<field name="description" type="string">

Which chat system the channel speaks to.

</field>

<field name="events" type="string" :required="true">

The event source of this plugin that takes the chat system's deliveries.

</field>

<field name="post" type="string" :required="true">

The function that posts or updates a message, `<tool>.<function>` of one of this plugin's tools. Its errors with `(retryable` in them are posted again later.

</field>

<field name="endpoint" type="map">

The chat system names where replies go in each delivery: `claim` (a claim of the source's `jwt` signature, which a message's `endpoint` must equal) and `hosts` (the hosts it may be on, `*.<domain>` for any name under it that the domain's owner holds; a wildcard over a public suffix is refused). The edge keeps a message's endpoint only when both hold, and hands it to `post`.

</field>
</field-group>

[Back to oz0-plugin.yaml](https://docs.oz0.ai/reference/plugin-manifest).


## Sitemap

See the full [sitemap](https://docs.oz0.ai/sitemap.md) for all pages.
