Tool policy

Rules for plugin and remote functions

Rules for plugin and remote functions for tenant admins and governance reviewers.

For: tenant admins and governance reviewers.

Remote MCP servers

A tool declared with url: runs on a remote MCP server that someone else controls, so its annotations are hints only. The policy counts what makes a call stricter and ignores what would loosen it:

  • Its functions are never read-only to the policy: a remote readOnlyHint: true makes the function Read (hints only) but does not let a call through the Read default, a failed call is not retried on its word until the server's listing is the approved snapshot, and shadow runs always stub it.
  • They always reach outside, whatever openWorldHint says, so they take the Reaches outside default too. It is Allow as shipped; a tenant that sets it to Refuse (oz0 tenant tool-policy set --open-world refuse) refuses every remote function no rule names.
  • A remote destructiveHint: true counts, and the call asks by default. Otherwise the function takes the Unknown default, which asks too, and the WHY column says remote (its annotations are hints only). So under the shipped defaults a remote function asks a person, through the Destructive or the Unknown default.
FUNCTION              TYPE                     POLICY  WHY
acme_crm.sync_record  Read, hints only         Ask     it is remote (its annotations are hints only)
acme_crm.delete       Destructive, hints only  Ask     it is destructive

A tenant rule still wins: oz0 policy allow acme_crm.lookup lets a remote function you trust run.

The policy's tasks block names the connections whose remote tools may run a long call as an MCP task, which the job follows from its timers instead of holding a node's slot (oz0 tenant tasks, see Long calls as tasks). It decides no call: a task's call is decided, asked and checked as any call is, and its result goes through the same checks.

One plugin's functions

oz0 plugin policy <plugin> shows what the policy does with each of one plugin's functions, grouped by type: Read, Write, Sends out, Destructive and Unknown. Each function shows Allow, Ask or Refuse, and why:

Terminal
oz0 plugin policy jev-tools
oz0 plugin policy jev-tools --set refund=ask --reason "refunds need a person"
oz0 plugin policy jev-tools --set read=refuse --set decide=allow --reason "only the decision service reads"
What tenant default's tool policy does with plugin jev-tools's functions:

READ (3)                    mixed
  jev.decide                Allow   the tenant's rule jev.decide
  jev_guard.check_refund    Refuse  the tenant's rule jev_guard.check_refund
  jev_support.lookup_order  Refuse  the tenant's rule jev_support.lookup_order

WRITE (1)             Ask
  jev_support.refund  Ask  the tenant's rule jev_support.refund

--set switches one function, by <tool>.<function> or its short name, or a whole group (read, write, sends-out, destructive, unknown; group:write where a function is called write, which the command otherwise refuses as unclear); approve is the same as ask. It writes ordinary rules naming each function, placed as oz0 policy places them, or changes the function's own rule (and says when that rule still has an end).

A plugin's own requires_approval still asks a person unless the policy refuses; when something else decides a function than what you set, the command says what and why.

Every switch takes a reason, and one that loosens waits for another admin only where four eyes are on, as any change of the policy does (two paths).

The switch is placed by the server

The console's install screen, oz0 plugin policy --set and plugin install --policy send the switches to the server, which places their rules (SetToolSwitches, which takes the tool policy's own permission, governance.write), with the same code that places the Configure tab's switches: one placement everywhere, against the policy as it stands.

--caller <plugin> makes the switches a rule for that plugin's agents only, such as a capability plugin's calls of a connector's function (oz0 plugin policy github --set create_issue=ask --caller pr-reviewer --reason "the reviewer asks first"); it stands before the rule that decides the function for every caller, after any rule that already decides only some callers' calls (an earlier refusal of hub plugins still wins).

A position of Judge is not available and is refused. A function's switch reads the version's measured listing before a node has listed its functions.

When an update makes a function more dangerous (its type rose: Read to Write, Write to Destructive, or it now sends out), the function's own rule, and any rule by caller that names exactly it, gets the stricter default its new type gives it, with the reason "its type rose from Write to Destructive in version 2.0.0".

That is a tightening, so it needs no reason of yours and no second admin; switching it back is a loosening, with a reason. The catalog marks such a function, and one new in the version, until the next version.

