Skip to content

fix(python): load dynamic plugin specs from TOML - #694

Merged
rapids-bot[bot] merged 9 commits into
NVIDIA:release/0.7from
bbednarski9:fix/python-dynamic-plugin-toml-specs
Aug 4, 2026
Merged

fix(python): load dynamic plugin specs from TOML#694
rapids-bot[bot] merged 9 commits into
NVIDIA:release/0.7from
bbednarski9:fix/python-dynamic-plugin-toml-specs

Conversation

@bbednarski9

@bbednarski9 bbednarski9 commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Overview

Adds a minimal Python compatibility API that converts standard [[plugins.dynamic]] records from one explicit plugins.toml into the existing DynamicPluginActivationSpec objects accepted by initialize_with_dynamic_plugins().

This unblocks Python applications that embed Relay without introducing the larger file-backed activation, lifecycle reconciliation, dynamic layering, or initialization redesign proposed for a later release. The new API is intentionally a temporary 0.7 surface:

plugin_config_path = os.environ["NEMO_RELAY_PLUGINS_TOML"]
dynamic_plugins = plugin.load_dynamic_plugin_activation_specs(plugin_config_path)
activation = await plugin.initialize_with_dynamic_plugins({}, dynamic_plugins)

NEMO_RELAY_PLUGINS_TOML is an optional host-side convention in this example. Relay does not read the environment variable automatically; the embedding application resolves a path through its environment, command-line, or configuration system and passes that path to the helper.

  • I confirm this contribution is my own work, or I have the right to submit it under this project's license.
  • I searched existing issues and open pull requests, and this does not duplicate existing work.

A broader implementation exists in #684. This PR is a deliberately scoped 0.7 alternative that reuses the existing activation owner instead of introducing shared lifecycle and host-configuration infrastructure.

Details

Public API

Adds:

def load_dynamic_plugin_activation_specs(
    plugin_config_path: str | os.PathLike[str],
) -> list[DynamicPluginActivationSpec]: ...

The helper:

  • Reads one explicitly selected plugins.toml.
  • Parses every [[plugins.dynamic]] record in declaration order.
  • Resolves relative manifest paths against the selected file.
  • Reads plugin.id and plugin.kind from each manifest.
  • Preserves the record's JSON-compatible config.
  • Rejects malformed TOML, invalid record shapes, unsupported fields, invalid plugin identities, duplicate plugin IDs, and non-JSON configuration.
  • Returns the existing activation-spec type without loading code.

The existing dynamic initializer now accepts a Sequence rather than only a list. This reflects its existing behavior and allows parser results, lists, and tuples to compose without casts.

Developer flow
flowchart LR
    User["User selects a plugins.toml"] -->
    Host["Embedding host resolves the path"]

    Env["Optional NEMO_RELAY_PLUGINS_TOML"] --> Host
    Host --> Helper["load_dynamic_plugin_activation_specs(path)"]
    Helper --> Config["Read one explicit plugins.toml"]
    Config --> Records["Parse [[plugins.dynamic]] records"]
    Records --> Manifests["Resolve and read relay-plugin.toml manifests"]
    Manifests --> Specs["Build DynamicPluginActivationSpec list"]
    Specs --> Initialize["initialize_with_dynamic_plugins(config, specs)"]
    Initialize --> Activation["Owned PluginHostActivation"]
    Activation --> Runtime["Host retains activation while work is admitted"]
    Runtime --> Close["await activation.close() during shutdown"]
Loading
Configuration behavior

The temporary dynamic path and existing static configuration path remain separate:

flowchart TB
    subgraph Static["Existing static component resolution"]
        UserConfig["User plugins.toml"] --> StaticLayering["User → project → system → programmatic overlay"]
        ProjectConfig["Project .nemo-relay/plugins.toml"] --> StaticLayering
        SystemConfig["System /etc/nemo-relay/plugins.toml"] --> StaticLayering
    end

    subgraph Dynamic["New 0.7 compatibility path"]
        ExplicitPath["One explicit plugins.toml path"] --> DynamicParser["Parse [[plugins.dynamic]] only"]
        DynamicParser --> DynamicSpecs["Explicit activation specs"]
    end

    StaticLayering --> HostInitializer["Existing dynamic host initializer"]
    DynamicSpecs --> HostInitializer
    HostInitializer --> OwnedHost["PluginHostActivation"]
Loading

The helper does not perform dynamic-plugin layering. It reads only the explicitly supplied file. Static [[components]] from that file are inherited only when the same file is also selected by Relay's normal static discovery.

Every dynamic declaration in the selected file becomes an activation spec. Passing those specs to initialize_with_dynamic_plugins() is explicit consent to load the referenced trusted native libraries or worker processes.

Python workers that require a lifecycle-managed environment_ref still require the existing explicit activation or CLI lifecycle path.

Intentional non-goals

