Patterns: six ways to wire a loop
The loop is the same everywhere. What changes is the wiring: who plans, when a human is asked, and whether steps run one at a time or all at once.
- You can pick a pattern from the shape of the task instead of from whatever a tutorial used.
- You know why "reflection" is not a cure-all, and where a human gate actually belongs.
Six patterns cover almost everything built in production today. They are not frameworks — you can implement any of them in a hundred lines with a model client and a while loop. Choosing well is mostly about two questions: does the next step depend on the last result? and what is the cost of a wrong action?
| Pattern | Shape | Best for | Main weakness |
|---|---|---|---|
| ReAct | Think → act → observe, repeatedly | Open-ended research, debugging, browsing | Wanders; context and cost grow fast |
| Plan & execute | Plan once → execute steps → replan on failure | Multi-step work with known shape; anything needing sign-off | Plan is stale the moment reality differs |
| Router | Classify input → send to a specialist path | Support triage, intent handling, cheap/expensive model selection | Misclassification is silent and systematic |
| Reflection | Produce → critique → revise | Writing, code review, catching obvious errors | Self-critique can entrench a confident mistake |
| Human gate | Any pattern + approval before risky action | Money, email, deletion, production changes | Approval fatigue — people click yes |
| Parallel fan-out | Split work → many workers → merge | Independent sub-tasks: 20 files, 10 sources, 5 candidates | Merge quality; duplicated effort; cost spikes |
6.1ReAct, in code
Reasoning and acting interleaved. It is the default because it handles surprise — which is what most real tasks are made of.
trajectory = [system_prompt, goal]
for step in range(MAX_STEPS):
thought = model(context=trajectory, tools=TOOLS)
if thought.is_final:
return verify(thought.answer) # never trust the claim
action = parse(thought.tool_call) # schema-validated
if not permitted(action):
return ask_human(action)
result = run(action, timeout=30) # errors are data
trajectory += [thought, result]
if budget_exceeded() or repeated_failure():
return escalate(trajectory)
The interesting parts are the lines that are not the model call: schema validation, the permission check, the timeout, the repetition detector, the budget. Those five lines are the difference between a demo and something you'd let touch a customer.
6.2Plan first, when the plan is the product
If a human needs to approve the approach, or you need an audit trail of intent, generate the plan as a separate artifact before doing anything. It costs one extra call and buys you reviewability, parallel execution, and a natural place to attach estimates.
plan = model.plan(goal) # steps, tools, dependencies, risks, cost estimate
if plan.requires_approval:
plan = await human.review(plan) # approve / edit / reject
for step in topo_sort(plan.steps): # independent steps can run in parallel
result = run(step.tool, step.args)
if step.failed:
plan = model.replan(goal, plan, results, error) # bounded: 2 attempts
Note the bound on replanning. Unbounded replanning is how an agent spends an afternoon and $40 rediscovering that the API key is expired.
6.3Routing: the cheapest win in the system
Most requests are easy. Sending every one of them to the largest model is the single most common way to waste budget. A router classifies first — by rules, by embedding similarity, or by a small fast model — then hands off.
6.4Choosing: answer two questions
ReAct loop — the next step genuinely depends on what the last one returned, so you need the flexibility; budget for 8–20 iterations and verify the final answer.
"Ask the model to critique its own answer" is popular because it's one line of code. It helps most on small, checkable errors (formatting, missing constraints) and helps least on factual confidence — a model that is wrong and certain will often critique itself into being wrong and more detailed. Prefer external checks: tests, schemas, citations, a second model with different information.
Put it immediately before the irreversible action, not at the start of the task. A person reviewing "the agent will send this email to this customer with this text" makes a real decision. A person approving "the agent may now work on the inbox" has approved nothing specific and will click yes every time.
A finance team wants an agent to reconcile accounts monthly, with a manager signing off. Which pattern fits best?
60-second recap
- Choose by task shape: unknown steps → ReAct; approval or audit → plan-and-execute; mixed difficulty → routing; independent pieces → fan-out.
- Every pattern needs the same safety lines: schema validation, permission check, timeout, repetition detection, budget cap.
- Gate the irreversible action, not the start of the task.
- Reflection is weak without external checks. Tests and citations beat self-critique.