Skip to content

feat(observability)!: add multi-sink ATOF export#416

Merged
rapids-bot[bot] merged 3 commits into
NVIDIA:mainfrom
willkill07:wkk_atof-multi-sinks
Jul 14, 2026
Merged

feat(observability)!: add multi-sink ATOF export#416
rapids-bot[bot] merged 3 commits into
NVIDIA:mainfrom
willkill07:wkk_atof-multi-sinks

Conversation

@willkill07

@willkill07 willkill07 commented Jul 14, 2026

Copy link
Copy Markdown
Member

Warning

BREAKING CHANGE — ATOF configuration and manual exporter APIs: observability config now requires components.config.version = 2 for ATOF and represents destinations as a tagged atof.sinks array. Legacy output_directory, filename, mode, and endpoints fields are rejected rather than translated. Manual ATOF exporters now accept one typed file or stream sink; path is absent for stream-backed exporters.

Overview

Add fan-out ATOF export through independently configured file and stream sinks.

  • 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

  • Introduce observability config version 2 and validate tagged file or stream ATOF sinks, including migration diagnostics for the legacy shape.
  • Create one exporter per sink so every event reaches every configured destination and teardown shuts down all exporters.
  • Update Rust, FFI, Python, Node, and Go manual APIs for one typed sink, including optional paths for stream exporters and header_env stream headers.
  • Update CLI destination reporting and doctor probes, and isolate Python and Node test recipes from ambient user plugin configuration.

Where should the reviewer start?

Start with crates/core/src/observability/plugin_component.rs for the v2 validation and fan-out lifecycle, then crates/core/src/observability/atof.rs for the single-sink exporter contract.

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

Closes RELAY-459

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Updated ATOF observability configuration to schema v2 with typed sinks (file and stream), including multi-sink destination support.
    • Added header_env support for stream sinks.
    • Stream-only ATOF exporters now expose an absent output path (instead of a file path).
  • Bug Fixes

    • Improved observability health checks and diagnostics to probe file/HTTP/NDJSON/WebSocket transports using sinks[...] details, with clearer messaging when no file sinks are configured or streaming is unavailable.
  • Documentation

    • Updated examples, bindings, and tests to use the sinks model (with compatibility aliases for prior endpoint naming).

Signed-off-by: Will Killian <wkillian@nvidia.com>
@willkill07
willkill07 requested a review from a team as a code owner July 14, 2026 03:09
@github-actions github-actions Bot added size:XL PR is extra large Feature a new feature breaking PR introduces a breaking change lang:go PR changes/introduces Go code lang:js PR changes/introduces Javascript/Typescript code lang:python PR changes/introduces Python code lang:rust PR changes/introduces Rust code labels Jul 14, 2026
@willkill07 willkill07 added this to the 0.6 milestone Jul 14, 2026
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 2d51895b-70b8-44bb-a3ee-be522fe9b80f

📥 Commits

Reviewing files that changed from the base of the PR and between f2b2a95 and dbb1105.

📒 Files selected for processing (7)
  • crates/core/src/observability/atof.rs
  • crates/core/tests/unit/observability/plugin_component_tests.rs
  • crates/ffi/tests/integration/api_tests.rs
  • crates/python/src/py_types/observability.rs
  • go/nemo_relay/nemo_relay.go
  • go/nemo_relay/observability_plugin.go
  • python/tests/test_types.py
📜 Recent review details
🧰 Additional context used
📓 Path-based instructions (25)
**/*.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

When changing the core Rust runtime or Rust-facing API surface, format Rust code with cargo fmt (rustfmt defaults), keep cargo clippy -- -D warnings clean, and satisfy cargo deny check per deny.toml.

**/*.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.
For Rust changes headed for review, run cargo fmt --all and cargo clippy --workspace --all-targets -- -D warnings even if relying on pre-commit.

Files:

  • crates/ffi/tests/integration/api_tests.rs
  • crates/python/src/py_types/observability.rs
  • crates/core/src/observability/atof.rs
  • crates/core/tests/unit/observability/plugin_component_tests.rs
crates/ffi/**

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

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

Files:

  • crates/ffi/tests/integration/api_tests.rs
crates/ffi/**/*.rs

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

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

Use C FFI export names prefixed with nemo_relay_ in the raw C FFI layer.

Files:

  • crates/ffi/tests/integration/api_tests.rs
**/*.{rs,py}

📄 CodeRabbit inference engine (AGENTS.md)

Follow binding naming conventions in Rust and Python: use snake_case.

Files:

  • crates/ffi/tests/integration/api_tests.rs
  • python/tests/test_types.py
  • crates/python/src/py_types/observability.rs
  • crates/core/src/observability/atof.rs
  • crates/core/tests/unit/observability/plugin_component_tests.rs
**/*.{rs,py,js,mjs,cjs,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{rs,py,js,mjs,cjs,ts,tsx}: 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 the wrapper layer.
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/ffi/tests/integration/api_tests.rs
  • python/tests/test_types.py
  • crates/python/src/py_types/observability.rs
  • crates/core/src/observability/atof.rs
  • crates/core/tests/unit/observability/plugin_component_tests.rs
**/*.{rs,py,go,js,ts,c,h}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Use language-appropriate naming conventions: Rust snake_case, C FFI exports prefixed nemo_relay_, Go PascalCase, Node.js camelCase, and Python snake_case.

Files:

  • crates/ffi/tests/integration/api_tests.rs
  • python/tests/test_types.py
  • go/nemo_relay/observability_plugin.go
  • crates/python/src/py_types/observability.rs
  • go/nemo_relay/nemo_relay.go
  • crates/core/src/observability/atof.rs
  • crates/core/tests/unit/observability/plugin_component_tests.rs
**/*.{rs,go,js,ts}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Add the SPDX license header to all Rust, Go, JavaScript, and TypeScript source files using the corresponding // comment form.

Files:

  • crates/ffi/tests/integration/api_tests.rs
  • go/nemo_relay/observability_plugin.go
  • crates/python/src/py_types/observability.rs
  • go/nemo_relay/nemo_relay.go
  • crates/core/src/observability/atof.rs
  • crates/core/tests/unit/observability/plugin_component_tests.rs
**/*

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

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

Files:

  • crates/ffi/tests/integration/api_tests.rs
  • python/tests/test_types.py
  • go/nemo_relay/observability_plugin.go
  • crates/python/src/py_types/observability.rs
  • go/nemo_relay/nemo_relay.go
  • crates/core/src/observability/atof.rs
  • crates/core/tests/unit/observability/plugin_component_tests.rs
**/*.{rs,py,go,js,ts}

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

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

Files:

  • crates/ffi/tests/integration/api_tests.rs
  • python/tests/test_types.py
  • go/nemo_relay/observability_plugin.go
  • crates/python/src/py_types/observability.rs
  • go/nemo_relay/nemo_relay.go
  • crates/core/src/observability/atof.rs
  • crates/core/tests/unit/observability/plugin_component_tests.rs
crates/{python,ffi,node}/**/*

⚙️ CodeRabbit configuration file

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

Files:

  • crates/ffi/tests/integration/api_tests.rs
  • crates/python/src/py_types/observability.rs
{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}

⚙️ CodeRabbit configuration file

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

Files:

  • crates/ffi/tests/integration/api_tests.rs
  • python/tests/test_types.py
  • crates/core/tests/unit/observability/plugin_component_tests.rs
**/*.py

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.py: When changing the Python wrapper package, tests, or docs tooling, lint with Ruff (E, F, W, I), format with Ruff formatter (120-character lines, double quotes), and pass ty type checking.
Add the SPDX license header to all Python source files using the # comment form.

Files:

  • python/tests/test_types.py
{crates/**/src/**/*.rs,python/**/*.py}

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

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

Files:

  • python/tests/test_types.py
  • crates/python/src/py_types/observability.rs
  • crates/core/src/observability/atof.rs
