Skip to content

feat: layer code-driven plugin config#199

Open
zhongxuanwang-nv wants to merge 7 commits into
NVIDIA:mainfrom
zhongxuanwang-nv:feat/relay-183-layer-code-driven-plugin-config
Open

feat: layer code-driven plugin config#199
zhongxuanwang-nv wants to merge 7 commits into
NVIDIA:mainfrom
zhongxuanwang-nv:feat/relay-183-layer-code-driven-plugin-config

Conversation

@zhongxuanwang-nv
Copy link
Copy Markdown
Member

@zhongxuanwang-nv zhongxuanwang-nv commented Jun 1, 2026

Overview

Layers code-driven plugin configuration over materialized file-backed plugin configuration so callers can combine discovered plugins.toml, inline [plugins].config, CLI overrides, request headers, and binding-level config builders deterministically.

  • 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

  • Added layer_plugin_config to the Rust core plugin API with recursive object merging, scalar/array replacement, and top-level component upsert by kind.
  • Exposed plugin config layering across FFI, Python, Node.js, WebAssembly, and Go wrapper surfaces.
  • Updated CLI config resolution so file-backed config remains compatible while code-driven layers like --plugin-config and request header config overlay the effective base.
  • Added effective plugin config source tracking in gateway config, doctor output, launcher printing, and server activation errors.
  • Updated plugin configuration docs and agent skills to describe precedence, file-source conflicts, and code-driven overlays.
  • Added focused tests across core, CLI, FFI, Python, Node.js, Go, and WebAssembly.

Quick functional check:

from nemo_relay import plugin

base = {
    "version": 1,
    "components": [
        {
            "kind": "observability",
            "enabled": False,
            "config": {
                "atof": {
                    "enabled": True,
                    "filename": "base.jsonl",
                }
            },
        }
    ],
}
overlay = {
    "components": [
        {
            "kind": "observability",
            "config": {
                "atof": {
                    "mode": "overwrite",
                }
            },
        }
    ],
}

plugin.layer(base, overlay)

Expected output:

{
    "version": 1,
    "components": [
        {
            "kind": "observability",
            "enabled": False,
            "config": {
                "atof": {
                    "enabled": True,
                    "filename": "base.jsonl",
                    "mode": "overwrite",
                }
            },
        }
    ],
}

Validation: full Rust, Python, Node.js, Go, WebAssembly, docs, cargo fmt, cargo clippy --workspace --all-targets -- -D warnings, and uv run pre-commit run --all-files passed. Focused reruns covered core layering, CLI layer precedence, the real nemo-relay run --dry-run binary path, and the Python/Node.js/Go/FFI/WebAssembly wrapper paths.

Where should the reviewer start?

Start with crates/core/src/plugin.rs for the shared layering semantics, then crates/cli/src/config.rs for source precedence and CLI/header composition.

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

Summary by CodeRabbit

Release Notes

  • New Features

    • Added automatic plugin configuration discovery from plugins.toml files in standard locations.
    • Implemented plugin configuration layering, allowing code-driven config to merge with file-backed config while preserving inherited omitted fields.
    • Enhanced --dry-run output to display the active plugin config source.
  • Breaking Changes

    • Removed --plugin-config CLI flag and x-nemo-relay-plugin-config header.
    • Updated plugin initialization APIs across all language bindings to accept optional config parameters.
  • Documentation

    • Updated plugin configuration guides with layering and discovery mechanics.

Signed-off-by: Zhongxuan Wang <daniewang@nvidia.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: c058a066-6936-48db-938b-aba03fc1856a

📥 Commits

Reviewing files that changed from the base of the PR and between 64508db and 75bc17f.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (35)
  • crates/cli/src/config.rs
  • crates/cli/src/installer.rs
  • crates/cli/src/launcher.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/config_tests.rs
  • crates/cli/tests/coverage/doctor_tests.rs
  • crates/cli/tests/coverage/installer_tests.rs
  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/tests/coverage/server_tests.rs
  • crates/core/Cargo.toml
  • crates/core/src/plugin.rs
  • crates/core/tests/unit/plugin_tests.rs
  • crates/ffi/nemo_relay.h
  • crates/ffi/src/api/mod.rs
  • crates/ffi/src/api/plugin.rs
  • crates/ffi/tests/unit/api/plugin_tests.rs
  • crates/node/plugin.d.ts
  • crates/node/plugin.js
  • crates/node/src/api/mod.rs
  • crates/node/tests/plugin_tests.mjs
  • crates/python/src/py_plugin.rs
  • crates/wasm/src/api/mod.rs
  • crates/wasm/wrappers/esm/plugin.d.ts
  • crates/wasm/wrappers/esm/plugin.js
  • crates/wasm/wrappers/nodejs/plugin.js
  • docs/build-plugins/plugin-configuration-files.mdx
  • docs/build-plugins/register-behavior.mdx
  • docs/nemo-relay-cli/basic-usage.mdx
  • go/nemo_relay/plugin.go
  • go/nemo_relay/plugin_gap_test.go
  • python/nemo_relay/_native.pyi
  • python/nemo_relay/plugin.py
  • python/nemo_relay/plugin.pyi
  • python/tests/test_plugin_config.py
  • skills/nemo-relay-build-plugin/SKILL.md
 __________________________________
< I void warranties and segfaults. >
 ----------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).

Walkthrough

This PR introduces plugin configuration layering—a cross-language feature enabling hierarchical composition of plugin configurations through code-driven overlays on file-backed defaults. The core layering logic merges JSON objects and components by "kind" field; the CLI tracks configuration provenance and layers header configs over process/file configs; and the feature is exposed across all language bindings (Rust, C, Node.js, Python, WebAssembly, Go) with comprehensive documentation and test coverage.

Changes

Plugin Configuration Layering

Layer / File(s) Summary
Core plugin configuration layering library
crates/core/src/plugin.rs, crates/core/tests/unit/plugin_tests.rs
Foundational layer_plugin_config(base, overlay) function recursively merges JSON configs, matching top-level components by "kind" field and preserving omitted fields; includes unit tests validating merge and replacement semantics.
CLI configuration source tracking and session layering
crates/cli/src/config.rs
Extends GatewayConfig with plugin_config_source: Option<String> field; implements session-scoped header overlay via layer_plugin_config; applies TOML and CLI config with provenance tracking; introduces source label formatting helpers.
CLI diagnostics and output
crates/cli/src/doctor.rs, crates/cli/src/launcher.rs, crates/cli/src/server.rs
Doctor report, launcher status, and server error messages now display plugin configuration source and overlay attribution in validation results and error context.
C FFI binding
crates/ffi/nemo_relay.h, crates/ffi/src/api/mod.rs, crates/ffi/src/api/plugin.rs, crates/ffi/tests/unit/api/plugin_tests.rs
Exposes nemo_relay_layer_plugin_config(base_json, overlay_json, out_json) C API with FFI error handling and wiring; includes smoke test verifying FFI round-trip.
Node.js binding
crates/node/src/api/mod.rs, crates/node/plugin.d.ts, crates/node/plugin.js, crates/node/tests/plugin_tests.mjs
NAPI function layer_plugin_config() delegates to core; Node wrapper exports layer(base, overlay) with TypeScript definitions and smoke test.
Python binding
crates/python/src/py_plugin.rs, python/nemo_relay/_native.pyi, python/nemo_relay/plugin.py, python/nemo_relay/plugin.pyi, python/tests/test_plugin_config.py
PyO3 wrapper layer_plugin_config_py converts Python objects to JSON; public layer() helper in plugin module with type stubs and test.
WebAssembly binding
crates/wasm/src/api/mod.rs, crates/wasm/wrappers/esm/index.js, crates/wasm/wrappers/esm/plugin.d.ts, crates/wasm/wrappers/esm/plugin.js, crates/wasm/wrappers/nodejs/plugin.js, crates/wasm/tests-js/plugin_tests.mjs
wasm-bindgen layerPluginConfig function; ESM and Node.js wrappers re-export layer() with TypeScript definitions and tests.
Go binding
go/nemo_relay/plugin.go, go/nemo_relay/plugin_gap_test.go
CGO wrapper LayerPluginConfig(base, overlay map[string]any) invokes native FFI; includes JSON serialization helper and test; fixes jsonCString to marshal provided value.
CLI integration test
crates/cli/tests/cli_tests.rs
End-to-end test verifying run --dry-run correctly layers --plugin-config override on top of plugins.toml and reports source provenance.
Configuration coverage tests
crates/cli/tests/coverage/config_tests.rs
Extended suite verifying session header layering over gateway config, TOML source attribution, and --plugin-config overlay precedence over file-backed and inline sources.
Test fixture updates
crates/cli/tests/coverage/doctor_tests.rs, crates/cli/tests/coverage/gateway_tests.rs, crates/cli/tests/coverage/server_tests.rs, crates/cli/tests/coverage/session_tests.rs
Scattered updates across test suites to include new required plugin_config_source: None field in test GatewayConfig literals (17 locations).
Documentation updates
docs/build-plugins/plugin-configuration-files.mdx, docs/build-plugins/register-behavior.mdx, docs/observability-plugin/atof.mdx, skills/nemo-relay-build-plugin/SKILL.md, skills/nemo-relay-tune-adaptive-config/SKILL.md
Comprehensive coverage of discovery rules, merge semantics, code-driven overlay examples in Python/Node/Rust, --dry-run verification, doctor validation guidance, and adaptive config composition best practices.

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed Title follows Conventional Commits format with 'feat' type and concise imperative summary under 72 characters.
Description check ✅ Passed Description provides Overview, Details, reviewer guidance, and Related Issues; all required template sections are present and substantive.
Docstring Coverage ✅ Passed Docstring coverage is 81.82% which is sufficient. The required threshold is 80.00%.
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

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


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

