---
title: "Plugin sets and policies"
description: "Look up the fields for plugin sets and policies."
canonical_url: "https://docs.oz0.ai/reference/plugin-manifest/sets-policy"
---
# Plugin sets and policies

> Look up the fields for plugin sets and policies.

For: Plugin authors looking up manifest fields.

Look up the fields for plugin sets and policies.

## `members`

A plugin of `kind: set` has no parts of its own. It lists plugins, each pinned to an exact version and source, with
settings and model mappings it suggests, and evals that run them together. Installing a set installs its members
as one decision; afterwards they are ordinary plugins.

```yaml [oz0-plugin.yaml]
apiVersion: oz0/v1
name: starter
version: 1.0.0
kind: set
description: The connectors and the capabilities that use them, evaluated together
members:
  - plugin: github
    version: 1.4.2
    source: hub:ai.oz0/github
    settings: {GITHUB_ORG: acme}
    models: {default: smart}
  - plugin: pr-reviewer
    version: 1.2.0
    source: hub:ai.oz0/pr-reviewer
evals: [evals/]
```

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

The member's name, unique in the set.

</field>

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

The member's exact version. A range is refused: "a set pins exact versions; ranges belong in uses:".

</field>

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

Where the member comes from, pinned: `hub:<publisher>/<name>`, a Git URL at a full commit
(`https://github.com/acme/notes.git@<40-hex commit>`), or an image by digest (`oci://<image>@sha256:<64 hex>`). A
tag or a branch is refused, since it can move.

</field>

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

Values of the member's own settings the set suggests, by setting name. Never a secret: a value that looks like a
token or a password is refused.

</field>

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

A model alias the member asks for, mapped to an alias of the tenant or `<provider>/<model>`.

</field>
</field-group>

- At least one member; a set of one is a warning.
- A set's evals name its members' agents and flows by address (`pr-reviewer/oz0-pr-reviewer`).
- Nothing else: `tools`, `agents`, `skills`, `flows`, `hooks`, `harnesses`, `events`, `channels`, `connections`,
`egress`, `requires.secrets`, optional secrets and settings, `provides`, `reads`, `records`, `startup`, `mail`,
`procedure`, `uses`, `policy` and `runtime` are each refused with "a set has no parts of its own: move `<key>` into a
member plugin".

## `policy`

The plugin's suggested policy: its word on its own functions, as rules that only tighten. An install writes them as
the tenant's rules, attributed to the plugin ([Tool policy](https://docs.oz0.ai/govern/tool-policy)).

```yaml [oz0-plugin.yaml]
policy:
  - match: oz0_github.merge_pull_request
    action: ask
    when: 'args.base == "main"'
    reason: the plugin asks before it merges
  - match: oz0_github.delete_branch
    action: judge
    question: Is this branch merged and no longer needed?
    reason: the plugin asks before it deletes
```

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

This plugin's own functions: `<tool>.<function>` or `<tool>.*` of one of its tools, or the same with its own
address (`github/oz0_github.*`). Any other pattern, a bare `*` among them, is refused: "a suggestion covers only
this plugin's functions".

</field>

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

`ask`, `refuse` or `judge`. `allow` is refused: a suggestion only tightens.

</field>

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

A condition over each call, in the tool policy's expression language, such as `args.base == "main"`.

</field>

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

Why the plugin asks, as a person reads it, at most 200 characters.

</field>

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

Required with `judge`, refused otherwise: what the judge decides.

</field>
</field-group>

Two rules with the same `match` and `when` are refused. The keys are a tool policy rule's, so an install writes each
as an ordinary rule.

**What a suggestion does at install.** The install writes each rule into the tenant's tool policy after the tenant's
own rules, marked as the plugin's, with its reason (`ask` and `judge` as an Ask, `refuse` as a Refuse). An installer
needs no governance permission for it, since it never overrides the tenant's own rules and only tightens what a
function's type gives it. A function's default is then the first that applies:

1. a rule the tenant already has that decides the function's calls;
2. the plugin's suggestion;
3. what the function declares (its annotations), and the plugin's `requires_approval`, which asks whatever comes
first unless the tenant refuses.

Where the tenant's policy is already stricter than a suggestion (it refuses destructive calls, say), the stricter word stays and the install says so. A pattern is written as one rule only where none of the tenant's defaults is stricter than it; otherwise one rule per function the plugin lists, so a function it does not list is decided by the tenant's defaults.

The check reads the measured listing, and, at an update, what nodes listed of the installed version; when the first node's listing of the version is recorded, the server checks the rules again against it, and narrows a rule that would decide a function it lists more loosely than the tenant's policy.

The installer may switch a suggested Ask to Allow, for a function or a group, with a reason and a second admin where four eyes are on; the switched rule is then the tenant's own. An update writes the new version's rules, keeps a rule the new version drops or loosens (and says which), and keeps asking about a function the new version takes off `requires_approval`.

Uninstalling the plugin removes its rules, and the rules written for its functions when the tenant tightened a default, except the ones the tenant switched.

**Only stricter.** `plugin lint` reads each rule against the [measured listing](https://docs.oz0.ai/reference/plugin-manifest/tools#toolslistingjson) (and a remote
tool's snapshot): a rule that would let a call run that the function's annotations or `requires_approval` already ask
a person about is an error, such as a `judge` on a destructive function, and an install refuses it too. A rule that
covers no listed function is a warning. A verified plugin asks about each write function this way ([OZ-S018](https://docs.oz0.ai/build/plugins/structure/lint-rules#oz-s018)).

**A judge** is written as an Ask until judges ship, and the install screen says so; its `question` is kept with the
rule.

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