Class: RobotLab::DoomLoopDetector

Inherits:
Object
  • Object
show all
Defined in:
lib/robot_lab/doom_loop_detector.rb

Overview

Detects and interrupts tool-call doom loops inside the LLM agent loop.

A doom loop occurs when the LLM repeatedly calls the same tool(s) in a cycle — either identical consecutive calls (A, A, A) or a repeating multi-step pattern (A, B, C, A, B, C, A, B, C).

When a doom loop is detected the warning is embedded in the tool result so the LLM sees it in its next context window and can self-correct.

Examples:

Basic usage

detector = DoomLoopDetector.new(threshold: 3)
detector.track("search")
detector.track("search")
detector.track("search")
detector.doom_loop?  # => true
detector.warning_message
# => "Tool 'search' has been called 3 times consecutively..."

Constant Summary collapse

DEFAULT_THRESHOLD =
3
MAX_PERIOD =
10

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(threshold: DEFAULT_THRESHOLD) ⇒ DoomLoopDetector

Returns a new instance of DoomLoopDetector.



27
28
29
30
# File 'lib/robot_lab/doom_loop_detector.rb', line 27

def initialize(threshold: DEFAULT_THRESHOLD)
  @threshold = threshold
  @sequence  = []
end

Instance Attribute Details

#sequenceObject (readonly)

Returns the value of attribute sequence.



25
26
27
# File 'lib/robot_lab/doom_loop_detector.rb', line 25

def sequence
  @sequence
end

Instance Method Details

#doom_loop?Boolean

Returns:

  • (Boolean)


36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/robot_lab/doom_loop_detector.rb', line 36

def doom_loop?
  seq = @sequence
  return false if seq.length < @threshold

  # Consecutive identical calls: A, A, A
  tail = seq.last(@threshold)
  return true if tail.uniq.length == 1

  # Cyclic multi-step patterns: A,B,C, A,B,C, A,B,C
  max_period = [MAX_PERIOD, seq.length / @threshold].min
  (2..max_period).each do |period|
    window = @threshold * period
    next if seq.length < window

    chunk = seq.last(window)
    pattern = chunk.first(period)
    return true if chunk == pattern * @threshold
  end

  false
end

#resetObject



58
59
60
# File 'lib/robot_lab/doom_loop_detector.rb', line 58

def reset
  @sequence = []
end

#track(tool_name) ⇒ Object



32
33
34
# File 'lib/robot_lab/doom_loop_detector.rb', line 32

def track(tool_name)
  @sequence << tool_name.to_s
end

#warning_messageObject



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/robot_lab/doom_loop_detector.rb', line 62

def warning_message
  seq = @sequence
  return "" if seq.empty?

  period = detect_period(seq)
  if period == 1
    tool = seq.last
    "Tool '#{tool}' has been called #{@threshold}+ times consecutively with " \
      "no progress. You appear to be stuck in a loop. Try a fundamentally " \
      "different approach, use a different tool, or ask for clarification."
  else
    pattern = seq.last(period).join("")
    "Tool call pattern [#{pattern}] has repeated #{@threshold}+ times. " \
      "You appear to be stuck in a loop. Try a fundamentally different " \
      "approach, use a different tool, or ask for clarification."
  end
end