Skip to content

fix!: streamline plugin configuration#346

Merged
rapids-bot[bot] merged 4 commits into
NVIDIA:mainfrom
willkill07:fix/relay-401-plugin-config
Jul 2, 2026
Merged

fix!: streamline plugin configuration#346
rapids-bot[bot] merged 4 commits into
NVIDIA:mainfrom
willkill07:fix/relay-401-plugin-config

Conversation

@willkill07

@willkill07 willkill07 commented Jul 1, 2026

Copy link
Copy Markdown
Member

Overview

Streamline CLI plugin configuration around plugins.toml and add a hidden path override for controlled runtime launches.

  • 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 hidden --plugin-config-path support with NEMO_RELAY_PLUGIN_CONFIG_PATH for daemon and run configuration.
  • Removed the --plugin-config CLI inputs, including hook forwarding.
  • Removed plugin configuration from config.toml; plugin activation now comes from plugins.toml.
  • Updated setup behavior, documentation, and regression coverage for path precedence and removed configuration forms.
  • Breaking changes: users must move plugin configuration out of config.toml and replace --plugin-config usage with a plugin file.

Validation:

  • just test-rust
  • cargo clippy --workspace --all-targets -- -D warnings
  • uv run pre-commit run --files ...
  • just docs

Where should the reviewer start?

Start with crates/cli/src/config.rs for source precedence and removal of inline plugin configuration, then review crates/cli/tests/coverage/config_tests.rs and crates/cli/tests/cli_tests.rs for the new path override and CLI behavior.

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

  • Closes RELAY-401

Summary by CodeRabbit

  • New Features
    • Plugin configuration is now loaded from plugins.toml, with support for an override that changes the load location.
  • Bug Fixes
    • config.toml no longer supports a [plugins] section; plugin configuration must live in plugins.toml.
    • Hook-forward requests no longer include plugin configuration in HTTP headers.
    • Setup merge mode now drops the existing top-level [plugins] section and focuses on updating the relevant [agents.*] blocks.
  • Documentation
    • Updated plugin discovery and CLI usage docs to remove --plugin-config and reflect the new configuration behavior.

Signed-off-by: Will Killian <wkillian@nvidia.com>
@willkill07 willkill07 requested review from a team and lvojtku as code owners July 1, 2026 22:04
@github-actions github-actions Bot added size:M PR is medium Bug issue describes bug; PR fixes bug lang:rust PR changes/introduces Rust code labels Jul 1, 2026
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 0d0c625a-85c7-47cf-9c55-2db458f3f012

📥 Commits

Reviewing files that changed from the base of the PR and between 6a806af and 25c8b23.

📒 Files selected for processing (6)
  • 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/launcher_tests.rs
💤 Files with no reviewable changes (1)
  • crates/cli/src/installer.rs
📜 Recent review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: Check / Run
  • GitHub Check: Preview docs
🧰 Additional context used
📓 Path-based instructions (8)
**/*.rs

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

**/*.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: Use cargo fmt (rustfmt defaults) for Rust code changed in the core runtime or Rust-facing API surface.
Run cargo clippy -- -D warnings and keep Rust code warning-free; all warnings are treated as errors.
Use Rust snake_case naming conventions.

Use snake_case for Rust identifiers.

Files:

  • crates/cli/src/launcher.rs
  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/config_tests.rs
  • crates/cli/src/config.rs
**/*

📄 CodeRabbit inference engine (AGENTS.md)

Keep SPDX headers on source, docs, scripts, and configuration files.

Files:

  • crates/cli/src/launcher.rs
  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/config_tests.rs
  • crates/cli/src/config.rs
**/*.{rs,py,go,ts,js,mjs,cjs,jsx,tsx,c,h}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • crates/cli/src/launcher.rs
  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/config_tests.rs
  • crates/cli/src/config.rs
crates/**/*.{rs,py,ts,js,c,h}

📄 CodeRabbit inference engine (AGENTS.md)

crates/**/*.{rs,py,ts,js,c,h}: 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, and keep errors explicit and binding-appropriate at wrapper layers.
Keep async behavior on the existing tokio-based model; bindings should preserve callback and future lifetimes rather than blocking or hiding async work unexpectedly.

Files:

  • crates/cli/src/launcher.rs
  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/config_tests.rs
  • crates/cli/src/config.rs
**/*.{rs,go,js,ts,py,html,md,mdx,toml,yml,yaml,c,h}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Include SPDX license headers in all source files using the correct comment syntax for the file type.

Files:

  • crates/cli/src/launcher.rs
  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/config_tests.rs
  • crates/cli/src/config.rs
crates/**/src/**/*.rs

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

crates/**/src/**/*.rs: Keep the stable plugin boundary explicit: native plugins cross a C ABI, and worker plugins cross grpc-v1. Do not pass Rust runtime types, trait objects, futures, or allocator-owned strings across the native dynamic-library boundary.
Manifest validation must cover kind, compatibility, load contract, integrity, capability mismatch, and disabled-plugin behavior.
The native loader must keep libraries alive until registered callbacks are cleared and must deregister plugin kinds before unload.
Runtime helpers must cover marks, scopes, continuations, and isolated scope stacks.
plugins list, plugins inspect, and plugins validate must report lifecycle and compatibility status without leaking secret config.
The top-level doctor command must report resolved dynamic plugin and host configuration status.

