Skip to main content

polaris_ai/
lib.rs

1#![cfg_attr(docsrs_dep, feature(doc_cfg))]
2
3//! A modular framework for building AI agents in Rust.
4//!
5//! Polaris is an ECS-inspired runtime for composing AI agents as directed
6//! graphs of systems. It provides layered abstractions — from low-level
7//! dependency-injected systems, through graph-based execution, up to
8//! session management and HTTP serving.
9//!
10//! # Why Polaris
11//!
12//! Building performant AI agents is a design problem. The bottleneck is not
13//! compute, APIs, or infrastructure — it is discovering how an agent should
14//! behave for a given use case, and being able to change that behavior quickly
15//! when it turns out to be wrong.
16//!
17//! Polaris provides composable primitives without prescribing how they should
18//! be assembled. There is no default execution loop. Agent behavior is
19//! constructed from small, replaceable parts, and the framework imposes no
20//! opinion on the result.
21//!
22//! # Architecture
23//!
24//! Polaris is organized into three layers. Lower layers are fixed primitives;
25//! upper layers are swappable.
26//!
27//! | Layer | Name | Modules | Scope |
28//! |-------|------|---------|-------|
29//! | **1** | System Framework | [`system`] | Systems, resources, plugins, server |
30//! | **2** | Graph Execution | [`graph`], [`agent`] | Directed-graph model, agent trait |
31//! | **3** | Plugins | [`tools`], [`models`], [`plugins`], [`sessions`], [`app`], [`shell`] | LLM providers, tools, HTTP, sessions |
32//!
33//! **Layer 1** provides the ECS-inspired primitives: systems as pure async
34//! functions, resources as shared state, dependency injection via typed
35//! parameters, plugins as the unit of composition, and the
36//! [`Server`](system::server::Server) runtime.
37//!
38//! **Layer 2** defines how agents are structured: a directed graph of nodes
39//! (computation, control flow) connected by edges (sequential, conditional,
40//! parallel, looping). The [`Agent`](agent::Agent) trait packages a behavior
41//! pattern as a reusable graph builder.
42//!
43//! **Layer 3** delivers every optional capability through plugins: LLM
44//! providers, tool registries, session management, HTTP serving, and more.
45//! Every component is replaceable.
46//!
47//! # Quick Start
48//!
49//! ```no_run
50//! # use polaris_ai::polaris_system;
51//! use polaris_ai::prelude::*;
52//! use polaris_ai::system::system;
53//! use polaris_ai::system::server::Server;
54//! use polaris_ai::plugins::MinimalPlugins;
55//! use polaris_ai::graph::GraphExecutor;
56//!
57//! #[system]
58//! async fn greet() -> String {
59//!     "Hello from Polaris!".to_string()
60//! }
61//!
62//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
63//! let mut server = Server::new();
64//! server.add_plugins(MinimalPlugins.build());
65//! server.finish().await;
66//!
67//! let graph = {
68//!     let mut g = Graph::new();
69//!     g.add_system(greet);
70//!     g
71//! };
72//!
73//! let mut ctx = server.create_context();
74//! let executor = GraphExecutor::new();
75//! let result = executor.execute(&graph, &mut ctx, None, None).await?;
76//! let output = result.output::<String>();
77//! # Ok(())
78//! # }
79//! ```
80//!
81//! # Core Concepts
82//!
83//! ## Systems and Resources
84//!
85//! A **system** is a pure async function that declares its dependencies as
86//! typed parameters. The `#[system]` macro generates the boilerplate:
87//!
88//! ```no_run
89//! # use polaris_ai::polaris_system;
90//! # use polaris_ai::system::system;
91//! # use polaris_ai::system::param::Res;
92//! # use polaris_ai::system::resource::LocalResource;
93//! # #[derive(Clone)] struct LlmClient;
94//! # impl LocalResource for LlmClient {}
95//! # #[derive(Clone)] struct Memory;
96//! # impl LocalResource for Memory {}
97//! # struct ReasoningResult { action: String }
98//! #[system]
99//! async fn reason(llm: Res<LlmClient>, memory: Res<Memory>) -> ReasoningResult {
100//!     // Access resources, produce output
101//!     ReasoningResult { action: "search".into() }
102//! }
103//! ```
104//!
105//! **Resources** are how agents get capabilities. An LLM provider, a tool
106//! registry, a memory backend — each exists as a resource in the
107//! [`SystemContext`](system::param::SystemContext):
108//!
109//! | Parameter | Resolution | Access | Use for |
110//! |-----------|------------|--------|---------|
111//! | [`Res<T>`](system::param::Res) | Hierarchy (local → parent → global) | Immutable | Config, registries, per-request input |
112//! | [`ResMut<T>`](system::param::ResMut) | Current context only | Exclusive | Accumulated state (conversation history) |
113//! | [`Out<T>`](system::param::Out) | Previous system output | Immutable | System-to-system data handoff |
114//! | [`ErrOut<T>`](system::param::ErrOut) | Error-edge output | Immutable | Error context in handler subgraphs |
115//!
116//! ## Graphs
117//!
118//! Agent logic is expressed as a directed graph of systems and control flow:
119//!
120//! ```no_run
121//! # use polaris_ai::graph::Graph;
122//! # struct ReasoningResult { needs_tool: bool }
123//! # async fn reason() -> ReasoningResult { ReasoningResult { needs_tool: false } }
124//! # async fn execute_tool() {}
125//! # async fn respond() {}
126//! let mut graph = Graph::new();
127//! graph
128//!     .add_system(reason)
129//!     .add_conditional_branch::<ReasoningResult, _, _, _>(
130//!         "needs_tool",
131//!         |r| r.needs_tool,
132//!         |g| { g.add_system(execute_tool); },
133//!         |g| { g.add_system(respond); },
134//!     );
135//! ```
136//!
137//! **Node types:** System, Decision, Switch, Parallel, Loop, Scope.
138//! **Edge types:** `Sequential`, `Conditional`, `Parallel`, `LoopBack`, `Error`, `Timeout`.
139//!
140//! The graph's full topology is inspectable, validated before execution, and
141//! restructured by rewiring edges. See the [`graph`] module for the full API.
142//!
143//! ## Plugins
144//!
145//! Every capability is delivered through plugins registered at startup:
146//!
147//! ```no_run
148//! # use std::sync::Arc;
149//! # use polaris_ai::system::server::Server;
150//! # use polaris_ai::plugins::{DefaultPlugins, MinimalPlugins};
151//! # use polaris_ai::tools::ToolsPlugin;
152//! # use polaris_ai::sessions::{SessionsPlugin, store::memory::InMemoryStore};
153//! # use polaris_ai::system::plugin::PluginGroup;
154//! let mut server = Server::new();
155//! server
156//!     .add_plugins(DefaultPlugins::new().build())
157//!     .add_plugins(ToolsPlugin)
158//!     .add_plugins(SessionsPlugin::new(Arc::new(InMemoryStore::new())));
159//! ```
160//!
161//! Plugins have a lifecycle: `build()` → `ready()` → `update()` → `cleanup()`.
162//! Dependencies are declared and resolved automatically. See the [`system`]
163//! module for the `Plugin` trait and the [`plugins`] module for built-in
164//! plugin groups.
165//!
166//! # Data Flow Patterns
167//!
168//! Choosing the right parameter type is critical for correct data flow:
169//!
170//! | Pattern | Use | Avoid |
171//! |---------|-----|-------|
172//! | Step A's result feeds step B | `Out<T>` — A returns `T`, B declares `Out<T>` | `ResMut<SharedState>` with `Option` fields |
173//! | Immutable per-request input | `Res<T>` via `ctx.insert(T)` in setup closure | `ResMut<WorkingState>` with `.input.clone()` |
174//! | Accumulated state (history, counters) | `ResMut<T>` — local resource | `Out<T>` — outputs are per-system |
175//! | Shared server-wide config | `Res<T>` — global resource | `ResMut<T>` — compile error on globals |
176//! | Error context in handler | `ErrOut<CaughtError>` | Custom `ResMut<LastError>` |
177//!
178//! ## Data Lifetimes
179//!
180//! | Data lives… | Mechanism |
181//! |-------------|-----------|
182//! | Server lifetime | `GlobalResource` + `Res<T>` |
183//! | Session lifetime | `LocalResource` inserted at session creation |
184//! | Single turn | `LocalResource` inserted in `process_turn_with` |
185//! | Between two systems | Return value + `Out<T>` |
186//! | Error handler subgraph | `ErrOut<CaughtError>` |
187//!
188//! # Common Integration Patterns
189//!
190//! | Goal | Pattern | Entry point |
191//! |------|---------|-------------|
192//! | Run one-shot agent | `sessions.run_oneshot(&agent_type, \|ctx\| { ... })` | [`sessions`] |
193//! | Multi-turn with cleanup | `sessions.scoped_session(&agent_type, \|ctx\| { ... })` | [`sessions`] |
194//! | Execute agent from HTTP | `DeferredState` → `SessionsAPI` → `HttpIOProvider` | [`app`] |
195//! | Register HTTP routes | `server.api::<HttpRouter>().add_routes(router)` | [`app`] |
196//! | Add tools for LLM | `#[tool]` macro + `ToolRegistry` | [`tools`] |
197//! | Add model provider | Implement `LlmProvider` + register via plugin | [`models`] |
198//! | Handle system errors | Fallible system + error edge + `ErrOut<CaughtError>` | [`graph`] |
199//! | Schedule plugin updates | `tick_schedules()` + `server.tick::<S>()` | [`system`] |
200//!
201//! # Crate Organisation
202//!
203//! | Module | Crate | Purpose |
204//! |--------|-------|---------|
205//! | [`system`] | `polaris_system` | ECS-inspired systems, resources, and plugins |
206//! | [`graph`] | `polaris_graph` | Directed-graph execution primitives |
207//! | [`agent`] | `polaris_agent` | Agent trait for reusable behavior patterns |
208//! | [`tools`] | `polaris_tools` | Tool framework for LLM-callable functions |
209//! | [`models`] | `polaris_models` / `polaris_model_providers` | Model registry and provider implementations |
210//! | [`plugins`] | `polaris_core_plugins` | Core infrastructure plugins (time, tracing, persistence) |
211//! | [`sessions`] | `polaris_sessions` | Session management and orchestration |
212//! | [`shell`] | `polaris_shell` | Shell command execution with permission model |
213//! | [`app`] | `polaris_app` | HTTP server runtime with plugin integration |
214//!
215//! # Exploration Map
216//!
217//! | If you want to find… | Start here |
218//! |----------------------|------------|
219//! | System primitives, resources, and plugin lifecycle | [`system`] |
220//! | Graph nodes, edges, execution, hooks, and middleware | [`graph`] |
221//! | LLM providers and provider plugins | [`models`] |
222//! | Core infrastructure plugins and observability | [`plugins`] |
223//! | Session lifecycle, persistence, and HTTP session routes | [`sessions`] |
224//! | Feature-gated exports and which module owns them | [Feature Export Map](#feature-export-map) |
225//!
226//! # Feature Flags
227//!
228//! All features are opt-in (none enabled by default). Features that originate
229//! from a sub-crate and would otherwise be ambiguous at the top level are
230//! prefixed with the sub-crate's short name (e.g. `sessions-http`). Features
231//! that are already unambiguous keep their original name (e.g. `anthropic`).
232//!
233//! ## Model Providers
234//!
235//! | Feature | Exported item | Find it under |
236//! |---------|---------------|---------------|
237//! | `anthropic` | [`models::AnthropicPlugin`] | [`models`] |
238//! | `openai` | [`models::OpenAiPlugin`] | [`models`] |
239//! | `bedrock` | [`models::BedrockPlugin`] | [`models`] |
240//!
241//! ## Observability
242//!
243//! | Feature | Exported item | Effect |
244//! |---------|---------------|--------|
245//! | `graph-tracing` | No new public type | Extends [`plugins::TracingPlugin`] with graph-execution spans |
246//! | `models-tracing` | No new public type | Extends [`plugins::TracingPlugin`] to decorate model providers |
247//! | `tools-tracing` | No new public type | Extends [`plugins::TracingPlugin`] to decorate tools |
248//! | `otel` | [`plugins::OpenTelemetryPlugin`] | Adds OTLP export via the tracing subscriber |
249//!
250//! ## Tokenization
251//!
252//! | Feature | Exported item | Effect |
253//! |---------|---------------|--------|
254//! | `tiktoken` | [`models::tokenizer::TiktokenCounter`] and [`models::tokenizer::EncodingFamily`] | Enables tiktoken-backed counting and [`models::TokenizerPlugin::default`] |
255//!
256//! ## Sessions
257//!
258//! | Feature | Exported item | Find it under |
259//! |---------|---------------|---------------|
260//! | `sessions-http` | [`sessions::HttpPlugin`] and [`sessions::http`] | [`sessions`] |
261//!
262//! ## Feature Coverage Map
263//!
264//! Use this table when the question is “what does feature `X` expose,
265//! modify, or wire up at runtime?”
266//!
267//! | Feature | Adds public items | Also changes | Runtime surface |
268//! |---------|-------------------|--------------|-----------------|
269//! | `anthropic` | [`models::anthropic`], [`models::AnthropicPlugin`] | Makes the `anthropic/...` provider family available through [`models::ModelRegistry`] once registered | [`models::AnthropicPlugin`] registers the Anthropic provider |
270//! | `openai` | [`models::openai`], [`models::OpenAiPlugin`] | Makes the `openai/...` provider family available through [`models::ModelRegistry`] once registered | [`models::OpenAiPlugin`] registers the `OpenAI` provider |
271//! | `bedrock` | [`models::bedrock`], [`models::BedrockPlugin`] | Makes the `bedrock/...` provider family available through [`models::ModelRegistry`] once registered | [`models::BedrockPlugin`] registers the Bedrock provider |
272//! | `graph-tracing` | No new public item | Extends [`plugins::TracingPlugin`] only; no separate `GraphTracingPlugin` exists | [`plugins::TracingPlugin`] registers graph middleware through [`graph::MiddlewareAPI`] |
273//! | `models-tracing` | No new public item | Extends [`plugins::TracingPlugin`] only | [`plugins::TracingPlugin`] decorates the global [`models::ModelRegistry`] |
274//! | `tools-tracing` | No new public item | Extends [`plugins::TracingPlugin`] only | [`plugins::TracingPlugin`] decorates the global [`tools::ToolRegistry`] |
275//! | `otel` | [`plugins::OpenTelemetryPlugin`] | Integrates with the existing [`plugins::TracingPlugin`] / [`plugins::TracingLayersApi`] surface | [`plugins::OpenTelemetryPlugin`] pushes an OTLP export layer into the tracing subscriber |
276//! | `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`] |
277//! | `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`] |
278
279// Re-export crates under their original names so proc-macro-generated code
280// can resolve `polaris::polaris_tools`, `polaris::polaris_system`, etc.
281#[doc(hidden)]
282pub use polaris_internal::polaris_core_plugins;
283#[doc(hidden)]
284pub use polaris_internal::polaris_models;
285#[doc(hidden)]
286pub use polaris_internal::polaris_system;
287#[doc(hidden)]
288pub use polaris_internal::polaris_tools;
289
290/// Re-export all common types for easy access.
291///
292/// # Examples
293///
294/// ```
295/// use polaris_ai::prelude::*;
296///
297/// let graph = Graph::new();
298/// ```
299pub mod prelude {
300    pub use polaris_internal::prelude::*;
301}
302
303#[doc = include_str!("docs/system.md")]
304pub mod system {
305    #[doc(inline)]
306    pub use polaris_internal::system::*;
307}
308
309#[doc = include_str!("docs/graph.md")]
310pub mod graph {
311    #[doc(inline)]
312    pub use polaris_internal::graph::*;
313}
314
315#[doc = include_str!("docs/agent.md")]
316pub mod agent {
317    #[doc(inline)]
318    pub use polaris_internal::agent::*;
319}
320
321#[doc = include_str!("docs/tools.md")]
322pub mod tools {
323    #[doc(inline)]
324    pub use polaris_internal::tools::*;
325}
326
327#[doc = include_str!("docs/models.md")]
328pub mod models {
329    #[doc(inline)]
330    pub use polaris_internal::models::*;
331}
332
333#[doc = include_str!("docs/plugins.md")]
334pub mod plugins {
335    #[doc(inline)]
336    pub use polaris_internal::plugins::*;
337}
338
339#[doc = include_str!("docs/sessions.md")]
340pub mod sessions {
341    #[doc(inline)]
342    pub use polaris_internal::sessions::*;
343}
344
345#[doc = include_str!("docs/shell.md")]
346pub mod shell {
347    #[doc(inline)]
348    pub use polaris_internal::shell::*;
349}
350
351#[doc = include_str!("docs/app.md")]
352pub mod app {
353    #[doc(inline)]
354    pub use polaris_internal::app::*;
355}