← Back to postsCoding Notes
EnglishPublished Jul 2, 2026Updated Jul 2, 202612 min read

Loop Engineering for Agentic Development: Rules, Patterns, and a Worked Example

Tips

An AI agent is not a single call to a model — it is a loop. The model observes, decides, acts with a tool, sees the result, and goes again until the job is done. Loop engineering is the discipline of designing and tuning that cycle so it converges on a correct answer instead of drifting, spinning forever, or confidently declaring a wrong result "done." This guide walks the full picture: what the agent loop is, the rules that keep it healthy, the patterns you compose from those rules, when to express a loop as a prompt versus as code, and a fully worked example that maps every design choice back to the rule it satisfies.


What the Agent Loop Actually Is

At its core, every agent runs the same cycle — often called the ReAct loop (Reason + Act):

code
observe → think → act → observe result → repeat

Each turn, the model sees the current state, chooses a tool, runs it, and feeds the result back into the next turn. The loop continues until the task is done or a stop condition fires. Here is the entire spine in Python against the Claude API — everything else in this article is tuning layered on top of it:

python
import anthropic

client = anthropic.Anthropic()

TOOLS = [{
    "name": "run_bash",
    "description": "Run a shell command, return stdout+stderr.",
    "input_schema": {
        "type": "object",
        "properties": {"cmd": {"type": "string"}},
        "required": ["cmd"],
    },
}]

def run_bash(cmd: str) -> str:
    import subprocess
    r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=60)
    return (r.stdout + r.stderr)[:4000]  # truncate — context is a budget

def agent_loop(task: str, max_steps: int = 25):
    messages = [{"role": "user", "content": task}]

    for step in range(max_steps):              # stop condition #1: step cap
        resp = client.messages.create(
            model="claude-opus-4-8",
            max_tokens=4096,
            tools=TOOLS,
            messages=messages,
        )
        messages.append({"role": "assistant", "content": resp.content})

        if resp.stop_reason != "tool_use":     # stop condition #2: model is done
            return "".join(b.text for b in resp.content if b.type == "text")

        results = []
        for block in resp.content:
            if block.type == "tool_use":
                try:
                    out = run_bash(**block.input)
                except Exception as e:
                    out = f"ERROR: {e}"          # error recovery — don't crash the loop
                results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": out,
                })
        messages.append({"role": "user", "content": results})

    raise RuntimeError("hit max_steps — no convergence")  # stop condition #3

Four lines already carry most of the engineering: max_steps prevents an infinite spin, stop_reason != "tool_use" lets the model signal completion, [:4000] protects the context window, and the try/except turns a tool failure into feedback the model can recover from rather than a crash. Everything below is about doing these four things well and adding the pieces a toy loop leaves out.

The key mental model: agent quality is loop quality, not just model intelligence. The same model wrapped in a better loop produces dramatically better results.


The 12 Rules of Loop Engineering

These are the constraints that separate a robust loop from one that burns tokens and lies about being finished.

1. Always define a stop condition

Every loop must terminate. Provide at least three exits: success (the goal is verifiably met), cap (a maximum number of rounds or steps), and budget (a ceiling on tokens or time). A loop with no stop condition will spin forever — this is the single most common failure.

2. Feed the loop a ground-truth signal

A loop converges on feedback. Real signals — a compiler error, a failing test, a linter, a diff — beat the model's own opinion of its work. With no external signal, the loop drifts and eventually hallucinates that it is done.

3. Each round must converge

Round N should be measurably better than round N-1. Track progress. If the loop oscillates (fixing A breaks B, fixing B breaks A), detect it and break out. When improvement flatlines, that diminishing return is itself a stop condition.

4. Verify before you trust "done"

The model claiming success is not the same as success. Gate completion behind a check. A separate critic beats self-checking, because a model shares its own blind spots. Make verification adversarial: prompt the checker to refute the result, not confirm it.

5. Treat context as a budget, not a dump

Curate what enters the window each round. Prune stale tool output, summarize when the window fills, and offload durable state to disk (a todo.md, a notes file). Spawn a sub-agent to absorb a noisy subtask and hand back one clean answer.

6. Failures feed back — they never crash

A tool error should be caught, formatted as text, and returned to the model so it can re-plan. One failed call must not kill the loop.

7. Separate the actor from the critic

Whoever does the work should not be the one who judges it. A model grading its own output is biased toward passing. Split the roles.

8. Isolate noisy subtasks

A wide search or a large file dump belongs in a sub-agent whose context absorbs the mess. The main loop stays lean, which protects convergence and keeps the window affordable.

9. Prefer a pipeline over a barrier

