Ground Truth

I Read the Stop Condition in Nine Agent Frameworks. The Third Column Is Empty.

Mostafa DhouibMostafa Dhouib··10 min read
The short answer

Nine agent frameworks, from nine organisations, in different languages, with no shared lineage. Every one decides whether to keep going using the same two ingredients: the model's opinion that it is not finished, and a counter. Not one of them checks a measure of the remaining work. The third column of the audit is empty, and it is empty for a reason that is not laziness.

I Read the Stop Condition in Nine Agent Frameworks. The Third Column Is Empty.

The short answer. I read the continue condition in the source of nine agent frameworks, not the documentation. Every one of them decides whether to keep looping using exactly two inputs: whether the model asked to continue, and whether a counter has run out. Nine organisations, different languages, different design philosophies, different years, no shared lineage, and the same two ingredients. Not one checks a number describing the remaining work. That third column is empty, and the reason is not that the authors were lazy. It is that the thing belonging in it cannot be written by a framework.

Every argument about runaway agents eventually reaches a claim about what the framework does. So rather than repeat the claim, I went and read the code.

Not the documentation, which describes intent. The continue condition itself, in the source, in each of nine frameworks.

What a stop condition has to be

Before the results, the standard they are being measured against, because otherwise this is just a list.

A loop provably terminates when you can name a quantity that decreases on every pass and cannot decrease forever. That is not an opinion about good design, it is the only known way to prove a loop stops, and it has been the only way for sixty years.

So a stop condition has three possible ingredients, and the audit is simply which of them each framework consults:

The model's opinion. Did the model ask to continue, or emit something that looks like a final answer? This is not a function of state: ask twice about the same state and you can get two different answers, which disqualifies it as a termination measure before you consider anything else.

A counter. Iterations used against a maximum. This is a valid measure. It decreases by one each pass and bottoms out at zero, which is a genuine termination proof.

A measure of the remaining work. A number computed from the task that shrinks as the task approaches done. This is what every other field that solved this problem needed.

The audit

Checks the model's opinion9 of 9string match on the output, or absence of tool calls
Checks a counter9 of 9a real termination proof, measuring spend
Checks remaining work0 of 9the column that would tell you whether it is finishing
Distinguishes gave up at the type1 of 9the exception, and proof the fix is available
Read at a point in time. Defaults change, so check your own version. The shape does not change.
FigureNine frameworks, nine organisations, different languages and years, no shared lineage. Every one checks the same two things, and not one checks a measure of the remaining work.

Nine frameworks. Every one checks the model's opinion. Every one carries a counter. The third column is empty across the board.

Two details are worth pulling out, because they are more instructive than the summary.

The exits are string matching. In several of these the "model is finished" signal is a literal string comparison against the model's output, looking for a phrase like a final-answer marker. In others it is the absence of tool calls in the response. Either way, the thing deciding whether your agent keeps spending money is a pattern match on generated text.

One default is not a bound at all. One framework's group-chat manager carries a maximum consecutive auto-reply that defaults to a number around nine quintillion. Every defence of a loop cap says it is a safety net, that it stops a runaway, that it bounds the blast radius. None of those defend that number. It is not a bound. It is decoration shaped like a bound, and it will still be there long after the run has cost more than the project.

And one framework gets something right that the others do not. At least one distinguishes hitting the step limit at the type level, returning a distinct error rather than a result. That is the missing third state, implemented. It is the exception rather than the norm, and it is worth naming because it proves the fix is available rather than theoretical.

What that guard actually sees

Strip a framework's loop back to its control flow and there is remarkably little in it.

The gate
Did the model ask for a tool, and is the iteration count below the maximum?
Run the tool and loop back
no reference to your task anywhere in the decision
Exit and return a result
whether it finished or ran out of budget
There is no third condition. Nothing else in the picture can stop the loop.
One of these is not a function of state: ask the model twice about the same state and you can get two answers.
FigureStrip a framework's loop to its control flow and one decision point holds the entire safety architecture. This is the complete list of what it is allowed to consider.

The model runs. A single decision point asks whether to continue. If yes, run the tool and loop back. If no, exit.

That diamond is the entire safety architecture. Nothing else in the picture can stop the loop. So crack it open and look at the complete list of what it is allowed to consider:

Did the model ask for a tool. And is the iteration count below the maximum.

That is everything. There is no third condition, no reference to your task, and no way for the loop to know whether the work is progressing.

Why the counter is a real proof and still does not help

This is the part that gets argued about, so it is worth being precise, because the common criticism of these frameworks is wrong.

If someone tells you agent frameworks have no termination proof, they have not read the source. Maximum iterations minus the current count decreases by one on every pass and bottoms out at zero. It is a function of the state, it is well founded, and it strictly decreases. It satisfies every requirement. It is a genuine termination proof, not a hack.

The loop provably stops.

So why did a real system run for 264 hours and bill 47,000 dollars?

What the proof says
Max iterations minus count is a function of state
It strictly decreases on every pass
It bottoms out at zero
The loop provably stops
What you asked
Is the work getting closer to done?
The counter measures spend, not progress
A fuel gauge cannot tell arriving from running dry
So done and gave up return the same thing
The framework answers the first question correctly. Everyone reads it as an answer to the second.
FigureThe counter is a genuine termination proof. It is also the wrong instrument, and both statements are true at once.

