Agent patterns are control-flow choices with cost and reliability consequences. Day 12 compares ReAct (reason+act loops), plan-then-act, and compiler-style agents that emit plans executed by deterministic machinery — and shows how to cap iterations before token fires.
⚡ TL;DR: Use ReAct for short exploratory tasks with tight caps. Use plan-then-act when the steps are knowable up front. Prefer compiler/codemod paths for mechanical edits. Always enforce max steps, max tool calls, and wall-clock budgets.
ReAct: flexible and hungry
ReAct interleaves thoughts and tool calls. It shines when the next action depends on live observations (search → read → patch). It fails when the model thrash-loops on the same error.
# ✅ Hard caps in the orchestrator — not suggestions in the prompt
MAX_STEPS = 8
MAX_TOOL_CALLS = 12
DEADLINE = time.time() + 120
while step < MAX_STEPS and time.time() < DEADLINE:
turn = model.act(state)
if turn.done:
break
result = tools.run(turn.tool, turn.args)
state = state.with_tool(result)
step += 1
else:
return fail("agent_budget_exceeded")
❌ Unbounded while True until the model says “final answer.”
Plan-then-act: cheaper when the map is clear
Ask once for a structured plan (steps[]), validate it, then execute without re-planning every hop. Better for “add logging to these three handlers” than for open-ended debugging.
- Pros: fewer tokens, auditable plan for humans.
- Cons: brittle if early steps invalidate later ones — allow a single replan on typed failure.
Compiler / structured intent agents
The model emits intents (rename_symbol, add_iam_statement) and a deterministic engine applies them. This is the highest reliability pattern for mechanical work (see AST-guided edits culture). Use when the transformation vocabulary is closed.
{"intent": "add_retry", "file": "src/http.ts", "fn": "fetchUser", "maxRetries": 3}
The compiler rejects unknown intents — structured outputs from Day 8 apply directly.
Choosing under budget
| Task shape | Prefer | Cap |
|---|---|---|
| Explore unfamiliar repo | ReAct | low steps, high refuse |
| Known multi-file change | Plan-then-act | replan ≤1 |
| Mechanical refactor | Compiler intents | no freeform shell |
| Long tests / builds | Async tools + HITL | wall clock |
Measure tokens per successful task and loop rate (repeated identical tool args). Loop rate is your early warning metric.
Closing checklist
- [ ] Max steps, tool calls, and wall-clock in orchestrator code
- [ ] Detect repeated identical tool calls and abort
- [ ] Prefer plan-then-act or compilers when the vocabulary is closed
- [ ] Log tokens per successful task by pattern
- [ ] One controlled replan policy — not infinite
- [ ] Ban unbounded shell-ReAct in production paths
Worked example: stop the flake loop
An agent calls run_tests, gets a flake, patches randomly, re-runs, patches again. Without caps you pay for entropy. With caps: after two identical failures, return retryable=false with “escalate to human” and open a draft PR with notes instead of burning 200k tokens.
Failure modes to watch
- Thought spam without tool progress.
- Replanning every step (plan-then-act in name only).
- Compiler intents that still shell out unbounded.
- No metric for repeated tool args.
Field notes from production
Detect loops by hashing tool name+canonical args. On third identical hash, abort with LOOP_DETECTED. This single rule saves more money than most prompt tweaks. Also budget tokens separately from steps — a step can still be huge.
Implementation sketch
# Implementation sketch: loop detector
seen = {}
def guard(tool, args):
h = hash((tool, canonicalize(args)))
seen[h] = seen.get(h, 0) + 1
if seen[h] >= 3: raise LoopDetected()
Operator addendum
Expose the agent pattern choice in traces (pattern=react|plan|compiler). When cost spikes, you want to know which control flow to fix, not just which model ID billed you.
Tokens per successful task
Define success oracles per workflow (tests green, PR opened, answer cited). Track median tokens and step counts for successes only — failures skew averages. When migrating from ReAct to plan-then-act, require the new pattern to win on both quality evals and tokens/success. Watch for “false success” where the agent stops early with a polite message; count those as failures in the oracle. Cap configurations belong in config files reviewed like code, not in a prompt that says “try not to loop.”
Extended discussion
Return to the core angle for Day 12: When loops help, when they burn tokens, and how to cap iterations. That sentence is the acceptance lens for every design review this week. If a proposed change does not make this angle easier to measure or enforce, it is a distraction.
Write down three metrics you will look at after shipping Day 12 ideas, schedule a 45-minute readout, and archive the notes next to the eval artifacts. Architecture without a readout becomes slideshow archaeology.
Pair this day with the adjacent lessons in the series navigation below. Forward links exist so you can keep momentum; backward links exist so you can repair foundations when a later lab fails for boring earlier reasons.
Practically, allocate half a day to implement the smallest vertical slice, half a day to wire measurement, and refuse to polish UI until both are done. This ordering is how bootcamp projects stay honest under time pressure.
Revisit assumptions whenever the model ID, embedding ID, or index alias changes — treat those as breaking changes for Day 12 behaviors, with the same seriousness as a database migration. Canary first, then promote.
Series navigation
Last updated September 11, 2026
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
