12 loopDetectionWindowSize = 10
13 loopDetectionMaxRepeats = 5
16// hasRepeatedToolCalls checks whether the agent is stuck in a loop by looking
17// at recent steps. It examines the last windowSize steps and returns true if
18// any tool-call signature appears more than maxRepeats times.
19func hasRepeatedToolCalls(steps []fantasy.StepResult, windowSize, maxRepeats int) bool {
20 if len(steps) < windowSize {
24 window := steps[len(steps)-windowSize:]
25 counts := make(map[string]int)
27 for _, step := range window {
28 sig := getToolInteractionSignature(step.Content)
33 if counts[sig] > maxRepeats {
41// getToolInteractionSignature computes a hash signature for the tool
42// interactions in a single step's content. It pairs tool calls with their
43// results (matched by ToolCallID) and returns a hex-encoded SHA-256 hash.
44// If the step contains no tool calls, it returns "".
45func getToolInteractionSignature(content fantasy.ResponseContent) string {
46 toolCalls := content.ToolCalls()
47 if len(toolCalls) == 0 {
51 // Index tool results by their ToolCallID for fast lookup.
52 resultsByID := make(map[string]fantasy.ToolResultContent)
53 for _, tr := range content.ToolResults() {
54 resultsByID[tr.ToolCallID] = tr
58 for _, tc := range toolCalls {
60 if tr, ok := resultsByID[tc.ToolCallID]; ok {
61 output = toolResultOutputString(tr.Result)
63 io.WriteString(h, tc.ToolName)
64 io.WriteString(h, "\x00")
65 io.WriteString(h, tc.Input)
66 io.WriteString(h, "\x00")
67 io.WriteString(h, output)
68 io.WriteString(h, "\x00")
70 return hex.EncodeToString(h.Sum(nil))
73// toolResultOutputString converts a ToolResultOutputContent to a stable string
74// representation for signature comparison.
75func toolResultOutputString(result fantasy.ToolResultOutputContent) string {
79 if text, ok := fantasy.AsToolResultOutputType[fantasy.ToolResultOutputContentText](result); ok {
82 if errResult, ok := fantasy.AsToolResultOutputType[fantasy.ToolResultOutputContentError](result); ok {
83 if errResult.Error != nil {
84 return errResult.Error.Error()
88 if media, ok := fantasy.AsToolResultOutputType[fantasy.ToolResultOutputContentMedia](result); ok {
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list