Files:

  • crates/cli/src/launcher.rs
  • crates/cli/src/config.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 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, integrations, 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
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
skills/       # Published Codex/agent skills for NeMo Relay usage patterns

Prerequisites

Insta...

Files:

  • crates/cli/src/launcher.rs
  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/config_tests.rs
  • crates/cli/src/config.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/cli/tests/coverage/launcher_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/config_tests.rs
🛑 Comments failed to post (2)
crates/cli/src/launcher.rs (1)

718-721: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the HTTP storage schema and any existing destination-rendering tests.
rg -n -C4 'struct HttpStorageConfig|HttpStorageConfig|endpoint' crates/core/src/observability/plugin_component.rs
rg -n -C4 'atif_storage_destination|exporter_destinations|render_status_frame' crates/cli/src/launcher.rs crates/cli/tests

Repository: NVIDIA/NeMo-Relay

Length of output: 29687


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the status-banner tests and any existing URL redaction helpers.
sed -n '703,740p' crates/cli/src/launcher.rs
rg -n -C3 'redact|userinfo|password|token|credential|auth' crates/cli/src crates/core/src crates/cli/tests
sed -n '398,440p' crates/cli/tests/coverage/launcher_tests.rs

Repository: NVIDIA/NeMo-Relay

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Probe URL parsing behavior relevant to ATIF HTTP storage endpoints.
python3 - <<'PY'
from urllib.parse import urlparse

samples = [
    "http://host/path",
    "https://user:pass@host/path",
    "https://host/path?token=secret",
    "https://user@host/path",
]
for s in samples:
    u = urlparse(s)
    print(s)
    print("  scheme =", u.scheme)
    print("  netloc =", u.netloc)
    print("  username =", u.username)
    print("  password =", u.password)
    print("  geturl  =", u.geturl())
PY

Repository: NVIDIA/NeMo-Relay

Length of output: 691


Redact ATIF HTTP endpoints before printing
crates/cli/src/launcher.rs:718-721 AtifStorageConfig::Http accepts full URLs, and HttpUploadConfig::resolve preserves them, so printing http.endpoint verbatim can echo userinfo or query tokens. Emit a redacted display form instead.

🤖 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/launcher.rs` around lines 718 - 721, The ATIF storage
destination helper currently returns the raw HTTP endpoint from
AtifStorageConfig::Http, which can leak credentials or tokens when printed.
Update atif_storage_destination to emit a redacted display form for the Http
branch, preserving only safe parts of the URL, and keep the existing S3 handling
unchanged. Use the existing AtifStorageConfig and HttpUploadConfig::resolve flow
to locate the affected formatting path.
crates/cli/tests/coverage/config_tests.rs (1)

326-368: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Permission-based tests may pass unintentionally when run as root.

unreadable_config_errors_include_the_source_path relies on chmod 0o000 to force a read failure. If just test-rust / CI runs inside a container as root (common for Docker-based CI), root bypasses file permission bits entirely, so the file remains readable and both unwrap_err() calls will panic instead of exercising the intended error path. Consider guarding with a root check (e.g., skip or use nix::unistd::Uid::effective().is_root()) or asserting a runtime precondition.

🔧 Suggested guard
 #[cfg(unix)]
 #[test]
 fn unreadable_config_errors_include_the_source_path() {
     use std::os::unix::fs::PermissionsExt;
 
+    if unsafe { libc::geteuid() } == 0 {
+        eprintln!("skipping: running as root, permission bits are not enforced");
+        return;
+    }
+
     let temp = tempfile::tempdir().unwrap();
📝 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.

#[cfg(unix)]
#[test]
fn unreadable_config_errors_include_the_source_path() {
    use std::os::unix::fs::PermissionsExt;

    if unsafe { libc::geteuid() } == 0 {
        eprintln!("skipping: running as root, permission bits are not enforced");
        return;
    }

    let temp = tempfile::tempdir().unwrap();
    let config_path = temp.path().join("config.toml");
    std::fs::write(&config_path, "").unwrap();
    std::fs::set_permissions(&config_path, std::fs::Permissions::from_mode(0o000)).unwrap();
    let command = RunCommand {
        agent: None,
        config: Some(config_path.clone()),
        openai_base_url: None,
        anthropic_base_url: None,
        session_metadata: None,
        plugin_config_path: None,
        dry_run: true,
        print: false,
        command: vec![],
    };
    let config_error = resolve_run_config(&command, None).unwrap_err().to_string();
    std::fs::set_permissions(&config_path, std::fs::Permissions::from_mode(0o600)).unwrap();

    assert!(config_error.contains("failed to read configuration file"));
    assert!(
        config_error.contains(config_path.to_string_lossy().as_ref()),
        "{config_error}"
    );

    let plugins_path = temp.path().join("plugins.toml");
    std::fs::write(&plugins_path, "version = 1\n").unwrap();
    std::fs::set_permissions(&plugins_path, std::fs::Permissions::from_mode(0o000)).unwrap();
    let plugin_error = load_plugin_toml_config_from_paths(vec![plugins_path.clone()])
        .unwrap_err()
        .to_string();
    std::fs::set_permissions(&plugins_path, std::fs::Permissions::from_mode(0o600)).unwrap();

    assert!(plugin_error.contains("failed to read plugin configuration file"));
    assert!(
        plugin_error.contains(plugins_path.to_string_lossy().as_ref()),
        "{plugin_error}"
    );
}
🤖 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/config_tests.rs` around lines 326 - 368, The
permission-based test in unreadable_config_errors_include_the_source_path can
fail unpredictably when run as root because chmod 0o000 does not block reads for
root. Add a runtime guard in this test to skip or short-circuit when the
effective user is root, or assert the precondition before calling
resolve_run_config and load_plugin_toml_config_from_paths. Keep the existing
assertions around the config and plugin error paths, but ensure the
unreadable-file setup is only exercised when permissions will actually cause
read failures.
🔇 Additional comments (9)
crates/cli/tests/cli_tests.rs (2)

