# Building a ReAct agent

> Understand and adapt the working ReAct example in the public repository.

Start by [running the ReAct CLI](/docs/polaris/examples/react-cli.md). This guide explains the working implementation rather than asking you to reconstruct it from partial snippets.

The source is in [`examples/src/react_agent`](https://github.com/RadiantAILabs/polaris/tree/5ff84646db63589d58bf87956976d749b8c754de/examples/src/react_agent). The CLI assembly is in [`examples/src/bin/cli.rs`](https://github.com/RadiantAILabs/polaris/blob/5ff84646db63589d58bf87956976d749b8c754de/examples/src/bin/cli.rs).

## Follow one turn

```text
receive input → initialise loop → call model
                                  ├─ tool calls → execute tools → call model again
                                  └─ final answer → finish turn
```

`ReActAgent::build` constructs this graph. Its systems use typed resources rather than a shared, unstructured state dictionary:

| Part             | Responsibility                                  |
| ---------------- | ----------------------------------------------- |
| `AgentConfig`    | Select the model                                |
| `AgentLlm`       | Hold the model handle resolved in `setup`       |
| `ContextManager` | Retain user, assistant and tool-result messages |
| `ReactState`     | Indicate whether the loop has completed         |
| `ToolRegistry`   | Supply available tools and dispatch calls       |
| `UserIO`         | Exchange messages with the caller               |

## Read the graph first

This excerpt uses the systems and types defined in `agent.rs`; it is not a standalone program:

```rust
fn build(&self, graph: &mut Graph) {
    graph.add_system(receive_user_input);
    graph.add_system(init_loop);
    graph.add_loop::<ReactState, _, _>(
        "react_loop",
        |state| state.is_complete,
        |g| {
            g.add_system(act);
            g.add_conditional_branch::<LlmResponse, _, _, _>(
                "has_tool_calls",
                |response| response.has_tool_calls(),
                |tools| { tools.add_system(execute_tools); },
                |done| { done.add_system(finalize); },
            );
            g.add_error_handler(|handler| { handler.add_system(recover); });
        },
    );
}
```

The loop predicate is the **stop condition**. Returning `ReactState { is_complete: true }` ends the loop. The CLI also constructs its executor with `with_default_max_iterations(10)` so a model cannot request tools indefinitely.

## Keep tool results in the conversation

`act` sends the current messages and tool definitions to the model. `execute_tools` processes requested calls, applies the example’s permission handling and appends tool results to the history. `finalize` sends the answer and marks the turn complete.

Inspect the full `execute_tools` implementation before changing dispatch: confirmation, denial and execution errors affect what the model should see next. Calling `ToolRegistry::execute` directly is not a replacement for the caller’s permission workflow.

## Understand setup and persistence

`ReActPlugin::build` registers fresh `ContextManager` and `ReactState` resources. Its asynchronous `ready` hook registers serialisation with `PersistenceAPI` when available. `ReActAgent::setup` resolves the model from `AgentConfig` for the session.

The CLI awaits `server.finish()`, gets `SessionsAPI`, registers the agent and creates or resumes a session. Keep those responsibilities outside individual reasoning systems. See [Sessions and persistence](/docs/polaris/provided-plugins/sessions-and-persistence.md) when embedding the agent elsewhere.

## Make one useful change

1. Run a task and save the baseline transcript.
2. Change one part: the system prompt, a tool description, the model configuration or the loop limit.
3. Repeat the task with a fresh session so prior history does not confound the result.
4. Compare task completion, tool failures, latency and usage.

For a new capability, [write a tool](/docs/polaris/guides/writing-custom-tools.md) and register it through a plugin. For a different reasoning strategy, change the graph. For a different delivery surface, keep the agent and use the repository’s HTTP example.

Add deterministic tests for the graph changes before evaluating model behaviour: [Testing agents](/docs/polaris/guides/testing-agents.md).
