oz0-plugin.yaml

Tool declarations

Look up the fields for tool declarations.

For: Plugin authors looking up manifest fields.

Look up the fields for tool declarations.

tools[]

Each tool is an MCP server. Set exactly one of mcp (your own code), package or url; package may also set mcp to name the command to run. A flow tool sets flow instead, and none of them.

id
string required
Lowercase letters, digits, dashes and underscores, unique in the plugin and in the tenant, and never __, which separates a tool from its function in the names models see (<tool>__<function>).Start it with the plugin's name, dashes as underscores, or with the name's first dash-separated segment (acme_github and acme_fetch in acme-github), so it cannot clash with another plugin's; oz0_ is reserved for Orchestrator Zero's verified plugins, and lint warns of a name that only looks like it (ozo_, 0z0_). Agent names follow the same rule with dashes.Models, policies and the audit see a function as <id>.<function>, and an address (<plugin>/<id>.<function>) only names its plugin, so an ID belongs to one plugin per tenant, as an agent's name does: an install whose tool ID another plugin of the tenant provides (in its installed version or a version waiting for its gate or rollout) is refused, naming that plugin.plugin lint checks one plugin on its own and cannot see the tenant; plugin preview and the install can. See Limits.
mcp
string[]
The command that starts the MCP server over stdio. Without package, it runs in the plugin's own environment, and python means that environment's Python. With package, it runs in the package's environment.
package
string
An existing MCP server: pypi:<name>[==<version>], npm:<name>[@<version>], oci:<registry>/<repository>@sha256:<digest> or binary:<url>. Unpinned PyPI and npm packages are a warning; the edge pins them at install time. An npm range or tag (@^1.2, @latest) is an error: name one version, or none. Every PyPI and npm package is also pinned by the hashes of its files, with every package it depends on, which nodes check when they install it (pinning and hashes). An oci: image must name its digest, and runs on nodes with Docker or Podman (container images). binary: is not run yet.
container
object
For an oci: tool only: memory_mb (default 512), cpus (default 1) and pids (default 256), the container's limits.
isolation
string
For an oci: tool: gvisor runs its container under gVisor's runsc, only on nodes labelled isolation.gvisor=ready, and never without it; the plugin's agents that call it require that label (Sandbox tier 2).For a command tool (mcp: or a package that is not an image), gvisor runs its server under the node's own rootless runsc, behind the egress proxy, only on nodes labelled isolation.gvisor-sandbox=ready; not with sandbox: set, and not for a remote tool (gVisor without Docker).microvm boots its image in a virtual machine of its own, experimental, only on nodes labelled isolation.microvm=ready, which need no Docker (microVMs). A microvm tool's env and its plugin's secrets cannot be named PATH, HOME, TMPDIR, a proxy's variable, or LD_*, DYLD_*, SMOLVM_* or OZ0_VMENV_*.
url
string
A remote MCP server over Streamable HTTP. http:// is a warning; use https://.
connection
string
A remote tool's (url) connection, one of this plugin's connections that lists the tool: each call carries the token the edge mints for it in its own session's Authorization header, never in _meta, and the server is listed with a token of it.The tool's url may then be https://{params.<name>}/<path>, its host (and port) from a param of the connection, https only, its path literal, on a connection with steps (which record the host when it is connected); the connection's scope names its functions or <tool>.*. It sends no Authorization header of its own and is not bound under requires.inject. See remote servers on connections.
functions
map
Overrides of a remote tool's functions, by the name its server lists each under (at most 256): annotations (readOnlyHint, destructiveHint, openWorldHint; they set what the server does not say and tighten what it says, and loosen only in an ai.oz0 listing of OZ's own index), output_schema (an object schema of at most 64 KB, which the node validates structured results against and cards are drawn from), usage (service, unit, units, cost_usd: what each successful call spends) and ai_label (a JSON pointer such as /body to the string argument the tenant's AI label goes at the start of).
headers
map
Headers a remote tool (url) sends with every request, such as Authorization: "Bearer ${SEARCH_TOKEN}". Each ${NAME} is a secret in requires.secrets, filled in on the node. A header that carries a credential must take it from a secret; at most 16 headers. Under a credential binding of that secret the node sends its placeholder, and its credential proxy puts the value on, on the bound host only. See headers of a remote server.
affinity
string
node (default): runs on the job's home node. any: runs on any node that has the plugin.
requires_approval
string[]
This tool's functions that a person must decide every time, as <id>.<function> (or <plugin>/<id>.<function> with this plugin's own name): every call to one waits for a decision (Approvals), whatever the tenant's tool policy says short of refusing it, and the install screen shows it as "Ask, set by the plugin", with no switch.Keep it for a call whose effect cannot be undone and reaches outside the tenant (sending mail, a merge), or one the system expects a named person to authorise. A write that should ask by default, which the installer may switch, is a rule of the plugin's policy instead.
manual
string[]
Skills of this plugin that say how to use the tool's functions well, by name or as <plugin>/<skill> with this plugin's own name: each one of the plugin's skills, listed once, at most 8. Another plugin's skill is refused ("a tool's manual is its own plugin's skills"). An agent granted any of the tool's functions gets them (A tool's manual).
approval
object
How this tool's calls are approved, when a person decides them: under requires_approval, the plugin's policy, or the tenant's own rules (Approvals).deadline: how long a call waits before it is denied, never approved, 1m to 720h (the agent's, then the tenant's, 24h when nobody sets one; a tenant rule wins).escalation: off (with escalation_reason, since it loosens oversight) or the ladder's steps, at most five, each {at: <fraction or "90%">, to: deciders | owner | admins | role:<role> | team:<name>}; the default reminds the people who may decide at a quarter, tells the agent's owner at half and the tenant's admins at three quarters.edit: false keeps a person from approving a call with other arguments. confirm: <argument> asks a person to type that argument's value before a call runs, for high-risk calls. deciders and not_starter say who may decide. plugin lint checks each.
env
map
Extra environment variables for the tool's process. Keys must be environment variable names, and not the dynamic loader's (LD_*, DYLD_*, GLIBC_TUNABLES, GCONV_PATH), PATH or the node's own (OZ0_*), which lint refuses and a node leaves out. OPENAI_BASE_URL and ANTHROPIC_BASE_URL may name a provider's public URL, never the node's loopback. An oci: tool's variables reach its container by a file; a value with a line break cannot, so under a name its container runtime reads on the host (DOCKER_HOST, CONTAINERS_*, HOME, XDG_* and the like) it is refused.
timeout
duration
How long one call of the tool may run, such as 35m: five minutes unless set, two hours at most. A call that runs longer fails.
sandbox
string
own for a tool that runs what it is given in a sandbox of its own, as pr-reviewer's run_tests runs a pull request's tests. In a job an outside event started, whose tools run in a sandbox of the job's, such a tool's calls run in the plugin's own server on the job's tier-1 home node instead, since a sandbox cannot start another.Only a plugin that holds nothing may say so: no requires.secrets, no optional_secrets, no connection that covers the tool, no egress, and its own mcp command; lint and installs refuse it otherwise, and the node and the edge check again.Only a call the job's workflow marked leaves the job's sandbox, and the edge checks each one and records it in the audit trail as tool.own_sandbox; a hook's call, an event's normaliser and a harness session's tool calls stay in the job's sandbox, where such a tool should refuse.The tool's server runs outside any sandbox as the node's user, so it must not parse what it is given either: unpack or read the input in the sandbox it starts. It takes up to four calls at once.host for a local tool that must see the node itself, its processes, mounts or services, as host-health does: on a Linux node with sandbox tier 1 every other plugin's server runs in a sandbox of its own, and this one runs as the node's user, next to the node's other processes.plugin lint warns. On a node with sandbox tier 1, where --plugin-sandbox strict is the default, such a tool does not start until the node's operator exempts its plugin (oz0-node run --plugin-sandbox-exempt <plugin>), and the same holds for own: a line of a manifest never takes a tool out of the sandbox by itself.The plugin's status and the node's findings say so. In a job an outside event started a host tool's calls are not made: they would run in the job's sandbox, where it could read only that sandbox. ::
approval.deciders
object
Who may decide the approvals of this tool's calls: role (operator, the default, or admin), teams (teams of the tenant, by name, at most 16) and people (accounts, as user:<name>, at most 32) and keys (the tenant's API keys that may decide, by key ID, at most 8: a tenant's own plugin's integration; no other key decides an approval). A decider holds at least the role and, when teams or people are named, is one of the people or in one of the teams. The edge enforces it on every decision (Who decides).
approval.not_starter
boolean
true refuses the job's starter as the one who approves (four eyes); they may still deny.
secret_arguments
map
The arguments of the tool's functions a model may write a {{secret:NAME}} placeholder into, by function, as JSON pointers (run: [/env/API_KEY]), at most 16 per function. A tenant admin binds a secret to one (oz0 tenant credential bind <tenant> NAME --function <id>.<function> --argument <pointer>); the node fills the value in inside the call and scrubs it from the result (Credential injection).
flow
string
A flow tool: the name of one of this plugin's flows, which the tool exposes as its one function, <id>.<flow>. An agent that lists the function decides whether to call it; its flow runs as a child job, each step through its own gates.Its description, arguments and result are the flow's description, input_schema (required, with an object at its root) and output_schema (a warning without one); its annotations are derived from the steps, never looser than the most consequential one, and it declares the action class oz0/class (host.read, host.write or host.destructive) when a step may be a host tool.It sets no mcp, package, url, env, container, headers, sandbox, timeout, limits, isolation, secret_arguments or affinity (its flow runs on its plugin's pool); requires_approval may name its function.Lint refuses a flow that is not in the plugin, a second flow tool of the same flow, and a flow tool that would reach itself. See Flows as tools.
limits
object
For a local tool (mcp, or a PyPI or npm package): what its server may use of a node in its plugin sandbox, memory_mb (at least 16), pids (processes and threads, at least 8) and cpus (0.01 or more).Unset, a server gets 1024 MiB, 256 and one CPU; a server that runs a browser, a JVM or a local model asks for more, such as limits: {memory_mb: 4096, pids: 2048}. The tenant's ceiling still caps it.plugin lint refuses it on a remote or OCI tool (an OCI tool has container) and warns that it does not apply with sandbox: host or own.
labels
object
What the tool's results are labelled when they say nothing in their _meta (taint and labels): source, the system its results come from (an unlabelled result then gets the tenant's default level, never the lowest), and untrusted: true for a tool whose results hold outside text although it stays inside the tenant, such as a mailbox. A result may say more in _meta under oz0/labels (building tools).
results.fit
boolean
true declares that the tool's functions whose input schema has an integer budget property beside a view of the views stub, summary, section and full follow the convention of results that fit a budget: they take view, budget and query, and answer with the biggest view that fits and a fit note under oz0/fit.The runtime then fills in a budget a model's call leaves out, before the tool policy and an approval see the call.plugin lint refuses it on a flow tool, warns on an empty results block, and, with a measured listing, warns when no function of the tool takes a budget and names each function with a budget but without such a view, for which the runtime fills in nothing.
uses_model.purpose
string
The tool's functions and hooks call a model, and why, in 1 to 200 characters. Each call then brings a key for the node's gateway path made for the call's job, in its MCP request's _meta under oz0/model; the model call is metered, budgeted and under the tenant's data rules like any model call of the job. An install needs the tenant's grant (plugin install --accept-model). Not on a remote or a flow tool (Calling a model from a hook or a tool).

