AI Engineer Dojo Contents
Chapter 3

The Agent Loop & Stopping

The loop is easy to write and easy to get badly wrong. Agents fail in two opposite ways: they stop too early with half an answer, or they never stop and burn through your budget. Designing when to stop is most of designing the agent.

Case study

Vectorly's loop wouldn't quit

Vectorly's research agent sometimes ran more than 40 steps without finishing: running the same search again, rereading the same page, going in circles. One run lasted twenty minutes before someone killed it. The agent had no clear definition of done and couldn't tell it was repeating itself.

They added a hard budget on steps and cost so no run could go unbounded. Then they defined "done" explicitly, made tool results easy for the model to read, and detected repeated no-progress states so the agent changed approach. Average steps fell from 14 to 6, and runs that never ended disappeared. Raising the step limit, which they'd tried first, had only made the expensive runs more expensive.

The model signals it's finished by replying with text and no tool calls. That's the natural end, but relying on it alone is how agents run away, because a confused model will keep calling tools. A real loop has three ways to stop, in this order:

Natural stop: the model gave a final answer. Stuck detection: the loop isn't making progress, so stop early. Step limit: a hard max_steps cap that guarantees the loop ends.

A runaway, in numbers

Give an agent with no cap a task it can't complete, such as a tool that always errors, and it won't give up gracefully. It retries, rephrases, tries other tools and keeps going. In one test, an uncapped agent on an impossible task ran more than 50 steps before being stopped by hand, spending about $4 on a request that should have failed in 3 steps for a fraction of a cent. At real traffic volumes, a missing cap is both an outage and an invoice. Every production loop needs one.

Stuck detection

The cap guarantees an end, but you still pay for every step before it. It's better to notice the agent spinning and stop sooner. The simplest signal is a repeated action: the same tool with the same arguments it already tried. If it has just called get_weather("Paris") a second time, it's looping. Tell it so ("You already tried that and got X; try something different or stop"), and if that doesn't help, end the run. In practice this catches most runaway runs well before the cap.

Log why each run ended

Record whether each run stopped naturally, got stuck or hit the cap. That mix is a health metric: a rising share of capped runs means the agent is failing without throwing any errors.

Decide what a failed run returns

On a natural stop, return the model's answer. On a stuck or capped run, returning the model's last confused message is a poor experience. Define a proper result instead: "I couldn't finish this. Here's what I found and where I got stuck." An agent that fails honestly is far more useful than one that guesses confidently after eight wasted steps.

Try it · ~25 minFree · no API key

Add a step limit and stuck detection

Point a loop at a task it can't solve (a tool that always errors) and add both guards. Measure how many steps the plain loop wastes compared with the guarded one, and confirm both end. The stand-in model keeps retrying, which is the behavior you're defending against.

Starter code

# A stand-in "model" that stubbornly retries the same failing tool.
def fake_model(messages):
    # Always wants to call broken_tool with the same args => a loop.
    return {"tool":"broken_tool", "args":{"x":1}}

def broken_tool(args):
    return "ERROR: service unavailable"

def loop(max_steps=20, detect_stuck=False):
    messages, seen, steps = [], set(), 0
    for steps in range(1, max_steps+1):
        act = fake_model(messages)
        if act is None:
            return steps, "natural"
        key = (act["tool"], tuple(sorted(act["args"].items())))
        if detect_stuck and key in seen:
            # nudge once already given? here we just stop on 2nd repeat
            return steps, "stuck"
        seen.add(key)
        result = broken_tool(act["args"])
        messages.append(result)
    return steps, "capped"

# TODO: run loop() both ways and print steps + reason for each.
Worked solution
print(loop(max_steps=20, detect_stuck=False))   # (20, 'capped')
print(loop(max_steps=20, detect_stuck=True))    # (2,  'stuck')

The plain loop runs all 20 steps and stops at the cap, paying for every call against a tool that will never work. The guarded loop stops at step 2, as soon as the same action repeats: 90% fewer wasted steps, with the cap still there as a backstop. The cap protects you from disaster and stuck detection protects you from waste, so use both.

To go further, make the stand-in model alternate between two failing tools. Exact-repeat detection misses that, which shows you need a broader "no progress" check, such as several steps with no new information.

Running case · Ridgeline

From day one, Ridgeline caps every ticket at a step and cost budget and defines "done" explicitly: the ticket is resolved or escalated. When the agent starts fetching the same order again, a no-progress check nudges it to escalate.

If you're starting out

"Our agent sometimes runs forever. What would you do?" is one of the most common agent interview questions. Give the three layers (natural stop, stuck detection, hard cap), add a per-request cost budget, and mention logging the stop reason. That's a complete senior-level answer, and it's all in this chapter.

If you run the team

Ask two things before any agent goes live: what's the maximum number of steps a single request can take, and what's the most one request can cost? If the answer to either is "there isn't one," the launch should wait. A single runaway can cost more than a day of normal traffic.

Also ask to see how runs end: what share finish naturally, get stuck or hit the limit. It's a quick health check you can ask for every week without reading any transcripts.

Quiz · Chapter 3

  1. How does the model normally signal that it's finished?
  2. Why is a hard max_steps cap required in production?
  3. Why add stuck detection when you already have a cap?
  4. What should a capped or stuck run return?
  5. Why log each run's stop reason?
Back Continue

The AI Agent Engineer · aiengineerdojo.com