This PR does not:

  • Consolidate initialize() and initialize_with_dynamic_plugins().
  • Add a unified initialize_from_plugins_toml() API.
  • Discover or merge dynamic records across user, project, and system layers.
  • Read or reconcile .dynamic-plugins.json.
  • Consult CLI enablement or tombstone state.
  • Provision or attest Python worker environments.
  • Change plugin enablement, install plugins, or execute package managers.
  • Change Rust, Node.js, Go, FFI, or CLI behavior.

The helper is documented as a 0.7 compatibility surface and is expected to be deprecated after the unified file-backed initializer lands. Keeping the conversion behind one Relay API lets embedded hosts remove their TOML and manifest parsing now while keeping the future migration localized to one call site.

Documentation and validation

Updates the Python type stub, plugin-configuration guide, and 0.7 release notes. Tests cover relative and absolute manifest resolution, native and worker spec construction, config preservation, malformed records and TOML, missing manifests, duplicate IDs, and end-to-end native activation from a real [[plugins.dynamic]] record.

Validation completed:

  • Focused parser and native-activation tests: 16 passed.
  • Ruff formatting and linting.
  • ty type checking.
  • Changed-file and repository-wide pre-commit suites.
  • Cargo formatting, clippy, check, and dependency-policy checks.
  • Python worker protobuf compatibility.
  • Go formatting and vet.
  • Node formatting and public docstring checks.
  • Fern structure and strict broken-link validation.

The complete dynamic-host Python module was also attempted locally. Pre-existing tests inherited an invalid machine-level /etc/nemo-relay/plugins.toml, and sandboxed worker tests could not bind Unix sockets. The tests directly covering this change passed independently.

Breaking changes: none.

Where should the reviewer start?

Start with python/nemo_relay/plugin.py, specifically load_dynamic_plugin_activation_specs().

The central design decision is that this helper performs only the missing file-to-activation-spec conversion. It deliberately reuses the existing dynamic initializer and owned activation lifetime rather than introducing another activation owner or pulling CLI lifecycle behavior into the Python binding.

Then review python/tests/test_dynamic_plugin_host.py for the standard TOML parsing, failure behavior, and end-to-end native activation coverage.

Related Issues: (use one of the action keywords Closes / Fixes / Resolves / Relates to)

Summary by CodeRabbit

  • New Features
    • Added a Python compatibility helper for loading dynamic plugin activation specifications from a selected plugins.toml file.
    • Supports manifest path resolution, ordered activation specifications, nested configuration, duplicate detection, and validation of plugin records and JSON values.
    • Dynamic plugin initialization now accepts any ordered collection of activation specifications.
  • Documentation
    • Added guidance covering configuration resolution, explicit loading consent, supported behavior, limitations, and planned deprecation.
  • Tests
    • Expanded coverage for valid configurations, absolute and nested manifest paths, malformed files, duplicate IDs, and missing manifests.

Summary by CodeRabbit

  • New Features
    • Added support for loading dynamic plugin activation settings from a selected plugins.toml file.
    • Added validation for manifests, duplicate identifiers, malformed configuration, and invalid JSON values.
    • Dynamic plugin initialization now accepts any ordered collection of activation specifications.
  • Documentation
    • Added configuration guidance, behavior details, limitations, compatibility notes, and planned deprecation information.
  • Tests
    • Added coverage for valid configurations, path resolution, nested settings, and common loading errors.

Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

The PR adds a public Python helper that loads dynamic plugin activation specifications from an explicit plugins.toml, validates manifests and configuration, supports sequence inputs during initialization, and documents the compatibility behavior.

Changes

Dynamic plugin loading

Layer / File(s) Summary
TOML loader and validation
python/nemo_relay/plugin.py, python/tests/test_dynamic_plugin_host.py
The loader parses dynamic plugin records, resolves native and worker manifests, validates fields and JSON-compatible configuration, rejects duplicates, preserves order, and reports malformed TOML or missing manifests.
Public API and host integration
python/nemo_relay/plugin.py, python/nemo_relay/plugin.pyi, python/tests/test_dynamic_plugin_host.py
The loader is exported and typed. initialize_with_dynamic_plugins accepts any Sequence. Integration tests load specifications from project configuration.
Compatibility documentation
docs/configure-plugins/plugin-configuration-files.mdx, docs/about-nemo-relay/release-notes/index.mdx
The documentation describes the helper, manifest resolution, omitted lifecycle behavior, compatibility status, and planned deprecation.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant PluginLoader
  participant PluginsToml
  participant PluginManifest
  participant PluginHost
  Caller->>PluginLoader: provide plugins.toml path
  PluginLoader->>PluginsToml: parse dynamic declarations
  PluginLoader->>PluginManifest: resolve and validate manifests
  PluginLoader-->>Caller: return activation specifications
  Caller->>PluginHost: initialize with specifications
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title follows Conventional Commits format, uses an allowed type and lowercase scope, states the change clearly, and is under 72 characters.
Description check ✅ Passed The description includes all required sections, completed confirmations, detailed implementation scope, reviewer guidance, related issues, and validation results.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@bbednarski9 bbednarski9 added this to the 0.7 milestone Aug 4, 2026
@bbednarski9 bbednarski9 self-assigned this Aug 4, 2026
@github-actions github-actions Bot added size:M PR is medium Bug issue describes bug; PR fixes bug lang:python PR changes/introduces Python code labels Aug 4, 2026
@bbednarski9
bbednarski9 marked this pull request as ready for review August 4, 2026 16:46
@bbednarski9
bbednarski9 requested review from a team as code owners August 4, 2026 16:46
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/configure-plugins/plugin-configuration-files.mdx`:
- Line 175: Update the load_dynamic_plugin_activation_specs example so the
placeholder path is written as "<path/to/plugins.toml>" in monospace, clearly
distinguishing it from a literal directory.
- Around line 172-177: Update the plugin configuration example around
initialize_with_dynamic_plugins to show valid asynchronous usage by wrapping the
call in an async function and invoking it appropriately, or explicitly state
that it must run in an async REPL; ensure the snippet cannot produce an
await-outside-function SyntaxError in a normal Python module.
- Around line 179-184: Update the helper documentation near the existing
behavior description to state that it returns accepted declarations in file
order and raises ValueError for invalid records, malformed TOML, invalid
manifest fields, duplicate IDs, or non-JSON configuration. Also document that
missing files raise FileNotFoundError and optional static JSON Schema validation
is not applied.

In `@python/tests/test_dynamic_plugin_host.py`:
- Around line 499-500: Update the test around initialize_with_dynamic_plugins to
convert the loaded dynamic_plugins list to a tuple before passing it, while
preserving all existing activation assertions and coverage of the same plugin
configuration.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 7cc0fa32-dcff-4f65-89bd-a54c69838328

📥 Commits

Reviewing files that changed from the base of the PR and between 0ef068f and 10e4b95.

📒 Files selected for processing (5)
  • docs/about-nemo-relay/release-notes/index.mdx
  • docs/configure-plugins/plugin-configuration-files.mdx
  • python/nemo_relay/plugin.py
  • python/nemo_relay/plugin.pyi
  • python/tests/test_dynamic_plugin_host.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (27)
**/*.mdx

📄 CodeRabbit inference engine (.agents/skills/review-doc-style/SKILL.md)

MDX top-of-file SPDX comments must use {/* ... */} delimiters instead of HTML comment delimiters (Must-Fix)

**/*.mdx: In MDX files, top-of-file comments must use JSX comment delimiters ({/* and */}); do not use HTML comments for MDX SPDX headers.
New or regenerated MDX files must use {/* ... */} for top-of-file SPDX comments.

Files:

  • docs/configure-plugins/plugin-configuration-files.mdx
  • docs/about-nemo-relay/release-notes/index.mdx
{docs,examples}/**/*

📄 CodeRabbit inference engine (.agents/skills/rename-surfaces/SKILL.md)

Update docs and examples.

Files:

  • docs/configure-plugins/plugin-configuration-files.mdx
  • docs/about-nemo-relay/release-notes/index.mdx
**/*

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

**/*: Format changed files with the language-native formatter before the final lint/test pass.
If dynamic plugin behavior changed, use maintain-dynamic-plugins and include the native SDK, worker protocol, Python SDK, docs, packaging, and Codecov surfaces in the validation plan.
If code changes alter APIs, bindings, commands, paths, packaging behavior, observability/adaptive semantics, or documented best practices, update any dependent maintainer or consumer skills in the same branch.
During iteration, prefer uv run pre-commit run --files <changed files...>.
Before review or handoff, run uv run pre-commit run --all-files.

**/*: Use release tags in raw Rust-compatible SemVer without a leading v; tags such as v0.1.0 are prohibited.
Use branch prefixes feat/, fix/, docs/, test/, or refactor/ according to the change purpose.
Every commit in a pull request must include a DCO Signed-off-by: sign-off.
Before submitting a pull request, ensure pre-commit hooks, relevant tests, target-specific builds, documentation updates, and a rebase on the latest main are complete.
Use commit messages in the form type: short description, with a valid type and a first line under 72 characters.

Files:

  • docs/configure-plugins/plugin-configuration-files.mdx
  • docs/about-nemo-relay/release-notes/index.mdx
  • python/nemo_relay/plugin.pyi
  • python/nemo_relay/plugin.py
  • python/tests/test_dynamic_plugin_host.py
docs/**/*

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If documentation examples or commands under docs/ change, run the targeted docs checks appropriate to the change.

Files:

  • docs/configure-plugins/plugin-configuration-files.mdx
  • docs/about-nemo-relay/release-notes/index.mdx
**/*.{md,mdx}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If links in documentation change, run just docs-linkcheck.

Use documented public APIs and stable wrapper commands in examples and user-facing documentation; do not rely on internal helpers.

**/*.{md,mdx}: Prefer the documented public API over internal shortcuts in documentation and examples.
Keep package names, repository references, and build commands current.
Contribution workflow documentation must require an issue before external contribution pull requests and note that NVIDIA contributors may use a GitHub or Linear issue.
Update entry-point documentation when examples or reading paths change.
Keep release-process and release-notes guidance in maintainer documentation such as RELEASING.md, rather than user-facing documentation pages or CHANGELOG.md.
Use stable user-facing wrappers at the scripts/ root in documentation and examples; reference namespaced helper paths only for internal maintenance documentation.
When detailed dynamic plugin guides exist, keep Rust native plugin examples, Python worker plugin examples, and grpc-v1 protocol details on separate pages.
Dynamic plugin manifests in documentation and examples should use compat.relay = ">=0.5,<1.0" unless deliberately narrower.
Render images, diagrams, tables, and other visual content at representative page widths, ensuring legibility and complete access without clipping; use responsive scaling, reflow, or overflow as appropriate and scope visual styling narrowly.
Dynamic plugin entry pages should link to native, worker, Rust example, Python example, and protocol pages when those pages exist.
Images, diagrams, tables, and custom visual content must remain legible and fully accessible at representative desktop and narrow page widths.
Release-policy documentation must point to GitHub Releases as the only release-history source of truth.
Run just docs when the documentation site changes; retain ./scripts/build-docs.sh html as the compatibility wrapper.

Files:

  • docs/configure-plugins/plugin-configuration-files.mdx
  • docs/about-nemo-relay/release-notes/index.mdx
**/*.{md,mdx,py,go,js,ts}

📄 CodeRabbit inference engine (.agents/skills/maintain-observability/SKILL.md)

Examples and documentation must use each exporter's documented flush/deregister order before shutdown.

Files:

  • docs/configure-plugins/plugin-configuration-files.mdx
  • docs/about-nemo-relay/release-notes/index.mdx
  • python/nemo_relay/plugin.py
  • python/tests/test_dynamic_plugin_host.py
docs/**/*.{md,mdx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Update relevant reference documentation when public behavior or APIs change.

Files:

  • docs/configure-plugins/plugin-configuration-files.mdx
  • docs/about-nemo-relay/release-notes/index.mdx
**/*.{rs,py,go,js,jsx,ts,tsx,c,h,html,md,mdx,toml}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Include the appropriate SPDX copyright and Apache-2.0 license header in every source file.

Files:

  • docs/configure-plugins/plugin-configuration-files.mdx
  • docs/about-nemo-relay/release-notes/index.mdx
  • python/nemo_relay/plugin.py
  • python/tests/test_dynamic_plugin_host.py
**/*.{md,mdx,rst}

📄 CodeRabbit inference engine (.agents/skills/review-doc-style/assets/nvidia-style-technical-docs.md)

**/*.{md,mdx,rst}: Use title case consistently for technical documentation headings and table headers; avoid quotation marks, ampersands, and exclamation marks in headings, while preserving official product, event, research, and whitepaper title case.
Format code elements, commands, parameters, package names, expressions, directories, file names, and paths in monospace; represent path placeholders with angle brackets inside monospace.
Format UI buttons, menus, fields, and labels in bold, and separate consecutive UI navigation labels with >.
Use quotation marks for error messages and strings when appropriate, italics for newly introduced terms and publication titles, and plain text for keyboard shortcuts.
Represent GitHub repositories with owner/repository link text, such as [NVIDIA/NeMo](link), rather than generic repository wording.
Introduce every code block with a complete sentence; do not let a code block complete or interrupt the grammar of surrounding prose; use syntax highlighting when supported.
Keep inline method, function, and class references consistent with nearby documentation; omit empty parentheses in prose when no call is shown.
Use descriptive link text matching the destination title when possible; avoid raw URLs, generic anchors, long-sentence links, and unnecessary links that distract from procedures.
Ensure lists have a complete lead-in sentence, more than one item, no more than two levels, parallel construction, one idea or action per item, and appropriate punctuation; use bullets for unordered items and numbers for ordered tasks.
Format definition lists with a bold term followed by a complete, parallel, punctuated definition.
Use tables for reference information, decision support, compatibility matrices, and comparable choices; flag one-row tables, missing captions or lead-ins, sentence-case headers where title case is expected, unexplained empty cells, and code or links that would be clearer as prose.
Write procedure steps as imperative ...

Files:

  • docs/configure-plugins/plugin-configuration-files.mdx
  • docs/about-nemo-relay/release-notes/index.mdx
{docs/**,README.md,CONTRIBUTING.md,RELEASING.md,SECURITY.md}

⚙️ CodeRabbit configuration file

{docs/**,README.md,CONTRIBUTING.md,RELEASING.md,SECURITY.md}: Review documentation for technical accuracy against the current API, command correctness, and consistency across language bindings.
Flag stale examples, missing SPDX headers where required, and instructions that no longer match CI or pre-commit behavior.

Files:

  • docs/configure-plugins/plugin-configuration-files.mdx
  • docs/about-nemo-relay/release-notes/index.mdx
docs/about-nemo-relay/release-notes/{index,highlights,known-issues}.mdx

📄 CodeRabbit inference engine (.agents/skills/draft-release-notes/SKILL.md)

docs/about-nemo-relay/release-notes/{index,highlights,known-issues}.mdx: Update only docs/about-nemo-relay/release-notes/index.mdx, docs/about-nemo-relay/release-notes/highlights.mdx, and docs/about-nemo-relay/release-notes/known-issues.mdx unless the release changes their route or entry points.
Preserve the existing MDX front matter and the JSX SPDX comment in the release-notes pages.

Files:

  • docs/about-nemo-relay/release-notes/index.mdx
{crates/python/src/py_api/mod.rs,python/nemo_relay/**/*.py,python/nemo_relay/**/*.pyi,go/nemo_relay/**/*.go,crates/node/src/api/**/*.rs}

📄 CodeRabbit inference engine (.agents/skills/add-binding-feature/SKILL.md)

Update the language-native bindings for every exposed surface in Python, Go, and Node.js.

Files:

  • python/nemo_relay/plugin.pyi
  • python/nemo_relay/plugin.py
{python/nemo_relay/**/*.py,python/nemo_relay/**/*.pyi,go/nemo_relay/**/*.go}

📄 CodeRabbit inference engine (.agents/skills/add-binding-feature/SKILL.md)

Update language wrapper helpers such as Python wrapper modules, Python type stubs, and Go shorthand packages when the new behavior belongs in those helper layers.

Files:

  • python/nemo_relay/plugin.pyi
  • python/nemo_relay/plugin.py
python/nemo_relay/**/*

⚙️ CodeRabbit configuration file

python/nemo_relay/**/*: Review Python wrapper changes for typed API consistency, contextvars-based scope isolation, async behavior, and parity with the native extension.
Stubs and runtime implementations should stay aligned.

Files:

  • python/nemo_relay/plugin.pyi
  • python/nemo_relay/plugin.py
{crates/**/src/**/*.rs,python/**/*.py}

📄 CodeRabbit inference engine (.agents/skills/maintain-dynamic-plugins/SKILL.md)

Do not add tests under src; Rust tests belong in crate tests/ trees, and Python SDK tests belong under python/tests.

Files:

  • python/nemo_relay/plugin.py
  • python/tests/test_dynamic_plugin_host.py
python/nemo_relay/{adaptive.py,plugin.py}

📄 CodeRabbit inference engine (.agents/skills/maintain-optimizer/SKILL.md)

Keep Python adaptive/plugin wrappers in python/nemo_relay/adaptive.py and python/nemo_relay/plugin.py synchronized with the shared adaptive/plugin boundary and lifecycle.

Files:

  • python/nemo_relay/plugin.py
**/*.{rs,py,go,js,ts}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If a language surface changed, always run that language's test target even when Rust core did not change.

**/*.{rs,py,go,js,ts}: When observability configuration or lifecycle is exposed, keep FFI and Python, Go, and Node.js binding-native config objects and subscriber/exporter methods aligned in logical knobs and semantics.
Require every OpenTelemetry endpoint to have a type and nonblank destination; resolve header_env values at activation and reject missing, blank, or duplicate headers.
Concatenate layered ATOF sink, ATIF storage, and OpenTelemetry endpoint lists with higher-precedence entries first.
Preserve correct handling of mark events, start/end events, orphan cases, and span or trajectory fields derived from intended event data.
Run affected Rust tests and just test-rust when event fields change; run just test-python, just test-go, and just test-node when binding-native configuration or lifecycle changes.

Files:

  • python/nemo_relay/plugin.py
  • python/tests/test_dynamic_plugin_host.py
**/*.{rs,py,js,ts,tsx,go,java,kt,swift}

📄 CodeRabbit inference engine (.agents/skills/add-middleware/SKILL.md)

Add tests covering registration and duplicate names, deregistration and missing names, priority ordering, callback failure policy, scope-local inheritance and cleanup, event payload semantics, immutable mark and scope fields, and parity across affected bindings.

Files:

  • python/nemo_relay/plugin.py
  • python/tests/test_dynamic_plugin_host.py
**/*.{rs,py,js,jsx,ts,tsx,go,c,h,cc,cpp,md,toml,yml,yaml,sh}

📄 CodeRabbit inference engine (AGENTS.md)

Keep SPDX headers on source, documentation, scripts, and configuration files; the project is Apache-2.0.

Files:

  • python/nemo_relay/plugin.py
  • python/tests/test_dynamic_plugin_host.py
**/*.{rs,py}

📄 CodeRabbit inference engine (AGENTS.md)

Use snake_case naming in Rust and Python.

Files:

  • python/nemo_relay/plugin.py
  • python/tests/test_dynamic_plugin_host.py
**/*.{rs,py,js,mjs,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Preserve the existing Tokio-based asynchronous model and callback/future lifetimes; do not unexpectedly block or hide async work in bindings.

Files:

  • python/nemo_relay/plugin.py
  • python/tests/test_dynamic_plugin_host.py
python/nemo_relay/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Keep Python wrapper modules under python/nemo_relay/; the native extension is built from crates/python with maturin.

Files:

  • python/nemo_relay/plugin.py
**/*.py

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.py: Lint Python with Ruff using rule sets E, F, W, and I.
Format Python with the Ruff formatter, using a 120-character line length and double quotes.
Run ty for Python type checking.
Use Python snake_case naming conventions.

Files:

  • python/nemo_relay/plugin.py
  • python/tests/test_dynamic_plugin_host.py
**/*.{rs,py,go,js,jsx,ts,tsx,c,h}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.{rs,py,go,js,jsx,ts,tsx,c,h}: Run tests for every language affected by a change; changes to the core Rust crate require tests across all bindings.
Use SONAR_IGNORE_START / SONAR_IGNORE_END only for documented false positives, keep ignored blocks minimal, explain them with a comment, and obtain reviewer sign-off.
Preserve the layered architecture in which Rust provides the core runtime and C FFI, PyO3, and NAPI provide bindings that mirror the full API surface.

Files:

  • python/nemo_relay/plugin.py
  • python/tests/test_dynamic_plugin_host.py
python/tests/**/*.py

📄 CodeRabbit inference engine (.agents/skills/test-python-binding/SKILL.md)

python/tests/**/*.py: Pytest is used to run tests.
Do not add @pytest.mark.asyncio to any test; async tests are automatically detected and run by the async runner.
Do not add a -> None return type annotation to test functions.
When mocking a class, do not define a new class; use unittest.mock.MagicMock or unittest.mock.AsyncMock, with the spec constructor argument when necessary.
Name mocked classes with the mock prefix, not fake.
Prefer pytest fixtures over helper methods.
Do not repeat fixtures; if a fixture is needed in multiple test files, place it in a conftest.py file.
When creating a fixture, use @pytest.fixture(name="<fixture_name>"[, scope="<scope>"]) and define the fixture function as def <fixture_name>_fixture() -> <return_type>:; only specify scope when it is not function.
Prefer pytest.mark.parametrize over creating individual tests for different input types.

Maintain test coverage for Python binding and wrapper changes with the Python test suite.

Files:

  • python/tests/test_dynamic_plugin_host.py
**/{test,tests}/**/*

📄 CodeRabbit inference engine (CONTRIBUTING.md)

When adding functionality, include tests in the appropriate test files for each affected language binding.

Files:

  • python/tests/test_dynamic_plugin_host.py
{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}

⚙️ CodeRabbit configuration file

{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}: Tests should cover the behavior promised by the changed API surface, including error paths and cross-request isolation where relevant.
Prefer assertions on lifecycle events, scope stacks, middleware ordering, and binding parity over shallow smoke tests.

Files:

  • python/tests/test_dynamic_plugin_host.py
🪛 ast-grep (0.45.0)
python/nemo_relay/plugin.py

[info] 504-504: use jsonify instead of json.dumps for JSON output
Context: json.dumps(config, allow_nan=False)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

🪛 Ruff (0.16.1)
python/nemo_relay/plugin.py

[warning] 425-425: Too many branches (13 > 12)

(PLR0912)


[warning] 450-450: Prefer TypeError exception for invalid type

(TRY004)


[warning] 450-450: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 454-454: Prefer TypeError exception for invalid type

(TRY004)


[warning] 454-454: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 460-460: Prefer TypeError exception for invalid type

(TRY004)


[warning] 460-460: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 470-473: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 482-482: Prefer TypeError exception for invalid type

(TRY004)


[warning] 482-482: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 486-488: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 492-494: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 496-496: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 501-503: Prefer TypeError exception for invalid type

(TRY004)


[warning] 501-503: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 507-510: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 527-527: Avoid specifying long messages outside the exception class

(TRY003)

python/tests/test_dynamic_plugin_host.py

[warning] 309-310: Unparenthesized implicit string concatenation in collection

Did you forget a comma?

(ISC004)


[warning] 338-338: Pattern passed to match= contains metacharacters but is neither escaped nor raw

(RUF043)


[warning] 346-346: Pattern passed to match= contains metacharacters but is neither escaped nor raw

(RUF043)

🔇 Additional comments (6)
docs/configure-plugins/plugin-configuration-files.mdx (1)

166-171: LGTM!

Also applies to: 186-188

docs/about-nemo-relay/release-notes/index.mdx (1)

101-106: LGTM!

python/nemo_relay/plugin.py (2)

14-15: LGTM!

Also applies to: 17-20, 425-529, 565-565, 720-720


16-16: 🩺 Stability & Availability

Keep tomllib

The package requires Python 3.11+, and CI targets Python 3.11. No compatibility fallback is needed.

			> Likely an incorrect or invalid review comment.
python/tests/test_dynamic_plugin_host.py (1)

200-349: LGTM!

Also applies to: 486-487

python/nemo_relay/plugin.pyi (1)

4-5: LGTM!

Also applies to: 164-171

Comment thread docs/configure-plugins/plugin-configuration-files.mdx
Comment thread docs/configure-plugins/plugin-configuration-files.mdx Outdated
Comment thread docs/configure-plugins/plugin-configuration-files.mdx Outdated
Comment thread python/tests/test_dynamic_plugin_host.py Outdated

@mnajafian-nv mnajafian-nv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

Comment thread python/nemo_relay/plugin.py
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@python/tests/test_dynamic_plugin_host.py`:
- Around line 264-270: Update
test_load_dynamic_plugin_activation_specs_rejects_unsupported_version to
parameterize the version value over True, 0, 2, and "1", writing each value into
plugins.toml and validating the corresponding unsupported-version error. Add a
match= pattern to pytest.raises so the assertion is narrowed and satisfies
PT011, while preserving the existing expected message validation.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 213e670a-bc47-4c00-97d6-769ea131a963

