| 💾 Saved. d722711 k33g 4h ago | 1 | // Package detector provides a mechanism to detect repetitive patterns in agent actions. |
| 2 | package detector |
| 3 | |
| 4 | import ( |
| 5 | "sync" |
| 6 | ) |
| 7 | |
| 8 | // Action represents a single step taken by the agent. |
| 9 | type Action struct { |
| 10 | ToolName string |
| 11 | Input string |
| 12 | Output string |
| 13 | } |
| 14 | |
| 15 | // LoopDetector tracks the history of actions to detect repetitive cycles. |
| 16 | type LoopDetector struct { |
| 17 | history []Action |
| 18 | historyMu sync.Mutex |
| 19 | maxHistory int |
| 20 | threshold int |
| 21 | } |
| 22 | |
| 23 | // NewLoopDetector creates a new detector with a given history limit and threshold. |
| 24 | func NewLoopDetector(maxHistory, threshold int) *LoopDetector { |
| 25 | return &LoopDetector{ |
| 26 | history: make([]Action, 0), |
| 27 | maxHistory: maxHistory, |
| 28 | threshold: threshold, |
| 29 | } |
| 30 | } |
| 31 | |
| 32 | // Record adds an action to the history and returns true if a loop is detected. |
| 33 | func (d *LoopDetector) Record(action Action) bool { |
| 34 | d.historyMu.Lock() |
| 35 | defer d.historyMu.Unlock() |
| 36 | |
| 37 | // Check if the current action (tool + input + output) has occurred consecutively. |
| 38 | count := 0 |
| 39 | for i := len(d.history) - 1; i >= 0; i-- { |
| 40 | if d.history[i].ToolName == action.ToolName && |
| 41 | d.history[i].Input == action.Input && |
| 42 | d.history[i].Output == action.Output { |
| 43 | count++ |
| 44 | if count >= d.threshold { |
| 45 | return true |
| 46 | } |
| 47 | } else { |
| 48 | break |
| 49 | } |
| 50 | } |
| 51 | |
| 52 | // Append the new action. |
| 53 | d.history = append(d.history, action) |
| 54 | if len(d.history) > d.maxHistory { |
| 55 | d.history = d.history[1:] |
| 56 | } |
| 57 | |
| 58 | return false |
| 59 | } |
| 60 | |
| 61 | // Reset forgets every recorded action and keeps the settings. It is what the |
| 62 | // /new command calls: a new session is a new task, and a command the model |
| 63 | // repeated in the previous one must not be flagged as a loop in this one. |
| 64 | // |
| 65 | // d := detector.NewLoopDetector(10, 3) |
| 66 | // // ... a session's worth of Record calls ... |
| 67 | // d.Reset() // start the count again |
| 68 | func (d *LoopDetector) Reset() { |
| 69 | d.historyMu.Lock() |
| 70 | defer d.historyMu.Unlock() |
| 71 | d.history = d.history[:0] |
| 72 | } |
| 73 | |
| 74 | // LoopError returns a standardized error message when a loop is detected. |
| 75 | func (d *LoopDetector) LoopError() string { |
| 76 | return "[LOOP_DETECTED: You have attempted the same action multiple times with the same result. Please try a different approach or provide more information.]" |
| 77 | } |