Your AI agent loops forever because the tool never said done

AI Agents · By Caleb Sakala · April 23, 2026

Cartoon golden retriever happily fetching sticks to an unresponsive stone statue surrounded by a huge pile of identical sticks

An agent that calls the same search tool 47 times in 3 minutes is not broken. It is doing exactly what the conversation history tells it to do. The tool returned "found 12 results" each time, and the agent, prompted to "be thorough," kept asking for more. Each call burned roughly 1,200 tokens. At GPT-4o's input pricing of $2.50 per million tokens, that 47-call AI agent infinite loop cost about $0.14. Scale that across 500 automated runs per day and the waste hits $70 daily, or $25,550 per year, for a single misbehaving tool. The standard fix is a guardrail: max_steps=20, maybe a timeout. Those stop the bleeding. They do not fix the wound.

An AI agent infinite loop starts before the agent decides anything

Here is what happens mechanically. The LLM receives the full conversation history and picks a tool with formatted arguments. The tool executes and returns a response. That response gets appended to the conversation history, and the LLM reads the updated context to decide what to do next.

Whether the agent calls the tool again depends entirely on what the tool response says. If the response is ambiguous (a raw JSON blob with no status field, or a message like "found results, more may be available"), the agent has no signal to stop. So it calls again. And again.

The root cause sits in the tool response format, not the agent logic.

Three loop patterns, one shared cause

The hard loop happens when a tool returns identical output on consecutive calls. The agent receives the same 12 results with no deduplication logic, so it calls again. Superface's "AI Agent Reality Gap" benchmark found that even basic CRM tasks (creating leads in Salesforce, updating HubSpot pipelines) fail up to 75% of the time under repeated execution, partly because tools do not distinguish between "action succeeded" and "action merely ran."

Retry storms happen when error-handling layers compound. The HTTP client retries on a 429, while the tool wrapper independently retries on any non-200, and then the agent itself retries because the tool "failed." Three layers with 3 retries each means up to 27 calls for one intended operation. The math: 27 calls at 1,200 tokens each is 32,400 tokens, roughly $0.08 at GPT-4o rates. Across 500 daily runs, that is $40/day in pure waste.

Then there is the semantic loop, which is the sneakiest. The agent rephrases the same query each time. "Search for billing API errors." Then "Find billing API error reports." Then "Look up API billing error logs." Each call feels like progress to the LLM because the wording changed. The results are identical every time. The Vercel AI SDK defaults to a 20-step cap via stepCountIs(20) for exactly this reason, but 20 steps of semantic looping still burns tokens before the cap kicks in.

All three patterns share one root cause: the tool response does not include an explicit terminal state.

max_steps is a fire extinguisher, not a fire prevention system

Setting max_iterations=15 in LangChain or maxSteps in the AI SDK stops runaway loops. The agent halts. But the run still fails. The user gets no answer, and the wasted steps still cost money.

Here is a rough calibration that most guides skip. A single-tool lookup (check weather, fetch a stock price) should complete in 2 to 3 steps. A multi-tool research task that searches, fetches details, and compares options should finish in 6 to 10 steps. Anything past 12 steps for a non-coding task is almost certainly looping. If an agent regularly needs 15 or more steps, the problem is the task decomposition or the tool design, not the step limit.

Framework authors set generous defaults because they cannot predict how many steps a specific task requires. LangChain defaults to 15 iterations. The Vercel AI SDK defaults to 20. AutoGen lets runs continue until the conversation naturally terminates, which in practice means "until something external stops it." These defaults protect against catastrophic runaway, but they are too high for most single-purpose agents.

Fix the tool response, not just the agent config

The solution is simple enough to feel anticlimactic. Every tool response should include a status field and a next_action hint.

# Before: ambiguous response that causes loops
def search_customers(query: str) -> dict:
    results = db.search(query)
    return {"customers": results}