1487-1511: Good error-path coverage.

New test validates the missing-explicit-config error path (message + path echoed in stderr), consistent with path instructions on covering error paths for changed API surface.


45-106: 📐 Maintainability & Code Quality

Lines 96-97 (runtime = "command", entrypoint = "plugin.py") are marked as changed but aren't referenced by the line-range change details for this cohort (plugin_config_path migration). No functional issue found in the shown diff, but confirm this isn't an unrelated/incidental edit bleeding in from another commit in the stack.

crates/cli/tests/coverage/config_tests.rs (2)

294-324: LGTM!


1338-1361: LGTM! Overrides correctly switched to plugin_config_path, and plugin_config assertions match the new resolution contract in crates/cli/src/config.rs.

Also applies to: 1714-1735

crates/cli/tests/coverage/launcher_tests.rs (2)

397-440: Coverage aligned with ATIF exporter behavior.

New exporter_destinations_describe_atif_remote_storage_instead_of_local_path test correctly asserts remote storage lines and the absence of local filename-template reporting when storage is configured.


61-215: LGTM! plugin_configplugin_config_path renames are mechanical and consistent with the updated RunCommand struct.

Also applies to: 878-986

crates/cli/src/config.rs (2)

1039-1042: Already covered: explicit plugin config paths are still skipped when missing.

This still calls read_config_file(..., false, ...) for plugin config candidates, so the previous missing-override concern remains covered by the earlier review thread.


199-200: LGTM!

Also applies to: 428-428, 443-451, 510-510, 629-633, 790-820, 833-855, 957-964, 1003-1003, 1029-1038, 1043-1083

crates/cli/src/launcher.rs (1)

8-10: LGTM!

Also applies to: 563-563, 602-684, 715-717, 722-730, 783-798


Walkthrough

This PR moves plugin configuration to plugins.toml, adds a hidden plugin_config_path override, removes config.toml plugin config handling, and updates hook-forward, setup merge, tests, and docs to match.

Changes

Plugin configuration source change

