Skip to content
Radiant LabsRadiant Labs

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.

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.

  • build performs synchronous registration and assembly.
  • ready performs asynchronous finalisation after plugins have been built.
  • server.finish().await resolves dependencies and awaits startup.
  • server.run().await runs 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.

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.

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.