Skip to content

feat: add dynamic plugin control-plane foundation#279

Merged
rapids-bot[bot] merged 7 commits into
NVIDIA:mainfrom
afourniernv:afournier/relay-337-plugin-control-plane-model
Jun 22, 2026
Merged

feat: add dynamic plugin control-plane foundation#279
rapids-bot[bot] merged 7 commits into
NVIDIA:mainfrom
afourniernv:afournier/relay-337-plugin-control-plane-model

Conversation

@afourniernv

@afourniernv afourniernv commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Overview

Add the first Rust-side dynamic plugin control-plane slice for RELAY-337.

  • 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.

Details

  • add crate::plugin::dynamic as the foundation for dynamic plugin control-plane types
  • introduce durable dynamic plugin record modeling with metadata, source, spec, and status
  • add authored relay-plugin.toml manifest types, parsing, path loading, strict validation, and conversion into registry records
  • add an in-memory dynamic plugin registry with add/get/list/enable/disable/remove semantics, tombstone revival, and status updates
  • enforce kind-specific manifest and raw-record shape validation for rust_dynamic and worker plugin lanes
  • add focused unit coverage for manifest validation, canonical path handling, registry lifecycle semantics, tombstones, generation behavior, and raw-record integrity checks
  • add PluginError::Conflict and propagate it through adaptive and FFI error mappings

Validation run:

  • cargo fmt --all --check
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo test -p nemo-relay plugin::dynamic::tests --lib

Known validation gap:

  • just test-rust is currently red on this branch because of the pre-existing failure in crates/core/tests/integration/pipeline_tests.rs for test_response_codec_failure_non_fatal, which appears unrelated to this change.

Where should the reviewer start?

Start in crates/core/src/plugin/dynamic.rs and then review:

  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs

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

  • Relates to RELAY-337

Summary by CodeRabbit

Release Notes

  • New Features

    • Added manifest-based dynamic plugin support with a durable control-plane schema.
    • Introduced a dynamic plugin registry for enable/disable/remove plus validation and runtime status tracking.
    • Added relay-plugin.toml parsing and conversion into durable plugin records.
  • Bug Fixes

    • Improved conflict error handling by treating plugin conflicts as “already exists” across components.
  • Tests

    • Expanded unit and coverage tests for dynamic plugin models, manifest validation/loading, and conflict error mappings.
  • Chores

    • Exposed the dynamic plugin module publicly and added the Conflict plugin error variant.

@afourniernv afourniernv requested review from a team and lvojtku as code owners June 16, 2026 18:27
@copy-pr-bot

copy-pr-bot Bot commented Jun 16, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions github-actions Bot added size:XXL PR is very large Feature a new feature lang:python PR changes/introduces Python code lang:rust PR changes/introduces Rust code labels Jun 16, 2026
@coderabbitai

coderabbitai Bot commented Jun 16, 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

Adds a dynamic plugin control-plane to crates/core: a new PluginError::Conflict variant, a full durable schema for DynamicPluginRecord (enums, structs, metadata helpers), relay-plugin.toml manifest parsing and validation with kind-specific constraints, and an in-memory DynamicPluginRegistry with lifecycle and status-update operations. Error mappings in adaptive and FFI crates are updated for the new variant.

Changes

Dynamic Plugin Control-Plane

Layer / File(s) Summary
PluginError::Conflict variant and error mapping
crates/core/src/plugin.rs, crates/adaptive/src/error.rs, crates/ffi/src/error.rs, crates/adaptive/tests/coverage/error_tests.rs, crates/ffi/tests/coverage/error_tests.rs
Adds Conflict(String) to PluginError, preserves it in clone_cached_plugin_error, exposes the dynamic submodule, and maps the variant to AdaptiveError::RegistrationFailed and NemoRelayStatus::AlreadyExists in error converters and tests.
Dynamic plugin durable schema: enums, structs, metadata helpers
crates/core/src/plugin/dynamic.rs
Defines exported enums (DynamicPluginKind, WorkerRuntime, DynamicPluginCapability, startup/attestation modes, check/runtime/failure state axes), durable structs (DynamicPluginMetadata, Source, Spec, Compatibility, LoadContract, Failure, ValidationStatus, RuntimeStatus, Status, Record), is_reconciled/is_tombstoned helpers, and crate-scoped timestamp/generation mutation functions.
Manifest schema, parsing, validation, and record conversion
crates/core/src/plugin/dynamic/manifest.rs
Defines the relay-plugin.toml data model and implements parse_toml, load_from_path with path canonicalization, comprehensive v1 validate (required fields, optional-string trimming, kind-specific capability/load/compat enforcement), into_record converting validated manifests to disabled DynamicPluginRecord, and validation_status.
DynamicPluginRegistry implementation
crates/core/src/plugin/dynamic/registry.rs
Implements DynamicPluginRegistry backed by BTreeMap: get/list, add/add_manifest (shape validation, conflict rejection, tombstone-lineage inheritance, metadata stamping), enable/disable/remove (tombstone guard, generation bump), status update methods with timestamp stamping, and validate_record_shape with kind-specific enforcement.
Unit tests
crates/core/tests/unit/plugin_dynamic_tests.rs
Tests DynamicPluginRecord defaults/reconciliation/tombstone/JSON round-trip, registry add/list/conflict/revival/lineage/enable/disable/remove/status-update semantics and NotFound errors, and manifest parse/validate/load/convert for both worker and rust-dynamic lanes with comprehensive rejection cases (missing fields, capability mismatches, duplicate capabilities, empty optional strings, unsupported manifest versions, tombstoned transitions).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.32% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title follows Conventional Commits format with type 'feat', lowercase scope-less declaration, and a concise imperative summary under 72 characters, accurately describing the primary change.
Description check ✅ Passed The PR description covers all required template sections: Overview with contributor confirmations, Details explaining the changes, Where to start with review guidance, and Related Issues with RELAY-337.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@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: 13

🤖 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 `@crates/adaptive/src/error.rs`:
- Line 56: The PluginError::Conflict variant is being mapped to semantically
incorrect downstream error types, losing conflict-related information needed for
proper error handling. At crates/adaptive/src/error.rs#L56-L56, change the
mapping from Self::InvalidConfig(message) to a conflict or state-failure
semantic error class such as Self::RegistrationFailed or an equivalent conflict
variant that better represents the duplicate-state nature of the error. At
crates/ffi/src/error.rs#L141-L143, change the mapping from
NemoRelayStatus::InvalidArg to NemoRelayStatus::AlreadyExists (or another
dedicated conflict status variant if available) to accurately represent the
conflict semantics when converting PluginError::Conflict to FFI error codes.
Both changes must preserve and forward the conflict context through the error
boundary mappings.

In `@crates/cli/tests/coverage/plugins_tests.rs`:
- Around line 56-74: The test fixtures and assertions for PII redaction are
using an outdated configuration contract that does not match the canonical
definition in crates/core/src/plugins/nemo_anonymizer/component.rs. Update the
pii_redaction_component_config function at lines 56-74 and all affected test
assertions at lines 194-224, 396-416, 1256-1269, and 1390-1402 to align with the
actual core contract: replace "mode": "builtin" with the correct mode schema
(likely "local"), remove the "builtin" configuration section and replace it with
the structure expected by the nemo_anonymizer component, replace any hardcoded
kind literals with the PII_REDACTION_PLUGIN_KIND constant imported from the core
component, and update all schema validation assertions to reflect the corrected
configuration contract.

In `@crates/core/Cargo.toml`:
- Around line 72-73: Remove the unused `regex` and `sha2` dependencies from the
Cargo.toml file in crates/core. Both dependencies are not used in the codebase:
`regex` only appears in comments and `sha2` has no references anywhere in
crates/core/src/. Delete both dependency declarations entirely to eliminate
unused-dependency warnings and maintain a clean dependency tree.

In `@crates/core/src/plugin/dynamic/manifest.rs`:
- Around line 332-343: Replace the Vec-based duplicate detection in the
reject_duplicate_capabilities function with a HashSet to improve performance
from O(n²) to O(n). Change the seen variable from a Vec to a HashSet, and update
the contains check to use the insert method (which returns false if the element
was already present), allowing you to detect duplicates while building the set
in a single pass.

In `@crates/core/src/plugin/dynamic/registry.rs`:
- Around line 59-60: The stamp_creation_metadata function sets both created_at
and updated_at to the same value, but the immediately following touch_metadata
call overwrites updated_at, creating an unintended microsecond gap for newly
added records. Either remove the touch_metadata call since
stamp_creation_metadata already sets updated_at appropriately, or if this
behavior is intentional (treating add as a distinct mutation), add a clear
comment explaining why both calls are necessary.

In `@crates/core/src/plugins/nemo_anonymizer/component.rs`:
- Around line 634-663: The validate_local_mode_requirements function has two
issues: first, the early return on line 640 when config.local.is_some() prevents
the deprecated builtin check from always being enforced; second, the final
push_policy_diag call on line 656 unconditionally rejects missing local settings
even for non-local modes. Restructure the function to always check for and
reject the deprecated builtin configuration regardless of whether local is
present, then gate the final diagnostic about missing local settings to only
emit when the configured mode is explicitly 'local' (check the policy or config
for the actual mode value to conditionally emit this final diagnostic).
- Around line 756-821: The validation logic contains duplicate diagnostic checks
that fire for the same invalid field states. When local.strategy equals
"substitute", both the first if block (checking strategy == "substitute") and
the second if block (checking strategy != "hash") execute and report identical
errors for local.algorithm and local.digest_length. Remove the duplicate checks
for local.algorithm.is_some() and local.digest_length.is_some() from within the
if local.strategy == "substitute" block, keeping only the check for
local.format_template and local.instructions in that block, since the second if
local.strategy != "hash" block will already catch and report the algorithm and
digest_length validation errors comprehensively.

In `@crates/python/tests/coverage/coverage_tests.rs`:
- Around line 48-55: The fake_guardrails_module_prelude function adds python_dir
to sys.path[0], but the with_isolated_nemo_relay_modules isolation helper only
restores sys.modules, not sys.path. This allows later tests to import from the
source tree instead of the test environment. Modify the
with_isolated_nemo_relay_modules function to also save the original sys.path
before executing the test code and restore it afterward, ensuring sys.path
modifications do not leak between tests.

In `@docs/nemo-anonymizer-plugin/about.mdx`:
- Line 49: On line 49 of the about.mdx file, change the word "builtin" to
"built-in" in the phrase "the previous builtin deterministic lane is no longer
part of this plugin" to maintain consistent technical documentation wording
conventions.