Layer / File(s) Summary
CLI flag/field contract change
crates/cli/src/config.rs
ServerArgs and RunCommand replace plugin_config with hidden plugin_config_path, and daemon-mode detection uses the new field.
Config resolution and plugins.toml loading pipeline
crates/cli/src/config.rs
FileConfig drops plugins; shared config loading threads plugin_config_path through server and run resolution; config.toml plugins are rejected; plugin TOML loading now uses plugins.toml only; obsolete conflict helpers are removed.
Hook-forward header and validation cleanup
crates/cli/src/installer.rs, crates/cli/src/launcher.rs
hook_forward stops validating plugin config, gateway_headers drops the plugin-config header path, and easy_path clears plugin_config_path.
Setup config merge no longer preserves [plugins]
crates/cli/src/setup/model.rs
write_or_merge no longer preserves a shared [plugins] section and now only merges the target agent block.
Test updates for plugin_config_path and removed [plugins] handling
crates/cli/tests/cli_tests.rs, crates/cli/tests/coverage/*
Tests switch to plugin_config_path, remove --plugin-config, add plugins.toml dry-run coverage, adjust header helper calls, and update merge/TOML assertions.
Documentation updates for plugin configuration sourcing
docs/build-plugins/plugin-configuration-files.mdx, docs/nemo-relay-cli/basic-usage.mdx, integrations/coding-agents/README.md
Docs now describe plugins.toml as the plugin config source and remove --plugin-config / [plugins].config references.

Estimated code review effort: 3 (Moderate) | ~30 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title follows Conventional Commits, marks the breaking change, and accurately summarizes the plugin-configuration streamlining.
Description check ✅ Passed The description matches the template well with Overview, Details, reviewer start, related issue, and validation notes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@willkill07 willkill07 added this to the 0.5 milestone Jul 1, 2026
@willkill07 willkill07 self-assigned this Jul 1, 2026
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
crates/cli/src/setup/model.rs (1)

160-181: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Stale [plugins] blocks aren't stripped during merge, causing a load-time break.

write_or_merge no longer touches any pre-existing [plugins] table in existing — it only merges the target agent's block and writes existing back verbatim. Per the config resolution contract (crates/cli/src/config.rs, load_shared_config), any top-level plugins key in config.toml now causes a hard error: "plugin configuration in {} is no longer supported; move it to plugins.toml".

So a user with a pre-PR config.toml containing [plugins] who runs nemo-relay setup in merge mode (Some(agent)) will get a merged file that still contains [plugins], and the very next run/daemon invocation will fail to load that config. The merge path should actively strip any legacy [plugins] table from existing before writing it back, to make the breaking change self-healing during the natural setup flow.

🔧 Proposed fix
     let existing_raw = std::fs::read_to_string(path)?;
     let mut existing: DocumentMut = existing_raw
         .parse()
         .map_err(|err| CliError::Config(format!("could not parse existing config: {err}")))?;
     let agent_key = agent_key_and_command(agent).0;
+    // Strip any legacy [plugins] table left over from before plugin config moved to
+    // plugins.toml, so the merged file doesn't immediately fail to load.
+    existing.remove("plugins");
     merge_agents_entry(&mut existing, doc, agent_key);
     std::fs::write(path, existing.to_string())?;
🤖 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/setup/model.rs` around lines 160 - 181, write_or_merge
currently merges the agent block but preserves any legacy top-level [plugins]
table in existing, which can leave config.toml in a state that
load_shared_config rejects. Update write_or_merge to remove or strip the
pre-existing plugins section from the parsed DocumentMut before
merge_agents_entry and before writing back, so merge mode self-heals old configs
while still merging the target agent identified by agent_key_and_command.
♻️ Duplicate comments (1)
crates/cli/tests/coverage/setup_tests.rs (1)

449-472: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Missing regression coverage for legacy [plugins] migration during merge.

This test seeds a config without [plugins] at all, so it can't catch the gap flagged on crates/cli/src/setup/model.rs (write_or_merge, lines 160-181): if the existing file already has a [plugins] table from before this PR, merge mode currently leaves it in place, which then fails to load. Consider adding a case that seeds [plugins] in the pre-existing file and asserts it's stripped after write_or_merge.

🤖 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/setup_tests.rs` around lines 449 - 472, Add
regression coverage in the coverage test for the legacy plugins migration path
handled by write_or_merge in setup/model.rs. The current test only covers a
config with no [plugins] table, so it misses the case where an existing file
already contains [plugins] and merge mode leaves it behind. Update or add a test
around write_or_merge_recovers_from_non_table_agents_value that seeds the temp
config with a [plugins] table, runs write_or_merge, and asserts the merged
output no longer contains [plugins] while still preserving the expected agents
data.
🤖 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/cli/src/config.rs`:
- Line 822: The plugin config lookup in `load_plugin_toml_config` currently
treats an explicit `plugin_config_path` like an optional candidate, so a missing
override can be silently ignored. Update the `plugin_toml` loading path in
`config.rs` to distinguish explicit overrides from implicit discovery: when
`explicit` is set, require `plugin_config_path` to exist and return an error if
it does not, while keeping the fallback candidate search behavior only for
non-explicit discovery. Use the existing `load_plugin_toml_config` flow and
related plugin config parsing symbols to wire the required-path check in before
continuing with plugin activation.

---

Outside diff comments:
In `@crates/cli/src/setup/model.rs`:
- Around line 160-181: write_or_merge currently merges the agent block but
preserves any legacy top-level [plugins] table in existing, which can leave
config.toml in a state that load_shared_config rejects. Update write_or_merge to
remove or strip the pre-existing plugins section from the parsed DocumentMut
before merge_agents_entry and before writing back, so merge mode self-heals old
configs while still merging the target agent identified by
agent_key_and_command.

---

Duplicate comments:
In `@crates/cli/tests/coverage/setup_tests.rs`:
- Around line 449-472: Add regression coverage in the coverage test for the
legacy plugins migration path handled by write_or_merge in setup/model.rs. The
current test only covers a config with no [plugins] table, so it misses the case
where an existing file already contains [plugins] and merge mode leaves it
behind. Update or add a test around
write_or_merge_recovers_from_non_table_agents_value that seeds the temp config
with a [plugins] table, runs write_or_merge, and asserts the merged output no
longer contains [plugins] while still preserving the expected agents data.
🪄 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: c5c59540-2106-44e9-8c85-3f24a879164d

📥 Commits

Reviewing files that changed from the base of the PR and between cc3e5f1 and e069227.

📒 Files selected for processing (12)
  • crates/cli/src/config.rs
  • crates/cli/src/installer.rs
  • crates/cli/src/launcher.rs
  • crates/cli/src/setup/model.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/config_tests.rs
  • crates/cli/tests/coverage/installer_tests.rs
  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/tests/coverage/setup_tests.rs
  • docs/build-plugins/plugin-configuration-files.mdx
  • docs/nemo-relay-cli/basic-usage.mdx
  • integrations/coding-agents/README.md
💤 Files with no reviewable changes (3)
  • docs/nemo-relay-cli/basic-usage.mdx
  • integrations/coding-agents/README.md
  • crates/cli/src/installer.rs
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: Check / Run
  • GitHub Check: Preview docs
🧰 Additional context used
📓 Path-based instructions (22)
{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/build-plugins/plugin-configuration-files.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/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/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/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:

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

  • 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/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:

  • 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/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/build-plugins/plugin-configuration-files.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 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, integrations, 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
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
skills/       # Published Codex/agent skills for NeMo Relay usage patterns

Prerequisites

Insta...

Files:

  • docs/build-plugins/plugin-configuration-files.mdx
  • crates/cli/src/launcher.rs
  • crates/cli/src/setup/model.rs
  • crates/cli/tests/coverage/installer_tests.rs
  • crates/cli/tests/coverage/setup_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/config_tests.rs
  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/src/config.rs
{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/build-plugins/plugin-configuration-files.mdx
**/*.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/cli/src/launcher.rs
  • crates/cli/src/setup/model.rs
  • crates/cli/tests/coverage/installer_tests.rs
  • crates/cli/tests/coverage/setup_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/config_tests.rs
  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/src/config.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/cli/src/launcher.rs
  • crates/cli/src/setup/model.rs
  • crates/cli/tests/coverage/installer_tests.rs
  • crates/cli/tests/coverage/setup_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/config_tests.rs
  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/src/config.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/cli/src/launcher.rs
  • crates/cli/src/setup/model.rs
  • crates/cli/tests/coverage/installer_tests.rs
  • crates/cli/tests/coverage/setup_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/config_tests.rs
  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/src/config.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/cli/src/launcher.rs
  • crates/cli/src/setup/model.rs
  • crates/cli/tests/coverage/installer_tests.rs
  • crates/cli/tests/coverage/setup_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/config_tests.rs
  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/src/config.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/cli/src/launcher.rs
  • crates/cli/src/setup/model.rs
  • crates/cli/tests/coverage/installer_tests.rs
  • crates/cli/tests/coverage/setup_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/config_tests.rs
  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/src/config.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/cli/src/launcher.rs
  • crates/cli/src/setup/model.rs
  • crates/cli/tests/coverage/installer_tests.rs
  • crates/cli/tests/coverage/setup_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/config_tests.rs
  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/src/config.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/cli/src/launcher.rs
  • crates/cli/src/setup/model.rs
  • crates/cli/tests/coverage/installer_tests.rs
  • crates/cli/tests/coverage/setup_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/config_tests.rs
  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/src/config.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/installer_tests.rs
  • crates/cli/tests/coverage/setup_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/config_tests.rs
  • crates/cli/tests/coverage/launcher_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/cli/tests/coverage/installer_tests.rs
  • crates/cli/tests/coverage/setup_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/config_tests.rs
  • crates/cli/tests/coverage/launcher_tests.rs
