Skip to content

Princeu3/agent-loop-detector

Repository files navigation

agent-loop-detector

Stop your tool-using AI agent from getting stuck in loops. A tiny, zero-dependency TypeScript guardrail that detects the three ways autonomous LLM agents spin their wheels — identical repeated tool calls, cyclic tool-call patterns, and idle status-check loops — and hands back a corrective nudge you can feed straight into the model.

npm coming soon license MIT types included zero dependencies node >=20

Why

Give an LLM a set of tools and a long-running task and it will, sooner or later, get stuck. Three failure modes dominate:

  1. Identical repeated calls — it calls read_file("config.json") over and over, expecting a different answer.
  2. Cyclic tool-call patterns — it oscillates between the same two or three tools every turn without making progress (the classic "submit proposal batch #265… submit proposal batch #265… forever" death spiral).
  3. Idle status-check loops — it keeps calling git_status, check_credits, list_goals and never actually does anything.

agent-loop-detector catches all three with a small amount of per-agent state and returns a plain-English reason you can inject as the next tool result so the model corrects course (or calls task_done). It escalates the cyclic case from a warning on the third repeat to a hard enforcement block if the agent ignores the warning.

Install

npm i github:Princeu3/agent-loop-detector

npm package coming soon. Until then, install straight from GitHub (the command above).

Quick start

import { LoopDetector } from "agent-loop-detector";

const detector = new LoopDetector({
  maxIdenticalCalls: 3, // block after N identical calls in a row
  maxIdleOnlyTurns: 3,  // nudge after N turns of only status tools
  windowSize: 10,       // how many recent turns/calls to remember
});

// On every tool call the agent makes:
function onToolCall(name: string, argsJson: string) {
  const check = detector.recordToolCall(name, argsJson);
  if (check.blocked) {
    // feed check.reason back to the model instead of executing the tool
    return { role: "tool", content: check.reason };
  }
  // ...actually run the tool...
}

// At the end of each agent turn:
function onTurnEnd() {
  const check = detector.endTurn();
  if (check.reason) {
    // inject check.reason as a system/tool nudge (warning, enforcement, or idle-loop)
  }
}

// Starting a brand-new task? Wipe the state:
detector.reset();

Custom idle/status tools

The notion of an "idle / status-only" tool is configurable. Pass a Set or a predicate:

import { LoopDetector, DEFAULT_IDLE_ONLY_TOOLS } from "agent-loop-detector";

new LoopDetector({
  idleOnlyTools: new Set([...DEFAULT_IDLE_ONLY_TOOLS, "get_dashboard"]),
});

new LoopDetector({
  idleOnlyTools: (name) => name.startsWith("status_") || name.endsWith("_info"),
});

If you omit it, DEFAULT_IDLE_ONLY_TOOLS is used.

API

new LoopDetector(config?: Partial<LoopDetectorConfig>)

option default meaning
maxIdenticalCalls 3 Block after this many identical (same name and same args) calls in a row.
maxIdleOnlyTurns 3 Nudge after this many consecutive turns that used only idle/status tools.
windowSize 10 How many recent turn-patterns / calls to keep in memory.
idleOnlyTools DEFAULT_IDLE_ONLY_TOOLS Set<string> or (name) => boolean deciding which tools are "idle/status-only".

recordToolCall(name: string, args: string): LoopCheckResult

Call for every tool invocation. Returns { blocked, reason }. When blocked is true, don't run the tool — feed reason back to the model. Detects the identical repeated call loop.

endTurn(): LoopCheckResult

Call at the end of each agent turn. Detects cyclic turn-patterns (warn on the 3rd repeat, then blocked: true enforcement if ignored) and idle-only turn loops (a non-blocking nudge). A non-empty reason with blocked: false is advisory — inject it, but let the turn proceed.

reset(): void

Clears all internal state. Call between independent tasks.

LoopCheckResult

interface LoopCheckResult {
  blocked: boolean; // true => do not execute; feed `reason` back to the model
  reason: string;   // human-readable nudge, or "" when nothing to say
}

Also exported: DEFAULT_IDLE_ONLY_TOOLS: ReadonlySet<string> and simpleHash(str): string.

Pairs well with react-agent-loop (the agent loop itself) and agent-policy-engine (spend / action policy) — drop this in as the loop-safety layer.

Attribution

Extracted, refactored, and decoupled by Prince Upadhyay (@Princeu3) from the MIT-licensed Conway-Research/automaton project. The idle-tool coupling was inlined and made configurable. See NOTICE for details.

Keywords

ai-agents, llm-agents, loop-detection, agent-loops, react-agent, tool-use, infinite-loop, guardrails, autonomous-agents, agent-safety, typescript, llm

License

MIT

About

Detect and break repetitive loops in tool-using AI agents — catches identical repeated tool calls, cyclic tool-call patterns, and idle status-check loops, and returns a corrective nudge.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors