Plugins

List a plugin

Prepare and submit a plugin listing for hub review.

For: plugin authors.

Submit a listing to a plugin hub so tenants can find and install your plugin by name. A hub such as orchestrator-zero/hub (source access coming soon) keeps one file per plugin and builds a signed plugin index.

The listing supplies sources, versions, maintainers and links. The hub uses the plugin's README.md as its documentation. Its CI runs plugin inspect on the pinned source with a pinned engine release to discover the plugin's contents, requirements and capabilities.

oz0 Hub (coming soon) is not public yet. Everything on this page works against a checkout of a hub repository, and a company can run a hub of its own in the same format.

Before you list

  • plugin lint passes, apart from a size budget error (tools.budget), which the hub only warns about, and the plugin installs from its source.
  • Its README.md is within the README's limits and says what the plugin does, how to set it up and its limits: the hub's page shows it as the plugin's documentation (below). plugin lint, plugin pack and hub submit fail on a README past them.
  • It has a licence file, and license: in oz0-plugin.yaml declares the licence as an SPDX identifier, such as MIT (the licence checks). hub submit refuses a plugin without a licence file (licence.missing), and lint fails when the file reads as another licence than the one declared. A text the engine does not recognise is not refused: a maintainer reads it at review.
  • You have a namespace you can prove, which becomes the first half of the plugin's name, <namespace>/<manifest name>:
    • io.github.<account>, proven by your GitHub account; for an organisation's namespace, a maintainer confirms its owners once and records the accounts that may list for it;
    • com.example for a domain you own, proven by a TXT record at _oz0-hub.example.com that names your key: v=oz0hub1; k=ed25519; p=<your key in base64>. Hosting domains such as github.io prove nothing;
    • with only a signing key, a namespace the hub's maintainers give you.
  • Your tool IDs and agent and flow names start with your plugin's name or its first dash-separated part: plugin acme-risk may have the tools acme_risk and acme_fetch and the agent acme-risk. A tool ID, an agent or flow name and a plugin name each belong to one plugin of a tenant, so a generic github or fetch cannot be installed next to anyone else's.
    The prefixes oz0_ and oz0- belong to the verified plugins of the publisher ai.oz0, and the hub refuses a new name that takes one of them or only looks like one (ozo_, 0z0-, an oz0 with a Cyrillic о), or a tool ID or agent name of a verified plugin; plugin lint warns of both.
    Their plugin names (github, slack, web and the rest) are theirs too: name yours acme-github, not github.
  • Your signing key is an Ed25519 key. The OpenSSH key you sign release tags with (ssh-ed25519 AAAA...) is the easiest: the same key signs tags, submissions and the DNS record.

By pull request

Terminal
oz0 hub submit https://github.com/alice/github-tools --ref v1.2.0 \
  --publisher io.github.alice --hub ../oz0-hub --maintainer "Alice <[email protected]>" --tag github
Output
Wrote ../oz0-hub/listings/io.github.alice/github-tools.yaml (io.github.alice/github-tools 1.2.0). Commit it on a branch and open a pull request:

Add io.github.alice/github-tools 1.2.0
...

hub submit inspects the plugin at the commit --ref resolves to, as plugin inspect does, and writes the listing: a new file, or a new version of the one there, keeping the rest of it, comments included. It pins the commit, the SHA-256 of the packed content and the forge's repository ID; never a tag. The pull request's text lists the source, the contents, everything the code may do and its requirements; --pr-text <file> writes it to a file instead.

On our hub, a maintainer approves every listing's pull request before it merges, a new version's too. The hub's checks run first and decide what the maintainer must look at: a pull request they block needs a maintainer's review in the listing as well.

hub submit and plugin inspect fetch a remote source as the edge does: public addresses over https:// only, pinned to the addresses the host resolved to, with no redirect followed. A repository or registry on a private network needs --allow-net <network>; one you serve yourself on this machine, at any of its addresses (inside a network --allow-net lists too) or over plain http://, needs --allow-local. A folder, a bundle file and a file:// repository are read as they are.

Bundles and OCI artifacts list the same way: hub submit team-1.2.0.tar.gz --url https://example.com/team-1.2.0.tar.gz, or hub submit oci://ghcr.io/alice/team@sha256:.... Their pages have no Repository link, since nothing ties the bytes to a repository.