In `@python/nemo_relay/_nemo_anonymizer_local.py`:
- Around line 323-334: The _annotated_message_text function currently joins all
text blocks with newlines, which loses the original block boundaries and causes
content to be truncated or shifted when sanitized text changes newline counts.
Instead of returning a single concatenated string, refactor this function to
return the extracted text as a per-block list structure that preserves each
block's original text separately. Update the callers (the OpenAI Responses and
Anthropic overlay helpers at lines 395-428) to overlay sanitized content by
block index instead of splitting the concatenated string on newlines, ensuring
block boundaries are maintained throughout the sanitization and overlay process.
- Around line 302-308: The function _apply_annotated_request_payload extracts
and restores several fields from the payload back to the request object
(messages, model, params, tools, tool_choice), but it does not restore the
sanitized extra field. Since extra fields are included in
_annotated_request_payload and can contain PII that gets sanitized, you must add
a line to assign the sanitized extra field from the payload back to the request
object using the same pattern as the other field assignments (request.extra =
payload.get("extra")).
- Around line 497-502: The sanitize_request function lacks error handling for
codec decoding failures, allowing decode errors to escape instead of falling
back to raw request sanitization like the response path does. Wrap the
codec.decode(request) call in a try-except block in the sanitize_request
function, and when decoding fails, implement a fallback path that sanitizes the
raw request payload directly using runner.sanitize_json without attempting to
annotate or re-encode it, ensuring PII is sanitized even when the codec fails.
- Around line 8-11: The CSV file handling at line 198 uses NamedTemporaryFile
with delete=True and passes the file path to the anonymizer while the file
handle remains open, causing Windows permission errors. Replace the
NamedTemporaryFile approach with TemporaryDirectory to create a temporary
directory, then write the CSV data to a file within that directory with explicit
UTF-8 encoding. Ensure the file is fully closed before passing its path to the
anonymizer runtime, and properly manage the TemporaryDirectory context to clean
up after the anonymizer completes its work.
🪄 Autofix (Beta)

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: 1830a9ff-248b-46f1-b26d-aea321b70a8b

📥 Commits

Reviewing files that changed from the base of the PR and between 2841a32 and 4c6eb28.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (36)
  • ATTRIBUTIONS-Rust.md
  • crates/adaptive/src/error.rs
  • crates/cli/src/plugins.rs
  • crates/cli/src/plugins/editor_model.rs
  • crates/cli/tests/coverage/plugins_tests.rs
  • crates/core/Cargo.toml
  • crates/core/src/api/llm.rs
  • crates/core/src/plugin.rs
  • crates/core/src/plugin/dynamic.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/src/plugins/mod.rs
  • crates/core/src/plugins/nemo_anonymizer/component.rs
  • crates/core/src/plugins/nemo_anonymizer/local.rs
  • crates/core/src/plugins/nemo_anonymizer/mod.rs
  • crates/core/src/plugins/nemo_guardrails/component.rs
  • crates/core/src/plugins/nemo_guardrails/local.rs
  • crates/core/src/stream.rs
  • crates/core/tests/integration/pipeline_tests.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
  • crates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rs
  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/ffi/src/error.rs
  • crates/python/src/lib.rs
  • crates/python/src/py_plugin.rs
  • crates/python/tests/coverage/coverage_tests.rs
  • crates/python/tests/coverage/py_plugin_coverage_tests.rs
  • docs/about-nemo-relay/concepts/plugins.mdx
  • docs/build-plugins/nemoguardrails.mdx
  • docs/index.yml
  • docs/nemo-anonymizer-plugin/about.mdx
  • docs/nemo-anonymizer-plugin/configuration.mdx
  • docs/nemo-guardrails-plugin/about.mdx
  • docs/nemo-guardrails-plugin/configuration.mdx
  • python/nemo_relay/_guardrails_local.py
  • python/nemo_relay/_nemo_anonymizer_local.py
💤 Files with no reviewable changes (1)
  • docs/build-plugins/nemoguardrails.mdx
📜 Review details
🧰 Additional context used
📓 Path-based instructions (48)
{docs/**,README.md,CONTRIBUTING.md}

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

{docs/**,README.md,CONTRIBUTING.md}: For docs-only changes, run targeted checks only if commands, package names, or examples changed. Use just docs for docs-site builds and just docs-linkcheck when links changed
Run docs site build with just docs

Files:

  • docs/about-nemo-relay/concepts/plugins.mdx
  • docs/index.yml
  • docs/nemo-anonymizer-plugin/about.mdx
  • docs/nemo-guardrails-plugin/about.mdx
  • docs/nemo-anonymizer-plugin/configuration.mdx
  • docs/nemo-guardrails-plugin/configuration.mdx
{docs/**,README.md,CONTRIBUTING.md,**/*.md}

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

Run docs link validation with just docs-linkcheck when links change

Files:

  • docs/about-nemo-relay/concepts/plugins.mdx
  • docs/index.yml
  • docs/nemo-anonymizer-plugin/about.mdx
  • docs/nemo-guardrails-plugin/about.mdx
  • docs/nemo-anonymizer-plugin/configuration.mdx
  • ATTRIBUTIONS-Rust.md
  • docs/nemo-guardrails-plugin/configuration.mdx
{docs/**,README.md}

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

Verify README and docs entry points still match current package names and paths for large or public-facing changes

Files:

  • docs/about-nemo-relay/concepts/plugins.mdx
  • docs/index.yml
  • docs/nemo-anonymizer-plugin/about.mdx
  • docs/nemo-guardrails-plugin/about.mdx
  • docs/nemo-anonymizer-plugin/configuration.mdx
  • docs/nemo-guardrails-plugin/configuration.mdx
{docs/**,examples/**,README.md}

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

Verify examples still run with documented commands for large or public-facing changes

Files:

  • docs/about-nemo-relay/concepts/plugins.mdx
  • docs/index.yml
  • docs/nemo-anonymizer-plugin/about.mdx
  • docs/nemo-guardrails-plugin/about.mdx
  • docs/nemo-anonymizer-plugin/configuration.mdx
  • docs/nemo-guardrails-plugin/configuration.mdx
{docs/**,README.md,**/Cargo.toml,**/package.json,**/*.md}

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

Ensure renamed public surfaces are reflected consistently in manifests and docs for large or public-facing changes

Files:

  • docs/about-nemo-relay/concepts/plugins.mdx
  • docs/index.yml
  • docs/nemo-anonymizer-plugin/about.mdx
  • crates/core/Cargo.toml
  • docs/nemo-guardrails-plugin/about.mdx
  • docs/nemo-anonymizer-plugin/configuration.mdx
  • ATTRIBUTIONS-Rust.md
  • docs/nemo-guardrails-plugin/configuration.mdx
**/*.{md,mdx,py,sh,yaml,yml,toml,json}

📄 CodeRabbit inference engine (.agents/skills/contribute-docs/SKILL.md)

Keep package names, repo references, and build commands current

Files:

  • docs/about-nemo-relay/concepts/plugins.mdx
  • docs/index.yml
  • docs/nemo-anonymizer-plugin/about.mdx
  • crates/core/Cargo.toml
  • docs/nemo-guardrails-plugin/about.mdx
  • docs/nemo-anonymizer-plugin/configuration.mdx
  • python/nemo_relay/_guardrails_local.py
  • python/nemo_relay/_nemo_anonymizer_local.py
  • ATTRIBUTIONS-Rust.md
  • docs/nemo-guardrails-plugin/configuration.mdx
**/*.mdx

📄 CodeRabbit inference engine (.agents/skills/contribute-docs/SKILL.md)

In MDX files, top-of-file comments must use JSX comment delimiters: {/* to open and */} to close. Do not use HTML comments for MDX SPDX headers.

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

Files:

  • docs/about-nemo-relay/concepts/plugins.mdx
  • docs/nemo-anonymizer-plugin/about.mdx
  • docs/nemo-guardrails-plugin/about.mdx
  • docs/nemo-anonymizer-plugin/configuration.mdx
  • docs/nemo-guardrails-plugin/configuration.mdx
