zeroclaw-labs/zeroclaw · error

Agent loop aborted by loop detector: {msg}

Error message

Agent loop aborted by loop detector: {msg}

What it means

collect_tool_results feeds every successful tool outcome into loop_detector.record(tool, args, output). When detection escalates past Warning (advice appended to history) and Block (output replaced with feedback) to Break — the circuit breaker — the whole turn aborts with the detector's message. This is the last-resort protection against runaway repetitive tool calling.

Source

Thrown at crates/zeroclaw-runtime/src/agent/turn/results_collect.rs:120

                        format!("[Loop Detection — BLOCKED] {msg}"),
                    );
                }
                crate::agent::loop_detector::LoopDetectionResult::Break(msg) => {
                    ::zeroclaw_log::record!(
                        WARN,
                        ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)
                            .with_category(::zeroclaw_log::EventCategory::Tool)
                            .with_outcome(::zeroclaw_log::EventOutcome::Failure)
                            .with_attrs(::serde_json::json!({
                                "model": model,
                                "iteration": iteration + 1,
                                "tool": tool_name,
                                "message": msg,
                                "trace_id": turn_id,
                            })),
                        "loop_detector_circuit_breaker"
                    );
                    anyhow::bail!("Agent loop aborted by loop detector: {msg}");
                }
            }
        }
        let canonical_output =
            canonicalize_tool_result_media_markers_for(&tool_name, &outcome.output);
        let mut result_output = truncate_tool_result(&canonical_output, max_tool_result_chars);
        // Append HMAC receipt to tool result when receipts are enabled
        if let Some(ref receipt) = outcome.receipt {
            ::zeroclaw_log::record!(
                DEBUG,
                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Note)
                    .with_category(::zeroclaw_log::EventCategory::Tool)
                    .with_attrs(::serde_json::json!({"tool": tool_name, "receipt": receipt})),
                "Tool receipt generated"
            );
            result_output = format!("{result_output}\n\n[receipt: {receipt}]");
            if let Some(store) = collected_receipts
                && let Ok(mut v) = store.lock()

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Read the detector msg plus the earlier Warning/Block log entries to identify the repeating tool and arguments
  2. Fix the tool output or prompt so the model can act on the result (clearer text, structured output, explicit completion markers)
  3. Switch to a stronger model or add explicit stop conditions to the system prompt
  4. Only after fixing the repetition, consider tuning loop-detector thresholds

Example fix

// before: system prompt gives no completion criteria
"Keep working on the task."

// after: bound the loop explicitly
"Attempt each step once. If a tool result repeats or you already have the answer, summarize findings and stop."
Defensive patterns

Strategy: validation

Validate before calling

fn is_repetitive(calls: &[ToolCall], window: usize) -> bool {
    let key = |c: &ToolCall| (c.name.clone(), c.arguments.to_string());
    if calls.len() < window { return false; }
    let last = key(&calls[calls.len() - 1]);
    calls[calls.len() - window..].iter().filter(|c| key(c) == last).count() >= window
}

Try / catch

match agent.run_turn(req).await {
    Err(ref e) if e.to_string().starts_with("Agent loop aborted by loop detector") => {
        // inspect which tool/args repeated from logs; fix prompt/tool output before retry
    }
    other => other,
}

Prevention

When it happens

Trigger: The model repeatedly issues effectively identical tool calls (same name and arguments), ignores injected Warning and Block feedback messages, and keeps going; only successful outcomes are recorded, so a pattern needs succeeding calls to trip the breaker.

Common situations: Model fixated on a tool whose output it misreads (unparseable format, truncated result); prompts with no termination criterion; under-powered models looping on one step; tools whose errors look like success.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/a6bb395a99fd1890. Report an issue: GitHub.