Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

License GitHub Release Codecov PyPI npm node Crates.io Crates.io Crates.io Ask DeepWiki

nemo-relay-plugin

nemo-relay-plugin is the Python authoring SDK for NeMo Relay out-of-process dynamic worker plugins. Use it when plugin code should run in its own Python process and communicate with Relay through the versioned grpc-v1 worker protocol.

Relay 0.8 establishes canonical tool results as the grpc-v1 baseline. Workers and custom bindings built for earlier releases must regenerate protobuf bindings, rebuild, and declare compat.relay beginning at 0.8.0. ToolNext.call() returns ToolExecutionResult, preserving opaque annotation metadata independently of the application result JSON.

Relay 0.10 adds LlmExecutionContext to non-streaming and streaming LLM execution callbacks. Workers that register either surface must update their callback signatures, rebuild with the 0.10 SDK, and set compat.relay to begin at 0.10.0. The protocol remains grpc-v1.

Authoring Surface

The following rows describe the plugin authoring surfaces available through this SDK.

Surface Role
WorkerPlugin and PluginContext Define validation and install all 16 worker-owned subscriber and middleware registrations.
serve_plugin Starts an AsyncIO gRPC server from the Relay-managed environment and authenticated local activation endpoints.
Typed runtime helpers Share JSON, event, scope, middleware, continuation, and diagnostic contracts with the Relay host.
Canonical tool results Preserve application results and opaque annotations across tool callbacks and continuations.
LlmExecutionContext Provides the selected request codec and, for non-streaming calls, the completed-response codec while the callback is active.
Generated transport bindings Ship private protobuf bindings in the wheel, so installation does not require protoc or grpcio-tools.

The worker process isolates Python dependencies and crashes from Relay while preserving host-owned execution, event, scope, cancellation, and shutdown semantics.

Installation

Add the SDK to the Python worker project's dependencies:

uv add nemo-relay-plugin

If you are not using uv, install it with pip:

pip install nemo-relay-plugin

Declare a module:function entrypoint that starts the worker with serve_plugin. Register the plugin manifest through the CLI; Relay creates a per-plugin virtual environment, installs source.manifest_root, and records that environment for activation:

nemo-relay plugins add ./relay-plugin.toml
nemo-relay plugins enable <plugin_id>

Python workers cannot be loaded directly from plugins.toml. They must be registered through plugins add, which provisions the required managed environment. plugins remove <plugin_id> deletes that environment.

Getting Started

A minimal worker plugin looks like this:

from nemo_relay_plugin import Json, PluginContext, WorkerPlugin, serve_plugin


class PolicyPlugin(WorkerPlugin):
    plugin_id = "acme.policy"

    def register(self, ctx: PluginContext, config: Json) -> None:
        async def tag_tool_request(tool_name: str, args: Json) -> Json:
            await ctx.runtime.emit_mark("acme.policy.tool_request", {"tool_name": tool_name})
            if isinstance(args, dict):
                return {**args, "policy": "checked"}
            return {"value": args, "policy": "checked"}

        ctx.register_tool_request_intercept("tag_tool_request", tag_tool_request)


async def main() -> None:
    await serve_plugin(PolicyPlugin())

Marks can also declare a typed data schema, exported-log severity, and semantic category without changing the stable grpc-v1 protocol identifier:

from nemo_relay_plugin import DataSchema, EventCategory, LogSeverity

await ctx.runtime.emit_mark(
    "acme.policy.decision",
    {"allowed": True},
    data_schema=DataSchema("acme.policy.decision", "1"),
    severity=LogSeverity.INFO,
    category=EventCategory.CUSTOM,
)

The original positional emit_mark(name, data, metadata) form remains supported. Relay validates the schema and severity again at the host boundary. Use emit_metric(name, measurements, metadata) to attach the reserved Relay metric schema; authoritative metric validation remains in Relay after mark sanitization.

Use await ctx.runtime.runtime_diagnostics() in an asynchronous callback to read an immutable, host-level RuntimeDiagnostics snapshot. Each entry has code, message, and count; entries is ordered by code and get(code) looks up one entry. Relay aggregates repeated codes, retains the latest message, saturates counts, and caps the snapshot at 32 entries. Entries do not identify the emitting plugin. An older host returns gRPC UNIMPLEMENTED, which the SDK reports as an unsupported-runtime-diagnostics error.