**/*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:

  • crates/cli/tests/coverage/config_tests.rs
  • crates/cli/src/config.rs
🔇 Additional comments (8)
crates/cli/src/setup/model.rs (1)

116-118: LGTM!

Also applies to: 185-206

crates/cli/tests/cli_tests.rs (1)

146-152: LGTM!

Also applies to: 1489-1562, 1624-1663

crates/cli/tests/coverage/config_tests.rs (1)

238-444: LGTM!

Also applies to: 1145-1195, 1198-1283, 1636-1696, 1737-1779

crates/cli/tests/coverage/installer_tests.rs (1)

92-118: LGTM!

crates/cli/tests/coverage/launcher_tests.rs (1)

57-80: LGTM!

Also applies to: 83-107, 110-134, 137-155, 158-180, 183-201, 204-224, 821-849, 871-887, 890-949

crates/cli/src/config.rs (1)

426-428: LGTM!

Also applies to: 443-451, 497-510, 627-633, 686-697, 713-721, 752-782, 790-821, 823-828, 861-875, 933-940, 976-980, 1047-1071

crates/cli/src/launcher.rs (1)

69-82: LGTM!

docs/build-plugins/plugin-configuration-files.mdx (1)

130-139: LGTM!

Also applies to: 346-347

Comment thread crates/cli/src/config.rs
Signed-off-by: Will Killian <wkillian@nvidia.com>
Comment thread crates/cli/src/setup/model.rs
Comment thread crates/cli/src/config.rs

@mnajafian-nv mnajafian-nv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM overall. Thanks for driving this cleanup. Conditional approval pending review of the inline suggestions.

@willkill07 willkill07 changed the title fix: streamline plugin configuration fix!: streamline plugin configuration Jul 2, 2026
@github-actions github-actions Bot added the breaking PR introduces a breaking change label Jul 2, 2026
Signed-off-by: Will Killian <wkillian@nvidia.com>

@mnajafian-nv mnajafian-nv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM!

@lvojtku lvojtku left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approved with no comments

Signed-off-by: Will Killian <wkillian@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

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

822-828: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Remove the remaining request-header plugin override.

load_shared_config now makes plugins.toml the plugin config source, but GatewayConfig::session_config_from_headers still lets x-nemo-relay-plugin-config override self.plugin_config. Any caller that can send gateway headers can still bypass the new source-of-truth. Drop that header branch or gate it behind a trusted/internal path.

Proposed fix
-        let plugin_config = header_json(headers, "x-nemo-relay-plugin-config")
-            .or_else(|| self.plugin_config.clone());
+        let plugin_config = self.plugin_config.clone();
🤖 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 822 - 828,
`GatewayConfig::session_config_from_headers` still allows
`x-nemo-relay-plugin-config` to override `self.plugin_config`, which bypasses
the new `plugins.toml` source of truth from `load_shared_config`. Remove that
header-based override path, or restrict it to a trusted/internal-only flow, and
keep `ResolvedConfig`/`apply_plugin_toml_config` as the only way plugin config
is set.
🤖 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/cli/src/launcher.rs`:
- Around line 718-721: The ATIF storage destination helper currently returns the
raw HTTP endpoint from AtifStorageConfig::Http, which can leak credentials or
tokens when printed. Update atif_storage_destination to emit a redacted display
form for the Http branch, preserving only safe parts of the URL, and keep the
existing S3 handling unchanged. Use the existing AtifStorageConfig and
HttpUploadConfig::resolve flow to locate the affected formatting path.

In `@crates/cli/tests/coverage/config_tests.rs`:
- Around line 326-368: The permission-based test in
unreadable_config_errors_include_the_source_path can fail unpredictably when run
as root because chmod 0o000 does not block reads for root. Add a runtime guard
in this test to skip or short-circuit when the effective user is root, or assert
the precondition before calling resolve_run_config and
load_plugin_toml_config_from_paths. Keep the existing assertions around the
config and plugin error paths, but ensure the unreadable-file setup is only
exercised when permissions will actually cause read failures.

---

Outside diff comments:
In `@crates/cli/src/config.rs`:
- Around line 822-828: `GatewayConfig::session_config_from_headers` still allows
`x-nemo-relay-plugin-config` to override `self.plugin_config`, which bypasses
the new `plugins.toml` source of truth from `load_shared_config`. Remove that
header-based override path, or restrict it to a trusted/internal-only flow, and
keep `ResolvedConfig`/`apply_plugin_toml_config` as the only way plugin config
is set.
🪄 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: 0d0c625a-85c7-47cf-9c55-2db458f3f012

📥 Commits

Reviewing files that changed from the base of the PR and between 6a806af and 25c8b23.

📒 Files selected for processing (6)
  • 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/launcher_tests.rs
💤 Files with no reviewable changes (1)
  • crates/cli/src/installer.rs
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: Check / Run
  • GitHub Check: Preview docs
🧰 Additional context used
📓 Path-based instructions (8)
**/*.rs

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

