Loop Protection

By the Syncfusion DevTutorials team · Last updated

The guards that stop a stuck agent: duplicate and cycle detection, frequency limits, forced exits, timeouts and failure isolation, proved with a 12-case chaos suite.

Think about a robot vacuum cleaner.

Most of the time it does its job. But sometimes it gets stuck. It bumps into the same chair leg again and again.

Or it goes back and forth between two rooms forever. Or one of its brushes jams and it keeps trying to spin it.

A good robot vacuum has safety rules built in. If it hits the same spot five times, it backs away.

If a brush jams, it switches that brush off and asks for help. If it has not moved for a while, it stops.

None of these rules make the vacuum smarter. They simply make sure that when it does get stuck, it fails safely instead of continuing until the battery runs out.

The main idea: Loop protection is a set of guards around the agent loop. Each guard looks for a specific kind of failure or repeated behavior and stops the run safely, even when the agent itself does not realize it is stuck.

A Real-World Example

An agent with no memory runs the tests and then reads web/format.js. On the next step, it has forgotten the test result, so it runs the tests again. Then it reads the file again.

Each step seems reasonable on its own. But the overall pattern is a loop:

  • Run tests
  • Read file
  • Run tests
  • Read file

With a step limit of 12, the agent wastes all 12 steps and never changes anything.

A cycle guard watches the run from the outside. When it sees:

  • tests
  • read
  • tests
  • read

with the same results each time, it stops the run at step 4 and reports the reason.

In simple words: The agent cannot always recognize that it is stuck. Guards can.

What You Will Learn

In this lesson, you will learn:

  1. The ways a loop gets stuck: circles, repeats, and stalled reasoning.
  2. How to detect duplicates, limit tool usage frequency, and force an exit.
  3. How to keep an agent stable when something fails: timeouts and failure isolation.
  4. How a chaos suite proves that your guards work.

The Agent Lab includes a Chaos tab. It runs 12 troubled agents against whatever guards you enable. With all 4 guards enabled, all 12 scenarios pass. Turn individual guards off and see which cases fail.

Four guards, four kinds of trouble. Each guard watches the loop from outside and stops one kind of stuck run.

Loop Detection

Circular Actions

A circular action pattern is a sequence of steps that repeats, such as:

  • A, B, A, B
  • A, B, C, A, B, C

No individual step repeats immediately, which makes these loops harder to spot.

Repeated Patterns

A repeated pattern is the simplest kind of loop: the same action produces the same result again and again.

You saw this in Lesson 10 with the agent that never changed its reasoning. It simply repeated the same behavior.

Stalled Reasoning

Stalled reasoning happens when the agent keeps thinking and acting, but nothing important changes:

  • No new file is found.
  • No new test result appears.
  • No new evidence is collected.

The progress check from Lesson 10 is designed to catch this situation.

Spot a repeat and a circle

function pattern(steps) {
  const n = steps.length;
  if (n >= 3 && steps[n - 1] === steps[n - 2] && steps[n - 2] === steps[n - 3]) return "repeat";
  if (n >= 4 && steps[n - 1] === steps[n - 3] && steps[n - 2] === steps[n - 4] && steps[n - 1] !== steps[n - 2]) return "circle";
  return "ok";
}

console.log(pattern(["tests:1 failed", "tests:1 failed", "tests:1 failed"]));
console.log(pattern(["tests:1 failed", "read:format.js", "tests:1 failed", "read:format.js"]));
console.log(pattern(["tests:1 failed", "read:format.js", "write:format.js", "tests:0 failed"]));

Expected output:

repeat
circle
ok

Each step here is recorded as tool:result. Comparing the result as well as the tool matters. Running the tests twice is fine if the result changed in between.

Loop Prevention

Duplicate Detection

Duplicate detection stops a run when the same action produces the same result 3 times in a row. There is nothing new to learn from a fourth attempt.

Duplicate guard

The agent runs the tests 3 times and gets the same result each time. The duplicate guard stops the run at step 3 instead of letting the agent use all 12 available steps.

Cycle guard

Tests, read, tests, read. No two consecutive steps are the same, so the duplicate guard does not trigger. The cycle guard recognizes the A, B, A, B pattern and stops the run at step 4.

Frequency Limits

A frequency limit caps how many times a particular tool may be used during a run. For example: at most 5 test runs. It catches longer loops that the other guards miss, such as A, B, C, A, B, C.

A frequency limit per tool

const limits = { run_tests: 4, write_file: 3 };
const used = {};

function allow(tool) {
  used[tool] = (used[tool] ?? 0) + 1;
  if (limits[tool] !== undefined && used[tool] > limits[tool]) return "BLOCKED: " + tool + " used " + used[tool] + " times (limit " + limits[tool] + ")";
  return "ok";
}

const run = ["run_tests", "read_file", "write_file", "run_tests", "write_file", "run_tests", "write_file", "run_tests", "write_file"];
for (const tool of run) {
  const r = allow(tool);
  if (r !== "ok") { console.log(r); break; }
}
console.log(JSON.stringify(used));

Expected output:

BLOCKED: write_file used 4 times (limit 3)
{"run_tests":4,"read_file":1,"write_file":4}

Four writes to fix one small bug is a sign that the agent keeps undoing its own work. The frequency limit stops the run, and a person can investigate why it is happening.

Forced Exits

A forced exit is the last line of defence: the step limit and the budgets from Lesson 10. It always works, but it is the most expensive guard because it allows the agent to use all of its available resources before stopping.

The other guards are cheaper because they stop a stuck run much earlier.

Execution Safety

Stability Checks

