Agentic Patterns — ReAct, Tool Loops, and Plan-and-Execute
Summary
Function Calling and Structured Output showed how to connect a model to a single tool in a single call. This page goes further: agentic patterns are the loop architectures that allow a model to use tools across multiple steps, reason about intermediate results, and complete tasks that no single LLM call can handle alone.
A user asks for "the cheapest compliant supplier, current contract status, last-quarter spend trend, and a draft recommendation memo." That sounds like one request, but operationally it is four different jobs hiding inside one sentence: search the approved supplier catalog, verify compliance status, check live contract metadata, pull finance data for the last quarter, compare trade-offs, and then draft a memo in the right tone for a human decision-maker. If your first instinct is "one prompt should handle that," this page is here to save you from a fun but expensive detour.
In a real enterprise system, that request touches multiple systems with different trust levels and update cycles. The supplier list may live in a procurement database, compliance status may come from a vendor-risk service, contract status may sit in a legal repository, and spend trend data may come from an ERP or BI layer. A single LLM call can summarize information it already has in context. It cannot reliably fetch, verify, compare, and synthesize all of that from live systems unless an application loop deliberately coordinates the work.
That is the key mental shift: the user experiences one question, but the system must perform a sequence of bounded steps. First gather the right facts. Then validate what is current. Then compare options using explicit criteria. Then draft the recommendation in a format a manager can review. Trying to compress all of that into one giant prompt usually produces one of three outcomes: the model guesses where data is missing, it overuses a tool without a stopping rule, or it returns a memo that sounds polished but is grounded in stale or partial evidence.
An agent is an LLM in a loop. The model reasons, decides on an action, observes the result, and decides what to do next — iteratively — until the task is complete or a stopping condition is met.
The pattern is powerful and operationally risky. The same loop that enables a model to research, plan, and execute a multi-step task can also spin indefinitely, call the wrong tool 12 times in a row, or accumulate runaway API costs. Engineering agentic systems requires understanding not just how to build the loop — but how to bound it.
| Pattern | Loop type | Best for | Primary risk |
|---|---|---|---|
| ReAct | Interleaved reasoning + action | Tasks requiring tool use and reasoning | Reasoning drift in long loops |
| Tool loop | Action → observe → decide | Data fetching and deterministic workflows | Incorrect tool selection at junctions |
| Plan-and-execute | Plan upfront, execute steps | Complex multi-step tasks with known structure | Plan invalidation mid-execution |
| Multi-agent | Specialist agents coordinated by orchestrator | Large tasks requiring parallel specialisation | Coordination overhead and error propagation |
A procurement assistant receives: "Find the three cheapest suppliers for item SKU-8821 who can deliver within 14 days, check our existing contracts with each, and draft a comparison table." No single LLM call can complete this. An agent using a supplier search tool, a contract database tool, and a document drafting tool — in a controlled loop — can complete it in 4–6 steps.
A single LLM call handles tasks that fit in one context window with one tool invocation. An agent handles tasks that require sequencing tool calls, observing results, and making decisions based on intermediate state — tasks that are too complex, too dynamic, or too dependent on real-time data to express as a single prompt.
Every agentic system is a variation of: reason → act → observe → repeat. The differences between patterns (ReAct, plan-and-execute, multi-agent) are in how reasoning and action are interleaved, how state is tracked, and how the loop terminates. Understanding the loop first makes every framework easier to evaluate.
An unbounded agent loop is an open-ended API bill and an open-ended risk surface. Every production agent must have: a maximum step count, a cost cap per run, a timeout, and a graceful degradation path when the loop cannot complete. These are not optional safety features — they are production requirements.
Why This Matters
An agent that cannot find the information it needs may loop indefinitely — calling the same search tool with slight variations, re-planning after each failure, and accumulating API costs with each iteration. Without a hard step limit (typically 10–20 steps for most tasks), a single agent run can exhaust an API budget that was designed for thousands of calls. This is not theoretical — it is the most common production incident type in agentic systems.
In a single-call system, a model error produces one wrong output. In an agentic loop, an incorrect tool call at step 3 may produce a plausible-looking intermediate result that subsequent steps build on — propagating the error through the entire chain. Step-level validation and intermediate result checks are required, not optional, for tasks where downstream steps depend on upstream tool outputs.
The ecosystem of agentic frameworks is crowded and evolving rapidly. Teams that choose a framework before understanding the underlying loop patterns find themselves constrained by abstractions they do not fully understand. The patterns on this page are framework-agnostic — they apply whether you are using OpenAI's Agents SDK, Microsoft's Semantic Kernel, or a hand-rolled loop in Python or C#.
Design the stopping conditions before you design the happy path. An agent that cannot be stopped reliably in production should not be deployed to production.
Core Concepts
🔄 Pattern 1 — ReAct (Reasoning + Acting)
ReAct interleaves the model's reasoning trace with its actions. For every step, the model produces a Thought (what it is reasoning), an Action (what tool to call), and an Observation (the tool's result). This cycle repeats until the model produces a final answer.
Thought: I need to find the supplier list for SKU-8821 and filter by 14-day delivery.
Action: search_suppliers(sku="SKU-8821", max_delivery_days=14)
Observation: [{"id": "S12", "name": "Acme Parts", "price": 4.20}, {"id": "S47", "name": "Delta Supply", "price": 3.95}, ...]
Thought: I have 5 suppliers. Now I need to check existing contracts for each.
Action: get_contracts(supplier_ids=["S12", "S47", "S31", "S08", "S19"])
Observation: {"S47": "contract_active", "S12": "no_contract", ...}
Thought: I have supplier data and contract status. I can now draft the comparison table.
Action: draft_comparison_table(suppliers=..., contracts=...)
Observation: [table content]
Final Answer: [formatted comparison table with suppliers, prices, delivery times, and contract status]
ReAct architecture:
The reason ReAct feels intuitive is that it mirrors how people work: think, try something, see what happened, adjust. The reason it needs guardrails is that models can also do that forever.
When to use ReAct:
- Tasks requiring interleaved reasoning and tool use
- Tasks where the next step depends on the result of the current step
- Research and investigation workflows
Limitations:
- Reasoning traces consume context window budget
- Long chains can drift from the original task goal
- Requires careful prompt design to keep thoughts concise
⚡ Pattern 2 — Tool Loop
A simpler pattern where the model selects and calls tools iteratively until a completion condition is met — without an explicit reasoning trace in the prompt.
When to use a tool loop:
- Well-scoped tasks with a clear completion signal
- Workflows where tool ordering matters but reasoning traces are unnecessary overhead
- High-volume tasks where context window cost is a concern
Key implementation detail — the completion signal: The loop terminates when the model returns a response with no tool_call — meaning it has enough information to answer. Design tools so their outputs make the "enough information" signal clear.
📋 Pattern 3 — Plan-and-Execute
The model first produces a complete plan (a structured list of steps), then executes each step in order. Planning and execution are separated into two distinct phases.
This pattern is especially useful when the wrong plan would be embarrassing, expensive, or audit-sensitive. The plan is something you can inspect before the system starts spending money on actions.
Example plan output (structured JSON):
{
"task": "Compare supplier options for SKU-8821",
"steps": [
{ "step": 1, "action": "search_suppliers", "args": { "sku": "SKU-8821", "max_days": 14 } },
{ "step": 2, "action": "get_contracts", "args": { "supplier_ids": "$step_1.ids" } },
{ "step": 3, "action": "get_pricing_history", "args": { "supplier_ids": "$step_1.ids" } },
{ "step": 4, "action": "draft_comparison_table", "args": { "data": "$step_1,$step_2,$step_3" } }
]
}
When to use plan-and-execute:
- Tasks with a known structure where the full plan can be determined upfront
- Systems where human review of the plan before execution is required
- Long tasks where mid-execution re-planning is undesirable
Limitation: If step 2's result invalidates the assumptions behind steps 3–4, the plan must be discarded and re-planned. For highly dynamic tasks, a ReAct approach is more robust.
🌐 Pattern 4 — Multi-Agent Systems
A coordinator agent delegates sub-tasks to specialist agents, each focused on a narrow capability. Results are collected and synthesised by the coordinator.
When to use multi-agent:
- Tasks requiring parallel specialisation that would exceed a single context window
- Workflows where different subtasks need different model configurations (e.g., different temperature or tools)
- Large research and synthesis tasks
Production caution: Multi-agent systems multiply the risk surface. Each agent can fail independently. Coordination overhead increases latency. Error propagation from one agent to another is harder to debug than a single-agent loop. Start with the simplest pattern that meets requirements.
Realistic Example
Scenario
An internal IT procurement agent. An employee submits: "I need to order 20 standing desks for the new floor. Check what models we've ordered before, get current pricing from the approved vendor list, verify budget approval status for department IT-7, and create a draft PO."
Implementation (OpenAI Agents SDK — Python)
from agents import Agent, Runner, function_tool
@function_tool
def get_order_history(category: str, limit: int = 10) -> list:
"""Retrieve previous orders for an item category from the procurement database."""
return procurement_db.query(f"SELECT * FROM orders WHERE category='{category}' LIMIT {limit}")
@function_tool
def get_vendor_pricing(item_model: str, vendor_ids: list[str]) -> dict:
"""Get current pricing from approved vendors for a specific item model."""
return vendor_api.get_pricing(item_model, vendor_ids)
@function_tool
def check_budget_approval(department_id: str, amount: float) -> dict:
"""Check if a department has budget approval for a given amount."""
return finance_api.check_budget(department_id, amount)
@function_tool
def create_draft_po(items: list, vendor_id: str, department_id: str) -> str:
"""Create a draft purchase order in the procurement system."""
return procurement_db.create_po(items, vendor_id, department_id, status="draft")
procurement_agent = Agent(
name="Procurement Assistant",
instructions="""
You are a procurement assistant. For purchase requests:
1. Check order history to identify previously used models.
2. Get current pricing from approved vendors.
3. Verify budget approval before creating any PO.
4. Create a draft PO only when all checks pass.
Never create a PO if budget approval check fails.
Stop after a maximum of 10 tool calls. If you cannot complete,
summarise what you found and what is missing.
""",
tools=[get_order_history, get_vendor_pricing, check_budget_approval, create_draft_po],
model="gpt-4.1",
)
result = Runner.run_sync(
procurement_agent,
"I need to order 20 standing desks for IT-7. Check history, pricing, budget, and draft the PO."
)
print(result.final_output)
Agentic loop steps the model takes:
get_order_history(category="standing_desks")→ finds model "Flexispot E7" was ordered 3× beforeget_vendor_pricing(item_model="Flexispot E7", vendor_ids=["V12","V08","V31"])→ cheapest: £420/unit × 20 = £8,400check_budget_approval(department_id="IT-7", amount=8400)→ approved up to £10,000 ✅create_draft_po(items=[...], vendor_id="V08", department_id="IT-7")→ PO-2026-4821 created
Final response: "I've created draft PO-2026-4821 for 20 × Flexispot E7 standing desks at £420 each (£8,400 total) from Vendor V08 — TechFurnish Ltd. This is within IT-7's approved budget of £10,000. The draft is ready for your approval in the procurement portal."
Senior Tech vs Dev Conversation
Senior Tech: We keep hearing about agents breaking in production. What are the most common failure modes?
Dev: Three are dominant. First: infinite loops — the agent cannot find what it needs and keeps retrying tool calls with slight variations. Fix: hard step limit (10–20), cost cap per run, and a graceful degradation response when the limit is hit. Second: error propagation — a wrong result at step 3 produces a plausible-looking output that steps 4–7 build on. Fix: intermediate result validation — after each tool call, check the result is in the expected format and range before continuing. Third: context accumulation — each tool result adds tokens to the context window; long agent runs push the window to the limit and older instructions get cut. Fix: tool result summarisation — replace verbose raw tool outputs with structured summaries before adding them to the loop context.
Senior Tech: When should we use a framework vs build the loop ourselves?
Dev: For production: use a framework that has active maintenance, observability built in, and aligns with your language stack. As of 2026, OpenAI Agents SDK is the lightest option for Python with OpenAI models. Microsoft Semantic Kernel is the best option for .NET with Azure OpenAI. LangGraph gives the most control for complex state machine workflows. Build from scratch only if you need very specific behaviour that frameworks don't support — and factor in the maintenance burden.
Senior Tech: What is the minimum a production agent must have?
Dev:
agent_guardrails:
max_steps: 15 # hard limit — no negotiation
max_cost_per_run: 0.50 # USD — kill run if exceeded
timeout_seconds: 120 # wall clock limit
tool_call_retry: 2 # max retries per tool call on transient failure
graceful_degradation: # what to do when limits hit
action: "summarise_progress_and_stop"
message: "I was unable to complete all steps. Here is what I found: {partial_results}"
audit_log: true # every tool call logged with args and result
Common Pitfalls
| Pitfall | What goes wrong | Prevention |
|---|---|---|
| No step limit | Agent loops indefinitely; API cost exhausted | Hard max_steps enforced before first deployment |
| No intermediate validation | Error at step 3 propagates silently to step 10 | Validate tool result schema and range after each call |
| Context window accumulation | Old instructions cut as tool results fill the window | Summarise tool outputs; cap history length explicitly |
| Write-access tools without confirmation | Agent takes irreversible actions on mis-parsed intent | Require user confirmation before any write/delete tool executes |
| No audit logging | Cannot debug agent failures in production | Log every tool call: input, output, model reasoning, timestamp |
| Choosing a framework before understanding the pattern | Constrained by abstractions you don't control | Implement one hand-rolled agent loop first; then adopt a framework |
References and Next Steps
- Previous: Context Management for LLM Systems →
- Next: LLM Evaluation and Observability →
- OpenAI Agents SDK: openai.github.io/openai-agents-python
- Microsoft Semantic Kernel (C#): learn.microsoft.com/semantic-kernel
- ReAct paper: ReAct: Synergizing Reasoning and Acting in Language Models (Yao et al., 2022)
- MCP — standardised tool protocol: Model Context Protocol →