# Systems

> Systems are async functions that form the building blocks of agent behavior.

A system is an async function that the framework runs. You write a regular async function, annotate it with `#[system]`, and the framework handles parameter injection, execution, and output routing.

```rust
use polaris_ai::system::system;
use polaris_ai::system::param::Res;
use polaris_ai::system::resource::GlobalResource;


struct AgentConfig {
    model_id: String,
}
impl GlobalResource for AgentConfig {}


struct Greeting {
    text: String,
}


#[system]
async fn greet(config: Res<AgentConfig>) -> Greeting {
    Greeting {
        text: format!("Using model: {}", config.model_id),
    }
}
```

The `greet` function takes one parameter and returns a value. The framework resolves the parameter from context before calling the function, and stores the return value so downstream systems can access it.

## The `#[system]` macro

The `#[system]` attribute transforms an async function into a struct that implements the `System` trait. For a function named `greet`, the macro generates a struct called `GreetSystem` and a factory function `greet()` that returns an instance of it.

The generated struct handles three things: fetching each parameter from the `SystemContext`, calling your function body, and reporting the system’s name for tracing and debugging. You do not interact with the `System` trait directly in normal usage.

## Parameter injection

Systems declare their dependencies as function parameters. The framework resolves each parameter from the system context before execution. You do not look up resources manually.

Three parameter types cover the most common cases:

* `Res<T>` reads a resource. The lookup walks up the context hierarchy: local scope first, then parent contexts, then server globals.
* `ResMut<T>` gives mutable access to a resource in the current scope only.
* `Out<T>` reads the return value of a preceding system in the same graph.

A system can take any combination of these. The following system reads a global resource, mutates a local resource, and consumes the output of a previous system:

```rust
use polaris_ai::system::system;
use polaris_ai::system::param::{Res, ResMut, Out};
use polaris_ai::system::resource::{GlobalResource, LocalResource};


struct Config {
    verbose: bool,
}
impl GlobalResource for Config {}


struct Memory {
    messages: Vec<String>,
}
impl LocalResource for Memory {}


struct PreviousResult {
    summary: String,
}


#[system]
async fn process(
    config: Res<Config>,
    mut memory: ResMut<Memory>,
    previous: Out<PreviousResult>,
) -> String {
    memory.messages.push(previous.summary.clone());
    if config.verbose {
        format!("processed {} messages", memory.messages.len())
    } else {
        String::new()
    }
}
```

If a parameter cannot be resolved (for example, the resource was never registered), the system returns a `ParamError` and the graph executor handles the failure.

## System outputs

A system’s return value is stored in the context as an output. The next system in the graph can read it with `Out<T>`, where `T` is the return type of the preceding system.

```rust
use polaris_ai::system::system;
use polaris_ai::system::param::Out;


struct AnalysisResult {
    score: f64,
}


#[system]
async fn analyze() -> AnalysisResult {
    AnalysisResult { score: 0.85 }
}


#[system]
async fn report(analysis: Out<AnalysisResult>) -> String {
    format!("Score: {:.0}%", analysis.score * 100.0)
}
```

The `report` system reads the `AnalysisResult` produced by `analyze`. This is how data flows between systems without shared mutable state.

## Fallible systems

Systems can return `Result<T, SystemError>` to indicate they may fail. The `#[system]` macro detects this pattern automatically and marks the system as fallible.

```rust
use polaris_ai::system::system;
use polaris_ai::system::param::Res;
use polaris_ai::system::prelude::SystemError;
use polaris_ai::system::resource::GlobalResource;
use polaris_ai::models::llm::{Llm, LlmResponse};


struct AgentLlm(Llm);
impl GlobalResource for AgentLlm {}


#[system]
async fn act(llm: Res<AgentLlm>) -> Result<LlmResponse, SystemError> {
    let response = llm.0
        .builder()
        .system("You are a helpful assistant.")
        .user("Say hello.")
        .generate()
        .await
        .map_err(|err| SystemError::ExecutionError(err.to_string()))?;


    Ok(response)
}
```

On success, the `T` value is stored as the system’s output, available to downstream systems via `Out<T>`. On failure, the `SystemError` propagates to the graph executor, which routes execution to an error edge if one is defined, or halts the graph.

## Systems as graph nodes

Systems are the nodes in a directed graph that defines your agent’s behavior. You add them to a graph with `add_system`, and the graph determines execution order, branching, and looping.

```rust
graph.add_system(receive_input);
graph.add_system(analyze);
graph.add_system(report);
```

This creates a sequential pipeline: `receive_input` runs first, then `analyze`, then `report`. Each system can read the output of the one before it. For conditional branching, loops, and parallel execution, see [Graphs](/docs/polaris/core-concepts/graphs.md).
