Package Discoverable Plugins
A discoverable plugin is a package containing one relay-plugin.toml manifest, its
native library or worker entrypoint, and an optional component JSON Schema. The manifest
lets Relay validate compatibility and integrity before code is loaded or a process is
started. Declare the package with [[plugins.dynamic]] in plugins.toml and
put its runtime settings under [plugins.dynamic.config]. The CLI gateway and
SDK initialization read these records and use lifecycle state to select plugins.
A manual record can activate at startup when no lifecycle state file exists.
Refer to
Dynamic Plugin Configuration
for the configuration shape.
Manifest Responsibilities
For native packages, compat.native_api = "1" is the authored manifest contract. It is
not the C host-table ABI number. The
current SDK requires ABI v7, and Relay 0.10 rejects v2-v6 compiled callback layouts.
For workers, declare compat.worker_protocol = "grpc-v1"; the
handshake still negotiates the exact
protocol, surfaces, authentication token, and lifecycle at startup.
Start from a Complete Manifest
These are the complete manifest shapes used by the checked examples. The path in
source.artifact is the file whose bytes the digest covers. The native loader opens that
same library; a worker loader either starts the compiled program or imports the Python
entrypoint from the managed environment.
For application embeddings, core initialization loads a private temporary copy of the native library bytes checked by the host trust policy. The copy remains available until the library unloads. Replacing the source manifest, library, or signature after validation does not change the library loaded by that activation. Native plugins used in embeddings must not depend on adjacent files being available relative to the loaded library’s location. The CLI retains its activation snapshot, which includes adjacent native dependencies. Native library digest checks use bounded regular-file reads and reject symbolic links and special files.
The Python package has one extra responsibility: source.manifest_root identifies the
directory Relay installs into its managed environment. The digest still covers only the
declared artifact, so regenerate it whenever worker.py changes. A compiled worker has
no managed Python environment and starts the executable named by load.entrypoint.
Both worker examples register LLM execution intercepts, so their Relay
compatibility range begins at 0.10.0. A worker that does not register those
surfaces does not need a 0.10 lower bound solely for the additive execution
codec context field. The worker protocol remains grpc-v1.
Package and Register the Artifact
Use the following procedure to turn the checked manifest template into an artifact that Relay can validate and activate:
-
Build the shared library or worker from a clean checkout and place the entrypoint at the relative path declared by
relay-plugin.toml. -
Validate the component schema against valid, invalid, and disabled example configurations. Keep unknown-field behavior aligned between the schema and the plugin’s validation callback.
-
Generate the artifact digest with
shasum -a 256 <artifact>on macOS,sha256sum <artifact>on Linux, orGet-FileHash -Algorithm SHA256 <artifact>in PowerShell. Do this after the finalsource.artifactbytes are in place; changing that artifact invalidates the digest.For example, this Linux command materializes a usable Rust worker manifest without changing the checked template:
The local manifest is deliberately untracked. Rebuilding the executable changes its bytes, so recalculate the digest before every validation attempt.
-
Validate the materialized manifest. For the compiled example above, run
nemo-relay plugins validate ./relay-plugin.local.toml. The Python example keeps its checked digest and uses./relay-plugin.tomldirectly. A successful command confirms manifest structure, compatibility syntax, entrypoint resolution, schema loading, and integrity metadata without activating the component. -
Register the same path with
nemo-relay plugins add --user <materialized-manifest>, or add an explicit[[plugins.dynamic]]reference to the intendedplugins.toml. -
Activate a valid component, inspect the runtime report, execute a representative managed call, then clear and unload or stop the plugin.
Success means package validation passes before activation, tampering causes integrity validation to fail, the runtime report identifies the dynamic component, and teardown removes its registrations before the library unloads or worker exits. The operator-side trust policy and file layout are documented in Manage.
Forward Host Environment Variables
Add load.forward_env to relay-plugin.toml to pass selected host environment
variables to a managed worker. This setting supports Python, Rust, and workers
that Relay starts with a custom command. The following example selects one
variable and all variables that start with EXAMPLE_CONFIG_:
This option requires Relay 0.11 or later. Set compat.relay to a range such as
>=0.11.0,<1.0 if your plugin needs forwarded variables.
Names must start with an ASCII letter or underscore. The rest of the name can
contain ASCII letters, digits, and underscores. An optional * at the end matches
any suffix, including an empty suffix. You can’t use * alone or leave an entry
empty. Names are case-sensitive on Unix. Windows ignores letter case.
Relay reads the selected variables from the host process environment when it
starts the worker. It does not read Windows registry settings or shell profiles.
It also does not read launchd or systemd configuration. Relay skips missing
variables. It keeps these host variables when they exist: PATH,
SYSTEMROOT, WINDIR, TMPDIR, TEMP, TMP, SSL_CERT_FILE, and SSL_CERT_DIR.
If you omit forward_env or set it to [], Relay forwards no other host variables.
Reactivate the plugin to apply changes.
The plugin author sets this option in the manifest. Component settings stay in
plugins.toml. Relay stores only the names and patterns in the plugin record.
It does not show forwarded values in plugin inspection output. Relay supplies its
own token, endpoints, and message limits. Forwarded values cannot replace these
settings.
Worker plugins are trusted code. Running them in a separate process does not
make them a security sandbox. Relay does not block any selected variable by name.
Choose specific names or narrow prefixes. The plugin can read any credentials
you forward. Settings such as PYTHONPATH can change how the worker starts.
Worker Message Limits
Set [load.max_message_bytes] in relay-plugin.toml with an optional
default_bytes and by_name overrides for exact callback registration names.
Keep these worker settings separate from plugins.dynamic.config. The following
example sets a default limit and a larger limit for one callback:
Limits must be whole numbers from 1 through 2,147,483,647 bytes. If you omit
default_bytes, Relay uses 4 MiB. Each limit applies to each sent or received
message, including stream chunks and calls to continuations. It does not limit
the total stream size. Control RPCs use the default limit. Zero is invalid and
does not disable the limit.
Reactivate the plugin after changing its manifest or host environment.
For example, by_name.input_guardrail = 16777216 raises the registered
input_guardrail callback and its continuations to 16 MiB. Host environment
settings override plugin settings. Refer to the
execution message-size contract
for validation, compatibility, and memory considerations.
Callback Deadlines
A deadline limits how long Relay waits for a callback to finish. Set deadlines
for short worker callbacks in relay-plugin.toml. These settings work with
Python, Rust, and workers that you start with a custom command.
The following example sets a default deadline and an override for one callback:
Use positive whole numbers in milliseconds. In this example, most short callbacks
have a limit of 60,000 milliseconds. The check_request callback has a limit of
90,000 milliseconds.
Relay chooses the deadline in this order:
- The value in
by_namefor the callback’s exact registration name. - The
default_msvalue, if set. - The built-in default of 30,000 milliseconds.
Names are case-sensitive. They cannot be empty or have spaces at the start or end. If several callbacks use the same name, they share the same override, even across components or callback types. Use distinct names when callbacks need different deadlines. Names that don’t match a registered callback have no effect. For a conditional middleware guardrail, use the guardrail’s callback name.
Plugin authors and operators use the same manifest settings. Edit the manifest and reactivate the plugin to apply a change. Relay keeps the settings it read when the plugin started until you reactivate it.
The deadline includes any time the callback spends waiting for calls back to Relay. When the deadline expires, Relay asks the worker to cancel the callback and clears the host state for that call. The callback follows its usual error or fallback behavior.
Tool and LLM execution intercepts, including streams, ignore these settings. Their callers control cancellation. Other worker calls, such as validation and shutdown, keep their 30,000-millisecond timeout. Native plugins run inside Relay and don’t use worker deadlines.