About Native Dynamic Plugins
A native dynamic plugin is a trusted Rust shared library that Relay loads into its own
process. It follows the same validation and PluginContext contract as other plugins,
but typed middleware crosses a stable C boundary without a worker process or gRPC JSON
envelope. This model is appropriate when reusable callback behavior is sensitive to
latency or throughput and the deployment can carry a platform-specific binary.
Native plugins are not sandboxed. They share the host address space, allocator boundary, and process fate. Load only reviewed artifacts, keep ABI ownership rules intact, and assume a native crash can terminate the Relay host.
Version Contracts
Three version values answer different questions:
Typed async middleware and SDK executor configuration require the 0.8 contract, so the
checked example declares compat.relay = ">=0.8.0,<1.0" and depends on
nemo-relay-plugin 0.8.0. A manifest that admits an older host cannot promise those
surfaces.
Relay 0.8 changes the native API 1 tool-result JSON contract without changing the v4
host-table layout. A tool callback and ToolNext continuation now return
ToolExecutionResult, which carries result and optional opaque annotation; an
execution intercept returns that pair plus pending marks. Rebuild native plugins for
this contract even though the negotiated host-table ABI remains v4.
The v4 SDK also lets a plugin add a data schema and log severity to a mark, or emit
validated metric measurements. PluginRuntime::runtime_diagnostics() returns the
current host-level diagnostic snapshot. The snapshot is ordered by code and is not
attributed to a particular plugin.
What the SDK Owns
The Rust SDK exports the stable entry symbol, converts host-owned JSON handles into
typed DTOs, registers all 16 plugin surfaces, and drives async middleware on one
SDK-owned multi-thread Tokio runtime per configured component. A plugin can set a
default executor size and accept a positive executor.worker_threads component
override. The default is two workers; change it only after measuring queued async work
and account for the number of native components in the host.
Subscribers remain synchronous and run on Relay’s subscriber dispatcher. Typed middleware returns futures and runs on the SDK executor. A callback has no stable OS thread affinity, separate invocations can overlap, and blocking an executor thread can delay unrelated calls from the same component.
The checked-in examples/rust-native-plugin project is the end-to-end implementation.
Its configuration, observation, request policy, execution wrappers, and runtime helpers
are separated into modules so each following page can explain one responsibility
without presenting a monolithic sample.
Implement the Native Plugin Entrypoint
The root module is intentionally small. validate checks both the example settings and
the SDK-owned executor override. register parses the same settings once, obtains the
component runtime handle, and delegates each feature group. The export macro produces
the nemo_relay_register_plugin symbol named in the manifest.
The kind in plugin_kind() must match [plugin].id in relay-plugin.toml. Returning
false from allows_multiple_components tells Relay to reject a document that tries to
activate two configurations of this native implementation. The SDK owns registrations
created through context; the plugin does not keep raw registry handles or unload them
itself.
Complete Path
Follow these pages in order to build, activate, exercise, and remove the native plugin:
- Follow Build and Package to build the
cdylib, validate its schema and manifest, calculate integrity, and register it. - Add observability behavior with Observe and Sanitize, including subscribers and all three event sanitizer surfaces.
- Add policy and request rewriting with Control Requests,
preserving annotations and making priority and
break_chainexplicit. - Add tool, unary model, and lazy stream wrappers with Wrap Execution.
- Verify marks, scopes, isolated stacks, cleanup, and executor control with Runtime Events and Scopes.
- Consult Native ABI Reference only when implementing or auditing the raw boundary.
Success is not merely a library that loads. The atomic lifecycle test builds the cdylib
in an isolated target directory, materializes and integrity-checks its manifest, activates
a valid component, executes a managed tool call, observes the runtime mark, and clears
registrations before the library unloads. The focused configuration tests cover rejected
input and schema shape; the scenario pages show the remaining callback contracts.