The Agent Loop: How an LLM Plans, Acts and Knows When to Stop
An agent is a model inside a loop: it reads the goal and the history, picks the next tool, sees the result, and repeats. Loop engineering is designing that loop on purpose, including how it stops.
A swim before ten
Alex wants to swim on Saturday morning and has one rule: it must be before 10:00, because the café next door stops serving breakfast then. The local pool has a booking assistant, so Alex types: "Book me a lane swim on Saturday before ten."
A model on its own can't do this. It can write a friendly reply, but it doesn't know which lanes are free, and it can't press the booking button. To help Alex it has to look at the timetable, act by booking a slot, check whether the booking worked, and only then answer. That's several steps, and each one depends on what the last one found.
Here is the timetable at the moment Alex asks. Keep it in mind: every example in this lesson uses it.
| Slot | Time | Lanes free | Before 10:00? |
|---|---|---|---|
sat-0700 | 07:00 | 0 | yes |
sat-0800 | 08:00 | 1 | yes |
sat-0900 | 09:00 | 3 | yes |
sat-1100 | 11:00 | 4 | no |
sat-0700- Time
- 07:00
- Lanes free
- 0
- Before 10:00?
- yes
sat-0800- Time
- 08:00
- Lanes free
- 1
- Before 10:00?
- yes
sat-0900- Time
- 09:00
- Lanes free
- 3
- Before 10:00?
- yes
sat-1100- Time
- 11:00
- Lanes free
- 4
- Before 10:00?
- no
Doing a task in steps, where each step depends on what the last one found, is what people mean by an agent. This is the first lesson of the Loop Engineering module, and loop engineering is the job of designing that repeat-until-done cycle on purpose: what the model sees, what it may do, how results are checked, and when it must stop.
The loop: read, decide, act, observe
Every agent, whatever library it's built with, runs the same cycle. Your code holds a history: the goal plus everything that has happened so far. On each turn of the loop:
- Read. Your code sends the goal and the whole history to the model.
- Decide. The model replies with one of two things: a final answer, or a tool call, a structured request such as
book_slotwith the argumentsat-0900. - Act. Your code, not the model, runs that tool. The model only asks.
- Observe. The tool's result, called an observation, is added to the history, and the loop goes round again.
- Alex's request (goal + history) to Assistant thinks (model)
- Assistant thinks (model) to Use a tool (tool call)
- Assistant thinks (model) to Reply to Alex (final answer)
- Use a tool (tool call) to Booking system (tool runs in your code)
- Booking system (tool runs in your code) to What happened (observation)
Model APIs from the main providers work this way. You describe each tool with a name, a short description and the arguments it takes, and the model's reply either is text or names a tool to call with arguments. A reply can carry some text too, and many APIs let it ask for several tools at once; this lesson runs one call per turn to keep the loop easy to follow. In a loop, a reply with no tool call is the final answer. The idea was popularised by the 2022 ReAct paper by Yao and colleagues, which interleaved the model's reasoning with actions and the observations they produced, instead of asking for an answer in one go.
One run, step by step
Here is Alex's request going round the loop. Just after the assistant reads the timetable, Bob books the last 08:00 lane from the pool's front desk.
| Step | Model decides | Observation | Why |
|---|---|---|---|
| 1 | list_slots(sat) | 07:00 full, 08:00 has 1, 09:00 has 3, 11:00 has 4 | It can't book what it hasn't seen |
| 2 | book_slot(sat-0800) | error: sat-0800 is full | Earliest free slot before 10:00 |
| 3 | book_slot(sat-0900) | booked sat-0900 | The error is in the history, so it tries the next slot |
| 4 | final answer | none | Booked, so there is nothing left to do |
1
- Model decides
list_slots(sat)- Observation
- 07:00 full, 08:00 has 1, 09:00 has 3, 11:00 has 4
- Why
- It can't book what it hasn't seen
2
- Model decides
book_slot(sat-0800)- Observation
- error:
sat-0800 is full - Why
- Earliest free slot before 10:00
3
- Model decides
book_slot(sat-0900)- Observation
booked sat-0900- Why
- The error is in the history, so it tries the next slot
4
- Model decides
- final answer
- Observation
- none
- Why
- Booked, so there is nothing left to do
Step 2 is the important one. The plan was reasonable when the model made it, and the world changed anyway. Because the failure came back as an observation instead of crashing the program, the model could read it and adapt. A one-shot reply would have told Alex "you're booked at 08:00", and Alex would have turned up to a full pool.
Notice also that nobody wrote "if 08:00 is full, try 09:00". The model chose the next step from the history. That flexibility is the reason to use a loop, and it's also the risk: the same freedom lets it choose badly.
Knowing when to stop
The normal way out of the loop is the model's final answer. But you can't rely on it alone, because a loop that only stops when the model decides to stop has no stop at all when the model gets stuck.
Bob builds his own version of the assistant to try it out, and his first test hits a bug: the model calls list_slots(sat), reads the result, and calls list_slots(sat) again, and again. Nothing in his code ends it. Every turn is another paid model call, and Bob only notices when the bill arrives. A loop without guards is an easy first bug to write, so the guards go in before the first test.
| Guard | Limit in this example | What it catches |
|---|---|---|
| Final answer | the model says it's done | The normal, happy ending |
| Step limit | 6 turns | A model that wanders and never finishes |
| Repeated call | same tool and arguments 3 times | A model stuck asking the same thing |
| Budget | assume 20,000 tokens or 60 seconds | A long run that costs too much or keeps Alex waiting |
Final answer
- Limit in this example
- the model says it's done
- What it catches
- The normal, happy ending
Step limit
- Limit in this example
- 6 turns
- What it catches
- A model that wanders and never finishes
Repeated call
- Limit in this example
- same tool and arguments 3 times
- What it catches
- A model stuck asking the same thing
Budget
- Limit in this example
- assume 20,000 tokens or 60 seconds
- What it catches
- A long run that costs too much or keeps Alex waiting
In code, the guards are a few lines around the loop. The step limit is simply the loop's range, and the repeat check counts each tool-and-arguments pair. Run on Bob's model, this stops on step 3; run on Alex's request, it returns the answer on step 4, as in the table above.
def run(goal, model, max_steps=6):
history = [{"goal": goal}]
counts = {}
for step in range(max_steps): # guard: step limit
move = model(history)
if "answer" in move: # normal stop: final answer
return move["answer"]
key = (move["tool"], str(move["args"]))
counts[key] = counts.get(key, 0) + 1
if counts[key] == 3: # guard: same call 3 times
return "stopped: same call 3 times"
result = run_tool(move["tool"], move["args"]) # validates, catches errors
history.append({"call": move, "result": result})
return "stopped: step limit"
model is the model call and run_tool runs one tool safely; both are left out because the loop is the point.When a guard fires, end the run on purpose: tell the user plainly that the booking didn't finish, and log the history so you can see why. Never let a guard look like success.
Checking what comes back
Two checks sit between the model and the tools, and both live in your code.
- Validate the arguments before running the tool. If the model asks for
book_slot(sat-8am), there is no such slot. Your code rejects it with an observation likeunknown slot 'sat-8am'and the model can correct itself on the next turn. Never pass unchecked arguments to something that changes the real world. - Turn tool errors into observations. When the booking system says a slot is full, or a network call times out, catch the error and put its message in the history. Some APIs, such as Anthropic's, also let you flag a tool result as an error; others just take the error text as the result. A crash ends the run; an observation gives the model a chance to recover.
Expert corner: where loops break
- Cost grows faster than steps. Each turn sends the whole history again (or the provider re-reads it for you), so turn ten pays again for everything in turns one to nine. Keep observations small:
list_slotsshould return only the free slots, not the pool's full year. - Retries can repeat side effects. If
book_slottimes out after the booking went through, a retry books Alex twice. Give actions a unique request key so a repeat is ignored, and ask the user to confirm anything that can't be undone, such as a payment. - Step limits cut both ways. Too low and good runs fail; too high and stuck runs burn money. Look at how many turns real runs take and set the limit a little above that.
- Not every task needs a loop. If the steps are always the same, list then book, write them as plain code. Use a loop when the next step depends on what the last one found.
In a system design interview, "design an agent that books appointments" is really a question about this loop. Draw it, then name the stop conditions, where tool errors go, which actions need confirmation, and what you log on every turn so a bad run can be replayed.
Recap
An agent reads the goal and history, decides on an answer or a tool call, lets your code run the tool, and adds the result to the history before going round again. Alex got a 09:00 lane because the failed 08:00 booking came back as an observation, and Bob's stuck loop ended because a guard was waiting for it.
Summary
Key takeaways
- An agent loop repeats four steps: read the goal and history, decide, run a tool, add the result to the history.
- The model only asks for a tool call; your code runs the tool and controls what is allowed.
- A final answer is the normal stop, but every loop also needs a step limit, a repeated-call check and a budget.
- Tool errors go back to the model as observations, so it can recover instead of the run crashing.
- Validate tool arguments in code before anything touches the real world.
- Loop engineering means designing this loop on purpose: inputs, tools, checks and stop conditions.
Practice
Check your understanding
0 of 5 answered
Finished this lesson?
Mark it done to track your progress. Saved in this browser only.
FAQ
Frequently asked questions
What is an agent loop in AI?
It's the cycle an LLM agent runs: your code sends the goal and history to the model, the model either answers or asks for a tool call, your code runs the tool, and the result is added to the history for the next turn. It repeats until the model answers or a guard stops it.
How does an AI agent know when to stop?
Normally the model returns a final answer instead of another tool call. Because a model can get stuck, the loop also needs hard limits in code: a maximum number of steps, a token or time budget, and a check for the same call repeating.
What should an agent do when a tool call fails?
Catch the error and add its message to the history as the tool's result and, where the API supports it, flag it as an error. The model reads it on the next turn and can try another approach, such as a different slot, instead of the program crashing.
Is the ReAct pattern the same as an agent loop?
ReAct, from a 2022 paper by Yao and colleagues, is one well-known form of it: the model alternates reasoning steps with actions and reads each action's observation. Today's tool-calling APIs run the same read, act, observe cycle with structured tool calls.


