Configure Discoverable Plugins
Use this guide to add a manifest-backed plugin that another author has
packaged. Discoverable plugins are separate from built-in [[components]]:
their relay-plugin.toml manifest describes a native shared library or a
grpc-v1 worker, while plugins.toml records where Relay can find it and
stores its component configuration.
Discoverable plugins are trusted extensions. Native plugins run in the gateway process. Worker plugins run in a separate process, but process isolation is not a security sandbox. Install manifests and artifacts only from sources you trust.
Add and Enable a Plugin
Install a GitHub Release Bundle
Install the GitHub CLI and sign in with gh auth login.
You can also set GH_TOKEN or GITHUB_TOKEN. GitHub CLI checks GH_TOKEN first,
then GITHUB_TOKEN, then your saved sign-in. Relay does not accept a token as a
command argument or save it with the plugin.
Replace <release-tag> with the exact tag of a published release:
Relay downloads the bundle for your operating system, CPU, and CLI libc target.
On Linux, musl CLI builds select linux-musl-x86_64 or linux-musl-arm64
bundles; GNU CLI builds select linux-x86_64 or linux-arm64 bundles. Relay
checks the archive’s .sha256 checksum and .json release metadata. It also checks the
plugin manifest, supported Relay versions, and the runtime file’s digest. If
the checks pass, Relay registers the plugin and tries to enable it. Enabled
plugins load when the gateway starts.
Use --no-enable to install and register the plugin without enabling it. Only
exact release tags work. The installer does not choose the latest release,
install from a source tree, or replace an existing plugin. It does not support
--config or --plugin-config-path.
Choose Where to Install
Without a scope flag, Relay installs the plugin for your user account. It
stores plugins.toml and the bundle in $XDG_CONFIG_HOME/nemo-relay/ if
XDG_CONFIG_HOME is set, or in ~/.config/nemo-relay/ otherwise. The bundle
goes in installed-plugins/. A Python worker’s environment goes in
.dynamic-plugin-environments/.
Use --global to install for the system. The configuration file is
/etc/nemo-relay/plugins.toml on Unix or
%ProgramData%\nemo-relay\plugins.toml on Windows. The bundle and any Python
environment use the same subdirectory names beside that file. Run the command
with an account that can write to the system directory. Sign in to GitHub CLI
as that account, or set a token in its environment.
On Unix, a global install makes the installed bundle and Python environment
readable by other local users. It sets their directories to mode 755 and
regular files to 644, or 755 when a file is executable. It sets
/etc/nemo-relay/ to 755, and sets plugins.toml and
.dynamic-plugins.json to 644. Relay removes the temporary download after
installation.
The installing account does not need to be root. Relay keeps the Python
environment’s verification key with the environment, so other users can verify
the same install. While the plugin is registered, its bundle and environment
remain available across Relay runs and user sessions.
When Relay updates global plugins.toml or .dynamic-plugins.json, it sets
the file to mode 644, even if it was private before. Other users also need
access through each parent directory to use the global plugin. Do not put
secrets in global plugins.toml; other local users can read it. Relay does not
change the targets of symbolic links in the installed bundle or Python
environment.
The bundle and Python environment permission changes apply to release installs.
plugins add --global keeps the permissions of local plugin files. On Windows,
access follows the directory’s Windows permissions.
Enable and Manage the Plugin
If the bundle has no signature, Relay registers it but cannot enable it under the default trust policy. The install command reports an activation error, and the plugin stays disabled. Python workers are an exception: their package setup can run code, so Relay rejects the install before setup if the host does not trust the bundle. Set the trust rule first, then retry the install.
To allow an unsigned plugin, add this rule to the selected plugins.toml.
Replace <plugin-id> with the ID in relay-plugin.toml. Registered plugins
also show their IDs in nemo-relay plugins list:
You can also use signature_if_present, which checks a signature when the
plugin has one. Use either setting only for a source you trust. Then run
nemo-relay plugins enable <plugin-id>. To require a signature, use a signed
bundle and add its public key to trusted_public_keys. Relay never changes
your trust policy during install.
nemo-relay plugins list --user shows user plugins.
nemo-relay plugins list --global shows system plugins. Without either flag,
nemo-relay plugins list shows both. The list shows the source and release tag
for managed installs. Use nemo-relay plugins inspect <plugin-id> for details.
Use these commands to remove a plugin:
nemo-relay plugins remove <plugin-id> --userremoves the registration but keeps the bundle. It deletes a managed Python environment, if one exists.nemo-relay plugins uninstall <plugin-id> --userremoves the registration, bundle, and managed Python environment. You can run it afterremove. It refuses plugins added from local paths.
Both commands accept --global for a system plugin. Without a scope, remove
asks you to choose one if the ID appears in both scopes; uninstall uses the
user scope.
Register a Local Manifest
Validate the manifest before registering it in user configuration:
add writes a [[plugins.dynamic]] reference to the selected plugins.toml
scope and stores CLI lifecycle state next to it. enable changes that
lifecycle state; it does not load code immediately. Relay validates and loads
enabled plugins when the gateway starts. Use plugins list, plugins inspect,
and plugins validate to review current state and diagnostics. Use disable
or remove to stop loading a registered plugin.
plugins list and plugins inspect calculate current validation and policy
status in memory and do not write .dynamic-plugins.json. plugins validate
persists its result for a registered plugin; add, enable, disable, and
remove persist their explicit lifecycle changes. Gateway startup also checks
plugin status without writing shared lifecycle state.
To share a Python worker across users, register it in the system scope with
nemo-relay plugins add --global <manifest> from an account that can write the
system configuration directory. Ensure the system plugins.toml, lifecycle
state, managed environment, and manifest are readable and traversable by every
Relay daemon user, while keeping them writable only by administrators. Startup
validates the managed environment without writing the shared lifecycle state.
You can also add the reference directly when configuration is provisioned by automation:
The manifest path resolves relative to the plugins.toml file. The config
table supplies the synthesized component configuration. Before enabling or
running a plugin, use nemo-relay plugins validate <plugin-id> to check the
manifest, trust evidence, and optional static JSON Schema. During gateway
activation, Relay loads the enabled adapter before it validates the synthesized
component; a worker runs its Validate call after its process starts.
Do not add a Python worker only with this TOML record. Run
nemo-relay plugins add <path> to register a Python worker because Relay must
create and retain its managed Python environment before it can activate the
worker.
Validate Before Loading Code
Relay validates a manifest before it activates plugin code. The manifest must
declare a supported manifest_version, plugin ID and kind, Relay compatibility,
the lane-specific ABI or worker protocol, supported capabilities, a disabled
default, and one load contract. A manifest that declares config_schema must
also declare the config_schema capability.
Every discoverable plugin needs source.artifact and an integrity.sha256
digest so Relay can verify the artifact. A host can also require an Ed25519
signature and trusted public key. Use nemo-relay plugins validate <plugin-id>
to evaluate the resolved host policy and artifact trust evidence before you run
the gateway.
The following policy requires a valid signature from one trusted Ed25519 public key:
startup = "required" makes lifecycle preflight failures for an enabled plugin
fatal. When a policy field is omitted, the host defaults to startup = "required" and attestation = "signature_required". An unsigned dynamic
plugin is therefore refused unless you sign its artifact and configure a
trusted public key, or explicitly select integrity_only or
signature_if_present. A later native or worker load or activation failure
still stops gateway startup; correct or disable the plugin to start without it.
attestation accepts integrity_only, signature_if_present, or
signature_required; integrity verification always checks the declared
artifact digest.
Use startup = "required" when failed integrity or signature verification must
prevent worker startup. An optional trust failure records failed lifecycle
state, but does not itself prevent an enabled worker from launching. The native
loader also verifies load.library; the worker loader relies on lifecycle trust
evaluation.
Use [[plugins.policy.rules]] to apply an effect by match_kind or
match_plugin_id, and [plugins.policy.overrides."plugin.id"] for one
specific plugin. Rules and overrides can set allowed, startup,
attestation, and trusted_public_keys.
Ask the plugin author for the correct manifest and artifact. If you also need to inspect or rebuild the package, the native plugin guide explains in-process Rust shared libraries, while the worker plugin guide covers Relay-managed Rust, Python, and custom-command workers. Protocol implementers can use the grpc-v1 protocol reference to inspect the complete transport contract.