Core concepts
Agents
An agent packages a graph as a reusable, named pattern.
An agent packages a graph as a reusable pattern. You implement the Agent trait to define a named behavior that can be registered with the sessions layer, persisted, and shared across your application.
use polaris_ai::agent::Agent;use polaris_ai::graph::Graph;use polaris_ai::system::system;
#[system]async fn summarize() -> String { "A short summary".into()}
#[system]async fn evaluate(summary: polaris_ai::system::param::Out<String>) -> usize { summary.len()}
struct SummarizeAgent;
impl Agent for SummarizeAgent { fn build(&self, graph: &mut Graph) { graph .add_system(summarize) .add_system(evaluate); }
fn name(&self) -> &'static str { "SummarizeAgent" }}Implement build and name; override setup when you need session initialization. to_graph is provided for you.
build(&self, graph: &mut Graph) constructs the graph that defines the agent’s behavior. This is called once when the agent is registered with the server. Everything you have seen on the Graphs page applies here: you add systems, branches, loops, and parallel sections to the graph argument.
Because build receives &self, you can store configuration on the agent struct and use it to vary the graph structure at registration time.
name() returns a stable &'static str identifier for the agent type. The sessions layer uses this name to associate sessions with the correct agent, so it must not change between releases if you are persisting sessions.
setup(&self, ctx: &mut SystemContext) runs before the first turn of a session. You can read configuration from the context, resolve an LLM handle, or insert session-scoped resources that your systems will need. The default implementation is a no-op, so you only override it when your agent needs initialization beyond what plugins provide.
impl Agent for ReActAgent { fn setup(&self, ctx: &mut SystemContext<'static>) -> Result<(), SetupError> { let config = ctx.get_resource::<AgentConfig>().map_err(SetupError::new)?; let llm = ctx .get_resource::<ModelRegistry>() .map_err(SetupError::new)? .llm(&config.model_id) .map_err(SetupError::new)?; ctx.insert(AgentLlm(llm)); Ok(()) }
// ...}to_graph
Section titled “to_graph”to_graph(&self) -> Graph is a convenience method that creates a new Graph, calls build, and returns it. You do not need to implement this yourself. It is useful in tests or when you need the graph as a standalone value.
let agent = SummarizeAgent;let graph = agent.to_graph();assert_eq!(graph.node_count(), 2);The ReAct skeleton
Section titled “The ReAct skeleton”A more realistic agent uses the graph primitives together. Here is the ReAct pattern: loop through LLM calls, branch on whether the model wants to use tools, and exit when it produces a final response.
impl Agent for ReActAgent { fn build(&self, graph: &mut Graph) { graph.add_system(receive_user_input); graph.add_system(init_loop);
graph.add_loop::<ReactState, _, _>( "react_loop", |state| state.is_complete, |g| { g.add_system(act); g.add_conditional_branch::<LlmResponse, _, _, _>( "has_tool_calls", |response| response.has_tool_calls(), |tool_branch| { tool_branch.add_system(execute_tools); }, |done_branch| { done_branch.add_system(finalize); }, ); g.add_error_handler(|h| { h.add_system(recover); }); }, ); }
fn name(&self) -> &'static str { "ReActAgent" }}Each system here is a regular async function annotated with #[system]. The agent does not execute anything itself. It builds a graph, and the GraphExecutor runs it. Plugins provide the resources those systems need: the ModelRegistry for LLM access, the ToolRegistry for tool execution, and so on.
That is the full picture. An agent builds a graph. The graph contains systems. Systems use resources. Plugins provide those resources. The Agent trait ties these layers into a single, named, registerable unit.
Copyright © 2026, Radiant AI Limited.