Skip to content
rev0Docs

Load a plugin

rev0 can load declarative commands and sandboxed HTML from independent sources. A source is either a local folder (a Git checkout, a mounted volume, or a directory an agent generated) or a repo URL the board clones for you. What a plugin may declare is in Plugin contract.

The board never writes inside a folder you point it at. It does not clone, pull, install, copy, commit, or generate anything there. Everything the board writes lives under ~/.rev0/plugins/, a directory it owns and created for this purpose, and it writes there only when you press Fetch or Update on a repo source. A repo source is pinned to one commit: it never moves on its own.

Add a source under External plugins in Settings > Connections > Integrations. The path field autocompletes as you type, drilling into folders and offering the repos discovered under repos_root, so a local source is picked rather than pasted. An invalid plugin publishes nothing and shows its failure beside the configured source.

Each configured source carries Load, Unload and Reload:

  • Unload adds the path to plugin_disabled_paths. The source stays configured and visible, but the board stops reading it, so its commands and UI surfaces disappear.
  • Load removes it from that list again. Because the path never left plugin_paths, switching a source back on never means finding it a second time.
  • Reload re-reads the source from disk. The catalogue is rebuilt per request with no cache, so this only refreshes the view (use it after editing a manifest).

Removing the path removes every contribution on the next refresh; that is the permanent action, and unload is the reversible one. When board authentication is enabled, only a logged-in browser session can change any of these through PUT /config: unloading decides whether a source publishes commands and UI, and a pin decides WHICH code it publishes, so an agent token must not reach them.

A source may be a repository URL instead of a path:

https://github.com/owner/repo the repo root, at the default branch
https://github.com/owner/repo@v1.2 pinned to a tag (or branch, or commit)
https://github.com/owner/repo@main#bundles/whale one bundle inside a monorepo
git@github.com:owner/repo#whale-pet scp form works too

#<subdir> is what makes a monorepo usable: a collection of bundles usually has no plugin.toml at its root, so each bundle is configured as its own source pointing at its own subdirectory. The subdirectory is relative to the repository root and cannot leave it.

The board clones into ~/.rev0/plugins/<host>/<owner>/<repo>@<ref>. The ref is part of the directory name, so two refs of one repository never share a checkout. Clones are shallow (--depth 1), with no tags, no submodules and no LFS. Private repositories use the credentials this machine already has (your ssh agent, or whatever gh auth configured), and the board stores no tokens of its own.

Fetching is never implicit. GET /plugins only ever reads the filesystem, so an ordinary poll can never hang on a slow remote. Two buttons touch the network, both on the source’s own row:

  • Fetch clones a source that has none yet and records the commit it resolved to.
  • Update moves the pin: it fetches the ref again, checks out whatever it now points at, and reports which contributions were added or removed by that move.

Until you press one of those, the source keeps serving the exact commit it is pinned to. That is the point: “why did the board behave differently today?” always has an answer.

A source that has never been fetched, or whose fetch failed, reads failed with the reason git gave: being offline looks like being offline, not like a corrupt bundle. A failed update changes nothing: the working checkout and its pin stay exactly as they were, and the error goes to a toast rather than knocking a loaded plugin out.

Removing a repo source drops its pin but leaves the cached checkout on disk, so re-adding it is instant and works offline.

A repo source is never trusted to run code by default, and moving a pin asks again (see Plugin trust). A pin is an approval of one specific commit; if Update silently inherited it, “update the plugin” would quietly mean “run new code under the old approval”. The changed list Update reports names hooks arriving or leaving, so that question can be asked with evidence.

An agent that has just written a plugin can activate it itself with the set_plugin_source MCP tool, naming the bundle’s absolute folder (or a repo URL) and one state:

  • loaded publishes it, unloaded keeps it configured but stops running it, and removed forgets the source. They are the same two lists the Settings buttons write, so an agent and a human are never operating two different lifecycles.
  • The tool asks every time. The first call parks the ticket with an approve or decline panel naming the path, the plugin id and every command, UI surface and action the bundle would publish; approving re-invokes the agent, which calls again to apply it.
  • The approval is of the ARGUMENTS, not of the tool: it covers that one path in that one direction. Loading a second plugin, or later removing the same one, asks again. An “Always allow this tool” click carries no path and so authorizes nothing here.
  • A bundle that does not parse is refused before anyone is asked, so a broken plugin never becomes something a human has to judge, and never half-registers itself.
  • A repo URL is the one case with nothing to parse yet, because fetching it before the grant would be doing the very thing being approved. The panel names the repository, ref and subdirectory instead, and approving it clones once and pins the commit. What that clone turns out to contain shows up on the source’s row afterwards.

The write goes through POST /plugins/sources, which authorizes against that permission grant rather than the caller’s token. PUT /config is unchanged and stays browser-only.

An agent can also request trust for a bundle’s code by passing trust_hooks; that is a separate approval, described in Plugin trust.