Expand description
A modular framework for building AI agents in Rust.
Polaris is an ECS-inspired runtime for composing AI agents as directed graphs of systems. It provides layered abstractions — from low-level dependency-injected systems, through graph-based execution, up to session management and HTTP serving.
§Why Polaris
Building performant AI agents is a design problem. The bottleneck is not compute, APIs, or infrastructure — it is discovering how an agent should behave for a given use case, and being able to change that behavior quickly when it turns out to be wrong.
Polaris provides composable primitives without prescribing how they should be assembled. There is no default execution loop. Agent behavior is constructed from small, replaceable parts, and the framework imposes no opinion on the result.
§Architecture
Polaris is organized into three layers. Lower layers are fixed primitives; upper layers are swappable.
| Layer | Name | Modules | Scope |
|---|---|---|---|
| 1 | System Framework | system | Systems, resources, plugins, server |
| 2 | Graph Execution | graph, agent | Directed-graph model, agent trait |
| 3 | Plugins | tools, models, plugins, sessions, app, shell | LLM providers, tools, HTTP, sessions |
Layer 1 provides the ECS-inspired primitives: systems as pure async
functions, resources as shared state, dependency injection via typed
parameters, plugins as the unit of composition, and the
Server runtime.
Layer 2 defines how agents are structured: a directed graph of nodes
(computation, control flow) connected by edges (sequential, conditional,
parallel, looping). The Agent trait packages a behavior
pattern as a reusable graph builder.
Layer 3 delivers every optional capability through plugins: LLM providers, tool registries, session management, HTTP serving, and more. Every component is replaceable.
§Quick Start
use polaris_ai::prelude::*;
use polaris_ai::system::system;
use polaris_ai::system::server::Server;
use polaris_ai::plugins::MinimalPlugins;
use polaris_ai::graph::GraphExecutor;
#[system]
async fn greet() -> String {
"Hello from Polaris!".to_string()
}
let mut server = Server::new();
server.add_plugins(MinimalPlugins.build());
server.finish().await;
let graph = {
let mut g = Graph::new();
g.add_system(greet);
g
};
let mut ctx = server.create_context();
let executor = GraphExecutor::new();
let result = executor.execute(&graph, &mut ctx, None, None).await?;
let output = result.output::<String>();§Core Concepts
§Systems and Resources
A system is a pure async function that declares its dependencies as
typed parameters. The #[system] macro generates the boilerplate:
#[system]
async fn reason(llm: Res<LlmClient>, memory: Res<Memory>) -> ReasoningResult {
// Access resources, produce output
ReasoningResult { action: "search".into() }
}Resources are how agents get capabilities. An LLM provider, a tool
registry, a memory backend — each exists as a resource in the
SystemContext:
| Parameter | Resolution | Access | Use for |
|---|---|---|---|
Res<T> | Hierarchy (local → parent → global) | Immutable | Config, registries, per-request input |
ResMut<T> | Current context only | Exclusive | Accumulated state (conversation history) |
Out<T> | Previous system output | Immutable | System-to-system data handoff |
ErrOut<T> | Error-edge output | Immutable | Error context in handler subgraphs |
§Graphs
Agent logic is expressed as a directed graph of systems and control flow:
let mut graph = Graph::new();
graph
.add_system(reason)
.add_conditional_branch::<ReasoningResult, _, _, _>(
"needs_tool",
|r| r.needs_tool,
|g| { g.add_system(execute_tool); },
|g| { g.add_system(respond); },
);Node types: System, Decision, Switch, Parallel, Loop, Scope.
Edge types: Sequential, Conditional, Parallel, LoopBack, Error, Timeout.
The graph’s full topology is inspectable, validated before execution, and
restructured by rewiring edges. See the graph module for the full API.
§Plugins
Every capability is delivered through plugins registered at startup:
let mut server = Server::new();
server
.add_plugins(DefaultPlugins::new().build())
.add_plugins(ToolsPlugin)
.add_plugins(SessionsPlugin::new(Arc::new(InMemoryStore::new())));Plugins have a lifecycle: build() → ready() → update() → cleanup().
Dependencies are declared and resolved automatically. See the system
module for the Plugin trait and the plugins module for built-in
plugin groups.
§Data Flow Patterns
Choosing the right parameter type is critical for correct data flow:
| Pattern | Use | Avoid |
|---|---|---|
| Step A’s result feeds step B | Out<T> — A returns T, B declares Out<T> | ResMut<SharedState> with Option fields |
| Immutable per-request input | Res<T> via ctx.insert(T) in setup closure | ResMut<WorkingState> with .input.clone() |
| Accumulated state (history, counters) | ResMut<T> — local resource | Out<T> — outputs are per-system |
| Shared server-wide config | Res<T> — global resource | ResMut<T> — compile error on globals |
| Error context in handler | ErrOut<CaughtError> | Custom ResMut<LastError> |
§Data Lifetimes
| Data lives… | Mechanism |
|---|---|
| Server lifetime | GlobalResource + Res<T> |
| Session lifetime | LocalResource inserted at session creation |
| Single turn | LocalResource inserted in process_turn_with |
| Between two systems | Return value + Out<T> |
| Error handler subgraph | ErrOut<CaughtError> |
§Common Integration Patterns
| Goal | Pattern | Entry point |
|---|---|---|
| Run one-shot agent | sessions.run_oneshot(&agent_type, |ctx| { ... }) | sessions |
| Multi-turn with cleanup | sessions.scoped_session(&agent_type, |ctx| { ... }) | sessions |
| Execute agent from HTTP | HttpRouter::add_routes_with → SessionsAPI → HttpIOProvider | app |
| Register HTTP routes | server.api::<HttpRouter>().add_routes(router) (stateless) / add_routes_with(|server| ...) (stateful) | app |
| Add tools for LLM | #[tool] macro + ToolRegistry | tools |
| Add model provider | Implement LlmProvider + register via plugin | models |
| Handle system errors | Fallible system + error edge + ErrOut<CaughtError> | graph |
| Schedule plugin updates | tick_schedules() + server.tick::<S>() | system |
§Crate Organisation
| Module | Crate | Purpose |
|---|---|---|
system | polaris_system | ECS-inspired systems, resources, and plugins |
graph | polaris_graph | Directed-graph execution primitives |
agent | polaris_agent | Agent trait for reusable behavior patterns |
tools | polaris_tools | Tool framework for LLM-callable functions |
models | polaris_models / polaris_model_providers | Model registry and provider implementations |
plugins | polaris_core_plugins | Core infrastructure plugins (time, tracing, persistence) |
sessions | polaris_sessions | Session management and orchestration |
shell | polaris_shell | Shell command execution with permission model |
app | polaris_app | HTTP server runtime with plugin integration |
§Exploration Map
| If you want to find… | Start here |
|---|---|
| System primitives, resources, and plugin lifecycle | system |
| Graph nodes, edges, execution, hooks, and middleware | graph |
| LLM providers and provider plugins | models |
| Core infrastructure plugins and observability | plugins |
| Session lifecycle, persistence, and HTTP session routes | sessions |
| Feature-gated exports and which module owns them | Feature Export Map |
§Feature Flags
Every workspace feature that downstream consumers might want is surfaced
through polaris-ai directly, so consumers don’t need to depend on the
inner crates (polaris_app, polaris_core_plugins, etc.). All features
are opt-in except file-store, which is on by default to preserve the
historical session-store behavior — set default-features = false to opt
out. Features that would otherwise be ambiguous at the top level are
prefixed with the sub-crate’s short name (e.g. sessions-http); features
that are already unambiguous keep their original name (e.g. anthropic).
§Model Providers
| Feature | Exported item | Find it under |
|---|---|---|
anthropic | models::AnthropicPlugin | models |
openai | models::OpenAiPlugin | models |
bedrock | models::BedrockPlugin | models |
§Observability
Graph, model, and tool tracing are always on — plugins::TracingPlugin
unconditionally registers graph middleware and decorates the global
models::ModelRegistry and tools::ToolRegistry. With no subscriber
attached the spans have no observable cost.
| Feature | Exported item | Effect |
|---|---|---|
otel | plugins::OpenTelemetryPlugin | Adds OTLP export via the tracing subscriber and switches HTTP request spans in app to OTel HTTP semantic-convention field names |
dashboard | plugins::SpanBuffer, plugins::SpanStorePlugin, plugins::UsagePricing | Mounts /v1/tracing/* endpoints on plugins::TracingPlugin, and turns on the equivalent surfaces in tools::ToolsPlugin / models::ModelsPlugin |
§Tokenization
| Feature | Exported item | Effect |
|---|---|---|
tiktoken | models::tokenizer::TiktokenCounter and models::tokenizer::EncodingFamily | Enables tiktoken-backed counting and models::TokenizerPlugin::default |
§Sessions
| Feature | Exported item | Find it under |
|---|---|---|
sessions-http | sessions::HttpPlugin and sessions::http | sessions |
file-store (default) | sessions::FileStore | sessions |
§Testing
| Feature | Exported item | Effect |
|---|---|---|
test-utils | plugins::MockClock, plugins::MockIOProvider | Mocks for clock and user IO; intended for downstream dev-dependencies |
§Feature Coverage Map
Use this table when the question is “what does feature X expose,
modify, or wire up at runtime?”
| Feature | Adds public items | Also changes | Runtime surface |
|---|---|---|---|
anthropic | models::anthropic, models::AnthropicPlugin | Makes the anthropic/... provider family available through models::ModelRegistry once registered | models::AnthropicPlugin registers the Anthropic provider |
openai | models::openai, models::OpenAiPlugin | Makes the openai/... provider family available through models::ModelRegistry once registered | models::OpenAiPlugin registers the OpenAI provider |
bedrock | models::bedrock, models::BedrockPlugin | Makes the bedrock/... provider family available through models::ModelRegistry once registered | models::BedrockPlugin registers the Bedrock provider |
otel | plugins::OpenTelemetryPlugin | Integrates with plugins::TracingPlugin / plugins::TracingLayers and switches HTTP request spans in app to OTel HTTP semantic-convention field names | plugins::OpenTelemetryPlugin pushes an OTLP export layer into the tracing subscriber; app::AppPlugin emits spans with http.request.method / url.path / http.response.status_code fields (plus otel.name / otel.kind) instead of the polaris.http.* defaults. Does not extract W3C traceparent headers — incoming requests start a fresh trace. |
tiktoken | models::tokenizer::TiktokenCounter, models::tokenizer::EncodingFamily | Adds Default for models::TokenizerPlugin and changes what models::TokenizerPlugin::default builds | models::TokenizerPlugin::default registers a global models::Tokenizer backed by models::tokenizer::TiktokenCounter |
sessions-http | sessions::http, sessions::HttpPlugin, sessions::http::models | Adds request/response model types and HTTP-facing session APIs under sessions | sessions::HttpPlugin registers routes through app::HttpRouter and depends on app::AppPlugin + sessions::SessionsPlugin |
file-store (default) | sessions::FileStore, plugins::FileSpanStore | Pulls tokio/fs into the dep graph | Lets sessions::SessionsPlugin use a filesystem-backed sessions::SessionStore, and (when dashboard is also on) plugins::SpanStorePlugin use a filesystem-backed span store |
dashboard | plugins::SpanBuffer, plugins::SpanStorePlugin, plugins::UsagePricing, plugins::RecordingLayer, /v1/tracing/* and /v1/sessions/{id}/usage endpoints | Activates the per-crate dashboard features on tools::ToolsPlugin, models::ModelsPlugin, and plugins::TracingPlugin so each host plugin mounts its dashboard HTTP surface | plugins::TracingPlugin mounts the tracing-buffer endpoints, tools::ToolsPlugin mounts a frozen-snapshot /v1/tools endpoint, and models::ModelsPlugin mounts /v1/models/providers |
typegen | None at runtime | Enables ts-rs derives on canonical wire types (SessionMetadata, SessionStatus, span/run/usage projections, …) | Running cargo test --features typegen regenerates the TypeScript bindings under bindings/ts/src/ |
test-utils | plugins::MockClock, plugins::MockIOProvider | None at runtime | Provides mocks for downstream test suites that exercise plugins::TimePlugin / plugins::IOProvider |
Modules§
- agent
- Agent trait for defining reusable behavior patterns.
- apis
- A catalog of every plugin-to-plugin coordination
APIexported bypolaris-ai. - app
- HTTP server runtime with plugin-based route composition.
- graph
- Directed-graph execution primitives for agent behavior.
- models
- Model registry and LLM provider implementations.
- plugins
- Core infrastructure plugins for the Polaris runtime, plus a catalog of every plugin shipped by this workspace.
- prelude
- Re-export all common types for easy access.
- resources
- A catalog of every consumer-facing
GlobalResourceandLocalResourceexported bypolaris-ai. - sessions
- Session management and agent orchestration.
- shell
- Shell command execution with a layered permission model.
- system
- ECS-inspired systems, resources, plugins, and the server runtime.
- tools
- Tool framework for LLM-callable functions.