Files between tools

A tool can hand another tool a file, such as ai.oz0/github's download_archive hands pr-reviewer's run_tests a repository's archive, without the bytes passing through a model, the job's history or either plugin's storage. The tool that makes the file returns it as an MCP embedded resource whose URI is oz0-file:sha256:<hex>, the SHA-256 of its bytes, and names that URI in its result.

The runtime checks the bytes against the name, keeps them on the edge (per tenant, at most 32 MiB, for 90 days after they were last stored, apart from the jobs' own payloads) and drops them from the result. A call whose argument is such a URI gets the file as a path of its own on the node, in the call's _meta under oz0/files ({"<uri>": "<path>"}), deleted when the call ends.

For a tool from a container image the path is under /oz0/files/, a folder the container gets read-only, and for one in a microVM under /work/.oz0-files/ in the folder the VM shares: the path is always one the tool opens where it runs. Only the job tree that stored the file reads it (with the later turns of its conversation, and a job a person with the content permission hands it to); any other job gets "not found" (who may read a file).

tools.listing.json

Not part of the manifest: a file next to it that plugin test and plugin pack --list-tools write and you commit, the plugin's measured tool listing. It holds, per tool, each function as a node records it for its snapshot (name as <tool>.<function>, the listing with its description, schemas and annotations, and its hash), and source_digest, the SHA-256 of the plugin's other files (without tools.amplification.json and sbom.cdx.json, which are written from them), which says when it is stale.

plugin inspect, plugin lint, the studio and the hub read it without running anything; the first node's snapshot still decides what nodes call. Do not edit it by hand: run plugin test again.

Back to oz0-plugin.yaml.

Copyright © 2026