---
title: "Dependencies and cross-plugin access"
description: "Look up the fields for dependencies and cross-plugin access."
canonical_url: "https://docs.oz0.ai/reference/plugin-manifest/dependencies"
---
# 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`

<field-group>
<field name="oz0" type="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.

</field>

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

`os/arch` pairs, with `os` in `linux`, `darwin`, `windows` and `arch` in `amd64`, `arm64`. Empty means every platform.

</field>

<field name="secrets" type="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](https://docs.oz0.ai/admin/secrets/plugin-secrets#secrets-for-plugins).

</field>

<field name="optional_secrets" type="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](https://docs.oz0.ai/admin/secrets/plugin-secrets#what-is-scanned)).

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.

</field>

<field name="settings" type="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](https://docs.oz0.ai/build/plugins/configure) 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](https://docs.oz0.ai/admin/secrets)).

A credential binding and a `{{secret:NAME}}` placeholder never take a setting. Installs, `plugin inspect` ("Reads these settings: ...") and hub listings name them.

```yaml
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](https://docs.oz0.ai/admin/secrets/settings#settings-are-not-secrets)).

</field>

<field name="inject" type="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](https://docs.oz0.ai/admin/credential-injection#bind-a-connections-token)). See [Credential injection](https://docs.oz0.ai/admin/credential-injection).

</field>

<field name="models" type="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](https://docs.oz0.ai/build/parts/tools/model-calls#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](https://docs.oz0.ai/build/plugins/structure/lint-rules#oz-s017)).

</field>

<field name="secrets_from" type="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.

</field>
</field-group>

## `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.

```yaml [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]
```

<field-group>
<field name="version" type="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](https://docs.oz0.ai/build/plugins/install/update#updates-and-the-plugins-that-use-them)). Give it an upper bound and test both ends ([test both ends of a range](https://docs.oz0.ai/build/plugins/structure#test-both-ends-of-a-range)); a verified capability must ([OZ-S021](https://docs.oz0.ai/build/plugins/structure/lint-rules#oz-s021)).

</field>

<field name="source" type="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.

</field>

<field name="tools" type="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`.

</field>

<field name="agents" type="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`).

</field>

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

The used plugin's [skills](https://docs.oz0.ai/build/parts/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.

</field>

<field name="optional" type="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](https://docs.oz0.ai/build/parts/flows/steps-and-expressions#expressions)); `plugin lint` warns about a step without one. False by default.

</field>
</field-group>

`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.

```yaml [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:

<table>
<thead>
  <tr>
    <th>
      Part
    </th>
    
    <th>
      Address
    </th>
    
    <th>
      Fields
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      A function
    </td>
    
    <td>
      <code>
        github/oz0_github.get_file
      </code>
      
      , with <code>
        *
      </code>
      
       where a field takes a pattern (<code>
        github/oz0_github.*
      </code>
      
      )
    </td>
    
    <td>
      an agent's <code>
        tools
      </code>
      
      , a flow step's <code>
        tool
      </code>
      
      , a hook's <code>
        call
      </code>
      
       (own only) and <code>
        match.tools
      </code>
      
      , <code>
        tools[].requires_approval
      </code>
      
       (own only), <code>
        uses.&lt;plugin>.tools
      </code>
      
      , <code>
        policy[].match
      </code>
      
       (own only)
    </td>
  </tr>
  
  <tr>
    <td>
      An agent or a flow
    </td>
    
    <td>
      <code>
        github/oz0-github
      </code>
      
      , <code>
        pr-reviewer/review
      </code>
    </td>
    
    <td>
      an agent's <code>
        delegation.allow
      </code>
      
      , <code>
        delegation.best_of.agents
      </code>
      
      , <code>
        handoffs
      </code>
      
       (own only), <code>
        colleagues.ask
      </code>
      
      , <code>
        outcome.reviewer
      </code>
      
      , <code>
        best_of.agents
      </code>
      
       and <code>
        conversation.continue_from
      </code>
      
      ; a flow step's <code>
        agent
      </code>
      
       and <code>
        best_of.agents
      </code>
      
      ; a hook's <code>
        match.agents
      </code>
      
      ; <code>
        tools[].flow
      </code>
      
       (own only); <code>
        uses.&lt;plugin>.agents
      </code>
      
      ; an eval's <code>
        agent
      </code>
      
       and <code>
        flow
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      A skill
    </td>
    
    <td>
      <code>
        web/research
      </code>
      
      , <code>
        web/*
      </code>
    </td>
    
    <td>
      an agent's <code>
        skills
      </code>
      
      , <code>
        tools[].manual
      </code>
      
       (own only), <code>
        uses.&lt;plugin>.skills
      </code>
    </td>
  </tr>
</tbody>
</table>

- **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](https://docs.oz0.ai/build/plugins/structure/lint-rules#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>`.<br />

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](https://docs.oz0.ai/reference/plugin-manifest).


## Sitemap

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