A stability check watches for early signs that the loop is going wrong before it becomes expensive or dangerous. Examples include the same error appearing repeatedly or a budget being consumed much faster than expected.

A stability check on the same error

const results = ["2 failed", "ERROR: disk full", "ERROR: disk full", "ERROR: disk full", "2 failed"];

function unstable(results, limit) {
  let streak = 0;
  for (let i = 0; i < results.length; i++) {
    streak = results[i].startsWith("ERROR") && results[i] === results[i - 1] ? streak + 1 : results[i].startsWith("ERROR") ? 1 : 0;
    if (streak >= limit) return "unstable at step " + (i + 1) + ": " + results[i];
  }
  return "stable";
}

console.log(unstable(results, 3));
console.log(unstable(results, 4));

Expected output:

unstable at step 4: ERROR: disk full
stable

The same error 3 times in a row is a sign that retrying is not helping. At that point, it is better to stop or try a different approach.

Failure Isolation

Failure isolation prevents one broken component from bringing down the entire run. If a tool crashes 2 times, the guard disables it.

Every later call receives a quick, clear error such as "run_tests is disabled". The agent does not have to wait for a third crash.

Failure isolation

This agent retries a crashed tool forever, with no limit of its own. After 2 crashes, the guard disables the test runner. At step 3, the agent sees "disabled", and at step 4, it stops and escalates.

Each crash message here is slightly different ("call 1", "call 2"), so the duplicate guard does not trigger. Failure isolation works because it counts crashes, not identical messages.

Runtime Protection

Runtime protection protects the loop from a single tool call that never finishes. A timeout guard stops any call that takes longer than a limit, such as 5 seconds, and returns an error instead.

Timeout guard

The first test run hangs. Without a guard, it would take 30 seconds. The timeout guard stops waiting after 5 seconds, and the agent's retry succeeds.

A timeout guard, as numbers

function callWithTimeout(takesMs, limitMs) {
  if (limitMs && takesMs > limitMs) return { waited: limitMs, result: "ERROR: timed out after " + limitMs / 1000 + " s" };
  return { waited: takesMs, result: "done" };
}

const noGuard = callWithTimeout(30000, 0);
const guard = callWithTimeout(30000, 5000);
console.log("no guard: waited " + noGuard.waited / 1000 + " s, " + noGuard.result);
console.log("guard: waited " + guard.waited / 1000 + " s, " + guard.result);

Expected output:

no guard: waited 30 s, done
guard: waited 5 s, ERROR: timed out after 5 s

The guard turns a slow hang into a quick error. The agent from Lesson 15 already knows how to handle errors.

The Chaos Suite

How do you know your guards work? You deliberately try to break the agent. A chaos suite is a collection of troubled runs, and every one of them must end safely.

The Agent Lab's suite contains 12 cases: 4 kinds of trouble across 3 versions of the bug.

TroubleWhat the agent doesThe guard that stops it
Repeats the same stepRuns the tests again and againDuplicate
Goes round in a circleTests, read, tests, readCycle
A tool call hangsWaits 30 s for one test runTimeout
A tool keeps crashingRetries a crashed tool foreverIsolation

A case passes if the run stops by itself, or by a guard, within 8 steps and 20 s. The step limit is 12, but reaching all 12 steps counts as a failure because the forced exit should never be the guard that saves the run.

Open the Chaos tab and press Run 12 scenarios:

  • All 4 guards enabled: 12 of 12 pass.
  • Any one guard disabled: its 3 related cases fail.
  • No guards enabled: 0 of 12 pass.

Which guard does each case need?

const cases = [
  { trouble: "repeat", needs: "duplicate" },
  { trouble: "circle", needs: "cycle" },
  { trouble: "hang", needs: "timeout" },
  { trouble: "crash", needs: "isolation" },
];
const bugs = 3;

function passed(guardsOn) {
  return cases.filter((c) => guardsOn.includes(c.needs)).length * bugs;
}

console.log("all guards: " + passed(["duplicate", "cycle", "timeout", "isolation"]) + " of 12");
console.log("no cycle guard: " + passed(["duplicate", "timeout", "isolation"]) + " of 12");
console.log("no guards: " + passed([]) + " of 12");

Expected output:

all guards: 12 of 12
no cycle guard: 9 of 12
no guards: 0 of 12

Easy Way to Remember: guards watch from outside the agent. A stuck agent cannot always see that it is stuck, so something else has to.

Don't rely on the step limit alone. It always stops the run, but only after every step and every token has already been spent.

Common Mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
Step limit only"Max steps will stop it."Add early guards too.Every stuck run burns the whole budget.
Matching the tool only"Two test runs is a loop."Match the tool and the result.Normal "test, fix, test" runs get stopped.
No cycle guard"The duplicate guard catches loops."Circular patterns need their own guard.A, B, A, B runs until the limit.
No timeouts"Tools always answer."Put a time limit on every call.One hang blocks the whole run.
Untested guards"The code looks right."Run a chaos suite.A guard fails the first time it is needed.

Exercises

Each exercise ends with a value on the last line. Press Check to compare it with the expected answer.

What to Remember

  • Guards watch the loop from outside. A stuck agent often cannot tell that it is stuck.
  • Use a duplicate guard for repeated actions, a cycle guard for circular patterns, and frequency limits for longer loops.
  • Use timeouts for tool calls that hang and failure isolation for tools that keep crashing.
  • The step limit is the last line of defence, not the main plan for stopping bad runs.
  • Prove that your guards work with a chaos suite. In the Agent Lab, all 12 scenarios pass only when every guard is enabled.

Next: Planning Agents, the first of six agents for real development work.