📥 Commits

Reviewing files that changed from the base of the PR and between 91332e1 and 622963a.

📒 Files selected for processing (2)
  • python/nemo_relay/plugin.py
  • python/tests/test_dynamic_plugin_host.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (19)
{crates/**/src/**/*.rs,python/**/*.py}

📄 CodeRabbit inference engine (.agents/skills/maintain-dynamic-plugins/SKILL.md)

Do not add tests under src; Rust tests belong in crate tests/ trees, and Python SDK tests belong under python/tests.

Files:

  • python/tests/test_dynamic_plugin_host.py
  • python/nemo_relay/plugin.py
python/tests/**/*.py

📄 CodeRabbit inference engine (.agents/skills/test-python-binding/SKILL.md)

python/tests/**/*.py: Pytest is used to run tests.
Do not add @pytest.mark.asyncio to any test; async tests are automatically detected and run by the async runner.
Do not add a -> None return type annotation to test functions.
When mocking a class, do not define a new class; use unittest.mock.MagicMock or unittest.mock.AsyncMock, with the spec constructor argument when necessary.
Name mocked classes with the mock prefix, not fake.
Prefer pytest fixtures over helper methods.
Do not repeat fixtures; if a fixture is needed in multiple test files, place it in a conftest.py file.
When creating a fixture, use @pytest.fixture(name="<fixture_name>"[, scope="<scope>"]) and define the fixture function as def <fixture_name>_fixture() -> <return_type>:; only specify scope when it is not function.
Prefer pytest.mark.parametrize over creating individual tests for different input types.