@github-actions github-actions Bot added size:L PR is large Feature a new feature lang:go PR changes/introduces Go code lang:js PR changes/introduces Javascript/Typescript code lang:python PR changes/introduces Python code lang:rust PR changes/introduces Rust code labels Jun 1, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

@zhongxuanwang-nv zhongxuanwang-nv self-assigned this Jun 2, 2026
Signed-off-by: Zhongxuan Wang <daniewang@nvidia.com>
@github-actions github-actions Bot added size:XL PR is extra large and removed size:L PR is large labels Jun 2, 2026
Signed-off-by: Zhongxuan Wang <daniewang@nvidia.com>
Signed-off-by: Zhongxuan Wang <daniewang@nvidia.com>
Signed-off-by: Zhongxuan Wang <daniewang@nvidia.com>
Signed-off-by: Zhongxuan Wang <daniewang@nvidia.com>
@zhongxuanwang-nv zhongxuanwang-nv marked this pull request as ready for review June 2, 2026 04:17
@zhongxuanwang-nv zhongxuanwang-nv requested a review from a team as a code owner June 2, 2026 04:17
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/cli/src/config.rs (1)

199-201: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve env-vs-flag provenance for plugin config layers.

ServerArgs.plugin_config is populated by clap from either --plugin-config or NEMO_RELAY_PLUGIN_CONFIG, but apply_cli_plugin_config hardcodes the provenance string as --plugin-config, so env-backed plugin config gets mis-tagged in plugin_config_source. That value is used by doctor, launcher output, and plugin activation error messages—so provenance tracking is incorrect. Thread the actual value source through to apply_code_plugin_config_layer (e.g., label env as NEMO_RELAY_PLUGIN_CONFIG), rather than always using --plugin-config.

Also applies to: 528-530, 806-811

🤖 Prompt for 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.

In `@crates/cli/src/config.rs` around lines 199 - 201, ServerArgs.plugin_config is
being labeled always as "--plugin-config" even when sourced from the
environment; update apply_cli_plugin_config to determine the actual provenance
(flag vs env) and pass that provenance string into
apply_code_plugin_config_layer so plugin_config_source reflects
"NEMO_RELAY_PLUGIN_CONFIG" for env-sourced values and "--plugin-config" for
flag-sourced values; specifically, locate uses of ServerArgs.plugin_config in
apply_cli_plugin_config and change the hardcoded provenance argument to compute
and thread the real source through to apply_code_plugin_config_layer (repeat the
same fix for the other spots noted around the apply_cli_plugin_config usages at
the other ranges so doctor, launcher output, and plugin activation errors get
the correct provenance).
crates/cli/tests/coverage/session_tests.rs (1)

99-107: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Consolidate duplicated GatewayConfig literals through session_test_config().

These inline configs now need the same field churn as the shared helper at Lines 4085-4095. Switching the tests that only need the default test config to session_test_config() would reduce future breakage when GatewayConfig changes again.

Also applies to: 1235-1243, 1305-1313, 1384-1392, 1815-1823, 1891-1899, 1963-1971, 2019-2027, 2065-2073, 2122-2130, 2219-2227, 2334-2342, 2427-2435, 4100-4108

🤖 Prompt for 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.

