Skip to content
Radiant LabsRadiant Labs

Guides

Building a ReAct agent

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

Start by running the ReAct CLI. This guide explains the working implementation rather than asking you to reconstruct it from partial snippets.

The source is in examples/src/react_agent. The CLI assembly is in examples/src/bin/cli.rs.

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

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

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.

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.

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 when embedding the agent elsewhere.

  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 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.