Skip to content

feat(ffi): initialize with dynamic plugins#368

Merged
rapids-bot[bot] merged 11 commits into
NVIDIA:mainfrom
bbednarski9:bbednarski/ffi-dynamic-plugin-host
Jul 14, 2026
Merged

feat(ffi): initialize with dynamic plugins#368
rapids-bot[bot] merged 11 commits into
NVIDIA:mainfrom
bbednarski9:bbednarski/ffi-dynamic-plugin-host

Conversation

@bbednarski9

@bbednarski9 bbednarski9 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

This exposes the shared owned dynamic-plugin host through the C nemo_relay_initialize_with_dynamic_plugins and Go InitializeWithDynamicPlugins entry points for evaluation. Static-only harness-native applications continue using nemo_relay_initialize_plugins or Go InitializePlugins. Dynamic activation requires at least one specification.

At startup, the binding discovers the standard plugins.toml files once, layers the explicit configuration over them, and then appends components loaded from the dynamic-plugin specifications. File-configured and explicitly configured static components initialize before dynamic components. Configuration files are not watched or reloaded.

  • expose the shared dynamic-plugin host through nemo_relay_initialize_with_dynamic_plugins, returning an opaque C ABI activation handle
  • return the validation report as caller-released JSON while keeping all library and worker ownership in Rust
  • layer explicit configuration over one-time plugins.toml discovery with explicit values taking precedence
  • require explicit resolved manifest paths for dynamic plugins; installation-state and manifest discovery remain outside the binding
  • provide sequentially idempotent clear/free functions with null and pointer-to-pointer safety
  • serialize concurrent C clear calls, while requiring callers to synchronize free against every use of the raw activation handle
  • report a C clear failure only on the call that performs teardown because the activation is consumed regardless of outcome
  • provide a Go PluginActivation wrapper with synchronous explicit Close and asynchronous defensive finalization
  • make copied Go activation values share one cleanup state and the same cached close result
  • reject nil or empty dynamic specifications before crossing CGo; the C API returns InvalidArg with actionable guidance
  • preserve the existing public Go config structs and JSON behavior while using a private activation wire shape so disabled static components remain disabled
  • preserve thread-local FFI diagnostics across Go/CGo calls
  • mark the dynamic activation surface as experimental in Rust docs, the generated C header, Go docs, and binding READMEs

Reviewer guidance

  • The entry points are named as initialization APIs because they perform one-shot discovered, static, and dynamic component initialization.
  • This revision is naming-only: lifecycle behavior, validation, layering, cleanup, and ownership are unchanged.
  • The old C and Go names are intentionally not retained as aliases because this experimental API has not shipped.

Validation

  • native and worker dynamic plugins load through the C ABI and Go/CGo wrapper
  • a static component discovered from a project plugins.toml and an explicitly supplied dynamic plugin activate together and both callbacks execute
  • explicit configuration overrides discovered values, static components initialize before dynamic components, and discovery occurs only once at startup
  • callbacks are unavailable after close
  • valid-first/invalid-second activation rolls back without leaking kinds or callbacks
  • asynchronous defensive finalization releases process-wide ownership without blocking Go's finalizer goroutine
  • empty specifications, invalid JSON, missing manifests, repeated sequential clear/free, copied or concurrent Go close, cached teardown errors, null cleanup, and incomplete FFI outputs are covered
  • public Go config JSON remains backward-compatible while activation serializes disabled static components explicitly
  • cargo test -p nemo-relay-ffi --all-features — 79 unit and 74 integration tests passed
  • cargo clippy --workspace --all-targets -- -D warnings
  • full go test -v ./... suite passed across all Go binding packages
  • focused finalizer and parser tests passed 20 repetitions under the Go race detector
  • go vet ./..., formatting, generated-header synchronization, and pre-commit checks passed

Production gate