**/*.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: Use cargo fmt (rustfmt defaults) for Rust code changed in the core runtime or Rust-facing API surface.
Run cargo clippy -- -D warnings and keep Rust code warning-free; all warnings are treated as errors.
Use Rust snake_case naming conventions.

Use snake_case for Rust identifiers.

Files:

  • crates/cli/src/launcher.rs
  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/config_tests.rs
  • crates/cli/src/config.rs
**/*

📄 CodeRabbit inference engine (AGENTS.md)

Keep SPDX headers on source, docs, scripts, and configuration files.

Files:

  • crates/cli/src/launcher.rs
  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/config_tests.rs
  • crates/cli/src/config.rs
**/*.{rs,py,go,ts,js,mjs,cjs,jsx,tsx,c,h}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • crates/cli/src/launcher.rs
  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/config_tests.rs
  • crates/cli/src/config.rs
crates/**/*.{rs,py,ts,js,c,h}

📄 CodeRabbit inference engine (AGENTS.md)

crates/**/*.{rs,py,ts,js,c,h}: 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, and keep errors explicit and binding-appropriate at wrapper layers.
Keep async behavior on the existing tokio-based model; bindings should preserve callback and future lifetimes rather than blocking or hiding async work unexpectedly.

Files:

  • crates/cli/src/launcher.rs
  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/config_tests.rs
  • crates/cli/src/config.rs
**/*.{rs,go,js,ts,py,html,md,mdx,toml,yml,yaml,c,h}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Include SPDX license headers in all source files using the correct comment syntax for the file type.

Files:

  • crates/cli/src/launcher.rs
  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/config_tests.rs
  • crates/cli/src/config.rs
crates/**/src/**/*.rs

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

crates/**/src/**/*.rs: Keep the stable plugin boundary explicit: native plugins cross a C ABI, and worker plugins cross grpc-v1. Do not pass Rust runtime types, trait objects, futures, or allocator-owned strings across the native dynamic-library boundary.
Manifest validation must cover kind, compatibility, load contract, integrity, capability mismatch, and disabled-plugin behavior.
The native loader must keep libraries alive until registered callbacks are cleared and must deregister plugin kinds before unload.
Runtime helpers must cover marks, scopes, continuations, and isolated scope stacks.
plugins list, plugins inspect, and plugins validate must report lifecycle and compatibility status without leaking secret config.
The top-level doctor command must report resolved dynamic plugin and host configuration status.

Files:

  • crates/cli/src/launcher.rs
  • crates/cli/src/config.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 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, integrations, 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
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
skills/       # Published Codex/agent skills for NeMo Relay usage patterns

Prerequisites

Insta...

Files:

  • crates/cli/src/launcher.rs
  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/config_tests.rs
  • crates/cli/src/config.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/cli/tests/coverage/launcher_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/config_tests.rs
🔇 Additional comments (9)
crates/cli/tests/cli_tests.rs (2)

1487-1511: Good error-path coverage.

New test validates the missing-explicit-config error path (message + path echoed in stderr), consistent with path instructions on covering error paths for changed API surface.


45-106: 📐 Maintainability & Code Quality

Lines 96-97 (runtime = "command", entrypoint = "plugin.py") are marked as changed but aren't referenced by the line-range change details for this cohort (plugin_config_path migration). No functional issue found in the shown diff, but confirm this isn't an unrelated/incidental edit bleeding in from another commit in the stack.

crates/cli/tests/coverage/config_tests.rs (2)

294-324: LGTM!


1338-1361: LGTM! Overrides correctly switched to plugin_config_path, and plugin_config assertions match the new resolution contract in crates/cli/src/config.rs.

Also applies to: 1714-1735

crates/cli/tests/coverage/launcher_tests.rs (2)

397-440: Coverage aligned with ATIF exporter behavior.

New exporter_destinations_describe_atif_remote_storage_instead_of_local_path test correctly asserts remote storage lines and the absence of local filename-template reporting when storage is configured.


61-215: LGTM! plugin_configplugin_config_path renames are mechanical and consistent with the updated RunCommand struct.

Also applies to: 878-986

crates/cli/src/config.rs (2)

1039-1042: Already covered: explicit plugin config paths are still skipped when missing.

This still calls read_config_file(..., false, ...) for plugin config candidates, so the previous missing-override concern remains covered by the earlier review thread.


