A Field Guide to Agent Loops
"Put the agent in a loop" hides a lot of choices. Which loop? There's a small zoo of them — and picking the wrong shape is why some agents grind forever, some quit early, and some melt your token budget. This is the visual catalog.
Thesis: Agent loops come in a handful of shapes (how control flows) and are described by a few axes (what ends them, what they remember, who checks them). You don't need to invent a loop — you need to recognize which one your task wants. This post is a skim-and-bookmark reference; the rest of the series goes deep on the hard parts.
This is Post 2 of "The Loop" — a reference interlude. Post 1 argued the loop is the unit of agentic work; here's the map of what that loop can look like before we dive into the tricky ones.
The Atom: Reason → Act → Observe
Every agent loop, no matter how elaborate, is built from one cell: the model reasons, takes an action, observes the result, and feeds that back in. This is the ReAct cycle, and it's the "hello world" of agentic behavior.
flowchart LR
Reason["reason<br/>(what next?)"] --> Act["act<br/>(tool call)"]
Act --> Observe["observe<br/>(result)"]
Observe --> Reason
Observe -.->|"stop condition"| Done["done"]
style Done fill:#2d6a4f,color:#fff
style Reason fill:#5a5a8a,color:#fff
Everything below is this atom, arranged into different control-flow shapes and wired to different stop conditions. Keep it in mind — the fancy diagrams are just this one, repeated.
Part 1 — The Shapes (control-flow constructs)
How does control actually flow through the loop? Six shapes cover almost everything.
Bounded — run at most N times
The simplest leash: iterate a fixed number of times, then stop no matter what. Predictable cost, but it stops on a count, not on done — which, as Post 3 argues, is usually the wrong reason to stop.
flowchart LR
Start --> Body["do work"]
Body --> Check{"i < N?"}
Check -->|yes| Body
Check -->|no| Stop["stop (count reached)"]
style Stop fill:#9d4444,color:#fff
Conditional — run while a gap is open
Loop until a condition is met — ideally a verified condition ("zero failing tests"). Runs as long as it needs, no more. The workhorse of good agent loops.
flowchart LR
Start --> Check{"gap open?"}
Check -->|yes| Body["do work"]
Body --> Check
Check -->|no| Stop["stop (gap closed)"]
style Stop fill:#2d6a4f,color:#fff
Generate-and-check: act first, judge after
A do-while: produce something, then run it past a check; if it fails, refine and go again. This is the shape of Self-Refine and every "make the tests pass" loop.
flowchart LR
Gen["generate / refine"] --> Chk{"passes check?"}
Chk -->|no, here's why| Gen
Chk -->|yes| Stop["ship"]
style Stop fill:#2d6a4f,color:#fff
style Gen fill:#5a5a8a,color:#fff
Fan-out and join — one loop, many parallel bodies
When the work is a set of independent items, don't loop serially — dispatch them in parallel and join. Each branch may be its own little loop.
flowchart TD
Split["split into items"] --> A["work item 1"]
Split --> B["work item 2"]
Split --> C["work item 3"]
A --> Join["join results"]
B --> Join
C --> Join
Join --> Stop["done"]
style Stop fill:#2d6a4f,color:#fff
style Split fill:#5a5a8a,color:#fff
Pipeline: each item through stages
When items need the same sequence of steps, stream them through stages independently. Item A can be in stage 3 while item B is still in stage 1 — no barrier between stages.
flowchart LR
In["items"] --> S1["stage 1"] --> S2["stage 2"] --> S3["stage 3"] --> Out["done"]
style Out fill:#2d6a4f,color:#fff
Recursive / nested — a step that is itself a loop
When a step is too big to do in one pass, it becomes its own loop. This is where recursion enters — and where it can run away if there's no floor (Post 7).
flowchart TD
Loop["loop"] --> Step["a step"]
Step -->|"step is big →<br/>spawn sub-work"| Child["child loop<br/>(same shape)"]
Child --> Loop
Step -->|"step is small →<br/>do it directly"| Floor["floor (no sub-loop)"]
style Floor fill:#2d6a4f,color:#fff
style Child fill:#5a5a8a,color:#fff
The shapes at a glance:
| Shape | Control flow | Reach for it when |
|---|---|---|
| Bounded | Fixed N iterations | Exploring; hard cost ceiling; throwaway |
| Conditional | While gap open | You have a checkable "done" (the default) |
| Generate-and-check | Produce → judge → refine | Output can be evaluated and improved |
| Fan-out / join | Parallel bodies, then barrier | Independent items, need all results |
| Pipeline | Items flow through stages | Same steps per item, want throughput |
| Recursive | Step spawns a sub-loop | A step is too big for one pass |
Part 2 — The Stop Signals (types by termination)
The single most important thing about a loop is what makes it stop. Same body, different terminator, completely different behavior.
flowchart TD
Body["loop body"] --> Q{"what ends it?"}
Q --> Count["count<br/>N iterations"]
Q --> Gap["verified gap<br/>check says done"]
Q --> Budget["budget<br/>tokens / time spent"]
Q --> Plateau["plateau<br/>no progress in K rounds"]
Q --> Dry["dry<br/>no work left in ledger"]
Q --> Human["human<br/>a person approves"]
style Gap fill:#2d6a4f,color:#fff
style Count fill:#9d4444,color:#fff
| Terminator | Stops when | Watch out for |
|---|---|---|
| Count | A fixed number of iterations elapse | Stops whether or not the work is done (Post 3) |
| Verified gap | An independent check says the bar is met | Needs a checkable goal; the ideal default |
| Budget | Tokens / wall-clock / cost limit hit | A backstop, not a definition of done |
| Plateau | No measurable progress for K rounds | Detecting "no progress" reliably is hard |
| Dry | No open tasks remain in the ledger | Only as good as what's in the ledger |
| Human | A person reviews and approves | Doesn't scale; but it's a real external check |
The healthy pattern is verified-gap as the terminator, budget as the backstop — stop when the work is right, bail loudly if it can't be. Everything else is a special case or a smell.
Part 3 — The Two Axes That Decide Everything
Beyond shape and stop signal, two axes determine whether a loop is trustworthy. They're the subject of the next few posts, but here's the map.
Axis 1 — Memory: does context accumulate, or reset each iteration? Axis 2 — Grader: does the loop check itself, or does something external check it?
Plot them and four quadrants fall out:
| Self-graded | Externally graded | |
|---|---|---|
| Accumulating context | ⚠️ The danger zone: context rots and the agent grades its own work | Better: honest verdict, but degrades over long runs |
| Fresh context (disk memory) | Sharper each iteration, but still trusts its own "done" | ✅ The target: fresh every time, verdict from outside |
The top-left quadrant isn't always wrong, though — a quick, throwaway, exploratory one-shot is a fine place for a cheap self-graded loop with accumulating context; the danger is running that shape for long, high-stakes work.
flowchart TD
Q1["Where does memory live?"] -->|"in the growing transcript"| Rot["accumulates → context rot"]
Q1 -->|"on disk, fresh each iter"| Fresh["stays sharp"]
Q2["Who decides 'done'?"] -->|"the agent itself"| Lie["can lie to escape the loop"]
Q2 -->|"an external check"| Trust["verdict you can trust"]
style Rot fill:#9d4444,color:#fff
style Lie fill:#9d4444,color:#fff
style Fresh fill:#2d6a4f,color:#fff
style Trust fill:#2d6a4f,color:#fff
If you take one thing from this primer: aim for the bottom-right — fresh context, external grader. Posts 4 and 5 are why.
Part 4 — Which Loop Do I Want?
A quick decision path. Start at the top; the leaves are the shape + terminator to reach for.
flowchart TD
Start{"Can you check<br/>'done' automatically?"}
Start -->|"no"| Human["Human-gated loop<br/>(or go build the check first)"]
Start -->|"yes"| Multi{"Independent items,<br/>or one evolving artifact?"}
Multi -->|"many items"| Same{"Same steps<br/>each item?"}
Same -->|"yes"| Pipe["Pipeline"]
Same -->|"no / need all results"| Fan["Fan-out + join"]
Multi -->|"one artifact"| Big{"Any step too big<br/>for one pass?"}
Big -->|"yes"| Rec["Recursive: conditional loop<br/>that spawns child loops"]
Big -->|"no"| Cond["Conditional loop:<br/>while verified gap open,<br/>budget as backstop"]
style Cond fill:#2d6a4f,color:#fff
style Pipe fill:#2d6a4f,color:#fff
style Fan fill:#2d6a4f,color:#fff
style Rec fill:#2d6a4f,color:#fff
style Human fill:#5a5a8a,color:#fff
Notice every green leaf shares a spine: a checkable stop condition. If you can't reach a green leaf, the honest first move isn't picking a loop — it's making the goal checkable, which is exactly Post 3.
The One-Page Version
- Every loop is the reason→act→observe atom, arranged and terminated differently.
- Shapes: bounded · conditional · generate-and-check · fan-out/join · pipeline · recursive.
- Terminators: count · verified-gap · budget · plateau · dry · human. Prefer verified-gap, with budget as backstop.
- Two axes: memory (accumulate vs fresh) and grader (self vs external). Aim for fresh + external.
- Recursion is just a conditional loop whose step spawns another conditional loop — safe when it has a floor.
What to Do Next
- Identify which shape your current loop actually is (Part 1's list). Most agents are a shape by accident, not by design.
- Check what terminates it. If the answer is "a retry count," that's the first thing to change.
- Plot your loop on the two axes (Part 3). If you land in the top-left quadrant, that's the one to fix first.
- Run new loop designs through the Part 4 decision tree before you build them — if you can't reach a green leaf, make the goal checkable before you pick a shape.
Where the Series Goes Deep
- Termination — Loop on the Gap, Not the Try-Count
- Trusting "done" — Agents Will Lie to Escape the Loop
- Memory — The Files Are the Memory
- Control — The Loop Is a Ledger, Not an Orchestrator
- Recursion — Recursion That Can't Run Away
Next: Loop on the Gap, Not the Try-Count — the first and most important axis, termination. Back to the frame: The Agent Is a While Loop.