When processing many items, let each flow through the stages independently. Only introduce a barrier (wait for all results) when a downstream stage genuinely needs every upstream result at once — deduplication, a global count, an early exit. Otherwise a barrier wastes wall-clock time.

10. Deduplicate across rounds

If the loop rediscovers the same issue every round, the count never drops and it never converges. Keep a seen set and dedupe against everything seen so far, not just the current round.

11. Log what you dropped

If a loop bounds coverage — top-N only, sampling, no retries — say so. Silent truncation reads as "covered everything" when it did not.

12. Scale the loop to the task

A small task deserves a tight loop with a couple of agents. A large audit, migration, or research task justifies a wide fan-out and several verification votes. Do not over-engineer the trivial, and do not under-engineer the hard.

The one-line core: terminate, converge, verify, curate. Miss any one and the loop fails in a predictable way.


Patterns You Compose From the Rules

The rules are primitives. Real loops combine them into recognizable shapes.

PatternWhat it doesUse when
Single loopOne agent grinds until doneSimple, linear task
Fan-out (parallel)Split independent work across agents, run concurrently, mergeWide coverage — e.g. review 10 files with 10 agents
PipelineEach item flows through stages with no barrier between themMulti-stage work where items are independent
Plan-then-executePlan first, then loop over each stepTask benefits from an upfront decomposition
Judge panelN agents solve the same problem from different angles; judges score; synthesize from the winnerThe solution space is wide
Loop-until-dryKeep spawning finders until K consecutive rounds surface nothing newUnknown-size discovery (bugs, edge cases)
Actor–critic loopAct → a separate critic checks → redo if it failsOutput quality matters and can be judged

Two verification patterns are worth calling out because they defend against the subtlest failure — a loop that converges on a stable wrong answer:

  • Adversarial verify: spawn several independent skeptics per finding, each told to refute it, and kill the finding if a majority succeed.
  • Perspective-diverse verify: give each verifier a different lens (correctness, security, does-it-reproduce). Redundant checkers share blind spots; diverse checkers do not.

Prompt or Code? You Rarely Need to Write a Loop by Hand

Loop engineering is a set of ideas, not a language. You can express a fully engineered loop in plain English — the rules simply become instructions. In a harness like Claude Code, describing the loop in a prompt is enough for the great majority of tasks; the orchestration is handled for you.

Prompt (English)Code (workflow script)
EffortLow — just describe itWrite and debug a script
ControlThe harness infers structureYou pin the exact flow
DeterminismVaries run to runSame every run
ScaleA handful of agentsDozens, with a concurrency cap
ResumeNoYes, cached
Best forMost tasks, one-offsMigrations, audits, repeatable production jobs

The rule of thumb: reach for a prompt first. Drop to code only when you need an exactly repeatable flow, a large fan-out, resume-after-crash, or a job you will run on a schedule.

A "loop command" is not loop engineering. Some tools ship a scheduling primitive — for example, Claude Code's /loop command re-runs a prompt on a fixed interval (/loop 5m /review) or lets the model self-pace. That is a time loop: repeat the same task every N minutes, typically to poll something that changes on its own (a CI run, a deploy, a queue). Loop engineering is a different axis entirely — it is about the convergence cycle inside a single task: observe → act → verify → repeat until the work is correct. A scheduled command repeats because the clock ticked; an engineered loop repeats because the result is not good enough yet. You can run an engineered loop with no scheduler at all, and you can schedule a command that contains no engineered loop. Don't conflate the two.


A Worked Example: A UI/UX Polish Loop

Consider a common request: "Act as a senior UI/UX reviewer — look at the pages, and where something is wrong or could be polished, report it to a senior frontend engineer to fix. Then review again and report again, polishing further..."