**/*.{html,md,mdx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Include SPDX license header in HTML and Markdown files using HTML comment syntax

Files:

  • docs/about-nemo-relay/concepts/plugins.mdx
  • docs/nemo-anonymizer-plugin/about.mdx
  • docs/nemo-guardrails-plugin/about.mdx
  • docs/nemo-anonymizer-plugin/configuration.mdx
  • ATTRIBUTIONS-Rust.md
  • docs/nemo-guardrails-plugin/configuration.mdx
docs/**/*.{md,mdx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Update embedded documentation snippets, patch docs, and binding-support notes if examples or supported bindings changed

Files:

  • docs/about-nemo-relay/concepts/plugins.mdx
  • docs/nemo-anonymizer-plugin/about.mdx
  • docs/nemo-guardrails-plugin/about.mdx
  • docs/nemo-anonymizer-plugin/configuration.mdx
  • docs/nemo-guardrails-plugin/configuration.mdx
docs/**

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run just docs or ./scripts/build-docs.sh html to regenerate ignored Fern API reference pages before validation for documentation site changes

Files:

  • docs/about-nemo-relay/concepts/plugins.mdx
  • docs/index.yml
  • docs/nemo-anonymizer-plugin/about.mdx
  • docs/nemo-guardrails-plugin/about.mdx
  • docs/nemo-anonymizer-plugin/configuration.mdx
  • docs/nemo-guardrails-plugin/configuration.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/about-nemo-relay/concepts/plugins.mdx
  • docs/index.yml
  • docs/nemo-anonymizer-plugin/about.mdx
  • docs/nemo-guardrails-plugin/about.mdx
  • docs/nemo-anonymizer-plugin/configuration.mdx
  • docs/nemo-guardrails-plugin/configuration.mdx
**

⚙️ CodeRabbit configuration file

**:

AGENTS.md

This file provides guidance to agents, including Claude Code and OpenAI Codex, when working in this repository.

Project Overview

NeMo Relay is a multi-language agent runtime framework for execution scopes, lifecycle events, middleware, plugins, and observability around tool and LLM calls. The core runtime is Rust. Primary supported bindings are Rust, Python, and Node.js. Go, WebAssembly, and the raw C FFI are experimental and source-first.

The shared runtime model is:

  1. Scope stacks decide where work belongs and which scope-local behavior is visible.
  2. Middleware registries decide what guardrails and intercepts run around managed calls.
  3. Plugins install reusable runtime behavior from configuration.
  4. Events record runtime behavior in ATOF form.
  5. Subscribers and exporters consume events in-process or export them to ATIF, OpenTelemetry, OpenInference, or other backends.

Repository Structure

The repository layout separates the Rust runtime, language bindings, documentation,
integration patches, and agent-facing skills.

crates/
  core/       # Rust core runtime crate, published as nemo-relay
  adaptive/   # Adaptive runtime primitives and plugin components
  python/     # PyO3 native extension for the Python package
  ffi/        # Raw C ABI layer used by downstream bindings such as Go
  node/       # NAPI Node.js binding and JavaScript/TypeScript entry points
  wasm/       # wasm-bindgen WebAssembly binding and JS wrappers
python/
  nemo_relay/  # Python wrapper package: scopes, tools, LLM, middleware, typed helpers, plugins, adaptive helpers
  tests/      # Python tests
go/
  nemo_relay/  # Experimental Go CGo binding and tests
fern/         # Fern documentation site
scripts/      # Stable wrappers and helper scripts; build/test/docs entry points live in justfile
third_party/  # P...

Files:

  • docs/about-nemo-relay/concepts/plugins.mdx
  • crates/core/src/plugins/mod.rs
  • docs/index.yml
  • crates/cli/src/plugins.rs
  • crates/ffi/src/error.rs
  • docs/nemo-anonymizer-plugin/about.mdx
  • crates/core/src/plugin.rs
  • crates/core/Cargo.toml
  • crates/core/tests/integration/pipeline_tests.rs
  • crates/adaptive/src/error.rs
  • crates/python/tests/coverage/py_plugin_coverage_tests.rs
  • crates/core/src/plugins/nemo_anonymizer/local.rs
  • docs/nemo-guardrails-plugin/about.mdx
  • docs/nemo-anonymizer-plugin/configuration.mdx
  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/core/src/plugins/nemo_guardrails/component.rs
  • crates/core/src/stream.rs
  • crates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rs
  • crates/python/src/py_plugin.rs
  • crates/core/src/plugins/nemo_guardrails/local.rs
  • crates/core/src/plugins/nemo_anonymizer/mod.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/python/tests/coverage/coverage_tests.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
  • crates/python/src/lib.rs
  • crates/core/src/api/llm.rs
  • python/nemo_relay/_guardrails_local.py
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/cli/tests/coverage/plugins_tests.rs
  • python/nemo_relay/_nemo_anonymizer_local.py
  • crates/core/src/plugins/nemo_anonymizer/component.rs
  • ATTRIBUTIONS-Rust.md
  • docs/nemo-guardrails-plugin/configuration.mdx
  • crates/cli/src/plugins/editor_model.rs
  • crates/core/src/plugin/dynamic.rs
**/*.rs

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

Use snake_case naming convention for Rust identifiers (e.g., nemo_relay_tool_call)

**/*.rs: Any Rust change must run just test-rust
Any Rust change must run cargo fmt --all
Any Rust change must run cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all for all FFI work since it is Rust work
Run just test-rust to validate FFI changes
Run cargo clippy --workspace --all-targets -- -D warnings to enforce strict linting on FFI work

When Rust files changed as part of Go work, also run cargo fmt --all, just test-rust, and cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all when Rust files are changed as part of Node work
Run cargo clippy --workspace --all-targets -- -D warnings when Rust files are changed as part of Node work
Run just test-rust when Rust files are changed as part of Node work

**/*.rs: Run cargo fmt --all to format all Rust code
Run cargo clippy --workspace --all-targets -- -D warnings to enforce all clippy lints as errors

**/*.rs: Run cargo fmt --all when Rust files changed as part of WebAssembly work
Run cargo clippy --workspace --all-targets -- -D warnings when Rust files changed as part of WebAssembly work

**/*.rs: If any Rust code changed, always run just test-rust
If any Rust code changed, also run cargo fmt --all
If any Rust code changed, also run cargo clippy --workspace --all-targets -- -D warnings
Run Rust formatting with cargo fmt --all
Run Rust linting with cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Use cargo fmt for Rust code formatting
Run cargo clippy -- -D warnings to lint Rust code and treat all warnings as errors
Use Rust snake_case naming convention for Rust identifiers
Include SPDX license header in all Rust source files using double-slash comment syntax
Validate Rust code with uv run pre-commit run --all-files to enforce cargo fmt formatting check, cargo clippy lints, and cargo deny aud...

Files:

  • crates/core/src/plugins/mod.rs
  • crates/cli/src/plugins.rs
  • crates/ffi/src/error.rs
  • crates/core/src/plugin.rs
  • crates/core/tests/integration/pipeline_tests.rs
  • crates/adaptive/src/error.rs
  • crates/python/tests/coverage/py_plugin_coverage_tests.rs
  • crates/core/src/plugins/nemo_anonymizer/local.rs
  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/core/src/plugins/nemo_guardrails/component.rs
  • crates/core/src/stream.rs
  • crates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rs
  • crates/python/src/py_plugin.rs
  • crates/core/src/plugins/nemo_guardrails/local.rs
  • crates/core/src/plugins/nemo_anonymizer/mod.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/python/tests/coverage/coverage_tests.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
  • crates/python/src/lib.rs
  • crates/core/src/api/llm.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/cli/tests/coverage/plugins_tests.rs
  • crates/core/src/plugins/nemo_anonymizer/component.rs
  • crates/cli/src/plugins/editor_model.rs
  • crates/core/src/plugin/dynamic.rs
**/{Cargo.toml,**/*.rs}

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

Maintain consistency between Rust package names in Cargo.toml and their actual usage across the codebase

Files:

  • crates/core/src/plugins/mod.rs
  • crates/cli/src/plugins.rs
  • crates/ffi/src/error.rs
  • crates/core/src/plugin.rs
  • crates/core/Cargo.toml
  • crates/core/tests/integration/pipeline_tests.rs
  • crates/adaptive/src/error.rs
  • crates/python/tests/coverage/py_plugin_coverage_tests.rs
  • crates/core/src/plugins/nemo_anonymizer/local.rs
  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/core/src/plugins/nemo_guardrails/component.rs
  • crates/core/src/stream.rs
  • crates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rs
  • crates/python/src/py_plugin.rs
  • crates/core/src/plugins/nemo_guardrails/local.rs
  • crates/core/src/plugins/nemo_anonymizer/mod.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/python/tests/coverage/coverage_tests.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
  • crates/python/src/lib.rs
  • crates/core/src/api/llm.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/cli/tests/coverage/plugins_tests.rs
  • crates/core/src/plugins/nemo_anonymizer/component.rs
  • crates/cli/src/plugins/editor_model.rs
  • crates/core/src/plugin/dynamic.rs
**/*.{h,hpp,c,cpp,rs}

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

Ensure FFI header and library naming follows consistent conventions across platform-specific builds

Files:

  • crates/core/src/plugins/mod.rs
  • crates/cli/src/plugins.rs
  • crates/ffi/src/error.rs
  • crates/core/src/plugin.rs
  • crates/core/tests/integration/pipeline_tests.rs
  • crates/adaptive/src/error.rs
  • crates/python/tests/coverage/py_plugin_coverage_tests.rs
  • crates/core/src/plugins/nemo_anonymizer/local.rs
  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/core/src/plugins/nemo_guardrails/component.rs
  • crates/core/src/stream.rs
  • crates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rs
  • crates/python/src/py_plugin.rs
  • crates/core/src/plugins/nemo_guardrails/local.rs
  • crates/core/src/plugins/nemo_anonymizer/mod.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/python/tests/coverage/coverage_tests.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
  • crates/python/src/lib.rs
  • crates/core/src/api/llm.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/cli/tests/coverage/plugins_tests.rs
  • crates/core/src/plugins/nemo_anonymizer/component.rs
  • crates/cli/src/plugins/editor_model.rs
  • crates/core/src/plugin/dynamic.rs
{crates/core,crates/adaptive}/**/*

📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)

Changes to crates/core or crates/adaptive must run the full language matrix

Files:

  • crates/core/src/plugins/mod.rs
  • crates/core/src/plugin.rs
  • crates/core/Cargo.toml
  • crates/core/tests/integration/pipeline_tests.rs
  • crates/adaptive/src/error.rs
  • crates/core/src/plugins/nemo_anonymizer/local.rs
  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/core/src/plugins/nemo_guardrails/component.rs
  • crates/core/src/stream.rs
  • crates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rs
  • crates/core/src/plugins/nemo_guardrails/local.rs
  • crates/core/src/plugins/nemo_anonymizer/mod.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
  • crates/core/src/api/llm.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugins/nemo_anonymizer/component.rs
  • crates/core/src/plugin/dynamic.rs
**/*.{rs,toml}

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

Update Rust crate names and module prefixes during coordinated rename operations

Files:

  • crates/core/src/plugins/mod.rs
  • crates/cli/src/plugins.rs
  • crates/ffi/src/error.rs
  • crates/core/src/plugin.rs
  • crates/core/Cargo.toml
  • crates/core/tests/integration/pipeline_tests.rs
  • crates/adaptive/src/error.rs
  • crates/python/tests/coverage/py_plugin_coverage_tests.rs
  • crates/core/src/plugins/nemo_anonymizer/local.rs
  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/core/src/plugins/nemo_guardrails/component.rs
  • crates/core/src/stream.rs
  • crates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rs
  • crates/python/src/py_plugin.rs
  • crates/core/src/plugins/nemo_guardrails/local.rs
  • crates/core/src/plugins/nemo_anonymizer/mod.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/python/tests/coverage/coverage_tests.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
  • crates/python/src/lib.rs
  • crates/core/src/api/llm.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/cli/tests/coverage/plugins_tests.rs
  • crates/core/src/plugins/nemo_anonymizer/component.rs
  • crates/cli/src/plugins/editor_model.rs
  • crates/core/src/plugin/dynamic.rs
crates/core/**/*.rs

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

If the change touched crates/core or shared runtime semantics, also use validate-change for broader validation

crates/core/**/*.rs: Use Json = serde_json::Value in Rust-facing runtime APIs where the existing code expects JSON payloads.
Use Result<T> with FlowError in core runtime paths. Keep errors explicit and binding-appropriate at the wrapper layer.

Files:

  • crates/core/src/plugins/mod.rs
  • crates/core/src/plugin.rs
  • crates/core/tests/integration/pipeline_tests.rs
  • crates/core/src/plugins/nemo_anonymizer/local.rs
  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/core/src/plugins/nemo_guardrails/component.rs
  • crates/core/src/stream.rs
  • crates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rs
  • crates/core/src/plugins/nemo_guardrails/local.rs
  • crates/core/src/plugins/nemo_anonymizer/mod.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
  • crates/core/src/api/llm.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugins/nemo_anonymizer/component.rs
  • crates/core/src/plugin/dynamic.rs
crates/{core,adaptive}/**

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

If crates/core or crates/adaptive changed, run the full matrix across Rust, Python, Go, Node.js, and WebAssembly

Files:

  • crates/core/src/plugins/mod.rs
  • crates/core/src/plugin.rs
  • crates/core/Cargo.toml
  • crates/core/tests/integration/pipeline_tests.rs
  • crates/adaptive/src/error.rs
  • crates/core/src/plugins/nemo_anonymizer/local.rs
  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/core/src/plugins/nemo_guardrails/component.rs
  • crates/core/src/stream.rs
  • crates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rs
  • crates/core/src/plugins/nemo_guardrails/local.rs
  • crates/core/src/plugins/nemo_anonymizer/mod.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
  • crates/core/src/api/llm.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugins/nemo_anonymizer/component.rs
  • crates/core/src/plugin/dynamic.rs
**/*.{rs,py,js,ts,tsx,jsx,go,sh,toml,yaml,yml,md}

📄 CodeRabbit inference engine (AGENTS.md)

Keep SPDX headers on source, docs, scripts, and configuration files. The project is Apache-2.0.

Files:

  • crates/core/src/plugins/mod.rs
  • docs/index.yml
  • crates/cli/src/plugins.rs
  • crates/ffi/src/error.rs
  • crates/core/src/plugin.rs
  • crates/core/Cargo.toml
  • crates/core/tests/integration/pipeline_tests.rs
  • crates/adaptive/src/error.rs
  • crates/python/tests/coverage/py_plugin_coverage_tests.rs
  • crates/core/src/plugins/nemo_anonymizer/local.rs
  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/core/src/plugins/nemo_guardrails/component.rs
  • crates/core/src/stream.rs
  • crates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rs
  • crates/python/src/py_plugin.rs
  • crates/core/src/plugins/nemo_guardrails/local.rs
  • crates/core/src/plugins/nemo_anonymizer/mod.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/python/tests/coverage/coverage_tests.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
  • crates/python/src/lib.rs
  • crates/core/src/api/llm.rs
  • python/nemo_relay/_guardrails_local.py
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/cli/tests/coverage/plugins_tests.rs
  • python/nemo_relay/_nemo_anonymizer_local.py
  • crates/core/src/plugins/nemo_anonymizer/component.rs
  • ATTRIBUTIONS-Rust.md
  • crates/cli/src/plugins/editor_model.rs
  • crates/core/src/plugin/dynamic.rs
**/*.{rs,py,go,js,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Follow binding naming conventions: Rust and Python use snake_case, C FFI exports prefixed nemo_relay_, Go uses PascalCase for public APIs, Node.js uses camelCase.

Files:

  • crates/core/src/plugins/mod.rs
  • crates/cli/src/plugins.rs
  • crates/ffi/src/error.rs
  • crates/core/src/plugin.rs
  • crates/core/tests/integration/pipeline_tests.rs
  • crates/adaptive/src/error.rs
  • crates/python/tests/coverage/py_plugin_coverage_tests.rs
  • crates/core/src/plugins/nemo_anonymizer/local.rs
  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/core/src/plugins/nemo_guardrails/component.rs
  • crates/core/src/stream.rs
  • crates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rs
  • crates/python/src/py_plugin.rs
  • crates/core/src/plugins/nemo_guardrails/local.rs
  • crates/core/src/plugins/nemo_anonymizer/mod.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/python/tests/coverage/coverage_tests.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
  • crates/python/src/lib.rs
  • crates/core/src/api/llm.rs
  • python/nemo_relay/_guardrails_local.py
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/cli/tests/coverage/plugins_tests.rs
  • python/nemo_relay/_nemo_anonymizer_local.py
  • crates/core/src/plugins/nemo_anonymizer/component.rs
  • crates/cli/src/plugins/editor_model.rs
  • crates/core/src/plugin/dynamic.rs
crates/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

crates/**/*.rs: Keep async behavior on the existing tokio-based model. Bindings should preserve callback and future lifetimes rather than blocking or hiding async work unexpectedly.
Use Json = serde_json::Value in Rust-facing runtime APIs for JSON payload handling.

Files:

  • crates/core/src/plugins/mod.rs
  • crates/cli/src/plugins.rs
  • crates/ffi/src/error.rs
  • crates/core/src/plugin.rs
  • crates/core/tests/integration/pipeline_tests.rs
  • crates/adaptive/src/error.rs
  • crates/python/tests/coverage/py_plugin_coverage_tests.rs
  • crates/core/src/plugins/nemo_anonymizer/local.rs
  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/core/src/plugins/nemo_guardrails/component.rs
  • crates/core/src/stream.rs
  • crates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rs
  • crates/python/src/py_plugin.rs
  • crates/core/src/plugins/nemo_guardrails/local.rs
  • crates/core/src/plugins/nemo_anonymizer/mod.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/python/tests/coverage/coverage_tests.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
  • crates/python/src/lib.rs
  • crates/core/src/api/llm.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/cli/tests/coverage/plugins_tests.rs
  • crates/core/src/plugins/nemo_anonymizer/component.rs
  • crates/cli/src/plugins/editor_model.rs
  • crates/core/src/plugin/dynamic.rs
crates/{core,adaptive}/**/*.rs

⚙️ CodeRabbit configuration file

crates/{core,adaptive}/**/*.rs: Review the Rust runtime for async correctness, scope isolation, middleware ordering, and event lifecycle regressions.
Pay close attention to task-local/thread-local scope propagation, callback lifetimes, stream finalization, and root_uuid isolation.
Public API changes should preserve existing behavior unless tests and docs show the intended migration path.

Files:

  • crates/core/src/plugins/mod.rs
  • crates/core/src/plugin.rs
  • crates/core/tests/integration/pipeline_tests.rs
  • crates/adaptive/src/error.rs
  • crates/core/src/plugins/nemo_anonymizer/local.rs
  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/core/src/plugins/nemo_guardrails/component.rs
  • crates/core/src/stream.rs
  • crates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rs
  • crates/core/src/plugins/nemo_guardrails/local.rs
  • crates/core/src/plugins/nemo_anonymizer/mod.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
  • crates/core/src/api/llm.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugins/nemo_anonymizer/component.rs
  • crates/core/src/plugin/dynamic.rs
**/*.{py,txt,toml,cfg,yaml,yml}

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

Update Python package names and top-level module imports during coordinated rename operations

Files:

  • docs/index.yml
  • crates/core/Cargo.toml
  • python/nemo_relay/_guardrails_local.py
  • python/nemo_relay/_nemo_anonymizer_local.py
**/{docs,examples,**/*.md,*.patch,*.diff,.github,*.sh,*.yaml,*.yml}

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

Update documentation, examples, CI configuration, and patch artifacts when performing rename operations

Files:

  • docs/index.yml
  • ATTRIBUTIONS-Rust.md
crates/ffi/**

📄 CodeRabbit inference engine (.agents/skills/test-ffi-surface/SKILL.md)

Rebuild the FFI crate in release mode so the shared library and header stay in sync when making changes to crates/ffi

Files:

  • crates/ffi/src/error.rs
crates/ffi/**/*.rs

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

If the change touched crates/ffi, also use test-ffi-surface for validation

Files:

  • crates/ffi/src/error.rs
crates/{python,ffi,node,wasm}/**/*

⚙️ CodeRabbit configuration file

crates/{python,ffi,node,wasm}/**/*: Treat binding changes as public API changes. Check for parity with the other language bindings, FFI ownership/lifetime safety,
callback error propagation, stable type conversion, and consistent async/stream semantics.
Flag changes that update one binding without corresponding tests or documentation for the same surface elsewhere.

Files:

  • crates/ffi/src/error.rs
  • crates/python/tests/coverage/py_plugin_coverage_tests.rs
  • crates/python/src/py_plugin.rs
  • crates/python/tests/coverage/coverage_tests.rs
  • crates/python/src/lib.rs
**/Cargo.toml

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

Update WebAssembly crate names and generated package names during coordinated rename operations

Confirm or infer the target release version from upstream/main:Cargo.toml. Derive the release branch as release/<major>.<minor>.

**/Cargo.toml: Maintain Cargo.toml [workspace.package].version as the source of truth for the Rust workspace and Python build versioning
Keep Cargo.toml [workspace.dependencies] self-references aligned with the workspace version when the workspace version changes
After updating workspace package entries, run cargo check --workspace to refresh Cargo.lock

Files:

  • crates/core/Cargo.toml
**/*.toml

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Include SPDX license header in TOML configuration files using hash comment syntax

Files:

  • crates/core/Cargo.toml
{crates/adaptive/**/*.rs,**/*test*.{rs,py,go,ts,js},**/*adaptive*test*.{rs,py,go,ts,js},docs/plugins/adaptive/**}

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

Maintain documented and tested validation and report behavior for adaptive surfaces

Files:

  • crates/core/tests/integration/pipeline_tests.rs
  • crates/adaptive/src/error.rs
  • crates/python/tests/coverage/py_plugin_coverage_tests.rs
  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rs
  • crates/python/tests/coverage/coverage_tests.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
  • crates/cli/tests/coverage/plugins_tests.rs
{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:

  • crates/core/tests/integration/pipeline_tests.rs
  • crates/python/tests/coverage/py_plugin_coverage_tests.rs
  • crates/core/tests/unit/plugins/nemo_guardrails/component_tests.rs
  • crates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rs
  • crates/python/tests/coverage/coverage_tests.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
  • crates/cli/tests/coverage/plugins_tests.rs
{crates/adaptive/**,python/nemo_relay/adaptive.py,python/nemo_relay/plugin.py,go/nemo_relay/adaptive/**,go/nemo_relay/!(adaptive)/**,**/node/**,**/wasm/**}

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

Keep adaptive surface in sync across crates/adaptive, shared plugin behavior in core and bindings, Python adaptive/plugin wrappers in python/nemo_relay/adaptive.py and python/nemo_relay/plugin.py, Go adaptive helpers under go/nemo_relay/adaptive plus shared plugin helpers in go/nemo_relay, and Node/WebAssembly adaptive helpers and plugin wrappers

Files:

  • crates/adaptive/src/error.rs
{crates/adaptive/**,python/nemo_relay/plugin.py,go/nemo_relay/**,**/node/**,**/wasm/**}

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

{crates/adaptive/**,python/nemo_relay/plugin.py,go/nemo_relay/**,**/node/**,**/wasm/**}: Maintain consistent plugin lifecycle across all language bindings (Python, Go, Node/WebAssembly, and Rust)
Keep plugin context surfaces aligned across all language implementations

Files:

  • crates/adaptive/src/error.rs
crates/python/**/*.rs

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

If the native Rust bridge changed, add the Rust crate tests for nemo-relay-python

Files:

  • crates/python/tests/coverage/py_plugin_coverage_tests.rs
  • crates/python/src/py_plugin.rs
  • crates/python/tests/coverage/coverage_tests.rs
  • crates/python/src/lib.rs
crates/core/src/api/**/*.rs

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

Implement behavior first in Rust core API modules: crates/core/src/api/ and related core modules such as crates/core/src/api/runtime/, crates/core/src/codec/, or crates/core/src/json.rs

Files:

  • crates/core/src/api/llm.rs
crates/core/src/api/{tool,llm}.rs

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

Wire the new middleware chain into the execute path in crates/core/src/api/tool.rs or crates/core/src/api/llm.rs at the appropriate pipeline stage

Files:

  • crates/core/src/api/llm.rs
{crates/python/src/py_api/**/*.rs,python/nemo_relay/**/*.py,python/nemo_relay/**/*.pyi}

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

Update Python native binding in crates/python/src/py_api/mod.rs with Python wrapper docstring in python/nemo_relay/<module>.py and type stubs in python/nemo_relay/*.pyi modules

Files:

  • python/nemo_relay/_guardrails_local.py
  • python/nemo_relay/_nemo_anonymizer_local.py
python/nemo_relay/**/*.py

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

Use snake_case naming convention for Python identifiers (e.g., nemo_relay.tools.call)

Format changed Python wrapper and test files with uv run ruff format python

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

Files:

  • python/nemo_relay/_guardrails_local.py
  • python/nemo_relay/_nemo_anonymizer_local.py
{pyproject.toml,**/*.py}

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

Maintain consistency between Python package names in pyproject.toml and import paths used throughout the codebase

Files:

  • python/nemo_relay/_guardrails_local.py
  • python/nemo_relay/_nemo_anonymizer_local.py
**/*.py

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

**/*.py: Run Python formatting with uv run ruff format python
Run Python testing with uv run pytest -k "<pattern>"

**/*.py: Use Ruff with rule sets E, F, W, I for Python linting
Use Ruff formatter with line length 120 and double quotes for Python code formatting
Run ty for Python type checking
Use Python snake_case naming convention for Python identifiers
Include SPDX license header in all Python source files using hash comment syntax
Validate Python code with uv run pre-commit run --all-files to enforce Ruff linting and formatting, and ty type checking

Files:

  • python/nemo_relay/_guardrails_local.py
  • python/nemo_relay/_nemo_anonymizer_local.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/_guardrails_local.py
  • python/nemo_relay/_nemo_anonymizer_local.py
**/*.{md,rst,html,txt}

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

**/*.{md,rst,html,txt}: Always spell NVIDIA in all caps. Do not use Nvidia, nvidia, nVidia, nVIDIA, or NV.
Use an NVIDIA before a noun because the name starts with an 'en' sound.
Do not add a registered trademark symbol after NVIDIA when referring to the company.
Use trademark symbols with product names only when the document type or legal guidance requires them.
Verify official capitalization, spacing, and hyphenation for product names.
Precede NVIDIA product names with NVIDIA on first mention when it is natural and accurate.
Do not rewrite product names for grammar or title-case rules.
Preserve third-party product names according to the owner's spelling.
Include the company name and full model qualifier on first use when it helps identify the model.
Preserve the official capitalization and punctuation of model names.
Use shorter family names only after the full name is established.
Spell out a term on first use and put the acronym in parentheses unless the acronym is widely understood by the intended audience.
Use the acronym on later mentions after it has been defined.
For long documents, reintroduce the full term if readers might lose context.
Form plurals of acronyms with s, not an apostrophe, such as GPUs.
In headings, common acronyms can remain abbreviated. Spell out the term in the first or second sentence of the body.
Common terms such as CPU, GPU, PC, API, and UI usually do not need to be spelled out for developer audiences.

Files:

  • ATTRIBUTIONS-Rust.md
**/*.{md,rst,html}

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

Link the first mention of a product name when the destination helps the reader.

Files:

  • ATTRIBUTIONS-Rust.md
**/*.md

📄 CodeRabbit inference engine (.agents/skills/contribute-integration/SKILL.md)

Documentation must be updated if activation or usage changed

**/*.md: Use title case consistently in technical documentation headings
Avoid quotation marks, ampersands, and exclamation marks in headings
Keep product, event, research, and whitepaper names in their official title case
Use title case for table headers
Do not force social-media sentence case into technical docs
Format code elements, commands, parameters, package names, and expressions in monospace
Format directories, file names, and paths in monospace using backticks
Use angle brackets inside monospace for variables inside paths, such as /home/<username>/.login
Format error messages and strings in quotation marks, keeping literal code strings in code formatting when clearer
Format UI buttons, menus, fields, and labels in bold
Use angle brackets between UI labels for menu paths, such as File > Save As
Use italics for new terms on first use, sparingly and only when introducing the term
Use italics for publication titles
Format keyboard shortcuts in plain text, such as Press Ctrl+Alt+Delete
Use owner/repo link text for GitHub repositories, preferring [NVIDIA/NeMo](link) over prose references like 'the GitHub repo'
Introduce every code block with a complete sentence
Do not make a code block complete the grammar of the previous sentence
Do not continue a sentence after a code block
Use syntax highlighting when the format supports it for code blocks
Avoid the word 'snippet' unless the surrounding docs already use it as a term of art
Keep inline method, function, and class references consistent with nearby docs, omitting empty parentheses for prose readability when no call is shown
Use descriptive anchor text that matches the destination title when possible for links
Avoid raw URLs in running text
Avoid generic anchor text such as 'here,' 'this page,' and 'read more'
Include acronyms in link text when a linked term includes an acronym
Do not link long sentences or multiple sentences
Avoid links ...

Files:

  • ATTRIBUTIONS-Rust.md
**/*.{md,rst,txt}

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

Spell NVIDIA in all caps. Do not use Nvidia, nvidia, or NV.

Files:

  • ATTRIBUTIONS-Rust.md
**/*.{md,rst}

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

**/*.{md,rst}: Format commands, code elements, expressions, package names, file names, and paths as inline code.
Use descriptive link text. Avoid raw URLs and weak anchors such as "here" or "read more."
Use title case consistently for technical documentation headings.
Introduce code blocks, lists, tables, and images with complete sentences.
Write procedures as imperative steps. Keep steps parallel and split long procedures into smaller tasks.
Prefer active voice, present tense, short sentences, contractions, and plain English.
Use can for possibility and reserve may for permission.
Use after for temporal relationships instead of once.
Prefer refer to over see when the wording points readers to another resource.
Avoid culture-specific idioms, unnecessary Latinisms, jokes, and marketing exaggeration in technical docs.
Spell out months in body text, avoid ordinal dates, and use clear time zones.
Spell out whole numbers from zero through nine unless they are technical values, parameters, versions, or UI values.
Use numerals for 10 or greater and include commas in thousands.
Do not add trademark symbols to learning-oriented docs unless the source, platform, or legal guidance explicitly requires them.

Files:

  • ATTRIBUTIONS-Rust.md
ATTRIBUTIONS-Rust.md

📄 CodeRabbit inference engine (.agents/skills/update-project-version/SKILL.md)

Regenerate ATTRIBUTIONS-Rust.md with ./scripts/generate_attributions.sh rust if Cargo metadata changed and committed attribution files must stay fresh

Files:

  • ATTRIBUTIONS-Rust.md
🪛 ast-grep (0.43.0)
python/nemo_relay/_guardrails_local.py

[info] 138-145: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"tool_name": name,
"arguments": args,
},
sort_keys=True,
separators=(",", ":"),
)
Note: Security best practice.

(use-jsonify)


[info] 149-157: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"tool_name": name,
"arguments": args,
"result": result,
},
sort_keys=True,
separators=(",", ":"),
)
Note: Security best practice.

