How to Fix 'agent infinite loop' in LlamaIndex (TypeScript)

By Cyprian AaronsUpdated 2026-04-21
agent-infinite-loopllamaindextypescript

When LlamaIndex says agent infinite loop, it usually means the agent kept calling tools or re-planning without ever reaching a final answer. In TypeScript, this often shows up with AgentRunner, ReActAgent, or workflow-based agents when tool outputs don’t give the model a clean path to stop.

Most of the time, the agent is either missing a valid termination condition, returning malformed tool output, or being fed a prompt/tool setup that encourages repeated calls.

The Most Common Cause

The #1 cause is a tool that returns something the agent can’t use to conclude the task, so it keeps calling the same tool again.

A common pattern is using a tool that returns vague text, undefined, or a response that doesn’t clearly satisfy the agent’s instruction.

Broken patternFixed pattern
Tool returns ambiguous outputTool returns structured, finalizable output
Agent prompt doesn’t define stop conditionAgent prompt tells the agent when to answer directly
Tool may be called repeatedly with same inputTool result changes state or clearly resolves query
// BROKEN: tool output doesn't help the agent terminate
import { FunctionTool, ReActAgent } from "llamaindex";

const lookupCustomer = FunctionTool.from(async ({ customerId }: { customerId: string }) => {
  const customer = await db.customers.findById(customerId);
  return customer?.name; // too little context
});

const agent = new ReActAgent({
  tools: [lookupCustomer],
  systemPrompt: "Help the user find customer details.",
});

// This can trigger:
// Error: agent infinite loop
// FIXED: return structured data and define a clear stop condition
import { FunctionTool, ReActAgent } from "llamaindex";

const lookupCustomer = FunctionTool.from(async ({ customerId }: { customerId: string }) => {
  const customer = await db.customers.findById(customerId);

  if (!customer) {
    return JSON.stringify({ found: false });
  }

  return JSON.stringify({
    found: true,
    customerId: customer.id,
    name: customer.name,
    status: customer.status,
  });
});

const agent = new ReActAgent({
  tools: [lookupCustomer],
  systemPrompt:
    "Use the tool once. If you have enough information to answer, respond directly and do not call tools again.",
});

If your tool output is just "John" or "done", the model often has no reason to stop. Give it machine-readable output and a prompt that makes termination explicit.

Other Possible Causes

1. Tool always returns the same thing

If a tool is effectively idempotent in a bad way, the agent may keep asking for more because nothing changes.

const getPolicyStatus = FunctionTool.from(async () => {
  return "status checked";
});

Fix it by returning actual state:

const getPolicyStatus = FunctionTool.from(async ({ policyId }: { policyId: string }) => {
  const policy = await db.policies.findById(policyId);
  return JSON.stringify({
    policyId,
    status: policy?.status ?? "not_found",
    updatedAt: policy?.updatedAt ?? null,
  });
});

2. Missing or weak maxIterations / step limit

Some agent setups will keep planning until they hit an internal guardrail. If your config doesn’t cap steps, you’ll see loops turn into errors.

const agent = new ReActAgent({
  tools: [lookupCustomer],
  maxIterations: 20,
});

If your version exposes a different option name, use the equivalent step limit on AgentRunner or your workflow runner. The point is simple: cap runaway reasoning.

3. Tool schema mismatch

If TypeScript types say one thing but runtime input shape says another, the model may keep retrying because every call fails validation.

// BROKEN: expects customer_id but prompt/model sends customerId
const lookupCustomer = FunctionTool.from(
  async ({ customer_id }: { customer_id: string }) => {
    return JSON.stringify({ customer_id });
  }
);

Fix by aligning schema names exactly:

const lookupCustomer = FunctionTool.from(
  async ({ customerId }: { customerId: string }) => {
    return JSON.stringify({ customerId });
  }
);

4. Agent prompt encourages tool spam

If you tell the model to “always verify” or “keep checking,” it may never decide it has enough evidence.

const agent = new ReActAgent({
  tools: [lookupCustomer],
  systemPrompt:
    "Keep using tools until you are absolutely certain and do not answer unless verified multiple times.",
});

That prompt invites loops. Replace it with a bounded instruction:

const agent = new ReActAgent({
  tools: [lookupCustomer],
  systemPrompt:
    "Use tools only when needed. Stop once you have enough information to answer confidently.",
});

How to Debug It

  1. Inspect the last few tool calls

    • Look for repeated calls with identical arguments.
    • If you see lookupCustomer({ customerId: "123" }) five times in a row, your tool output isn’t advancing state.
  2. Log raw tool responses

    • Print exactly what each FunctionTool returns.
    • Watch for undefined, empty strings, "ok", or vague text like "checked".
  3. Check your iteration guardrails

    • Verify maxIterations, step count, or workflow limits.
    • If there’s no cap, add one before chasing deeper bugs.
  4. Validate schema and prompt together

    • Confirm tool argument names match what the model sees in the prompt.
    • Make sure the system prompt includes an explicit stopping rule.

A good debugging log looks like this:

const lookupCustomer = FunctionTool.from(async ({ customerId }: { customerId: string }) => {
  const result = await db.customers.findById(customerId);
  console.log("lookupCustomer input:", { customerId });
  console.log("lookupCustomer output:", result);

  return JSON.stringify(result ?? { found: false });
});

If your logs show valid inputs but useless outputs, you’ve found the loop source.

Prevention

  • Return structured JSON from tools instead of short free-text strings.
  • Set an explicit iteration limit on every production agent.
  • Write prompts that say when to stop calling tools and answer directly.
  • Keep tool schemas and TypeScript types aligned; don’t let runtime shapes drift from compile-time assumptions.

If you’re seeing Error: agent infinite loop in LlamaIndex TypeScript, start with the tool output first. In practice, that’s where most loops are born.


Keep learning

By Cyprian Aarons, AI Consultant at Topiax.

Want the complete 8-step roadmap?

Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.

Get the Starter Kit

Related Guides