"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


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.