**/*.{py,go,js,ts}

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

Keep Python, Go, and Node.js config objects and subscriber/exporter methods aligned so all bindings expose the same logical knobs and semantics.

Files:

  • python/tests/test_types.py
  • go/nemo_relay/observability_plugin.go
  • go/nemo_relay/nemo_relay.go
python/tests/**/*.py

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

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

Files:

  • python/tests/test_types.py
go/nemo_relay/**/*.go

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

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

Use PascalCase for public Go APIs.

Files:

  • go/nemo_relay/observability_plugin.go
  • go/nemo_relay/nemo_relay.go
**/*.go

📄 CodeRabbit inference engine (CONTRIBUTING.md)

When changing the experimental Go binding, format Go code with gofmt and keep go vet ./... passing.

Files:

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

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

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

Files:

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

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

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

Files:

  • go/nemo_relay/observability_plugin.go
  • go/nemo_relay/nemo_relay.go
go/nemo_relay/**

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

Keep shared plugin helpers in go/nemo_relay aligned with plugin registration, composition, and lifecycle behavior.

Files:

  • go/nemo_relay/observability_plugin.go
  • go/nemo_relay/nemo_relay.go
go/nemo_relay/**/*

⚙️ CodeRabbit configuration file

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

Files:

  • go/nemo_relay/observability_plugin.go
  • go/nemo_relay/nemo_relay.go
{crates/core,crates/adaptive}/**/*

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

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

Files:

  • crates/core/src/observability/atof.rs
  • crates/core/tests/unit/observability/plugin_component_tests.rs
crates/core/**/*.rs

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

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

Files:

  • crates/core/src/observability/atof.rs
  • crates/core/tests/unit/observability/plugin_component_tests.rs
crates/{core,adaptive}/**/*

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

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

Files:

  • crates/core/src/observability/atof.rs
  • crates/core/tests/unit/observability/plugin_component_tests.rs
crates/{core,adaptive}/**/*.rs

⚙️ CodeRabbit configuration file

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

Files:

  • crates/core/src/observability/atof.rs
  • crates/core/tests/unit/observability/plugin_component_tests.rs
🔇 Additional comments (7)
python/tests/test_types.py (1)

15-16: LGTM!

Also applies to: 453-483, 484-489, 491-498, 508-508

go/nemo_relay/observability_plugin.go (1)

35-36: LGTM!

Also applies to: 54-60, 75-81

crates/python/src/py_types/observability.rs (1)

169-170: LGTM!

Also applies to: 179-214, 288-312, 318-332, 359-362

go/nemo_relay/nemo_relay.go (1)

1685-1686: LGTM!

Also applies to: 1711-1717, 1754-1760, 1783-1785

crates/core/src/observability/atof.rs (1)

308-361: LGTM!

Also applies to: 409-440, 454-529, 634-634

crates/core/tests/unit/observability/plugin_component_tests.rs (1)

358-428: LGTM!

crates/ffi/tests/integration/api_tests.rs (1)

811-818: 🚀 Performance & Scalability

http:// is invalid for transport: "websocket". WebSocket sinks require a ws:// or wss:// URL here, so InvalidArg is the correct expectation.

			> Likely an incorrect or invalid review comment.

Walkthrough

ATOF configuration changes from endpoint-based settings to typed file and stream sinks. Core exporters, plugin validation and registration, CLI diagnostics, FFI behavior, and Node, Python, and Go bindings now use the sink model with observability configuration version 2.

Changes

ATOF sink migration