(use-jsonify)

python/nemo_relay/_nemo_anonymizer_local.py

[info] 359-359: use jsonify instead of json.dumps for JSON output
Context: json.dumps(sanitized_call["arguments"], separators=(",", ":"))
Note: Security best practice.

(use-jsonify)


[info] 375-375: use jsonify instead of json.dumps for JSON output
Context: json.dumps(sanitized_call["arguments"], separators=(",", ":"))
Note: Security best practice.

(use-jsonify)

🪛 LanguageTool
docs/nemo-anonymizer-plugin/about.mdx

[grammar] ~49-~49: Ensure spelling is correct
Context: ...ackend. In particular: - the previous builtin deterministic lane is no longer part of...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

docs/nemo-guardrails-plugin/configuration.mdx

[style] ~294-~294: To form a complete sentence, be sure to include a subject.
Context: ...g_yamlis required. -colang_contentcan only be used withconfig_yaml. - rem...

(MISSING_IT_THERE)

🪛 markdownlint-cli2 (0.22.1)
ATTRIBUTIONS-Rust.md

[warning] 3127-3127: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Above

(MD022, blanks-around-headings)


[warning] 3127-3127: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 7481-7481: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 7484-7484: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Above

(MD022, blanks-around-headings)


[warning] 7484-7484: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 7485-7485: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


