Skip to content
Radiant LabsRadiant Labs

Getting started

Your first agent

Run a complete graph without credentials, then add a model call.

Start in the hello-polaris project from Installation. First check the execution path without a model or network request.

Replace src/main.rs with this complete program:

use polaris_ai::graph::{Graph, GraphExecutor};
use polaris_ai::system::{server::Server, system};
#[system]
async fn greet() -> String {
"Hello from Polaris!".to_owned()
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut server = Server::new();
server.finish().await?;
let mut graph = Graph::new();
graph.add_system(greet);
assert!(graph.validate().is_ok(), "graph must be valid");
let mut ctx = server.create_context();
let result = GraphExecutor::new()
.execute(&graph, &mut ctx, None, None)
.await?;
println!(
"{}",
result.output::<String>().expect("greet returns a String")
);
Ok(())
}
Terminal window
cargo run

After compilation, the program prints:

Hello from Polaris!

#[system] adapts greet into a graph system. graph.add_system(greet) adds one step. The executor runs it and returns its typed output. graph.validate() checks the graph structure before execution.

server.finish().await? completes plugin startup. It is asynchronous: calling finish() without awaiting it does not initialise your server.

Enable the anthropic feature as shown in Installation. Replace src/main.rs with this complete program:

use polaris_ai::graph::{Graph, GraphExecutor};
use polaris_ai::models::{AnthropicPlugin, ModelRegistry, ModelsPlugin};
use polaris_ai::system::param::Res;
use polaris_ai::system::prelude::SystemError;
use polaris_ai::system::{server::Server, system};
#[system]
async fn ask_llm(registry: Res<ModelRegistry>) -> Result<String, SystemError> {
let model_id = std::env::var("POLARIS_MODEL")
.unwrap_or_else(|_| "anthropic/claude-sonnet-5-5".to_owned());
let llm = registry
.llm(&model_id)
.map_err(|err| SystemError::ExecutionError(err.to_string()))?;
let response = llm
.builder()
.system("Answer in one sentence.")
.user("What is Rust's ownership model?")
.generate()
.await
.map_err(|err| SystemError::ExecutionError(err.to_string()))?;
Ok(response.text())
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut server = Server::new();
server
.add_plugins(ModelsPlugin)
.add_plugins(AnthropicPlugin::from_env("ANTHROPIC_API_KEY"));
server.finish().await?;
let mut graph = Graph::new();
graph.add_system(ask_llm);
assert!(graph.validate().is_ok(), "graph must be valid");
let mut ctx = server.create_context();
let result = GraphExecutor::new()
.execute(&graph, &mut ctx, None, None)
.await?;
println!(
"{}",
result.output::<String>().expect("ask_llm returns a String")
);
Ok(())
}

Set ANTHROPIC_API_KEY in the environment using your normal secret-management method, then run cargo run again. This makes a billable model request. The result should be a one-sentence answer; wording will vary.

The default is Claude Sonnet 5.5. Choose another model available to your account without editing the code:

Terminal window
POLARIS_MODEL=anthropic/claude-opus-5-5 cargo run

anthropic/ selects the registered provider; the rest is the provider’s model ID. See Anthropic’s current model list. To use a different provider, enable its Cargo feature, register its plugin and configure its credentials too.

Authentication errors indicate a missing or invalid key; a model-access error is a separate issue. The pinned Polaris version may not have pricing entries for newer models, so missing cost estimates do not mean a request is free.

  • Add a second system that consumes the previous output with Out<String>: Systems.
  • Run a complete tool-using agent: ReAct CLI.
  • Test your graph without paid model calls: Testing agents.

This first example is intentionally a single model invocation. The graph becomes a multi-step agent when you add tool use, branching, memory and a stopping condition.