Maintain test coverage for Python binding and wrapper changes with the Python test suite.

Files:

  • python/tests/test_dynamic_plugin_host.py
**/*

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

**/*: Format changed files with the language-native formatter before the final lint/test pass.
If dynamic plugin behavior changed, use maintain-dynamic-plugins and include the native SDK, worker protocol, Python SDK, docs, packaging, and Codecov surfaces in the validation plan.
If code changes alter APIs, bindings, commands, paths, packaging behavior, observability/adaptive semantics, or documented best practices, update any dependent maintainer or consumer skills in the same branch.
During iteration, prefer uv run pre-commit run --files <changed files...>.
Before review or handoff, run uv run pre-commit run --all-files.

**/*: Use release tags in raw Rust-compatible SemVer without a leading v; tags such as v0.1.0 are prohibited.
Use branch prefixes feat/, fix/, docs/, test/, or refactor/ according to the change purpose.
Every commit in a pull request must include a DCO Signed-off-by: sign-off.
Before submitting a pull request, ensure pre-commit hooks, relevant tests, target-specific builds, documentation updates, and a rebase on the latest main are complete.
Use commit messages in the form type: short description, with a valid type and a first line under 72 characters.

Files:

  • python/tests/test_dynamic_plugin_host.py
  • python/nemo_relay/plugin.py
**/*.{rs,py,go,js,ts}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