Layer / File(s) Summary
Core sink model and exporter runtime
crates/core/src/observability/atof.rs, crates/core/tests/unit/observability/*
Introduces file and stream sink types, optional exporter paths, stream-only operation, and sink-aware lifecycle handling.
Plugin schema, validation, and registration
crates/core/src/observability/plugin_component.rs, crates/core/tests/unit/observability/plugin_component_tests.rs, crates/cli/tests/coverage/plugins_tests.rs
Adds typed sink configuration, editor metadata, sink validation, version 2 diagnostics, feature gating, and multi-sink exporter registration.
Node, Python, and Go sink APIs
crates/node/*, python/*, go/nemo_relay/*
Updates public configuration models and serialization to typed sinks, including stream headers and optional exporter paths.
CLI destinations and health probes
crates/cli/src/*, crates/cli/tests/coverage/*
Reports destinations and probes file or stream sinks using sink-indexed diagnostics.
FFI and test configuration updates
crates/ffi/*, crates/pii-redaction/*, justfile, integrations/openclaw/*
Handles null paths for stream exporters, updates version-2 fixtures, and isolates test configuration directories.

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

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Plugin
  participant AtofExporter
  participant FileSink
  participant StreamSink
  Client->>Plugin: provide version-2 sinks configuration
  Plugin->>AtofExporter: build one exporter per sink
  AtofExporter->>FileSink: write JSONL for file sinks
  AtofExporter->>StreamSink: enqueue raw JSON for stream sinks
  Client->>AtofExporter: request exporter path
  AtofExporter-->>Client: file path or null for stream sink
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.65% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title follows Conventional Commits, marks the breaking change, and accurately summarizes the multi-sink ATOF export change.
Description check ✅ Passed The description matches the template with Overview, Details, reviewer start, checklist, and related issue reference, and it is sufficiently specific.
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

Warning

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

🔧 golangci-lint (2.12.2)

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


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

@willkill07 willkill07 self-assigned this Jul 14, 2026
@github-actions

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

Caution

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

⚠️ Outside diff range comments (4)
go/nemo_relay/nemo_relay.go (1)

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

MarshalJSON re-declares every field to inject the "type" discriminator — drift risk. All four sink MarshalJSON methods duplicate the exact field list and JSON tags of their outer struct in an unexported anonymous struct purely to avoid infinite recursion when adding "type". Adding a field to the sink struct later and forgetting to mirror it here will silently drop that field from the wire format.

  • go/nemo_relay/nemo_relay.go#L1710-1719: replace the manual fileSinkJSON struct in AtofFileSinkConfig.MarshalJSON with the type-alias trick (type alias AtofFileSinkConfig; json.Marshal(struct{ Type string \json:"type"`; alias }{"file", alias(config)})`).
  • go/nemo_relay/nemo_relay.go#L1756-1767: apply the same alias-based fix to AtofStreamSinkConfig.MarshalJSON.
  • go/nemo_relay/observability_plugin.go#L53-62: apply the same alias-based fix to ObservabilityAtofFileSinkConfig.MarshalJSON.
  • go/nemo_relay/observability_plugin.go#L76-88: apply the same alias-based fix to ObservabilityAtofStreamSinkConfig.MarshalJSON.
♻️ Example fix (applies analogously to all four sites)
 func (config AtofFileSinkConfig) MarshalJSON() ([]byte, error) {
-	type fileSinkJSON struct {
-		Type            string           `json:"type"`
-		OutputDirectory string           `json:"output_directory,omitempty"`
-		Mode            AtofExporterMode `json:"mode,omitempty"`
-		Filename        string           `json:"filename,omitempty"`
-	}
-	return json.Marshal(fileSinkJSON{"file", config.OutputDirectory, config.Mode, config.Filename})
+	type alias AtofFileSinkConfig
+	return json.Marshal(struct {
+		Type string `json:"type"`
+		alias
+	}{"file", alias(config)})
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go/nemo_relay/nemo_relay.go` at line 1, Replace the manually mirrored JSON
helper structs in AtofFileSinkConfig.MarshalJSON,
AtofStreamSinkConfig.MarshalJSON, ObservabilityAtofFileSinkConfig.MarshalJSON,
and ObservabilityAtofStreamSinkConfig.MarshalJSON with the type-alias embedding
pattern: define an alias of the receiver type, embed it alongside the type
discriminator field, and marshal the combined value. Preserve each method’s
existing discriminator value and JSON key.
crates/core/src/observability/atof.rs (2)

625-639: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Stale "endpoints" terminology leaks into user-facing errors post-migration to sinks.

start_endpoint_workers is now only ever invoked with a single-element slice (&[stream_sink] from AtofExporter::new, line ~384), so the enumerate()/index here is vestigial, and the emitted eprintln!/AtofExporterError::InvalidEndpoint text still says "endpoints[{index}]: ..." (always index 0) while the rest of this PR consistently renamed this concept to sinks[{index}] (see plugin_component.rs's build_atof_sink_config and doctor.rs's probe messages). This stale wording can surface directly in plugin registration error messages/logs, confusing users mid-migration.

♻️ Suggested simplification
-fn start_endpoint_workers(configs: &[AtofStreamSinkConfig]) -> Result<Vec<AtofEndpointWorker>> {
-    let mut workers = Vec::with_capacity(configs.len());
-    for (index, config) in configs.iter().enumerate() {
-        match start_endpoint_worker(index, config.clone()) {
-            Ok(worker) => workers.push(worker),
-            Err(error) => {
-                eprintln!("nemo_relay: invalid ATOF endpoint[{index}]: {error}");
-                return Err(AtofExporterError::InvalidEndpoint(format!(
-                    "endpoints[{index}]: {error}"
-                )));
-            }
-        }
-    }
-    Ok(workers)
-}
+fn start_endpoint_workers(configs: &[AtofStreamSinkConfig]) -> Result<Vec<AtofEndpointWorker>> {
+    let mut workers = Vec::with_capacity(configs.len());
+    for (index, config) in configs.iter().enumerate() {
+        match start_endpoint_worker(index, config.clone()) {
+            Ok(worker) => workers.push(worker),
+            Err(error) => {
+                eprintln!("nemo_relay: invalid ATOF stream sink: {error}");
+                return Err(AtofExporterError::InvalidEndpoint(error.to_string()));
+            }
+        }
+    }
+    Ok(workers)
+}
🤖 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/observability/atof.rs` around lines 625 - 639, Update
start_endpoint_workers to use the migrated “sinks” terminology in user-facing
diagnostics, replacing the stale “endpoints” wording in both eprintln! and
AtofExporterError::InvalidEndpoint messages. Remove the vestigial
enumerate/index handling if the single-sink invocation contract permits, while
preserving the existing worker startup and error propagation behavior.

302-330: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Undocumented silent no-op when sink is a stream.

with_output_directory/with_mode/with_filename silently do nothing once self.sink is Stream (e.g. if called after with_stream_sink/with_endpoint, or on a config built for streaming). with_endpoint's doc comment already warns about override semantics, but these three setters don't mention the no-op behavior at all — a direct Rust API consumer building a config in the "wrong" order loses settings with no error/warning.

📝 Suggested doc addition
     /// Override the output directory.
+    ///
+    /// No-op when the configured sink is a stream sink.
     pub fn with_output_directory(mut self, output_directory: impl Into<PathBuf>) -> Self {

(repeat for with_mode / with_filename)

🤖 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/observability/atof.rs` around lines 302 - 330, Document the
silent no-op behavior in the doc comments for
AtofExporterConfig::with_output_directory, with_mode, and with_filename: each
setter only applies when self.sink is AtofSinkConfig::File and does nothing when
the sink is Stream, including when configured via with_stream_sink or
with_endpoint. Preserve the existing implementation and mention the ordering
requirement for callers.
crates/ffi/tests/integration/api_tests.rs (1)

811-818: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Rename invalid_endpoint to invalid_sink. The InvalidArg check is correct here because websocket requires a ws:// or wss:// URL.

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

In `@crates/ffi/tests/integration/api_tests.rs` around lines 811 - 818, Rename the
local variable invalid_endpoint to invalid_sink in the test while preserving the
existing JSON payload and InvalidArg assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/core/tests/unit/observability/plugin_component_tests.rs`:
- Around line 371-374: Update the schema completeness property list in the
relevant observability plugin component test to include "header_env" alongside
the other ATOF sink properties. Ensure the test verifies that
AtofStreamSinkSectionConfig exposes header_env in the generated JSON schema.

In `@crates/python/src/py_types/observability.rs`:
- Around line 256-331: Validate that self.url is non-empty at the start of the
"stream" arm in PyAtofExporterConfig::to_rust_config, returning a PyValueError
with the message "stream sink requires url" when missing. Preserve the existing
endpoint conversion for valid URLs and add a Python binding test covering stream
configuration without a URL.

In `@go/nemo_relay/nemo_relay.go`:
- Around line 1683-1719: Refactor AtofFileSinkConfig.MarshalJSON and the
similarly structured serializer near the referenced sink configuration to avoid
duplicating every field and JSON tag solely to add the "type" discriminator.
Reuse the existing config serialization and inject the discriminator through a
minimal wrapper or equivalent mechanism while preserving all current field
names, omission behavior, and output.

In `@python/nemo_relay/__init__.py`:
- Line 97: Expose AtofFileSinkConfig alongside AtofStreamSinkConfig in the
package-root exports and __all__ entries in python/nemo_relay/__init__.py at
lines 97-97 and 481-481, and add the same symbol to the stub exports in
python/nemo_relay/__init__.pyi at lines 60-62, preserving the existing
observability import path and type declarations.

In `@python/tests/test_types.py`:
- Around line 484-488: Remove the unused tmp_path fixture parameter from
test_endpoint_field_name_policy_is_validated, leaving the test setup and
stream-sink validation behavior unchanged.

---

Outside diff comments:
In `@crates/core/src/observability/atof.rs`:
- Around line 625-639: Update start_endpoint_workers to use the migrated “sinks”
terminology in user-facing diagnostics, replacing the stale “endpoints” wording
in both eprintln! and AtofExporterError::InvalidEndpoint messages. Remove the
vestigial enumerate/index handling if the single-sink invocation contract
permits, while preserving the existing worker startup and error propagation
behavior.
- Around line 302-330: Document the silent no-op behavior in the doc comments
for AtofExporterConfig::with_output_directory, with_mode, and with_filename:
each setter only applies when self.sink is AtofSinkConfig::File and does nothing
when the sink is Stream, including when configured via with_stream_sink or
with_endpoint. Preserve the existing implementation and mention the ordering
requirement for callers.

In `@crates/ffi/tests/integration/api_tests.rs`:
- Around line 811-818: Rename the local variable invalid_endpoint to
invalid_sink in the test while preserving the existing JSON payload and
InvalidArg assertion.

In `@go/nemo_relay/nemo_relay.go`:
- Line 1: Replace the manually mirrored JSON helper structs in
AtofFileSinkConfig.MarshalJSON, AtofStreamSinkConfig.MarshalJSON,
ObservabilityAtofFileSinkConfig.MarshalJSON, and
ObservabilityAtofStreamSinkConfig.MarshalJSON with the type-alias embedding
pattern: define an alias of the receiver type, embed it alongside the type
discriminator field, and marshal the combined value. Preserve each method’s
existing discriminator value and JSON key.
🪄 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: 166a4e39-4fac-4878-9bde-d46bf9a57c97

📥 Commits

Reviewing files that changed from the base of the PR and between 8889361 and ac34d2e.

📒 Files selected for processing (33)
  • crates/cli/src/doctor.rs
  • crates/cli/src/launcher.rs
  • crates/cli/tests/coverage/doctor_tests.rs
  • crates/cli/tests/coverage/plugins_tests.rs
  • crates/cli/tests/coverage/session_tests.rs
  • crates/core/src/observability/atof.rs
  • crates/core/src/observability/plugin_component.rs
  • crates/core/tests/unit/observability/atof_tests.rs
  • crates/core/tests/unit/observability/plugin_component_tests.rs
  • crates/ffi/nemo_relay.h
  • crates/ffi/src/api/observability.rs
  • crates/ffi/tests/integration/api/coverage_sweeps_tests.rs
  • crates/ffi/tests/integration/api_tests.rs
  • crates/ffi/tests/unit/api/core_tests.rs
  • crates/node/observability.d.ts
  • crates/node/observability.js
  • crates/node/src/api/mod.rs
  • crates/node/tests/atof_tests.mjs
  • crates/node/tests/observability_plugin_tests.mjs
  • crates/pii-redaction/tests/unit/component_tests.rs
  • crates/python/src/py_types/observability.rs
  • go/nemo_relay/atof_test.go
  • go/nemo_relay/nemo_relay.go
  • go/nemo_relay/observability_plugin.go
  • go/nemo_relay/observability_plugin_test.go
  • justfile
  • python/nemo_relay/__init__.py
  • python/nemo_relay/__init__.pyi
  • python/nemo_relay/_native.pyi
  • python/nemo_relay/observability.py
  • python/nemo_relay/observability.pyi
  • python/tests/test_observability_plugin.py
  • python/tests/test_types.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (35)
**/*.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

