Skip to main content
A plugin is a folder of components that fold into your running agent. Ziro reads the Claude Code plugin format directly, so plugins written for Claude Code work here without conversion. The system has three independent layers, and confusing them is the main source of “why is my plugin not showing up”:

What a plugin can surface

Enabled plugin components fold into the agent namespaced as <plugin-id-last-segment>:<name>. Native components always shadow a plugin component on a name clash. Prompt macros expand $ARGUMENTS, $1$9, and @file references, then run as a normal turn. Macros whose body is a shell (!bash) command are filtered out: a command file is a prompt template, not an execution channel. Hook events are mapped from Claude Code’s vocabulary onto Ziro’s. An event with no Ziro equivalent is skipped rather than guessed at.

Discovery roots

Ziro scans, in order:
The Claude Code import is read-only: Ziro reads your existing installation and surfaces those plugins, it does not rewrite anything Claude Code owns. Every parse and fetch failure is non-fatal. A malformed plugin is skipped with a warning; it never blocks startup. This is the security spine of the whole system. Third-party plugin code can run shell commands and spawn subprocesses, so those capabilities are inert until you explicitly trust the plugin. During import, Ziro extracts a grant for each capability a plugin wants. Three grant kinds are treated as dangerous: Network access and http/sse MCP servers are not dangerous grants. They surface without a trust decision, because they cannot execute local code. Before anything installs, Ziro renders a consent manifest listing exactly what the plugin wants, and waits for your decision.
1

Stage

Clone or copy the plugin at a pinned ref, import it, and build the consent manifest. Nothing is active yet.
2

Decide

install records trust and enables everything. safe_only installs with the dangerous parts inert. cancel discards the staged copy.
3

Commit

Write the ledger entry and reload the registry.

Trust is content-hashed

Trust is keyed on plugin id plus a hash of its contents. Edit any file in a trusted plugin (including via an update) and the hash drifts, which auto-revokes trust until you confirm again. A plugin cannot be trusted once and then quietly changed underneath you.
Dangerous grants are force-inert under ZIRO_STRICT_SECURITY, and in any headless run. A background job or a -p one-shot has no human at the keyboard to consent, so it fails closed. This is threaded through the TUI runner (when not interactive) and the background worker pool (always).
Revoking is symmetric: remove the plugin, or disable it, and its hooks and stdio MCP servers stop being surfaced.

Installing

Two flags short-circuit the consent prompt, and both are decisions you are making in advance: Without a flag, consent is interactive on a TTY and fails closed otherwise.

Managing

Marketplaces

A marketplace is a registry of plugin listings, parsed from a Claude Code marketplace.json. Registered marketplaces live in a JSON file under your user root.
Listings resolve to a concrete source: a local listing points at a subdirectory of the marketplace repo, a github or git listing at a URL. Installing then uses the normal staged-consent flow.
preapproved_marketplaces in plugin_config.yaml is discovery only. It auto-registers a fetch origin so ziro plugin install <market>:<plugin> resolves without a manual marketplace add. It never skips the consent manifest and never grants trust.

In chat

/plugins (alias /plugin) is one command covering the whole surface. Install uses a staged-then-confirm flow rather than a blocking prompt, so it works identically in the TUI and the REPL. Changes hot-load without a restart: skills reindex, MCP servers reconcile through sync_servers(), and prompt macros re-register.

Thread-scoped activation

Installation and trust are global. Activation is per thread. A thread entry in threads.json carries an optional per-plugin override map. Activation resolves per plugin:
So /plugins disable foo in one thread turns foo off for that conversation only, leaving your other threads untouched. To change the global default, use the CLI (ziro plugin enable|disable) or install the plugin. Enforcement is hybrid, applied at each point of use rather than by filtering one list:
  • Skill hits owned by an inactive plugin are dropped from search results.
  • A hook belonging to an inactive plugin no-ops when invoked.
  • A prompt macro declines at dispatch.
  • Spawning an inactive plugin’s subagent is rejected. A spawned child inherits the parent’s active set.
  • MCP servers are re-synced on a toggle rather than filtered at call time.
The TUI sidebar has an Active plugins section reflecting the thread-effective set.

The plugin_config.yaml policy gate

Everything above describes what is currently on. plugin_config.yaml describes what is permitted at all. It is a ceiling, not a switch: a thread override can narrow within it, but can never widen past it.
An agent that no config layer supplies a plugin_config.yaml for surfaces ZERO plugins. Plugins are per-agent opt-in. A newly scaffolded agent does not silently inherit whatever happens to be installed on the machine.
This applies to the absence of the file, not to an unset enabled: key inside one. A file that only narrows with allow / deny still means the author wanted plugins, and stays permissive within its allowlist.
The file resolves through the standard chain, most-specific first:
Of the shipped agents, only default (the Generalist) ships this file, and it ships permissive: discovering and using whatever a task needs is its whole job. researcher and researcher_docker ship no file and are therefore plugin-free until you add one.
A malformed plugin_config.yaml fails closed: no plugins, plus a loud warning. The file existing means someone meant to constrain something, so a parse error must not be read as “no constraints”.

Project layers are trust-gated too

plugin_config.yaml is a dangerous project file. A project-layer copy is ignored wholesale until you trust the folder, and folder trust is content-hashed the same way plugin trust is. A cloned untrusted repo cannot set plugin policy on you.

The SDK is hermetic

A code-first create_agent(...) surfaces zero plugins by default, so the same script behaves identically on two machines regardless of what each has installed. Opt in explicitly:
Passing a bare string raises TypeError, so wrap it in a list. An empty list normalizes to “no plugins”, kept distinct from True.

Next steps

Plugin CLI reference

Every ziro plugin subcommand and flag.

Skills

How plugin skills join the skill index.

MCP servers

How plugin MCP servers fold into the live manager.