Agent Step Limits and Stop Conditions by kirupa (https://www.kirupa.com/me/index.htm) | filed under AI Fundamentals (https://www.kirupa.com/ai_fundamentals/index.htm) An agent is a loop. The model picks a step, our code runs it, the model looks at the result, and then it picks the next step. It keeps going until the model decides it's done. (If that loop is new to you, Workflows vs. Agents (https://www.kirupa.com/ai_fundamentals/workflows_vs_agents.htm) builds it up from scratch.) The tricky part is that last bit: until the model decides it's done. What if it never decides that? What if the thing it's looking for doesn't exist, and it keeps looking anyway? This happens more often than you'd think, and every agent needs a plan for it. In the following sections, we'll watch an agent get stuck, see why getting stuck gets more expensive with every step, and then add the stop conditions that get it unstuck. Onwards! Meet Scout Scout is a travel assistant agent. It has one tool, search_flights, and a user just asked it to find the cheapest direct flight from Seattle to Tokyo next Friday. Here is Scout's loop, exactly as we built it: [Image: Scout's loop. Scout decides, calls search_flights, adds the result to its notes, reads the notes, and decides again. The only exit is Scout choosing to give a final answer, and a handwritten note calls it the only way out.] (https://www.kirupa.com/ai_fundamentals/images/agent_step_limits_one_exit.png) Notice there is only one way out of this loop. Scout has to decide it's done. Here is what the user sees right after asking Scout for that flight: [Image: Scout sits in a chat panel in the corner of a trip-planning app. The panel header shows a robot avatar with an airplane badge, the name Scout, and the tagline Your travel assistant. A traveler asks, Find the cheapest direct flight from Seattle to Tokyo next Friday. Scout replies, On it! Searching flights now... A handwritten note says This is Scout!] (https://www.kirupa.com/ai_fundamentals/images/agent_step_limits_meet_scout.png) Scout Gets Stuck Let's say that on this particular Friday, there's no direct flight from Seattle to Tokyo. Here is what Scout's run could look like: [Image: A trace of five searches, each returning zero direct flights: Seattle to Narita, to Haneda, to the Tokyo city code, to Narita with a nonstop filter, and then Seattle to Narita again. The first and fifth searches are highlighted as identical, with a handwritten note: same search again.] (https://www.kirupa.com/ai_fundamentals/images/agent_step_limits_trace.png) Each step looks reasonable on its own. Maybe Tokyo's other airport has one? Maybe the city code works better? Maybe a nonstop filter helps? By step five, Scout is repeating its very first search, and nothing in our loop will ever stop it. From the user's side, this looks like a spinner that never goes away. Meanwhile, every step is costing us money. Why Each Step Costs More Than the Last Stuck agents are extra painful because of how the loop talks to the model. Every time Scout takes a step, we send the model the whole history so far: the user's question, every previous tool call (https://www.kirupa.com/ai_fundamentals/tool_calling.htm), and every result. The model has no memory between calls, so the history is how it knows what already happened. That means the history grows with every step, and so does the size of each call: [Image: A bar chart of input tokens per call over ten steps. Every bar has the same question and instructions at the bottom, a band of history that grows with each step, and a small cap for the new step, so each call is bigger than the one before.] (https://www.kirupa.com/ai_fundamentals/images/agent_step_limits_growing_calls.png) So ten steps don't cost ten times as much as one step. They cost quite a bit more than that, because the later steps carry everything that came before. Tokens and How They Set Your Bill (https://www.kirupa.com/ai_fundamentals/tokens_and_your_bill.htm) walks through the same effect in a growing chat. If the loop runs long enough, the history can even outgrow the most the model can read in one request (its context window (https://www.kirupa.com/ai_fundamentals/context_window_limits.htm)), and then the run fails in a different, more confusing way. Adding More Exits The fix is to give the loop more ways out. A stop condition is any rule, checked by our code, that ends the run. Here is Scout's loop with a set of them added on the way back from the notes: [Image: The same Scout loop, but between the notes and Scout, our code runs four checks: max steps, budget, time limit, and repeats. Any of them can send the run to stop and explain instead of back to Scout.] (https://www.kirupa.com/ai_fundamentals/images/agent_step_limits_with_checks.png) The done exit is the one we already had. The model gives a final answer, and we return it. The new exits are all checks in our code. A step limit caps how many times the loop can run. Most agent frameworks have one built in. OpenAI's Agents SDK, for example, takes a max_turns setting and raises a MaxTurnsExceeded error when a run hits it. If ours doesn't, it's one counter in our loop. A budget caps tokens or dollars for the whole run. A step limit alone isn't quite enough, because, as we just saw, eight late steps can cost far more than eight early ones. A time limit caps wall-clock time. If a user is waiting on the other end, we probably don't want to keep them waiting for many minutes. A repeat detector looks for loops that aren't making progress. The simplest version checks whether the model is about to make a tool call with the exact same arguments it already used. Scout's step five would have tripped that one immediately. A slightly fancier version notices the same error coming back several times in a row. All of these checks live in our code, and that detail matters a lot. Telling the model don't take more than 8 steps in its instructions is only a hint. The model can lose track of it, or decide it doesn't apply this time. A counter in our loop stops the run every time. Here is the difference side by side: [Image: Two ways to cap Scout's steps. On the left, an instruction in the prompt says don't take more than 8 steps. It's only a hint, and the model can lose track of it or decide it doesn't apply this time. On the right, a counter in our loop is checked by our code on every step and stops the run every time.] (https://www.kirupa.com/ai_fundamentals/images/agent_step_limits_hint_vs_counter.png) Let the Model Raise Its Hand There's one more exit worth adding, and it's a friendlier one. We can give the agent a tool that means I can't do this: { name: "give_up", description: "Call this if the task can't be completed. Explain what you tried and what you found.", parameters: { reason: "string" } } With that in its toolbox, Scout has a respectable way to say there are no direct flights that day and offer the closest options. Without it, a model that's been told to find a direct flight may keep trying to find one. A similar tool, ask_user, lets the agent stop and ask a question instead of guessing. Stopping Gracefully When one of our limits trips, we shouldn't just throw an error and show the user a blank screen. Scout did learn something during its searches. A good stop hands that back, something like this: I couldn't find a direct flight from Seattle to Tokyo next Friday. I checked both Narita and Haneda. Want me to look at Thursday, or at flights with one stop? Here is that reply in Scout's chat window: [Image: Scout's chat window in a trip-planning app. A traveler asks, Find the cheapest direct flight from Seattle to Tokyo next Friday. Scout replies, I couldn't find a direct flight from Seattle to Tokyo next Friday. I checked both Narita and Haneda. Want me to look at Thursday, or at flights with one stop?] (https://www.kirupa.com/ai_fundamentals/images/agent_step_limits_chat_graceful.png) A graceful stop tells the user what happened in plain words and hands back whatever partial results we have. Then it offers a next step, whether that's asking the user a question, trying again with a change, or handing the task to a human. For long-running agents, it can also save the state so the run can pick up where it left off, instead of starting over from step one. Picking the Numbers So what should the step limit actually be? There's no universal number, and anyone who gives you one without seeing your agent is guessing. What works is looking at real runs. Collect traces (step-by-step records of real runs, like the list of Scout's five searches we looked at earlier) of the agent doing its job well, and look at how many steps and tokens the successful runs actually used. Set the limits a comfortable amount above that. If successful runs almost always finish in 4 to 6 steps, a limit around 10 or 12 leaves room for harder tasks while still catching a Scout that's spinning in circles. Then watch which limit trips most often. If the step limit fires constantly, the agent might be missing a tool, or its instructions might be unclear. Either way, it's a sign to go read those traces. Conclusion An agent loop with one exit trusts the model to always know when to stop, and Scout showed us how that goes. Searching for a flight that doesn't exist, it kept trying small variations, and every attempt was bigger than the last because the whole history rode along each time. Adding stop conditions turns that open loop into a bounded one. Step limits, budgets, time limits, and a repeat detector all live in our code, where the model can't talk its way past them. A give_up tool gives the model a polite way out. And when a limit does trip, a graceful stop turns something went wrong into here's what I found, and here's what we can try next.