ninemin.lilulab.ai

“Agent stopped due to iteration limit or time limit.” — LangChain’s string is your output

{'input': '...', 'output': 'Agent stopped due to iteration limit or time limit.'}

This is the most widely pasted of the strings on this site and the most widely misunderstood, because it is not an error message. It is a constant that LangChain’s AgentExecutor puts in the output key where your answer was going to go. Nothing raised, nothing logged at error level, the chain returned normally. If you print result["output"], you print that sentence; if you write it to a database, you have stored that sentence as an answer. And the steps the agent did finish are sitting right there in the same call, which is the part worth staying for.

Where this string comes from

The loop stops on a condition that deliberately covers both limits:

def _should_continue(self, iterations: int, time_elapsed: float) -> bool: if self.max_iterations is not None and iterations >= self.max_iterations: return False return self.max_execution_time is None or time_elapsed < self.max_execution_time

and when it does, the executor asks the agent for a stopped response and returns it through the same path a successful answer takes:

output = self._action_agent.return_stopped_response( self.early_stopping_method, intermediate_steps, **inputs, ) return self._return(output, intermediate_steps, run_manager=run_manager)

The string itself is in the agent, not the executor:

if early_stopping_method == "force": # `force` just returns a constant string return AgentFinish( {"output": "Agent stopped due to iteration limit or time limit."}, "", ) msg = f"Got unsupported early_stopping_method `{early_stopping_method}`" raise ValueError(msg)

Quoted verbatim from libs/langchain/langchain_classic/agents/agent.py at commit 5a9b1ec2. Note the path: on master the AgentExecutor lives in langchain_classic, not in langchain.agents. If you went looking for this code at the path in an older answer, that is why you did not find it.

The file contains two different stopped strings. Which one you got identifies your agent.

There are three implementations of return_stopped_response in that one file, and they do not agree:

So in Python the sentence you got is a fact about which base class your agent subclasses, not about which limit stopped it. Both of the modern runnable agents inherit from one of the two base classes above — RunnableAgent from BaseSingleActionAgent and RunnableMultiActionAgent from BaseMultiActionAgent — which has a consequence worth knowing before you go looking for the documented escape hatch: early_stopping_method="generate" raises ValueError on a single-action runnable agent. The field’s own docstring offers “Either 'force' or 'generate'”; the implementation you are probably running accepts only the first.

If you are in TypeScript, you almost certainly got the other one

The JavaScript package splits the two strings differently, and the difference is a piece of dead code. In libs/langchain-classic/src/agents/agent.ts, returnStoppedResponse under "force" returns:

return { returnValues: { output: "Agent stopped due to max iterations." }, log: "", };

while the other sentence lives in a method on the executor itself:

_returnStoppedResponse(earlyStoppingMethod: StoppingMethod) { if (earlyStoppingMethod === "force") { return { returnValues: { output: "Agent stopped due to iteration limit or time limit.", }, log: "", } as AgentFinish; }

Nothing calls that method. The two places the TypeScript executor actually stops — _stop() in the iterator and the fall-out at the end of _call — both call this.agent.returnStoppedResponse(...), which is the first block. So in TypeScript the sentence at the top of this page is reachable only by calling a method the library never calls itself, and the output you will have seen is Agent stopped due to max iterations.

Both quotations from agents/agent.ts and agents/executor.ts at commit b261746b. The “no caller” claim is scoped to exactly that: a search of those two files at that commit found the declaration of _returnStoppedResponse and no call to it. We have not searched the rest of the repository or any package that imports it. The TypeScript maxIterations default is also 15, and its _shouldContinue tests the iteration count only — there is no time term in it at all, which makes the “or time limit” wording doubly inapt on that side.

If you are reading this because a tool built on the TypeScript package gave you a capped run — n8n’s older AI Agent node versions are the common case — then the string to check your output against is the shorter one.

Confirm it in ten seconds

The sentence names two limits and tells you nothing about which one fired. You can resolve that from your own configuration without another run, because of the defaults:

max_iterations: int | None = 15 max_execution_time: float | None = None early_stopping_method: str = "force"

And the check that belongs in your code rather than in a terminal: compare result["output"] against the two constants above and treat a match as a failure. They are constants in the library, which is the one thing that makes this reliable — nothing else about a capped run differs from a successful one.

Why raising max_iterations moves the wall

Fifteen is the default. An iteration is one pass of the agent loop: one model call, then the tool call it asked for. Raising it to 50 buys 35 more passes, and if your task genuinely needed 19, that is the correct fix and the page ends for you here.

What does not change is the ending. The terminal act of a capped AgentExecutor is to substitute a constant for your answer. It does that at 15 and it does it at 1,500; the larger number only buys more model calls before the substitution. And because the substitution is silent, the cost of getting it wrong grows with the cap: an agent looping without a reachable stopping condition under a cap of 15 wastes fifteen calls, and the same agent under a cap of 200 wastes two hundred and returns the identical string. There is also a second ceiling most readers have left at None: max_iterations set to None is documented in the source as something that “could lead to an infinite loop”, so removing the limit is not a fix either.

The restructure: the finished steps are already in the return value

This framework gives you something the others on this site do not, and it costs one keyword argument.

Set return_intermediate_steps=True. Look again at the call site above: the stopped path calls self._return(output, intermediate_steps, ...), with the same intermediate_steps list the successful path passes. Inside _return, those steps are attached to the result when the flag is on. So on a capped run you get back every (action, observation) pair the agent completed, in order, alongside the substituted sentence. The finished work is not lost at all — by default it is simply never handed to you. Turn the flag on, treat the two constant strings as failure, and process the steps.

Then make each step durable rather than merely returned. Steps that only exist in the return value are lost if the process dies rather than the loop ending. The general move is the one every page on this site arrives at: have each tool write its own result where the next attempt can find it, so a second run is a continuation instead of a replay.

And make one iteration do more. A step that handles one item and a step that handles forty cost the same single model call. Runs that reach fifteen iterations are usually not hard, they are iterative — a model walking a list by hand because every tool it was given takes one element. Hand it a tool that takes the list and the iteration count stops tracking the item count.

What is behind this site

There is a written guide: the iteration-and-turn arithmetic as a formula you can run against a brief before you launch it, the reasons raising a cap does not finish the job, and batch.py, one standard-library file that collapses a per-item loop into a single pass — the change this page recommends last, written out.

No page on this site has a checkout widget of its own. There is a written guide behind this host and it is on sale at $19 on a storefront that delivers the files automatically and carries a 30-day money-back guarantee: buy it there (checked 2 October 2026); the guide can also be paid for with 19 USDC on Base at the payment page, where delivery is by hand as a reply to your email. Every page on this site, including this one, is free to read in full, with no sign-up and nothing gated.

The short version: Agent stopped due to iteration limit or time limit. is not an error, it is the value of result["output"] — a constant substituted for your answer when the loop runs out of iterations. With the defaults, max_execution_time is None, so it was the iteration limit at 15. A multi-action agent gets a different constant, Agent stopped due to max iterations., and early_stopping_method="generate" raises on a single-action runnable agent. Set return_intermediate_steps=True and the finished steps come back with the substituted string; check the output against both constants, and raise the cap only when you can say how many iterations the task needs.

Nearby

This page counts anonymous readership. Each load sends the page path, the address of the page you came from (our server keeps only its domain), how long the page was open, whether you scrolled, and any campaign or outreach code in the link you followed; a second “engaged” event is sent once, ten seconds after the page opens — whether or not the tab is in front of you — or as soon as you scroll a quarter of it. The campaign codes from the first link you arrived on are kept in this browser’s local storage, and a later visit that arrives with no codes of its own is counted against them, outreach code included; a link carrying its own codes is used for that visit, and the stored first touch is never replaced. If a link carries a different outreach code, that later touch is recorded alongside the first rather than in place of it. The referrer of that first visit is stored in the same place and is never sent anywhere — every visit sends the referrer it actually had. A return visit is still counted, and an outreach code is removed from the address bar after it is read. The page also asks this domain for Vercel’s analytics script; on 26 September 2026 we requested that address on each of the five hosts we publish — lilulab.ai, willcall, ninemin, ghmirror and pinpoint — and every one returned HTTP 404 and no script, so no script from another company was served to your browser and none ran. The page still asks for it on every load, so this stops being true the moment that address starts answering, without a single byte of this page changing — and this page will not know it has, and will go on saying what it says. Whether Vercel counts the request at its own edge is its record and not ours; as of 26 September 2026 no one here had opened it, and we keep no copy of it. No cookie; the local storage above does that job. Some things are recorded that the list above does not name. Your browser and the network attach these to the request rather than the page sending them: the identification string your browser gives with every request; the two-letter country the network assigns your address — no city, and your address itself is never stored; and the site address you asked for. Our own server then writes its own bookkeeping about the record: the date and time of your visit, to the thousandth of a second, from our clock; which request header it took that site address from; and a number naming the record format it wrote. It also stores the domain your browser said the count was fired from, which for a normal load of this page is this site itself, and which our server records as the word “unknown” when the browser sends nothing it can read. Every row is kept in Vercel’s blob storage and not on a machine of our own, measured 26 September 2026 by reading the handler. The page sends how long it was open twice, from two different counters, and our server reads only one of the two names — so some rows carry it and some are empty (measured 26 September 2026). This page reads an outreach code from the address if one is present and keeps it in your browser, which would let us tell one reader from another — but we have sent no link for this page and hold no list of recipients for it, so as of 2 October 2026 there is no name for any code to resolve to. This page also loads a second counter of its own. It sends its own copy of the view event on every load, so one load writes two view rows and any rate measured against them reads half its true value; it also measures the time differently, counting only the seconds the page was actually in front of you where the first counter sends wall-clock time since the page opened. It also sends an event once you have had the page in front of you for ten seconds and moved the mouse, touched the screen, scrolled or pressed a key. That ten-second event also depends on the count before it: this second counter does not send it unless its own copy of the view event went out first. Its “engaged” is stricter still — it is sent only after that ten-second event has gone out, you have had the page in front of you for ninety seconds in all, and you have scrolled far enough to reach a marker we put where the explaining part of this page ends; a reader who stops short of that marker never produces one. Because both counters send an event called “engaged” under different rules, a single visit can produce two of them. The host that serves it keeps its own request logs; those are its record and not ours. Every quotation above is from the linked file at the pinned commit; the same links are listed for machine readers at /llms.txt.