bots-garden/mini-mepublic Fork 0
main
Commits
Clone
git clone https://git.rickub.com/bots-garden/mini-me.git
git clone ssh://git@rickub.com/bots-garden/mini-me.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

💾 Saved. d722711 · on main · k33g · 3h ago
detector.go · 77 lines · 2.1 KBGo Blame HistoryRaw
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// Package detector provides a mechanism to detect repetitive patterns in agent actions.
package detector

import (
	"sync"
)

// Action represents a single step taken by the agent.
type Action struct {
	ToolName string
	Input    string
	Output   string
}

// LoopDetector tracks the history of actions to detect repetitive cycles.
type LoopDetector struct {
	history    []Action
	historyMu  sync.Mutex
	maxHistory int
	threshold  int
}

// NewLoopDetector creates a new detector with a given history limit and threshold.
func NewLoopDetector(maxHistory, threshold int) *LoopDetector {
	return &LoopDetector{
		history:    make([]Action, 0),
		maxHistory: maxHistory,
		threshold:  threshold,
	}
}

// Record adds an action to the history and returns true if a loop is detected.
func (d *LoopDetector) Record(action Action) bool {
	d.historyMu.Lock()
	defer d.historyMu.Unlock()

	// Check if the current action (tool + input + output) has occurred consecutively.
	count := 0
	for i := len(d.history) - 1; i >= 0; i-- {
		if d.history[i].ToolName == action.ToolName &&
			d.history[i].Input == action.Input &&
			d.history[i].Output == action.Output {
			count++
			if count >= d.threshold {
				return true
			}
		} else {
			break
		}
	}

	// Append the new action.
	d.history = append(d.history, action)
	if len(d.history) > d.maxHistory {
		d.history = d.history[1:]
	}

	return false
}

// Reset forgets every recorded action and keeps the settings. It is what the
// /new command calls: a new session is a new task, and a command the model
// repeated in the previous one must not be flagged as a loop in this one.
//
//	d := detector.NewLoopDetector(10, 3)
//	// ... a session's worth of Record calls ...
//	d.Reset() // start the count again
func (d *LoopDetector) Reset() {
	d.historyMu.Lock()
	defer d.historyMu.Unlock()
	d.history = d.history[:0]
}

// LoopError returns a standardized error message when a loop is detected.
func (d *LoopDetector) LoopError() string {
	return "[LOOP_DETECTED: You have attempted the same action multiple times with the same result. Please try a different approach or provide more information.]"
}