When changing the core Rust runtime or Rust-facing API surface, format Rust code with cargo fmt (rustfmt defaults), keep cargo clippy -- -D warnings clean, and satisfy cargo deny check per deny.toml.

**/*.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.
For Rust changes headed for review, run cargo fmt --all and cargo clippy --workspace --all-targets -- -D warnings even if relying on pre-commit.

Files:

  • crates/pii-redaction/tests/unit/component_tests.rs
  • crates/ffi/tests/integration/api_tests.rs
  • crates/cli/tests/coverage/doctor_tests.rs
  • crates/ffi/tests/integration/api/coverage_sweeps_tests.rs
  • crates/ffi/src/api/observability.rs
  • crates/cli/src/launcher.rs
  • crates/ffi/tests/unit/api/core_tests.rs
  • crates/cli/tests/coverage/plugins_tests.rs
  • crates/cli/tests/coverage/session_tests.rs
  • crates/node/src/api/mod.rs
  • crates/core/tests/unit/observability/atof_tests.rs
  • crates/cli/src/doctor.rs
  • crates/core/src/observability/atof.rs
  • crates/python/src/py_types/observability.rs
  • crates/core/tests/unit/observability/plugin_component_tests.rs
  • crates/core/src/observability/plugin_component.rs
**/*.{rs,py}

📄 CodeRabbit inference engine (AGENTS.md)

Follow binding naming conventions in Rust and Python: use snake_case.

Files:

  • crates/pii-redaction/tests/unit/component_tests.rs
  • crates/ffi/tests/integration/api_tests.rs
  • crates/cli/tests/coverage/doctor_tests.rs
  • crates/ffi/tests/integration/api/coverage_sweeps_tests.rs
  • crates/ffi/src/api/observability.rs
  • crates/cli/src/launcher.rs
  • crates/ffi/tests/unit/api/core_tests.rs
  • crates/cli/tests/coverage/plugins_tests.rs
  • crates/cli/tests/coverage/session_tests.rs
  • python/nemo_relay/__init__.py
  • python/nemo_relay/observability.py
  • python/tests/test_types.py
  • python/tests/test_observability_plugin.py
  • crates/node/src/api/mod.rs
  • crates/core/tests/unit/observability/atof_tests.rs
  • crates/cli/src/doctor.rs
  • crates/core/src/observability/atof.rs
  • crates/python/src/py_types/observability.rs
  • crates/core/tests/unit/observability/plugin_component_tests.rs
  • crates/core/src/observability/plugin_component.rs
**/*.{rs,py,js,mjs,cjs,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{rs,py,js,mjs,cjs,ts,tsx}: 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 the wrapper layer.
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/pii-redaction/tests/unit/component_tests.rs
  • crates/ffi/tests/integration/api_tests.rs
  • crates/cli/tests/coverage/doctor_tests.rs
  • crates/ffi/tests/integration/api/coverage_sweeps_tests.rs
  • crates/ffi/src/api/observability.rs
  • crates/cli/src/launcher.rs
  • crates/ffi/tests/unit/api/core_tests.rs
  • crates/node/tests/atof_tests.mjs
  • crates/cli/tests/coverage/plugins_tests.rs
  • crates/cli/tests/coverage/session_tests.rs
  • crates/node/observability.d.ts
  • python/nemo_relay/__init__.py
  • python/nemo_relay/observability.py
  • python/tests/test_types.py
  • crates/node/observability.js
  • python/tests/test_observability_plugin.py
  • crates/node/tests/observability_plugin_tests.mjs
  • crates/node/src/api/mod.rs
  • crates/core/tests/unit/observability/atof_tests.rs
  • crates/cli/src/doctor.rs
  • crates/core/src/observability/atof.rs
  • crates/python/src/py_types/observability.rs
  • crates/core/tests/unit/observability/plugin_component_tests.rs
  • crates/core/src/observability/plugin_component.rs
**/*.{rs,py,go,js,ts,c,h}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Use language-appropriate naming conventions: Rust snake_case, C FFI exports prefixed nemo_relay_, Go PascalCase, Node.js camelCase, and Python snake_case.

Files:

  • crates/pii-redaction/tests/unit/component_tests.rs
  • crates/ffi/tests/integration/api_tests.rs
  • crates/cli/tests/coverage/doctor_tests.rs
  • crates/ffi/tests/integration/api/coverage_sweeps_tests.rs
  • crates/ffi/nemo_relay.h
  • crates/ffi/src/api/observability.rs
  • crates/cli/src/launcher.rs
  • crates/ffi/tests/unit/api/core_tests.rs
  • crates/cli/tests/coverage/plugins_tests.rs
  • crates/cli/tests/coverage/session_tests.rs
  • crates/node/observability.d.ts
  • python/nemo_relay/__init__.py
  • python/nemo_relay/observability.py
  • python/tests/test_types.py
  • crates/node/observability.js
  • go/nemo_relay/atof_test.go
  • python/tests/test_observability_plugin.py
  • go/nemo_relay/observability_plugin.go
  • crates/node/src/api/mod.rs
  • go/nemo_relay/observability_plugin_test.go
  • crates/core/tests/unit/observability/atof_tests.rs
  • crates/cli/src/doctor.rs
  • go/nemo_relay/nemo_relay.go
  • crates/core/src/observability/atof.rs
  • crates/python/src/py_types/observability.rs
  • crates/core/tests/unit/observability/plugin_component_tests.rs
  • crates/core/src/observability/plugin_component.rs
**/*.{rs,go,js,ts}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Add the SPDX license header to all Rust, Go, JavaScript, and TypeScript source files using the corresponding // comment form.

Files:

  • crates/pii-redaction/tests/unit/component_tests.rs
  • crates/ffi/tests/integration/api_tests.rs
  • crates/cli/tests/coverage/doctor_tests.rs
  • crates/ffi/tests/integration/api/coverage_sweeps_tests.rs
  • crates/ffi/src/api/observability.rs
  • crates/cli/src/launcher.rs
  • crates/ffi/tests/unit/api/core_tests.rs
  • crates/cli/tests/coverage/plugins_tests.rs
  • crates/cli/tests/coverage/session_tests.rs
  • crates/node/observability.d.ts
  • crates/node/observability.js
  • go/nemo_relay/atof_test.go
  • go/nemo_relay/observability_plugin.go
  • crates/node/src/api/mod.rs
  • go/nemo_relay/observability_plugin_test.go
  • crates/core/tests/unit/observability/atof_tests.rs
  • crates/cli/src/doctor.rs
  • go/nemo_relay/nemo_relay.go
  • crates/core/src/observability/atof.rs
  • crates/python/src/py_types/observability.rs
  • crates/core/tests/unit/observability/plugin_component_tests.rs
  • crates/core/src/observability/plugin_component.rs
**/*

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

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