hub submit runs the same README and licence checks as plugin lint, and fails on their errors before it writes anything; there a missing licence file is an error too. It has no --documentation flag and asks for no repository link: the README is the documentation, and the hub computes the Repository link from the source it verified.

For a new version of a listed plugin, hub submit adds only the version. Flags that describe the plugin (--summary, --maintainer, the links, --tag, --track) are refused then: edit the listing file instead.

Check it before the pull request

Terminal
oz0 hub submit https://github.com/alice/github-tools --ref v1.2.0 \
  --publisher io.github.alice --hub ../oz0-hub --check --pr-author alice

With --check, hub submit writes the listing and then runs the checkout's own checks on it, as the hub's pull request CI runs them: the checkout's hubtool check, built with go build when bin/hubtool is missing or older than its code, against the commit the pull request goes into, with the engine release its engine.lock pins. The findings are the ones the pull request will get, and the command fails when they block the listing until a maintainer reviews it, as for a first plugin or a growth in permissions.

  • --pr-author is the forge account that will open the pull request (default $HUB_PR_AUTHOR), for the check of who changed the listing; without it that check only notes it was not done.
  • --base <commit> is the commit the pull request goes into, which the checks diff against. By default it is where your branch left the hub's default branch (the merge base of HEAD with origin/HEAD or origin/main), so listings you committed on a branch count as changed, as they will in the pull request; a checkout without origin uses HEAD.
  • The pinned engine comes from the hub's cache (~/.cache/oz0-hub, or $HUB_CACHE), or $HUB_ENGINE. When neither has it, --engine-source <engine checkout> builds it once with the hub's scripts/engine.sh; without one the check runs with your own binary and says the findings may differ from the CI's.
  • DNS records and forge accounts are asked of the network, as the CI does. Set HUB_FIXTURES=fixtures to answer them from the hub's offline fixtures instead, as the hub's own Makefile does for local runs.

Signing a pull request

The hub ties every change to its publisher, not only the first listing: each new version, a change to the summary, maintainers or links, and a status change. A pull request by the account that owns an io.github.<account> namespace, or by an account the publisher's file names as a member, proves itself. For a namespace proven by a domain or only a key, add --sign-key:

Terminal
oz0 hub submit https://git.example.com/alice/team --ref v1.3.0 --publisher com.example \
  --hub ../oz0-hub --sign-key ~/.ssh/id_ed25519

It writes submissions/com.example/team/1.3.0.json next to the listing: a copy of that version signed with your key, which the hub's checks verify against the key your namespace's DNS record names. Commit both. The copy binds the version exactly as written (its source, when it was added and by what), and for a new plugin its summary, maintainers and links too, so a listing that differs from it is refused: edit the listing before you sign, or sign again. A change nobody can tie to the publisher is blocked until a maintainer reviews it in the listing.

The proof decides whether the checks block, not whether a maintainer looks: every listing's pull request still needs a maintainer's approval.

Without GitHub

Terminal
oz0 hub submit https://git.example.com/alice/team --ref v1.2.0 --publisher com.example \
  --bundle submission.json --sign-key ~/.ssh/id_ed25519

The bundle is one signed file: the listing's version, what your engine found and the pull request's text, signed with your key (an unencrypted OpenSSH ed25519 key, or an engine key file from index keygen).

For a new version of a plugin that is listed already, add --new-version, so the pull request's text says so. Send it to the hub's maintainers.

They import it with the hub's own script, which checks the signature against the key the hub has for your namespace, or records it for a first submission, keeps the bundle as the version's proof, and the same checks run as for a pull request.

List a set

A set is listed, signed and kept in custody like any plugin: submit its folder as you would a plugin's. Its listing carries its members under generated.members, each plugin with its exact version and its pinned source. When the index is signed, each member from the same index (a hub: source) gets the commit and content digest its pinned version is listed at, and the set is refused while a member's version is not listed, or is withdrawn or quarantined, or is a set itself.

The set's clashes are its members' (two members that clash refuse it), and the hub's filters find it by its members' facets, with the kind set.

A set's page states its evals and the date they last passed, instead of a claim about the combination: a set says which versions its evals ran on, and nothing more.

Previous section links
Copyright © 2026