Because the counter measures how much you have spent, not how much work remains. It is a fuel gauge. A fuel gauge is accurate and honest, and when it reaches empty the car stops, guaranteed. But two things stop a car, arriving and running out of fuel, and the gauge cannot tell you which.

The proof is about the loop. Your question is about the work. Those are different questions, and the framework answers the first one correctly while everyone reads it as an answer to the second.

The test that settles whether a variable is doing anything

There is a ten-second check that applies to any variable in any system, and it settles this one cleanly.

Delete it, rerun, and see what changes.

Delete the congestion window from TCP and throughput falls off a cliff. That is not hypothetical: in 1986 congestion collapse dropped a link between two buildings a few hundred yards apart from 32 kbit/s to 40 bit/s, roughly a thousandfold. The variable was load-bearing, and removing it broke the system.

Now delete the iteration cap from your agent. It makes the same tool calls, in the same order, with the same results, for a bit longer. The trace does not flinch.

Delete TCP's congestion windowthe internet falls over1986: a link between two nearby buildings dropped from 32 kbit/s to 40 bit/s
Delete your agent's iteration capsame calls, same order, longerthe trace does not flinch
One is load-bearing. The other is scaffolding: real, correctly enforced, and bounding nothing you care about.
FigureThe ten-second test for whether a variable is doing anything: delete it, rerun, and see what changes.

That is the difference between a load-bearing variable and scaffolding. The cap is real, correctly enforced, and bounds something. It just does not bound anything you care about.

Formal methods has a name for state added purely so a proof goes through without changing behaviour: ghost state. Add a counter to an infinite loop and you have a genuine termination proof and an unchanged program. Both statements are true at once, which is how a method that proves everything proves nothing.

Why the column is empty, and why waiting will not fill it

Here is where the audit stops being a criticism.

Every field that ever built a reliable automatic stopping rule needed a model of the task. Sequential analysis in 1945 needed a parametric model of the observation process. Clinical trials need a model of the trend and the patients remaining. Bayesian optimization needs a surrogate model of the objective, and it got one on day one, in 1978. Bandits need a reward distribution per arm. Proof assistants refuse to compile until you supply the measure yourself.

Eighty years, seven fields that do not read each other's papers, one requirement.

Your agent has a model of language. It has no model of your task, and it cannot have one, because it does not know what your task is. That is not a gap in the framework, it is the definition of a general framework.

So the authors did the honest thing. They refused to ship a measure that does not exist and gave you a counter instead. I would much rather have a counter and know it is a counter than a fabricated progress metric I might believe.

Which means the answer to "should we wait for the frameworks to fix this" is no, and not because they are slow. You are asking them for a stopping rule on a task you have not defined. Nobody can define it but you. It is not a roadmap item, it is a category error.

What to do with this

The audit is only useful if it changes something on Monday.

Read your own framework's continue condition. It is usually under thirty lines. You are looking for what it consults, and specifically whether anything in it refers to your task. This takes about ten minutes and it is more informative than any amount of documentation.

Name the number that goes down. Rows not yet migrated, tests still failing, records unmatched, required fields still empty, pages not yet processed. If your task has one, put it in the guard, in code, not in a prompt. That is roughly three days of work.

Demote the model to a proposer. It suggests the next action. It does not certify its own completion, for the same reason a fuel gauge is a float in the tank rather than the engine's opinion of how much fuel is left.

Give the run three terminal states. Done, gave up, killed, distinguishable at the return type rather than in a log line.

And bound the system, not the loop. Every framework in this audit bounds a single run. None of them bounds a tree of runs calling runs, which is where the five-figure invoices actually come from.

A note on how this ages

Defaults change, frameworks get rewritten, and this was read at a point in time. Treat the specific numbers as a snapshot rather than a permanent fact, and check your own version.

What will not change is the shape. Until a framework can know your task, the third column stays empty, and the measure has to come from you.

FAQ

Do AI agent frameworks have a termination proof? Yes, and this is commonly stated incorrectly. The iteration counter is a genuine termination proof: it is a function of state, strictly decreasing, and well founded. The loop provably stops. The problem is that it measures budget spent rather than work remaining, so it cannot distinguish finishing from giving up.

What do agent frameworks actually check before continuing? Two things: whether the model asked to continue, often by string-matching its output for a final-answer marker or checking for the absence of tool calls, and whether an iteration counter is below its maximum. Nothing in the condition refers to the task itself.

Why don't agent frameworks measure remaining work? Because a stopping rule requires a model of the task, and a general framework cannot have one since it does not know what your task is. Every field that solved this, from sequential analysis in 1945 to modern proof assistants, had or demanded such a model. The framework authors shipped a counter rather than fabricating a progress metric.

Is a max-iterations setting enough to make an agent safe? No. It bounds one run, which is real and worth having, but it cannot tell done from gave up, it cannot fire at all when a tool call hangs, and it does not bound a tree of runs that each spawn more runs. That last case is where large unexpected bills come from.

How do I check my own framework? Read the continue condition in the source rather than the documentation; it is usually under thirty lines. Ask what it consults and whether anything in it refers to your task. Then delete the iteration cap and rerun: if the behaviour is unchanged apart from running longer, the cap was scaffolding rather than a control.

Carrying a program like this one?

Tell us the system, the stakes, and the date that matters. You get a straight technical reply from the person who would lead the work, within 24 hours.

Bring us the program