A version without a measured listing is compared when a node first lists it; with management apart from the edges, management makes that comparison a few seconds after the edge records the listing.

Two paths for a plugin's functions

A plugin's function can be switched along two paths, and each loosening waits for a different reason. A plugin's configuration that loosens always waits for another admin; a function's switch that loosens waits for another admin only where four eyes are on.

PathWhereA loosening
A plugin's configurationThe Configure tab, oz0 plugin config apply, and the parts of plugin install --config other than its settings and tools:Always a change request of kind plugin_config, which the admin who proposed it never approves, under confirm too and with four eyes off (plugin configurations)
A function's switchThe install screen, plugin install --policy, plugin install --config's tools:, oz0 plugin policy --setApplies at once, unless four eyes are on: then a change request of kind governance, which the admin who proposed it never approves

plugin install --config takes both paths at once: its settings are set and its tools: switched as the install's own steps, and its models, budgets, egress and schedules are filed as the plugin's configuration change. Whether the two paths should follow one rule is open; this page says what each does today.

A plugin's suggested policy

A plugin may suggest what the policy does with its own functions, in its manifest's policy: (A suggested policy); the verified plugins suggest that every write asks a person.

An install writes the suggestion as ordinary rules of this policy, after your own rules, each marked as the plugin's and saying the plugin's reason, such as "the plugin asks before it writes".

So a function's default is the first that applies: a rule of yours that decides it, then the plugin's suggestion, then what it declares.

A suggestion never overrides your own rules and only tightens what a function's type would give it: an installer needs no governance permission for it, and where your policy is stricter already, the stricter word stays.

A suggestion for a group of functions (oz0_github.*) is written as one rule only where none of your defaults is stricter than it; otherwise it is written one rule per function the plugin lists, so a function a node lists later is decided by your defaults.

A function no listing names yet, so that nothing says what it does, gets the suggested Ask only where none of your defaults is stricter.

An update that rewords or tightens a suggestion writes its rules where the earlier ones stood, so a rule you added after them, such as a broad allow, still comes after them.

The check at install reads the listing the plugin's author measured. What the first node lists of the plugin decides what nodes call, so the server checks the plugin's rules again when that listing is recorded (and when you approve a changed one with oz0 plugin approve-tools): a rule that would decide a listed function more loosely than your policy, because the node lists it as destructive where the author's listing said it only writes, say, is narrowed to the functions it does not loosen, and your policy decides the rest.

The audit log has a tenant.tool_policy entry by the node saying what changed, and the server logs a warning. An update checks its suggestions against what nodes listed of the installed version as well.

A default you tighten later reaches the functions a suggestion decides: when oz0 tenant tool-policy set (or the console) makes a default stricter, such as --destructive refuse, each of the plugin's functions that default now covers gets a rule with that word just before the plugin's rule, so the suggested Ask does not hide it.

Such a rule says "the tenant's policy" and stands for your default: it goes again when you make the default less strict, so the plugin's suggestion decides once more. It is marked as written for the plugin, so uninstalling the plugin takes it out with the plugin's own suggested rules.

It reaches the functions a node has listed or the plugin's measured listing names; a function no listing names yet keeps the suggested rule until you add a rule for it.

oz0 plugin policy <plugin> --set <function or group>=allow --reason ... switches a suggested Ask: the rule is then yours, and an update keeps it. A function under the plugin's requires_approval shows "Ask, set by the plugin" and cannot be switched below Ask: requires_approval is the one setting of a plugin that no rule of yours lowers, and only a Refuse beats it.

Two things stand in for the person it asks: an agent's changes policy for the call that applies its changeset, and a person's allowance of the same call in one job.

Uninstalling the plugin takes its suggested rules out. In oz0 policy show --json and the API, a suggested rule carries suggested: {plugin, version, action}; the mark goes once someone changes the rule.

Functions that declare nothing ask a person at every call. oz0 plugin declare <plugin> <function>=read (or write, sends-out, destructive) writes one rule for the function with the action your policy gives that type and your reason. Saying a function reads or writes loosens the policy, so it takes a reason and, under four eyes, a second admin. Rules by kind and conditions on tool.class still see such a function as one that declares nothing.

Copyright © 2026