Dependencies and cross-plugin access
For: Plugin authors looking up manifest fields.
Look up the fields for dependencies and cross-plugin access.
requires
>=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.os/arch pairs, with os in linux, darwin, windows and arch in amd64, arm64. Empty means every platform.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.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.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 foroptional_secrets(WEB_SEARCH_URLforweb);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://orhttps://and a host),host(a name or address, optionally with:port),boolean(true,false,1,0,yes,no,on,off),one_of(withvalues: [...]),integer(a whole number, such as10or-3),number(a decimal, such as0.5; no infinities, hex or underscores),duration(such as30s,5mor1h30m, in the unitsns,us,ms,s,mandh; never negative), or empty for any text;minandmax(optional, forinteger,numberanddurationonly): 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 lintrefuses bounds that do not read, aminabove themax, and adefaultor anexampleoutside them;groupandlabel(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;labelis 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 nodefault, since a default would always fill it;example(optional): a value shown as a hint where an admin types one, such ashttps://search.example.com. It must pass the format, and the plugin never gets it.
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=orkey=. - 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.
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
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.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,smartor 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_metaunderoz0/gateway:anthropic-messages,openai-chat,openai-responses,google-generate-contentoropenai-embeddings, each once. Not for a plugin whose tools are all remote.
secrets or optional_secrets (house rule OZ-S017).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.
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]
>=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).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.<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.delegation.allow, flow steps, outcome.reviewer, best_of.agents) or ask as colleagues (colleagues.ask).* for every one. An agent names them under its own skills:; their text is untrusted input, which taints the job that reads it.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.
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:
| Part | Address | Fields |
|---|---|---|
| A function | github/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 flow | github/oz0-github, pr-reviewer/review | an 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 skill | web/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 lintwarns (OZ-S019): an error for verified plugins.plugin lint --fixwrites the address. - The plugin in an address is this plugin or one listed under
uses:. Another is an error that gives theuses:line to add. A field marked "own only" takes only this plugin's name. A hook'smatch.toolsandmatch.agentsmay name any plugin: a match only watches calls, and auses: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 asoz.delegatetoo, a hook'smatch.tools,uses.<plugin>.tools,policy[].match), is at most 256 bytes, address included, and so is a flow step'sagent: 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.