Before removing the DO NOT MERGE label, identify a real consumer and validate at minimum: host startup/shutdown integration, activation-handle ownership, in-flight callback teardown, failure diagnostics, packaging/linking, and repeated process lifecycle behavior.

Stack

Summary by CodeRabbit

  • New Features
    • Added experimental dynamic plugin activation in the FFI and Go bindings, including an opaque activation handle and JSON activation reports.
    • Introduced new lifecycle APIs to clear and free activation handles.
    • Enabled layering discovered plugins.toml configuration with explicitly provided activation specs.
  • Bug Fixes
    • Improved robustness of activation teardown (idempotent clear/free and safer cleanup after failures).
    • Added stricter validation for empty/invalid specs and malformed inputs.
  • Documentation
    • Added prominent “not production-ready” warnings for the experimental activation lifecycle.
  • Tests
    • Added extensive Rust and Go unit/integration tests covering activation, layering, rollback, cleanup, and finalization behavior.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds transactional dynamic plugin activation across the Rust core, FFI, and Go bindings, with discovered configuration layering, opaque ownership handles, explicit cleanup, native and worker support, and lifecycle validation.

Changes

Dynamic plugin activation

Layer / File(s) Summary
Discovered configuration activation
crates/core/src/plugin.rs, crates/core/src/plugin/dynamic/..., crates/core/tests/integration/...
Resolves discovered plugin configuration before activation and validates combined static and dynamic plugin registration.
Rust FFI activation API
crates/ffi/nemo_relay.h, crates/ffi/src/api/..., crates/ffi/src/types/...
Adds activation, clear, and free APIs with JSON parsing, transactional loading, reports, mutex-protected state, and idempotent cleanup.
Go activation and teardown lifecycle
go/nemo_relay/plugin.go
Adds dynamic plugin specifications, activation serialization, OS-thread-affined FFI calls, synchronized Close, and finalizer cleanup.
Rust and Go lifecycle validation
crates/ffi/tests/..., go/nemo_relay/plugin_activation_test.go
Tests invalid inputs, rollback, native and worker activation, interception behavior, serialization, copied-handle teardown, finalizers, and ownership release.
Build and lifecycle contract notes
crates/ffi/Cargo.toml, crates/ffi/README.md, go/nemo_relay/README.md
Adds worker FFI support, a test dependency, and experimental lifecycle warnings.

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

Sequence Diagram(s)

sequenceDiagram
  participant GoCaller
  participant GoBinding
  participant RustFFI
  participant PluginHostActivation
  participant DynamicPlugin
  GoCaller->>GoBinding: ActivateDynamicPlugins(config, specs)
  GoBinding->>RustFFI: send serialized activation request
  RustFFI->>PluginHostActivation: activate parsed specifications
  PluginHostActivation->>DynamicPlugin: load and start plugin
  DynamicPlugin-->>PluginHostActivation: activation result
  PluginHostActivation-->>RustFFI: activation handle and report
  RustFFI-->>GoBinding: return handle and report JSON
  GoCaller->>GoBinding: Close()
  GoBinding->>RustFFI: clear and free activation
  RustFFI->>DynamicPlugin: stop and unload plugin
Loading

Possibly related PRs

  • NVIDIA/NeMo-Relay#364: Introduces the core owned dynamic-plugin activation lifecycle used by these FFI APIs.
  • NVIDIA/NeMo-Relay#365: Adds Python bindings over the same discovered-configuration activation and owned-handle lifecycle.
  • NVIDIA/NeMo-Relay#366: Adds Node API coverage that delegates to the native dynamic-plugin activation entrypoint.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title follows Conventional Commits and accurately summarizes the dynamic-plugin activation change.
Description check ✅ Passed The description is detailed and covers the change, validation, and rollout guidance, though it does not follow the exact template.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@github-actions github-actions Bot added size:XL PR is extra large Feature a new feature lang:go PR changes/introduces Go code lang:rust PR changes/introduces Rust code labels Jul 6, 2026
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

License Diff

Compared against origin/main.

Lockfile license changes

Lockfile License Changes

Rust

Added

  • None

Removed

  • None

Updated/Changed

  • None

Node

Added

  • None

Removed

  • None

Updated/Changed

  • None

Python

Added

  • None

Removed

  • None

Updated/Changed

  • None
Status output
[license-diff] selected languages: rust, node, python
[license-diff] generating current inventory
[license-diff] current: generating Rust inventory
[license-diff] current: Rust inventory complete (379 packages)
[license-diff] current: generating Node inventory
[license-diff] current: Node inventory complete (363 packages)
[license-diff] current: generating Python inventory
[license-diff] current: Python inventory complete (105 packages)
[license-diff] current inventory complete
[license-diff] checking out base ref origin/main into a temporary worktree
[license-diff] base: generating Rust inventory
[license-diff] base: Rust inventory complete (379 packages)
[license-diff] base: generating Node inventory
[license-diff] base: Node inventory complete (363 packages)
[license-diff] base: generating Python inventory
[license-diff] base: Python inventory complete (105 packages)
[license-diff] base inventory complete
[license-diff] removing temporary base worktree
[license-diff] comparing inventories
[license-diff] rendering Markdown output
[license-diff] done

@willkill07 willkill07 added this to the 0.6 milestone Jul 6, 2026
@bbednarski9
bbednarski9 force-pushed the bbednarski/ffi-dynamic-plugin-host branch from 39e39dd to ef56a85 Compare July 7, 2026 00:32
@bbednarski9
bbednarski9 force-pushed the bbednarski/ffi-dynamic-plugin-host branch from ef56a85 to ad20605 Compare July 7, 2026 14:17
@bbednarski9
bbednarski9 force-pushed the bbednarski/ffi-dynamic-plugin-host branch from ad20605 to beb3d68 Compare July 7, 2026 19:55
@github-actions github-actions Bot added size:XXL PR is very large and removed size:XL PR is extra large labels Jul 7, 2026
@bbednarski9 bbednarski9 added the DO NOT MERGE PR should not be merged; see PR for details label Jul 7, 2026
@bbednarski9 bbednarski9 changed the title feat(ffi): activate dynamic plugins [EXPERIMENTAL] feat(ffi): activate dynamic plugins Jul 7, 2026
@github-actions github-actions Bot removed the Feature a new feature label Jul 7, 2026
@bbednarski9
bbednarski9 force-pushed the bbednarski/ffi-dynamic-plugin-host branch from beb3d68 to e2e5e8c Compare July 14, 2026 04:17
@github-actions github-actions Bot added size:XL PR is extra large and removed size:XXL PR is very large labels Jul 14, 2026
@bbednarski9
bbednarski9 marked this pull request as ready for review July 14, 2026 04:56
@bbednarski9
bbednarski9 requested a review from a team as a code owner July 14, 2026 04:56
@bbednarski9 bbednarski9 changed the title [EXPERIMENTAL] feat(ffi): activate dynamic plugins feat(ffi): activate dynamic plugins Jul 14, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
crates/ffi/src/types/mod.rs (1)

296-320: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Docs now warn about the clear/free race, but free() still doesn't synchronize with clear()'s mutex — UAF risk remains.

nemo_relay_plugin_activation_clear (in crates/ffi/src/api/plugin.rs) holds a MutexGuard on activation.0 for the full teardown call. free() here drops the entire Box<FfiPluginActivation> (mutex included) without ever locking it first. This is the same critical race flagged in prior review rounds — only the doc comment changed this round; the implementation is untouched.

As per path instructions, "Check for... FFI ownership/lifetime safety" for binding changes. Given the raw C ABI is meant for consumers beyond the Go binding, and the PR is gated on real-consumer validation, this residual UAF should be fixed with actual synchronization (e.g., shared Arc<Mutex<...>> so free() can fence on the same lock) rather than relying solely on caller-observed documentation.

🔒 Sketch of a fence-before-drop fix
 pub unsafe extern "C" fn nemo_relay_plugin_activation_free(ptr: *mut *mut FfiPluginActivation) {
     if ptr.is_null() {
         return;
     }
     let activation = unsafe { ptr.replace(std::ptr::null_mut()) };
     if !activation.is_null() {
+        // Fence on the same mutex `clear()` locks so a live guard's unlock
+        // never runs against freed memory.
+        if let Ok(mut guard) = unsafe { &*activation }.0.lock() {
+            guard.take();
+        }
         drop(unsafe { Box::from_raw(activation) });
     }
 }
🤖 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/src/types/mod.rs` around lines 296 - 320, Synchronize
nemo_relay_plugin_activation_free with nemo_relay_plugin_activation_clear before
dropping the activation allocation. Make the activation state use a shared
synchronization primitive that both functions lock, have free acquire the same
lock and complete teardown fencing before releasing the Box, and preserve the
existing nulling and repeated-call behavior.
🤖 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.

Duplicate comments:
In `@crates/ffi/src/types/mod.rs`:
- Around line 296-320: Synchronize nemo_relay_plugin_activation_free with
nemo_relay_plugin_activation_clear before dropping the activation allocation.
Make the activation state use a shared synchronization primitive that both
functions lock, have free acquire the same lock and complete teardown fencing
before releasing the Box, and preserve the existing nulling and repeated-call
behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: a2f3593e-09d4-4ca8-a2d2-f390849e7788

📥 Commits

Reviewing files that changed from the base of the PR and between 23492c7 and bded6f5.

📒 Files selected for processing (5)
  • crates/ffi/nemo_relay.h
  • crates/ffi/src/api/plugin.rs
  • crates/ffi/src/types/mod.rs
  • go/nemo_relay/plugin.go
  • go/nemo_relay/plugin_activation_test.go
📜 Review details
🧰 Additional context used
📓 Path-based instructions (21)
crates/ffi/**

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

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

Files:

  • crates/ffi/nemo_relay.h
  • crates/ffi/src/types/mod.rs
  • crates/ffi/src/api/plugin.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
**/*.{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/nemo_relay.h
  • crates/ffi/src/types/mod.rs
  • crates/ffi/src/api/plugin.rs
  • go/nemo_relay/plugin.go
  • go/nemo_relay/plugin_activation_test.go
{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/plugin.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/nemo_relay.h
  • crates/ffi/src/types/mod.rs
  • crates/ffi/src/api/plugin.rs
  • go/nemo_relay/plugin.go
  • go/nemo_relay/plugin_activation_test.go
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/nemo_relay.h
  • crates/ffi/src/types/mod.rs
  • crates/ffi/src/api/plugin.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/ffi/src/types/mod.rs
  • crates/ffi/src/api/plugin.rs
crates/ffi/**/*.rs

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

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

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

Files:

  • crates/ffi/src/types/mod.rs
  • crates/ffi/src/api/plugin.rs
**/*.{rs,py}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • crates/ffi/src/types/mod.rs
  • crates/ffi/src/api/plugin.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/src/types/mod.rs
  • crates/ffi/src/api/plugin.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/src/types/mod.rs
  • crates/ffi/src/api/plugin.rs
  • go/nemo_relay/plugin.go
  • go/nemo_relay/plugin_activation_test.go
{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/types/mod.rs
  • crates/ffi/src/api/plugin.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/src/types/mod.rs
  • crates/ffi/src/api/plugin.rs
  • go/nemo_relay/plugin.go
  • go/nemo_relay/plugin_activation_test.go
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/plugin.go
  • go/nemo_relay/plugin_activation_test.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/plugin.go
  • go/nemo_relay/plugin_activation_test.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/plugin.go
  • go/nemo_relay/plugin_activation_test.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/plugin.go
  • go/nemo_relay/plugin_activation_test.go
**/*.{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:

  • go/nemo_relay/plugin.go
  • go/nemo_relay/plugin_activation_test.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/plugin.go
  • go/nemo_relay/plugin_activation_test.go
go/nemo_relay/**/*

⚙️ CodeRabbit configuration file

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

Files:

  • go/nemo_relay/plugin.go
  • go/nemo_relay/plugin_activation_test.go
{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:

  • go/nemo_relay/plugin_activation_test.go
🔇 Additional comments (6)
crates/ffi/nemo_relay.h (1)

1469-1481: LGTM!

Also applies to: 2535-2543

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

275-287: LGTM!

go/nemo_relay/plugin.go (2)

118-154: LGTM!

Also applies to: 171-180


447-452: LGTM!

go/nemo_relay/plugin_activation_test.go (2)

13-13: LGTM!

Also applies to: 30-31, 961-991, 993-1039


298-360: LGTM!

rapids-bot Bot pushed a commit that referenced this pull request Jul 14, 2026
## Overview

Adds a shared harness-native activation path that layers explicit plugin configuration over the default discovered plugins.toml files before loading dynamic plugins. The existing exact activation path remains available for hosts such as the Relay CLI that already resolved their configuration.

## Details

- Reuse the same one-time config resolution used by static initialization.
- Add PluginHostActivation::activate_with_discovered_config for language and FFI bindings.
- Keep PluginHostActivation::activate exact and free of implicit discovery.
- Validate that a file-configured static component and a dynamic component activate in one owned transaction.

## Validation

- cargo test -p nemo-relay --test native_plugin_integration (28 passed)
- cargo clippy -p nemo-relay --all-targets -- -D warnings
- cargo fmt --all -- --check
- git diff --check

## Related Issues

Relates to #365, #366, and #368.


## Summary by CodeRabbit

* **New Features**
  * Added a plugin activation entrypoint that layers discovered `plugins.toml` configuration with dynamically provided plugin components.
* **Refactor**
  * Improved configuration resolution by centralizing how default/discovered configuration is merged with provided settings before activation.
* **Bug Fixes**
  * Ensured activation cleanup correctly deregisters the discovered static-base plugin kind after clearing, without impacting dynamic registrations.
* **Tests**
  * Added an end-to-end integration test covering combined discovered + dynamic activation and verifying post-clear deregistration.

Authors:
  - Bryan Bednarski (https://github.com/bbednarski9)

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

URL: #418
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
@bbednarski9
bbednarski9 force-pushed the bbednarski/ffi-dynamic-plugin-host branch from bded6f5 to ccf3991 Compare July 14, 2026 13:46
@bbednarski9 bbednarski9 removed the DO NOT MERGE PR should not be merged; see PR for details label Jul 14, 2026
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
@bbednarski9 bbednarski9 added experimental Experimental or early-access feature DO NOT MERGE PR should not be merged; see PR for details labels Jul 14, 2026
@bbednarski9 bbednarski9 changed the title feat(ffi): activate dynamic plugins feat(ffi): initialize with dynamic plugins Jul 14, 2026
@bbednarski9
bbednarski9 marked this pull request as draft July 14, 2026 15:15
@bbednarski9
bbednarski9 marked this pull request as ready for review July 14, 2026 16:09
@bbednarski9

Copy link
Copy Markdown
Contributor Author

/ok to test d68108f

@willkill07 willkill07 removed the DO NOT MERGE PR should not be merged; see PR for details label Jul 14, 2026
@willkill07

Copy link
Copy Markdown
Member

/merge

@rapids-bot
rapids-bot Bot merged commit 8cd4c74 into NVIDIA:main Jul 14, 2026
41 of 42 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

experimental Experimental or early-access feature Feature a new feature lang:go PR changes/introduces Go 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