> This page is for NeMo Relay, version v0.10.0.
> For other versions, use one of these documentation indexes:
> - Latest (v0.10.0) (default): https://docs.nvidia.com/nemo/relay/latest/llms.txt
> - dev: https://docs.nvidia.com/nemo/relay/dev/llms.txt
> - v0.10.0: https://docs.nvidia.com/nemo/relay/v0.10.0/llms.txt
> - v0.9.4: https://docs.nvidia.com/nemo/relay/v0.9.4/llms.txt
> - v0.8.4: https://docs.nvidia.com/nemo/relay/v0.8.4/llms.txt
> - v0.7.3: https://docs.nvidia.com/nemo/relay/v0.7.3/llms.txt
> - v0.6.0: https://docs.nvidia.com/nemo/relay/v0.6.0/llms.txt
> - v0.5.0: https://docs.nvidia.com/nemo/relay/v0.5.0/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nvidia.com/nemo/relay/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/nemo/relay/_mcp/server.

# Configure Discoverable Plugins

> Install, validate, trust, and configure manifest-backed NeMo Relay 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.

> **Warning**
>
> 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](https://cli.github.com/) 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:

```bash
nemo-relay plugins install "github:NVIDIA/NeMo-Relay-Plugins@<release-tag>" --user
nemo-relay plugins list --user
```

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`:

```toml
[plugins.policy.overrides."<plugin-id>"]
attestation = "integrity_only"
```

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> --user` removes the registration but
  keeps the bundle. It deletes a managed Python environment, if one exists.
* `nemo-relay plugins uninstall <plugin-id> --user` removes the registration,
  bundle, and managed Python environment. You can run it after `remove`. 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:

```bash
nemo-relay plugins validate ./acme-plugin/relay-plugin.toml
nemo-relay plugins add --user ./acme-plugin/relay-plugin.toml
nemo-relay plugins inspect acme.plugin
nemo-relay plugins enable acme.plugin
nemo-relay plugins validate acme.plugin
```

`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:

```toml
[[plugins.dynamic]]
manifest = "./acme-plugin/relay-plugin.toml"

[plugins.dynamic.config]
mode = "audit"
```

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:

```toml
[plugins.policy.defaults]
startup = "required"
attestation = "signature_required"
trusted_public_keys = ["ed25519:<base64-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](/build-plugins/native/about)
explains in-process Rust shared libraries, while the [worker plugin guide](/build-plugins/workers/about) covers Relay-managed Rust, Python, and
custom-command workers. Protocol implementers can use the [grpc-v1 protocol reference](/build-plugins/workers/grpc-v1-protocol) to inspect the complete
transport contract.