Loop detection

Goa has several independent safeguards for runaway model behaviour. They do not share a single counter, so changing one setting does not change the others. Values below are configured under execution in YAML (or through /config where noted). A value of 0 means the documented default unless stated otherwise.

Tool-call repeat detector

This detector watches identical tool calls (same tool and arguments) and reports a loop when they repeat consecutively.

Setting Default Purpose
loop_warning 10 Same tool-call streak that produces a warning.
loop_interrupt 15 Same tool-call streak that interrupts execution. Must exceed loop_warning.
max_tool_repeat_total off Maximum identical calls in a turn, regardless of spacing.
max_tool_repeat_consecutive 2 Maximum consecutive identical calls in a turn.
max_tool_calls 200 Maximum occurrences of one identical call in the rolling window. This is not a total tool-call allowance.
tool_call_limit_reset_window 10 Number of recent calls considered by the rolling-window guard.
disable_tool_budget false Disables the agent tool-call budget guards. It does not disable the core loop detector.

The interactive path is /config → Loop detection → Thresholds. The same values can be set with /config:set:execution.<key>:<value>.

Consecutive tool-call rounds

max_consecutive_tool_rounds (default 15) limits consecutive LLM rounds that end in tool calls. It catches loops where every call is different, so duplicate-call guards never fire. When reached, Goa injects an internal recovery instruction asking the model to answer with the information already gathered. This is the source of the “per-turn tool-call round limit” message.

It is available in /config → Loop detection → Thresholds. Set it to 0 to disable it. This is separate from max_tool_calls: the embedded default is 200, and changing that duplicate-call limit does not raise this round limit.

Thinking/reasoning detector

The thinking detector tracks repeated significant reasoning lines and low-diversity reasoning windows within one turn.

Setting Purpose
disable_thinking_loop_detection Persistently disable thinking-loop detection.
disable_thinking_stall_detection Persistently disable the reasoning-only stall watchdog.
thinking_stall_warn_seconds Delay before a reasoning-only stall warning.
thinking_stall_stop_seconds Delay before stopping a reasoning-only stall.

Thinking-loop thresholds use the runtime loop-detector defaults and are not currently separate YAML fields. Session-only overrides are available with /config:temp:think_loop_detection:on|off and /config:temp:thinking_stall_detection:on|off.

Streaming text detector

This detector catches repeated text in the assistant's streamed output, even when no tool is called.

Setting Default Purpose
stream_loop_max_repeats 5 Repeated blocks required before stopping the stream.
stream_loop_min_period 50 Minimum repeated unit length in characters. Values below 8 are rejected.
stream_loop_max_strikes 3 Loop detections before stopping the turn.
stream_loop_reset_after 10 Clean messages/tool calls before resetting strikes.
disable_stream_loop_detection false Persistently disable this detector.

Session-only override: /config:temp:stream_loop_detection:on|off.

Loop auto-resume

By default, when a loop detector (thinking or tool) interrupts the turn, the agent stops and waits for the user. Loop auto-resume keeps the stop but automatically sends a follow-up message so the agent resumes instead of staying idle.

Setting Default Description
loop_auto_resume false Send a follow-up message after a loop-detector stop so the agent resumes.
loop_auto_resume_message loop detected and you were stopped - resume now The message injected as a user turn on auto-resume.
loop_auto_resume_max 3 Maximum consecutive loop-triggered auto-resumes before giving up. Each genuine user turn resets the counter, so a stuck agent cannot resume forever.

The interactive path is /config → Loop detection → Loop auto-resume, which exposes the on/off toggle and the resume-message editor. The same values can be set with /config:set:execution.loop_auto_resume:true and /config:set:execution.loop_auto_resume_message:<text>.

If you typed steering input while the looped turn was still running, that steering is delivered instead of the automated resume — your message takes precedence.

Cross-turn repeated-response guardrail

This detector watches the assistant's last message of each turn and stops the session when the same response repeats across consecutive turns. Goal continuation turns deliberately keep its counters, so a driver loop latches; a genuine user turn resets it.

Setting Default Purpose
runaway_loop_max_repeats 2 Repeats after the first occurrence before the session stops.

Progress overrides repetition: a turn whose tool calls produced non-error results never accumulates a strike, whatever its text. A truly empty response ((empty response): no text, no reasoning, no tool calls) is judged against the whole turn — if that turn executed successful tool calls, the emptiness is a provider stall (an empty stop right after tool results), not the model repeating itself, and the turn reports a silent stop instead (see the goal pause reason “stopped without producing a reply”). Only turns that produced nothing at all still latch.

Configuration examples

execution:
  loop_warning: 10
  loop_interrupt: 15
  max_tool_repeat_total: 15
  max_tool_repeat_consecutive: 2
  max_tool_calls: 200
  tool_call_limit_reset_window: 10
  max_consecutive_tool_rounds: 15
  stream_loop_max_repeats: 5
  runaway_loop_max_repeats: 2
  loop_auto_resume: false
  loop_auto_resume_message: "loop detected and you were stopped - resume now"
  loop_auto_resume_max: 3

If a warning appears sooner than expected, identify which detector produced it. In particular, max_tool_calls controls duplicate calls in a rolling window; it does not control consecutive tool-call rounds.