199-200: LGTM!

Also applies to: 428-428, 443-451, 510-510, 629-633, 790-820, 833-855, 957-964, 1003-1003, 1029-1038, 1043-1083

crates/cli/src/launcher.rs (1)

8-10: LGTM!

Also applies to: 563-563, 602-684, 715-717, 722-730, 783-798

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 2

Caution

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

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

822-828: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Remove the remaining request-header plugin override.

load_shared_config now makes plugins.toml the plugin config source, but GatewayConfig::session_config_from_headers still lets x-nemo-relay-plugin-config override self.plugin_config. Any caller that can send gateway headers can still bypass the new source-of-truth. Drop that header branch or gate it behind a trusted/internal path.

Proposed fix
-        let plugin_config = header_json(headers, "x-nemo-relay-plugin-config")
-            .or_else(|| self.plugin_config.clone());
+        let plugin_config = self.plugin_config.clone();
🤖 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 822 - 828,
`GatewayConfig::session_config_from_headers` still allows
`x-nemo-relay-plugin-config` to override `self.plugin_config`, which bypasses
the new `plugins.toml` source of truth from `load_shared_config`. Remove that
header-based override path, or restrict it to a trusted/internal-only flow, and
keep `ResolvedConfig`/`apply_plugin_toml_config` as the only way plugin config
is set.
🤖 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/cli/src/launcher.rs`:
- Around line 718-721: The ATIF storage destination helper currently returns the
raw HTTP endpoint from AtifStorageConfig::Http, which can leak credentials or
tokens when printed. Update atif_storage_destination to emit a redacted display
form for the Http branch, preserving only safe parts of the URL, and keep the
existing S3 handling unchanged. Use the existing AtifStorageConfig and
HttpUploadConfig::resolve flow to locate the affected formatting path.

In `@crates/cli/tests/coverage/config_tests.rs`:
- Around line 326-368: The permission-based test in
unreadable_config_errors_include_the_source_path can fail unpredictably when run
as root because chmod 0o000 does not block reads for root. Add a runtime guard
in this test to skip or short-circuit when the effective user is root, or assert
the precondition before calling resolve_run_config and
load_plugin_toml_config_from_paths. Keep the existing assertions around the
config and plugin error paths, but ensure the unreadable-file setup is only
exercised when permissions will actually cause read failures.

---

Outside diff comments:
In `@crates/cli/src/config.rs`:
- Around line 822-828: `GatewayConfig::session_config_from_headers` still allows
`x-nemo-relay-plugin-config` to override `self.plugin_config`, which bypasses
the new `plugins.toml` source of truth from `load_shared_config`. Remove that
header-based override path, or restrict it to a trusted/internal-only flow, and
keep `ResolvedConfig`/`apply_plugin_toml_config` as the only way plugin config
is set.
🪄 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: 0d0c625a-85c7-47cf-9c55-2db458f3f012

📥 Commits

Reviewing files that changed from the base of the PR and between 6a806af and 25c8b23.

📒 Files selected for processing (6)
  • 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/launcher_tests.rs
💤 Files with no reviewable changes (1)
  • crates/cli/src/installer.rs
📜 Review details
🔇 Additional comments (9)
crates/cli/tests/cli_tests.rs (2)

1487-1511: Good error-path coverage.

New test validates the missing-explicit-config error path (message + path echoed in stderr), consistent with path instructions on covering error paths for changed API surface.


45-106: 📐 Maintainability & Code Quality

Lines 96-97 (runtime = "command", entrypoint = "plugin.py") are marked as changed but aren't referenced by the line-range change details for this cohort (plugin_config_path migration). No functional issue found in the shown diff, but confirm this isn't an unrelated/incidental edit bleeding in from another commit in the stack.

crates/cli/tests/coverage/config_tests.rs (2)

294-324: LGTM!


1338-1361: LGTM! Overrides correctly switched to plugin_config_path, and plugin_config assertions match the new resolution contract in crates/cli/src/config.rs.

Also applies to: 1714-1735

crates/cli/tests/coverage/launcher_tests.rs (2)

397-440: Coverage aligned with ATIF exporter behavior.

New exporter_destinations_describe_atif_remote_storage_instead_of_local_path test correctly asserts remote storage lines and the absence of local filename-template reporting when storage is configured.


61-215: LGTM! plugin_configplugin_config_path renames are mechanical and consistent with the updated RunCommand struct.

Also applies to: 878-986

crates/cli/src/config.rs (2)

1039-1042: Already covered: explicit plugin config paths are still skipped when missing.

This still calls read_config_file(..., false, ...) for plugin config candidates, so the previous missing-override concern remains covered by the earlier review thread.


199-200: LGTM!

Also applies to: 428-428, 443-451, 510-510, 629-633, 790-820, 833-855, 957-964, 1003-1003, 1029-1038, 1043-1083

crates/cli/src/launcher.rs (1)

8-10: LGTM!

Also applies to: 563-563, 602-684, 715-717, 722-730, 783-798

🛑 Comments failed to post (2)
crates/cli/src/launcher.rs (1)

718-721: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the HTTP storage schema and any existing destination-rendering tests.
rg -n -C4 'struct HttpStorageConfig|HttpStorageConfig|endpoint' crates/core/src/observability/plugin_component.rs
rg -n -C4 'atif_storage_destination|exporter_destinations|render_status_frame' crates/cli/src/launcher.rs crates/cli/tests

Repository: NVIDIA/NeMo-Relay

Length of output: 29687


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the status-banner tests and any existing URL redaction helpers.
sed -n '703,740p' crates/cli/src/launcher.rs
rg -n -C3 'redact|userinfo|password|token|credential|auth' crates/cli/src crates/core/src crates/cli/tests
sed -n '398,440p' crates/cli/tests/coverage/launcher_tests.rs

Repository: NVIDIA/NeMo-Relay

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Probe URL parsing behavior relevant to ATIF HTTP storage endpoints.
python3 - <<'PY'
from urllib.parse import urlparse

samples = [
    "http://host/path",
    "https://user:pass@host/path",
    "https://host/path?token=secret",
    "https://user@host/path",
]
for s in samples:
    u = urlparse(s)
    print(s)
    print("  scheme =", u.scheme)
    print("  netloc =", u.netloc)
    print("  username =", u.username)
    print("  password =", u.password)
    print("  geturl  =", u.geturl())
PY

Repository: NVIDIA/NeMo-Relay

Length of output: 691


Redact ATIF HTTP endpoints before printing
crates/cli/src/launcher.rs:718-721 AtifStorageConfig::Http accepts full URLs, and HttpUploadConfig::resolve preserves them, so printing http.endpoint verbatim can echo userinfo or query tokens. Emit a redacted display form instead.

🤖 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/launcher.rs` around lines 718 - 721, The ATIF storage
destination helper currently returns the raw HTTP endpoint from
AtifStorageConfig::Http, which can leak credentials or tokens when printed.
Update atif_storage_destination to emit a redacted display form for the Http
branch, preserving only safe parts of the URL, and keep the existing S3 handling
unchanged. Use the existing AtifStorageConfig and HttpUploadConfig::resolve flow
to locate the affected formatting path.
crates/cli/tests/coverage/config_tests.rs (1)

