oz0-plugin.yaml

Dependencies and cross-plugin access

Look up the fields for dependencies and cross-plugin access.

For: Plugin authors looking up manifest fields.

Look up the fields for dependencies and cross-plugin access.

requires

oz0
string
The Orchestrator Zero versions the plugin works with: one or more constraints such as >=0.4 or >=0.4, <0.6, using >=, <=, >, <, =, ^ or ~. The loader refuses a plugin whose constraint the running release does not satisfy: "this plugin needs Orchestrator Zero >=1.2; this is 1.0.0". A development build satisfies every constraint. Every key on this page ships with oz0/v1 in the first release, so no plugin needs requires.oz0 for one of them.
platforms
string[]
os/arch pairs, with os in linux, darwin, windows and arch in amd64, arm64. Empty means every platform.
secrets
string[]
Tenant secrets the plugin's processes get as environment variables of the same names, such as GITHUB_TOKEN: its tools, hooks and harnesses, on every node that runs it, held in memory only. A node does not start the plugin while one of them is not set in the tenant. See Secrets.
optional_secrets
string[]
Tenant secrets the plugin's processes get when the tenant has them, as secrets are given; the plugin starts without them, such as an API key a backend may or may not need.A value that is not secret (an address, a backend's name, a switch) is a setting: declare it under settings, below, because every secret's value is refused in tool calls (the secret scan).Each name starts with the plugin's name in capitals, dashes as underscores, and an underscore (WEB_SEARCH_URL for web, CVE_TRIAGE_OSV_API_URL for cve-triage), so a plugin never reads another's secret this way; a secret two plugins share goes under secrets.Plugin names nest (web, web-search), so a name is the plugin's of the longest prefix: WEB_SEARCH_KEY is web-search's, and while both are installed web may not declare it (an install of either that would take the other's name is refused, and so is a private candidate, and nodes never get such a name).Only installed plugins own names: a private candidate of a plugin the tenant has not installed gets none that an installed plugin's prefix covers, and placing one changes nothing an installed plugin gets.The same holds for settings. A name cannot be in both lists. Installs, plugin inspect and hub listings name them, as secrets the plugin's processes read.
settings
object[]
The plugin's settings: per-tenant configuration that is not secret, such as a search backend's address or a mirror's path. Its processes get each as an environment variable of the same name, as they get secrets, on every node that runs it; dev and plugin test take them from your environment.A tenant admin sets them with oz0 setting set NAME VALUE (or on the web UI's Plugin settings tab), and operators and admins see their values.Unlike secrets they are stored and shown as they are and never scanned for in tool calls or requests, so a fetch of a page on the same address as a setting is made. Each entry has:
  • name: an environment variable name with the plugin's prefix, as for optional_secrets (WEB_SEARCH_URL for web);
  • description: what it is for, which the CLI and the web UI show;
  • default (optional): what the processes get while the tenant has not set it;
  • format (optional): url (http:// or https:// and a host), host (a name or address, optionally with :port), boolean (true, false, 1, 0, yes, no, on, off), one_of (with values: [...]), integer (a whole number, such as 10 or -3), number (a decimal, such as 0.5; no infinities, hex or underscores), duration (such as 30s, 5m or 1h30m, in the units ns, us, ms, s, m and h; never negative), or empty for any text;
  • min and max (optional, for integer, number and duration only): the lowest and highest value allowed, both included, written in the setting's own format (min: 1, max: 1h). Either may be left out. plugin lint refuses bounds that do not read, a min above the max, and a default or an example outside them;
  • group and label (optional, at most 64 bytes each): how the plugin's Configure tab lays the setting out. Settings of one group are shown together under its name, in the order the manifest lists them, after the settings without a group; label is the field's name there (the setting's name when empty). Neither changes what the plugin gets;
  • required (optional): the plugin cannot work without a value the tenant gives. Install names each one the tenant has not set, with the command that sets it, and nodes do not start the plugin until it is set, as with a missing secret. A required setting has no default, since a default would always fill it;
  • example (optional): a value shown as a hint where an admin types one, such as https://search.example.com. It must pass the format, and the plugin never gets it.
A value is checked against its format and bounds wherever it is set: oz0 setting set, the admin API, the console and a configuration file (oz0 plugin config apply). Every key here is optional, so a manifest without them stays valid. The plugin's processes get the value as it was written, as text: parse it in the plugin.A value is at most 4 KiB of text. A setting is no place for a secret. oz0 setting set refuses:
  • A value containing one of the tenant's secrets, checked on an edge.
  • A URL with a user name or password, or a credential in its query such as sig=, token= or key=.
  • A chat system's webhook URL.
  • A connection string with a password or account key.
  • A value in a known token format: GitHub, GitLab, Slack, AWS, model-provider keys, private keys, signed JWTs or Orchestrator Zero API keys.
Each refusal gives the fix: keep it a secret.plugin lint refuses a name that is also in secrets or optional_secrets, and the loader's and the node's variables (LD_*, DYLD_*, GLIBC_TUNABLES, GCONV_PATH, OZ0_*), and warns on one that reads like a secret's (a word that holds SECRET, PWD, DSN, BEARER, COOKIE, JWT, PRIVATE, HMAC, SIGNING, SIGNATURE or WEBHOOK, starts or ends with KEY, TOKEN or PASS or their plurals, or is one such as AUTH, BASICAUTH, SAS, SIG, CREDS, SALT, CONNECTION_STRING or a last SESSION: APIKEYS does).A name your plugin read as an optional secret before becomes a setting by itself when it reads like configuration (its last word is one such as URL, HOST, BACKEND, REGION, MODE or TIMEOUT, and no word reads like a secret's): a tenant's old secret of that name is copied to the setting, and stays a secret until its admin deletes it (Secrets).A credential binding and a {{secret:NAME}} placeholder never take a setting. Installs, plugin inspect ("Reads these settings: ...") and hub listings name them.
requires:
  optional_secrets: [WEB_SEARCH_KEY]
  settings:
    - name: WEB_SEARCH_URL
      description: The search backend's base URL
      format: url
      required: true
      example: https://search.example.com
    - name: WEB_SEARCH_BACKEND
      description: searxng or brave
      format: one_of
      values: [searxng, brave]
    - name: WEB_SEARCH_RESULTS
      description: How many results a search returns
      format: integer
      min: 1
      max: 50
      default: "10"
      group: Results
      label: Results per search
    - name: WEB_SEARCH_TIMEOUT
      description: How long a search may take
      format: duration
      min: 1s
      max: 2m
      default: 20s
      group: Results
A plugin that read such a value as an optional secret in an earlier version can move it here in a new version: a tenant's old secret of that name becomes the setting by itself, and is deleted once no installed version reads it as a secret (Secrets).
inject
map
Credential bindings of the plugin's own secrets, by secret: a list of the hosts each goes to, each with host (a name or *.domain, inside the plugin's egress; a wildcard over a public suffix such as *.github.io is refused, except for SigV4 signing, and a wildcard covers only the hosts its domain's owner holds), port (443), one of header with format ("Bearer {secret}"), basic_user or query, and optionally methods, path_prefix and shape (the placeholder's look, such as ghp_{36}).The plugin's servers then hold a placeholder of the secret, and the node's egress proxy puts the value on requests to the host only. Only a secret in secrets or the plugin's own optional_secrets, never a setting.A key may also be one of the plugin's connections by its name (github:): each covered call then gets the connection's token sealed to the node, and the tool holds a placeholder of it (Bind a connection's token). See Credential injection.
models
object
How the plugin's tools reach models without a key of their own; either needs the tenant's grant at install (plugin install --accept-model), and every such call is a model call of the job that called the tool (Tools that need a model):
  • sampling: an alias name (default, fast, smart or one of the plugin's own, which the operator maps). The runtime offers the plugin's servers MCP sampling and answers their requests with that alias; tool policy matches them as <tool>.sampling.
  • gateway: the model APIs the plugin's local servers call through the node's gateway path with each call's token in _meta under oz0/gateway: anthropic-messages, openai-chat, openai-responses, google-generate-content or openai-embeddings, each once. Not for a plugin whose tools are all remote.
A verified plugin names no provider's key in secrets or optional_secrets (house rule OZ-S017).
secrets_from
string
How the plugin's MCP servers get their secrets: env (the default), as environment variables, or fd, as one JSON object on an inherited file descriptor whose number is in OZ0_SECRETS_FD, with no secret in the server's environment, which every process of the node's user could read in /proc/<pid>/environ for the server's whole life. Read it with the plugin SDK's oz0.secrets (secrets.load(), then secrets.require("NAME")), which falls back to the environment. Harness sessions, and every server on a Windows node, get them as environment variables either way.

uses

What the plugin's agents and flows use of other plugins, by the used plugin's name (its manifest's name, not its hub name). From v1.0 they reach their own plugin's tools and agents, oz.*, and what this lists, once a tenant admin grants it at install.

oz0-plugin.yaml
uses:
  github:
    version: ">=1.0, <2"
    source: hub:ai.oz0/github
    tools: [oz0_github.get_pull_request, oz0_github.merge_pull_request]
    agents: [oz0-github]
version
string
The versions of the used plugin this one works with: >=1.0, <2, ^1.2, ~1.2 or 1.4.0. Empty takes any. An install is refused when the tenant's version is outside it, and once this plugin is installed, so is an install or update of the used plugin outside it, or without a function, agent or skill this one is granted, unless forced with a reason (updates and the plugins that use them). Give it an upper bound and test both ends (test both ends of a range); a verified capability must (OZ-S021).
source
string
Where an install gets the used plugin when the tenant does not have it: hub:<name>[@<version>], a Git URL, oci://... or mcp:<name>. Without it, a trusted plugin index that lists a plugin of that name in the range. The admin's --dependency wins over it.
tools
string[]
The used plugin's functions, <tool>.<function>, or <tool>.* for every function of a tool. Not oz.* (built-ins need no grant) and not this plugin's own tools. An item may be the function's address, github/oz0_github.get_pull_request, which must name this entry's plugin; the same holds for agents and skills.
agents
string[]
The used plugin's agents this plugin's agents and flows may start as child jobs (delegation.allow, flow steps, outcome.reviewer, best_of.agents) or ask as colleagues (colleagues.ask).
skills
string[]
The used plugin's skills this plugin's agents may load, by name, or * for every one. An agent names them under its own skills:; their text is untrusted input, which taints the job that reads it.
optional
boolean
true when the plugin works without the used one, such as a capability that works with whichever of several ticket systems a tenant has. An install then asks for this entry's grant only when the tenant has the used plugin, in a version the range takes and not retired (otherwise it warns why and goes on without it), and never installs it (unless the admin names it with --dependency); installing the used plugin later says what this plugin asks of it, and oz0 plugin grant <plugin> --accept-uses <digest> grants it then.Removing an optionally used plugin is not refused for this plugin: the removal takes it out of this plugin's grant.A flow step that needs it runs only while it is granted: write when: '"<plugin>" in uses' (uses in expressions); plugin lint warns about a step without one. False by default.

plugin lint checks the shape, and warns about an agent's tool, a flow's tool step, a delegation, a reviewer, a candidate, a colleague or a flow's agent step that names another plugin's tool or agent without uses: listing it, with the address to write. An address whose plugin uses: does not list is an error that gives the uses: line to add. A flow step whose agent is a ${{ expression }} reaches only agents it was granted at run time.

oz0-plugin.yaml
uses:
  email:
    version: ">=1.0, <2"
    tools: [oz0_email.send]
  servicenow:
    version: ">=1.0, <2"
    optional: true          # asked for only where the tenant has ServiceNow
    tools: [servicenow.get_incident, servicenow.update_incident]

A used plugin may have a uses: of its own: an install of this plugin grants the whole tree as one decision and installs what the tenant lacks, bottom-up. What an admin granted can be narrowed or revoked later (oz0 plugin revoke); the next install of a version that still lists it asks again.

Addresses

Every part has one address, <plugin>/<part>, and every field that names a part takes it beside the bare name:

PartAddressFields
A functiongithub/oz0_github.get_file, with * where a field takes a pattern (github/oz0_github.*)an agent's tools, a flow step's tool, a hook's call (own only) and match.tools, tools[].requires_approval (own only), uses.<plugin>.tools, policy[].match (own only)
An agent or a flowgithub/oz0-github, pr-reviewer/reviewan agent's delegation.allow, delegation.best_of.agents, handoffs (own only), colleagues.ask, outcome.reviewer, best_of.agents and conversation.continue_from; a flow step's agent and best_of.agents; a hook's match.agents; tools[].flow (own only); uses.<plugin>.agents; an eval's agent and flow
A skillweb/research, web/*an agent's skills, tools[].manual (own only), uses.<plugin>.skills
  • A bare name means this plugin's part. A bare name of another plugin's function or agent still works, since names are unique in a tenant, and plugin lint warns (OZ-S019): an error for verified plugins. plugin lint --fix writes the address.
  • The plugin in an address is this plugin or one listed under uses:. Another is an error that gives the uses: line to add. A field marked "own only" takes only this plugin's name. A hook's match.tools and match.agents may name any plugin: a match only watches calls, and a uses: entry would also let the plugin's agents make them.
  • What nodes see does not change. The loader writes each address's bare form into the bundle nodes get, so running jobs, recorded histories and older nodes see the names they saw before; the plugin's files keep what you wrote. Tool IDs and agent and flow names stay unique in a tenant: an address names the plugin, not a second part of the same name.
  • A flow step whose agent is a ${{ expression }} is checked when it renders.
  • Patterns and names are short. A function's name or pattern, in any field above that takes one (an agent's tools, built-ins such as oz.delegate too, a hook's match.tools, uses.<plugin>.tools, policy[].match), is at most 256 bytes, address included, and so is a flow step's agent: 256 bytes hold the longest address, <plugin>/<tool>.<function>.
    The edge matches these against tool calls, on every call, so loading a plugin refuses a longer one, at its place. A version installed before this bound that holds a longer one is left out where it is read (the edge serves none of it, and logs it) until it is installed again from a fixed manifest.

Back to oz0-plugin.yaml.

On this page
Copyright © 2026