feat: add dynamic plugin control-plane foundation#279
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Enterprise Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
WalkthroughAdds a dynamic plugin control-plane to ChangesDynamic Plugin Control-Plane
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 13
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/adaptive/src/error.rs`:
- Line 56: The PluginError::Conflict variant is being mapped to semantically
incorrect downstream error types, losing conflict-related information needed for
proper error handling. At crates/adaptive/src/error.rs#L56-L56, change the
mapping from Self::InvalidConfig(message) to a conflict or state-failure
semantic error class such as Self::RegistrationFailed or an equivalent conflict
variant that better represents the duplicate-state nature of the error. At
crates/ffi/src/error.rs#L141-L143, change the mapping from
NemoRelayStatus::InvalidArg to NemoRelayStatus::AlreadyExists (or another
dedicated conflict status variant if available) to accurately represent the
conflict semantics when converting PluginError::Conflict to FFI error codes.
Both changes must preserve and forward the conflict context through the error
boundary mappings.
In `@crates/cli/tests/coverage/plugins_tests.rs`:
- Around line 56-74: The test fixtures and assertions for PII redaction are
using an outdated configuration contract that does not match the canonical
definition in crates/core/src/plugins/nemo_anonymizer/component.rs. Update the
pii_redaction_component_config function at lines 56-74 and all affected test
assertions at lines 194-224, 396-416, 1256-1269, and 1390-1402 to align with the
actual core contract: replace "mode": "builtin" with the correct mode schema
(likely "local"), remove the "builtin" configuration section and replace it with
the structure expected by the nemo_anonymizer component, replace any hardcoded
kind literals with the PII_REDACTION_PLUGIN_KIND constant imported from the core
component, and update all schema validation assertions to reflect the corrected
configuration contract.
In `@crates/core/Cargo.toml`:
- Around line 72-73: Remove the unused `regex` and `sha2` dependencies from the
Cargo.toml file in crates/core. Both dependencies are not used in the codebase:
`regex` only appears in comments and `sha2` has no references anywhere in
crates/core/src/. Delete both dependency declarations entirely to eliminate
unused-dependency warnings and maintain a clean dependency tree.
In `@crates/core/src/plugin/dynamic/manifest.rs`:
- Around line 332-343: Replace the Vec-based duplicate detection in the
reject_duplicate_capabilities function with a HashSet to improve performance
from O(n²) to O(n). Change the seen variable from a Vec to a HashSet, and update
the contains check to use the insert method (which returns false if the element
was already present), allowing you to detect duplicates while building the set
in a single pass.
In `@crates/core/src/plugin/dynamic/registry.rs`:
- Around line 59-60: The stamp_creation_metadata function sets both created_at
and updated_at to the same value, but the immediately following touch_metadata
call overwrites updated_at, creating an unintended microsecond gap for newly
added records. Either remove the touch_metadata call since
stamp_creation_metadata already sets updated_at appropriately, or if this
behavior is intentional (treating add as a distinct mutation), add a clear
comment explaining why both calls are necessary.
In `@crates/core/src/plugins/nemo_anonymizer/component.rs`:
- Around line 634-663: The validate_local_mode_requirements function has two
issues: first, the early return on line 640 when config.local.is_some() prevents
the deprecated builtin check from always being enforced; second, the final
push_policy_diag call on line 656 unconditionally rejects missing local settings
even for non-local modes. Restructure the function to always check for and
reject the deprecated builtin configuration regardless of whether local is
present, then gate the final diagnostic about missing local settings to only
emit when the configured mode is explicitly 'local' (check the policy or config
for the actual mode value to conditionally emit this final diagnostic).
- Around line 756-821: The validation logic contains duplicate diagnostic checks
that fire for the same invalid field states. When local.strategy equals
"substitute", both the first if block (checking strategy == "substitute") and
the second if block (checking strategy != "hash") execute and report identical
errors for local.algorithm and local.digest_length. Remove the duplicate checks
for local.algorithm.is_some() and local.digest_length.is_some() from within the
if local.strategy == "substitute" block, keeping only the check for
local.format_template and local.instructions in that block, since the second if
local.strategy != "hash" block will already catch and report the algorithm and
digest_length validation errors comprehensively.
In `@crates/python/tests/coverage/coverage_tests.rs`:
- Around line 48-55: The fake_guardrails_module_prelude function adds python_dir
to sys.path[0], but the with_isolated_nemo_relay_modules isolation helper only
restores sys.modules, not sys.path. This allows later tests to import from the
source tree instead of the test environment. Modify the
with_isolated_nemo_relay_modules function to also save the original sys.path
before executing the test code and restore it afterward, ensuring sys.path
modifications do not leak between tests.
In `@docs/nemo-anonymizer-plugin/about.mdx`:
- Line 49: On line 49 of the about.mdx file, change the word "builtin" to
"built-in" in the phrase "the previous builtin deterministic lane is no longer
part of this plugin" to maintain consistent technical documentation wording
conventions.
In `@python/nemo_relay/_nemo_anonymizer_local.py`:
- Around line 323-334: The _annotated_message_text function currently joins all
text blocks with newlines, which loses the original block boundaries and causes
content to be truncated or shifted when sanitized text changes newline counts.
Instead of returning a single concatenated string, refactor this function to
return the extracted text as a per-block list structure that preserves each
block's original text separately. Update the callers (the OpenAI Responses and
Anthropic overlay helpers at lines 395-428) to overlay sanitized content by
block index instead of splitting the concatenated string on newlines, ensuring
block boundaries are maintained throughout the sanitization and overlay process.
- Around line 302-308: The function _apply_annotated_request_payload extracts
and restores several fields from the payload back to the request object
(messages, model, params, tools, tool_choice), but it does not restore the
sanitized extra field. Since extra fields are included in
_annotated_request_payload and can contain PII that gets sanitized, you must add
a line to assign the sanitized extra field from the payload back to the request
object using the same pattern as the other field assignments (request.extra =
payload.get("extra")).
- Around line 497-502: The sanitize_request function lacks error handling for
codec decoding failures, allowing decode errors to escape instead of falling
back to raw request sanitization like the response path does. Wrap the
codec.decode(request) call in a try-except block in the sanitize_request
function, and when decoding fails, implement a fallback path that sanitizes the
raw request payload directly using runner.sanitize_json without attempting to
annotate or re-encode it, ensuring PII is sanitized even when the codec fails.
- Around line 8-11: The CSV file handling at line 198 uses NamedTemporaryFile
with delete=True and passes the file path to the anonymizer while the file
handle remains open, causing Windows permission errors. Replace the
NamedTemporaryFile approach with TemporaryDirectory to create a temporary
directory, then write the CSV data to a file within that directory with explicit
UTF-8 encoding. Ensure the file is fully closed before passing its path to the
anonymizer runtime, and properly manage the TemporaryDirectory context to clean
up after the anonymizer completes its work.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Enterprise
Run ID: 1830a9ff-248b-46f1-b26d-aea321b70a8b
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (36)
ATTRIBUTIONS-Rust.mdcrates/adaptive/src/error.rscrates/cli/src/plugins.rscrates/cli/src/plugins/editor_model.rscrates/cli/tests/coverage/plugins_tests.rscrates/core/Cargo.tomlcrates/core/src/api/llm.rscrates/core/src/plugin.rscrates/core/src/plugin/dynamic.rscrates/core/src/plugin/dynamic/manifest.rscrates/core/src/plugin/dynamic/registry.rscrates/core/src/plugins/mod.rscrates/core/src/plugins/nemo_anonymizer/component.rscrates/core/src/plugins/nemo_anonymizer/local.rscrates/core/src/plugins/nemo_anonymizer/mod.rscrates/core/src/plugins/nemo_guardrails/component.rscrates/core/src/plugins/nemo_guardrails/local.rscrates/core/src/stream.rscrates/core/tests/integration/pipeline_tests.rscrates/core/tests/unit/plugin_dynamic_tests.rscrates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rscrates/core/tests/unit/plugins/nemo_guardrails/component_tests.rscrates/ffi/src/error.rscrates/python/src/lib.rscrates/python/src/py_plugin.rscrates/python/tests/coverage/coverage_tests.rscrates/python/tests/coverage/py_plugin_coverage_tests.rsdocs/about-nemo-relay/concepts/plugins.mdxdocs/build-plugins/nemoguardrails.mdxdocs/index.ymldocs/nemo-anonymizer-plugin/about.mdxdocs/nemo-anonymizer-plugin/configuration.mdxdocs/nemo-guardrails-plugin/about.mdxdocs/nemo-guardrails-plugin/configuration.mdxpython/nemo_relay/_guardrails_local.pypython/nemo_relay/_nemo_anonymizer_local.py
💤 Files with no reviewable changes (1)
- docs/build-plugins/nemoguardrails.mdx
📜 Review details
🧰 Additional context used
📓 Path-based instructions (48)
{docs/**,README.md,CONTRIBUTING.md}
📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)
{docs/**,README.md,CONTRIBUTING.md}: For docs-only changes, run targeted checks only if commands, package names, or examples changed. Usejust docsfor docs-site builds andjust docs-linkcheckwhen links changed
Run docs site build withjust docs
Files:
docs/about-nemo-relay/concepts/plugins.mdxdocs/index.ymldocs/nemo-anonymizer-plugin/about.mdxdocs/nemo-guardrails-plugin/about.mdxdocs/nemo-anonymizer-plugin/configuration.mdxdocs/nemo-guardrails-plugin/configuration.mdx
{docs/**,README.md,CONTRIBUTING.md,**/*.md}
📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)
Run docs link validation with
just docs-linkcheckwhen links change
Files:
docs/about-nemo-relay/concepts/plugins.mdxdocs/index.ymldocs/nemo-anonymizer-plugin/about.mdxdocs/nemo-guardrails-plugin/about.mdxdocs/nemo-anonymizer-plugin/configuration.mdxATTRIBUTIONS-Rust.mddocs/nemo-guardrails-plugin/configuration.mdx
{docs/**,README.md}
📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)
Verify README and docs entry points still match current package names and paths for large or public-facing changes
Files:
docs/about-nemo-relay/concepts/plugins.mdxdocs/index.ymldocs/nemo-anonymizer-plugin/about.mdxdocs/nemo-guardrails-plugin/about.mdxdocs/nemo-anonymizer-plugin/configuration.mdxdocs/nemo-guardrails-plugin/configuration.mdx
{docs/**,examples/**,README.md}
📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)
Verify examples still run with documented commands for large or public-facing changes
Files:
docs/about-nemo-relay/concepts/plugins.mdxdocs/index.ymldocs/nemo-anonymizer-plugin/about.mdxdocs/nemo-guardrails-plugin/about.mdxdocs/nemo-anonymizer-plugin/configuration.mdxdocs/nemo-guardrails-plugin/configuration.mdx
{docs/**,README.md,**/Cargo.toml,**/package.json,**/*.md}
📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)
Ensure renamed public surfaces are reflected consistently in manifests and docs for large or public-facing changes
Files:
docs/about-nemo-relay/concepts/plugins.mdxdocs/index.ymldocs/nemo-anonymizer-plugin/about.mdxcrates/core/Cargo.tomldocs/nemo-guardrails-plugin/about.mdxdocs/nemo-anonymizer-plugin/configuration.mdxATTRIBUTIONS-Rust.mddocs/nemo-guardrails-plugin/configuration.mdx
**/*.{md,mdx,py,sh,yaml,yml,toml,json}
📄 CodeRabbit inference engine (.agents/skills/contribute-docs/SKILL.md)
Keep package names, repo references, and build commands current
Files:
docs/about-nemo-relay/concepts/plugins.mdxdocs/index.ymldocs/nemo-anonymizer-plugin/about.mdxcrates/core/Cargo.tomldocs/nemo-guardrails-plugin/about.mdxdocs/nemo-anonymizer-plugin/configuration.mdxpython/nemo_relay/_guardrails_local.pypython/nemo_relay/_nemo_anonymizer_local.pyATTRIBUTIONS-Rust.mddocs/nemo-guardrails-plugin/configuration.mdx
**/*.mdx
📄 CodeRabbit inference engine (.agents/skills/contribute-docs/SKILL.md)
In MDX files, top-of-file comments must use JSX comment delimiters: {/* to open and */} to close. Do not use HTML comments for MDX SPDX headers.
MDX top-of-file SPDX comments must use {/* ... */} delimiters instead of HTML comment delimiters (Must-Fix)
Files:
docs/about-nemo-relay/concepts/plugins.mdxdocs/nemo-anonymizer-plugin/about.mdxdocs/nemo-guardrails-plugin/about.mdxdocs/nemo-anonymizer-plugin/configuration.mdxdocs/nemo-guardrails-plugin/configuration.mdx
**/*.{html,md,mdx}
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Include SPDX license header in HTML and Markdown files using HTML comment syntax
Files:
docs/about-nemo-relay/concepts/plugins.mdxdocs/nemo-anonymizer-plugin/about.mdxdocs/nemo-guardrails-plugin/about.mdxdocs/nemo-anonymizer-plugin/configuration.mdxATTRIBUTIONS-Rust.mddocs/nemo-guardrails-plugin/configuration.mdx
docs/**/*.{md,mdx}
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Update embedded documentation snippets, patch docs, and binding-support notes if examples or supported bindings changed
Files:
docs/about-nemo-relay/concepts/plugins.mdxdocs/nemo-anonymizer-plugin/about.mdxdocs/nemo-guardrails-plugin/about.mdxdocs/nemo-anonymizer-plugin/configuration.mdxdocs/nemo-guardrails-plugin/configuration.mdx
docs/**
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Run
just docsor./scripts/build-docs.sh htmlto regenerate ignored Fern API reference pages before validation for documentation site changes
Files:
docs/about-nemo-relay/concepts/plugins.mdxdocs/index.ymldocs/nemo-anonymizer-plugin/about.mdxdocs/nemo-guardrails-plugin/about.mdxdocs/nemo-anonymizer-plugin/configuration.mdxdocs/nemo-guardrails-plugin/configuration.mdx
{docs/**,README.md,CONTRIBUTING.md,RELEASING.md,SECURITY.md}
⚙️ CodeRabbit configuration file
{docs/**,README.md,CONTRIBUTING.md,RELEASING.md,SECURITY.md}: Review documentation for technical accuracy against the current API, command correctness, and consistency across language bindings.
Flag stale examples, missing SPDX headers where required, and instructions that no longer match CI or pre-commit behavior.
Files:
docs/about-nemo-relay/concepts/plugins.mdxdocs/index.ymldocs/nemo-anonymizer-plugin/about.mdxdocs/nemo-guardrails-plugin/about.mdxdocs/nemo-anonymizer-plugin/configuration.mdxdocs/nemo-guardrails-plugin/configuration.mdx
**
⚙️ CodeRabbit configuration file
**:AGENTS.md
This file provides guidance to agents, including Claude Code and OpenAI Codex, when working in this repository.
Project Overview
NeMo Relay is a multi-language agent runtime framework for execution scopes, lifecycle events, middleware, plugins, and observability around tool and LLM calls. The core runtime is Rust. Primary supported bindings are Rust, Python, and Node.js. Go, WebAssembly, and the raw C FFI are experimental and source-first.
The shared runtime model is:
- Scope stacks decide where work belongs and which scope-local behavior is visible.
- Middleware registries decide what guardrails and intercepts run around managed calls.
- Plugins install reusable runtime behavior from configuration.
- Events record runtime behavior in ATOF form.
- Subscribers and exporters consume events in-process or export them to ATIF, OpenTelemetry, OpenInference, or other backends.
Repository Structure
The repository layout separates the Rust runtime, language bindings, documentation,
integration patches, and agent-facing skills.crates/ core/ # Rust core runtime crate, published as nemo-relay adaptive/ # Adaptive runtime primitives and plugin components python/ # PyO3 native extension for the Python package ffi/ # Raw C ABI layer used by downstream bindings such as Go node/ # NAPI Node.js binding and JavaScript/TypeScript entry points wasm/ # wasm-bindgen WebAssembly binding and JS wrappers python/ nemo_relay/ # Python wrapper package: scopes, tools, LLM, middleware, typed helpers, plugins, adaptive helpers tests/ # Python tests go/ nemo_relay/ # Experimental Go CGo binding and tests fern/ # Fern documentation site scripts/ # Stable wrappers and helper scripts; build/test/docs entry points live in justfile third_party/ # P...
Files:
docs/about-nemo-relay/concepts/plugins.mdxcrates/core/src/plugins/mod.rsdocs/index.ymlcrates/cli/src/plugins.rscrates/ffi/src/error.rsdocs/nemo-anonymizer-plugin/about.mdxcrates/core/src/plugin.rscrates/core/Cargo.tomlcrates/core/tests/integration/pipeline_tests.rscrates/adaptive/src/error.rscrates/python/tests/coverage/py_plugin_coverage_tests.rscrates/core/src/plugins/nemo_anonymizer/local.rsdocs/nemo-guardrails-plugin/about.mdxdocs/nemo-anonymizer-plugin/configuration.mdxcrates/core/tests/unit/plugins/nemo_guardrails/component_tests.rscrates/core/src/plugins/nemo_guardrails/component.rscrates/core/src/stream.rscrates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rscrates/python/src/py_plugin.rscrates/core/src/plugins/nemo_guardrails/local.rscrates/core/src/plugins/nemo_anonymizer/mod.rscrates/core/src/plugin/dynamic/registry.rscrates/python/tests/coverage/coverage_tests.rscrates/core/tests/unit/plugin_dynamic_tests.rscrates/python/src/lib.rscrates/core/src/api/llm.rspython/nemo_relay/_guardrails_local.pycrates/core/src/plugin/dynamic/manifest.rscrates/cli/tests/coverage/plugins_tests.rspython/nemo_relay/_nemo_anonymizer_local.pycrates/core/src/plugins/nemo_anonymizer/component.rsATTRIBUTIONS-Rust.mddocs/nemo-guardrails-plugin/configuration.mdxcrates/cli/src/plugins/editor_model.rscrates/core/src/plugin/dynamic.rs
**/*.rs
📄 CodeRabbit inference engine (.agents/skills/add-binding-feature/SKILL.md)
Use
snake_casenaming convention for Rust identifiers (e.g.,nemo_relay_tool_call)
**/*.rs: Any Rust change must runjust test-rust
Any Rust change must runcargo fmt --all
Any Rust change must runcargo clippy --workspace --all-targets -- -D warnings
**/*.rs: Runcargo fmt --allfor all FFI work since it is Rust work
Runjust test-rustto validate FFI changes
Runcargo clippy --workspace --all-targets -- -D warningsto enforce strict linting on FFI workWhen Rust files changed as part of Go work, also run
cargo fmt --all,just test-rust, andcargo clippy --workspace --all-targets -- -D warnings
**/*.rs: Runcargo fmt --allwhen Rust files are changed as part of Node work
Runcargo clippy --workspace --all-targets -- -D warningswhen Rust files are changed as part of Node work
Runjust test-rustwhen Rust files are changed as part of Node work
**/*.rs: Runcargo fmt --allto format all Rust code
Runcargo clippy --workspace --all-targets -- -D warningsto enforce all clippy lints as errors
**/*.rs: Runcargo fmt --allwhen Rust files changed as part of WebAssembly work
Runcargo clippy --workspace --all-targets -- -D warningswhen Rust files changed as part of WebAssembly work
**/*.rs: If any Rust code changed, always runjust test-rust
If any Rust code changed, also runcargo fmt --all
If any Rust code changed, also runcargo clippy --workspace --all-targets -- -D warnings
Run Rust formatting withcargo fmt --all
Run Rust linting withcargo clippy --workspace --all-targets -- -D warnings
**/*.rs: Usecargo fmtfor Rust code formatting
Runcargo clippy -- -D warningsto 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 withuv run pre-commit run --all-filesto enforce cargo fmt formatting check, cargo clippy lints, and cargo deny aud...
Files:
crates/core/src/plugins/mod.rscrates/cli/src/plugins.rscrates/ffi/src/error.rscrates/core/src/plugin.rscrates/core/tests/integration/pipeline_tests.rscrates/adaptive/src/error.rscrates/python/tests/coverage/py_plugin_coverage_tests.rscrates/core/src/plugins/nemo_anonymizer/local.rscrates/core/tests/unit/plugins/nemo_guardrails/component_tests.rscrates/core/src/plugins/nemo_guardrails/component.rscrates/core/src/stream.rscrates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rscrates/python/src/py_plugin.rscrates/core/src/plugins/nemo_guardrails/local.rscrates/core/src/plugins/nemo_anonymizer/mod.rscrates/core/src/plugin/dynamic/registry.rscrates/python/tests/coverage/coverage_tests.rscrates/core/tests/unit/plugin_dynamic_tests.rscrates/python/src/lib.rscrates/core/src/api/llm.rscrates/core/src/plugin/dynamic/manifest.rscrates/cli/tests/coverage/plugins_tests.rscrates/core/src/plugins/nemo_anonymizer/component.rscrates/cli/src/plugins/editor_model.rscrates/core/src/plugin/dynamic.rs
**/{Cargo.toml,**/*.rs}
📄 CodeRabbit inference engine (.agents/skills/maintain-packaging/SKILL.md)
Maintain consistency between Rust package names in
Cargo.tomland their actual usage across the codebase
Files:
crates/core/src/plugins/mod.rscrates/cli/src/plugins.rscrates/ffi/src/error.rscrates/core/src/plugin.rscrates/core/Cargo.tomlcrates/core/tests/integration/pipeline_tests.rscrates/adaptive/src/error.rscrates/python/tests/coverage/py_plugin_coverage_tests.rscrates/core/src/plugins/nemo_anonymizer/local.rscrates/core/tests/unit/plugins/nemo_guardrails/component_tests.rscrates/core/src/plugins/nemo_guardrails/component.rscrates/core/src/stream.rscrates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rscrates/python/src/py_plugin.rscrates/core/src/plugins/nemo_guardrails/local.rscrates/core/src/plugins/nemo_anonymizer/mod.rscrates/core/src/plugin/dynamic/registry.rscrates/python/tests/coverage/coverage_tests.rscrates/core/tests/unit/plugin_dynamic_tests.rscrates/python/src/lib.rscrates/core/src/api/llm.rscrates/core/src/plugin/dynamic/manifest.rscrates/cli/tests/coverage/plugins_tests.rscrates/core/src/plugins/nemo_anonymizer/component.rscrates/cli/src/plugins/editor_model.rscrates/core/src/plugin/dynamic.rs
**/*.{h,hpp,c,cpp,rs}
📄 CodeRabbit inference engine (.agents/skills/maintain-packaging/SKILL.md)
Ensure FFI header and library naming follows consistent conventions across platform-specific builds
Files:
crates/core/src/plugins/mod.rscrates/cli/src/plugins.rscrates/ffi/src/error.rscrates/core/src/plugin.rscrates/core/tests/integration/pipeline_tests.rscrates/adaptive/src/error.rscrates/python/tests/coverage/py_plugin_coverage_tests.rscrates/core/src/plugins/nemo_anonymizer/local.rscrates/core/tests/unit/plugins/nemo_guardrails/component_tests.rscrates/core/src/plugins/nemo_guardrails/component.rscrates/core/src/stream.rscrates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rscrates/python/src/py_plugin.rscrates/core/src/plugins/nemo_guardrails/local.rscrates/core/src/plugins/nemo_anonymizer/mod.rscrates/core/src/plugin/dynamic/registry.rscrates/python/tests/coverage/coverage_tests.rscrates/core/tests/unit/plugin_dynamic_tests.rscrates/python/src/lib.rscrates/core/src/api/llm.rscrates/core/src/plugin/dynamic/manifest.rscrates/cli/tests/coverage/plugins_tests.rscrates/core/src/plugins/nemo_anonymizer/component.rscrates/cli/src/plugins/editor_model.rscrates/core/src/plugin/dynamic.rs
{crates/core,crates/adaptive}/**/*
📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)
Changes to
crates/coreorcrates/adaptivemust run the full language matrix
Files:
crates/core/src/plugins/mod.rscrates/core/src/plugin.rscrates/core/Cargo.tomlcrates/core/tests/integration/pipeline_tests.rscrates/adaptive/src/error.rscrates/core/src/plugins/nemo_anonymizer/local.rscrates/core/tests/unit/plugins/nemo_guardrails/component_tests.rscrates/core/src/plugins/nemo_guardrails/component.rscrates/core/src/stream.rscrates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rscrates/core/src/plugins/nemo_guardrails/local.rscrates/core/src/plugins/nemo_anonymizer/mod.rscrates/core/src/plugin/dynamic/registry.rscrates/core/tests/unit/plugin_dynamic_tests.rscrates/core/src/api/llm.rscrates/core/src/plugin/dynamic/manifest.rscrates/core/src/plugins/nemo_anonymizer/component.rscrates/core/src/plugin/dynamic.rs
**/*.{rs,toml}
📄 CodeRabbit inference engine (.agents/skills/rename-surfaces/SKILL.md)
Update Rust crate names and module prefixes during coordinated rename operations
Files:
crates/core/src/plugins/mod.rscrates/cli/src/plugins.rscrates/ffi/src/error.rscrates/core/src/plugin.rscrates/core/Cargo.tomlcrates/core/tests/integration/pipeline_tests.rscrates/adaptive/src/error.rscrates/python/tests/coverage/py_plugin_coverage_tests.rscrates/core/src/plugins/nemo_anonymizer/local.rscrates/core/tests/unit/plugins/nemo_guardrails/component_tests.rscrates/core/src/plugins/nemo_guardrails/component.rscrates/core/src/stream.rscrates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rscrates/python/src/py_plugin.rscrates/core/src/plugins/nemo_guardrails/local.rscrates/core/src/plugins/nemo_anonymizer/mod.rscrates/core/src/plugin/dynamic/registry.rscrates/python/tests/coverage/coverage_tests.rscrates/core/tests/unit/plugin_dynamic_tests.rscrates/python/src/lib.rscrates/core/src/api/llm.rscrates/core/src/plugin/dynamic/manifest.rscrates/cli/tests/coverage/plugins_tests.rscrates/core/src/plugins/nemo_anonymizer/component.rscrates/cli/src/plugins/editor_model.rscrates/core/src/plugin/dynamic.rs
crates/core/**/*.rs
📄 CodeRabbit inference engine (.agents/skills/test-go-binding/SKILL.md)
If the change touched
crates/coreor shared runtime semantics, also usevalidate-changefor broader validation
crates/core/**/*.rs: UseJson = serde_json::Valuein Rust-facing runtime APIs where the existing code expects JSON payloads.
UseResult<T>withFlowErrorin core runtime paths. Keep errors explicit and binding-appropriate at the wrapper layer.
Files:
crates/core/src/plugins/mod.rscrates/core/src/plugin.rscrates/core/tests/integration/pipeline_tests.rscrates/core/src/plugins/nemo_anonymizer/local.rscrates/core/tests/unit/plugins/nemo_guardrails/component_tests.rscrates/core/src/plugins/nemo_guardrails/component.rscrates/core/src/stream.rscrates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rscrates/core/src/plugins/nemo_guardrails/local.rscrates/core/src/plugins/nemo_anonymizer/mod.rscrates/core/src/plugin/dynamic/registry.rscrates/core/tests/unit/plugin_dynamic_tests.rscrates/core/src/api/llm.rscrates/core/src/plugin/dynamic/manifest.rscrates/core/src/plugins/nemo_anonymizer/component.rscrates/core/src/plugin/dynamic.rs
crates/{core,adaptive}/**
📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)
If
crates/coreorcrates/adaptivechanged, run the full matrix across Rust, Python, Go, Node.js, and WebAssembly
Files:
crates/core/src/plugins/mod.rscrates/core/src/plugin.rscrates/core/Cargo.tomlcrates/core/tests/integration/pipeline_tests.rscrates/adaptive/src/error.rscrates/core/src/plugins/nemo_anonymizer/local.rscrates/core/tests/unit/plugins/nemo_guardrails/component_tests.rscrates/core/src/plugins/nemo_guardrails/component.rscrates/core/src/stream.rscrates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rscrates/core/src/plugins/nemo_guardrails/local.rscrates/core/src/plugins/nemo_anonymizer/mod.rscrates/core/src/plugin/dynamic/registry.rscrates/core/tests/unit/plugin_dynamic_tests.rscrates/core/src/api/llm.rscrates/core/src/plugin/dynamic/manifest.rscrates/core/src/plugins/nemo_anonymizer/component.rscrates/core/src/plugin/dynamic.rs
**/*.{rs,py,js,ts,tsx,jsx,go,sh,toml,yaml,yml,md}
📄 CodeRabbit inference engine (AGENTS.md)
Keep SPDX headers on source, docs, scripts, and configuration files. The project is Apache-2.0.
Files:
crates/core/src/plugins/mod.rsdocs/index.ymlcrates/cli/src/plugins.rscrates/ffi/src/error.rscrates/core/src/plugin.rscrates/core/Cargo.tomlcrates/core/tests/integration/pipeline_tests.rscrates/adaptive/src/error.rscrates/python/tests/coverage/py_plugin_coverage_tests.rscrates/core/src/plugins/nemo_anonymizer/local.rscrates/core/tests/unit/plugins/nemo_guardrails/component_tests.rscrates/core/src/plugins/nemo_guardrails/component.rscrates/core/src/stream.rscrates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rscrates/python/src/py_plugin.rscrates/core/src/plugins/nemo_guardrails/local.rscrates/core/src/plugins/nemo_anonymizer/mod.rscrates/core/src/plugin/dynamic/registry.rscrates/python/tests/coverage/coverage_tests.rscrates/core/tests/unit/plugin_dynamic_tests.rscrates/python/src/lib.rscrates/core/src/api/llm.rspython/nemo_relay/_guardrails_local.pycrates/core/src/plugin/dynamic/manifest.rscrates/cli/tests/coverage/plugins_tests.rspython/nemo_relay/_nemo_anonymizer_local.pycrates/core/src/plugins/nemo_anonymizer/component.rsATTRIBUTIONS-Rust.mdcrates/cli/src/plugins/editor_model.rscrates/core/src/plugin/dynamic.rs
**/*.{rs,py,go,js,ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Follow binding naming conventions: Rust and Python use
snake_case, C FFI exports prefixednemo_relay_, Go usesPascalCasefor public APIs, Node.js usescamelCase.
Files:
crates/core/src/plugins/mod.rscrates/cli/src/plugins.rscrates/ffi/src/error.rscrates/core/src/plugin.rscrates/core/tests/integration/pipeline_tests.rscrates/adaptive/src/error.rscrates/python/tests/coverage/py_plugin_coverage_tests.rscrates/core/src/plugins/nemo_anonymizer/local.rscrates/core/tests/unit/plugins/nemo_guardrails/component_tests.rscrates/core/src/plugins/nemo_guardrails/component.rscrates/core/src/stream.rscrates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rscrates/python/src/py_plugin.rscrates/core/src/plugins/nemo_guardrails/local.rscrates/core/src/plugins/nemo_anonymizer/mod.rscrates/core/src/plugin/dynamic/registry.rscrates/python/tests/coverage/coverage_tests.rscrates/core/tests/unit/plugin_dynamic_tests.rscrates/python/src/lib.rscrates/core/src/api/llm.rspython/nemo_relay/_guardrails_local.pycrates/core/src/plugin/dynamic/manifest.rscrates/cli/tests/coverage/plugins_tests.rspython/nemo_relay/_nemo_anonymizer_local.pycrates/core/src/plugins/nemo_anonymizer/component.rscrates/cli/src/plugins/editor_model.rscrates/core/src/plugin/dynamic.rs
crates/**/*.rs
📄 CodeRabbit inference engine (AGENTS.md)
crates/**/*.rs: Keep async behavior on the existing tokio-based model. Bindings should preserve callback and future lifetimes rather than blocking or hiding async work unexpectedly.
UseJson = serde_json::Valuein Rust-facing runtime APIs for JSON payload handling.
Files:
crates/core/src/plugins/mod.rscrates/cli/src/plugins.rscrates/ffi/src/error.rscrates/core/src/plugin.rscrates/core/tests/integration/pipeline_tests.rscrates/adaptive/src/error.rscrates/python/tests/coverage/py_plugin_coverage_tests.rscrates/core/src/plugins/nemo_anonymizer/local.rscrates/core/tests/unit/plugins/nemo_guardrails/component_tests.rscrates/core/src/plugins/nemo_guardrails/component.rscrates/core/src/stream.rscrates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rscrates/python/src/py_plugin.rscrates/core/src/plugins/nemo_guardrails/local.rscrates/core/src/plugins/nemo_anonymizer/mod.rscrates/core/src/plugin/dynamic/registry.rscrates/python/tests/coverage/coverage_tests.rscrates/core/tests/unit/plugin_dynamic_tests.rscrates/python/src/lib.rscrates/core/src/api/llm.rscrates/core/src/plugin/dynamic/manifest.rscrates/cli/tests/coverage/plugins_tests.rscrates/core/src/plugins/nemo_anonymizer/component.rscrates/cli/src/plugins/editor_model.rscrates/core/src/plugin/dynamic.rs
crates/{core,adaptive}/**/*.rs
⚙️ CodeRabbit configuration file
crates/{core,adaptive}/**/*.rs: Review the Rust runtime for async correctness, scope isolation, middleware ordering, and event lifecycle regressions.
Pay close attention to task-local/thread-local scope propagation, callback lifetimes, stream finalization, and root_uuid isolation.
Public API changes should preserve existing behavior unless tests and docs show the intended migration path.
Files:
crates/core/src/plugins/mod.rscrates/core/src/plugin.rscrates/core/tests/integration/pipeline_tests.rscrates/adaptive/src/error.rscrates/core/src/plugins/nemo_anonymizer/local.rscrates/core/tests/unit/plugins/nemo_guardrails/component_tests.rscrates/core/src/plugins/nemo_guardrails/component.rscrates/core/src/stream.rscrates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rscrates/core/src/plugins/nemo_guardrails/local.rscrates/core/src/plugins/nemo_anonymizer/mod.rscrates/core/src/plugin/dynamic/registry.rscrates/core/tests/unit/plugin_dynamic_tests.rscrates/core/src/api/llm.rscrates/core/src/plugin/dynamic/manifest.rscrates/core/src/plugins/nemo_anonymizer/component.rscrates/core/src/plugin/dynamic.rs
**/*.{py,txt,toml,cfg,yaml,yml}
📄 CodeRabbit inference engine (.agents/skills/rename-surfaces/SKILL.md)
Update Python package names and top-level module imports during coordinated rename operations
Files:
docs/index.ymlcrates/core/Cargo.tomlpython/nemo_relay/_guardrails_local.pypython/nemo_relay/_nemo_anonymizer_local.py
**/{docs,examples,**/*.md,*.patch,*.diff,.github,*.sh,*.yaml,*.yml}
📄 CodeRabbit inference engine (.agents/skills/rename-surfaces/SKILL.md)
Update documentation, examples, CI configuration, and patch artifacts when performing rename operations
Files:
docs/index.ymlATTRIBUTIONS-Rust.md
crates/ffi/**
📄 CodeRabbit inference engine (.agents/skills/test-ffi-surface/SKILL.md)
Rebuild the FFI crate in release mode so the shared library and header stay in sync when making changes to crates/ffi
Files:
crates/ffi/src/error.rs
crates/ffi/**/*.rs
📄 CodeRabbit inference engine (.agents/skills/test-go-binding/SKILL.md)
If the change touched
crates/ffi, also usetest-ffi-surfacefor validation
Files:
crates/ffi/src/error.rs
crates/{python,ffi,node,wasm}/**/*
⚙️ CodeRabbit configuration file
crates/{python,ffi,node,wasm}/**/*: Treat binding changes as public API changes. Check for parity with the other language bindings, FFI ownership/lifetime safety,
callback error propagation, stable type conversion, and consistent async/stream semantics.
Flag changes that update one binding without corresponding tests or documentation for the same surface elsewhere.
Files:
crates/ffi/src/error.rscrates/python/tests/coverage/py_plugin_coverage_tests.rscrates/python/src/py_plugin.rscrates/python/tests/coverage/coverage_tests.rscrates/python/src/lib.rs
**/Cargo.toml
📄 CodeRabbit inference engine (.agents/skills/rename-surfaces/SKILL.md)
Update WebAssembly crate names and generated package names during coordinated rename operations
Confirm or infer the target release version from
upstream/main:Cargo.toml. Derive the release branch asrelease/<major>.<minor>.
**/Cargo.toml: MaintainCargo.toml[workspace.package].versionas the source of truth for the Rust workspace and Python build versioning
KeepCargo.toml[workspace.dependencies]self-references aligned with the workspace version when the workspace version changes
After updating workspace package entries, runcargo check --workspaceto refreshCargo.lock
Files:
crates/core/Cargo.toml
**/*.toml
📄 CodeRabbit inference engine (CONTRIBUTING.md)
Include SPDX license header in TOML configuration files using hash comment syntax
Files:
crates/core/Cargo.toml
{crates/adaptive/**/*.rs,**/*test*.{rs,py,go,ts,js},**/*adaptive*test*.{rs,py,go,ts,js},docs/plugins/adaptive/**}
📄 CodeRabbit inference engine (.agents/skills/maintain-optimizer/SKILL.md)
Maintain documented and tested validation and report behavior for adaptive surfaces
Files:
crates/core/tests/integration/pipeline_tests.rscrates/adaptive/src/error.rscrates/python/tests/coverage/py_plugin_coverage_tests.rscrates/core/tests/unit/plugins/nemo_guardrails/component_tests.rscrates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rscrates/python/tests/coverage/coverage_tests.rscrates/core/tests/unit/plugin_dynamic_tests.rscrates/cli/tests/coverage/plugins_tests.rs
{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}
⚙️ CodeRabbit configuration file
{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}: Tests should cover the behavior promised by the changed API surface, including error paths and cross-request isolation where relevant.
Prefer assertions on lifecycle events, scope stacks, middleware ordering, and binding parity over shallow smoke tests.
Files:
crates/core/tests/integration/pipeline_tests.rscrates/python/tests/coverage/py_plugin_coverage_tests.rscrates/core/tests/unit/plugins/nemo_guardrails/component_tests.rscrates/core/tests/unit/plugins/nemo_anonymizer/component_tests.rscrates/python/tests/coverage/coverage_tests.rscrates/core/tests/unit/plugin_dynamic_tests.rscrates/cli/tests/coverage/plugins_tests.rs
{crates/adaptive/**,python/nemo_relay/adaptive.py,python/nemo_relay/plugin.py,go/nemo_relay/adaptive/**,go/nemo_relay/!(adaptive)/**,**/node/**,**/wasm/**}
📄 CodeRabbit inference engine (.agents/skills/maintain-optimizer/SKILL.md)
Keep adaptive surface in sync across crates/adaptive, shared plugin behavior in core and bindings, Python adaptive/plugin wrappers in python/nemo_relay/adaptive.py and python/nemo_relay/plugin.py, Go adaptive helpers under go/nemo_relay/adaptive plus shared plugin helpers in go/nemo_relay, and Node/WebAssembly adaptive helpers and plugin wrappers
Files:
crates/adaptive/src/error.rs
{crates/adaptive/**,python/nemo_relay/plugin.py,go/nemo_relay/**,**/node/**,**/wasm/**}
📄 CodeRabbit inference engine (.agents/skills/maintain-optimizer/SKILL.md)
{crates/adaptive/**,python/nemo_relay/plugin.py,go/nemo_relay/**,**/node/**,**/wasm/**}: Maintain consistent plugin lifecycle across all language bindings (Python, Go, Node/WebAssembly, and Rust)
Keep plugin context surfaces aligned across all language implementations
Files:
crates/adaptive/src/error.rs
crates/python/**/*.rs
📄 CodeRabbit inference engine (.agents/skills/test-python-binding/SKILL.md)
If the native Rust bridge changed, add the Rust crate tests for
nemo-relay-python
Files:
crates/python/tests/coverage/py_plugin_coverage_tests.rscrates/python/src/py_plugin.rscrates/python/tests/coverage/coverage_tests.rscrates/python/src/lib.rs
crates/core/src/api/**/*.rs
📄 CodeRabbit inference engine (.agents/skills/add-binding-feature/SKILL.md)
Implement behavior first in Rust core API modules:
crates/core/src/api/and related core modules such ascrates/core/src/api/runtime/,crates/core/src/codec/, orcrates/core/src/json.rs
Files:
crates/core/src/api/llm.rs
crates/core/src/api/{tool,llm}.rs
📄 CodeRabbit inference engine (.agents/skills/add-middleware/SKILL.md)
Wire the new middleware chain into the execute path in
crates/core/src/api/tool.rsorcrates/core/src/api/llm.rsat the appropriate pipeline stage
Files:
crates/core/src/api/llm.rs
{crates/python/src/py_api/**/*.rs,python/nemo_relay/**/*.py,python/nemo_relay/**/*.pyi}
📄 CodeRabbit inference engine (.agents/skills/add-binding-feature/SKILL.md)
Update Python native binding in
crates/python/src/py_api/mod.rswith Python wrapper docstring inpython/nemo_relay/<module>.pyand type stubs inpython/nemo_relay/*.pyimodules
Files:
python/nemo_relay/_guardrails_local.pypython/nemo_relay/_nemo_anonymizer_local.py
python/nemo_relay/**/*.py
📄 CodeRabbit inference engine (.agents/skills/add-binding-feature/SKILL.md)
Use
snake_casenaming convention for Python identifiers (e.g.,nemo_relay.tools.call)Format changed Python wrapper and test files with
uv run ruff format pythonPython wrapper modules live under
python/nemo_relay/; the native extension is built fromcrates/pythonwithmaturin.
Files:
python/nemo_relay/_guardrails_local.pypython/nemo_relay/_nemo_anonymizer_local.py
{pyproject.toml,**/*.py}
📄 CodeRabbit inference engine (.agents/skills/maintain-packaging/SKILL.md)
Maintain consistency between Python package names in
pyproject.tomland import paths used throughout the codebase
Files:
python/nemo_relay/_guardrails_local.pypython/nemo_relay/_nemo_anonymizer_local.py
**/*.py
📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)
**/*.py: Run Python formatting withuv run ruff format python
Run Python testing withuv 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
Runtyfor 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 withuv run pre-commit run --all-filesto enforce Ruff linting and formatting, and ty type checking
Files:
python/nemo_relay/_guardrails_local.pypython/nemo_relay/_nemo_anonymizer_local.py
python/nemo_relay/**/*
⚙️ CodeRabbit configuration file
python/nemo_relay/**/*: Review Python wrapper changes for typed API consistency, contextvars-based scope isolation, async behavior, and parity with the native extension.
Stubs and runtime implementations should stay aligned.
Files:
python/nemo_relay/_guardrails_local.pypython/nemo_relay/_nemo_anonymizer_local.py
**/*.{md,rst,html,txt}
📄 CodeRabbit inference engine (.agents/skills/review-doc-style/assets/nvidia-style-brand-terminology.md)
**/*.{md,rst,html,txt}: Always spellNVIDIAin all caps. Do not useNvidia,nvidia,nVidia,nVIDIA, orNV.
Usean NVIDIAbefore a noun because the name starts with an 'en' sound.
Do not add a registered trademark symbol afterNVIDIAwhen 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 withNVIDIAon 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 withs, not an apostrophe, such asGPUs.
In headings, common acronyms can remain abbreviated. Spell out the term in the first or second sentence of the body.
Common terms such asCPU,GPU,PC,API, andUIusually do not need to be spelled out for developer audiences.
Files:
ATTRIBUTIONS-Rust.md
**/*.{md,rst,html}
📄 CodeRabbit inference engine (.agents/skills/review-doc-style/assets/nvidia-style-brand-terminology.md)
Link the first mention of a product name when the destination helps the reader.
Files:
ATTRIBUTIONS-Rust.md
**/*.md
📄 CodeRabbit inference engine (.agents/skills/contribute-integration/SKILL.md)
Documentation must be updated if activation or usage changed
**/*.md: Use title case consistently in technical documentation headings
Avoid quotation marks, ampersands, and exclamation marks in headings
Keep product, event, research, and whitepaper names in their official title case
Use title case for table headers
Do not force social-media sentence case into technical docs
Format code elements, commands, parameters, package names, and expressions in monospace
Format directories, file names, and paths in monospace using backticks
Use angle brackets inside monospace for variables inside paths, such as/home/<username>/.login
Format error messages and strings in quotation marks, keeping literal code strings in code formatting when clearer
Format UI buttons, menus, fields, and labels in bold
Use angle brackets between UI labels for menu paths, such as File > Save As
Use italics for new terms on first use, sparingly and only when introducing the term
Use italics for publication titles
Format keyboard shortcuts in plain text, such as Press Ctrl+Alt+Delete
Use owner/repo link text for GitHub repositories, preferring[NVIDIA/NeMo](link)over prose references like 'the GitHub repo'
Introduce every code block with a complete sentence
Do not make a code block complete the grammar of the previous sentence
Do not continue a sentence after a code block
Use syntax highlighting when the format supports it for code blocks
Avoid the word 'snippet' unless the surrounding docs already use it as a term of art
Keep inline method, function, and class references consistent with nearby docs, omitting empty parentheses for prose readability when no call is shown
Use descriptive anchor text that matches the destination title when possible for links
Avoid raw URLs in running text
Avoid generic anchor text such as 'here,' 'this page,' and 'read more'
Include acronyms in link text when a linked term includes an acronym
Do not link long sentences or multiple sentences
Avoid links ...
Files:
ATTRIBUTIONS-Rust.md
**/*.{md,rst,txt}
📄 CodeRabbit inference engine (.agents/skills/review-doc-style/assets/nvidia-style-guide.md)
Spell
NVIDIAin all caps. Do not useNvidia,nvidia, orNV.
Files:
ATTRIBUTIONS-Rust.md
**/*.{md,rst}
📄 CodeRabbit inference engine (.agents/skills/review-doc-style/assets/nvidia-style-guide.md)
**/*.{md,rst}: Format commands, code elements, expressions, package names, file names, and paths as inline code.
Use descriptive link text. Avoid raw URLs and weak anchors such as "here" or "read more."
Use title case consistently for technical documentation headings.
Introduce code blocks, lists, tables, and images with complete sentences.
Write procedures as imperative steps. Keep steps parallel and split long procedures into smaller tasks.
Prefer active voice, present tense, short sentences, contractions, and plain English.
Usecanfor possibility and reservemayfor permission.
Useafterfor temporal relationships instead ofonce.
Preferrefer tooverseewhen the wording points readers to another resource.
Avoid culture-specific idioms, unnecessary Latinisms, jokes, and marketing exaggeration in technical docs.
Spell out months in body text, avoid ordinal dates, and use clear time zones.
Spell out whole numbers from zero through nine unless they are technical values, parameters, versions, or UI values.
Use numerals for 10 or greater and include commas in thousands.
Do not add trademark symbols to learning-oriented docs unless the source, platform, or legal guidance explicitly requires them.
Files:
ATTRIBUTIONS-Rust.md
ATTRIBUTIONS-Rust.md
📄 CodeRabbit inference engine (.agents/skills/update-project-version/SKILL.md)
Regenerate
ATTRIBUTIONS-Rust.mdwith./scripts/generate_attributions.sh rustif Cargo metadata changed and committed attribution files must stay fresh
Files:
ATTRIBUTIONS-Rust.md
🪛 ast-grep (0.43.0)
python/nemo_relay/_guardrails_local.py
[info] 138-145: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"tool_name": name,
"arguments": args,
},
sort_keys=True,
separators=(",", ":"),
)
Note: Security best practice.
(use-jsonify)
[info] 149-157: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{
"tool_name": name,
"arguments": args,
"result": result,
},
sort_keys=True,
separators=(",", ":"),
)
Note: Security best practice.
(use-jsonify)
python/nemo_relay/_nemo_anonymizer_local.py
[info] 359-359: use jsonify instead of json.dumps for JSON output
Context: json.dumps(sanitized_call["arguments"], separators=(",", ":"))
Note: Security best practice.
(use-jsonify)
[info] 375-375: use jsonify instead of json.dumps for JSON output
Context: json.dumps(sanitized_call["arguments"], separators=(",", ":"))
Note: Security best practice.
(use-jsonify)
🪛 LanguageTool
docs/nemo-anonymizer-plugin/about.mdx
[grammar] ~49-~49: Ensure spelling is correct
Context: ...ackend. In particular: - the previous builtin deterministic lane is no longer part of...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
docs/nemo-guardrails-plugin/configuration.mdx
[style] ~294-~294: To form a complete sentence, be sure to include a subject.
Context: ...g_yamlis required. -colang_contentcan only be used withconfig_yaml. - rem...
(MISSING_IT_THERE)
🪛 markdownlint-cli2 (0.22.1)
ATTRIBUTIONS-Rust.md
[warning] 3127-3127: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Above
(MD022, blanks-around-headings)
[warning] 3127-3127: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 7481-7481: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 7484-7484: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Above
(MD022, blanks-around-headings)
[warning] 7484-7484: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 7485-7485: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
[warning] 7485-7485: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 8111-8111: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Above
(MD022, blanks-around-headings)
[warning] 8111-8111: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 8768-8768: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Above
(MD022, blanks-around-headings)
[warning] 8768-8768: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 32177-32177: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 32180-32180: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Above
(MD022, blanks-around-headings)
[warning] 32180-32180: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 32181-32181: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
[warning] 32181-32181: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
[warning] 39251-39251: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Above
(MD022, blanks-around-headings)
[warning] 39251-39251: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
🪛 Ruff (0.15.17)
python/nemo_relay/_guardrails_local.py
[warning] 75-78: Avoid specifying long messages outside the exception class
(TRY003)
[warning] 79-82: Avoid specifying long messages outside the exception class
(TRY003)
[warning] 92-92: Dynamically typed expressions (typing.Any) are disallowed in status
(ANN401)
[warning] 96-96: Dynamically typed expressions (typing.Any) are disallowed in annotated
(ANN401)
[warning] 101-101: Dynamically typed expressions (typing.Any) are disallowed in rails
(ANN401)
[warning] 102-102: Dynamically typed expressions (typing.Any) are disallowed in rail_type
(ANN401)
[warning] 103-103: Dynamically typed expressions (typing.Any) are disallowed in rail_status
(ANN401)
[warning] 131-135: Avoid specifying long messages outside the exception class
(TRY003)
[warning] 165-169: Avoid specifying long messages outside the exception class
(TRY003)
[warning] 172-176: Avoid specifying long messages outside the exception class
(TRY003)
[warning] 180-180: Dynamically typed expressions (typing.Any) are disallowed in result
(ANN401)
[warning] 183-189: Avoid specifying long messages outside the exception class
(TRY003)
[warning] 193-193: Dynamically typed expressions (typing.Any) are disallowed in rails
(ANN401)
[warning] 194-194: Dynamically typed expressions (typing.Any) are disallowed in rail_type
(ANN401)
[warning] 195-195: Dynamically typed expressions (typing.Any) are disallowed in rail_status
(ANN401)
[warning] 211-211: Dynamically typed expressions (typing.Any) are disallowed in rails
(ANN401)
[warning] 215-215: Dynamically typed expressions (typing.Any) are disallowed in rails
(ANN401)
[warning] 215-215: Dynamically typed expressions (typing.Any) are disallowed in _output_streaming_config
(ANN401)
[warning] 219-219: Dynamically typed expressions (typing.Any) are disallowed in rails
(ANN401)
[warning] 224-224: Too many return statements (10 > 6)
(PLR0911)
[warning] 224-224: Too many branches (13 > 12)
(PLR0912)
[warning] 280-280: Missing return type annotation for private function _queue_string_stream
(ANN202)
[warning] 280-280: Remove quotes from type annotation
Remove quotes
(UP037)
[warning] 290-290: Dynamically typed expressions (typing.Any) are disallowed in rails
(ANN401)
[warning] 292-292: Remove quotes from type annotation
Remove quotes
(UP037)
[warning] 309-313: Avoid specifying long messages outside the exception class
(TRY003)
[warning] 316-316: Dynamically typed expressions (typing.Any) are disallowed in rails_config_cls
(ANN401)
[warning] 316-316: Dynamically typed expressions (typing.Any) are disallowed in _build_guardrails_config
(ANN401)
[warning] 328-328: Avoid specifying long messages outside the exception class
(TRY003)
[warning] 333-333: Dynamically typed expressions (typing.Any) are disallowed in rails
(ANN401)
[warning] 334-334: Dynamically typed expressions (typing.Any) are disallowed in rail_type
(ANN401)
[warning] 335-335: Dynamically typed expressions (typing.Any) are disallowed in rail_status
(ANN401)
[warning] 356-356: Dynamically typed expressions (typing.Any) are disallowed in rails
(ANN401)
[warning] 357-357: Dynamically typed expressions (typing.Any) are disallowed in rail_type
(ANN401)
[warning] 358-358: Dynamically typed expressions (typing.Any) are disallowed in rail_status
(ANN401)
[warning] 385-385: Missing return type annotation for private function _make_llm_intercept
(ANN202)
[warning] 387-387: Dynamically typed expressions (typing.Any) are disallowed in rails
(ANN401)
[warning] 388-388: Dynamically typed expressions (typing.Any) are disallowed in rail_type
(ANN401)
[warning] 389-389: Dynamically typed expressions (typing.Any) are disallowed in rail_status
(ANN401)
[warning] 394-394: Missing return type annotation for private function intercept
(ANN202)
[warning] 424-424: Missing return type annotation for private function _make_llm_stream_intercept
(ANN202)
[warning] 426-426: Dynamically typed expressions (typing.Any) are disallowed in rails
(ANN401)
[warning] 427-427: Dynamically typed expressions (typing.Any) are disallowed in rail_type
(ANN401)
[warning] 428-428: Dynamically typed expressions (typing.Any) are disallowed in rail_status
(ANN401)
[warning] 434-434: Missing return type annotation for private function stream_intercept
(ANN202)
[warning] 452-455: Avoid specifying long messages outside the exception class
(TRY003)
[warning] 459-462: Avoid specifying long messages outside the exception class
(TRY003)
[warning] 467-467: Missing return type annotation for private function guarded_provider_stream
(ANN202)
[warning] 500-500: Missing return type annotation for private function _make_tool_intercept
(ANN202)
[warning] 502-502: Dynamically typed expressions (typing.Any) are disallowed in rails
(ANN401)
[warning] 503-503: Dynamically typed expressions (typing.Any) are disallowed in rail_type
(ANN401)
[warning] 504-504: Dynamically typed expressions (typing.Any) are disallowed in rail_status
(ANN401)
[warning] 508-508: Missing return type annotation for private function tool_intercept
(ANN202)
[warning] 536-536: Dynamically typed expressions (typing.Any) are disallowed in result
(ANN401)
[warning] 542-547: Avoid specifying long messages outside the exception class
(TRY003)
python/nemo_relay/_nemo_anonymizer_local.py
[warning] 13-13: Import from collections.abc instead: Callable
Import from collections.abc
(UP035)
[warning] 63-63: Remove quotes from type annotation
Remove quotes
(UP037)
[warning] 113-116: Avoid specifying long messages outside the exception class
(TRY003)
[warning] 117-120: Avoid specifying long messages outside the exception class
(TRY003)
[warning] 154-154: Dynamically typed expressions (typing.Any) are disallowed in _build_replace_method
(ANN401)
[warning] 180-180: Avoid specifying long messages outside the exception class
(TRY003)
[warning] 291-291: Dynamically typed expressions (typing.Any) are disallowed in request
(ANN401)
[warning] 302-302: Dynamically typed expressions (typing.Any) are disallowed in request
(ANN401)
[warning] 310-310: Dynamically typed expressions (typing.Any) are disallowed in response
(ANN401)
[warning] 519-519: Do not catch blind exception: Exception
(BLE001)
| fn from(value: PluginError) -> Self { | ||
| match value { | ||
| PluginError::InvalidConfig(message) => Self::InvalidConfig(message), | ||
| PluginError::Conflict(message) => Self::InvalidConfig(message), |
There was a problem hiding this comment.
PluginError::Conflict is being flattened into the wrong downstream error classes. The shared root cause is conflict-semantics loss at boundary mappings, which breaks accurate machine-level handling of duplicate-state failures.
crates/adaptive/src/error.rs#L56-L56: mapPluginError::Conflictto a conflict/state-failure adaptive class (orRegistrationFailed) instead ofInvalidConfig.crates/ffi/src/error.rs#L141-L143: mapPluginError::ConflicttoNemoRelayStatus::AlreadyExists(or a dedicated conflict status if added) instead ofInvalidArg.
📍 Affects 2 files
crates/adaptive/src/error.rs#L56-L56(this comment)crates/ffi/src/error.rs#L141-L143
🤖 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/adaptive/src/error.rs` at line 56, The PluginError::Conflict variant
is being mapped to semantically incorrect downstream error types, losing
conflict-related information needed for proper error handling. At
crates/adaptive/src/error.rs#L56-L56, change the mapping from
Self::InvalidConfig(message) to a conflict or state-failure semantic error class
such as Self::RegistrationFailed or an equivalent conflict variant that better
represents the duplicate-state nature of the error. At
crates/ffi/src/error.rs#L141-L143, change the mapping from
NemoRelayStatus::InvalidArg to NemoRelayStatus::AlreadyExists (or another
dedicated conflict status variant if available) to accurately represent the
conflict semantics when converting PluginError::Conflict to FFI error codes.
Both changes must preserve and forward the conflict context through the error
boundary mappings.
| fn pii_redaction_component_config() -> serde_json::Map<String, Value> { | ||
| json!({ | ||
| "mode": "builtin", | ||
| "codec": "openai_chat", | ||
| "output": true, | ||
| "input": false, | ||
| "tool_input": false, | ||
| "tool_output": false, | ||
| "builtin": { | ||
| "action": "regex_replace", | ||
| "pattern": "sk-[A-Za-z0-9_-]+", | ||
| "replacement": "[REDACTED]", | ||
| "target_paths": ["/message"] | ||
| } | ||
| }) | ||
| .as_object() | ||
| .unwrap() | ||
| .clone() | ||
| } |
There was a problem hiding this comment.
PII redaction tests are pinned to an outdated config contract.
The new fixtures/assertions assume mode = "builtin" and kind = "pii_redaction", but the current core contract (in crates/core/src/plugins/nemo_anonymizer/component.rs) defines PII_REDACTION_PLUGIN_KIND as an alias to nemo_anonymizer and constrains mode schema to local. This makes several new tests assert invalid behavior.
Please align the fixture and assertions with the canonical config contract (including schema expectations and kind rendering checks using PII_REDACTION_PLUGIN_KIND, not a hardcoded literal).
Suggested direction
- "mode": "builtin",
+ "mode": "local",
...
- assert_eq!(schema.field("mode").unwrap().enum_values, &["builtin", "anonymizer_local"]);
+ assert_eq!(schema.field("mode").unwrap().enum_values, &["local"]);
...
- assert!(rendered.contains("kind = \"pii_redaction\""));
+ assert!(rendered.contains(&format!("kind = \"{}\"", PII_REDACTION_PLUGIN_KIND)));Also applies to: 194-224, 396-416, 1256-1269, 1390-1402
🤖 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/plugins_tests.rs` around lines 56 - 74, The test
fixtures and assertions for PII redaction are using an outdated configuration
contract that does not match the canonical definition in
crates/core/src/plugins/nemo_anonymizer/component.rs. Update the
pii_redaction_component_config function at lines 56-74 and all affected test
assertions at lines 194-224, 396-416, 1256-1269, and 1390-1402 to align with the
actual core contract: replace "mode": "builtin" with the correct mode schema
(likely "local"), remove the "builtin" configuration section and replace it with
the structure expected by the nemo_anonymizer component, replace any hardcoded
kind literals with the PII_REDACTION_PLUGIN_KIND constant imported from the core
component, and update all schema validation assertions to reflect the corrected
configuration contract.
| regex = "1" | ||
| sha2 = "0.10" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for regex and sha2 usage in crates/core
echo "=== regex usage ==="
rg -n --type rust '\bregex\b' crates/core/src/ || echo "No regex usage found"
echo ""
echo "=== sha2 usage ==="
rg -n --type rust '\bsha2\b' crates/core/src/ || echo "No sha2 usage found"Repository: NVIDIA/NeMo-Relay
Length of output: 251
Remove unused regex and sha2 dependencies from crates/core/Cargo.toml.
Verification found no actual usage of either dependency: regex appears only in a comment, and sha2 has zero references anywhere in crates/core/src/. Remove lines 72–73 to avoid unused-dependency warnings and keep the dependency tree clean.
🤖 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/Cargo.toml` around lines 72 - 73, Remove the unused `regex` and
`sha2` dependencies from the Cargo.toml file in crates/core. Both dependencies
are not used in the codebase: `regex` only appears in comments and `sha2` has no
references anywhere in crates/core/src/. Delete both dependency declarations
entirely to eliminate unused-dependency warnings and maintain a clean dependency
tree.
| fn reject_duplicate_capabilities(capabilities: &[DynamicPluginCapability]) -> Result<()> { | ||
| let mut seen = Vec::with_capacity(capabilities.len()); | ||
| for capability in capabilities { | ||
| if seen.contains(capability) { | ||
| return Err(PluginError::InvalidConfig(format!( | ||
| "capabilities.items contains duplicate capability '{capability:?}'" | ||
| ))); | ||
| } | ||
| seen.push(*capability); | ||
| } | ||
| Ok(()) | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial | 💤 Low value
Consider HashSet for duplicate detection.
reject_duplicate_capabilities uses a Vec with linear search, making this O(n²). Since DynamicPluginCapability implements Hash + Eq, a HashSet would be O(n). Not critical given typical capability counts, but a minor efficiency improvement.
♻️ Suggested refactor
+use std::collections::HashSet;
+
fn reject_duplicate_capabilities(capabilities: &[DynamicPluginCapability]) -> Result<()> {
- let mut seen = Vec::with_capacity(capabilities.len());
+ let mut seen = HashSet::with_capacity(capabilities.len());
for capability in capabilities {
- if seen.contains(capability) {
+ if !seen.insert(*capability) {
return Err(PluginError::InvalidConfig(format!(
"capabilities.items contains duplicate capability '{capability:?}'"
)));
}
- seen.push(*capability);
}
Ok(())
}🤖 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/dynamic/manifest.rs` around lines 332 - 343, Replace
the Vec-based duplicate detection in the reject_duplicate_capabilities function
with a HashSet to improve performance from O(n²) to O(n). Change the seen
variable from a Vec to a HashSet, and update the contains check to use the
insert method (which returns false if the element was already present), allowing
you to detect duplicates while building the set in a single pass.
| stamp_creation_metadata(&mut record.metadata); | ||
| touch_metadata(&mut record.metadata); |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial | 💤 Low value
created_at and updated_at will differ for freshly added records.
stamp_creation_metadata sets both timestamps to the same value, but then touch_metadata immediately overwrites updated_at. This results in a microsecond gap between created_at and updated_at even for brand-new records. If this is intentional (treating add as a mutation distinct from creation), document it; otherwise, consider removing the touch_metadata call here since stamp_creation_metadata already sets updated_at.
🤖 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/dynamic/registry.rs` around lines 59 - 60, The
stamp_creation_metadata function sets both created_at and updated_at to the same
value, but the immediately following touch_metadata call overwrites updated_at,
creating an unintended microsecond gap for newly added records. Either remove
the touch_metadata call since stamp_creation_metadata already sets updated_at
appropriately, or if this behavior is intentional (treating add as a distinct
mutation), add a clear comment explaining why both calls are necessary.
|
|
||
| In particular: | ||
|
|
||
| - the previous builtin deterministic lane is no longer part of this plugin |
There was a problem hiding this comment.
Use built-in Instead Of builtin In Prose
Line 49 uses builtin; use built-in for consistent technical documentation wording.
Suggested edit
-- the previous builtin deterministic lane is no longer part of this plugin
+- the previous built-in deterministic lane is no longer part of this pluginAs per coding guidelines, "User-facing text quality issues such as typographical and grammatical errors in documentation and UI strings" should be flagged and fixed.
📝 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.
| - the previous builtin deterministic lane is no longer part of this plugin | |
| - the previous built-in deterministic lane is no longer part of this plugin |
🧰 Tools
🪛 LanguageTool
[grammar] ~49-~49: Ensure spelling is correct
Context: ...ackend. In particular: - the previous builtin deterministic lane is no longer part of...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
🤖 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/nemo-anonymizer-plugin/about.mdx` at line 49, On line 49 of the
about.mdx file, change the word "builtin" to "built-in" in the phrase "the
previous builtin deterministic lane is no longer part of this plugin" to
maintain consistent technical documentation wording conventions.
Sources: Coding guidelines, Linters/SAST tools
| import csv | ||
| import importlib | ||
| import json | ||
| import tempfile |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Python tempfile NamedTemporaryFile Windows reopen by name while file is open delete=True documentation
💡 Result:
On Windows, you generally cannot reopen a NamedTemporaryFile by name while it is still open if you use the default delete=True parameter [1][2]. When delete=True, Python creates the file with the O_TEMPORARY flag on Windows [3]. This flag instructs the operating system to set FILE_FLAG_DELETE_ON_CLOSE and, crucially, to share delete access [2][4]. Because of how the Windows API handles this, the file can only be reopened by other processes (or by subsequent calls in your own program) if those subsequent opens also share delete access [1][2]. The standard Python open function does not share this access, leading to a sharing violation (PermissionError) [2][4]. To work around this limitation, you have the following options: 1. Use delete=False: This is the most common approach [5][6]. By setting delete=False, the file is not automatically deleted when closed [3]. You are then responsible for manually closing the file object and deleting the file (using os.unlink) once you are finished with it [7][6]. 2. Use delete=True and delete_on_close=False (Python 3.12+): If you are using a newer version of Python, you can set delete=True (default) and delete_on_close=False [1][8]. This prevents the file from being deleted immediately upon closing the handle, allowing you to reopen it by name while the context manager is still active [1]. Note that you must ensure the file is closed before the context manager exits, or the final cleanup (which attempts to unlink the file) may fail with a PermissionError if other handles remain open [1][8]. Example of the recommended approach (using delete_on_close=False for newer Python versions): with tempfile.NamedTemporaryFile(delete=True, delete_on_close=False) as fp: fp.write(b'data') fp.close # Now safe to reopen with open(fp.name, 'rb') as f: data = f.read # Automatically deleted on exit Example of the manual approach (for older Python versions): try: tmp = tempfile.NamedTemporaryFile(delete=False) tmp.write(b'data') tmp.close # Now safe to reopen with open(tmp.name, 'rb') as f: data = f.read finally: os.unlink(tmp.name) # Manual cleanup [6]
Citations:
- 1: https://docs.python.org/3/library/tempfile.html
- 2: https://bugs.python.org/issue44055
- 3: https://github.com/python/cpython/blob/a6a4dc816d68df04a7d592e0b6af8c7ecc4d4344/Lib/tempfile.py
- 4: NamedTemporaryFile opened twice on Windows python/cpython#88221
- 5: https://bugs.python.org/issue14243
- 6: https://stackoverflow.com/questions/66744497/python-tempfile-namedtemporaryfile-cant-use-generated-tempfile
- 7: https://docs.python.org/3.11/library/tempfile.html
- 8: https://docs.python.org/release/3.12.1/library/tempfile.html
Close the CSV file before passing its path to the anonymizer runtime.
The code at line 198 opens a NamedTemporaryFile with delete=True (the default) and passes handle.name to the anonymizer while the handle remains open. On Windows, this prevents the anonymizer from reopening the file: the OS creates it with FILE_FLAG_DELETE_ON_CLOSE and share-delete access, but standard open() does not share that access, raising PermissionError. Use TemporaryDirectory with explicit UTF-8 encoding instead.
Proposed portability fix
import importlib
import json
import tempfile
from dataclasses import dataclass
+from pathlib import Path
from typing import Any, Callable, NamedTuple, cast
@@
- with tempfile.NamedTemporaryFile("w", suffix=".csv", newline="", delete=True) as handle:
- writer = csv.DictWriter(handle, fieldnames=["record_id", "text"])
- writer.writeheader()
- for index, text in enumerate(texts):
- writer.writerow({"record_id": index, "text": text})
- handle.flush()
-
+ with tempfile.TemporaryDirectory() as tmpdir:
+ input_path = Path(tmpdir) / "input.csv"
+ with input_path.open("w", newline="", encoding="utf-8") as handle:
+ writer = csv.DictWriter(handle, fieldnames=["record_id", "text"])
+ writer.writeheader()
+ for index, text in enumerate(texts):
+ writer.writerow({"record_id": index, "text": text})
+
result = self._anonymizer.preview(
config=self._anonymizer_config,
data=self._runtime.anonymizer_input_cls(
- source=handle.name,
+ source=str(input_path),
text_column="text",
id_column="record_id",
data_summary=self._config.data_summary,🤖 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/_nemo_anonymizer_local.py` around lines 8 - 11, The CSV
file handling at line 198 uses NamedTemporaryFile with delete=True and passes
the file path to the anonymizer while the file handle remains open, causing
Windows permission errors. Replace the NamedTemporaryFile approach with
TemporaryDirectory to create a temporary directory, then write the CSV data to a
file within that directory with explicit UTF-8 encoding. Ensure the file is
fully closed before passing its path to the anonymizer runtime, and properly
manage the TemporaryDirectory context to clean up after the anonymizer completes
its work.
| def _apply_annotated_request_payload(request: Any, payload: dict[str, Json]) -> None: | ||
| request.messages = payload["messages"] | ||
| request.model = payload.get("model") | ||
| request.params = payload.get("params") | ||
| request.tools = payload.get("tools") | ||
| request.tool_choice = payload.get("tool_choice") | ||
|
|
There was a problem hiding this comment.
Write sanitized extra fields back to the annotated request.
_annotated_request_payload includes extra, so target paths under /extra can be sanitized, but Line 308 drops the sanitized value before re-encoding. Provider-specific request fields can leave PII unchanged.
Proposed fix
def _apply_annotated_request_payload(request: Any, payload: dict[str, Json]) -> None:
request.messages = payload["messages"]
request.model = payload.get("model")
request.params = payload.get("params")
request.tools = payload.get("tools")
request.tool_choice = payload.get("tool_choice")
+ request.extra = payload.get("extra")📝 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.
| def _apply_annotated_request_payload(request: Any, payload: dict[str, Json]) -> None: | |
| request.messages = payload["messages"] | |
| request.model = payload.get("model") | |
| request.params = payload.get("params") | |
| request.tools = payload.get("tools") | |
| request.tool_choice = payload.get("tool_choice") | |
| def _apply_annotated_request_payload(request: Any, payload: dict[str, Json]) -> None: | |
| request.messages = payload["messages"] | |
| request.model = payload.get("model") | |
| request.params = payload.get("params") | |
| request.tools = payload.get("tools") | |
| request.tool_choice = payload.get("tool_choice") | |
| request.extra = payload.get("extra") |
🧰 Tools
🪛 Ruff (0.15.17)
[warning] 302-302: Dynamically typed expressions (typing.Any) are disallowed in request
(ANN401)
🤖 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/_nemo_anonymizer_local.py` around lines 302 - 308, The
function _apply_annotated_request_payload extracts and restores several fields
from the payload back to the request object (messages, model, params, tools,
tool_choice), but it does not restore the sanitized extra field. Since extra
fields are included in _annotated_request_payload and can contain PII that gets
sanitized, you must add a line to assign the sanitized extra field from the
payload back to the request object using the same pattern as the other field
assignments (request.extra = payload.get("extra")).
| def _annotated_message_text(message: Json) -> str | None: | ||
| if isinstance(message, str): | ||
| return message | ||
| if not isinstance(message, list): | ||
| return None | ||
| parts = [] | ||
| for part in message: | ||
| if not isinstance(part, dict): | ||
| continue | ||
| if part.get("type") == "text" and isinstance(part.get("text"), str): | ||
| parts.append(cast(str, part["text"])) | ||
| return "\n".join(parts) if parts else None |
There was a problem hiding this comment.
Preserve content-block boundaries during response overlays.
_annotated_message_text collapses all text blocks with "\n", and the OpenAI Responses and Anthropic overlay helpers split that string back on "\n". A single original block containing a newline, or sanitized text that changes newline counts, can be truncated or shifted across blocks. Carry sanitized per-block text and overlay by block index instead.
Also applies to: 395-428
🤖 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/_nemo_anonymizer_local.py` around lines 323 - 334, The
_annotated_message_text function currently joins all text blocks with newlines,
which loses the original block boundaries and causes content to be truncated or
shifted when sanitized text changes newline counts. Instead of returning a
single concatenated string, refactor this function to return the extracted text
as a per-block list structure that preserves each block's original text
separately. Update the callers (the OpenAI Responses and Anthropic overlay
helpers at lines 395-428) to overlay sanitized content by block index instead of
splitting the concatenated string on newlines, ensuring block boundaries are
maintained throughout the sanitization and overlay process.
| def sanitize_request(request: LLMRequest) -> LLMRequest: | ||
| annotated = codec.decode(request) | ||
| payload = _annotated_request_payload(annotated) | ||
| sanitized_payload = cast(dict[str, Json], runner.sanitize_json(payload)) | ||
| _apply_annotated_request_payload(annotated, sanitized_payload) | ||
| return codec.encode(annotated, request) |
There was a problem hiding this comment.
Fallback to raw request sanitization when codec decoding fails.
The response path sanitizes the raw payload if decoding fails, but the request path lets decode errors escape. The Rust wrapper’s sanitizer fallback can then return the original request, leaving PII unsanitized.
Proposed fix
codec = _CODECS[codec_name]()
def sanitize_request(request: LLMRequest) -> LLMRequest:
- annotated = codec.decode(request)
+ try:
+ annotated = codec.decode(request)
+ except Exception:
+ request.content = cast(dict[str, Json], runner.sanitize_json(cast(Json, request.content)))
+ return request
payload = _annotated_request_payload(annotated)
sanitized_payload = cast(dict[str, Json], runner.sanitize_json(payload))
_apply_annotated_request_payload(annotated, sanitized_payload)🤖 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/_nemo_anonymizer_local.py` around lines 497 - 502, The
sanitize_request function lacks error handling for codec decoding failures,
allowing decode errors to escape instead of falling back to raw request
sanitization like the response path does. Wrap the codec.decode(request) call in
a try-except block in the sanitize_request function, and when decoding fails,
implement a fallback path that sanitizes the raw request payload directly using
runner.sanitize_json without attempting to annotate or re-encode it, ensuring
PII is sanitized even when the codec fails.
Signed-off-by: Alex Fournier <afournier@nvidia.com>
4c6eb28 to
f0bad61
Compare
Signed-off-by: Alex Fournier <afournier@nvidia.com>
|
A little more implementation detail on what this foundation slice is actually establishing. This PR is centered on one durable control-plane record and one authored manifest contract. Record contractThe core type is
The intended contract is:
That separation is the main mechanism that keeps “what the user wants” separate from “what Relay has observed”.
Generation lives on Manifest contractThe authored manifest is This PR establishes:
The current manifest model distinguishes:
with kind-specific requirements for:
Examples of what is enforced now:
So this PR is not just adding structs; it is making the authored contract executable. Registry mechanisms
It currently provides:
Important behaviors already in place:
That gives later CLI/runtime work a stable lifecycle model instead of re-deciding semantics. Boundary decisions in this PRThis slice intentionally stops at the control-plane boundary. It does not yet implement:
Those are follow-on issues, but this PR is meant to establish the contracts they plug into. Why this shapeThe main reason for this split is to avoid backing into the plugin model through CLI or runtime implementation details. The order here is:
That is the actual purpose of this foundation PR. |
Overview
Add the first Rust-side dynamic plugin control-plane slice for RELAY-337.
Details
crate::plugin::dynamicas the foundation for dynamic plugin control-plane typesmetadata,source,spec, andstatusrelay-plugin.tomlmanifest types, parsing, path loading, strict validation, and conversion into registry recordsrust_dynamicandworkerplugin lanesPluginError::Conflictand propagate it through adaptive and FFI error mappingsValidation run:
cargo fmt --all --checkcargo clippy --workspace --all-targets -- -D warningscargo test -p nemo-relay plugin::dynamic::tests --libKnown validation gap:
just test-rustis currently red on this branch because of the pre-existing failure incrates/core/tests/integration/pipeline_tests.rsfortest_response_codec_failure_non_fatal, which appears unrelated to this change.Where should the reviewer start?
Start in
crates/core/src/plugin/dynamic.rsand then review:crates/core/src/plugin/dynamic/manifest.rscrates/core/src/plugin/dynamic/registry.rscrates/core/tests/unit/plugin_dynamic_tests.rsRelated Issues: (use one of the action keywords Closes / Fixes / Resolves / Relates to)