326-368: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Permission-based tests may pass unintentionally when run as root.

unreadable_config_errors_include_the_source_path relies on chmod 0o000 to force a read failure. If just test-rust / CI runs inside a container as root (common for Docker-based CI), root bypasses file permission bits entirely, so the file remains readable and both unwrap_err() calls will panic instead of exercising the intended error path. Consider guarding with a root check (e.g., skip or use nix::unistd::Uid::effective().is_root()) or asserting a runtime precondition.

🔧 Suggested guard
 #[cfg(unix)]
 #[test]
 fn unreadable_config_errors_include_the_source_path() {
     use std::os::unix::fs::PermissionsExt;
 
+    if unsafe { libc::geteuid() } == 0 {
+        eprintln!("skipping: running as root, permission bits are not enforced");
+        return;
+    }
+
     let temp = tempfile::tempdir().unwrap();
📝 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.

#[cfg(unix)]
#[test]
fn unreadable_config_errors_include_the_source_path() {
    use std::os::unix::fs::PermissionsExt;

    if unsafe { libc::geteuid() } == 0 {
        eprintln!("skipping: running as root, permission bits are not enforced");
        return;
    }

    let temp = tempfile::tempdir().unwrap();
    let config_path = temp.path().join("config.toml");
    std::fs::write(&config_path, "").unwrap();
    std::fs::set_permissions(&config_path, std::fs::Permissions::from_mode(0o000)).unwrap();
    let command = RunCommand {
        agent: None,
        config: Some(config_path.clone()),
        openai_base_url: None,
        anthropic_base_url: None,
        session_metadata: None,
        plugin_config_path: None,
        dry_run: true,
        print: false,
        command: vec![],
    };
    let config_error = resolve_run_config(&command, None).unwrap_err().to_string();
    std::fs::set_permissions(&config_path, std::fs::Permissions::from_mode(0o600)).unwrap();

    assert!(config_error.contains("failed to read configuration file"));
    assert!(
        config_error.contains(config_path.to_string_lossy().as_ref()),
        "{config_error}"
    );

    let plugins_path = temp.path().join("plugins.toml");
    std::fs::write(&plugins_path, "version = 1\n").unwrap();
    std::fs::set_permissions(&plugins_path, std::fs::Permissions::from_mode(0o000)).unwrap();
    let plugin_error = load_plugin_toml_config_from_paths(vec![plugins_path.clone()])
        .unwrap_err()
        .to_string();
    std::fs::set_permissions(&plugins_path, std::fs::Permissions::from_mode(0o600)).unwrap();

    assert!(plugin_error.contains("failed to read plugin configuration file"));
    assert!(
        plugin_error.contains(plugins_path.to_string_lossy().as_ref()),
        "{plugin_error}"
    );
}
🤖 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/config_tests.rs` around lines 326 - 368, The
permission-based test in unreadable_config_errors_include_the_source_path can
fail unpredictably when run as root because chmod 0o000 does not block reads for
root. Add a runtime guard in this test to skip or short-circuit when the
effective user is root, or assert the precondition before calling
resolve_run_config and load_plugin_toml_config_from_paths. Keep the existing
assertions around the config and plugin error paths, but ensure the
unreadable-file setup is only exercised when permissions will actually cause
read failures.

@willkill07

Copy link
Copy Markdown
Member Author

/merge

@rapids-bot rapids-bot Bot merged commit 6adbad2 into NVIDIA:main Jul 2, 2026
34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking PR introduces a breaking change Bug issue describes bug; PR fixes bug lang:rust PR changes/introduces Rust code size:M PR is medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants