calute.runtime.loop_detection#

Tool-loop detection for Calute.

Detects repetitive tool call patterns and prevents infinite loops:

  1. Same-call repetition: The same tool with the same arguments is called N times in a row without meaningful progress.

  2. Ping-pong detection: Two tools alternate back and forth (A→B→A→B).

  3. Total iteration cap: Hard limit on total tool calls per session turn.

Each detector emits observable events/log entries and can be configured with warning and critical thresholds.

class calute.runtime.loop_detection.LoopDetectionConfig(same_call_warning: int = 3, same_call_critical: int = 5, pingpong_warning: int = 4, pingpong_critical: int = 6, max_tool_calls_per_turn: int = 25, enabled: bool = True)[source]#

Bases: object

Configuration for loop detection thresholds.

same_call_warning#

Consecutive identical calls before warning.

Type

int

same_call_critical#

Consecutive identical calls before blocking.

Type

int

pingpong_warning#

Alternation count before warning.

Type

int

pingpong_critical#

Alternation count before blocking.

Type

int

max_tool_calls_per_turn#

Hard cap on total tool calls in one turn.

Type

int

enabled#

Master switch for loop detection.

Type

bool

enabled: bool = True#
max_tool_calls_per_turn: int = 25#
pingpong_critical: int = 6#
pingpong_warning: int = 4#
same_call_critical: int = 5#
same_call_warning: int = 3#
class calute.runtime.loop_detection.LoopDetector(config: calute.runtime.loop_detection.LoopDetectionConfig | None = None)[source]#

Bases: object

Stateful loop detector for a single agent turn.

Create a new LoopDetector for each turn (or session). Call record_call after every tool invocation. The detector returns a LoopEvent with severity OK, WARNING, or CRITICAL.

Example

>>> detector = LoopDetector()
>>> event = detector.record_call("search", {"q": "hello"})
>>> event.severity
<LoopSeverity.OK: 'ok'>
add_listener(callback) None[source]#

Register a callable invoked with each emitted LoopEvent.

Listeners receive every WARNING and CRITICAL event. Exceptions raised by listeners are caught and logged without interrupting detection.

Parameters

callback – A callable that accepts a single LoopEvent argument.

property call_count: int#

Return the total number of tool calls recorded so far.

Returns

The count of tool calls stored in the internal history.

record_call(tool_name: str, arguments: dict | str | None = None) LoopEvent[source]#

Record a tool call and check for loop patterns.

Appends the call to the internal history and runs all detection checks in order: max-calls cap, same-call repetition, and ping-pong alternation. The first non-OK result is returned immediately.

Parameters
  • tool_name – Name of the tool being invoked.

  • arguments – The call arguments as a dict, a JSON string, or None. Arguments are hashed for comparison; the raw values are not stored.

Returns

A LoopEvent describing the detection result. Severity CRITICAL means the call should be blocked; WARNING means it should be logged but allowed; OK means no pattern was detected.

reset() None[source]#

Reset detector state for a new turn.

Clears the call history so the detector can be reused across turns without creating a new instance. Registered listeners are preserved.

class calute.runtime.loop_detection.LoopEvent(severity: LoopSeverity, pattern: str, tool_name: str, details: str, call_count: int = 0)[source]#

Bases: object

Emitted when a loop pattern is detected or when a call is checked.

Carries the severity, pattern type, and descriptive details for every tool call check performed by the LoopDetector.

severity#

The severity level of the detection result.

Type

calute.runtime.loop_detection.LoopSeverity

pattern#

The type of loop pattern detected. One of "same_call", "pingpong", "max_calls", "none", or "disabled".

Type

str

tool_name#

Name of the tool that triggered the event.

Type

str

details#

Human-readable description of the detection result.

Type

str

call_count#

The relevant repetition or alternation count that triggered the event. Defaults to 0 for OK events.

Type

int

call_count: int = 0#
details: str#
pattern: str#
severity: LoopSeverity#
tool_name: str#
class calute.runtime.loop_detection.LoopSeverity(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)[source]#

Bases: Enum

Severity level for a loop detection event.

OK#

No loop pattern detected; tool call proceeds normally.

WARNING#

A potential loop pattern has been detected but the call is still allowed. A log entry is emitted for observability.

CRITICAL#

A confirmed loop pattern has been detected and the call should be blocked to prevent infinite execution.

CRITICAL = 'critical'#
OK = 'ok'#
WARNING = 'warning'#
exception calute.runtime.loop_detection.ToolLoopError(event: LoopEvent)[source]#

Bases: Exception

Raised when a tool call is blocked due to loop detection.

Contains the LoopEvent that triggered the block, allowing callers to inspect the pattern type, severity, and details.

event#

The LoopEvent that caused the tool call to be blocked.