[warning] 7485-7485: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 8111-8111: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Above

(MD022, blanks-around-headings)


[warning] 8111-8111: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 8768-8768: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Above

(MD022, blanks-around-headings)


[warning] 8768-8768: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 32177-32177: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 32180-32180: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Above

(MD022, blanks-around-headings)


[warning] 32180-32180: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 32181-32181: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


[warning] 32181-32181: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 39251-39251: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Above

(MD022, blanks-around-headings)


[warning] 39251-39251: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

🪛 Ruff (0.15.17)
python/nemo_relay/_guardrails_local.py

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

(TRY003)


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

(TRY003)


[warning] 92-92: Dynamically typed expressions (typing.Any) are disallowed in status

(ANN401)


[warning] 96-96: Dynamically typed expressions (typing.Any) are disallowed in annotated

(ANN401)


[warning] 101-101: Dynamically typed expressions (typing.Any) are disallowed in rails

(ANN401)


[warning] 102-102: Dynamically typed expressions (typing.Any) are disallowed in rail_type

(ANN401)


[warning] 103-103: Dynamically typed expressions (typing.Any) are disallowed in rail_status

(ANN401)


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

(TRY003)


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

(TRY003)


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

(TRY003)


[warning] 180-180: Dynamically typed expressions (typing.Any) are disallowed in result

(ANN401)


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

(TRY003)


[warning] 193-193: Dynamically typed expressions (typing.Any) are disallowed in rails

(ANN401)


[warning] 194-194: Dynamically typed expressions (typing.Any) are disallowed in rail_type

(ANN401)


[warning] 195-195: Dynamically typed expressions (typing.Any) are disallowed in rail_status

(ANN401)


[warning] 211-211: Dynamically typed expressions (typing.Any) are disallowed in rails

(ANN401)


[warning] 215-215: Dynamically typed expressions (typing.Any) are disallowed in rails

(ANN401)


[warning] 215-215: Dynamically typed expressions (typing.Any) are disallowed in _output_streaming_config

(ANN401)


[warning] 219-219: Dynamically typed expressions (typing.Any) are disallowed in rails

(ANN401)


[warning] 224-224: Too many return statements (10 > 6)

(PLR0911)


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

(PLR0912)


[warning] 280-280: Missing return type annotation for private function _queue_string_stream

(ANN202)


[warning] 280-280: Remove quotes from type annotation

Remove quotes

(UP037)


[warning] 290-290: Dynamically typed expressions (typing.Any) are disallowed in rails

(ANN401)


[warning] 292-292: Remove quotes from type annotation

Remove quotes

(UP037)


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

(TRY003)


[warning] 316-316: Dynamically typed expressions (typing.Any) are disallowed in rails_config_cls

(ANN401)


[warning] 316-316: Dynamically typed expressions (typing.Any) are disallowed in _build_guardrails_config

(ANN401)


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

(TRY003)


[warning] 333-333: Dynamically typed expressions (typing.Any) are disallowed in rails

(ANN401)


[warning] 334-334: Dynamically typed expressions (typing.Any) are disallowed in rail_type

(ANN401)


[warning] 335-335: Dynamically typed expressions (typing.Any) are disallowed in rail_status

(ANN401)


[warning] 356-356: Dynamically typed expressions (typing.Any) are disallowed in rails

(ANN401)


[warning] 357-357: Dynamically typed expressions (typing.Any) are disallowed in rail_type

(ANN401)


[warning] 358-358: Dynamically typed expressions (typing.Any) are disallowed in rail_status

(ANN401)


[warning] 385-385: Missing return type annotation for private function _make_llm_intercept

(ANN202)


[warning] 387-387: Dynamically typed expressions (typing.Any) are disallowed in rails

(ANN401)


[warning] 388-388: Dynamically typed expressions (typing.Any) are disallowed in rail_type

(ANN401)


[warning] 389-389: Dynamically typed expressions (typing.Any) are disallowed in rail_status

(ANN401)


[warning] 394-394: Missing return type annotation for private function intercept

(ANN202)


[warning] 424-424: Missing return type annotation for private function _make_llm_stream_intercept

(ANN202)


[warning] 426-426: Dynamically typed expressions (typing.Any) are disallowed in rails

(ANN401)


[warning] 427-427: Dynamically typed expressions (typing.Any) are disallowed in rail_type

(ANN401)


[warning] 428-428: Dynamically typed expressions (typing.Any) are disallowed in rail_status

(ANN401)


[warning] 434-434: Missing return type annotation for private function stream_intercept

(ANN202)


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

(TRY003)


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

(TRY003)


[warning] 467-467: Missing return type annotation for private function guarded_provider_stream

(ANN202)


[warning] 500-500: Missing return type annotation for private function _make_tool_intercept

(ANN202)


[warning] 502-502: Dynamically typed expressions (typing.Any) are disallowed in rails

(ANN401)


[warning] 503-503: Dynamically typed expressions (typing.Any) are disallowed in rail_type

(ANN401)


[warning] 504-504: Dynamically typed expressions (typing.Any) are disallowed in rail_status

(ANN401)


[warning] 508-508: Missing return type annotation for private function tool_intercept

(ANN202)


[warning] 536-536: Dynamically typed expressions (typing.Any) are disallowed in result

(ANN401)


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

(TRY003)

python/nemo_relay/_nemo_anonymizer_local.py

[warning] 13-13: Import from collections.abc instead: Callable

Import from collections.abc

(UP035)


[warning] 63-63: Remove quotes from type annotation

Remove quotes

(UP037)


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

(TRY003)


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

(TRY003)


[warning] 154-154: Dynamically typed expressions (typing.Any) are disallowed in _build_replace_method

(ANN401)


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

(TRY003)


[warning] 291-291: Dynamically typed expressions (typing.Any) are disallowed in request

(ANN401)


[warning] 302-302: Dynamically typed expressions (typing.Any) are disallowed in request

(ANN401)


[warning] 310-310: Dynamically typed expressions (typing.Any) are disallowed in response

(ANN401)


[warning] 519-519: Do not catch blind exception: Exception

(BLE001)

