Prepare plugins for an upgrade
For: cluster operators.
Some server releases require a plugin update first. Check the named versions before upgrading.
Lockfiles and hash pins
From the release with lockfile checks, the edge refuses to install or update a plugin whose own Python project has no uv.lock, or whose Node.js project has no package-lock.json, and it pins every pypi: and npm: package by the hashes of its files, with every package it depends on (lockfiles, pinning and hashes).
Plugins installed before keep running on every node as they are: their bundles carry no hashes, and nodes install them as before.
Only their next update is refused until the plugin commits its lockfile (uv lock, npm install --package-lock-only).
An npm: package with a range (@^1.2) is refused at its next install or update; pin one version.
The edge reaches the package indexes at every install of such a plugin now, not only for unpinned packages: point it at your mirrors with OZ0_PLUGIN_PYPI_URL and OZ0_PLUGIN_NPM_URL if it may not reach pypi.org and registry.npmjs.org.
Eval recordings and the status block
From the release with the status block, the runtime ends each model request of an agent's loop with a status block of the budget, steps and time used. A plugin's eval recordings made before it hold requests without the block, so plugin test warns that the first call of each case differs from the recording, and answers it from the recording as before.
Record the cases again once (plugin test --record) to bring them up to date. From then on a replay leaves the block out when it compares a request with its recording, so a replay that takes longer than the recording still matches.
Manifests: one part on its own, addresses and requires.oz0
- A
SKILL.mdat a folder's root loads as an Orchestrator Zero plugin. Until this release, a folder with only aSKILL.mdat its root imported as a Claude Code skill, with its text untrusted. Now a rootSKILL.mdwithout Claude Code's folders beside it (skills/,commands/,agents/,hooks/hooks.json,.mcp.json) is a plugin of that one skill, named after it, with a generated manifest (A plugin that is one part).
One installed before keeps running as it is. Its next update loads it as the new kind of plugin; a skill from a marketplace was named after its marketplace entry, and is now named after the skill's ownname:, so where the two differ, install it again under the new name and remove the old plugin. requires.oz0is enforced. A plugin whoserequires.oz0the running release does not satisfy is refused at lint and install: "this plugin needs Orchestrator Zero >=1.2; this is 1.0.0". A development build satisfies every constraint.- References across plugins name their plugin. A bare name of another plugin's function or agent still resolves,
and
plugin lintwarns (OZ-S019); verified plugins fail lint on it. Write the address,github/oz0_github.get_file, or runplugin lint --fix. Plugins written with bare names keep running: nodes get the same bare names as before. runtime:is optional for a plugin without code of its own, and a Markdown file can be an agent. Neither changes a plugin you have.
Addresses in commands, the APIs and tool policy rules
Everywhere a part is named after its plugin is installed, its address works beside its bare name, and the server checks it against the part's owner:
- Commands and APIs take addresses.
oz0 run,oz0 chat,oz0 tool call,oz0 kill agent|tool,oz0 inventory set,oz0 job search --agent,oz0 policyandoz0 tenant tool-policy set --rule, and the runtime API'sStartJob,CallTool,GetCatalogandListJobs, take<plugin>/<part>. An address that names another plugin is refused with the owner found (github/triage is not an agent of github: triage is support-triage's). Bare names work as before: nothing you run today changes. - What people read shows addresses.
oz0 catalog,oz0 job get,oz0 job trace,oz0 approval list,oz0 inventory list,oz0 plugin inspectand the console show agents, flows and functions as<plugin>/<part>. The JSON outputs keep their fields and addaddressbeside them; a script that readsnameoragentstill gets the bare name. - A tool policy rule may name its plugin. A rule written
github/*decides only github's functions; the policy stores the plugin apart (plugin, besidematch).
A policy without such rules is stored, sent to nodes and versioned byte for byte as before.
A node of an earlier release ignores the field, so the edge sends such a node what it can
read: a rule that allows or asks by address with a wildcard in its tool part (github/*, github/*.get*) goes
with one copy for each of the plugin's tools (oz0_github.*), so that node decides github's calls as the rule says
and every other plugin's calls by your later rules and the classes, exactly as an upgraded node does.
The copies follow the plugin's tools: an update that adds a tool sends them again.
A rule that names a whole tool
(github/oz0_github.*) needs no copies, and a refuse rule by address reaches every plugin's matching functions on
such a node, which only refuses more until you upgrade its runtime.
The copies show in a node's policy file and
decide nothing on an upgraded node; the reason an older node gives names the copy (the tenant's rule oz0_github.*), with the rule's ID.
- Two plugins that define one agent or flow name run neither, at the edge and on every node, where the edge used to
start the first one and a node ran the last. Installs have refused a second one for several releases;
oz0 catalogshows such a pair. - Model-visible function names over 64 characters (
<tool>__<function>) are left out on a node, since OpenAI's formats refuse them;plugin lintwarns about one in a plugin's measured listing.
What an install prints, and switches the server places
plugin installandplugin updateprint three more blocks after the install: what the plugin needs, what it may do and what it reaches. They ask only when their input, output and errors are all a terminal, without--jsonor--yes: then they show the blocks first and ask once, so an interactive install now waits for a yes.
Anywhere else they ask nothing, as before, and that includes a script started from a terminal whose output is captured or redirected.
A script you run by hand whoseoz0 plugin installwrites straight to the terminal passes--yes, or setsOZ0_YES=1once at its top (export OZ0_YES=1), which does the same for every install and update in it. Their--jsonanswer gainsscreen; nothing else in it changes.oz0 plugin policy --setplaces its rules on the server (SetToolSwitches), with the same placement as before, so the rules it writes are the same. Against a server older than this release it places them as it did.- An update brings back a stricter default for a function whose type rose since the installed version, as an ask rule written at the update (a tightening, audited as a change of the tool policy). A tenant that allowed such a function asks again until an admin switches it back with a reason.
Updates are checked against the plugins that use them
From this release, installing or updating a plugin, and its quality gate's pass and its rollout's start and end, are
checked against every installed plugin whose uses: names it: a version outside a dependent's range, or without a
function, agent or skill a dependent is granted, is refused with the list
(updates and the plugins that use them).
Nothing installed
changes at the upgrade. A script that updates plugins and meets the refusal either updates the dependents first or
passes --force --reason <why>, which the audit log keeps as plugin.update.forced. A rollout or gate already running
at the upgrade is checked at its end. ai.oz0/pr-reviewer now takes ai.oz0/github from 1.1 (github 1.0 has no
download_archive, which it calls).
Event sources: what a node answers is held to the delivery
From this release the edge checks what an event source's function answers against the delivery it verified, by the fields the source binds. After the upgrade:
- Update the chat plugins (Slack, Teams, Discord) and the event-source plugins (GitHub, GitLab, ServiceNow, Atlassian Data Center) to their versions of this release: a chat channel acts only on a message whose author, room, button, text and chosen agent its source binds, and refuses the messages of an older plugin, saying why. The Essentials set's new version pins them.
- A routine, flow or wait that names a type, a subject, a filter or a sender fires only on fields its source binds. A third-party source that binds nothing still records its events, gives one event a delivery, and lint warns.
- A delivery's job is named by its body's digest (
sha256-...), not by the sender's delivery header, which is kept assender_deliveryin its record: a body sent again starts nothing new. A flow or routine webhook that sends the same body again on purpose signsX-Oz0-Timestamp. - A Slack form left open across the upgrade is refused when it is sent: open it again from its message.
Event sources: bound values compare by type and presence
From this release a bound value is compared with its type: false, 0, "" and null are different values, a number is not its digits, and an attribute a binding gives as null must be left out of the event. A value that cannot be compared refuses the delivery.
- Update
ai.oz0/githubto 1.5.1 andai.oz0/gitlabto 1.2.1 before you upgrade the servers (oz0 plugin update github, or the Essentials set's 1.1.1). Their earlier versions bind"",0orfalsefor attributes their functions leave out, so an upgraded edge refuses every delivery from them, each recorded asevent.failed, and no routine, flow or wait fires on them until the plugin is updated. The new versions pass on servers of the release before, so updating first loses nothing. A plugin you copied or forked fromexamples/github-eventsneeds the changes of its 1.1.1. - A third-party source whose bindings say
'',0orfalsefor an attribute its function leaves out fails the same way. Make such a binding givenull. A field the provider sends either as a value or asnullis named in the binding, ascel.bind(v, body.?x.orValue(''), v == null ? '' : v). - While edges of two releases run side by side, a tool call whose short arguments hold an integer no double holds (2^53 + 1, say) can be refused its connection's token, since the old edge rounded the integer and the new one does not. It passes once every edge is upgraded. An approval on such an integer that waits across the upgrade keeps the digest it was asked with.