If a language surface changed, always run that language's test target even when Rust core did not change.

**/*.{rs,py,go,js,ts}: When observability configuration or lifecycle is exposed, keep FFI and Python, Go, and Node.js binding-native config objects and subscriber/exporter methods aligned in logical knobs and semantics.
Require every OpenTelemetry endpoint to have a type and nonblank destination; resolve header_env values at activation and reject missing, blank, or duplicate headers.
Concatenate layered ATOF sink, ATIF storage, and OpenTelemetry endpoint lists with higher-precedence entries first.
Preserve correct handling of mark events, start/end events, orphan cases, and span or trajectory fields derived from intended event data.
Run affected Rust tests and just test-rust when event fields change; run just test-python, just test-go, and just test-node when binding-native configuration or lifecycle changes.

Files:

  • python/tests/test_dynamic_plugin_host.py
  • python/nemo_relay/plugin.py
**/*.{rs,py,js,ts,tsx,go,java,kt,swift}

📄 CodeRabbit inference engine (.agents/skills/add-middleware/SKILL.md)

Add tests covering registration and duplicate names, deregistration and missing names, priority ordering, callback failure policy, scope-local inheritance and cleanup, event payload semantics, immutable mark and scope fields, and parity across affected bindings.

Files:

  • python/tests/test_dynamic_plugin_host.py
  • python/nemo_relay/plugin.py