Comment thread crates/adaptive/src/error.rs Outdated
Comment thread crates/cli/tests/coverage/plugins_tests.rs Outdated
Comment thread crates/core/Cargo.toml Outdated
Comment thread crates/core/src/plugin/dynamic/manifest.rs
Comment thread crates/core/src/plugin/dynamic/registry.rs Outdated
Comment thread docs/nemo-anonymizer-plugin/about.mdx Outdated
Comment thread python/nemo_relay/_nemo_anonymizer_local.py Outdated
Comment thread python/nemo_relay/_nemo_anonymizer_local.py Outdated
Comment thread python/nemo_relay/_nemo_anonymizer_local.py Outdated
Comment thread python/nemo_relay/_nemo_anonymizer_local.py Outdated
@afourniernv afourniernv force-pushed the afournier/relay-337-plugin-control-plane-model branch from 4c6eb28 to f0bad61 Compare June 16, 2026 18:44
@github-actions github-actions Bot added size:XL PR is extra large and removed size:XXL PR is very large lang:python PR changes/introduces Python code labels Jun 16, 2026
@afourniernv afourniernv marked this pull request as draft June 16, 2026 19:01
@willkill07 willkill07 removed request for a team and lvojtku June 16, 2026 19:25
@afourniernv

afourniernv commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator Author

A little more implementation detail on what this foundation slice is actually establishing.

This PR is centered on one durable control-plane record and one authored manifest contract.

Record contract

The core type is DynamicPluginRecord, which is split into:

  • metadata
  • source
  • spec
  • status

The intended contract is:

  • metadata = stable identity and record-level bookkeeping
  • source = resolved manifest/artifact/environment references known to the control plane
  • spec = desired state owned by user-facing lifecycle operations
  • status = observed validation/runtime state owned by reconciliation/runtime updates

That separation is the main mechanism that keeps what the user wants separate from what Relay has observed.

spec currently carries:

  • present
  • enabled
  • config_ref

status currently carries:

  • decomposed validation state
  • runtime state
  • startup classification
  • attestation mode
  • last failure summary

Generation lives on metadata, and only desired-state mutations bump generation. Status updates do not. That is the reconciliation mechanism the later runtime and CLI work will build on.

Manifest contract

The authored manifest is relay-plugin.toml.

This PR establishes:

  • kind-specific manifest parsing
  • strict validation
  • manifest-to-record conversion

The current manifest model distinguishes:

  • plugin.kind = "rust_dynamic"
  • plugin.kind = "worker"

with kind-specific requirements for:

  • compatibility declarations
  • load contract fields
  • capability shape

The durable load contract is now lane-specific rather than one shared optional-field bag:

  • worker records carry runtime + entrypoint
  • native records carry library + symbol

That makes the worker and native registration targets deterministic in the control-plane model instead of relying on loosely related optional fields.

Examples of what is enforced now:

  • compat.relay is required for all plugins
  • compat.native_api is required for rust_dynamic
  • compat.worker_protocol is required for worker
  • worker manifests require runtime + entrypoint
  • native manifests require library + symbol
  • plugins are rejected if they claim invalid kind/load/compatibility combinations
  • plugins are rejected if defaults.enabled = true

So this PR is not just adding structs; it is making the authored contract executable.

Registry mechanisms

DynamicPluginRegistry is intentionally in-memory for now, but the lifecycle semantics are already explicit.

It currently provides:

  • add
  • add_manifest
  • get
  • list
  • enable
  • disable
  • remove
  • validation/runtime/last-error status updates

Important behaviors already in place:

  • add_manifest validates and converts authored manifests before registration
  • duplicate live plugin IDs fail with PluginError::Conflict
  • remove is tombstone-based, not destructive
  • tombstoned IDs can be revived while preserving lineage
  • list(false) hides tombstones
  • list(true) includes them
  • enable / disable / remove are desired-state mutations and bump generation
  • status updates stamp observed timestamps but do not bump generation

That gives later CLI/runtime work a stable lifecycle model instead of re-deciding semantics.

Boundary decisions in this PR

This slice intentionally stops at the control-plane boundary.

It does not yet implement:

  • CLI lifecycle commands
  • worker runtime protocol
  • Python worker supervision
  • native dynamic loading
  • host policy evaluation
  • persistence backend / cross-process coordination

Those are follow-on issues, but this PR is meant to establish the contracts they plug into.

Why this shape

The main reason for this split is to avoid backing into the plugin model through CLI or runtime implementation details.

The order here is:

  1. make the record model concrete
  2. make the authored manifest contract concrete
  3. make lifecycle mutation semantics concrete
  4. build CLI, policy, worker, and native runtime work on top of those contracts

That is the actual purpose of this foundation PR.

@afourniernv afourniernv marked this pull request as ready for review June 22, 2026 17:57
@afourniernv

Copy link
Copy Markdown
Collaborator Author

/ok to test dc158dc

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

Signed-off-by: Alex Fournier <afournier@nvidia.com>
@afourniernv afourniernv force-pushed the afournier/relay-337-plugin-control-plane-model branch from dc158dc to ad5e385 Compare June 22, 2026 18:20

@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: 3

🤖 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 `@crates/core/src/plugin/dynamic/manifest.rs`:
- Around line 281-285: The validation in this section checks if plugin.id is
non-empty after trimming, but the untrimmed value is retained and stored,
causing inputs with leading/trailing whitespace to pass validation but later
fail during lookup or load operations. Instead of only validating the trimmed
value, assign the trimmed result back to self.plugin.id to ensure the canonical
non-whitespace value is persisted. Apply the same trimming and assignment
pattern to all other validated fields mentioned in the comment: the required
compat strings and load fields at the additional line ranges provided.

In `@crates/core/src/plugin/dynamic/registry.rs`:
- Around line 176-239: The validate_record_shape function does not enforce the
required relay compatibility constraint that is mandated by the manifest
contract. Add a validation check in the validate_record_shape function to ensure
that record.compatibility.relay is present for all plugin kinds (both
RustDynamic and Worker variants). This check should be added after the initial
empty id validation to catch records that lack the relay compatibility before
they are stored, preventing inconsistent durable state across different
registration paths.

In `@crates/core/tests/unit/plugin_dynamic_tests.rs`:
- Around line 163-214: The existing tests validate that registry.add() rejects
records with invalid capability, load, and compatibility shapes, but they do not
test that the method rejects records missing the required compat.relay field.
Add a new test function following the same pattern as
registry_rejects_invalid_raw_record_shapes,
registry_rejects_invalid_raw_record_load_shapes, and
registry_rejects_invalid_raw_record_compatibility_shapes that creates a sample
record, sets the relay field within the compatibility struct to None, calls
registry.add(), and asserts that it fails with a PluginError::InvalidConfig
containing an appropriate message about the missing relay field to ensure this
required field contract is validated at the add() boundary.
🪄 Autofix (Beta)

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: e4a6ba9c-06aa-4f53-9fd4-ebf7e7b2c44b

📥 Commits

Reviewing files that changed from the base of the PR and between dc158dc and ad5e385.

📒 Files selected for processing (9)
  • crates/adaptive/src/error.rs
  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/core/src/plugin.rs
  • crates/core/src/plugin/dynamic.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
  • crates/ffi/src/error.rs
  • crates/ffi/tests/coverage/error_tests.rs
📜 Review details
🧰 Additional context used
📓 Path-based instructions (19)
**/*.rs

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

Use snake_case naming convention for Rust identifiers (e.g., nemo_relay_tool_call)

**/*.rs: Any Rust change must run just test-rust
Any Rust change must run cargo fmt --all
Any Rust change must run cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all for all FFI work since it is Rust work
Run just test-rust to validate FFI changes
Run cargo clippy --workspace --all-targets -- -D warnings to enforce strict linting on FFI work

When Rust files changed as part of Go work, also run cargo fmt --all, just test-rust, and cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all when Rust files are changed as part of Node work
Run cargo clippy --workspace --all-targets -- -D warnings when Rust files are changed as part of Node work
Run just test-rust when Rust files are changed as part of Node work

**/*.rs: Run cargo fmt --all to format all Rust code
Run cargo clippy --workspace --all-targets -- -D warnings to enforce all clippy lints as errors

**/*.rs: Run cargo fmt --all when Rust files changed as part of WebAssembly work
Run cargo clippy --workspace --all-targets -- -D warnings when Rust files changed as part of WebAssembly work

