Docs / Observability / Loops

loops

Repetitive agent behavior: too many steps, identical LLM calls fired in a row, or near-identical calls clustered together. The loops detector covers three sub-patterns that share a mental model ("the agent is stuck instead of making progress") but answer different specific questions. All three sub-signatures live under one failure class so they cluster as one chip in the dashboard.

What this detector catches

Four sub-signatures:

  • step_count_<bucket> — total telemetry events in a single execution exceeded a healthy-agent threshold. Bucketed: step_count_10+ (10-49 events), 50+ (50-99), 100+ (100-499), 500+ (500-4,999), 5000+ (5,000+). The right bucket usually tells you which scale of remediation you need: the 10-49 range is a retry loop, the 5000+ range is a runaway.
  • identical_call_<hash> — three or more llm_call events in one execution with the exact same (model, user_message) pair. Mesedi hashes (model + user_message) per call and flags executions where the same hash recurs 3+ times. The most reliable signal because exact-match is unambiguous.
  • similar_call_<hash> — a cluster of 3+ llm_callevents whose user_message text falls within a cosine-distance threshold (default 0.20). Catches the case where the prompt drifts slightly each iteration (timestamps, counters, "try again"-style reformulations) but the underlying request is the same.
  • time_budget_<bucket> — the execution's wall-clock duration exceeded the configured time budget. Bucketed: time_budget_1s+, _10s+, _60s+, _10m+, _1h+. Catches the stuck-but-not-step-bound case where a single LLM call or tool call hangs.

Example failures it groups

  • Retry logic without backoff or jitter — the agent retries a failed call by re-running the exact same prompt (identical_call).
  • A reasoning loop where the model keeps reaching the same conclusion and the agent keeps re-asking with only a counter increment (similar_call).
  • A long-running planning agent that emits hundreds of tool_calls + checkpoints (step_count_500+).
  • A "try-then-validate-then-retry" loop where the validator keeps rejecting and the agent keeps re-prompting (similar_call OR identical_call depending on whether the prompt changes).

What to do when it fires

The failure-group detail page shows a Playbook specific to the sub-signature that fired (one of 4 markdown files: identical_call.md / similar_call.md / step_count.md / time_budget.md). Common remediation patterns across all 4: identical_call almost always means a missing backoff or a deterministic-failure retry path — wrap the LLM call with retry-with-backoff and a max-attempt cap; similar_callmeans the prompt is drifting on a dimension that doesn't advance the agent's progress (often a counter or timestamp embedded in the system prompt) — strip the drifting dimension or cap the cluster at a max-iteration count; step_count means the agent has no termination condition — add a hard step budget and surface budget-exceeded as a structured error so the agent can escalate to a human instead of continuing; time_budget is the wall-clock equivalent — either a single call is hanging (add per-call timeouts and retries with a smaller window) or the cumulative work exceeds what the project budget allows (raise time_budget_ms in Settings if the workload is intentionally long, or split into smaller executions).

When NOT to investigate

  • Long-running research agents that legitimately need 100+ steps. Raise step_count_threshold (and time_budget_ms if the project is also tripping the wall-clock budget) — see Tunable thresholds below.
  • Agents with intentional re-asks where each iteration adds new context (RAG with progressive retrieval, multi-step planners). These produce similar_call clusters that are correct behavior, not failure — lower similar_call_distance_threshold (a tighter cluster needs more lexical agreement to fire) or raise similar_call_min_cluster_size to reduce false positives.
  • Bulk-processing agents where the same prompt template applies to many items in a row (each call is genuinely independent work, just structurally similar). identical_call will fire here; consider whether each item should run in its own execution rather than batching N into one.

Tunable thresholds + allowlist

All four sub-signatures have per-project tunables (none tier-capped except time_budget_ms). Tune via the Detector thresholds primitive (the first four) or the dedicated GET/PUT /me/time-budget-config endpoint (the last one):

  • step_count_threshold (default 10; bounds [2, 10000]). Total events per execution above which loops/step_count fires.
  • identical_call_min_repeats (default 3; bounds [2, 100]). Distinct repeats of the same (model, user_message) hash before identical_call fires.
  • similar_call_distance_threshold (default 0.20; bounds [0.05, 0.50]). Cosine distance below which two user_messages count as similar.
  • similar_call_min_cluster_size (default 3; bounds [2, 100]). Minimum cluster size before similar_call fires.
  • time_budget_ms (default 60_000; tier-capped via GET /me/time-budget-config — Hobby / Team / Enterprise have distinct max values). Wall- clock duration above which time_budget fires.

The loops detector does NOT support the per-project Allowlist primitive today (only crashes, tool_failures, and validator_failures do). To silence loops in specific executions, use the dashboard's severity routing to downgrade the failure class to info-tier, or tune the thresholds above.

Related docs

  • token_waste — the parallel signal on the same underlying pattern (identical/similar LLM calls). token_waste groups by prefix-hash and surfaces under its own failure class; loops surfaces the same data under the "repetitive behavior" framing. Both fire when applicable — by design, they answer different questions about the same calls.
  • semantic_loop — the "checkpoint state revisited" detector, a sibling pattern that operates on customer-emitted checkpoint events rather than on LLM calls.