**/*.{md,mdx,py,go,js,ts}

📄 CodeRabbit inference engine (.agents/skills/maintain-observability/SKILL.md)

Examples and documentation must use each exporter's documented flush/deregister order before shutdown.

Files:

  • python/tests/test_dynamic_plugin_host.py
  • python/nemo_relay/plugin.py
**/*.{rs,py,js,jsx,ts,tsx,go,c,h,cc,cpp,md,toml,yml,yaml,sh}

📄 CodeRabbit inference engine (AGENTS.md)

Keep SPDX headers on source, documentation, scripts, and configuration files; the project is Apache-2.0.

Files:

  • python/tests/test_dynamic_plugin_host.py
  • python/nemo_relay/plugin.py
**/*.{rs,py}

📄 CodeRabbit inference engine (AGENTS.md)

Use snake_case naming in Rust and Python.

Files:

  • python/tests/test_dynamic_plugin_host.py
  • python/nemo_relay/plugin.py
**/*.{rs,py,js,mjs,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Preserve the existing Tokio-based asynchronous model and callback/future lifetimes; do not unexpectedly block or hide async work in bindings.

Files:

  • python/tests/test_dynamic_plugin_host.py
  • python/nemo_relay/plugin.py
**/*.py

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.py: Lint Python with Ruff using rule sets E, F, W, and I.
Format Python with the Ruff formatter, using a 120-character line length and double quotes.
Run ty for Python type checking.
Use Python snake_case naming conventions.

Files:

  • python/tests/test_dynamic_plugin_host.py
  • python/nemo_relay/plugin.py
**/*.{rs,py,go,js,jsx,ts,tsx,c,h}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.{rs,py,go,js,jsx,ts,tsx,c,h}: Run tests for every language affected by a change; changes to the core Rust crate require tests across all bindings.
Use SONAR_IGNORE_START / SONAR_IGNORE_END only for documented false positives, keep ignored blocks minimal, explain them with a comment, and obtain reviewer sign-off.
Preserve the layered architecture in which Rust provides the core runtime and C FFI, PyO3, and NAPI provide bindings that mirror the full API surface.

Files:

  • python/tests/test_dynamic_plugin_host.py
  • python/nemo_relay/plugin.py
**/{test,tests}/**/*

📄 CodeRabbit inference engine (CONTRIBUTING.md)

When adding functionality, include tests in the appropriate test files for each affected language binding.

Files:

  • python/tests/test_dynamic_plugin_host.py
**/*.{rs,py,go,js,jsx,ts,tsx,c,h,html,md,mdx,toml}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Include the appropriate SPDX copyright and Apache-2.0 license header in every source file.

Files:

  • python/tests/test_dynamic_plugin_host.py
  • python/nemo_relay/plugin.py
{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}

⚙️ CodeRabbit configuration file

{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}: Tests should cover the behavior promised by the changed API surface, including error paths and cross-request isolation where relevant.
Prefer assertions on lifecycle events, scope stacks, middleware ordering, and binding parity over shallow smoke tests.

Files:

  • python/tests/test_dynamic_plugin_host.py
{crates/python/src/py_api/mod.rs,python/nemo_relay/**/*.py,python/nemo_relay/**/*.pyi,go/nemo_relay/**/*.go,crates/node/src/api/**/*.rs}

