Guides
Writing custom plugins
Package resources and startup wiring in a small, testable plugin.
A plugin supplies capabilities to a server. Use one to register per-run resources, shared services, APIs or tools; keep the agent’s control flow in its graph.
Start with one resource
Section titled “Start with one resource”This complete program uses the dependencies from Installation. Replace src/main.rs with:
use polaris_ai::system::plugin::{Plugin, Version};use polaris_ai::system::resource::LocalResource;use polaris_ai::system::server::Server;
#[derive(Default)]struct Scratchpad { notes: Vec<String>,}impl LocalResource for Scratchpad {}
struct ScratchpadPlugin;impl Plugin for ScratchpadPlugin { const ID: &'static str = "hello::scratchpad"; const VERSION: Version = Version::new(0, 1, 0); fn build(&self, server: &mut Server) { server.register_local(Scratchpad::default); }}
#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let mut server = Server::new(); server.add_plugins(ScratchpadPlugin); server.finish().await?; let ctx = server.create_context(); assert!(ctx.get_resource::<Scratchpad>()?.notes.is_empty()); println!("Scratchpad ready"); Ok(())}Run cargo run. It prints Scratchpad ready after checking that the execution context has an empty scratchpad.
register_local installs a factory: each new context receives its own Scratchpad. Use a local resource for per-run state. Shared immutable services instead belong in global resources; see Resources.
Understand the lifecycle
Section titled “Understand the lifecycle”buildperforms synchronous registration and assembly.readyperforms asynchronous finalisation after plugins have been built.server.finish().awaitresolves dependencies and awaits startup.server.run().awaitruns the managed lifecycle when you need scheduling and shutdown.
A ready override must be async fn ready(&self, server: &mut Server). A cleanup override is asynchronous too. Do not copy older synchronous lifecycle signatures into this revision.
Declare what your plugin needs
Section titled “Declare what your plugin needs”If your plugin reads or extends a capability supplied by another plugin, declare that relationship. The current API supports typed Requires<T>, Extends<T> and Optional<T> build parameters through #[plugin], plus explicit PluginAccess declarations for manual implementations. Capability types implement Contract to carry their version.
Use dependencies() for an explicit plugin ordering requirement where appropriate. The repository’s FileToolsPlugin shows the small manual pattern: depend on ToolsPlugin, then register tools during build.
See Plugins for typed capability registration and the public plugin reference for the full contract API. Declaring a provider dependency does not itself add that provider to the server.
Keep assembly visible
Section titled “Keep assembly visible”Add plugins in one place near application startup. Give each plugin a stable, namespaced ID, an explicit VERSION and documentation listing its resources, APIs and dependencies. Test it with only its declared dependencies so unrelated default plugins do not hide missing wiring.
When a plugin owns a long-lived service, provide shutdown behaviour as well as startup. Await the managed lifecycle in the application; a discarded future does not run it.
Copyright © 2026, Radiant AI Limited.