# After: explicit terminal state
def search_customers(query: str) -> dict:
    results = db.search(query)
    if not results:
        return {
            "status": "no_results",
            "next_action": "stop",
            "message": f"No customers match '{query}'. Try different spelling."
        }
    return {
        "status": "success",
        "next_action": "stop",
        "customers": results,
        "message": f"Found {len(results)} customers. This is the complete result set."
    }

That last sentence, "This is the complete result set," does more work than any guardrail. It tells the LLM that calling again will produce the same data. Anthropic's tool use documentation recommends returning results with clear human-readable summaries so the model can assess whether the task is complete.

The other half of the fix is error classification. Not all errors deserve retries.

A 429 rate limit means wait and retry once. A 401 or 403 auth failure means stop and escalate, because retrying an auth rejection will never produce a different outcome no matter how many times the agent tries. Validation errors (400) mean stop and fix the parameters. Server errors (500) deserve retry with exponential backoff, capped at 3 attempts, while a 504 timeout gets one retry before falling back to a different approach entirely.

Most agent frameworks treat every non-200 response as "retry." That default is how a single 401 auth error generates 27 calls instead of one.

Where tool response design breaks down

This approach has a real limitation, and it is worth being upfront about: you do not always control the tool. Third-party APIs return whatever they return. The Stripe API gives clear error codes with actionable messages. The average internal REST endpoint returns {"error": "something went wrong"} with a 500 status.

For tools outside your control, the fix moves to a wrapper layer. Build a thin adapter between the agent and the external API that translates raw responses into the status/next_action format. Without that adapter, ambiguous API responses propagate through every downstream step, and each step re-invokes the failing call because it never received a terminal signal. Chase Agents automations solve this at the workflow layer: each step defines its own success criteria and input validation schema, so an ambiguous upstream result triggers a classified error at the step boundary instead of silently propagating.

Semantic loops present a different challenge. Sometimes the agent knows the search returned 12 results but decides it should "verify" by searching again with slightly different terms. That is a prompt design problem. The fix is explicit instructions in the system prompt: "Do not call the same tool category more than once unless the previous call returned status: partial." This instruction is blunt, but it works. Sophisticated, nuanced prompting is how you get sophisticated, nuanced looping.

The production sharp edge nobody documents

Here is the gotcha that turns an expensive problem into a dangerous one: tools with side effects and no idempotency keys make loops destructive, not just wasteful. An agent looping on a send_email tool sends duplicate emails. An agent looping on a create_order tool creates duplicate orders. Even if a guardrail stops the loop after 5 iterations, the first 4 duplicate side effects already happened.

The mitigation is separating tools by side-effect risk. Read-only tools can tolerate retries safely because calling a search or lookup endpoint twice produces the same data without side effects. Write tools (anything that sends, creates, updates, or deletes) need idempotency keys at the API level and should have max_retries=1 at the tool level, independent of the agent's step budget. Most agent setups hand every tool to every step, which means a looping research call can accidentally trigger a purchase or send call if the LLM picks the wrong tool during a confused retry. Chase Agents automations use action-type routing to separate research steps from execution steps, so the authorization boundary doubles as a loop boundary: a research step physically cannot invoke a purchase tool because it lacks access to those operations.

That separation sounds obvious when stated plainly. In practice, loop prevention for write tools depends entirely on the LLM's judgment about when to stop. Which is the same judgment that started the loop.

Why AI agent infinite loop fixes keep missing the real problem

Every existing article about AI agent infinite loops, including the best ones currently ranking, frames loop prevention as a runtime problem. Add guardrails. Add detection. Add monitoring and alerting. All of that is downstream. The upstream problem is that tools are API interfaces designed for human developers reading documentation, and agents need tool interfaces designed for machines reading structured responses.

The Vercel AI SDK added stopWhen and prepareStep hooks. LangChain added max_iterations years ago. OpenAI's function calling spec still has no standard for terminal-state signaling in tool responses. MCP (Model Context Protocol) defines tool schemas but not response schemas. That gap will close, probably through an MCP extension or a competing standard, within the next 12 months. Until then, the burden falls on every team wrapping tools for agent use, and most of them are learning the same lesson independently: return "done" or return chaos.