📄 CodeRabbit inference engine (.agents/skills/add-binding-feature/SKILL.md)

Update the language-native bindings for every exposed surface in Python, Go, and Node.js.

Files:

  • python/nemo_relay/plugin.py
{python/nemo_relay/**/*.py,python/nemo_relay/**/*.pyi,go/nemo_relay/**/*.go}

📄 CodeRabbit inference engine (.agents/skills/add-binding-feature/SKILL.md)

Update language wrapper helpers such as Python wrapper modules, Python type stubs, and Go shorthand packages when the new behavior belongs in those helper layers.

Files:

  • python/nemo_relay/plugin.py
python/nemo_relay/{adaptive.py,plugin.py}

📄 CodeRabbit inference engine (.agents/skills/maintain-optimizer/SKILL.md)

Keep Python adaptive/plugin wrappers in python/nemo_relay/adaptive.py and python/nemo_relay/plugin.py synchronized with the shared adaptive/plugin boundary and lifecycle.

Files:

  • python/nemo_relay/plugin.py
python/nemo_relay/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Keep Python wrapper modules under python/nemo_relay/; the native extension is built from crates/python with maturin.

Files:

  • python/nemo_relay/plugin.py
python/nemo_relay/**/*

⚙️ CodeRabbit configuration file

python/nemo_relay/**/*: Review Python wrapper changes for typed API consistency, contextvars-based scope isolation, async behavior, and parity with the native extension.
Stubs and runtime implementations should stay aligned.

Files:

  • python/nemo_relay/plugin.py
🪛 Ruff (0.16.1)
python/tests/test_dynamic_plugin_host.py

[warning] 268-268: pytest.raises(ValueError) is too broad, set the match parameter or use a more specific exception

(PT011)

python/nemo_relay/plugin.py

[warning] 450-450: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (1)
python/nemo_relay/plugin.py (1)

425-522: LGTM!

Comment thread python/tests/test_dynamic_plugin_host.py Outdated

@mnajafian-nv mnajafian-nv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, well done!

Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
@bbednarski9

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit e5876f1 into NVIDIA:release/0.7 Aug 4, 2026
37 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Aug 4, 2026
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug issue describes bug; PR fixes bug lang:python PR changes/introduces Python code size:M PR is medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants