About Build Plugins
A plugin turns reusable runtime behavior into a named, configurable component. It can observe Relay events, remove sensitive fields from observability records, reject or rewrite tool and model requests, wrap real execution, or combine those responsibilities when one configuration contract genuinely controls them.
The smallest useful plugin has four visible stages:
- Define the implementation.
- Register its stable kind.
- Activate one configured component.
- Clear the behavior owned by that component.
The complete language-binding quickstart shows
this application-owned example in Python, Node.js, and Rust. In every version, the tool
request intercept adds plugin_tag to the real tool arguments, so the final assertion
proves that activation changed execution rather than only producing a successful report.
The implementation object is not middleware by itself. Relay calls register only for
an enabled, valid component and owns every callback installed through the supplied
context. That ownership lets cleanup remove the intercept before the kind is
deregistered.
The first design decision is where the plugin should run. That choice determines how quickly callbacks execute, what must be packaged, which failures can affect the host, and how much control the application keeps over dependencies.
Choose the Execution Model
Language-binding plugins are appropriate for application-owned behavior such as adding a tenant header, enforcing a local model policy, or installing an event subscriber beside the code that consumes it. They have the simplest build and distribution path, so start with Language Binding Plugins unless a separate artifact solves a concrete operational problem.
Native Rust plugins suit reusable middleware whose callback latency or throughput is
important enough to justify platform-specific binaries and full in-process trust. A
native plugin uses manifest compatibility compat.native_api = "1"; the current SDK
negotiates C host-table ABI v4 and retains frozen v3 and v2 host-table compatibility.
Those are different version axes, as the Native ABI Reference
explains.
Worker plugins suit dependencies that should live outside the application environment,
teams that need a different runtime, or deployments that want a separate crash boundary.
That separation comes with gRPC, JSON-envelope, and scheduling overhead. The Python and
Rust SDKs implement the same grpc-v1 contract. A custom command worker is an advanced
protocol implementation path rather than a fourth plugin model.
Relay 0.8 makes the tool result explicit at every dynamic boundary. A managed tool
callback and its continuation return a ToolExecutionResult: the application payload in
result and an optional opaque annotation. A tool execution intercept returns that
same pair plus Relay-owned pending marks. Native and worker plugins built for an earlier
release must rebuild for this contract. Workers retain the grpc-v1 name and protobuf
package; their tool-result fields, not their protocol identity, changed.
Match Behavior to a Plugin
Whichever model you choose, the shared contract comes first. Plugin Shape explains lifecycle and ownership, Configuration and Validation defines the operator-facing boundary, and PluginContext describes every registration surface. Each model-specific section then follows a complete path from invalid configuration through activation, representative execution, observable verification, and teardown.
Plugins can also own gates that control whether another global runtime registration is included in future snapshots. Read Conditional Middleware Guardrails before adding this operational control to any delivery model.