That describes a loop, but it is not yet engineered. As written it has no stop condition ("polishing further" is infinite), a vague quality signal ("polish"), no deduplication (it will re-report fixed issues every round), and no verification (it trusts the engineer's claim of "fixed"). Here is the same idea written as a hardened prompt, followed by a table mapping each clause to the rule it enforces.

text
Act as a SENIOR UI/UX reviewer auditing these pages: home.html, checkout.html.
Work with a SENIOR FRONTEND ENGINEER (separate role) who applies fixes.

LOOP each round:

1. REVIEW — find concrete issues only. For each: cite the selector/element,
   the exact problem, and why it hurts UX (accessibility, hierarchy, spacing,
   contrast, responsiveness). No vague "make it nicer" — every issue must be
   verifiable against a standard or a specific broken behavior.

2. FIX — hand each NEW issue to the FE engineer. They edit the file, return the diff.

3. VERIFY — a separate check on each fix: actively TRY to prove the fix did NOT
   work. Default to "NOT fixed" if uncertain. Mark fixed only if genuinely resolved.
   Also confirm the fix did not break another page or element.

4. Record: issues closed this round, issues still open, any fix that broke
   something else.

STOP when ANY of these hit:
  - No new issues found in a full review round (converged), OR
  - 3 rounds completed, OR
  - Issue count stops dropping round-over-round (diminishing return — stop, report why).

GUARDS:
  - Don't re-report issues already fixed and verified. Track what's closed.
  - If fixing issue A re-opens or breaks issue B (oscillation), STOP that pair,
    flag it for a human decision — do not loop A<->B.
  - Never claim "all done" unless the final review round found zero issues.

OUTPUT each round: fixed (verified) | still open | broke-something | round number.
FINAL: total closed, total still open, any oscillations flagged, why the loop stopped.
Clause in the promptRule it enforcesWhy it matters
"senior UI/UX reviewer" + named pagesR7 actor≠critic, scope boundSets a distinct critic role and stops scope creep
"cite the selector… no vague 'make it nicer'"R2 ground truthForces measurable issues so the loop can tell if it improved
"hand each NEW issue to the FE engineer"R7 + R8Separate fixer role; each issue is an isolated unit
"actively TRY to prove the fix did NOT work"R4 adversarial verifyCatches fake or partial fixes instead of trusting a claim
"confirm the fix did not break another page"R3 regression checkPrevents progress in one place from causing damage elsewhere
"STOP when… 3 rounds / no new issues / count stops dropping"R1 + R3Three real exits, including diminishing-return
"don't re-report issues already fixed"R10 dedupeKeeps the issue count meaningful so convergence is visible
"if A breaks B… do not loop A<->B"R3 oscillation guardBreaks the sneaky infinite loop a round cap alone won't catch
"never claim 'all done' unless… zero issues" + final auditR11 honest reportingReports remaining work truthfully instead of faking completion

The engineered version is the same idea as the naive request — the engineering is entirely in the guards that were missing.


The Non-Obvious Parts

The rules keep a loop healthy; these are the judgment calls, failure modes, and operational realities that are easy to overlook even once you know the rules. They fall into three groups.

Judgment: whether and how tightly to loop

  • Know when not to loop. For a genuine one-shot task, or one with no verifiable signal, a loop only adds cost, latency, and spin risk. Loop only when iteration genuinely improves the result and you can measure the improvement.
  • Keep a human in the loop. Irreversible actions (deleting data, deploying, spending money) and genuinely ambiguous judgments ("is this good enough?") should pause and escalate rather than push through.
  • Tune the loop granularity. Verifying after every tiny step is slow and expensive; verifying only at the very end lets the loop drift far before you catch a problem. Match the checking cadence to how risky each step is — cheap, reversible steps can batch; expensive or irreversible ones deserve their own gate.

Failure modes to defend against

  • Garbage in, polished garbage out. A loop is only as good as its round-one input. A wrong goal or wrong target gets refined into a confident wrong answer. Fix the seed before you trust the loop.
  • State poisoning compounds across rounds. A bad or hallucinated output in an early round becomes the input to the next round, where it compounds. This is why verification belongs before state carries forward, not only at the end — catch the corruption before it propagates.
  • Convergence is not correctness. A loop can settle on a stable wrong answer, and redundant verifiers that share a blind spot will happily agree on it. Diverse verification lenses are the defense.
  • Guard against prompt injection. A loop that reads external content (web pages, user files) has an injection surface that grows with its length. Treat fetched content as data, never as instructions, and never let it rewrite the loop's own rules.

Operating the loop

  • Watch cost per round, not just total. Log what each round spends and does. In a healthy loop the cost per round falls as it converges — rising or flat cost across rounds is a signal the loop is thrashing, not progressing. A blind loop is impossible to debug once it starts to spin.
  • Expect non-determinism. The same prompt can produce a different result on each run, because the model is stochastic. This matters for reproducibility, testing, and explaining "why was today's run different." A coded workflow with resume is more repeatable than a pure prompt; if you need determinism, lean toward code and pin what you can.
  • Degrade gracefully. When the cap is hit but the goal is not met, return the best result so far plus an honest list of what remains — not an empty failure.

Takeaways

  • An agent is a loop; loop quality, not raw model intelligence, is what you engineer.
  • The four things that matter most: terminate, converge, verify, curate.
  • You almost never need to write loop code — a prompt that spells out the stop condition, the ground-truth signal, the verification step, and the deduplication is loop engineering.
  • The difference between a naive "keep polishing" request and a production-grade loop is entirely in the guards: an explicit stop, an adversarial verifier, cross-round dedup, an oscillation breaker, and an honest final report.