**/*.rs: If any Rust code changed, always run just test-rust
If any Rust code changed, also run cargo fmt --all
If any Rust code changed, also run cargo clippy --workspace --all-targets -- -D warnings
Run Rust formatting with cargo fmt --all
Run Rust linting with cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Use cargo fmt for Rust code formatting
Run cargo clippy -- -D warnings to lint Rust code and treat all warnings as errors
Use Rust snake_case naming convention for Rust identifiers
Include SPDX license header in all Rust source files using double-slash comment syntax
Validate Rust code with uv run pre-commit run --all-files to enforce cargo fmt formatting check, cargo clippy lints, and cargo deny aud...

Files:

  • crates/ffi/tests/coverage/error_tests.rs
  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/adaptive/src/error.rs
  • crates/ffi/src/error.rs
  • crates/core/src/plugin.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugin/dynamic.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
{crates/adaptive/**/*.rs,**/*test*.{rs,py,go,ts,js},**/*adaptive*test*.{rs,py,go,ts,js},docs/plugins/adaptive/**}

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

Maintain documented and tested validation and report behavior for adaptive surfaces

Files:

  • crates/ffi/tests/coverage/error_tests.rs
  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/adaptive/src/error.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
**/{Cargo.toml,**/*.rs}

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

Maintain consistency between Rust package names in Cargo.toml and their actual usage across the codebase

Files:

  • crates/ffi/tests/coverage/error_tests.rs
  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/adaptive/src/error.rs
  • crates/ffi/src/error.rs
  • crates/core/src/plugin.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugin/dynamic.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
**/*.{h,hpp,c,cpp,rs}

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

Ensure FFI header and library naming follows consistent conventions across platform-specific builds

Files:

  • crates/ffi/tests/coverage/error_tests.rs
  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/adaptive/src/error.rs
  • crates/ffi/src/error.rs
  • crates/core/src/plugin.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugin/dynamic.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
**/*.{rs,toml}

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

Update Rust crate names and module prefixes during coordinated rename operations

Files:

  • crates/ffi/tests/coverage/error_tests.rs
  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/adaptive/src/error.rs
  • crates/ffi/src/error.rs
  • crates/core/src/plugin.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugin/dynamic.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
crates/ffi/**

📄 CodeRabbit inference engine (.agents/skills/test-ffi-surface/SKILL.md)

Rebuild the FFI crate in release mode so the shared library and header stay in sync when making changes to crates/ffi

Files:

  • crates/ffi/tests/coverage/error_tests.rs
  • crates/ffi/src/error.rs
crates/ffi/**/*.rs

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

If the change touched crates/ffi, also use test-ffi-surface for validation

Files:

  • crates/ffi/tests/coverage/error_tests.rs
  • crates/ffi/src/error.rs
**/*.{rs,py,js,ts,tsx,jsx,go,sh,toml,yaml,yml,md}

📄 CodeRabbit inference engine (AGENTS.md)

Keep SPDX headers on source, docs, scripts, and configuration files. The project is Apache-2.0.

Files:

  • crates/ffi/tests/coverage/error_tests.rs
  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/adaptive/src/error.rs
  • crates/ffi/src/error.rs
  • crates/core/src/plugin.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugin/dynamic.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
**/*.{rs,py,go,js,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Follow binding naming conventions: Rust and Python use snake_case, C FFI exports prefixed nemo_relay_, Go uses PascalCase for public APIs, Node.js uses camelCase.

Files:

  • crates/ffi/tests/coverage/error_tests.rs
  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/adaptive/src/error.rs
  • crates/ffi/src/error.rs
  • crates/core/src/plugin.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugin/dynamic.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
crates/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

crates/**/*.rs: Keep async behavior on the existing tokio-based model. Bindings should preserve callback and future lifetimes rather than blocking or hiding async work unexpectedly.
Use Json = serde_json::Value in Rust-facing runtime APIs for JSON payload handling.

Files:

  • crates/ffi/tests/coverage/error_tests.rs
  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/adaptive/src/error.rs
  • crates/ffi/src/error.rs
  • crates/core/src/plugin.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugin/dynamic.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
crates/{python,ffi,node,wasm}/**/*

⚙️ CodeRabbit configuration file

crates/{python,ffi,node,wasm}/**/*: Treat binding changes as public API changes. Check for parity with the other language bindings, FFI ownership/lifetime safety,
callback error propagation, stable type conversion, and consistent async/stream semantics.
Flag changes that update one binding without corresponding tests or documentation for the same surface elsewhere.

Files:

  • crates/ffi/tests/coverage/error_tests.rs
  • crates/ffi/src/error.rs
{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:

  • crates/ffi/tests/coverage/error_tests.rs
  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
**

⚙️ CodeRabbit configuration file

**:

AGENTS.md

This file provides guidance to agents, including Claude Code and OpenAI Codex, when working in this repository.

Project Overview

NeMo Relay is a multi-language agent runtime framework for execution scopes, lifecycle events, middleware, plugins, and observability around tool and LLM calls. The core runtime is Rust. Primary supported bindings are Rust, Python, and Node.js. Go, WebAssembly, and the raw C FFI are experimental and source-first.

The shared runtime model is:

  1. Scope stacks decide where work belongs and which scope-local behavior is visible.
  2. Middleware registries decide what guardrails and intercepts run around managed calls.
  3. Plugins install reusable runtime behavior from configuration.
  4. Events record runtime behavior in ATOF form.
  5. Subscribers and exporters consume events in-process or export them to ATIF, OpenTelemetry, OpenInference, or other backends.

Repository Structure

The repository layout separates the Rust runtime, language bindings, documentation,
integration patches, and agent-facing skills.

crates/
  core/       # Rust core runtime crate, published as nemo-relay
  adaptive/   # Adaptive runtime primitives and plugin components
  python/     # PyO3 native extension for the Python package
  ffi/        # Raw C ABI layer used by downstream bindings such as Go
  node/       # NAPI Node.js binding and JavaScript/TypeScript entry points
  wasm/       # wasm-bindgen WebAssembly binding and JS wrappers
python/
  nemo_relay/  # Python wrapper package: scopes, tools, LLM, middleware, typed helpers, plugins, adaptive helpers
  tests/      # Python tests
go/
  nemo_relay/  # Experimental Go CGo binding and tests
fern/         # Fern documentation site
scripts/      # Stable wrappers and helper scripts; build/test/docs entry points live in justfile
third_party/  # P...

Files:

  • crates/ffi/tests/coverage/error_tests.rs
  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/adaptive/src/error.rs
  • crates/ffi/src/error.rs
  • crates/core/src/plugin.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugin/dynamic.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
{crates/adaptive/**,python/nemo_relay/adaptive.py,python/nemo_relay/plugin.py,go/nemo_relay/adaptive/**,go/nemo_relay/!(adaptive)/**,**/node/**,**/wasm/**}

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

Keep adaptive surface in sync across crates/adaptive, shared plugin behavior in core and bindings, Python adaptive/plugin wrappers in python/nemo_relay/adaptive.py and python/nemo_relay/plugin.py, Go adaptive helpers under go/nemo_relay/adaptive plus shared plugin helpers in go/nemo_relay, and Node/WebAssembly adaptive helpers and plugin wrappers

Files:

  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/adaptive/src/error.rs
{crates/adaptive/**,python/nemo_relay/plugin.py,go/nemo_relay/**,**/node/**,**/wasm/**}

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

{crates/adaptive/**,python/nemo_relay/plugin.py,go/nemo_relay/**,**/node/**,**/wasm/**}: Maintain consistent plugin lifecycle across all language bindings (Python, Go, Node/WebAssembly, and Rust)
Keep plugin context surfaces aligned across all language implementations

Files:

  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/adaptive/src/error.rs
{crates/core,crates/adaptive}/**/*

📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)

Changes to crates/core or crates/adaptive must run the full language matrix

Files:

  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/adaptive/src/error.rs
  • crates/core/src/plugin.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugin/dynamic.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
crates/{core,adaptive}/**

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

If crates/core or crates/adaptive changed, run the full matrix across Rust, Python, Go, Node.js, and WebAssembly

Files:

  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/adaptive/src/error.rs
  • crates/core/src/plugin.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugin/dynamic.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
crates/{core,adaptive}/**/*.rs

⚙️ CodeRabbit configuration file

crates/{core,adaptive}/**/*.rs: Review the Rust runtime for async correctness, scope isolation, middleware ordering, and event lifecycle regressions.
Pay close attention to task-local/thread-local scope propagation, callback lifetimes, stream finalization, and root_uuid isolation.
Public API changes should preserve existing behavior unless tests and docs show the intended migration path.

Files:

  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/adaptive/src/error.rs
  • crates/core/src/plugin.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugin/dynamic.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
crates/core/**/*.rs

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

If the change touched crates/core or shared runtime semantics, also use validate-change for broader validation

crates/core/**/*.rs: Use Json = serde_json::Value in Rust-facing runtime APIs where the existing code expects JSON payloads.
Use Result<T> with FlowError in core runtime paths. Keep errors explicit and binding-appropriate at the wrapper layer.

Files:

  • crates/core/src/plugin.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugin/dynamic.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
🔇 Additional comments (7)
crates/core/src/plugin.rs (2)

42-44: Confirm required Rust validation commands were run for this change set.

Please verify this PR ran the Rust checks required by repo policy: cargo fmt --all, cargo clippy --workspace --all-targets -- -D warnings, just test-rust, and uv run pre-commit run --all-files (or equivalent CI jobs covering them).
As per coding guidelines, "Any Rust change must run just test-rust", "Any Rust change must run cargo fmt --all", "Any Rust change must run cargo clippy --workspace --all-targets -- -D warnings", and "Validate Rust code with uv run pre-commit run --all-files".

Source: Coding guidelines


59-62: LGTM!

Also applies to: 786-786

crates/adaptive/src/error.rs (1)

56-56: LGTM!

crates/adaptive/tests/coverage/error_tests.rs (1)

64-69: LGTM!

crates/ffi/src/error.rs (1)

141-141: LGTM!

crates/ffi/tests/coverage/error_tests.rs (1)

99-103: LGTM!

crates/core/src/plugin/dynamic.rs (1)

1-425: Please confirm required Rust validation commands were executed for this change set.

I don’t see evidence in the provided context that the mandatory Rust checks were run (just test-rust, cargo fmt --all, and cargo clippy --workspace --all-targets -- -D warnings).

As per coding guidelines, "**/*.rs: Any Rust change must run just test-rust / cargo fmt --all / cargo clippy --workspace --all-targets -- -D warnings."

Source: Coding guidelines

Comment thread crates/core/src/plugin/dynamic/manifest.rs
Comment thread crates/core/src/plugin/dynamic/registry.rs
Comment thread crates/core/tests/unit/plugin_dynamic_tests.rs
Comment thread crates/core/src/plugin/dynamic/manifest.rs
@afourniernv afourniernv force-pushed the afournier/relay-337-plugin-control-plane-model branch from ad5e385 to cb980a5 Compare June 22, 2026 19:52
@afourniernv

Copy link
Copy Markdown
Collaborator Author

/ok to test cb980a5

@NVIDIA NVIDIA deleted a comment from copy-pr-bot Bot Jun 22, 2026

@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 `@crates/core/src/plugin/dynamic/registry.rs`:
- Around line 200-215: In the validate_record_shape function, the validation
checks for compat.native_api and compat.worker_protocol fields (mentioned at
lines 231-236 and 254-259) only check if the field is None but do not validate
that the value is non-empty after trimming whitespace. Update these validation
checks to use the same pattern as the relay field validation, which uses
is_none_or with a closure that checks if the trimmed value is empty. This
ensures that required compatibility fields containing only whitespace are
properly rejected and prevents invalid records from being added.
🪄 Autofix (Beta)

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: eae3b688-68f0-4264-8139-06184b4892fb

📥 Commits

Reviewing files that changed from the base of the PR and between ad5e385 and cb980a5.

📒 Files selected for processing (8)
  • crates/adaptive/src/error.rs
  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/core/src/plugin/dynamic.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
  • crates/ffi/src/error.rs
  • crates/ffi/tests/coverage/error_tests.rs
📜 Review details
🧰 Additional context used
📓 Path-based instructions (19)
**/*.rs

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

Use snake_case naming convention for Rust identifiers (e.g., nemo_relay_tool_call)

**/*.rs: Any Rust change must run just test-rust
Any Rust change must run cargo fmt --all
Any Rust change must run cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all for all FFI work since it is Rust work
Run just test-rust to validate FFI changes
Run cargo clippy --workspace --all-targets -- -D warnings to enforce strict linting on FFI work

When Rust files changed as part of Go work, also run cargo fmt --all, just test-rust, and cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all when Rust files are changed as part of Node work
Run cargo clippy --workspace --all-targets -- -D warnings when Rust files are changed as part of Node work
Run just test-rust when Rust files are changed as part of Node work

**/*.rs: Run cargo fmt --all to format all Rust code
Run cargo clippy --workspace --all-targets -- -D warnings to enforce all clippy lints as errors

**/*.rs: Run cargo fmt --all when Rust files changed as part of WebAssembly work
Run cargo clippy --workspace --all-targets -- -D warnings when Rust files changed as part of WebAssembly work

**/*.rs: If any Rust code changed, always run just test-rust
If any Rust code changed, also run cargo fmt --all
If any Rust code changed, also run cargo clippy --workspace --all-targets -- -D warnings
Run Rust formatting with cargo fmt --all
Run Rust linting with cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Use cargo fmt for Rust code formatting
Run cargo clippy -- -D warnings to lint Rust code and treat all warnings as errors
Use Rust snake_case naming convention for Rust identifiers
Include SPDX license header in all Rust source files using double-slash comment syntax
Validate Rust code with uv run pre-commit run --all-files to enforce cargo fmt formatting check, cargo clippy lints, and cargo deny aud...

Files:

  • crates/ffi/src/error.rs
  • crates/adaptive/src/error.rs
  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/ffi/tests/coverage/error_tests.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/src/plugin/dynamic.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
**/{Cargo.toml,**/*.rs}

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

Maintain consistency between Rust package names in Cargo.toml and their actual usage across the codebase

Files:

  • crates/ffi/src/error.rs
  • crates/adaptive/src/error.rs
  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/ffi/tests/coverage/error_tests.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/src/plugin/dynamic.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
**/*.{h,hpp,c,cpp,rs}

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

Ensure FFI header and library naming follows consistent conventions across platform-specific builds

Files:

  • crates/ffi/src/error.rs
  • crates/adaptive/src/error.rs
  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/ffi/tests/coverage/error_tests.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/src/plugin/dynamic.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
**/*.{rs,toml}

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

Update Rust crate names and module prefixes during coordinated rename operations

Files:

  • crates/ffi/src/error.rs
  • crates/adaptive/src/error.rs
  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/ffi/tests/coverage/error_tests.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/src/plugin/dynamic.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
crates/ffi/**

📄 CodeRabbit inference engine (.agents/skills/test-ffi-surface/SKILL.md)

Rebuild the FFI crate in release mode so the shared library and header stay in sync when making changes to crates/ffi

Files:

  • crates/ffi/src/error.rs
  • crates/ffi/tests/coverage/error_tests.rs
crates/ffi/**/*.rs

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

If the change touched crates/ffi, also use test-ffi-surface for validation

Files:

  • crates/ffi/src/error.rs
  • crates/ffi/tests/coverage/error_tests.rs
**/*.{rs,py,js,ts,tsx,jsx,go,sh,toml,yaml,yml,md}

📄 CodeRabbit inference engine (AGENTS.md)

Keep SPDX headers on source, docs, scripts, and configuration files. The project is Apache-2.0.

Files:

  • crates/ffi/src/error.rs
  • crates/adaptive/src/error.rs
  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/ffi/tests/coverage/error_tests.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/src/plugin/dynamic.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
**/*.{rs,py,go,js,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Follow binding naming conventions: Rust and Python use snake_case, C FFI exports prefixed nemo_relay_, Go uses PascalCase for public APIs, Node.js uses camelCase.

Files:

  • crates/ffi/src/error.rs
  • crates/adaptive/src/error.rs
  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/ffi/tests/coverage/error_tests.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/src/plugin/dynamic.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
crates/**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

crates/**/*.rs: Keep async behavior on the existing tokio-based model. Bindings should preserve callback and future lifetimes rather than blocking or hiding async work unexpectedly.
Use Json = serde_json::Value in Rust-facing runtime APIs for JSON payload handling.

Files:

  • crates/ffi/src/error.rs
  • crates/adaptive/src/error.rs
  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/ffi/tests/coverage/error_tests.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/src/plugin/dynamic.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
**

⚙️ CodeRabbit configuration file

**:

AGENTS.md

This file provides guidance to agents, including Claude Code and OpenAI Codex, when working in this repository.

Project Overview

NeMo Relay is a multi-language agent runtime framework for execution scopes, lifecycle events, middleware, plugins, and observability around tool and LLM calls. The core runtime is Rust. Primary supported bindings are Rust, Python, and Node.js. Go, WebAssembly, and the raw C FFI are experimental and source-first.

The shared runtime model is:

  1. Scope stacks decide where work belongs and which scope-local behavior is visible.
  2. Middleware registries decide what guardrails and intercepts run around managed calls.
  3. Plugins install reusable runtime behavior from configuration.
  4. Events record runtime behavior in ATOF form.
  5. Subscribers and exporters consume events in-process or export them to ATIF, OpenTelemetry, OpenInference, or other backends.

Repository Structure

The repository layout separates the Rust runtime, language bindings, documentation,
integration patches, and agent-facing skills.

crates/
  core/       # Rust core runtime crate, published as nemo-relay
  adaptive/   # Adaptive runtime primitives and plugin components
  python/     # PyO3 native extension for the Python package
  ffi/        # Raw C ABI layer used by downstream bindings such as Go
  node/       # NAPI Node.js binding and JavaScript/TypeScript entry points
  wasm/       # wasm-bindgen WebAssembly binding and JS wrappers
python/
  nemo_relay/  # Python wrapper package: scopes, tools, LLM, middleware, typed helpers, plugins, adaptive helpers
  tests/      # Python tests
go/
  nemo_relay/  # Experimental Go CGo binding and tests
fern/         # Fern documentation site
scripts/      # Stable wrappers and helper scripts; build/test/docs entry points live in justfile
third_party/  # P...

Files:

  • crates/ffi/src/error.rs
  • crates/adaptive/src/error.rs
  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/ffi/tests/coverage/error_tests.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/src/plugin/dynamic.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
crates/{python,ffi,node,wasm}/**/*

⚙️ CodeRabbit configuration file

crates/{python,ffi,node,wasm}/**/*: Treat binding changes as public API changes. Check for parity with the other language bindings, FFI ownership/lifetime safety,
callback error propagation, stable type conversion, and consistent async/stream semantics.
Flag changes that update one binding without corresponding tests or documentation for the same surface elsewhere.

Files:

  • crates/ffi/src/error.rs
  • crates/ffi/tests/coverage/error_tests.rs
{crates/adaptive/**,python/nemo_relay/adaptive.py,python/nemo_relay/plugin.py,go/nemo_relay/adaptive/**,go/nemo_relay/!(adaptive)/**,**/node/**,**/wasm/**}

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

Keep adaptive surface in sync across crates/adaptive, shared plugin behavior in core and bindings, Python adaptive/plugin wrappers in python/nemo_relay/adaptive.py and python/nemo_relay/plugin.py, Go adaptive helpers under go/nemo_relay/adaptive plus shared plugin helpers in go/nemo_relay, and Node/WebAssembly adaptive helpers and plugin wrappers

Files:

  • crates/adaptive/src/error.rs
  • crates/adaptive/tests/coverage/error_tests.rs
{crates/adaptive/**,python/nemo_relay/plugin.py,go/nemo_relay/**,**/node/**,**/wasm/**}

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

{crates/adaptive/**,python/nemo_relay/plugin.py,go/nemo_relay/**,**/node/**,**/wasm/**}: Maintain consistent plugin lifecycle across all language bindings (Python, Go, Node/WebAssembly, and Rust)
Keep plugin context surfaces aligned across all language implementations

Files:

  • crates/adaptive/src/error.rs
  • crates/adaptive/tests/coverage/error_tests.rs
{crates/adaptive/**/*.rs,**/*test*.{rs,py,go,ts,js},**/*adaptive*test*.{rs,py,go,ts,js},docs/plugins/adaptive/**}

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

Maintain documented and tested validation and report behavior for adaptive surfaces

Files:

  • crates/adaptive/src/error.rs
  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/ffi/tests/coverage/error_tests.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
{crates/core,crates/adaptive}/**/*

📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)

Changes to crates/core or crates/adaptive must run the full language matrix

Files:

  • crates/adaptive/src/error.rs
  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/src/plugin/dynamic.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
crates/{core,adaptive}/**

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

If crates/core or crates/adaptive changed, run the full matrix across Rust, Python, Go, Node.js, and WebAssembly

Files:

  • crates/adaptive/src/error.rs
  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/src/plugin/dynamic.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
crates/{core,adaptive}/**/*.rs

⚙️ CodeRabbit configuration file

crates/{core,adaptive}/**/*.rs: Review the Rust runtime for async correctness, scope isolation, middleware ordering, and event lifecycle regressions.
Pay close attention to task-local/thread-local scope propagation, callback lifetimes, stream finalization, and root_uuid isolation.
Public API changes should preserve existing behavior unless tests and docs show the intended migration path.

Files:

  • crates/adaptive/src/error.rs
  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/src/plugin/dynamic.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
{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:

  • crates/adaptive/tests/coverage/error_tests.rs
  • crates/ffi/tests/coverage/error_tests.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
crates/core/**/*.rs

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

If the change touched crates/core or shared runtime semantics, also use validate-change for broader validation

crates/core/**/*.rs: Use Json = serde_json::Value in Rust-facing runtime APIs where the existing code expects JSON payloads.
Use Result<T> with FlowError in core runtime paths. Keep errors explicit and binding-appropriate at the wrapper layer.

Files:

  • crates/core/src/plugin/dynamic/manifest.rs
  • crates/core/src/plugin/dynamic/registry.rs
  • crates/core/src/plugin/dynamic.rs
  • crates/core/tests/unit/plugin_dynamic_tests.rs
🔇 Additional comments (9)
crates/adaptive/src/error.rs (1)

56-56: LGTM!

crates/adaptive/tests/coverage/error_tests.rs (1)

64-69: LGTM!

crates/ffi/src/error.rs (1)

141-144: LGTM!

crates/ffi/tests/coverage/error_tests.rs (1)

99-103: LGTM!

crates/core/src/plugin/dynamic/registry.rs (1)

1-17: Please attach required Rust/core validation evidence for this PR

I can’t verify from the provided context that the required checks were run for Rust and crates/core changes. Please share command outputs for:

  • just test-rust
  • cargo fmt --all
  • cargo clippy --workspace --all-targets -- -D warnings
  • validate-change
  • full affected language matrix per core-change guidance

As per coding guidelines, **/*.rs requires running just test-rust, cargo fmt --all, and cargo clippy --workspace --all-targets -- -D warnings, and crates/core/**/*.rs requires validate-change plus full matrix checks for core/adaptive changes.

Sources: Coding guidelines, Path instructions

crates/core/tests/unit/plugin_dynamic_tests.rs (1)

53-56: LGTM!

Also applies to: 140-140, 183-186, 216-231, 233-259, 478-498, 525-568, 586-590, 767-769

crates/core/src/plugin/dynamic.rs (1)

222-250: LGTM!

crates/core/src/plugin/dynamic/manifest.rs (2)

225-332: Validate the required Rust/core check matrix before merge.

I can’t verify from this diff that the mandatory validation commands were run for this crates/core Rust change. Please attach results for just test-rust, cargo fmt --all, cargo clippy --workspace --all-targets -- -D warnings, uv run pre-commit run --all-files, validate-change, and the full language matrix required for core changes.

As per coding guidelines, "If any Rust code changed, always run just test-rust", "run cargo fmt --all", "run cargo clippy --workspace --all-targets -- -D warnings", "uv run pre-commit run --all-files", and "{crates/core,crates/adaptive}/**/*: Changes ... must run the full language matrix"; as per path instructions, "crates/core/**/*.rs: If the change touched crates/core ... also use validate-change for broader validation."

Also applies to: 334-381

Sources: Coding guidelines, Path instructions


150-199: LGTM!

Also applies to: 421-431, 470-499, 501-532

Comment thread crates/core/src/plugin/dynamic/registry.rs Outdated
@NVIDIA NVIDIA deleted a comment from copy-pr-bot Bot Jun 22, 2026
Signed-off-by: Alex Fournier <afournier@nvidia.com>
@afourniernv afourniernv force-pushed the afournier/relay-337-plugin-control-plane-model branch from cb980a5 to 44cd43c Compare June 22, 2026 20:29
@afourniernv

Copy link
Copy Markdown
Collaborator Author

/ok to test 44cd43c

@willkill07

Copy link
Copy Markdown
Member

/ok to test 8451479

@willkill07 willkill07 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sorry for the additional round of back-and-forth

Comment thread crates/core/src/plugin/dynamic.rs Outdated
Comment thread crates/core/src/plugin/dynamic.rs Outdated
Comment thread crates/core/src/plugin/dynamic.rs Outdated
@afourniernv

Copy link
Copy Markdown
Collaborator Author

/ok to test 8451479

Signed-off-by: Alex Fournier <afournier@nvidia.com>
Signed-off-by: Alex Fournier <afournier@nvidia.com>
Signed-off-by: Alex Fournier <afournier@nvidia.com>
@afourniernv

Copy link
Copy Markdown
Collaborator Author

/ok to test 1e7b5c5

@willkill07

Copy link
Copy Markdown
Member

/merge

@rapids-bot rapids-bot Bot merged commit 6a93612 into NVIDIA:main Jun 22, 2026
70 of 71 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature a new feature lang:rust PR changes/introduces Rust code size:XL PR is extra large

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants