Grant access between plugins
For: tenant administrators and plugin authors.
Review, grant and revoke access to parts owned by another plugin.
Plugins that use other plugins
A plugin that declares uses: needs a tenant admin's grant of what it uses of other plugins. The first install is refused with the request and its digest:
oz0 plugin install https://github.com/acme/pr-reviewer
pr-reviewer 0.1.0 uses other plugins:
pr-reviewer wants oz0_github.get_pull_request and delegation to oz0-github, from plugin github
install plugin github from hub:ai.oz0/[email protected] (a trusted hub index lists it)
Grant it, with the plugins it installs first, with: --accept-uses sha256:3f1c...
Check the request, then install again with --accept-uses <digest>. That grants it and installs the used plugins the tenant does not have first, with the same selector. Where a used plugin comes from, in order: --dependency github=<source> (a Git URL, hub:<name>, oci://... or mcp:<name>; a new source is a new digest), the plugin's own uses.github.source, or a trusted index that lists a plugin of that name in the version range.
A used plugin that uses other plugins in turn is part of the same decision: the refusal shows what it wants under it, down the tree, and the one digest grants all of it.
deep 0.1.0 uses other plugins:
deep wants delegation to mid-adder, from plugin mid
install plugin mid from https://github.com/acme/mid (the plugin's uses: source)
mid wants calc.add, from plugin calc
install plugin calc from hub:acme/[email protected] (a trusted hub index lists it)
--dependency <plugin>=<source> names a source at any depth, also over a source a plugin of the tree names for it. A plugin that two branches of the tree use is built, planned and installed once. A tree goes at most five plugins deep and holds at most 25 plugins the tenant lacks, and plugins that use each other in a cycle are refused. A used plugin whose install needs another kind of decision (an imported plugin's inventory) is refused with it: install it on its own first.
If the install is refused after it installed plugins for itself (a check of the plugin's own, such as an agent name another plugin has), those plugins are removed again, newest first, as a removal removes a plugin (its agents tombstoned, its jobs' authority revoked, its grants, bindings and suggested rules gone), and the error says so: the plugins it installed first were removed again: mid, calc.
One that another plugin's grant names is kept, and the error says that too; what the refused install granted it goes in the same transaction (audited as uses.revoke), so it reaches nothing of other plugins until you grant it again, and a plugin the tree installed only for it is removed with the rest. A plugin someone installed from elsewhere after the tree installed it is left as that install left it, and the error says so.
If rolling one back fails, the error says it was kept with what the refused install granted it still in place: oz0 plugin revoke <plugin> --all takes that back.
- More needs a new grant, less does not. An update whose request the grant covers installs as usual; one that asks for more is refused with only what is new. A version that declares less reaches less, without a new grant.
- Versions. A used plugin whose installed version is outside the range is refused with the fix, such as
pr-reviewer uses plugin github ^2, and tenant default has github 1.4.0: update it.
Optional entries
An entry marked optional: true is asked for only when the tenant has the used plugin, in a version the range takes and not retired; otherwise the install neither installs nor grants it and says why in a warning, and the plugin's steps that need it are skipped. When you install the used plugin later, or update it into the range, that install's warnings name each plugin that uses it optionally, with oz0 plugin grant <plugin> --accept-uses <digest>, which grants it without reinstalling anything; after unretiring it, oz0 plugin grant <plugin> without the digest shows the request.
Removing an optionally used plugin is not refused for its optional users; it takes the plugin out of their grants (uses.revoke). --dependency <plugin>=<source> installs an optional plugin in the same decision.
- Ownership. A function must be the named plugin's:
uses.github.tools: [jira.get]is refused when tooljirabelongs to pluginjira. - Removing a plugin drops its grant, and takes it out of every other plugin's grant (audited as
uses.revoke, with the reason): installing it again, from wherever, asks again, for it and for the plugins that use it. A removal forced past its dependents does the same.
Another origin is another plugin
Installing a plugin from another origin than the one it was installed from (another hub listing, repository or folder, OCI repository, or kind of source) asks for its whole request again, and the grant that results is that decision's alone; so are the records it provides and reads (--accept-provides, --accept-reads).
For the plugins that use it, such an install is a removal of the old one: it is refused while a plugin whose grant reaches it needs it, listing them, unless you revoke what they are granted of it first or force it (--force --reason); a plugin that uses it only optionally goes on without it, and the install says so.
Either way the install takes it out of every other plugin's grant (audited as uses.revoke, with the reason), so they reach the new one only once you grant it to them again; and so do the results other plugins were granted to read of it (reads: [results] targets naming it, audited as plugin.reads.revoked with the reason). An update from the same origin asks only for what is new.
The origin that counts is the one the plugin's grants were decided for. A plugin whose first install never got past its quality gate has no version, but still keeps its origin. Installing the same name from elsewhere asks again.
A version from another origin can wait beside the installed one for its gate or a rollout. It keeps its origin separately. The plugin's row continues to name the installed version's origin, and plugin update follows that origin until the new version is installed.
If the waiting version never becomes the installed one, installing the old origin again also asks. The grants were decided for the other origin.
A grant you accept is bound to the origins of the installed plugins it reaches: if one of them is installed from another origin between the question and your --accept-uses, the install asks again. It is bound as well to the origin each plugin the tree installs first comes from: if one of those is installed from elsewhere before the plugin that needs it is granted, the install is refused, and installing it again asks again.
Revoking without removing
oz0 plugin revoke pr-reviewer --tool oz0_github.merge_pull_request takes one function out of the grant (--tool <tool>.* a tool's every function, --agent <name> an agent, --all the whole grant); the plugin stays installed, and its panel in the console has Revoke on each function and agent. The edge refuses what was revoked within seconds, and a job that is already running reads its grant again before its next call of another plugin, so the model reads the refusal and goes on.
A function that a granted <tool>.* covers narrows the wildcard to the tool's other functions, as the nodes listed them; before any node has listed them, the panel's dialog offers to revoke <tool>.* instead. Revoking is audited (uses.revoke). A later install of a version that still declares what you revoked asks for it again, and an install that was already running when you revoked keeps it revoked, with a warning.
Hooks need no grant
The platform invokes a plugin's hooks for every matching job without a grant between plugins. The edge checks each invocation: the function is a hook of that plugin, asked about an event of the kind it is for, of an agent and a tool its match: takes; a hook that matches some agents only is asked only by those agents' jobs (the job's own agent or one of its plugin's), and one that matches some tools only, only about calls the job may make.
The edge decides on the call's input, which is the event the node that runs the call gets, read by its exact keys: only the events the platform asks hooks about pass (a tool event names its tool, a job's start or end none), and the header a call names its event in to the edge must name that same event, or the call is refused at the edge and on the node. A job cannot call a hook's function with input of its own choosing, nor name one event to the edge and give the hook another.
Tool patterns in match: are matched alike on the edge and on the node, as Python's fnmatchcase does: [!...] is a set's complement, and * matches a / too. The tenant's per-call conditions decide a hook's call as any call's, and it has its run.tool_intent entry in the audit trail.
- Who granted what. A grant that grows keeps who granted what it held, when and why; what the new decision adds is recorded as its own. A connection's page under Credentials lists, for each plugin granted its tools, who granted which of them and when; each decision is also its own
uses.grantentry in the audit log. - A grant that cannot be read (a damaged row) reaches nothing, and nothing is written over it: an install or a narrowing of it is refused with the fix,
oz0 plugin revoke <plugin> --all, after which you grant it again. - The console shows the same request on the install screen, with Grant and install, in Studio's install dialog, and in its own dialog when an update from a plugin index asks for more (Grant and update); a plugin's panel shows what it may reach.
Every grant and refusal is audited
The audit events are uses.grant, uses.revoke, uses.refuse, and uses.denied. The last records what the edge refused (a call, a child job, a session of another plugin's agent, a run continued on another plugin's queue, a colleague question), for a job the node failed for want of a grant, and, once per job and refusal, for what the node refused and the model or the flow read and went on from: a call not made, a delegation, a flow step or a colleague question (at: node, went_on: true, with what was refused).
The edge records at most 20 of those per job, then one entry that says the rest are left out.
- A refused install keeps its warnings. The CLI prints them and the console shows them with the refusal, such as an index's clash that says why the install was refused.
plugin list shows each plugin's grant (team uses github: oz0_github.get_pull_request), and plugin preview shows the request before you install. Plugins installed before v1.0 that reached other plugins without declaring it keep exactly that, as a grandfathered grant (audit entry uses.grandfathered), and plugin list and the Plugins page mark it as a finding until their next version declares it. A version that declares what the grandfathered grant covers installs without a new decision and reaches what it declares.
While it waits for its quality gate or rolls out, the installed version beside it keeps the whole grandfathered grant, so nothing breaks on the nodes the rollout has not reached, nor after an aborted rollout. Once the declaring version is the installed one the finding clears, and the grant becomes a normal one (at once for an install on every node at once, otherwise at the plugin's next install); from then on each version reaches only what it declares.
Grandfathering keeps only what a plugin reached before: it never changes a grant an admin decided, adds back what an admin revoked, or replaces who granted it, since every grant, revoke and install of such a plugin, and every removal that changes its grant, grandfathers it first.
While a stored version of a plugin of the tenant cannot be read, what the marked plugins reached cannot be known, so they keep their mark until it can (each management start says why) and reach nothing of other plugins meanwhile; a decision on such a plugin's grant made in that time takes its mark off instead (audit entry uses.grandfathered, kept as decided), and the decision is its grant from then on. An install from another origin takes the mark off too.
Grandfathering keeps only what a plugin reached of the plugins installed at the upgrade, never one installed, replaced from another origin or removed after it: every install, update, move and removal of a plugin that a marked plugin may reach grandfathers the marked plugin first, with what it reaches before the change, or, while grandfathering is held back, takes its mark off (audit entry uses.grandfathered, kept as decided, naming the change) so that you grant it what it needs.
What depends on a plugin counts a version from before v1.0 that still runs under such a grant as a user, and a marked plugin as a user of what it reaches, so the used plugin is not removed, replaced from another origin, nor updated without what it uses, from under it, unless you force it with a reason.
A set
A set installs its members, each at the version and source it pins, with what they use of each other,
as one decision on one screen: the members and what the install does with each (install, update to the pin, keep at
the pin, keep a newer version or one from another source), then each member's own needs, may do and reaches. One
--accept-uses <digest> grants the whole tree. Afterwards the members are ordinary plugins, and removing the set
removes the members it installed that nothing else uses.
Revocation during a running segment
An OpenAI Agents SDK agent keeps its delegation list for its current run segment. A revoked grant therefore produces a refused delegation rather than removing that choice from the list immediately.
The edge can learn a revocation before a node. During that interval, the edge can fail the job when the node attempts the revoked call. The revocation is enforced; restart only after adjusting the agent's requested access or work.