Every field of protoagent.plugin.yaml — the file that makes a directory a plugin.
Generated from the PluginManifest dataclass in graph/plugins/manifest.py, which is
what the loader actually reads, so this list is complete by construction.
The manifest is declarative on purpose: it is parsed before the plugin's Python is ever imported, so the host can gate, configure, and display a plugin it has never run. That is what makes an install reviewable and a disabled plugin inert.
See Plugins for how to use these fields, and Install & publish plugins for the distribution ones.
| Field | Type | Default | Meaning |
|---|---|---|---|
id |
str |
required | the plugin's slug — required, unique, namespaces everything it owns |
name |
str |
required | the human label the console shows — required |
version |
str |
'0.0.0' |
semantic version string; drives update + pin resolution |
description |
str |
'' |
one-line summary shown in the console's plugin list |
enabled |
bool |
False |
enabled: true in the manifest is an author opt-in (for plugins you wrote/dropped in yourself) |
builtin |
bool |
False |
builtin: true marks a plugin as core runtime infrastructure (e.g. the delegate registry): it ALWAYS loads — ignoring both the enable gate and the plugins.disabled list — and is hidden from the… |
requires_env |
list[str] |
[] |
Env vars that must be set for the plugin to load — a HARD gate: a missing one skips the plugin with a logged reason rather than half-loading it |
capabilities |
dict |
{} |
Declarative, for transparency in the console — not yet enforced |
entrypoint |
str |
'' |
The module filename the loader imports to find register(registry) |
config_section |
str |
'' |
the top-level YAML section the plugin claims (default: id) |
config |
dict |
{} |
defaults for that section (key → default value) |
secrets |
list[str] |
[] |
keys in the section routed to the secrets.yaml overlay |
settings |
list[dict] |
[] |
Settings-schema field specs ({key, label, type, ...}) |
settings_tabs |
list[dict] |
[] |
Ordered Configure-dialog tabs (#3179/#3180) |
test |
bool |
False |
Test action (ADR 0029) — when true, the plugin serves a credential check at POST /api/config/test-<config_section> (e.g. the chat_surface wirer mounts… |
guide_url |
str |
'' |
Optional setup-guide URL (ADR 0059) — the console renders a generic "Setup guide" link next to the plugin's settings, so no per-plugin frontend is needed |
views |
list[dict] |
[] |
Console surfaces (ADR 0026) — each entry adds a left-rail icon opening a full view (an iframe of a page the plugin serves at path) |
commands |
list[dict] |
[] |
Palette commands (ADR 0057) — declarative command-palette entries, parsed and never imported, exactly like views |
public_paths |
list[str] |
[] |
Auth-exempt paths — prefixes under THIS plugin's own /plugins/<id>/ (or /api/plugins/<id>/) namespace that the default-deny auth middleware lets through WITHOUT a bearer |
federation_paths |
list[str] |
[] |
Federation-tier paths (#2747) — prefixes under THIS plugin's own namespace that accept the federation credential ([ADR… |
emits |
list[str] |
[] |
topics this plugin broadcasts — its public event API |
subscribes |
list[str] |
[] |
topics it listens for, declared so the wiring is visible to operators |
emits_schemas |
dict[str, dict] |
{} |
Typed event contracts (#1636) — topic → {"summary": str, "schema": dict} for emits: entries that declared more than a bare name |
requires_pip |
list[str] |
[] |
declared pip deps |
optional_pip |
list[str] |
[] |
the optional tier (#1953) — specs from optional: true entries |
pip_scopes |
dict[str, str] |
{} |
pkg name -> "host" | "runtime" (#2246) |
repository |
str |
'' |
provenance, shown in the install review |
homepage |
str |
'' |
provenance, shown in the install review |
min_protoagent_version |
str |
'' |
compat guard — the loader refuses to load the plugin when the host is older than declared (malformed strings only warn and load) |
supersedes |
list[str] |
[] |
The standalone repo(s) this BUNDLED plugin replaces — how an external plugin moves into core under the SAME id (so plugins.enabled, its config section and every archetype's enable list keep… |
enables |
list[str] |
[] |
Other plugins this BUNDLED plugin turns on (#3450) |
- Type
str· Default required
the plugin's slug — required, unique, namespaces everything it owns
- Type
str· Default required
the human label the console shows — required
- Type
str· Default'0.0.0'
semantic version string; drives update + pin resolution
- Type
str· Default''
one-line summary shown in the console's plugin list
- Type
bool· DefaultFalse
enabled: true in the manifest is an author opt-in (for plugins you wrote/dropped in yourself). An operator can also enable by id via plugins.enabled in config. Either path counts as consent.
- Type
bool· DefaultFalse
builtin: true marks a plugin as core runtime infrastructure (e.g. the delegate registry): it ALWAYS loads — ignoring both the enable gate and the plugins.disabled list — and is hidden from the Plugins management list, since it isn't an optional add-on the operator toggles. Its config lives in the core Workspace settings, not the Plugins panel.
- Type
list[str]· Default[]
Env vars that must be set for the plugin to load — a HARD gate: a missing one skips the plugin with a logged reason rather than half-loading it. Use it for what the plugin cannot function without; use settings[].required (ADR 0019) instead when the operator should be prompted in the console rather than blocked at boot.
- Type
dict· Default{}
Declarative, for transparency in the console — not yet enforced.
- Type
str· Default''
The module filename the loader imports to find register(registry). Empty means the default search: __init__.py, then plugin.py.
- Type
str· Default''
the top-level YAML section the plugin claims (default: id)
- Type
dict· Default{}
defaults for that section (key → default value)
- Type
list[str]· Default[]
keys in the section routed to the secrets.yaml overlay
- Type
list[dict]· Default[]
Settings-schema field specs ({key, label, type, ...})
- Type
list[dict]· Default[]
Ordered Configure-dialog tabs (#3179/#3180). A schema-backed {id, label} descriptor is targeted by settings[].tab; a path-backed {id, label, path} descriptor embeds plugin-owned UI from /plugins/<id>/... through the sandboxed view bridge. One descriptor has one kind — settings cannot target a path-backed tab. Plugins that omit this keep the flat Configuration form.
- Type
bool· DefaultFalse
Test action (ADR 0029) — when true, the plugin serves a credential check at POST /api/config/test-<config_section> (e.g. the chat_surface wirer mounts one), and the console renders a generic "Test connection" button for the group. No console edit needed per plugin.
- Type
str· Default''
Optional setup-guide URL (ADR 0059) — the console renders a generic "Setup guide" link next to the plugin's settings, so no per-plugin frontend is needed.
- Type
list[dict]· Default[]
Console surfaces (ADR 0026) — each entry adds a left-rail icon opening a full view (an iframe of a page the plugin serves at path). Declared as data so it's known without importing the plugin, and surfaced to the frontend via /api/runtime/status. Each: {id, label, icon, path, tabs?, slot?, palette?}. path must (1) be a path a registered router actually serves — the console iframes it verbatim, so a path no router answers is a blank surface — and (2) be a same-origin RELATIVE path (no scheme/host/port): an absolute URL escapes the ADR 0042 fleet proxy origin and breaks the same-origin postMessage token handshake. See _parse_views (warns on non-same-origin) and docs/guides/building-react-plugin-views.md. palette (ADR 0057) opts the view into the command palette's INLINE morph, where picking its entry expands the view's iframe inside the palette body instead of navigating to its rail panel. Exactly two spellings are honored: the literal string inline (morph the same page path already renders), or a mapping {path: ...} naming a DIFFERENT page to morph — a tighter palette-sized editor beside the full rail panel. That mapping's page is auto-exempted from the auth gate like any other view page. Every view is already a "Go to …" palette entry without opting in, so palette is only ever about the inline morph. Any other value — true being the tempting wrong guess — is dropped with a warning and the view itself still loads, because the console ignores an unrecognized shape silently.
- Type
list[dict]· Default[]
Palette commands (ADR 0057) — declarative command-palette entries, parsed and never imported, exactly like views. Each: {id, title, hint?, keywords?, icon?, group?, action?, provider?}, where action is what the entry DOES and provider makes it a live-search row that queries the plugin for results as the operator types. The console compiles both into behavior inside its own trusted adapter — plugin code never enters the bundle — so the vocabulary is closed and every field is validated at parse time: navigate and open_view take a view naming a view THIS manifest declares — navigate opens it on its rail/dock, open_view morphs it into the palette body and always ships inline: true (there is no non-inline open_view; navigate is that); tool takes a route under /api/plugins/<id>/ plus an optional method (absent ⇒ POST — a verb is never guessed); emit takes a topic published on the ADR 0039 bus and an optional data mapping (the payload POST /api/events/publish carries); command takes a command naming another entry in this same list. A provider makes the entry a live search: it takes the route the console queries as the operator types plus the result_action that running one of its result rows performs — required, because the rows are data and that declared action is the only thing the palette can run. Unlike a bad view path (which only blanks an iframe) a bad route becomes an AUTHENTICATED call carrying the operator bearer, so anything reaching outside the plugin's own namespace — an absolute or cross-origin route, a ../ escape (percent-encoded spellings unwrapped first), a route carrying a tab, newline or other control character (the URL parser DELETES those before it resolves the dot segments, so .<TAB>./.<TAB>./config validates clean and requests /api/config), a topic in another plugin's namespace, a view or command this manifest never declared — is DROPPED with a warning rather than kept. See _parse_commands. The console's trusted adapter (apps/web/src/app/pluginPaletteCommands.ts) is what turns a surviving entry into a palette row — grouped with the plugin's view rows and chipped with its name, in the console palette and the desktop launcher alike. It MIRRORS the route and topic namespace checks above rather than trusting the status payload, so an entry that fails the mirror is simply absent. Three console-side limits worth knowing while writing a manifest: navigate and open_view may only name a view the console mounts as a SURFACE — a rail, right-panel or bottom-dock view — because a slot: "chat" claimant renders under the core chat id and a utility widget is a bottom-left pill opening a dialog, so neither is reconciled onto a dock and neither has anywhere for a row to go (a command naming one is dropped with a warning, and the console drops it again on its own side); open_view needs its target view to have opted into the inline morph via views[].palette — something this parser cannot see, so a command naming a view that did not opt in opens it on its rail instead of morphing; and a provider is parsed and shipped but not compiled yet (ADR 0057 §8 leaves its per-query timeout/cancel + result-cap budget open), so an entry declaring only a provider contributes no row.
- Type
list[str]· Default[]
Auth-exempt paths — prefixes under THIS plugin's own /plugins/<id>/ (or /api/plugins/<id>/) namespace that the default-deny auth middleware lets through WITHOUT a bearer. The escape hatch for an inbound webhook (no bearer — the plugin verifies its own signature) or a public view page that must load in a browser iframe under a token-gated deployment. Namespace-scoped by the parser so a plugin can never exempt a core route.
- Type
list[str]· Default[]
Federation-tier paths (#2747) — prefixes under THIS plugin's own namespace that accept the federation credential (ADR 0066) where the /api operator ceiling would otherwise 403 it. NOT auth-exempt: a valid bearer is still required; only the tier ceiling is lowered. The seam for a deterministic plugin-owned RPC that a peer holding only the federation token must reach (a second device syncing a plugin-owned store) without being issued the operator bearer. Same namespace-scoping as public_paths — a plugin can never lower a core route.
- Type
list[str]· Default[]
topics this plugin broadcasts — its public event API
- Type
list[str]· Default[]
topics it listens for, declared so the wiring is visible to operators
- Type
dict[str, dict]· Default{}
Typed event contracts (#1636) — topic → {"summary": str, "schema": dict} for emits: entries that declared more than a bare name. emits above stays the names-only topic list (every entry, bare or typed), so existing consumers are untouched; this map carries the optional payload contract a cross-plugin consumer can discover instead of reverse-engineering the emitter. Purely declarative (like capabilities) — payloads are NOT validated at publish time. See _parse_emits.
- Type
list[str]· Default[]
declared pip deps. NOT auto-installed (install ≠ code exec); the operator installs them explicitly. Missing → clear error on enable. An entry is a bare PEP 508 spec string (a HARD dep) or a mapping {pkg: "pillow>=10", optional: true} — see _parse_requires_pip.
- Type
list[str]· Default[]
the optional tier (#1953) — specs from optional: true entries. The plugin runs without them (lazy import, graceful degradation), so the frozen-app gate (ADR 0058 D2) warns instead of refusing, and install-deps installs them best-effort.
- Type
dict[str, str]· Default{}
pkg name -> "host" | "runtime" (#2246). Which INTERPRETER has to be able to import the dep. runtime (the default, matching the compute-plugin pattern) means the managed Python runtime that serves execute_code children; host means this process. They have separate site-packages, so a dep the managed runtime satisfies is NOT importable in a frozen host — and a plugin whose tools import it in-process would pass the install gate and then die at tool time. Only non-default (host) entries are recorded; absent ⇒ runtime.
- Type
str· Default''
provenance, shown in the install review.
- Type
str· Default''
provenance, shown in the install review.
- Type
str· Default''
compat guard — the loader refuses to load the plugin when the host is older than declared (malformed strings only warn and load).
- Type
list[str]· Default[]
The standalone repo(s) this BUNDLED plugin replaces — how an external plugin moves into core under the SAME id (so plugins.enabled, its config section and every archetype's enable list keep working). Honored only on the copy shipped in protoAgent's own plugins/ tree; inert anywhere else. Each entry is the git URL of a retired repo. When plugins.lock records the installed copy of this id as fetched from one of them, the bundled copy wins over it — at any version — and the operator gets a setup gap saying the installed copy can be removed. Installing or updating from a listed URL (directly, or as a bundle/archetype member) is skipped rather than refused, so archetype repos that still list the old URL keep working on old and new hosts alike. A copy installed from any OTHER URL (a fork) still wins as a deliberate override. Matching ignores the transport spelling (https://, ssh://, git@host:path), userinfo, port, letter case, and a trailing .git or slash. An entry that isn't a remote git URL naming a repo (a local path, file://, a glob) is dropped with a warning; a bare string is read as a one-entry list.
- Type
list[str]· Default[]
Other plugins this BUNDLED plugin turns on (#3450). While this plugin is enabled, each listed plugin id is enabled too, unless the operator turned that plugin off explicitly (plugins.disabled always wins). Turning this plugin off returns each one to its own default (off, unless the operator enabled it themselves). Honored only on the copy shipped in protoAgent's own plugins/ tree, like supersedes: a plugin that can switch another on could switch on code execution, so it's inert anywhere else. A bare string is read as a one-entry list; a non-string, blank or self entry is dropped with a warning.