🧭 Design the five-step cycle
Every decent agent loop follows the same five-step skeleton: discover (pull in the missing context), plan (decide the next move), act (execute — run code, call a tool, write), verify (check whether the result is any good) and stop (exit the loop, either successfully or by giving up). Each of these five is a node. The entire loop is a graph.
🆕 New here? Two words before you continue
- Loop: a section of a process that repeats until a condition changes. Here, the graph path loops back to a previous node—Track 1 has already shown you this.
- Verify: the step that decides whether the work is any good. It becomes the entire focus of Module 2.2.
What to look at: follow the "verify" arrow back to "discover" — this is the edge that closes the cycle and turns the queue of steps into a real loop. The other exit, to "stop", is what keeps the graph from running forever. Without both arrows leaving "verify," you don't have a loop; you have a disguised DAG.
Discovery + planning are cheap; acting and verifying are expensive
Combining context and making decisions cost tokens. Taking action (running code, editing a file) and verifying (running a test, calling another model) cost real time and money. Optimize the loop by reducing rounds, not the two cheap steps.
Not every loop needs all five steps spelled out
A simple “try again” loop can combine discover + plan into a single prompt. But all five still exist underneath — naming them helps you debug when the loop gets stuck.
Key concepts
Discover, plan, act, verify, stop
The loop and the graph of these five nodes
Loop back (cycle) or stop (exit)
Optimize by reducing round trips
🛑 Write the stopping condition
There are two ways for a loop to stop: on success (the verifier approved) or by giving up (hit a limit and the loop won't get past it, no matter how much you insist). A loop that only knows how to stop on success is a loop that can run forever—you need both outcomes, not just one.
💡 Layered stopping
A good stopping condition is never just one line. It’s a stack of three:
- •Normal criterion — “the tests passed,” “the reviewer approved it.” The happy path.
- •Cap — a hard number: max 10 iterations, or max $2 in tokens. Regardless of the criterion, that cuts off the loop.
- •Human — if it hits the limit without succeeding, someone needs to be notified. Silent abandonment is the worst possible outcome.
✓ Good stopping condition
- ✓Criterion verifiable by code, not “looks good”
- ✓Explicit iteration cap in the loop code
- ✓Abandonment triggers an alert for a human
✗ Poor stopping condition
- ✗"The model decides when to stop" with no limit underneath
- ✗There is a cap, but when it’s reached, it just ends without notifying anyone
- ✗Success criterion is “the LLM said it looks good” — with no external reference
⚠️ Warning: silent abandonment looks like success
A loop that hits its limit and simply stops, without logging anything or notifying anyone, is the worst kind of bug: it looks like it worked because there was no visible error. Treat giving up as a first-class event — it needs to show up somewhere a human will see it.
Key concepts
Verifier approved
Hit the ceiling without going over
Criterion + cap + human
Abandonment needs to send an alert
🎒 Decide what carries over between turns
Each iteration of the loop has access to a context window — the amount of text the model can "see" at once, including the prompt, history, and tool results. The question you need to answer before writing the loop is: what carries over from one iteration to the next, and what gets left behind?
🆕 New here? Context window
Think of it as a fixed-size workbench. Everything the model needs to see in that round — instructions, files, previous error — has to fit on it. Passing too much won’t fit; passing too little leaves out something essential.
✗ Too much context
- ✗The context window gets clogged: the model ignores or forgets relevant parts
- ✗Each iteration gets slower and more expensive
- ✗Old noise (attempts already discarded) confuses the current decision
✗ Too little context
- ✗The loop repeats the same mistake because it forgot it already tried that
- ✗Loses a decision made two rounds ago and contradicts itself
- ✗Reconstructs from scratch information that had already been discovered
📊 What usually passes through
- Original objective — always. Without it, the loop drifts.
- Last error/result — what needs to be fixed now.
- Decisions already made — summarized, not the entire raw history.
- Turn counter — to know the distance to the abandonment threshold.
This decision—what stays, what gets removed, and how to summarize what’s removed—is the seed of what Track 4 will call state schema: an explicit, named definition of what travels between us. In a simple loop, you can decide this informally. In a graph with multiple nodes, informal doesn’t hold up.
Key concepts
Fixed-size table
Ignores or forgets what's relevant
Loses what has already been tried
Formalize in Track 4
🔂 Recognize the Ralph loop
There is a radically simple version of a loop that became popular: run the same prompt repeatedly until the goal is reached, without changing anything between rounds except what the agent itself wrote to files. This technique was popularized through the method Ralph, by Geoffrey Huntley — hence the nickname "Ralph loop" you’ll hear around.
The prompt stays fixed; the world changes
You don't rewrite the prompt every iteration.
The agent reads the repository’s current state (files, tests, TODOs), and a generic prompt is enough to decide the next step—because the environment itself has become the memory.
Works well for large, well-defined tasks
"Leave it running all night, migrating this codebase."
Each iteration advances a little, records what it has done (in a progress file, for example), and the next iteration reads that marker. The iteration limit and cost limit remain mandatory.
💡 Simple isn't wrong; it's incomplete
The Ralph loop isn’t a “lesser” or naive technique—it’s the loop in its rawest form, and raw works for a lot of things. What it doesn’t solve on its own is exactly what the next two topics (open vs. closed, nesting) and Module 2.2 (the verifier) will cover: without a real verifier and without knowing when to stop, “run the same prompt” becomes “run the same prompt forever.”
Key concepts
Same prompt every iteration
Geoffrey Huntley
Files carry progress
Still needs a verifier and a limit
🔄 Close the loop (open vs. closed)
A loop closed measures the effect of what it just did and uses that measure to decide the next iteration—this is called feedback (in English, feedback): the result of one action becomes the input for the next. A loop open just executes, without looking back.
⚠️ Without measuring the effect, it’s an expensive cron job
A script that runs every hour and never checks whether what it did worked isn’t an agent loop — it’s a scheduler. That’s exactly the difference between a real loop and a cron job disguised as AI: the loop reacts to its own result. If you remove the feedback, all that’s left is repetition — and repetition without reaction is expensive and learns nothing.
✓ Closed loop
- ✓"I ran the test, it failed on this line, so I fix this line"
- ✓The verifier from topic 1 feeds into the next "plan"
- ✓The quality improves (or at least changes) with each iteration
✗ Open loop
- ✗"Send the email every day at 9 a.m.," without checking whether anyone reads it
- ✗Repeats the same action even when it keeps failing
- ✗There's no real “planning”—just “acting” again
And here’s a limit worth remembering: the quality of the measure is the quality ceiling for the entire loop. If your verifier only checks “the code ran without errors” but not “the code does what it’s supposed to,” your closed loop converges (reaches a stable answer)—but it converges on the wrong thing. Closing the loop isn’t enough; closing it well is the work.
Key concepts
The result becomes the input
Responds vs. just executes
Costly, blind repetition
A loop is only as good as its measurement
🪆 Nest loops (and write the first one)
Nothing prevents a loop from containing another loop. The outer loop decides what do (which task to pick up next), the inner loop runs how do that specific task until its own verifier passes. Each turn of the outer loop becomes a node in the larger graph itself.
What to look at: the large box has its own return arrow—it’s a loop. Each small box inside it also has a return arrow—another loop, hidden inside the first one. That’s nesting: a loop whose nodes, when you open them, are themselves entire loops.
Nesting works well as long as the loops don’t talk to each other
Each internal loop handles its task in isolation, without needing to know what the neighboring task loop is doing. It’s a legitimate, inexpensive pattern to scale.
When internal agents need to talk to each other, the external one can’t handle it alone
If task 2 depends on something task 1 discovered, “choosing the next task” is no longer enough — you need explicit edges between the internal loops. This is the bridge to the graph, and Track 3 will break down when it makes sense.
🧪 Practical example: a minimal loop with layered stopping
Objective: see the five steps and the layered stop condition running for real, in a short pseudocode snippet you can run in the browser console (F12) or in Node.
// condicao de parada em camadas: criterio + teto
function loop(tarefa, verificar, agir, MAX_VOLTAS = 5) {
let contexto = { tarefa, tentativas: [] };
for (let volta = 1; volta <= MAX_VOLTAS; volta++) {
// descobrir + planejar (aqui simplificados: so olham o contexto)
const plano = "tentativa " + volta + " para: " + contexto.tarefa;
// agir
const resultado = agir(plano, contexto);
contexto.tentativas.push(resultado);
// verificar
if (verificar(resultado)) {
return { status: "sucesso", volta, resultado }; // parar por sucesso
}
}
return { status: "desistencia", volta: MAX_VOLTAS }; // parar por teto
}
// exemplo concreto: "adivinhar" um numero
const alvo = 7;
const r = loop(
"achar o numero " + alvo,
(res) => res === alvo,
(plano, ctx) => ctx.tentativas.length + 3 // tenta 3, 4, 5, 6, 7...
);
console.log(r);
How to verify: you should see
{ status: "sucesso", volta: 5, resultado: 7 } in the console — the loop found the target on the 5th pass and stopped.
Change MAX_VOLTAS to 3 and run it again: now you should see
{ status: "desistencia", volta: 3 } — the limit cut it off before success.
Now swap it for yours: replace
<agir> through your actual action (calling a model, running a command) and
<verificar> with a real test. If "giving up" never shows up in your actual log, your cap is probably too high for you to notice.
Quick check (doesn’t block anything): a loop runs 40 iterations without ever measuring whether the result improved, just repeating the same action. This is:
Key concepts
Which task to pick up
How to do that task
Each outer iteration is a larger node
When internal agents need to talk
📌 Module Summary
Next Module:
2.2 — The verifier is the bottleneck: why the quality of your loop never exceeds the quality of whoever checks the result.