Set load.entrypoint to your_module:main in relay-plugin.toml. Relay imports that function and awaits the returned coroutine when it starts the worker process.

Request Intercepts

LLM request intercepts return one canonical outcome:

from nemo_relay_plugin import AnnotatedLLMRequest, Json, LlmRequestInterceptOutcome, PendingMarkSpec


def intercept(
    model_name: str,
    request: Json,
    annotated: AnnotatedLLMRequest | None,
) -> LlmRequestInterceptOutcome:
    del model_name
    headers = {**request.get("headers", {}), "x-policy": "checked"}
    return LlmRequestInterceptOutcome(
        request={**request, "headers": headers},
        annotated_request=annotated,
        pending_marks=[PendingMarkSpec("acme.policy.checked")],
    )

When annotated is present, it is authoritative for provider-body content: leave raw request["content"] unchanged, edit normalized fields or provider extensions through the annotation, and use request["headers"] for transport headers.

Callback Concurrency

The gRPC AsyncIO server can keep multiple RPCs in flight. Callback execution is cooperative: asynchronous callbacks overlap only when they yield control at an await. Synchronous callbacks and synchronous stream iterators run on the worker event-loop thread. Blocking I/O, time.sleep, or long-running CPU work in those callbacks stalls all worker RPCs. Wrap blocking work in an asynchronous callback and offload it with asyncio.to_thread() or another appropriate executor.

The SDK does not configure maximum_concurrent_rpcs, so gRPC does not enforce an application-level RPC admission limit.

Invocation Cancellation

Relay assigns every unary and streaming callback an invocation ID. The host sends CancelInvocation when its managed caller is cancelled, its worker RPC times out, or it stops consuming a worker-backed stream. The SDK cancels the matching asyncio.Task and reports a structured worker.cancelled result.

Cancellation is idempotent. The first request that matches an active callback returns accepted = true; requests for unknown, completed, or already cancelled IDs return accepted = false. Treat acceptance as confirmation that the SDK found and cancelled the task, not as proof that arbitrary user code has stopped.

Python task cancellation is cooperative. Async callbacks should allow asyncio.CancelledError to propagate and use try/finally for cleanup. Synchronous callbacks run on the event-loop thread and cannot be preempted by task cancellation. A blocking synchronous callback can delay both the cancellation RPC and all other worker RPCs, so offload blocking work and make its own cancellation behavior explicit.

grpc-v1 workers are expected to implement this best-effort cancellation contract. Relay remains compatible with older workers that return accepted = false; in that case it still drops the transport request, but it cannot guarantee worker-side interruption.

Windows ARM64 is not currently supported because grpcio does not publish a usable wheel for that platform. The NeMo Relay workspace skips installation and tests for this SDK on Windows ARM64 rather than creating a package without its required gRPC runtime.

Metadata

Release files for nemo-relay-plugin 0.10.0rc2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distribution (wheel)

Table of built distributions (wheels) for nemo-relay-plugin 0.10.0rc2
File Interpreter ABI Platform
nemo_relay_plugin-0.10.0rc2-py3-none-any.whl Python 3 none any Details

Release files / nemo_relay_plugin-0.10.0rc2-py3-none-any.whl

Download URL nemo_relay_plugin-0.10.0rc2-py3-none-any.whl
Size 43.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
296eac271e5b72dc8c2ce5b1d289a43cbc17ce09f1d2c33a1e81da8922fdaa15
BLAKE2b-256 checksum
How to use checksums
b7573e3ec463d7b0c7dfec15367e89f39e3f00e2a7531966ffc01bf25cc04fa7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.

Transparency log

Release history Release notifications | RSS feed

0.10.2

1 release file

0.10.1

1 release file

0.10.0

1 release file

This release

0.10.0rc2 This release

1 release file

0.9.4

1 release file

0.9.3

1 release file

0.9.2

1 release file

0.9.1

1 release file

0.9.0

1 release file

0.8.4

1 release file

0.8.3

1 release file

0.8.2

1 release file

0.8.1

1 release file

0.8.0

1 release file

0.7.3

1 release file

0.7.2

1 release file

0.7.1

1 release file

0.7.0

1 release file

0.6.0

1 release file

0.5.0

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page