Files:

  • crates/pii-redaction/tests/unit/component_tests.rs
  • crates/ffi/tests/integration/api_tests.rs
  • crates/cli/tests/coverage/doctor_tests.rs
  • crates/ffi/tests/integration/api/coverage_sweeps_tests.rs
  • crates/ffi/nemo_relay.h
  • crates/ffi/src/api/observability.rs
  • crates/cli/src/launcher.rs
  • crates/ffi/tests/unit/api/core_tests.rs
  • crates/node/tests/atof_tests.mjs
  • crates/cli/tests/coverage/plugins_tests.rs
  • crates/cli/tests/coverage/session_tests.rs
  • crates/node/observability.d.ts
  • python/nemo_relay/__init__.py
  • python/nemo_relay/observability.py
  • python/nemo_relay/__init__.pyi
  • python/tests/test_types.py
  • crates/node/observability.js
  • go/nemo_relay/atof_test.go
  • python/nemo_relay/observability.pyi
  • justfile
  • python/tests/test_observability_plugin.py
  • go/nemo_relay/observability_plugin.go
  • crates/node/tests/observability_plugin_tests.mjs
  • crates/node/src/api/mod.rs
  • go/nemo_relay/observability_plugin_test.go
  • crates/core/tests/unit/observability/atof_tests.rs
  • python/nemo_relay/_native.pyi
  • crates/cli/src/doctor.rs
  • go/nemo_relay/nemo_relay.go
  • crates/core/src/observability/atof.rs
  • crates/python/src/py_types/observability.rs
  • crates/core/tests/unit/observability/plugin_component_tests.rs
  • crates/core/src/observability/plugin_component.rs
**/*.{rs,py,go,js,ts}

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

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

Files:

  • crates/pii-redaction/tests/unit/component_tests.rs
  • crates/ffi/tests/integration/api_tests.rs
  • crates/cli/tests/coverage/doctor_tests.rs
  • crates/ffi/tests/integration/api/coverage_sweeps_tests.rs
  • crates/ffi/src/api/observability.rs
  • crates/cli/src/launcher.rs
  • crates/ffi/tests/unit/api/core_tests.rs
  • crates/cli/tests/coverage/plugins_tests.rs
  • crates/cli/tests/coverage/session_tests.rs
  • crates/node/observability.d.ts
  • python/nemo_relay/__init__.py
  • python/nemo_relay/observability.py
  • python/tests/test_types.py
  • crates/node/observability.js
  • go/nemo_relay/atof_test.go
  • python/tests/test_observability_plugin.py
  • go/nemo_relay/observability_plugin.go
  • crates/node/src/api/mod.rs
  • go/nemo_relay/observability_plugin_test.go
  • crates/core/tests/unit/observability/atof_tests.rs
  • crates/cli/src/doctor.rs
  • go/nemo_relay/nemo_relay.go
  • crates/core/src/observability/atof.rs
  • crates/python/src/py_types/observability.rs
  • crates/core/tests/unit/observability/plugin_component_tests.rs
  • crates/core/src/observability/plugin_component.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/pii-redaction/tests/unit/component_tests.rs
  • crates/ffi/tests/integration/api_tests.rs
  • crates/cli/tests/coverage/doctor_tests.rs
  • crates/ffi/tests/integration/api/coverage_sweeps_tests.rs
  • crates/ffi/tests/unit/api/core_tests.rs
  • crates/node/tests/atof_tests.mjs
  • crates/cli/tests/coverage/plugins_tests.rs
  • crates/cli/tests/coverage/session_tests.rs
  • python/tests/test_types.py
  • go/nemo_relay/atof_test.go
  • python/tests/test_observability_plugin.py
  • crates/node/tests/observability_plugin_tests.mjs
  • go/nemo_relay/observability_plugin_test.go
  • crates/core/tests/unit/observability/atof_tests.rs
  • crates/core/tests/unit/observability/plugin_component_tests.rs
crates/ffi/**

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

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

Files:

  • crates/ffi/tests/integration/api_tests.rs
  • crates/ffi/tests/integration/api/coverage_sweeps_tests.rs
  • crates/ffi/nemo_relay.h
  • crates/ffi/src/api/observability.rs
  • crates/ffi/tests/unit/api/core_tests.rs
crates/ffi/**/*.rs

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

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

Use C FFI export names prefixed with nemo_relay_ in the raw C FFI layer.

Files:

  • crates/ffi/tests/integration/api_tests.rs
  • crates/ffi/tests/integration/api/coverage_sweeps_tests.rs
  • crates/ffi/src/api/observability.rs
  • crates/ffi/tests/unit/api/core_tests.rs
crates/{python,ffi,node}/**/*

⚙️ CodeRabbit configuration file

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

Files:

  • crates/ffi/tests/integration/api_tests.rs
  • crates/ffi/tests/integration/api/coverage_sweeps_tests.rs
  • crates/ffi/nemo_relay.h
  • crates/ffi/src/api/observability.rs
  • crates/ffi/tests/unit/api/core_tests.rs
  • crates/node/tests/atof_tests.mjs
  • crates/node/observability.d.ts
  • crates/node/observability.js
  • crates/node/tests/observability_plugin_tests.mjs
  • crates/node/src/api/mod.rs
  • crates/python/src/py_types/observability.rs
crates/ffi/nemo_relay.h

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

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

Update generated or generated-from-build surfaces such as crates/ffi/nemo_relay.h through the proper build step.

Files:

  • crates/ffi/nemo_relay.h
{crates/ffi/src/api/*.rs,crates/ffi/nemo_relay.h}

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

Add or update the shared C/FFI surface in the relevant crates/ffi/src/api/*.rs module, re-export it through crates/ffi/src/api/mod.rs, and keep the generated crates/ffi/nemo_relay.h header correct.

Files:

  • crates/ffi/nemo_relay.h
  • crates/ffi/src/api/observability.rs
{crates/**/src/**/*.rs,python/**/*.py}

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

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

Files:

  • crates/ffi/src/api/observability.rs
  • crates/cli/src/launcher.rs
  • python/nemo_relay/__init__.py
  • python/nemo_relay/observability.py
  • python/tests/test_types.py
  • python/tests/test_observability_plugin.py
  • crates/node/src/api/mod.rs
  • crates/cli/src/doctor.rs
  • crates/core/src/observability/atof.rs
  • crates/python/src/py_types/observability.rs
  • crates/core/src/observability/plugin_component.rs
