By 2026, "build an agent" is no longer a research project. The Anthropic SDK ships tool use out of the box, every serious team has at least one production loop on Claude Sonnet or Opus, and the question is no longer can we but which pattern. The honest answer is uncomfortable: most teams overbuild. They reach for Tree-of-Thoughts where a 30-line ReAct loop would have been done by Tuesday.
This post covers the four patterns that matter: ReAct, Plan-and-Execute, Reflexion and Tree-of-Thoughts. It comes with working code for https://api.codexkey.online/v1, notes on when to switch from Sonnet to Opus in the middle of a loop, and the observability practices that make such a system debuggable in production.
Which patterns survived into 2026
The zoo of agent patterns has shrunk. Five years of papers and frameworks settled into a small set of loops that genuinely move results:
- ReAct: interleave reasoning and tool calls. The default.
- Plan-and-Execute: generate the whole plan first, then run the steps.
- Reflexion: the model critiques its own output and redoes it.
- Tree-of-Thoughts: grow several reasoning branches in parallel and vote for a winner.
Everything else (CoT, ReWOO, LATS, AutoGPT-style endless loops) either folded into one of these four or quietly died. Claude Sonnet 4.6 is strong enough that elaborate scaffolding does more harm than good. The base model already plans, corrects itself and knows when it needs a tool. Your job is to give it the right loop, not to build a brain.
ReAct: the default to start with
Start with ReAct. The model thinks, calls a tool, looks at the result, thinks again, and repeats until done. Anthropic's tool use API is built for exactly this rhythm, and you do not even need a framework.
from anthropic import Anthropic
client = Anthropic(base_url="https://api.codexkey.online/v1")
tools = [
{
"name": "search_docs",
"description": "Search internal documentation",
"input_schema": {
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"],
},
},
{
"name": "run_sql",
"description": "Run a read-only SQL query",
"input_schema": {
"type": "object",
"properties": {"sql": {"type": "string"}},
"required": ["sql"],
},
},
]
def react_loop(user_msg: str, max_turns: int = 10):
messages = [{"role": "user", "content": user_msg}]
for turn in range(max_turns):
resp = client.messages.create(
model="claude-sonnet-5",
max_tokens=4096,
tools=tools,
messages=messages,
)
messages.append({"role": "assistant", "content": resp.content})
if resp.stop_reason == "end_turn":
return resp
tool_results = []
for block in resp.content:
if block.type == "tool_use":
result = dispatch_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result,
})
messages.append({"role": "user", "content": tool_results})
raise RuntimeError("max turns exceeded")
That is the whole pattern. No graph, no state machine, no orchestrator. The conversation history is the state. On this loop Sonnet handles 5-15 step workflows without breaking a sweat.
ReAct works when:
- The next action depends on the previous observation (most real workflows are like this).
- Tools are cheap and fast, with sub-second latency.
- You do not know up front how many steps it will take.
- The user needs streamed output.
ReAct hurts when:
- Steps are independent and could run in parallel.
- Each tool call costs real money or wall-clock time.
- A human has to see the plan before it runs.
Plan-and-Execute: when steps are independent
Plan-and-Execute splits the job in two. The first call returns a numbered plan. The second pass executes the steps, often in parallel and often on a cheaper model. Opus or Sonnet plans, gpt-5.6-luna executes.
Haiku is in the catalogue too: claude-haiku-5.5 and claude-haiku-4.5, with prices on the models page. They are available on the Premium channel and not on the Cheap channel.
def plan_and_execute(goal: str):
plan_resp = client.messages.create(
model="claude-opus-4.8",
max_tokens=2048,
messages=[{
"role": "user",
"content": f"Produce a JSON plan to accomplish: {goal}\n"
f"Format: {{\"steps\": [{{\"id\": 1, \"action\": \"...\", "
f"\"depends_on\": []}}]}}",
}],
)
plan = json.loads(extract_json(plan_resp.content[0].text))
results = {}
for step in topological_sort(plan["steps"]):
deps = {d: results[d] for d in step["depends_on"]}
results[step["id"]] = execute_step(step["action"], deps)
return results
def execute_step(action: str, context: dict) -> str:
resp = client.messages.create(
model="gpt-5.6-luna",
max_tokens=1024,
tools=tools,
messages=[{
"role": "user",
"content": f"Context: {json.dumps(context)}\nDo: {action}",
}],
)
return resp.content[0].text
The economics are real. A plan from Opus costs about $0.05, and ten gpt-5.6-luna executions cost pennies. Against running the whole workflow on Opus from start to finish, that is a 5-10x cost reduction at comparable quality on tasks that decompose well.
The catch: if step 3 shows that the assumption behind step 2 was wrong, a static plan cannot recover. Pure Plan-and-Execute is brittle wherever the world pushes back. The cure is replanning: after each step, ask the planner whether the rest of the plan still makes sense. That hybrid, sometimes called Plan-Execute-Replan, is what actually ships.
Plan-and-Execute fits when:
- The work breaks cleanly into independent steps.
- Steps can run in parallel and you want the speedup.
- A human or an audit trail has to see the plan before execution.
- Most steps are simple, and gpt-5.6-luna can handle them.
Reflexion: quality for triple the tokens
Reflexion adds a self-critique pass. The agent tries the task, then a second prompt asks it to judge its own answer against the original goal, find the failures and try again with that critique in context. It works, and it is expensive.
def reflexion(task: str, max_attempts: int = 3):
history = []
for attempt in range(max_attempts):
attempt_resp = client.messages.create(
model="claude-sonnet-5",
max_tokens=4096,
messages=[{
"role": "user",
"content": f"Task: {task}\n"
f"Previous attempts and critiques:\n{format_history(history)}",
}],
)
answer = attempt_resp.content[0].text
critique_resp = client.messages.create(
model="claude-opus-4.8",
max_tokens=2048,
messages=[{
"role": "user",
"content": f"Task: {task}\nProposed answer:\n{answer}\n\n"
f"Is this correct and complete? If not, identify "
f"specific failures. Reply with PASS or detailed critique.",
}],
)
critique = critique_resp.content[0].text
if critique.strip().startswith("PASS"):
return answer
history.append({"answer": answer, "critique": critique})
return answer
Token spend roughly triples: every successful task pays for an attempt and a critique, and every retry adds another round trip. On open-ended generation (writing, code, analysis) Reflexion measurably lifts quality. On clear-cut tool-use tasks it only adds latency.
Keep Reflexion for workflows where safety or quality is critical. A customer-facing legal summary? The 3x is justified. Filing a Jira ticket? ReAct is enough.
Tree-of-Thoughts: rarely worth it in 2026
Tree-of-Thoughts (ToT) runs several reasoning branches in parallel, scores them and picks a winner. In 2023, with GPT-4 and Claude 2, this gave a real gain on hard reasoning. In 2026, with Sonnet 4.6 and Opus 4.8, the base model's single-pass reasoning is good enough that the overhead of parallel branches almost never pays back.
We still see ToT in two niches:
- Adversarial search (games, puzzles), where lookahead is genuinely needed.
- Generative design, where you want a diverse set of candidates rather than one best answer.
For business workflows, RAG and tool use? Skip it. The compute that would go into three parallel reasoning trees is better spent on Reflexion or on simply moving to Opus.
Switching from Sonnet to Opus mid-loop
A pattern that quietly became standard in 2025: the loop starts on Sonnet and moves to Opus only when Sonnet shows signs of uncertainty. You catch those two ways: parse the model's own hedging ("I'm not sure", "this is ambiguous"), or check whether tool calls are going in circles.
def adaptive_react(user_msg: str):
model = "claude-sonnet-5"
messages = [{"role": "user", "content": user_msg}]
consecutive_tool_failures = 0
for turn in range(15):
resp = client.messages.create(model=model, max_tokens=4096, tools=tools, messages=messages)
messages.append({"role": "assistant", "content": resp.content})
if resp.stop_reason == "end_turn":
return resp
if detect_uncertainty(resp.content) or consecutive_tool_failures >= 2:
model = "claude-opus-4.8"
# ... handle tool_use blocks, update consecutive_tool_failures ...
The cost math holds. Most turns run on cheap Sonnet, and you pay Opus prices only for the hard ones. On workloads of mixed difficulty this routinely cuts spend by 60-70% compared with running everything on Opus, with no measurable quality loss on the easy turns.
Observability: log everything
The biggest mistake we see in production agent systems is opaque logging. Teams record "agent ran for 8 turns and finished", and when a regression shows up there is nothing to explain it.
The minimum observability for an agent loop:
- Every
tool_useblock: tool name, input arguments, timestamp, turn number. - Every
tool_resultblock: tool_use_id, output (truncated if huge), latency, success or failure. - Every model response: model name, input tokens, output tokens, stop_reason.
- The full message history at the end of the loop, kept for at least 30 days.
OpenTelemetry now has sensible conventions for this: a span per turn, attributes for tool names and token counts. Whatever you pick, the test is the same. Could someone, six weeks from now, work out why the agent did what it did on Tuesday at 3pm? If not, you are flying blind.
A small but underrated practice: log the prompt that produced each response, not just the response. When prompts drift between deploys, you need to be able to diff them.
Bottom line: what to pick
We have run these patterns in production for two years. The takeaways:
- ReAct by default. The simplest loop, and it carries 80% of real work. Build it first.
- Plan-and-Execute when steps are independent and parallelizable, or when a human has to review the plan.
- Add Reflexion only to output where safety or quality is critical. The triple token cost is real, so pay it deliberately.
- Skip Tree-of-Thoughts unless you do adversarial search or generative design. Modern Claude is too capable for the overhead to pay back on ordinary workloads.
- Switch from Sonnet to Opus inside the loop to save money. Most turns do not need Opus.
- Log every tool_use and tool_result. Future you will be grateful.
The loops are simple. The discipline is in not building a bigger loop than you need.