ReAct
The canonical observe / think / act loop, and the default architecture. Yao et al. 2022. ReAct: Synergizing Reasoning and Acting in Language Models.
┌────────── loop until no tool calls ──────────┐
│ │
prompt ───► Model ───► tool calls? ──yes──► run tools ──► results
│ (parallel)
└─────────► no calls ───► final outputWhat each turn does
- Check budget (token / cost / wall-clock); emit
BUDGET_WARNINGorBUDGET_EXCEEDEDif needed. - Call the model with the current message stack + available tools.
- Stream tokens and aggregate
text+tool_calls+usage. - If the assistant has no tool calls, append the final message and break.
- Otherwise dispatch every tool call in parallel under a single
anyio.create_task_group. Throughbefore_toolhooks, then permissions, then the tool host. Append the results asrole=toolmessages. - Loop back to step 1.
Usage
from loomflow import Agent
# Default — no kwarg needed
agent = Agent("You are helpful.", model="claude-opus-4-7")
# Or pass the spec explicitly
agent = Agent("...", model="...", architecture="react")
# Or an instance for tuning
from loomflow import ReAct
agent = Agent(
"...",
model="claude-opus-4-7",
architecture=ReAct(),
)max_turns lives on the Agent, not on ReAct. Pass
Agent(..., max_turns=20) (default 50). Hitting the cap returns a
RunResult with interrupted=True and
interruption_reason="max_turns".
Parallel tool dispatch
Tool calls in the same model turn run concurrently. The model
emits multiple tool_calls in one assistant message; the framework
fans them out under one task group, awaits them all, then continues.
Tool results are appended in arrival order. Deterministic for the
model’s next turn.
A failing tool doesn’t poison the others: each tool’s exception is
captured in its own ToolResult(ok=False) and the loop continues
with the partial set.
When ReAct is the right call
- The number of steps is hard to predict upfront → ReAct’s one-step-at-a-time observation pays off.
- Tools occasionally fail or return surprising data that should re-shape the plan.
- You want the simplest possible loop with good production behaviour.
When to swap it out
- Cost matters and you know the steps. Plan-and-Execute or ReWOO cut LLM calls by half.
- Quality matters more than cost. ActorCritic or Reflexion iterate on output until a critic signs off.
- The problem is combinatorial. Tree of Thoughts branches and beam-searches.
- You need multiple specialists. Router / Supervisor / Swarm.
Replay correctness. Every model call and every tool dispatch is
journaled by (session_id, step_name). With a SqliteRuntime or
PostgresRuntime, a crashed run resumes by re-using the cached
results for completed steps and only re-executing the un-completed
work. See Runtime.