The Agent Loop
ReAct and the think-act-observe cycle — how iteration works, why termination is the hardest part, and the limits every agent needs
The Agent Loop
TL;DR
The loop is what makes an agent an agent. Think → act → observe → repeat. Each pass, the model looks at everything that happened so far and decides the next move. The hard part is not starting the loop — it is stopping it. Termination is the most common source of agent bugs, and every agent needs hard limits as a safety net.
| Property | Value |
|---|---|
| Level | Beginner → Intermediate |
| Reading time | ~20 minutes |
| Prerequisites | Tools & Function Calling |
| You will understand | ReAct, iteration, termination, and the limits you must set |
The Cycle
The agent loop
Think
Given everything so far, what should I do next?
Act
Request a tool call — or decide to answer
Observe
Your code runs the tool and appends the result
Decide
Do I have enough to answer, or do I need another pass?
Watch it run on a real task:
The agent loop, step by step
The task
“What was our Q3 revenue in Europe, and how does it compare to last year?”
I need Q3 revenue for Europe, and the same figure for last year to compare.
The model is not answering. It is deciding what to do next. This is the whole difference between an agent and a chatbot.
Two things to notice. The model never runs a tool — it asks, and your code decides whether to comply. And the context only ever grows: every observation is added and nothing is removed, which is why long agent runs hit the context limit and why managing that is a real design problem.
ReAct — Reason + Act
ReAct is the name for interleaving reasoning with actions, rather than doing all the thinking up front. It is the default pattern for most agents.
The key idea: the model writes its reasoning out loud before each action. That has two effects.
| Effect | Why it matters |
|---|---|
| Better decisions | Reasoning in text before acting measurably improves the choice of action |
| A readable trace | You can see why it did something, not just what it did — which is most of your debugging ability |
Thought: I need this year's Europe figure first.
Action: get_revenue(region="Europe", quarter="Q3", year=2025)
Observation: { "revenue": 4820000 }
Thought: Now the same quarter last year, to compare.
Action: get_revenue(region="Europe", quarter="Q3", year=2024)
Observation: { "revenue": 4110000 }
Thought: I have both figures. No more tools needed.
Answer: €4.82M, up 17.3%.Notice that the second thought depends on the first observation. That dependency is the entire justification for a loop. If the second action were fixed regardless of the first result, you would write a two-step workflow and skip all of this.
Stopping: The Hard Part
An agent needs to know it is finished. This turns out to be genuinely difficult, and failures come in two opposite flavours.
Two ways termination goes wrong
Never stops
The agent keeps calling tools, re-checking, refining, or repeating the same call. It runs until your cap stops it, having burned the full budget. Usually because nothing told it what "done" looks like.
Stops too early
It answers after one lookup when it needed three, producing a confident, incomplete answer. Usually because the prompt rewarded answering over thoroughness, or because a failed tool call was read as "no data exists".
How to make stopping work
| Technique | What it does |
|---|---|
| Say what "done" looks like | "You are finished when you have both figures and have compared them." Concrete beats abstract. |
| Give an explicit finish action | A finish(answer) tool makes ending a deliberate choice rather than an absence |
| Detect repeats | The same tool with the same arguments twice means it is stuck — break out |
| Require a plan | An agent with a checklist can see what remains |
| Allow giving up | "If you cannot make progress, say so and hand to a human" — otherwise it invents progress |
Always set hard caps, even after you fix the real cause. Iterations, total tokens, and money. These are not the solution to a looping agent — they are the seatbelt for when your solution fails. An agent without caps in production is an unbounded bill waiting for the right input.
The limits every agent needs
| Limit | Typical | Why |
|---|---|---|
| Max iterations | 10–25 | Beyond this it is nearly always stuck, not working |
| Max tokens per task | Set from your budget | Catches context growth that iterations alone miss |
| Max cost per task | Set from your budget | The one your finance team cares about |
| Wall-clock timeout | 30–120 s | A hanging tool should not hang the task |
| Repeat detection | 2 identical calls | Cheapest loop-breaker there is |
What Actually Goes in Each Request
A common confusion: the model is stateless. It does not remember the previous iteration. Every request carries the whole history.
Request on iteration 3 contains:
[system prompt] Your standing instructions
[tool definitions] The full list, again
[user task] The original request
[thought 1] \
[action 1] |
[observation 1] | Everything from
[thought 2] | earlier iterations
[action 2] |
[observation 2] /
→ model produces thought 3This is why agent cost grows faster than linearly with steps. Iteration 10 re-sends everything from iterations 1–9. Ten steps is not ten times one step — it is closer to fifty, because each step carries all the ones before it.
That single fact drives most of agent engineering:
| Consequence | What you do about it |
|---|---|
| Cost climbs steeply with steps | Keep runs short; summarise observations |
| Context eventually fills | Compact old steps; keep a separate running state |
| Middle steps get skimmed | Keep the goal restated near the end |
| Long tool results are expensive | Summarise before appending, never dump raw |
Go deeper: Context Engineering
Loop Variations
| Variation | What changes | Use when |
|---|---|---|
| Plain ReAct | Think, act, observe, repeat | Short tasks, few steps |
| Plan first | Make a full plan, then execute it | Longer tasks that drift |
| Reflect after each step | Add "did that work?" between iterations | Quality matters more than cost |
| Replan on failure | Revise the plan when something surprises it | Unreliable tools or environments |
| Human checkpoint | Pause for approval at set points | Irreversible actions |
Every variation adds model calls, so every variation adds cost and latency. Start with plain ReAct, look at real traces, and add structure only where you can see it going wrong.
Common Mistakes
What goes wrong in the loop
No iteration cap
One bad input and the agent runs until your budget alerts fire. Non-negotiable in production.
No definition of 'done'
The most common cause of looping. The agent has no finish line, so it keeps refining.
Failed tool read as an answer
A tool returns an error or empty result, the agent takes it as fact, stops early, and reports it confidently.
No repeat detection
An agent calling the same tool with the same arguments three times is stuck. That is trivially detectable and rarely detected.
Dumping raw tool output into context
One large API response can consume most of your budget and push earlier reasoning out.
No trace logging
Without every thought, action, and observation recorded, you cannot reconstruct what happened — and agents are not reproducible, so you may not get a second chance.
Concept Checks
Check yourself
Why does a ten-step agent cost much more than ten times a one-step call?
Because the model is stateless and each request re-sends the entire history. Iteration 10 includes the system prompt, all tool definitions, and every thought, action, and observation from iterations 1 through 9. Input tokens therefore grow with each step, and you pay the accumulated total every time. The cost curve is closer to quadratic than linear, which is why keeping runs short matters more than it first appears.
Your agent hits the iteration cap on 15% of tasks. Is raising the cap the fix?
No — the cap is doing its job by revealing a problem. Hitting it means the agent could not decide it was finished, and giving it more iterations usually just costs more before failing the same way. Look at the traces: it is typically an unclear definition of done, a tool returning something the agent cannot act on, or the same call repeating. Fix the termination condition; keep the cap as a safety net.
Why does writing reasoning before each action help, beyond making traces readable?
Because the reasoning text becomes part of the context the next decision is made from. Committing to an explicit rationale before choosing an action measurably improves the choice, and it also gives later iterations something concrete to check against. The readable trace is a genuine second benefit — it is most of your debugging ability — but the decision-quality effect is the reason the pattern exists.
A tool fails and the agent confidently answers 'there is no data'. Where is the bug?
In the tool's error handling, not the agent. A bare empty result or an unstructured error string arrives in the context looking exactly like a legitimate observation, so the agent reasons from it as fact. The fix is to return something explicit and actionable — "query failed: region 'EU' not recognised, valid values are Europe, Americas, APAC" — which lets the agent correct itself instead of drawing a false conclusion.
Key Concepts Recap
| Concept | One-line summary |
|---|---|
| The loop | Think, act, observe, repeat |
| ReAct | Reasoning written out before each action |
| Dependency justifies the loop | If the next step is fixed, use a workflow |
| Stateless model | Every request carries the whole history |
| Cost grows steeply | Ten steps costs far more than ten single calls |
| Termination is the hard part | Both looping forever and stopping early are common |
| Say what "done" looks like | The best single fix for looping |
| Hard caps always | Iterations, tokens, cost, wall clock |
| Repeat detection | Identical calls mean stuck |
| Trace everything | Agents are not reproducible; the log is your only record |
Next
Loops that just react tend to wander on long tasks. The fix is planning: Planning & Reasoning.