crates/node/**/*.{js,mjs,cjs,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use camelCase for Node.js public APIs.

Files:

  • crates/node/tests/atof_tests.mjs
  • crates/node/observability.d.ts
  • crates/node/observability.js
  • crates/node/tests/observability_plugin_tests.mjs
crates/node/**/*.{js,ts,jsx,tsx,json}

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

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

Files:

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

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

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

Files:

  • crates/node/observability.d.ts
**/*.{py,go,js,ts}

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

Keep Python, Go, and Node.js config objects and subscriber/exporter methods aligned so all bindings expose the same logical knobs and semantics.

Files:

  • crates/node/observability.d.ts
  • python/nemo_relay/__init__.py
  • python/nemo_relay/observability.py
  • python/tests/test_types.py
  • crates/node/observability.js
  • go/nemo_relay/atof_test.go
  • python/tests/test_observability_plugin.py
  • go/nemo_relay/observability_plugin.go
  • go/nemo_relay/observability_plugin_test.go
  • go/nemo_relay/nemo_relay.go
python/nemo_relay/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • python/nemo_relay/__init__.py
  • python/nemo_relay/observability.py
**/*.py

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.py: When changing the Python wrapper package, tests, or docs tooling, lint with Ruff (E, F, W, I), format with Ruff formatter (120-character lines, double quotes), and pass ty type checking.
Add the SPDX license header to all Python source files using the # comment form.

Files:

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

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

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

Files:

  • python/nemo_relay/__init__.py
  • python/nemo_relay/observability.py
  • python/nemo_relay/__init__.pyi
  • go/nemo_relay/atof_test.go
  • python/nemo_relay/observability.pyi
  • go/nemo_relay/observability_plugin.go
  • crates/node/src/api/mod.rs
  • go/nemo_relay/observability_plugin_test.go
  • python/nemo_relay/_native.pyi
  • go/nemo_relay/nemo_relay.go
{python/nemo_relay/**/*.py,python/nemo_relay/**/*.pyi,go/nemo_relay/**/*.go}

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

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

Files:

  • python/nemo_relay/__init__.py
  • python/nemo_relay/observability.py
  • python/nemo_relay/__init__.pyi
  • go/nemo_relay/atof_test.go
  • python/nemo_relay/observability.pyi
  • go/nemo_relay/observability_plugin.go
  • go/nemo_relay/observability_plugin_test.go
  • python/nemo_relay/_native.pyi
  • go/nemo_relay/nemo_relay.go
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/__init__.py
  • python/nemo_relay/observability.py
  • python/nemo_relay/__init__.pyi
  • python/nemo_relay/observability.pyi
  • python/nemo_relay/_native.pyi
python/tests/**/*.py

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

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

Files:

  • python/tests/test_types.py
  • python/tests/test_observability_plugin.py
go/nemo_relay/**/*.go

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

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

Use PascalCase for public Go APIs.

Files:

  • go/nemo_relay/atof_test.go
  • go/nemo_relay/observability_plugin.go
  • go/nemo_relay/observability_plugin_test.go
  • go/nemo_relay/nemo_relay.go
**/*.go

📄 CodeRabbit inference engine (CONTRIBUTING.md)

When changing the experimental Go binding, format Go code with gofmt and keep go vet ./... passing.

Files:

  • go/nemo_relay/atof_test.go
  • go/nemo_relay/observability_plugin.go
  • go/nemo_relay/observability_plugin_test.go
  • go/nemo_relay/nemo_relay.go
go/nemo_relay/**

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

Keep shared plugin helpers in go/nemo_relay aligned with plugin registration, composition, and lifecycle behavior.

Files:

  • go/nemo_relay/atof_test.go
  • go/nemo_relay/observability_plugin.go
  • go/nemo_relay/observability_plugin_test.go
  • go/nemo_relay/nemo_relay.go
go/nemo_relay/**/*

⚙️ CodeRabbit configuration file

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

Files:

  • go/nemo_relay/atof_test.go
  • go/nemo_relay/observability_plugin.go
  • go/nemo_relay/observability_plugin_test.go
  • go/nemo_relay/nemo_relay.go
{justfile,codecov.yml,codecov.yaml,.github/workflows/**/*.yml,.github/workflows/**/*.yaml}

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

justfile, Codecov, and CI package/test workflows must include new plugin crates and packages.

Files:

  • justfile
justfile

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

Keep justfile build, test, clean, version, and package recipes for plugin crates and packages aligned with the current packaging layout.

Files:

  • justfile
{.github/**,.gitlab-ci.yml,.pre-commit-config.yaml,justfile,scripts/**}

⚙️ CodeRabbit configuration file

{.github/**,.gitlab-ci.yml,.pre-commit-config.yaml,justfile,scripts/**}: Review automation changes for reproducibility, pinned versions where appropriate, secret handling, and consistency with the documented validation matrix.
Pay attention to commands that need generated native artifacts, FFI libraries, or platform-specific environment variables.

Files:

  • justfile
{crates/core,crates/adaptive}/**/*

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

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

Files:

  • crates/core/tests/unit/observability/atof_tests.rs
  • crates/core/src/observability/atof.rs
  • crates/core/tests/unit/observability/plugin_component_tests.rs
  • crates/core/src/observability/plugin_component.rs
crates/core/**/*.rs

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

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

Files:

  • crates/core/tests/unit/observability/atof_tests.rs
  • crates/core/src/observability/atof.rs
  • crates/core/tests/unit/observability/plugin_component_tests.rs
  • crates/core/src/observability/plugin_component.rs
crates/{core,adaptive}/**/*

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

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

Files:

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

Applied to files:

  • crates/node/observability.d.ts
📚 Learning: 2026-07-14T02:53:55.471Z
Learnt from: willkill07
Repo: NVIDIA/NeMo-Relay PR: 414
File: crates/node/observability.d.ts:61-61
Timestamp: 2026-07-14T02:53:55.471Z
Learning: In `crates/node/observability.d.ts` and `crates/node/observability.js`, treat `OtlpConfig`/`otlpConfig` and related helpers as an intentional mirror of the snake_case TOML/plugin configuration schema consumed by `plugin.initialize()`. Do not apply the usual “Node.js public APIs use camelCase” naming review expectation to this plugin-config schema surface. Instead, camelCase review expectations should apply to the native binding surface (e.g., `OpenTelemetrySubscriber`/`OpenInferenceSubscriber` constructors and their `attributeMappings`), which expose camelCase separately.

Applied to files:

  • crates/node/observability.d.ts
  • crates/node/observability.js
📚 Learning: 2026-07-14T02:53:44.529Z
Learnt from: willkill07
Repo: NVIDIA/NeMo-Relay PR: 414
File: crates/node/tests/observability_plugin_tests.mjs:34-34
Timestamp: 2026-07-14T02:53:44.529Z
Learning: Do not flag camelCase style violations for keys returned by observability plugin configuration helpers (e.g., `observability.otlpConfig()` and similar helpers like `atofConfig()` / `atifConfig()`) in the Node observability module and its tests. These helpers intentionally return the snake_case plugin configuration schema consumed by `plugin.initialize()` and written/read via TOML. This is distinct from the Node public API / native subscriber options (e.g., fields like `attributeMappings`) which follow the camelCase guideline; only the plugin-config schema helpers should be exempt.

Applied to files:

  • crates/node/observability.js
  • crates/node/tests/observability_plugin_tests.mjs
🪛 ast-grep (0.44.1)
go/nemo_relay/nemo_relay.go

[warning] 345-345: Narrowing a non-constant integer to a smaller fixed-width type (int8/int16/int32, uint8/uint16/uint32) can silently overflow or wrap, yielding negative or truncated values that are dangerous in size, length, or index logic. Validate the source value is within the target type's range before converting (e.g. bounds-check, or use a checked helper), and avoid narrowing untrusted or len()/parsed values.
Context: int32(status)
Note: [CWE-190] Integer Overflow or Wraparound.

(integer-overflow-narrowing-conversion-go)

🪛 Ruff (0.15.21)
python/nemo_relay/observability.py

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

Remove quotes

(UP037)

python/tests/test_types.py

[warning] 484-484: Unused method argument: tmp_path

(ARG002)

🔇 Additional comments (42)
crates/node/observability.d.ts (1)

11-34: LGTM!

python/nemo_relay/observability.py (1)

58-120: LGTM!

Also applies to: 255-255, 298-299

python/nemo_relay/observability.pyi (1)

23-46: LGTM!

python/tests/test_observability_plugin.py (1)

18-19: LGTM!

Also applies to: 33-33, 57-74, 104-122, 147-153

crates/node/observability.js (1)

13-19: LGTM! The version bump to 2 and dropping the stale top-level mode default are consistent with the new per-sink schema.

Based on learnings, the snake_case fields here (version, enabled) intentionally mirror the plugin configuration schema and are exempt from the Node camelCase convention.

Also applies to: 22-32

go/nemo_relay/nemo_relay.go (2)

337-346: LGTM! Always marshaling config and delegating to create_from_json is a clean simplification and correctly aligns with the FFI contract (nemo_relay_atof_exporter_create_from_json deserializes the tagged sink JSON).


1769-1811: LGTM! NewAtofExporterConfig/NewAtofFileSinkConfig/NewAtofStreamSinkConfig defaults match the Rust core defaults, and Path()'s pointer semantics correctly represent "no local path" for stream-backed exporters.

go/nemo_relay/observability_plugin.go (2)

34-49: LGTM! The Sinks-based model correctly matches the core AtofFileSinkConfig/AtofStreamSinkConfig field sets, and the sealed atofSinkConfig() interface method appropriately restricts sink implementations to this package.

The MarshalJSON field duplication here shares the same pattern flagged in go/nemo_relay/nemo_relay.go; see the consolidated comment.

Also applies to: 64-72


235-253: LGTM! Version 2 default and the sink-config constructors' defaults (append for file, http_post/3000ms/preserve for stream) match core defaults.

go/nemo_relay/atof_test.go (1)

17-44: LGTM! Sink-based construction, Path() pointer handling, and mode assertions are all correctly updated for the new API.

Also applies to: 46-61, 87-87, 102-138

go/nemo_relay/observability_plugin_test.go (1)

29-104: LGTM! Test assertions correctly track the Sinks-based config shape and the version-2 default.

Also applies to: 229-247, 322-338

crates/pii-redaction/tests/unit/component_tests.rs (1)

672-672: LGTM! Correctly adapts to path() now returning Option<PathBuf>, with a clear panic message if the file-sink invariant is ever violated.

justfile (1)

1127-1129: LGTM! Isolating XDG_CONFIG_HOME per test run correctly prevents Python/Node tests from picking up a real ~/.config/nemo-relay, matching core's user_config_dir() resolution order. Confirmed npm's own per-user config defaults to $HOME/.npmrc rather than XDG_CONFIG_HOME, so this isolation doesn't interfere with npm install/npm test.

Also applies to: 1369-1371

crates/node/src/api/mod.rs (3)

175-240: LGTM!


3352-3378: LGTM!


3395-3401: LGTM!

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

36-71: LGTM!

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

21-23: LGTM!

Also applies to: 42-42, 53-66, 76-78, 111-114, 160-164

crates/python/src/py_types/observability.rs (4)

179-184: LGTM!


186-254: LGTM!


349-355: LGTM!


169-169: 🎯 Functional Correctness

from_py_object is a valid #[pyclass] option here; no change needed.

			> Likely an incorrect or invalid review comment.
python/nemo_relay/_native.pyi (1)

833-886: LGTM!

python/tests/test_types.py (1)

15-16: LGTM!

Also applies to: 453-483, 493-524

crates/core/src/observability/atof.rs (4)

169-297: LGTM!


332-353: LGTM!

Also applies to: 368-397


444-520: LGTM!


400-420: 🎯 Functional Correctness

No issue here The workspace targets Rust 2024 and pins rust-toolchain.toml to 1.93.0, so this let chain is supported.

			> Likely an incorrect or invalid review comment.
crates/core/tests/unit/observability/atof_tests.rs (1)

352-368: LGTM!

Also applies to: 371-400, 506-527, 530-548, 551-572, 576-612, 688-869, 872-971, 974-1041, 1044-1100, 1103-1199, 1202-1251, 1272-1287, 1652-1673

crates/core/src/observability/plugin_component.rs (3)

150-218: LGTM!

Also applies to: 486-566


750-794: LGTM!

Also applies to: 1593-1610, 1698-1718, 1841-1854, 1890-2136, 2442-2444


706-748: 🩺 Stability & Availability

Check AtofExporter teardown on drop. If Drop does not fully stop the endpoint worker and close any opened handles, a later sink failure can leave partially constructed exporters leaking resources.

crates/core/tests/unit/observability/plugin_component_tests.rs (1)

21-91: LGTM!

Also applies to: 119-147, 253-305, 358-370, 375-427, 473-633, 627-765, 768-828, 831-925

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

738-799: LGTM!

Also applies to: 960-1049, 1131-1284

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

671-695: LGTM!

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

875-891: LGTM!

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

12-14: LGTM!

Also applies to: 2538-2555

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

3022-3022: LGTM!

Also applies to: 3165-3165, 3509-3509

crates/ffi/nemo_relay.h (1)

1296-1296: LGTM!

crates/ffi/src/api/observability.rs (1)

471-471: LGTM!

Correctly mirrors the core path() -> Option<PathBuf> contract and returns a null pointer with NemoRelayStatus::Ok for stream sinks, consistent with the Python (Option<String>) and Node bindings' handling of the same optional-path semantics.

Also applies to: 489-493

crates/ffi/tests/integration/api/coverage_sweeps_tests.rs (1)

1086-1086: LGTM!

crates/ffi/tests/unit/api/core_tests.rs (1)

204-212: LGTM!

Also applies to: 241-241, 332-332

Comment thread crates/core/tests/unit/observability/plugin_component_tests.rs
Comment thread crates/python/src/py_types/observability.rs
Comment thread go/nemo_relay/nemo_relay.go
Comment thread python/nemo_relay/__init__.py
Comment thread python/tests/test_types.py Outdated
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.

Caution

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

⚠️ Outside diff range comments (3)
crates/core/src/observability/plugin_component.rs (3)

750-794: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Mode/transport/field_name_policy parsing and error strings are duplicated between build_atof_sink_config and validate_atof_stream_sink_values.

Both functions independently re-parse mode, transport, and field_name_policy with hard-coded, duplicated error text. If one copy's accepted values or message wording changes, the other can silently drift, causing validation and registration to disagree.

Consider extracting shared parse-or-diagnostic helpers (e.g. parse_atof_mode(index, &str) -> Result<AtofExporterMode, String>) used by both the validation and build paths.

🤖 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/observability/plugin_component.rs` around lines 750 - 794,
Extract shared parse-and-diagnostic helpers for AtofExporterMode,
AtofEndpointTransport, and AtofEndpointFieldNamePolicy, using the existing
index-aware invalid-value messages. Update build_atof_sink_config and
validate_atof_stream_sink_values to call these helpers so accepted values and
diagnostics remain consistent.

737-745: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Shutdown only surfaces the first exporter's error; later failures are silently dropped.

The loop correctly continues shutting down every exporter after one fails (good), but only the first error is returned/reported — if a second sink also fails to flush/close, that failure is invisible to callers/logs.

♻️ Suggested: collect and log all shutdown errors
         Box::new(move || {
-            let mut first_error = None;
+            let mut errors = Vec::new();
             for exporter in &exporters {
                 if let Err(error) = exporter.shutdown() {
-                    first_error.get_or_insert_with(|| observability_registration_error(error));
+                    errors.push(observability_registration_error(error));
                 }
             }
-            first_error.map_or(Ok(()), Err)
+            errors.into_iter().next().map_or(Ok(()), |error| {
+                tracing::warn!(?errors, "additional ATOF sink shutdown errors");
+                Err(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/core/src/observability/plugin_component.rs` around lines 737 - 745,
Update the shutdown closure around the exporter loop to collect every error
returned by exporter.shutdown(), rather than retaining only the first via
first_error. Ensure all exporters are still attempted, and aggregate or
otherwise report all collected errors through the returned Result so no shutdown
failure is silently discarded.

710-794: 📐 Maintainability & Code Quality | 🔵 Trivial

Run the required validation matrix for this crates/core change. Include cargo fmt --all, cargo clippy --workspace --all-targets -- -D warnings, just test-rust, and the broader Rust/Python/Go/Node matrix.

🤖 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/observability/plugin_component.rs` around lines 710 - 794,
Validate the ATOF changes around build_atof_sink_config and its registration
flow by running cargo fmt --all, cargo clippy --workspace --all-targets -- -D
warnings, and just test-rust. Then run the repository’s documented broader Rust,
Python, Go, and Node validation matrix, fixing any failures before completing
the change.

Source: Coding guidelines

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

Outside diff comments:
In `@crates/core/src/observability/plugin_component.rs`:
- Around line 750-794: Extract shared parse-and-diagnostic helpers for
AtofExporterMode, AtofEndpointTransport, and AtofEndpointFieldNamePolicy, using
the existing index-aware invalid-value messages. Update build_atof_sink_config
and validate_atof_stream_sink_values to call these helpers so accepted values
and diagnostics remain consistent.
- Around line 737-745: Update the shutdown closure around the exporter loop to
collect every error returned by exporter.shutdown(), rather than retaining only
the first via first_error. Ensure all exporters are still attempted, and
aggregate or otherwise report all collected errors through the returned Result
so no shutdown failure is silently discarded.
- Around line 710-794: Validate the ATOF changes around build_atof_sink_config
and its registration flow by running cargo fmt --all, cargo clippy --workspace
--all-targets -- -D warnings, and just test-rust. Then run the repository’s
documented broader Rust, Python, Go, and Node validation matrix, fixing any
failures before completing the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: bfd7630d-bb8d-447a-9b99-b65eb20e698e

📥 Commits

Reviewing files that changed from the base of the PR and between ac34d2e and f2b2a95.

📒 Files selected for processing (8)
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/doctor_tests.rs
  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/tests/coverage/plugins_tests.rs
  • crates/cli/tests/coverage/server_tests.rs
  • crates/cli/tests/coverage/session_tests.rs
  • crates/core/src/observability/plugin_component.rs
  • integrations/openclaw/test/live-smoke.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: Check / Run
  • GitHub Check: Preview docs
🧰 Additional context used
📓 Path-based instructions (14)
**/*.{rs,py,js,mjs,cjs,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{rs,py,js,mjs,cjs,ts,tsx}: 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 the wrapper layer.
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:

  • integrations/openclaw/test/live-smoke.test.ts
  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/session_tests.rs
  • crates/cli/tests/coverage/doctor_tests.rs
  • crates/cli/tests/coverage/server_tests.rs
  • crates/cli/tests/coverage/plugins_tests.rs
  • crates/core/src/observability/plugin_component.rs
**/*.{rs,py,go,js,ts,c,h}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Use language-appropriate naming conventions: Rust snake_case, C FFI exports prefixed nemo_relay_, Go PascalCase, Node.js camelCase, and Python snake_case.

Files:

  • integrations/openclaw/test/live-smoke.test.ts
  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/session_tests.rs
  • crates/cli/tests/coverage/doctor_tests.rs
  • crates/cli/tests/coverage/server_tests.rs
  • crates/cli/tests/coverage/plugins_tests.rs
  • crates/core/src/observability/plugin_component.rs
**/*.{rs,go,js,ts}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Add the SPDX license header to all Rust, Go, JavaScript, and TypeScript source files using the corresponding // comment form.

Files:

  • integrations/openclaw/test/live-smoke.test.ts
  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/session_tests.rs
  • crates/cli/tests/coverage/doctor_tests.rs
  • crates/cli/tests/coverage/server_tests.rs
  • crates/cli/tests/coverage/plugins_tests.rs
  • crates/core/src/observability/plugin_component.rs
**/*.{py,go,js,ts}

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

Keep Python, Go, and Node.js config objects and subscriber/exporter methods aligned so all bindings expose the same logical knobs and semantics.

Files:

  • integrations/openclaw/test/live-smoke.test.ts
**/*

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

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

Files:

  • integrations/openclaw/test/live-smoke.test.ts
  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/session_tests.rs
  • crates/cli/tests/coverage/doctor_tests.rs
  • crates/cli/tests/coverage/server_tests.rs
  • crates/cli/tests/coverage/plugins_tests.rs
  • crates/core/src/observability/plugin_component.rs
**/*.{rs,py,go,js,ts}

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

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

Files:

  • integrations/openclaw/test/live-smoke.test.ts
  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/session_tests.rs
  • crates/cli/tests/coverage/doctor_tests.rs
  • crates/cli/tests/coverage/server_tests.rs
  • crates/cli/tests/coverage/plugins_tests.rs
  • crates/core/src/observability/plugin_component.rs
**/*.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

When changing the core Rust runtime or Rust-facing API surface, format Rust code with cargo fmt (rustfmt defaults), keep cargo clippy -- -D warnings clean, and satisfy cargo deny check per deny.toml.

**/*.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.
For Rust changes headed for review, run cargo fmt --all and cargo clippy --workspace --all-targets -- -D warnings even if relying on pre-commit.

Files:

  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/session_tests.rs
  • crates/cli/tests/coverage/doctor_tests.rs
  • crates/cli/tests/coverage/server_tests.rs
  • crates/cli/tests/coverage/plugins_tests.rs
  • crates/core/src/observability/plugin_component.rs
**/*.{rs,py}

📄 CodeRabbit inference engine (AGENTS.md)

Follow binding naming conventions in Rust and Python: use snake_case.

Files:

  • crates/cli/tests/coverage/launcher_tests.rs
  • crates/cli/tests/cli_tests.rs
  • crates/cli/tests/coverage/session_tests.rs
  • crates/cli/tests/coverage/doctor_tests.rs
  • crates/cli/tests/coverage/server_tests.rs
  • crates/cli/tests/coverage/plugins_tests.rs
  • crates/core/src/observability/plugin_component.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/session_tests.rs
  • crates/cli/tests/coverage/doctor_tests.rs
  • crates/cli/tests/coverage/server_tests.rs
  • crates/cli/tests/coverage/plugins_tests.rs
{crates/core,crates/adaptive}/**/*

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

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

Files:

  • crates/core/src/observability/plugin_component.rs
crates/core/**/*.rs

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

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

Files:

  • crates/core/src/observability/plugin_component.rs
{crates/**/src/**/*.rs,python/**/*.py}

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

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

Files:

  • crates/core/src/observability/plugin_component.rs
crates/{core,adaptive}/**/*

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

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

Files:

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

Applied to files:

  • integrations/openclaw/test/live-smoke.test.ts
🔇 Additional comments (15)
integrations/openclaw/test/live-smoke.test.ts (1)

35-35: LGTM!

crates/core/src/observability/plugin_component.rs (4)

160-217: LGTM!


1895-1963: LGTM! Sink-empty guard and per-sink mode/url/transport validation logic look correct — empty-URL and unknown-transport diagnostics are mutually exclusive from the URL-format check as intended.


2047-2068: LGTM! The #[cfg(not(feature = "atof-streaming"))] let _ = value; correctly silences the unused-parameter warning when the reqwest-gated header-value validation is compiled out.


710-722: 🩺 Stability & Availability

No cleanup gap here. AtofExporter’s worker threads are owned by the exporter’s sender; if construction fails before registration, dropping the temporary Arc<AtofExporter> values closes the channel and the worker loops exit.

			> Likely an incorrect or invalid review comment.
crates/cli/tests/coverage/plugins_tests.rs (3)

188-213: LGTM!


652-695: LGTM! Fixture and assertion updates correctly reflect the new atof.sinks tagged-union shape, including default mode: "append" injection.

Also applies to: 768-795, 817-883


1478-1506: LGTM!

Also applies to: 1985-2013, 2284-2297, 2575-2592

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

573-664: LGTM! Fixtures consistently migrated to the atof.sinks file-sink format, and the invalid-mode assertion correctly matches the updated core validation error path.

Also applies to: 684-770, 773-973, 976-1122, 1125-1241, 1244-1536, 1539-1672, 1839-1871

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

1097-1145: LGTM! The expected check name "ATOF stream sink" and sink-indexed structure match probe_atof_stream_sink in crates/cli/src/doctor.rs.


1183-1215: LGTM!

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

339-398: LGTM!

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

98-120: LGTM!


159-165: LGTM! read_atof_events itself is unchanged; callers correctly .expect() the now-optional file sink path before invoking it.

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

1541-1591: LGTM!

Comment thread crates/core/src/observability/plugin_component.rs
Comment thread crates/core/src/observability/plugin_component.rs
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!

@willkill07

Copy link
Copy Markdown
Member Author

/merge

@rapids-bot
rapids-bot Bot merged commit 300add4 into NVIDIA:main Jul 14, 2026
94 of 114 checks passed
rapids-bot Bot pushed a commit that referenced this pull request Jul 16, 2026
#### Overview

Documents the v2 multi-sink ATOF export configuration introduced by #416. This is a documentation and E2E-fixture follow-up; it does not change runtime behavior.

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

#### Details

- Updates ATOF, ATIF, observability-configuration, and plugin-configuration-file guidance for named sink configurations.
- Updates the coding-agent E2E fixtures and relevant agent-skill references to use the v2 schema.
- Covers tagged sinks, configuration precedence, and migration from the legacy single-sink configuration.
- Contains no implementation changes.
- Breaking changes: none.

Validation:

- `just docs`
- `just docs-linkcheck`
- targeted `uv run pre-commit run --files ...`
- `bash -n` for the three updated E2E scripts

#### Where should the reviewer start?

Start with `docs/configure-plugins/observability/atof.mdx`.

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

- Relates to: #416

Authors:
  - Will Killian (https://github.com/willkill07)

Approvers:
  - Maryam Najafian (https://github.com/mnajafian-nv)
  - https://github.com/lvojtku

URL: #417
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 Feature a new feature lang:go PR changes/introduces Go code lang:js PR changes/introduces Javascript/Typescript code lang:python PR changes/introduces Python code lang:rust PR changes/introduces Rust code size:XL PR is extra large

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants