All posts
Agentic AI

max_steps is a safety control, not a tuning knob

Our agent's loop runs until it has an answer or it hits a step limit.

while not context.final_result and context.current_step < self.max_steps:
    await self.step(context)

That max_steps reads like a performance setting, the kind of number you bump when the agent feels cramped. It is a safety control, and treating it as anything less is how a small fault becomes an expensive one.

An agent loop has no natural end

A normal function returns. An agent loop returns only when the model decides it is done, and the model does not always decide that.

A search keeps coming back unhelpful, so the model tries again, and again. A tool keeps returning an error the model reads as "retry". A task is genuinely too hard, so the model keeps reaching for one more tool. Each pass is another model call and another tool execution, and nothing in the loop stops on its own. The step limit is the only thing that does.

Remove the limit, or set it high enough to feel generous, and a stuck agent runs until something external breaks: a rate limit, a budget alert, or an invoice.

Every loop costs, and the costs are real actions

The reason this bites is that each pass round the loop is not free reasoning. It is a real model call over a context that grew since the last pass, and a real tool execution that does something.

We measured the model-call side earlier: an agent that loops ten times over a growing context pays for that context ten times. Now add the tool side. Every loop can hit an external API, run a search that costs money, send a request to a service that rate-limits, or trigger an action with a side effect. A runaway loop is not just a large bill. It is a large number of real calls to real systems, made by software that thinks it is making progress.

Every pass is a paid model call plus a real tool action. Without a ceiling, a stuck agent multiplies both.Every pass is a paid model call plus a real tool action. Without a ceiling, a stuck agent multiplies both.

It can be pushed, not just stuck

A loop that runs away on its own is a reliability problem. A loop that can be driven to run away is a security one.

Recall that a tool result is untrusted input, from earlier in this series. A tool that returns "not quite, try a broader search" on every call can keep an agent looping deliberately. The content that steers the loop comes from outside, and the agent reads it as guidance, exactly as it reads every other tool result. So the same buffer that can redirect an agent's goal can also just make it spin, and spinning has a cost attached.

The security name for this is resource overload: driving a system's compute, calls or spend until it degrades or fails, by exploiting how resource-intensive each step is.

A step counter is a floor, not the whole control

Capping the number of steps is necessary and not sufficient. Steps are a proxy for cost, and a loose one: ten cheap steps and ten expensive ones hit the same cap at very different bills.

ControlWhat it bounds
A real max_steps, set deliberatelyThe number of loop passes. The floor everything else builds on
A per-run cost or token budgetThe actual spend, which steps only approximate
A wall-clock timeoutA run that stalls rather than loops
Per-tool call limitsA single expensive tool called over and over
Alert when a run hits the capThe signal that something is wrong, instead of a silent truncation
Treat "hit the cap" as a failure, not an answerA truncated run returning a confident partial answer

A step count is the floor. Cost, time and per-tool budgets bound what the step count only approximates.A step count is the floor. Cost, time and per-tool budgets bound what the step count only approximates.

The last two rows carry the most weight. A run that hits its ceiling has failed, and it should say so loudly rather than return whatever it had and let the caller mistake a truncation for a conclusion. A cap reached in silence is a cap you will only notice on the invoice.

What this touches

For the frameworks, this sits on the MAESTRO Agent Frameworks and Deployment layers and maps to T4 Resource Overload, which the pack defines as targeting the compute, memory and service capacity of a system to degrade it, exploiting its resource-intensive nature. In the OWASP Agentic Top 10 the anchors are ASI02 and ASI06.

On the regulatory side, framed as scope rather than a citation: this touches operational resilience and availability, which GCC rulebooks address for any critical system. An agent with no cost ceiling is an availability and financial exposure that the resilience obligations were written to prevent, even though they did not picture a language-model loop as the cause. Which obligations apply depends on your regulator and architecture, and that mapping is an advisory estimate until someone checks it properly.

The part worth keeping

The loop is what makes an agent an agent. Letting the model decide how many steps a task needs is the whole point, and a fixed script would be worse.

The step limit does not fight that. It bounds it. The model still decides how to solve the problem; the limit decides how much the attempt is allowed to cost before a human looks. Set it as the safety control it is, budget the cost as well as the count, and make hitting it loud. Then a stuck or driven loop becomes a caught exception instead of a story about an invoice.