Trail content
8 minEnglish narration, Portuguese subtitlesPodcast (RSS)
Chapters
  1. 0:00Opening
  2. 0:54The diagnosis
  3. 2:08Why it happens
  4. 2:50What it costs
  5. 3:49A challenge
  6. 4:27The treatment
  7. 6:21Close relatives
  8. 7:00When to ignore
  9. 7:17Wrap-up
Transcript

Opening

A browser agent downloads bills from a supplier's portal. Every night it logs in, walks through the pages, and saves each invoice.

One night, the portal changed its next page button. It was still on the screen, but it didn't load anything.

Here's the next morning's trace. Step four hundred twelve, click next page. Okay. Read the page. Invoices one to twenty, of three hundred forty. Click again. Okay. Read again. Invoices one to twenty.

All the way to step two thousand three hundred eighteen.

Nobody wrote a bug into that loop. It simply had no end, other than when the model thinks it's done. And the model never thought so. In this lesson, you'll learn to spot this smell before the bill does, and what a real end looks like.

The diagnosis

Look at two details in that trace.

The click answers okay, even when nothing changes.

And reading the page always shows the same first twenty invoices. The evidence that the agent was stuck was right there. But no code was looking at it.

Here's the shape of the smell. The model requests an action that looks sensible.

The tool responds without an error, even with no real progress.

Every turn resends the whole history, which grows with each step.

The loop's only exit is the model deciding to stop.

And when it doesn't decide, the provider's invoice is what stops the run.

How do you spot it? The agent's code says while true, or while not done, with no step counter.

There's no time, token or money limit per run.

Nobody can say what happens if the model never gives a final answer. And the month's most expensive run cost dozens of times the median.

The trace shows the same tool, with the same arguments, several times in a row.

And the only alarm that goes off is the credit card's.

Why it happens

The prototype was born this way. The simplest agent examples use while true, and the loop shipped unchanged.

It works in testing. On demo tasks, the model finishes in five steps. Nobody tested the portal with a broken button.

Tools that lie by omission. Okay, as the result of a click, doesn't say whether anything changed. The model has no way to notice it's stuck.

And the provider looks like a cap. People assume the context window will eventually stop the agent. But many frameworks trim old history to make it fit. And then the loop runs forever.

What it costs

Now the cost, with illustrative numbers. Say the system prompt and the tool descriptions are three thousand tokens, and each turn adds about four hundred. A normal run of ten turns sends about fifty-two thousand tokens in total. A stuck run of two hundred turns sends about eight point six million.

Twenty times more turns, and over one hundred sixty times more tokens. Cost grows with the square of the step count, because every turn pays again for everything before it. A single stuck run can cost more than a whole day of normal use.

And money is the smaller part. The loop eats the account's request quota, and takes down other runs sharing the same key.

If the stuck tool has a side effect, like sending, creating or paying, every turn repeats it.

And a trace with thousands of steps is close to unreadable.

A challenge

Here's one for you. An agent runs in while true. But its tool has a rate limit of ten calls per minute, so it never spends much per minute. Is this an unbounded loop?

Yes. A rate limit controls speed, not the total. A loop stuck at ten calls a minute still makes over fourteen thousand calls a day. It only spreads the cost over time. And the context window isn't a reliable brake either. Many frameworks drop old messages to fit, and the loop keeps paying for a full window, on every turn.

The treatment

The treatment is a refactoring called add loop budget. And the budget has four parts. The last one is the one people forget.

A step cap. Pick it from your traces. If ninety-nine percent of runs finish within fifteen steps, a cap of forty leaves room, without allowing a whole night.

Time and cost caps. Steps can be cheap or expensive. A money limit per run catches the case where a few steps send huge contexts.

A repetition detector. The same tool, with the same arguments, three times in a row, means the agent is stuck. Stop before the cap.

And behavior at the cap. Return what was done, explain where it stopped, and record the case. An agent that stops with a silent exception, and makes the task disappear, still leaves the user with no answer. The cap exists, but the treatment isn't finished.

Most agent frameworks already give you the first piece, as a maximum number of turns, or a recursion limit. You write the rest. And along with the budget, review your tools. A result that says what changed, like page two of seventeen loaded, helps the model notice when it's stuck. The pattern you're heading for is called stop conditions, and it's two lessons ahead.

And a week after you set the cap, say two percent of runs end there. Don't raise the cap to four hundred. Without reading the traces, you'd only let stuck runs spend ten times more before stopping. And don't ignore them either. Read those traces, find the cause, like a vague tool result or a page that changed, and turn each one into an eval case. They're the best source of hard cases you have.

Close relatives

Two cases to sharpen the eye. A job walks three hundred invoices, calling the model once per invoice, with no step counter. Is that this smell? No. The list is finite, and the code decides the next turn, not the model. The loop ends at invoice three hundred, either way.

And an agent with a thirty step limit, where each step can spawn subagents, which also have thirty steps, and can also spawn subagents. Each level has a cap. The total doesn't. That's a related smell, subagent recursion. The budget has to cover the whole tree, not each node.

When to ignore

When can you relax? A coding assistant in your terminal, with you watching and able to interrupt at any time, tolerates a high cap. Set one anyway. And no production agent, running without a person watching, should ever run without a cap.

Wrap-up

So here's the idea to take with you. The model can say it's done, but it can't be the only brake. Steps, time and money belong to your code, along with a repetition check and a clear answer when the cap is hit. Then the most expensive run of the month is a number you chose.

In the next lesson, we'll build that budget step by step: add loop budget. See you there.

SmellControl-flow abusers

Unbounded Loop

Loop Sem Fim

Unbounded Loop is an AI code smell, a sign of trouble in LLM systems, in the Control-flow abusers group. The agent can call tools forever; the only limit is the credit card.

Element
09/37
Level
Intermediate
Read
4 min
Updated
On this page 7
  1. 01Signs and symptoms
  2. 02Reasons
  3. 03Cost of ignoring
  4. 04Treatment
  5. 05Payoff
  6. 06When to ignore
  7. 07Is it this smell?
  8. ··Quick answers

Signs and symptoms

A browser agent downloads bills from a supplier portal. One night the portal changed its “Next page” button: it was still on screen, but it loaded nothing. The next morning’s trace (anonymized excerpt):

step 412  tool=click        args={"selector": "a.next-page"}   result="ok"
step 413  tool=read_page    args={}                             result="Invoices 1 to 20 of 340 ..."
step 414  tool=click        args={"selector": "a.next-page"}   result="ok"
step 415  tool=read_page    args={}                             result="Invoices 1 to 20 of 340 ..."
...
step 2318 tool=click        args={"selector": "a.next-page"}   result="ok"

Nobody wrote a bug into the loop. The loop simply had no end other than “when the model thinks it is done”. The model never thought so.

Look at two details in the trace. The click answers “ok” even when nothing changes. And reading the page always shows “Invoices 1 to 20”. The evidence that the agent was stuck was there, but no code looked at it.

Signs of an Unbounded Loop:

  • The agent code has while True or while not done with no step counter.
  • There is no time, token, or money limit per run.
  • Nobody can say what happens if the model never gives a final answer.
  • The month’s most expensive run cost dozens of times the median.
  • The trace shows the same tool, with the same arguments, several times in a row.
  • The only alarm that goes off is the credit card’s.
flowchart TD
  M(["1. Model picks a tool"]):::llm --> T["2. Tool answers ok"]:::tool
  T --> O[("3. History grows")]:::store
  O --> D{"4. Does the model think it is done?"}
  D -->|no| M
  D -->|never| X(["5. Provider invoice"]):::io
  1. The model requests an action that looks sensible.
  2. The tool responds without error, even with no real progress.
  3. Every turn resends the whole history, which grows each step.
  4. The loop’s only exit is the model deciding to stop.
  5. When it does not decide, the bill is what stops the run.
Check +20 XP on first tryYour support agent calls the find_order tool with the same number 30 times in a row, and the loop is while not done. Which design flaw lets this go on?

Reasons

  • The prototype was born this way. The simplest agent examples use while True, and the loop shipped unchanged.
  • It works in testing. On demo tasks the model finishes in five steps. Nobody tested the portal with a broken button.
  • Tools that lie by omission. “ok” as the result of a click does not say whether anything changed. The model has no way to notice it is stuck.
  • Fear of cutting it short. A limit feels risky: “what if the task needs more steps?”. With no answer to that, nobody sets a limit.
  • The provider looks like a cap. Many people assume the context window or the request limit will stop the agent. Many frameworks trim old history to fit the window, and then the loop runs forever.

Cost of ignoring

  • Money. Each step resends the history, so turn 2,000 costs far more than turn 10. A single stuck run can cost more than a whole day of normal use.
  • Quota. The loop eats the account’s request limit and takes down other runs sharing the same key.
  • Repeated effects. If the stuck tool has a side effect (send, create, pay), every turn repeats it.
  • Diagnosis. A trace with thousands of steps is close to unreadable.

A worked example of the curve

The numbers below are illustrative. Suppose a system prompt plus tool descriptions of 3,000 tokens. Each turn adds a call and a result, about 400 tokens. On turn n, the agent sends roughly 3,000 + 400 × n tokens.

Run Turns Total tokens sent
Normal 10 about 52 thousand
Stuck 200 about 8.6 million

Twenty times more turns cost over 160 times more tokens. Cost grows with the square of the step count, because every turn pays again for everything before it.

Around turn 500, each call is already past 200 thousand tokens. If the framework starts dropping old messages to fit, the cost per turn stays high and flat, and nothing forces an end anymore. Prompt caching makes the repeated prefix cheaper, but it does not create an end.

Treatment

Refactoring Choose it when
Add Loop Budget Always. A cap on steps, time, and cost, with defined behavior for when the cap is hit.

The budget has four parts, and the last one is the one people forget:

  1. Step cap. Pick it from your traces: if 99% of runs finish within 15 steps, a cap of 40 leaves room without allowing a whole night.
  2. Time and cost caps. Steps can be cheap or expensive. A money limit per run catches the case where a few steps send huge contexts.
  3. Repetition detector. The same tool with the same arguments three times in a row means the agent is stuck. Stop before the cap.
  4. Behavior at the cap. Return what was done, explain where it stopped, and record the case. Stopping with a silent exception leaves the user with no answer.

Frameworks already provide the first piece: the OpenAI Agents SDK has max_turns, and LangGraph has recursion_limit. Anthropic’s “Building effective agents” guide (Dec. 2024) also recommends stopping conditions such as a maximum number of iterations. You write the rest.

Along with the budget, review the tools. A result that says what changed (“page 2 of 17 loaded”) helps the model notice it is stuck. The target pattern is Stop Conditions, which also defines when the agent gives up or asks for help.

Check +20 XP on first tryYou set a 40-step cap and a repetition detector. A week later, 2% of runs end at the cap. What is the most useful next step?

Payoff

  • Every run ends, in a known state: success, partial answer, giving up, or asking for help.
  • The maximum cost of a task is a number you chose.
  • Runs that hit the cap become eval cases instead of invoice surprises.
  • Stuck tools get detected in minutes, not at the end of the month.

When to ignore

  • A loop under constant supervision. A coding assistant in the terminal, with a person watching who can interrupt at any time, tolerates a high cap. Set one anyway.
  • Tasks whose end is guaranteed by code. If the loop walks a finite list in code and the model only processes each item, the limit already exists. Make sure code really controls the end.

No production agent without a person watching should run without a cap.

Is it this smell?

Check +20 XP on first tryThe agent has a 50-step limit. When it hits the limit, the code throws an exception and the task disappears, with no record and no notice to the user. Is this an Unbounded Loop?

Check +20 XP on first tryThe agent has a 30-step limit, but each step can spawn subagents, which also have a 30-step limit and can also spawn subagents. What is the diagnosis?

Check +20 XP on first tryAn agent runs in while True, but its tool has a rate limit of 10 calls per minute, so it never spends much per minute. Is this an Unbounded Loop?

Check +20 XP on first tryA job walks 300 invoices with for invoice in invoices: and calls the model once per invoice to extract the total. There is no step counter. Is this an Unbounded Loop?

Quick answers

What is Unbounded Loop?

Unbounded Loop is an AI code smell, a sign of trouble in LLM systems, in the Control-flow abusers group. The agent can call tools forever; the only limit is the credit card.

How do you fix Unbounded Loop?

With Add Loop Budget. Each refactoring has safe step-by-step mechanics with an eval before and after.

Where does Unbounded Loop show up in practice?

In the real-world scenarios Coding agent, Legacy code migration, Browser agent for repetitive tasks, and Back-office automation with durable execution.

Go further

Relations

Refactorings that treat it 1
Part of the tracks 1
Search