Agent Loops: Claude Code's Creator No Longer Prompts, He Writes Loops
“I no longer prompt Claude. I have loops running, they are the ones prompting Claude and deciding what to do. My job is to write loops.” This sentence is from Boris Cherny, the creator of Claude Code, and the clip has garnered about 700,000 views in 24 hours on X. Since then, everyone is talking about “loop engineering,” and almost no one explains how to create one.
We’re going to fix that, starting from scratch. No prerequisites, other than having typed a question into Claude Code at least once. First, we’ll look at the loop that’s already running before your eyes without you knowing it, and then we’ll build a real one, in three levels of difficulty, with the files to copy and the exact place to put them.
Along the way, we’re going to clear up a very common misunderstanding: the /loop command in Claude Code is not an improvement loop. It’s a timer.
Five Vocabulary Words, and We’re Good
If you’re a beginner, half of the confusion comes from jargon. Five definitions and we’re done talking about it.
- A tool: an action that Claude is allowed to execute on your machine. Read a file, write it, run a command in the terminal, search the web. He doesn’t execute them himself; he requests them, and the Claude Code program executes them for him.
- A turn: a complete round trip. Claude speaks, tools run, results come back to him. A somewhat hefty question means five, ten, or thirty turns, and you only see one scrolling in the terminal.
- The context: everything Claude has in front of him at any given moment. Your request, the files read, command outputs. It’s limited, it fills up, and when it’s full, Claude summarizes the old to make space. This summary is called compaction, and it loses details.
- The file
CLAUDE.md: a text file placed at the root of your project, which Claude reads with each request. It’s your permanent internal regulation. Unlike your first sentence of the conversation, which eventually disappears during compaction, it always stays there. - A sub-agent: a disposable Claude, launched by the main Claude for a specific mission, with its own brand new context. It works in its corner, and only its conclusion comes back. It’s a Markdown file in
.claude/agents/, and you can write as many as you want.
Let’s add a sixth word, the one that will change everything below: a hook, it’s a little script of yours that Claude Code executes automatically at a certain moment, for example just before writing a file, or just before handing back control. A script, not an AI. It does what it’s told, always the same, and it doesn’t cost a single token.
The Loop That’s Already Running Without You Asking for Anything
Before building anything, you need to see the one that already exists. Anthropic’s documentation on the agent loop describes it in five steps, and that’s exactly what happens every time you hit Enter.
- You send your request.
- Claude reads it and responds, either in text or by asking for tools.
- Claude Code executes the requested tools.
- The results go back to Claude, added to the context.
- We start again at step 2, until Claude responds without asking for any tools. That message closes the loop.
Let’s get concrete. You type “fix the tests that fail in auth.ts.” Here’s what really happens, while you only see a wall of text scrolling:
| Turn | What Claude asks | What he gets back |
|---|---|---|
| 1 | runs npm test | three tests failed |
| 2 | reads auth.ts and the test file | the content of both files |
| 3 | modifies auth.ts, reruns npm test | everything is green |
| 4 | nothing, it responds in text | end, the loop stops |
Four turns. You wrote only one sentence, and there were four back-and-forths, six tool calls, and one stop decision. That's the loop.
Remember the consequence, because that's the whole article: an agent IS already a loop. Everything you add on top, sub-agents, hooks, config files, slash commands, serves only one purpose: to decide when it is allowed to stop.
And therein lies the problem. By default, it is Claude himself who decides that he is done. In turn 4, he felt it was good. He did not check that the project still compiles fully, nor that no one has broken something else elsewhere. He just had the feeling that he was finished. Doing loop engineering means taking that decision and entrusting it to something less subjective than a feeling.
The four families of loops
The Claude Code team defines a loop as "an agent that repeats cycles of work until a stop condition is met." There are four kinds, and they do not serve the same purpose at all. This is the table to keep handy.
| Family | Trigger | Stop Condition | What for |
|---|---|---|---|
| By turns | your prompt | Claude feels he is done | daily tasks, short tasks |
By goal (/goal) | your prompt | the goal is reached, or the quota of turns is exhausted | anything with a verifiable criterion |
By time (/loop) | an interval | you cancel, or the session closes | monitoring, external systems |
| Proactive | an event, without you | each task exits when its goal is reached | bug sorting, migrations, upgrades |
No, /loop does not improve anything
The confusion comes from its name. Look at what it really does:
/loop 5m checks if the deployment is completeEvery five minutes, Claude Code sends back the same prompt, word for word. Not "better" each time: identical. The documentation is clear, the command "launches a prompt repeatedly as long as the session remains open." It's a cron with a brain, not an upward spiral. Two useful details though: if you omit the interval, Claude self-paces between two iterations, and if you omit the prompt, it will read .claude/loop.md.
The command that really improves is /goal. It takes a condition in natural language, and Claude continues to work through the turns as long as that condition is false:
/goal raise the Lighthouse score of the homepage to 90 or more, stop after 5 attemptsTwo things matter in this line, and if you remember only one paragraph from the entire article, take this one.
The "90 or more," first. A measurable condition can be verified without any possible discussion. Compare with "make the page faster": faster than what, judged by whom? A loop needs a number or a passing test, not an adjective. Writing a loop is first forcing yourself to transform a wish into a verifiable criterion, and that's frankly the hard part.
The "stop after 5 attempts," next. A loop without a counter is a bill. The model never gets tired, never gets bored, and has no idea what the cost of the thirtieth attempt is.
"Do my agents in .claude already create a loop?"
That's THE question I get, and the answer is no. Well, not quite, and the detail is worth it.
Let's take a classic setup, the one that many end up writing after a few weeks: a sub-agent architect who designs before we code, a sub-agent developer who implements, a sub-agent reviewer who reviews the work, and a CLAUDE.md that specifies the order in which to call them. It's clean, it's useful, it avoids a lot of mistakes. But as it is, it's a chain, not a loop.
The difference lies in a single pencil stroke. In the chain, the reviewer is the last box: they give their report, and the turn ends. In the loop, their report becomes an input for the developer again, and it restarts as long as the verdict is not good.
A loop needs three ingredients, and the third is the one we systematically forget:
- a repeatable action, you already have that, it's your sequence of agents,
- a measurable stop condition, not "when it's good" but "when the build passes and the reviewer finds no more blocking issues,"
- a return edge, meaning a path that brings the output of the reviewer back into the hands of the developer.
Without the third, your reviewer produces a nice report at the end of the run and no one reads it. With it, the chain becomes a cycle and the program improves with each round. Good news: closing this edge takes about ten lines.
Level 1: your first loop takes one line
Before writing any file, know that 80% of the benefit comes from typing this into Claude Code:
/goal the build passes and the tests are green, stop after 5 attemptsClaude will code, run the build, see the error, correct it, rerun, and he will only give you back control when the condition is true or when he has exhausted his five attempts. You just did loop engineering. Really. Try this before anything else.
The limitation is that it only lasts as long as your command. Tomorrow, in a new session, you will have to retype it, and you will forget. Hence the next two levels, which make the rule permanent.
Level 2: the definition of "done," written once and for all
The CLAUDE.md is reviewed with each request, including after a context compaction. It is written in black and white in the SDK documentation: the persistent rules go there, not in your first message, which will eventually disappear. So this is where the stop condition lives.
Create the file at the root of your project, if it does not exist, and add this block to it:
## Definition of Done
A task is not finished unless all four points are true:
1. the build passes, with no new warnings
2. the sub-agent `reviewer` has returned VERDICT: PASS
3. HISTORY.md describes the what and why of the project
4. the version is incremented
As long as one point is false: correct it, then go back to point 1.It's short, it's numbered, and especially the last line explicitly draws the return edge. From now on, Claude will review himself and go back on his own in most cases.
In most cases, yes. Not all. A model remains a model, it sometimes decides that it is finished when it is not, especially at the end of a long session. If you want it to be a law and not just advice, you need level 3.
Level 3: the Stop hook, the one that has the right to say no
Here is the complete structure we will obtain. Everything happens in a .claude/ folder at the root of the project, to be created if it does not exist.
your-project/
.claude/
settings.json which hooks to launch, and when
agents/
reviewer.md the reviewing sub-agent
hooks/
done-or-continue.sh the script that accepts or refuses the end
CLAUDE.md the permanent rules (level 2)1. A reviewer that gives a verdict readable by a machine
The trap of the reviewing sub-agent is that it produces prose. Very well written, often correct, but impossible to derive an automatic decision from: no script knows how to read "overall it's correct but I would have a doubt about error handling." So we impose a final standardized line, and everything becomes possible.
File .claude/agents/reviewer.md. The part between the two dashed lines is called the frontmatter, it is the agent's identity card. The rest is its instructions.
---
name: reviewer
description: Review the work after each completed implementation.
model: opus
tools: Read, Glob, Grep, Bash
---
You only review what has just changed (the diff, plus untracked files).
Class each remark as CRITICAL, WARNING, or INFO.
You never write code, you never commit.
Write your report in .claude/last-review.md and end it with a single line:
VERDICT: PASS if there are no CRITICAL left
VERDICT: FAIL otherwiseNote the tools: which contains neither Edit nor Write. This is not vanity: a reviewer who can correct starts defending their own corrections. We separate the one who does from the one who judges, otherwise the judgment is worthless.
2. The script that refuses the end of the round
The hook Stop triggers right when Claude wants to hand control back to you. He has the right to refuse. File .claude/hooks/done-or-continue.sh, to be made executable with chmod +x :
#!/usr/bin/env bash
# Refuse the end of the round as long as the Definition of Done is false.
set -uo pipefail
[ -f .claude/loop.off ] && exit 0 # emergency switch
STATE=.claude/loop-count
COUNT=$(cat "$STATE" 2>/dev/null || echo 0)
if [ "$COUNT" -ge 5 ]; then # circuit breaker
rm -f "$STATE"
exit 0
fi
if ! npm run build >/tmp/build.log 2>&1; then
echo $((COUNT + 1)) > "$STATE"
jq -n '{decision:"block",
reason:"The build fails. Read /tmp/build.log, fix it, then continue."}'
exit 0
fi
if ! grep -q "VERDICT: PASS" .claude/last-review.md 2>/dev/null; then
echo $((COUNT + 1)) > "$STATE"
jq -n '{decision:"block",
reason:"Green build but no VERDICT: PASS. Restart the sub-agent
reviewer, apply the CRITICAL, then continue."}'
exit 0
fi
rm -f "$STATE"
exit 0If you do not read bash fluently, here is the same thing in English, in order:
- If the file
.claude/loop.offexists, we do nothing and let Claude stop. It's your switch: atouch .claude/loop.offand the loop is neutralized, without killing anything. - We read a counter stored in a small file. If it is already at 5, we give up and let it pass. It's the circuit breaker, it prevents the infinite loop.
- We launch the build. If it breaks, we increment the counter and respond
blockwith a reason. Claude cannot stop and receives this reason as a new instruction. - If the build passes, we look for the line
VERDICT: PASSin the reviewer's report. If absent, we block again, specifying exactly what to do. - If everything is good, we clear the counter and exit silently. Claude has the right to finish.
Three fields are enough to control all this, and there are no others to know to get started. decision: "block" prevents Claude from stopping and transmits your reason as a new instruction. continue: false cuts everything, no matter what happens. And hookSpecificOutput.additionalContext injects a remark without blocking, when you want to guide rather than force. Note for those who do not have jq installed: a simple exit 2 with a message on the error output produces the same blocking, in a more brutal way.
3. Declare the hook
Now we need to tell Claude Code when to run this script. File .claude/settings.json:
{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{ "type": "command", "command": ".claude/hooks/done-or-continue.sh" }
]
}
]
}
}4. Check that it works
The simplest test, and do it, otherwise you will doubt for three days. Intentionally break a line of code, ask Claude for any small modification, and let him try to finish. He should restart on his own while correcting, with your reason visible on the screen. If nothing happens, check in order: the script is executable, the path in settings.json is correct, and running the script manually in a terminal produces no errors.
Result: the developer codes, the reviewer judges, the hook refuses the output until the verdict is good, and it loops up to five times before giving up cleanly. There, yes, you have a loop.
Three loops to try tonight
Harmless examples to get your hands dirty, from the simplest to the least simple.
/goal all tests pass, stop after 5 attemptsThe classic. Works on any project that has a test command.
/goal no more linter warnings in src/, stop after 3 attemptsExcellent first try, because the criterion is binary and the fixes are safe.
/loop 10m watch new files in ./inbox, summarize each in ./resumesThis one is a monitoring loop, not an improvement, and it illustrates the difference well. It does not progress, it watches.
The self-improving loop
There is one more level. So far, the loop improves the program. It can also improve itself, and this is the pattern popularized by Andrej Karpathy in March 2026 with his repository autoresearch, which surpassed 50,000 stars on GitHub in a few weeks. The principle is summed up in one sentence from his README: we give an agent a real little model training, it modifies the code, it trains for five minutes, it checks if the metric has improved, it keeps or discards, and it starts again. All night long.
What makes the difference with a simple /loop is the memory. A file that the agent rereads at the beginning of each pass and updates at the end, with what it has just learned. Without this file, iteration 200 is as foolish as iteration 1, it makes the same mistakes with the same enthusiasm.
Transposed to a normal project, it adds a few more lines in the CLAUDE.md:
## Self-improvement
Before handing over:
- if I had to correct you, add the corresponding rule in LESSONS.md,
a dry sentence, in the imperative, never an explanation
- if a step has been redone twice, note why in NOTES.md
- if a manual check comes back with each project, turn it
into a script
LESSONS.md is reviewed at the beginning of each session, before any action.After a month, the LESSONS.md is worth more than the code. It is the record of the mistakes that the loop will not repeat. And it is the file that everyone forgets to fill out, even though it costs nothing.
Beginner mistakes that are costly
The patterns that recur among everyone running loops in production are not very glamorous, but they are what keeps the house standing.
- A vague stopping condition. "When the code is clean" is not a condition, it's an opinion. If you can't verify it with a command that answers yes or no, your loop will never stop at the right time.
- No ceiling. The SDK exposes
max_turnsandmax_budget_usd, and when the limit is reached, the result comes back with an explicit subtype (error_max_turns,error_max_budget_usd) instead of going into a silent spiral. Use this, and always start with a low ceiling. - The same agent does and checks. In the same context, it always reviews itself kindly. A sub-agent starts with a fresh context, which provides a true outside perspective, and it costs less since only its conclusion is sent back.
- Two loops in the same folder. They will overwrite each other's files. Git worktrees solve this for almost nothing.
- No switch. Always plan for the witness file that you can create manually to stop everything without killing the process. You will need it sooner than you think.
- Run everything on the largest model. A loop running every five minutes means 288 executions per day. The choice of model and effort level is the first cost lever, far ahead of the rest.
On cost, specifically: a poorly bounded loop is the only known way to spend a monthly budget during lunch. Start with a narrow goal, a ceiling of five turns, and see what it consumes before opening the floodgates.
Running this without you
Once the loop is closed, it can be connected to something other than your keyboard. The composition recommended by Anthropic simply stacks a clock and a goal:
/schedule every hour: check #project-feedback for bug reports.
/goal: don't stop until every report found this run is triaged,
actioned, and responded to.The first decides when, the second decides how far. This is exactly what Cherny describes when he talks about hundreds of instances reading GitHub issues, Slack feedback, and X threads to come up with the next thing to do. No one at the keyboard, yet work is progressing. But don't go there before you've run levels 1 to 3 for a few days, under your eyes.
What it changes concretely
The shift from prompt to loop is not a vocabulary trend. Writing a prompt is asking for a result. Writing a loop is describing what a good result is, and letting the machine bang against it until it gets there. The work shifts: less formulation, more acceptance criteria, automatable checks, and safeguards.
Personally, the part I find most underestimated is the line VERDICT: PASS. Three words imposed on a sub-agent, and a prose report becomes a stopping condition exploitable by a ten-line shell script. That’s what loop engineering really is: making measurable what wasn’t before. The rest is plumbing.
Sources
- Anthropic: How the agent loop works
- Anthropic: Loop engineering, getting started with loops
- Claude Code: command reference (/loop, /goal, /code-review)
- Claude Code: hooks, including Stop and SubagentStop
- Claude Code: sub-agents
- Andrej Karpathy: autoresearch
- From Prompts to Loops: a practical guide to building agentic workflows
- Memeburn: Boris Cherny says loop engineering has replaced prompting


No comments yet.