In `@crates/cli/tests/coverage/session_tests.rs` around lines 99 - 107, Replace
the duplicated inline GatewayConfig constructions in session_tests.rs with the
shared helper session_test_config() so tests that only require the default test
setup use that central source of truth; find the inline GatewayConfig instances
(constructed with fields like bind, openai_base_url, anthropic_base_url,
metadata, plugin_config, plugin_config_source) and swap them to let config =
session_test_config(), or if a test needs minor overrides, call
session_test_config() and then modify only those fields; ensure
session_test_config() is imported/visible in session_tests.rs and remove the
duplicated literal to avoid future churn when GatewayConfig changes.
🤖 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.rs`:
- Around line 187-197: The layering loop in layer_plugin_config wrongly
collapses multiple overlay components with the same kind into the first base
match (see overlay_components, base_components, json_component_kind,
merge_json_value), breaking configs where validate_plugin_config and
allows_multiple_components permit duplicates; update layer_plugin_config to
either (A) detect duplicate kinds in the overlay for kinds that
allow_multiple_components() and reject early with a clear error, or (B)
implement stable per-instance matching instead of first-match merging — e.g.,
require or derive a per-instance identifier (or positional/occurrence index) and
use that to find the correct base entry before calling merge_json_value, falling
back to appending a new component when no matching instance-id is found. Ensure
behavior is consistent with validate_plugin_config and document which strategy
is used.

In `@crates/ffi/nemo_relay.h`:
- Around line 1116-1125: The header for nemo_relay_layer_plugin_config is
missing ownership semantics for out_json; update the function doc comment to
state that on success the function allocates a NUL-terminated JSON string and
stores it in *out_json and that the caller takes ownership and must free
*out_json using nemo_relay_string_free (or equivalent API) to avoid leaks;
reference nemo_relay_layer_plugin_config and nemo_relay_string_free in the
updated comment so C consumers know who is responsible for deallocation.

In `@crates/ffi/tests/unit/api/plugin_tests.rs`:
- Around line 8-23: Add two negative tests for nemo_relay_layer_plugin_config:
one that passes an invalid JSON C string (e.g., cstring("not-json")) as either
base or overlay and asserts the call returns the FFI error status for bad JSON
(compare to the appropriate NemoRelayStatus variant), and one that passes a null
mutable out_json pointer (use &mut ptr::null_mut() or equivalent) and asserts
the call returns the FFI error status for a null/invalid output pointer; place
these alongside test_ffi_layer_plugin_config_round_trips_merge and use the same
helpers (cstring, returned_json) but do not attempt to dereference or consume
out_json on error paths.

In `@docs/observability-plugin/atof.mdx`:
- Around line 44-45: The docs for ATOF's `output_directory` and `filename` are
inconsistent with runtime behavior: update the description for
`output_directory` to state that the runtime (and the `doctor` command) will
create the directory if missing and that `doctor` only emits a warning rather
than requiring pre-creation; keep `filename` notes about the exporter creating
the file but not parent directories but clarify that parent directories are
created by runtime when using `output_directory`. Edit the `output_directory`
and `filename` table entries in the ATOF docs to reflect this corrected contract
and mention `doctor` by name so readers know where the warning originates.

In `@go/nemo_relay/plugin_gap_test.go`:
- Around line 38-53: Add a failing-path test for LayerPluginConfig that ensures
marshaling errors are returned before crossing the cgo boundary: update
TestLayerPluginConfigRoundTripsMerge (or add a new test) to call
LayerPluginConfig with an unsupported value (e.g., a channel, function, or
cyclic structure) and assert that it returns a non-nil error and does not return
merged JSON; this will exercise the jsonCString/serialization failure path and
ensure the binding surfaces the Go marshaling failure properly.

In `@python/nemo_relay/plugin.py`:
- Around line 291-309: The current layer() call normalizes both base and overlay
via _normalize_object(), but _normalize() removes dict entries with value None
so an overlay intending to set a key to JSON null (e.g. {"config": {"timeout":
None}}) is lost; update the normalization logic so explicit None values from
overlays are preserved: either modify _normalize()/_normalize_object() to not
drop keys whose value is None (or add a flag like preserve_nulls and call
_normalize_object(overlay, preserve_nulls=True) from layer), then call
_layer_plugin_config with the normalized base and the overlay normalized in a
way that retains explicit None overrides (reference functions: layer,
_normalize_object, _normalize).

In `@python/tests/test_plugin_config.py`:
- Around line 7-10: Update the test_layer_plugin_config_round_trips_merge test
to also exercise the typed wrapper path: call plugin.layer with a PluginConfig
instance (e.g., PluginConfig({"a": 1}) and PluginConfig({"b": 2}) or one
PluginConfig and one dict) in addition to the raw dict case and assert the
returned merged JSON equals {"a": 1, "b": 2}; use the PluginConfig symbol from
the module so Python-side conversion is exercised and consider parametrizing the
test if you prefer both forms tested succinctly.

---

Outside diff comments:
In `@crates/cli/src/config.rs`:
- Around line 199-201: ServerArgs.plugin_config is being labeled always as
"--plugin-config" even when sourced from the environment; update
apply_cli_plugin_config to determine the actual provenance (flag vs env) and
pass that provenance string into apply_code_plugin_config_layer so
plugin_config_source reflects "NEMO_RELAY_PLUGIN_CONFIG" for env-sourced values
and "--plugin-config" for flag-sourced values; specifically, locate uses of
ServerArgs.plugin_config in apply_cli_plugin_config and change the hardcoded
provenance argument to compute and thread the real source through to
apply_code_plugin_config_layer (repeat the same fix for the other spots noted
around the apply_cli_plugin_config usages at the other ranges so doctor,
launcher output, and plugin activation errors get the correct provenance).

In `@crates/cli/tests/coverage/session_tests.rs`:
- Around line 99-107: Replace the duplicated inline GatewayConfig constructions
in session_tests.rs with the shared helper session_test_config() so tests that
only require the default test setup use that central source of truth; find the
inline GatewayConfig instances (constructed with fields like bind,
openai_base_url, anthropic_base_url, metadata, plugin_config,
plugin_config_source) and swap them to let config = session_test_config(), or if
a test needs minor overrides, call session_test_config() and then modify only
those fields; ensure session_test_config() is imported/visible in
session_tests.rs and remove the duplicated literal to avoid future churn when
GatewayConfig changes.
🪄 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: a835da00-09af-46cc-826f-6d45f8cb9660

📥 Commits

Reviewing files that changed from the base of the PR and between b0557a9 and 64508db.

📒 Files selected for processing (38)
  • crates/cli/src/config.rs
  • crates/cli/src/doctor.rs
  • crates/cli/src/launcher.rs
  • crates/cli/src/server.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/config_tests.rs
  • crates/cli/tests/coverage/doctor_tests.rs
  • crates/cli/tests/coverage/gateway_tests.rs
  • crates/cli/tests/coverage/server_tests.rs
  • crates/cli/tests/coverage/session_tests.rs
  • crates/core/src/plugin.rs
  • crates/core/tests/unit/plugin_tests.rs
  • crates/ffi/nemo_relay.h
  • crates/ffi/src/api/mod.rs
  • crates/ffi/src/api/plugin.rs
  • crates/ffi/tests/unit/api/plugin_tests.rs
  • crates/node/plugin.d.ts
  • crates/node/plugin.js
  • crates/node/src/api/mod.rs
  • crates/node/tests/plugin_tests.mjs
  • crates/python/src/py_plugin.rs
  • crates/wasm/src/api/mod.rs
  • crates/wasm/tests-js/plugin_tests.mjs
  • crates/wasm/wrappers/esm/index.js
  • crates/wasm/wrappers/esm/plugin.d.ts
  • crates/wasm/wrappers/esm/plugin.js
  • crates/wasm/wrappers/nodejs/plugin.js
  • docs/build-plugins/plugin-configuration-files.mdx
  • docs/build-plugins/register-behavior.mdx
  • docs/observability-plugin/atof.mdx
  • go/nemo_relay/plugin.go
  • go/nemo_relay/plugin_gap_test.go
  • python/nemo_relay/_native.pyi
  • python/nemo_relay/plugin.py
  • python/nemo_relay/plugin.pyi
  • python/tests/test_plugin_config.py
  • skills/nemo-relay-build-plugin/SKILL.md
  • skills/nemo-relay-tune-adaptive-config/SKILL.md
📜 Review details
🧰 Additional context used
📓 Path-based instructions (69)
**/*.{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:

  • skills/nemo-relay-build-plugin/SKILL.md
  • skills/nemo-relay-tune-adaptive-config/SKILL.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:

  • skills/nemo-relay-build-plugin/SKILL.md
  • skills/nemo-relay-tune-adaptive-config/SKILL.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:

  • skills/nemo-relay-build-plugin/SKILL.md
  • skills/nemo-relay-tune-adaptive-config/SKILL.md
**/{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:

  • skills/nemo-relay-build-plugin/SKILL.md
  • skills/nemo-relay-tune-adaptive-config/SKILL.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:

  • skills/nemo-relay-build-plugin/SKILL.md
  • skills/nemo-relay-tune-adaptive-config/SKILL.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:

  • skills/nemo-relay-build-plugin/SKILL.md
  • skills/nemo-relay-tune-adaptive-config/SKILL.md
{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:

  • skills/nemo-relay-build-plugin/SKILL.md
  • skills/nemo-relay-tune-adaptive-config/SKILL.md
  • docs/observability-plugin/atof.mdx
  • docs/build-plugins/register-behavior.mdx
  • docs/build-plugins/plugin-configuration-files.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:

  • skills/nemo-relay-build-plugin/SKILL.md
  • skills/nemo-relay-tune-adaptive-config/SKILL.md
  • docs/observability-plugin/atof.mdx
  • docs/build-plugins/register-behavior.mdx
  • docs/build-plugins/plugin-configuration-files.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:

  • skills/nemo-relay-build-plugin/SKILL.md
  • skills/nemo-relay-tune-adaptive-config/SKILL.md
  • python/nemo_relay/plugin.py
  • docs/observability-plugin/atof.mdx
  • docs/build-plugins/register-behavior.mdx
  • python/tests/test_plugin_config.py
  • docs/build-plugins/plugin-configuration-files.mdx
**/*.{html,md,mdx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

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

Files:

  • skills/nemo-relay-build-plugin/SKILL.md
  • skills/nemo-relay-tune-adaptive-config/SKILL.md
  • docs/observability-plugin/atof.mdx
  • docs/build-plugins/register-behavior.mdx
  • docs/build-plugins/plugin-configuration-files.mdx
**/*.{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:

  • skills/nemo-relay-build-plugin/SKILL.md
  • crates/wasm/wrappers/esm/index.js
  • skills/nemo-relay-tune-adaptive-config/SKILL.md
  • python/nemo_relay/plugin.py
  • crates/node/plugin.js
  • crates/core/src/plugin.rs
  • crates/wasm/wrappers/esm/plugin.d.ts
  • crates/node/src/api/mod.rs
  • crates/node/plugin.d.ts
  • crates/ffi/src/api/mod.rs
  • crates/wasm/wrappers/nodejs/plugin.js
  • crates/cli/tests/coverage/doctor_tests.rs
  • crates/cli/src/launcher.rs
  • crates/wasm/src/api/mod.rs
  • crates/cli/tests/coverage/server_tests.rs
  • go/nemo_relay/plugin_gap_test.go
  • crates/cli/tests/cli_tests.rs
  • crates/wasm/wrappers/esm/plugin.js
  • go/nemo_relay/plugin.go
  • crates/core/tests/unit/plugin_tests.rs
  • crates/cli/src/server.rs
  • crates/cli/src/doctor.rs
  • crates/ffi/src/api/plugin.rs
  • crates/cli/tests/coverage/gateway_tests.rs
  • python/tests/test_plugin_config.py
  • crates/ffi/tests/unit/api/plugin_tests.rs
  • crates/python/src/py_plugin.rs
  • crates/cli/tests/coverage/session_tests.rs
  • crates/cli/src/config.rs
  • crates/cli/tests/coverage/config_tests.rs
**/SKILL.md

📄 CodeRabbit inference engine (AGENTS.md)

SKILL.md files are skill entrypoints and do not need SPDX headers, but they must always start with YAML frontmatter containing at least name and description.

Files:

  • skills/nemo-relay-build-plugin/SKILL.md
  • skills/nemo-relay-tune-adaptive-config/SKILL.md

⚙️ CodeRabbit configuration file

**/SKILL.md: Do not flag SKILL.md files for missing SPDX headers. Skill entrypoints intentionally start with YAML frontmatter instead.
Verify that every SKILL.md keeps valid YAML frontmatter with at least name and description fields before the Markdown body.

Files:

  • skills/nemo-relay-build-plugin/SKILL.md
  • skills/nemo-relay-tune-adaptive-config/SKILL.md
{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/wasm/wrappers/esm/index.js
  • crates/node/tests/plugin_tests.mjs
  • python/nemo_relay/plugin.py
  • crates/node/plugin.js
  • crates/wasm/wrappers/esm/plugin.d.ts
  • crates/node/src/api/mod.rs
  • crates/node/plugin.d.ts
  • crates/wasm/wrappers/nodejs/plugin.js
  • crates/wasm/src/api/mod.rs
  • crates/wasm/tests-js/plugin_tests.mjs
  • crates/wasm/wrappers/esm/plugin.js
{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/wasm/wrappers/esm/index.js
  • crates/node/tests/plugin_tests.mjs
  • python/nemo_relay/plugin.py
  • crates/node/plugin.js
  • crates/wasm/wrappers/esm/plugin.d.ts
  • crates/node/src/api/mod.rs
  • crates/node/plugin.d.ts
  • crates/wasm/wrappers/nodejs/plugin.js
  • crates/wasm/src/api/mod.rs
  • go/nemo_relay/plugin_gap_test.go
  • crates/wasm/tests-js/plugin_tests.mjs
  • crates/wasm/wrappers/esm/plugin.js
  • go/nemo_relay/plugin.go
**/*.{wasm,js,ts}{,x}

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

Ensure WebAssembly package naming conventions are consistent with generated package expectations and downstream consumption

Files:

  • crates/wasm/wrappers/esm/index.js
  • crates/node/plugin.js
  • crates/wasm/wrappers/esm/plugin.d.ts
  • crates/node/plugin.d.ts
  • crates/wasm/wrappers/nodejs/plugin.js
  • crates/wasm/wrappers/esm/plugin.js
crates/wasm/{wrappers,tests-js,scripts}/**/*.{js,ts,jsx,tsx}

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

Format changed WebAssembly JS/TS wrapper files with npm run precommit:format --workspace=nemo-relay-node -- crates/wasm/wrappers crates/wasm/tests-js crates/wasm/scripts

Files:

  • crates/wasm/wrappers/esm/index.js
  • crates/wasm/wrappers/esm/plugin.d.ts
  • crates/wasm/wrappers/nodejs/plugin.js
  • crates/wasm/wrappers/esm/plugin.js
**/*.{js,ts,jsx,tsx}

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

Run Node.js formatting with npm run format --workspace=nemo-relay-node

Include SPDX license header in all JavaScript and TypeScript source files using double-slash comment syntax

Files:

  • crates/wasm/wrappers/esm/index.js
  • crates/node/plugin.js
  • crates/wasm/wrappers/esm/plugin.d.ts
  • crates/node/plugin.d.ts
  • crates/wasm/wrappers/nodejs/plugin.js
  • crates/wasm/wrappers/esm/plugin.js
crates/wasm/**/*.{js,ts,jsx,tsx}

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

Run WebAssembly formatting with npm run precommit:format --workspace=nemo-relay-node -- crates/wasm/wrappers crates/wasm/tests-js crates/wasm/scripts

Files:

  • crates/wasm/wrappers/esm/index.js
  • crates/wasm/wrappers/esm/plugin.d.ts
  • crates/wasm/wrappers/nodejs/plugin.js
  • crates/wasm/wrappers/esm/plugin.js
**/*.{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/wasm/wrappers/esm/index.js
  • python/nemo_relay/plugin.py
  • crates/node/plugin.js
  • crates/core/src/plugin.rs
  • crates/wasm/wrappers/esm/plugin.d.ts
  • crates/node/src/api/mod.rs
  • crates/node/plugin.d.ts
  • crates/ffi/src/api/mod.rs
  • crates/wasm/wrappers/nodejs/plugin.js
  • crates/cli/tests/coverage/doctor_tests.rs
  • crates/cli/src/launcher.rs
  • crates/wasm/src/api/mod.rs
  • crates/cli/tests/coverage/server_tests.rs
  • go/nemo_relay/plugin_gap_test.go
  • crates/cli/tests/cli_tests.rs
  • crates/wasm/wrappers/esm/plugin.js
  • go/nemo_relay/plugin.go
  • crates/core/tests/unit/plugin_tests.rs
  • crates/cli/src/server.rs
  • crates/cli/src/doctor.rs
  • crates/ffi/src/api/plugin.rs
  • crates/cli/tests/coverage/gateway_tests.rs
  • python/tests/test_plugin_config.py
  • crates/ffi/tests/unit/api/plugin_tests.rs
  • crates/python/src/py_plugin.rs
  • crates/cli/tests/coverage/session_tests.rs
  • crates/cli/src/config.rs
  • crates/cli/tests/coverage/config_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/wasm/wrappers/esm/index.js
  • crates/node/tests/plugin_tests.mjs
  • crates/node/plugin.js
  • crates/wasm/wrappers/esm/plugin.d.ts
  • crates/ffi/nemo_relay.h
  • crates/node/src/api/mod.rs
  • crates/node/plugin.d.ts
  • crates/ffi/src/api/mod.rs
  • crates/wasm/wrappers/nodejs/plugin.js
  • crates/wasm/src/api/mod.rs
  • crates/wasm/tests-js/plugin_tests.mjs
  • crates/wasm/wrappers/esm/plugin.js
  • crates/ffi/src/api/plugin.rs
  • crates/ffi/tests/unit/api/plugin_tests.rs
  • crates/python/src/py_plugin.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/node/tests/plugin_tests.mjs
  • crates/cli/tests/coverage/doctor_tests.rs
  • crates/cli/tests/coverage/server_tests.rs
  • go/nemo_relay/plugin_gap_test.go
  • crates/cli/tests/cli_tests.rs
  • crates/core/tests/unit/plugin_tests.rs
  • crates/cli/tests/coverage/gateway_tests.rs
  • python/tests/test_plugin_config.py
  • crates/ffi/tests/unit/api/plugin_tests.rs
  • crates/cli/tests/coverage/session_tests.rs
  • crates/cli/tests/coverage/config_tests.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/plugin.py
  • python/nemo_relay/plugin.pyi
  • python/nemo_relay/_native.pyi
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/plugin.py
{python/nemo_relay/adaptive.py,python/nemo_relay/plugin.py,go/nemo_relay/adaptive/**,**/node/adaptive/**,**/wasm/adaptive/**}

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

Ensure typed helper constructors still map cleanly to the same config document in adaptive bindings across Python, Go, Node, and WebAssembly

Files:

  • python/nemo_relay/plugin.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/plugin.py
  • python/tests/test_plugin_config.py
**/*.{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:

  • python/nemo_relay/plugin.py
  • python/tests/test_plugin_config.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/plugin.py
  • python/tests/test_plugin_config.py
python/nemo_relay/**/*

⚙️ CodeRabbit configuration file

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

Files:

  • python/nemo_relay/plugin.py
  • python/nemo_relay/plugin.pyi
  • python/nemo_relay/_native.pyi
crates/node/**/*.{js,ts,jsx,tsx,json}

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

Format changed Node files with npm run format --workspace=nemo-relay-node

Files:

  • crates/node/plugin.js
  • crates/node/plugin.d.ts
crates/node/**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Node.js public entry points include the main runtime package plus nemo-relay-node/typed, nemo-relay-node/plugin, and nemo-relay-node/adaptive.

Files:

  • crates/node/plugin.js
  • crates/node/plugin.d.ts
**/*.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/plugin.rs
  • crates/node/src/api/mod.rs
  • crates/ffi/src/api/mod.rs
  • crates/cli/tests/coverage/doctor_tests.rs
  • crates/cli/src/launcher.rs
  • crates/wasm/src/api/mod.rs
  • crates/cli/tests/coverage/server_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/core/tests/unit/plugin_tests.rs
  • crates/cli/src/server.rs
  • crates/cli/src/doctor.rs
  • crates/ffi/src/api/plugin.rs
  • crates/cli/tests/coverage/gateway_tests.rs
  • crates/ffi/tests/unit/api/plugin_tests.rs
  • crates/python/src/py_plugin.rs
  • crates/cli/tests/coverage/session_tests.rs
  • crates/cli/src/config.rs
  • crates/cli/tests/coverage/config_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/core/src/plugin.rs
  • crates/node/src/api/mod.rs
  • crates/ffi/src/api/mod.rs
  • crates/cli/tests/coverage/doctor_tests.rs
  • crates/cli/src/launcher.rs
  • crates/wasm/src/api/mod.rs
  • crates/cli/tests/coverage/server_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/core/tests/unit/plugin_tests.rs
  • crates/cli/src/server.rs
  • crates/cli/src/doctor.rs
  • crates/ffi/src/api/plugin.rs
  • crates/cli/tests/coverage/gateway_tests.rs
  • crates/ffi/tests/unit/api/plugin_tests.rs
  • crates/python/src/py_plugin.rs
  • crates/cli/tests/coverage/session_tests.rs
  • crates/cli/src/config.rs
  • crates/cli/tests/coverage/config_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/core/src/plugin.rs
  • crates/ffi/nemo_relay.h
  • crates/node/src/api/mod.rs
  • crates/ffi/src/api/mod.rs
  • crates/cli/tests/coverage/doctor_tests.rs
  • crates/cli/src/launcher.rs
  • crates/wasm/src/api/mod.rs
  • crates/cli/tests/coverage/server_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/core/tests/unit/plugin_tests.rs
  • crates/cli/src/server.rs
  • crates/cli/src/doctor.rs
  • crates/ffi/src/api/plugin.rs
  • crates/cli/tests/coverage/gateway_tests.rs
  • crates/ffi/tests/unit/api/plugin_tests.rs
  • crates/python/src/py_plugin.rs
  • crates/cli/tests/coverage/session_tests.rs
  • crates/cli/src/config.rs
  • crates/cli/tests/coverage/config_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/core/src/plugin.rs
  • crates/core/tests/unit/plugin_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/core/src/plugin.rs
  • crates/node/src/api/mod.rs
  • crates/ffi/src/api/mod.rs
  • crates/cli/tests/coverage/doctor_tests.rs
  • crates/cli/src/launcher.rs
  • crates/wasm/src/api/mod.rs
  • crates/cli/tests/coverage/server_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/core/tests/unit/plugin_tests.rs
  • crates/cli/src/server.rs
  • crates/cli/src/doctor.rs
  • crates/ffi/src/api/plugin.rs
  • crates/cli/tests/coverage/gateway_tests.rs
  • crates/ffi/tests/unit/api/plugin_tests.rs
  • crates/python/src/py_plugin.rs
  • crates/cli/tests/coverage/session_tests.rs
  • crates/cli/src/config.rs
  • crates/cli/tests/coverage/config_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/tests/unit/plugin_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/core/src/plugin.rs
  • crates/core/tests/unit/plugin_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/core/src/plugin.rs
  • crates/node/src/api/mod.rs
  • crates/ffi/src/api/mod.rs
  • crates/cli/tests/coverage/doctor_tests.rs
  • crates/cli/src/launcher.rs
  • crates/wasm/src/api/mod.rs
  • crates/cli/tests/coverage/server_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/core/tests/unit/plugin_tests.rs
  • crates/cli/src/server.rs
  • crates/cli/src/doctor.rs
  • crates/ffi/src/api/plugin.rs
  • crates/cli/tests/coverage/gateway_tests.rs
  • crates/ffi/tests/unit/api/plugin_tests.rs
  • crates/python/src/py_plugin.rs
  • crates/cli/tests/coverage/session_tests.rs
  • crates/cli/src/config.rs
  • crates/cli/tests/coverage/config_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/core/src/plugin.rs
  • crates/core/tests/unit/plugin_tests.rs
{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/observability-plugin/atof.mdx
  • docs/build-plugins/register-behavior.mdx
  • docs/build-plugins/plugin-configuration-files.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/observability-plugin/atof.mdx
  • docs/build-plugins/register-behavior.mdx
  • docs/build-plugins/plugin-configuration-files.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/observability-plugin/atof.mdx
  • docs/build-plugins/register-behavior.mdx
  • docs/build-plugins/plugin-configuration-files.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/observability-plugin/atof.mdx
  • docs/build-plugins/register-behavior.mdx
  • docs/build-plugins/plugin-configuration-files.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/observability-plugin/atof.mdx
  • docs/build-plugins/register-behavior.mdx
  • docs/build-plugins/plugin-configuration-files.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/observability-plugin/atof.mdx
  • docs/build-plugins/register-behavior.mdx
  • docs/build-plugins/plugin-configuration-files.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/observability-plugin/atof.mdx
  • docs/build-plugins/register-behavior.mdx
  • docs/build-plugins/plugin-configuration-files.mdx
**/*.{h,c}

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

Update C header names and symbol prefixes during coordinated rename operations

Files:

  • crates/ffi/nemo_relay.h
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/nemo_relay.h
  • crates/ffi/src/api/mod.rs
  • crates/ffi/src/api/plugin.rs
  • crates/ffi/tests/unit/api/plugin_tests.rs
crates/ffi/nemo_relay.h

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

Check the generated header diff when any exported symbol or type changed in the FFI surface

Ensure FFI header sync for crates/ffi/nemo_relay.h through Cargo/build.rs

Files:

  • crates/ffi/nemo_relay.h
crates/ffi/*.h

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Prefix C FFI exports with nemo_relay_

Files:

  • crates/ffi/nemo_relay.h
crates/node/src/api/**/*.rs

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

Update Node.js binding in crates/node/src/api/mod.rs for language-native bindings

Files:

  • crates/node/src/api/mod.rs
crates/node/src/**/*.rs

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

Use camelCase naming convention for Node.js identifiers (e.g., toolCall)

Files:

  • crates/node/src/api/mod.rs
crates/node/**/*.{ts,tsx,d.ts}

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

Use npm run check:docstrings --workspace=nemo-relay-node to validate public API docstring checks when surface docs changed

Files:

  • crates/node/plugin.d.ts
crates/ffi/src/api/**/*.rs

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

crates/ffi/src/api/**/*.rs: Add or update FFI wrappers in relevant crates/ffi/src/api/*.rs modules, re-export through crates/ffi/src/api/mod.rs, and ensure generated crates/ffi/nemo_relay.h stays correct
Use nemo_relay_ prefix for C FFI function names (e.g., nemo_relay_tool_call)

Files:

  • crates/ffi/src/api/mod.rs
  • crates/ffi/src/api/plugin.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/api/mod.rs
  • crates/ffi/src/api/plugin.rs
  • crates/ffi/tests/unit/api/plugin_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/cli/tests/coverage/doctor_tests.rs
  • crates/cli/tests/coverage/server_tests.rs
  • go/nemo_relay/plugin_gap_test.go
  • crates/cli/tests/cli_tests.rs
  • crates/core/tests/unit/plugin_tests.rs
  • crates/cli/tests/coverage/gateway_tests.rs
  • python/tests/test_plugin_config.py
  • crates/ffi/tests/unit/api/plugin_tests.rs
  • crates/cli/tests/coverage/session_tests.rs
  • crates/cli/tests/coverage/config_tests.rs
crates/wasm/src/api/**/*.rs

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

Update WebAssembly binding in crates/wasm/src/api/mod.rs for language-native bindings

Files:

  • crates/wasm/src/api/mod.rs
crates/wasm/src/**/*.rs

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

Use camelCase naming convention for WebAssembly identifiers (e.g., toolCall)

Files:

  • crates/wasm/src/api/mod.rs
crates/wasm/**/*.rs

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

Run cargo test -p nemo-relay-wasm when Rust-only WebAssembly helpers changed

Files:

  • crates/wasm/src/api/mod.rs
go/nemo_relay/**/*.go

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

Update Go wrapper in go/nemo_relay/nemo_relay.go with doc comment and shorthand package if the capability belongs there

go/nemo_relay/**/*.go: Format changed Go packages with cd go/nemo_relay && go fmt ./...
Run Go tests with just test-go to build and test the NeMo Relay Go binding
Use just build-go when you want an explicit build-only pass or need the artifact for other work
Use just ci=true test-go when you need the CI-style coverage and JUnit path
On macOS, set DYLD_LIBRARY_PATH to the ../../target/release directory before running the raw go test command directly

Files:

  • go/nemo_relay/plugin_gap_test.go
  • go/nemo_relay/plugin.go
go/**/*.go

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

Use PascalCase naming convention for Go identifiers (e.g., nemo_relay.ToolCall)

Run Go formatting with cd go/nemo_relay && go fmt ./...

Files:

  • go/nemo_relay/plugin_gap_test.go
  • go/nemo_relay/plugin.go
{go/nemo_relay/go.mod,go/**/*.go}

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

Ensure Go module path in go/nemo_relay/go.mod matches import statements in Go source files

Files:

  • go/nemo_relay/plugin_gap_test.go
  • go/nemo_relay/plugin.go
**/*.go

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

Update Go module paths and package paths during coordinated rename operations

**/*.go: Use gofmt for Go code formatting
Run go vet ./... for Go static analysis
Use Go PascalCase naming convention for Go identifiers
Include SPDX license header in all Go source files using double-slash comment syntax
Validate Go code with uv run pre-commit run --all-files to enforce gofmt formatting and go vet static analysis

Files:

  • go/nemo_relay/plugin_gap_test.go
  • go/nemo_relay/plugin.go
go/nemo_relay/**/*

⚙️ CodeRabbit configuration file

go/nemo_relay/**/*: Review Go binding changes for cgo memory ownership, race safety, callback cleanup, idiomatic exported APIs, and parity with Rust/FFI behavior.
Any API change should include focused Go tests and consider race-test behavior.

Files:

  • go/nemo_relay/plugin_gap_test.go
  • go/nemo_relay/plugin.go
**/test_*.{py,py}

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

Relevant integration tests or smoke coverage must exist for the integration path

Files:

  • python/tests/test_plugin_config.py
**/*config*.{rs,ts,py,go,js,json,yaml,yml}

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

Ensure dynamic config shape still matches the documented canonical model

Files:

  • python/tests/test_plugin_config.py
  • crates/cli/src/config.rs
  • crates/cli/tests/coverage/config_tests.rs
python/**/*test*.py

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

python/**/*test*.py: Do not add @pytest.mark.asyncio to any test in Python test files
Do not add a -> None return type annotation to test functions
When mocking a class, use unittest.mock.MagicMock or unittest.mock.AsyncMock with the spec constructor argument when necessary, rather than defining a new class
Prefix mocked class names with mock, not fake
Prefer pytest fixtures over helper methods in Python tests
Prefer pytest.mark.parametrize over creating individual tests for different input types

Files:

  • python/tests/test_plugin_config.py
python/**/{conftest.py,*test*.py}

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

When creating a fixture follow the pattern: @pytest.fixture(name="<fixture_name>"[, scope="<scope>"]) def <fixture_name>_fixture() -> <return_type>: and only specify the scope argument when the value is something other than "function"

Files:

  • python/tests/test_plugin_config.py
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/src/py_plugin.rs
🧠 Learnings (1)
📚 Learning: 2026-05-07T18:04:44.387Z
Learnt from: mnajafian-nv
Repo: NVIDIA/NeMo-Flow PR: 67
File: integrations/openclaw/src/modules.ts:1-2
Timestamp: 2026-05-07T18:04:44.387Z
Learning: In NVIDIA/NeMo-Flow, TypeScript source files should use `//` line comments for SPDX headers (e.g., `// SPDX-FileCopyrightText: ...` and `// SPDX-License-Identifier: ...`) rather than C-style block comments (`/* ... */`). The repo’s copyright checker enforces this mapping, so `//` SPDX headers in `.ts` files should not be flagged as a style violation.

Applied to files:

  • crates/wasm/wrappers/esm/plugin.d.ts
  • crates/node/plugin.d.ts
🔇 Additional comments (12)
crates/ffi/src/api/plugin.rs (1)

12-18: LGTM!

Also applies to: 129-155

crates/node/tests/plugin_tests.mjs (1)

9-13: LGTM!

crates/wasm/tests-js/plugin_tests.mjs (1)

18-22: LGTM!

crates/wasm/wrappers/esm/index.js (1)

43-43: LGTM!

go/nemo_relay/plugin.go (1)

28-28: LGTM!

Also applies to: 94-112, 263-278, 588-598

crates/wasm/src/api/mod.rs (1)

2167-2178: LGTM!

crates/node/plugin.d.ts (1)

164-176: LGTM!

crates/node/plugin.js (1)

51-65: LGTM!

Also applies to: 175-179

python/nemo_relay/plugin.pyi (1)

111-111: LGTM!

crates/wasm/wrappers/esm/plugin.d.ts (1)

162-174: LGTM!

crates/wasm/wrappers/esm/plugin.js (1)

4-13: LGTM!

Also applies to: 54-68

crates/wasm/wrappers/nodejs/plugin.js (1)

6-15: LGTM!

Also applies to: 56-70, 179-181

Comment thread crates/core/src/plugin.rs
Comment on lines +187 to +197
for component in overlay_components {
let Some(kind) = json_component_kind(&component).map(str::to_owned) else {
base_components.push(component);
continue;
};
if let Some(existing) = base_components
.iter_mut()
.find(|candidate| json_component_kind(candidate) == Some(kind.as_str()))
{
merge_json_value(existing, component);
} else {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Do not merge duplicate component kinds into the first match.

Line 192 makes layer_plugin_config lossy for valid configs that contain multiple components with the same kind. validate_plugin_config already allows duplicate kinds for plugins where allows_multiple_components() is true, but this loop always merges the overlay into the first matching base entry and leaves no way to target later instances. An overlay like [{kind:"x"}, {kind:"x"}] will also collapse onto one base component instead of preserving two distinct updates.

Please either reject duplicate kind values before layering or introduce a stable per-instance key for matching; otherwise this public API can silently rewrite the wrong component across every binding.

🤖 Prompt for 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.

In `@crates/core/src/plugin.rs` around lines 187 - 197, The layering loop in
layer_plugin_config wrongly collapses multiple overlay components with the same
kind into the first base match (see overlay_components, base_components,
json_component_kind, merge_json_value), breaking configs where
validate_plugin_config and allows_multiple_components permit duplicates; update
layer_plugin_config to either (A) detect duplicate kinds in the overlay for
kinds that allow_multiple_components() and reject early with a clear error, or
(B) implement stable per-instance matching instead of first-match merging —
e.g., require or derive a per-instance identifier (or positional/occurrence
index) and use that to find the correct base entry before calling
merge_json_value, falling back to appending a new component when no matching
instance-id is found. Ensure behavior is consistent with validate_plugin_config
and document which strategy is used.

Comment thread crates/ffi/nemo_relay.h Outdated
Comment on lines +1116 to +1125
/**
* Layer one raw plugin config document over another and return the effective JSON document.
*
* # Safety
* `base_json` and `overlay_json` must be valid C strings and `out_json` must be a valid,
* non-null pointer.
*/
NemoRelayStatus nemo_relay_layer_plugin_config(const char *base_json,
const char *overlay_json,
char **out_json);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Document out_json ownership explicitly.

This API returns an allocated JSON string via out_json, but the header does not say the caller must free *out_json with nemo_relay_string_free. That omission is easy to turn into leaks for C consumers.

Proposed doc fix
 /**
  * Layer one raw plugin config document over another and return the effective JSON document.
+ *
+ * On success, writes an allocated JSON string to `out_json`. The caller must
+ * free `*out_json` with `nemo_relay_string_free`.
  *
  * # Safety
  * `base_json` and `overlay_json` must be valid C strings and `out_json` must be a valid,
  * non-null pointer.
  */
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* Layer one raw plugin config document over another and return the effective JSON document.
*
* # Safety
* `base_json` and `overlay_json` must be valid C strings and `out_json` must be a valid,
* non-null pointer.
*/
NemoRelayStatus nemo_relay_layer_plugin_config(const char *base_json,
const char *overlay_json,
char **out_json);
/**
* Layer one raw plugin config document over another and return the effective JSON document.
*
* On success, writes an allocated JSON string to `out_json`. The caller must
* free `*out_json` with `nemo_relay_string_free`.
*
* # Safety
* `base_json` and `overlay_json` must be valid C strings and `out_json` must be a valid,
* non-null pointer.
*/
NemoRelayStatus nemo_relay_layer_plugin_config(const char *base_json,
const char *overlay_json,
char **out_json);
🤖 Prompt for 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.

In `@crates/ffi/nemo_relay.h` around lines 1116 - 1125, The header for
nemo_relay_layer_plugin_config is missing ownership semantics for out_json;
update the function doc comment to state that on success the function allocates
a NUL-terminated JSON string and stores it in *out_json and that the caller
takes ownership and must free *out_json using nemo_relay_string_free (or
equivalent API) to avoid leaks; reference nemo_relay_layer_plugin_config and
nemo_relay_string_free in the updated comment so C consumers know who is
responsible for deallocation.

Comment on lines +8 to +23
#[test]
fn test_ffi_layer_plugin_config_round_trips_merge() {
// Smoke test only: merge semantics are covered by the core crate. This
// verifies the FFI boundary forwards both documents and returns merged JSON.
let base = cstring(&json!({ "a": 1 }).to_string());
let overlay = cstring(&json!({ "b": 2 }).to_string());

unsafe {
let mut out_json = ptr::null_mut();
assert_eq!(
nemo_relay_layer_plugin_config(base.as_ptr(), overlay.as_ptr(), &mut out_json),
NemoRelayStatus::Ok
);
assert_eq!(returned_json(out_json), json!({ "a": 1, "b": 2 }));
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add FFI error-path coverage for the new ABI.

This only pins the success case. Please add boundary tests for at least invalid JSON input and null out_json so the public FFI status mapping does not regress unnoticed.

As per coding guidelines, Tests should cover the behavior promised by the changed API surface, including error paths and cross-request isolation where relevant.

🤖 Prompt for 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.

In `@crates/ffi/tests/unit/api/plugin_tests.rs` around lines 8 - 23, Add two
negative tests for nemo_relay_layer_plugin_config: one that passes an invalid
JSON C string (e.g., cstring("not-json")) as either base or overlay and asserts
the call returns the FFI error status for bad JSON (compare to the appropriate
NemoRelayStatus variant), and one that passes a null mutable out_json pointer
(use &mut ptr::null_mut() or equivalent) and asserts the call returns the FFI
error status for a null/invalid output pointer; place these alongside
test_ffi_layer_plugin_config_round_trips_merge and use the same helpers
(cstring, returned_json) but do not attempt to dereference or consume out_json
on error paths.

Comment on lines +44 to +45
| `output_directory` | Current working directory | Directory containing the JSONL file. The directory must exist before initialization. |
| `filename` | Timestamped `nemo-relay-events-*.jsonl` | Explicit output filename inside `output_directory`. The exporter creates the file but not parent directories. |
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Align the ATOF directory docs with current runtime behavior.

These notes now contradict the current CLI/runtime contract: doctor treats a missing ATOF directory as a warning and explicitly says the runtime will create it. As written, this page tells users pre-creating the directory is required.

Suggested doc fix
-| `output_directory` | Current working directory | Directory containing the JSONL file. The directory must exist before initialization. |
-| `filename` | Timestamped `nemo-relay-events-*.jsonl` | Explicit output filename inside `output_directory`. The exporter creates the file but not parent directories. |
+| `output_directory` | Current working directory | Directory containing the JSONL file. If it does not exist, the runtime creates it. |
+| `filename` | Timestamped `nemo-relay-events-*.jsonl` | Explicit output filename inside `output_directory`. The exporter creates the file in that directory. |

As per coding guidelines, "Review documentation for technical accuracy against the current API, command correctness, and consistency across language bindings."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| `output_directory` | Current working directory | Directory containing the JSONL file. The directory must exist before initialization. |
| `filename` | Timestamped `nemo-relay-events-*.jsonl` | Explicit output filename inside `output_directory`. The exporter creates the file but not parent directories. |
| `output_directory` | Current working directory | Directory containing the JSONL file. If it does not exist, the runtime creates it. |
| `filename` | Timestamped `nemo-relay-events-*.jsonl` | Explicit output filename inside `output_directory`. The exporter creates the file in that directory. |
🤖 Prompt for 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.

In `@docs/observability-plugin/atof.mdx` around lines 44 - 45, The docs for ATOF's
`output_directory` and `filename` are inconsistent with runtime behavior: update
the description for `output_directory` to state that the runtime (and the
`doctor` command) will create the directory if missing and that `doctor` only
emits a warning rather than requiring pre-creation; keep `filename` notes about
the exporter creating the file but not parent directories but clarify that
parent directories are created by runtime when using `output_directory`. Edit
the `output_directory` and `filename` table entries in the ATOF docs to reflect
this corrected contract and mention `doctor` by name so readers know where the
warning originates.

Comment thread go/nemo_relay/plugin_gap_test.go Outdated
Comment on lines +38 to +53
func TestLayerPluginConfigRoundTripsMerge(t *testing.T) {
// Smoke test only: merge semantics are covered by the core crate. This
// verifies the cgo boundary forwards both documents and returns merged JSON.
merged, err := LayerPluginConfig(
map[string]any{"a": float64(1)},
map[string]any{"b": float64(2)},
)
if err != nil {
t.Fatalf("LayerPluginConfig failed: %v", err)
}

expected := map[string]any{"a": float64(1), "b": float64(2)}
if !reflect.DeepEqual(merged, expected) {
t.Fatalf("merged config mismatch:\n got: %#v\nwant: %#v", merged, expected)
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Cover the new binding’s serialization failure path.

This only verifies the happy path. LayerPluginConfig now marshals arbitrary Go values before crossing cgo, so add a case with an unsupported value and assert it fails before FFI. That protects the jsonCString path this PR touched.

Suggested test addition
 func TestLayerPluginConfigRoundTripsMerge(t *testing.T) {
 	// Smoke test only: merge semantics are covered by the core crate. This
 	// verifies the cgo boundary forwards both documents and returns merged JSON.
 	merged, err := LayerPluginConfig(
 		map[string]any{"a": float64(1)},
 		map[string]any{"b": float64(2)},
 	)
 	if err != nil {
 		t.Fatalf("LayerPluginConfig failed: %v", err)
 	}

 	expected := map[string]any{"a": float64(1), "b": float64(2)}
 	if !reflect.DeepEqual(merged, expected) {
 		t.Fatalf("merged config mismatch:\n got: %#v\nwant: %#v", merged, expected)
 	}
 }
+
+func TestLayerPluginConfigSerializationErrorsSurfaceBeforeFFI(t *testing.T) {
+	_, err := LayerPluginConfig(
+		map[string]any{"a": float64(1)},
+		map[string]any{"bad": make(chan int)},
+	)
+	if err == nil {
+		t.Fatal("expected LayerPluginConfig serialization error")
+	}
+}

As per coding guidelines, go/nemo_relay/**/*_test.go tests should cover the changed API surface, including error paths, and go/nemo_relay/**/* API changes should include focused Go tests.

🤖 Prompt for 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.

In `@go/nemo_relay/plugin_gap_test.go` around lines 38 - 53, Add a failing-path
test for LayerPluginConfig that ensures marshaling errors are returned before
crossing the cgo boundary: update TestLayerPluginConfigRoundTripsMerge (or add a
new test) to call LayerPluginConfig with an unsupported value (e.g., a channel,
function, or cyclic structure) and assert that it returns a non-nil error and
does not return merged JSON; this will exercise the jsonCString/serialization
failure path and ensure the binding surfaces the Go marshaling failure properly.

Comment thread python/nemo_relay/plugin.py Outdated
Comment on lines +291 to +309
def layer(base: PluginConfig | JsonObject, overlay: PluginConfig | JsonObject) -> JsonObject:
"""Layer one plugin configuration over another.

Args:
base: Lower-precedence plugin config, usually loaded from files.
overlay: Higher-precedence plugin config, usually built in code.

Returns:
The effective raw JSON plugin config.

Behavior:
Objects merge recursively, arrays and scalar values are replaced by the
overlay, and top-level components merge by `kind`. Passing raw mappings
preserves omitted fields so they can inherit from the base config.
"""
return cast(
JsonObject,
_layer_plugin_config(_normalize_object(base), _normalize_object(overlay)),
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve explicit None overrides in layer().

layer() normalizes both inputs through _normalize_object(), and _normalize() drops dict entries whose value is None. That means an overlay like {"config": {"timeout": None}} can never clear a base value to JSON null; the key disappears and the base value leaks through instead. This breaks the new layering contract for scalar replacement and raw-mapping overlays.

Proposed fix
-def _normalize(value: object) -> Json:
+def _normalize(value: object, *, drop_none: bool = True) -> Json:
     if hasattr(value, "to_dict"):
-        return cast(_SupportsToDict, value).to_dict()
+        return cast(_SupportsToDict, value).to_dict()
     if is_dataclass(value) and not isinstance(value, type):
         return {
-            field_info.name: _normalize(field_value)
+            field_info.name: _normalize(field_value, drop_none=drop_none)
             for field_info in fields(value)
-            if (field_value := getattr(value, field_info.name)) is not None
+            if (field_value := getattr(value, field_info.name)) is not None or not drop_none
         }
     if isinstance(value, list):
-        return [_normalize(item) for item in value]
+        return [_normalize(item, drop_none=drop_none) for item in value]
     if isinstance(value, dict):
-        return {cast(str, key): _normalize(val) for key, val in value.items() if val is not None}
+        return {
+            cast(str, key): _normalize(val, drop_none=drop_none)
+            for key, val in value.items()
+            if val is not None or not drop_none
+        }
     return cast(Json, value)
 
 
-def _normalize_object(value: object) -> JsonObject:
-    return cast(JsonObject, _normalize(value))
+def _normalize_object(value: object, *, drop_none: bool = True) -> JsonObject:
+    return cast(JsonObject, _normalize(value, drop_none=drop_none))
@@
 def layer(base: PluginConfig | JsonObject, overlay: PluginConfig | JsonObject) -> JsonObject:
@@
     return cast(
         JsonObject,
-        _layer_plugin_config(_normalize_object(base), _normalize_object(overlay)),
+        _layer_plugin_config(
+            _normalize_object(base, drop_none=False),
+            _normalize_object(overlay, drop_none=False),
+        ),
     )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def layer(base: PluginConfig | JsonObject, overlay: PluginConfig | JsonObject) -> JsonObject:
"""Layer one plugin configuration over another.
Args:
base: Lower-precedence plugin config, usually loaded from files.
overlay: Higher-precedence plugin config, usually built in code.
Returns:
The effective raw JSON plugin config.
Behavior:
Objects merge recursively, arrays and scalar values are replaced by the
overlay, and top-level components merge by `kind`. Passing raw mappings
preserves omitted fields so they can inherit from the base config.
"""
return cast(
JsonObject,
_layer_plugin_config(_normalize_object(base), _normalize_object(overlay)),
)
def layer(base: PluginConfig | JsonObject, overlay: PluginConfig | JsonObject) -> JsonObject:
"""Layer one plugin configuration over another.
Args:
base: Lower-precedence plugin config, usually loaded from files.
overlay: Higher-precedence plugin config, usually built in code.
Returns:
The effective raw JSON plugin config.
Behavior:
Objects merge recursively, arrays and scalar values are replaced by the
overlay, and top-level components merge by `kind`. Passing raw mappings
preserves omitted fields so they can inherit from the base config.
"""
return cast(
JsonObject,
_layer_plugin_config(
_normalize_object(base, drop_none=False),
_normalize_object(overlay, drop_none=False),
),
)
🤖 Prompt for 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.

In `@python/nemo_relay/plugin.py` around lines 291 - 309, The current layer() call
normalizes both base and overlay via _normalize_object(), but _normalize()
removes dict entries with value None so an overlay intending to set a key to
JSON null (e.g. {"config": {"timeout": None}}) is lost; update the normalization
logic so explicit None values from overlays are preserved: either modify
_normalize()/_normalize_object() to not drop keys whose value is None (or add a
flag like preserve_nulls and call _normalize_object(overlay,
preserve_nulls=True) from layer), then call _layer_plugin_config with the
normalized base and the overlay normalized in a way that retains explicit None
overrides (reference functions: layer, _normalize_object, _normalize).

Comment thread python/tests/test_plugin_config.py Outdated
Comment on lines +7 to +10
def test_layer_plugin_config_round_trips_merge():
# Smoke test only: merge semantics are covered by the core crate. This
# verifies the binding forwards both documents and returns merged JSON.
assert plugin.layer({"a": 1}, {"b": 2}) == {"a": 1, "b": 2}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Cover the typed wrapper input path too.

This only exercises raw dict inputs. The public Python surface for layer also accepts PluginConfig, so add a second case for wrapper objects (or parametrize both forms) to catch Python-side conversion regressions that the core Rust tests will not see.

Suggested test shape
 from nemo_relay import plugin
 
 
 def test_layer_plugin_config_round_trips_merge():
     # Smoke test only: merge semantics are covered by the core crate. This
     # verifies the binding forwards both documents and returns merged JSON.
     assert plugin.layer({"a": 1}, {"b": 2}) == {"a": 1, "b": 2}
+
+
+def test_layer_plugin_config_accepts_wrapper_types():
+    base = plugin.PluginConfig(
+        components=[plugin.ComponentSpec("example", config={"a": 1})],
+    )
+    overlay = plugin.PluginConfig(
+        components=[plugin.ComponentSpec("example", config={"b": 2})],
+    )
+
+    layered = plugin.layer(base, overlay)
+    assert layered["components"][0]["config"] == {"a": 1, "b": 2}

As per coding guidelines "{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."

🤖 Prompt for 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.

In `@python/tests/test_plugin_config.py` around lines 7 - 10, Update the
test_layer_plugin_config_round_trips_merge test to also exercise the typed
wrapper path: call plugin.layer with a PluginConfig instance (e.g.,
PluginConfig({"a": 1}) and PluginConfig({"b": 2}) or one PluginConfig and one
dict) in addition to the raw dict case and assert the returned merged JSON
equals {"a": 1, "b": 2}; use the PluginConfig symbol from the module so
Python-side conversion is exercised and consider parametrizing the test if you
prefer both forms tested succinctly.

Copy link
Copy Markdown
Member

@willkill07 willkill07 left a comment

Choose a reason for hiding this comment

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

code-driven plugin configuration is accomplished through each language binding's plugin initialize function.

  1. I do not see the "base" layer being inferred automatically from user/project/global config (which was the entire point of layering).
  2. Providing per-language binding layering is nice, but I believe we can drop --plugin-config outright. Folks should be using the configuration files rather than ad-hoc.
  3. The added/improved error output is good!

Signed-off-by: Zhongxuan Wang <daniewang@nvidia.com>
@zhongxuanwang-nv zhongxuanwang-nv requested a review from a team as a code owner June 2, 2026 19:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature a new feature lang:go PR changes/introduces Go code lang:js PR changes/introduces Javascript/Typescript code lang:python PR changes/introduces Python code 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