ReAct Reasoning Loop

The ReActAgent in Agent Substrate implements an autonomous Reason + Act decision cycle engineered for durable execution, strict tool validation, budget boundaries, and crash recovery.


How the Loop Works

In each step of the reasoning loop:

  1. Context Window Assembly: The agent compiles the recent turn history, active system instructions, and available tool schemas into a compacted prompt.
  2. Thought & Action Generation: The model reasons through the prompt and decides whether to output a final answer or call one or more tools.
  3. Tripwire & Middleware Inspection: Any proposed tool call passes through MiddlewarePipeline stages (such as PII masking, schema validation, and rate limiting) and human approval gates if required.
  4. Durable Execution & WAL Journaling: Tools execute with declared idempotency and risk levels (ToolRisk.SAFE, HIGH, CRITICAL). All invocations and results are recorded in the append-only write-ahead log.
  5. Observation Evaluation: The model receives the tool results formatted into ToolResultBlock entries and proceeds to the next reasoning step.

Basic Configuration

Agents are pure definitions: a name, a model, instructions, and tools. They execute durably on a Runtime:

python
import asyncio
from substrate.agents import ReActAgent
from substrate.runtime import Runtime
from substrate.tools import ToolRisk, tool
from substrate.types import ExecutionBudget

@tool(risk=ToolRisk.SAFE, idempotent=True)
def calculate_tax(state: str, amount: float) -> float:
    """Compute state tax rate for an invoice.

    Args:
        state: Two-letter state code (e.g. CA, NY).
        amount: Order total before tax.
    """
    rate = 0.0825 if state == "CA" else 0.05
    return round(amount * rate, 2)

async def main() -> None:
    agent = ReActAgent(
        "tax_assistant",
        model=my_chat_model,
        tools=[calculate_tax],
        system_instructions="You are a concise financial calculation assistant.",
        max_iterations=10,
        execution_budget=ExecutionBudget(max_cost_usd=1.50, max_tokens=25_000),
    )

    # Runtime.open opens the local store in ./.substrate (or PostgreSQL in production)
    async with Runtime.open("./.substrate") as runtime:
        outcome = await runtime.run(agent, "Calculate CA tax for a $4,500 order.")
        print(f"Status: {outcome.status.value}")
        print(f"Response: {outcome.output}")

if __name__ == "__main__":
    asyncio.run(main())

Agent Constructor Parameters

ParameterTypeDescription
namestrUnique actor identifier for this agent type.
modelChatModelAny provider implementing the ChatModel protocol (OpenAI, Anthropic, Gemini, local).
toolslist[Tool] | ToolRegistryCallable functions decorated with @tool or classes implementing Tool.
system_instructionsstrStatic instructions injected into the system prompt.
max_iterationsintMaximum reasoning loop turns before terminating (default: 10).
execution_budgetExecutionBudgetHard cost and token ceiling (max_cost_usd, max_tokens).
approval_handlerApprovalHandlerHandler invoked when a tool call meets or exceeds approval_required_risk.
approval_required_riskToolRiskRisk threshold requiring human sign-off (SAFE, LOW, MEDIUM, HIGH, CRITICAL).
middlewareMiddlewarePipelinePipeline dispatched at TURN, CHAT, and TOOL stages.
contextContextConfigCompaction strategy (such as SlidingWindowCompaction).

Step Backtracking & Error Recovery

If a tool throws a connection error or returns invalid data:

  • Observation Feeding: The exception is caught and formatted as an error observation rather than terminating the run.
  • Self-Correction: The model reflects on the failure output and selects an alternative strategy (e.g., retrying with adjusted parameters or falling back to another tool).
  • Crash Recovery: If the host process is killed midway through a tool execution, non-idempotent actions remain protected: upon restart, the runtime checks the journal and avoids duplicate external writes.
  • Budget Exceeded Protection: If the agent consumes more tokens or dollars than allotted by ExecutionBudget, the runtime halts immediately with BudgetExhaustedError.
Was this page helpful?