// Loop detectors — detects repetitive patterns in assistant output // that indicate a model is stuck in a loop. export interface LoopDetectionResult { isLoop: boolean; reason?: string; confidence: number; // 0-1 } const REPEATED_PHRASE_MIN_COUNT = 4; const REPEATED_TOOL_MIN_COUNT = 3; export function detectContentRepeat(messages: string[]): LoopDetectionResult { if (messages.length < REPEATED_PHRASE_MIN_COUNT) { return { isLoop: false, confidence: 0 }; } const recent = messages.slice(-REPEATED_PHRASE_MIN_COUNT); const unique = new Set(recent); if (unique.size === 1) { return { isLoop: true, reason: `Same content repeated ${REPEATED_PHRASE_MIN_COUNT} times`, confidence: 0.9, }; } if (unique.size <= 2 && recent.length >= 4) { return { isLoop: true, reason: 'Content oscillating between two variants', confidence: 0.7, }; } return { isLoop: false, confidence: 0 }; } export function detectToolLoop(toolNames: string[]): LoopDetectionResult { if (toolNames.length < REPEATED_TOOL_MIN_COUNT) return { isLoop: false, confidence: 0 }; const recent = toolNames.slice(-REPEATED_TOOL_MIN_COUNT); const unique = new Set(recent); if (unique.size === 1) { return { isLoop: true, reason: `Same tool "${recent[0]}" called ${REPEATED_TOOL_MIN_COUNT} times consecutively`, confidence: 0.85, }; } return { isLoop: false, confidence: 0 }; } export function detectDoomLoop( messages: string[], toolNames: string[], ): LoopDetectionResult { const contentResult = detectContentRepeat(messages); if (contentResult.isLoop) return contentResult; const toolResult = detectToolLoop(toolNames); if (toolResult.isLoop) return toolResult; return { isLoop: false, confidence: 0 }; }