Advanced topics
Context hierarchies
SystemContext forms a parent-child tree that isolates per-agent state while sharing server-level resources.
SystemContext forms a parent-child tree that isolates per-agent state while sharing server-level resources. Every system runs against a context, and contexts can be nested to create scoped layers of state.
Server (globals: AppConfig, ToolRegistry, ModelRegistry) │ └── Agent Context (locals: AgentMemory) │ └── Session Context (locals: ConversationHistory) │ └── Turn Context (locals: Scratchpad)This diagram illustrates a hierarchy you can construct; the sessions API does not automatically create all four levels. Each level in the tree owns its own local resources and can read from any ancestor. The server sits at the root, holding global resources that every context can reach.
How the hierarchy is built
Section titled “How the hierarchy is built”The server creates root contexts with Server::create_context(). Each root context gets its own fresh local resources (instantiated from the factories you registered) and a shared reference to the server’s global resources.
use polaris_ai::system::server::Server;use polaris_ai::system::resource::{GlobalResource, LocalResource};
struct AppConfig { model_id: String }impl GlobalResource for AppConfig {}
struct ConversationHistory { messages: Vec<String> }impl LocalResource for ConversationHistory {}
let mut server = Server::new();server.insert_global(AppConfig { model_id: "claude-sonnet-4-20250514".into() });server.register_local(|| ConversationHistory { messages: Vec::new() });server.finish().await?;
let agent_ctx = server.create_context();The returned SystemContext<'static> is decoupled from the server’s lifetime because it holds an Arc to the global resources. You can store it, move it across threads, or hand it to an executor.
Creating child contexts
Section titled “Creating child contexts”Any context can spawn a child with SystemContext::child(). The child holds a read-only reference to its parent and inherits the same global resources handle. It starts with an empty local resource set and empty outputs.
use polaris_ai::system::param::SystemContext;use polaris_ai::system::resource::LocalResource;
struct Scratchpad { notes: Vec<String> }impl LocalResource for Scratchpad {}
let agent_ctx = server.create_context();
let mut turn_ctx = agent_ctx.child();turn_ctx.insert(Scratchpad { notes: Vec::new() });The turn_ctx can read everything in agent_ctx (and through it, the server’s globals), but it has its own Scratchpad that no other context can see. You can nest children arbitrarily deep, though in practice the typical depth is Server, Agent, Session, Turn.
Resource lookup with Res
Section titled “Resource lookup with Res”When a system requests Res<T>, the context searches for T in this order:
- The current context’s local resources
- The parent context’s local resources, then the grandparent’s, walking up the chain
- The server’s global resources
The first match wins. A local resource in a child context shadows the same type in a parent context.
use polaris_ai::system::param::{Res, ResMut};use polaris_ai::system::resource::{GlobalResource, LocalResource};use polaris_ai::system::system;
struct AppConfig { agent_name: String }impl GlobalResource for AppConfig {}
struct ConversationHistory { messages: Vec<String> }impl LocalResource for ConversationHistory {}
#[system]async fn summarize( config: Res<AppConfig>, history: Res<ConversationHistory>,) -> String { format!("{}: {} messages so far", config.agent_name, history.messages.len())}In this system, Res<AppConfig> reaches all the way up to the server’s globals, while Res<ConversationHistory> resolves from whichever ancestor first has one. The system does not need to know where in the hierarchy each resource lives.
Scope isolation with ResMut
Section titled “Scope isolation with ResMut”ResMut<T> does not walk the hierarchy. It accesses only the current context’s local resources. This is a deliberate constraint: mutable access to a parent’s state from a child would break isolation between sibling contexts.
struct Scratchpad { notes: Vec<String> }impl LocalResource for Scratchpad {}
#[system]async fn take_notes( config: Res<AppConfig>, mut scratch: ResMut<Scratchpad>,) { scratch.notes.push(format!("Turn started for {}", config.agent_name));}If Scratchpad exists in the current context, ResMut<Scratchpad> resolves to it. If it only exists in a parent context, the fetch fails with a ParamError::ResourceNotFound. This ensures that a turn context cannot mutate the session’s resources, and a session context cannot mutate the agent’s resources.
Multi-agent pattern
Section titled “Multi-agent pattern”The hierarchy makes multi-agent setups straightforward. Two agents share the same server globals (model registry, tool registry, configuration) but each gets its own local resources from the factory.
use polaris_ai::system::server::Server;use polaris_ai::system::resource::{GlobalResource, LocalResource};
struct ToolRegistry { /* ... */ }impl GlobalResource for ToolRegistry {}
struct AgentMemory { history: Vec<String> }impl LocalResource for AgentMemory {}
let mut server = Server::new();server.insert_global(ToolRegistry { /* ... */ });server.register_local(|| AgentMemory { history: Vec::new() });server.finish().await?;
let researcher_ctx = server.create_context();let writer_ctx = server.create_context();Both researcher_ctx and writer_ctx read from the same ToolRegistry through Res<ToolRegistry>, but each has its own AgentMemory. Mutations to one agent’s memory are invisible to the other.
Within each agent, you can create session and turn child contexts to add further layers of isolation:
let session_ctx = researcher_ctx.child();let turn_one = session_ctx.child();let turn_two = session_ctx.child();Each turn context can read the session’s resources and the agent’s resources, but can only mutate its own.
The SystemContext struct
Section titled “The SystemContext struct”The internal layout of SystemContext reflects the hierarchy design:
SystemContext<'parent>├── parent: Option<&'parent SystemContext> // read-only ancestor chain├── globals: Option<Arc<Resources>> // server-level globals├── resources: Resources // owned local state└── outputs: Outputs // owned ephemeral outputsThe 'parent lifetime ties a child to its parent, enforced by the borrow checker. A root context created by Server::create_context() has 'static lifetime because the Arc<Resources> keeps globals alive independently. The globals reference is cloned into every child context, so any context in the tree can reach server-level resources regardless of depth.
Outputs do not resolve through the parent resource hierarchy. They are owned by each context; clear stale outputs explicitly when starting a new independent turn. Successful execution moves the last output into ExecutionResult, rather than clearing every typed slot. See Resources for how Out<T> works.
Copyright © 2026, Radiant AI Limited.