Skip to main content

Circular Fix Detection System - User Guide

Version: 1.0.0 Last Updated: December 22, 2025 Status: Production Ready


Overview

The Circular Fix Detection System automatically detects when an AI agent is repeatedly attempting similar fixes that have already failed, preventing infinite loops and suggesting alternative strategies.

Key Features:

  • Jaccard similarity-based pattern detection
  • Configurable thresholds and detection parameters
  • Alternative strategy suggestions
  • Visual similarity timeline
  • Automatic cleanup on success
  • Summary statistics and reporting

Quick Start

Basic Usage

from scripts.circular_fix_detector import CircularFixDetector, FixOutcome

# Initialize detector
detector = CircularFixDetector()

# Record a fix attempt
detector.record_attempt(
subtask_id="fix-login-bug",
approach="Updated authentication logic in login.py",
files_modified=["src/auth/login.py"],
outcome=FixOutcome.FAILED,
error_message="Authentication still failing"
)

# Check if next approach is circular
result = detector.is_circular_fix(
subtask_id="fix-login-bug",
current_approach="Modified authentication in login module"
)

if result.is_circular:
print(f"⚠️ Circular pattern detected!")
print(f"Suggested: {result.suggested_strategy.value}")
print(f"Reasoning: {result.reasoning}")

CLI Usage

# Record an attempt
python3 scripts/circular-fix-detector.py \
--subtask "fix-login-bug" \
--approach "Updated authentication logic" \
--files src/auth/login.py \
--outcome failed

# Check if circular
python3 scripts/circular-fix-detector.py \
--subtask "fix-login-bug" \
--approach "Modified authentication" \
--check

# Visualize history
python3 scripts/circular-fix-detector.py \
--subtask "fix-login-bug" \
--visualize

# Get summary statistics
python3 scripts/circular-fix-detector.py --summary

How It Works

1. Jaccard Similarity Algorithm

The system uses Jaccard similarity to compare fix approaches:

Similarity = |Intersection| / |Union|

Example:

Approach 1: "Fixed import statement in main.py"
Approach 2: "Updated import statement in main.py"

Tokens 1: {fixed, import, statement, main}
Tokens 2: {updated, import, statement, main}

Intersection: {import, statement, main} = 3 words
Union: {fixed, updated, import, statement, main} = 5 words

Similarity = 3/5 = 60%

2. Token Normalization

Before comparison, text is normalized:

  1. Lowercase all text
  2. Remove punctuation (except underscores in identifiers)
  3. Split on whitespace
  4. Remove stopwords (the, a, is, are, fix, update, etc.)
  5. Basic stemming (fixing → fix, updated → update)

Example:

Original: "Fixed the import statement by adding it to main.py"
Tokens: [fix, statement, add, main]
(Removed: the, import, by, it, to)

3. Circular Detection Logic

# Default configuration
SIMILARITY_THRESHOLD = 0.30 # 30% word overlap
MIN_SIMILAR_ATTEMPTS = 2 # At least 2 similar attempts
LOOKBACK_WINDOW = 5 # Check last 5 attempts

# Detection algorithm:
1. Get recent attempts (lookback window)
2. Calculate similarity to each
3. Count attempts above threshold
4. If count >= min_similar_attempts → CIRCULAR

4. Alternative Strategy Suggestions

When circular pattern detected:

ConditionStrategyExample
>5 attemptsescalate_human"Already tried 7 times. Human intervention recommended."
>70% similaritydifferent_method"Very high similarity. Try completely different approach."
Same 1-2 filesdifferent_files"Repeatedly modifying auth.py. Try different files."
Defaultanalyze_root_cause"Detected circular pattern. Recommend deeper root cause analysis."

Configuration

Configuration File

Located at config/circular-fix-config.json:

{
"detection_parameters": {
"similarity_threshold": 0.30,
"description": "Minimum Jaccard similarity (0.0-1.0)"
},
"pattern_detection": {
"min_similar_attempts": 2,
"description": "Minimum similar attempts to flag as circular"
},
"history_management": {
"lookback_window": 5,
"description": "Number of recent attempts to analyze"
},
"automation": {
"auto_cleanup_on_success": true,
"retention_days": 7
}
}

Tuning Guidelines

Similarity Threshold

"similarity_threshold": 0.30  # Default: 30%

// Tuning:
0.20-0.30 → Catches broad patterns (recommended)
0.30-0.50 → Moderate similarity detection
0.50-0.70 → Only very similar approaches
>0.70 → Almost identical only

Min Similar Attempts

"min_similar_attempts": 2  # Default: 2

// Tuning:
2 → Early detection (may have false positives)
3 → Balanced detection (recommended)
4-5 → Conservative (may miss patterns)

Lookback Window

"lookback_window": 5  # Default: 5

// Tuning:
3-5 → Recent context only (fast, focused)
5-10 → Moderate history (recommended)
10+ → Deep analysis (slower, comprehensive)

Integration Examples

With Recovery Manager

from scripts.circular_fix_detector import CircularFixDetector
from scripts.recovery_manager import RecoveryManager

detector = CircularFixDetector()
recovery = RecoveryManager()

# Attempt a fix
attempt = detector.record_attempt(
subtask_id=subtask.id,
approach=fix_description,
files_modified=modified_files,
outcome=FixOutcome.FAILED
)

# Check if circular
result = detector.is_circular_fix(subtask.id, next_approach)

if result.is_circular:
# Use suggested strategy
if result.suggested_strategy == AlternativeStrategy.ESCALATE_HUMAN:
recovery.escalate_to_human(subtask.id, result.reasoning)

elif result.suggested_strategy == AlternativeStrategy.DIFFERENT_FILES:
# Try fixing in different files
alternative_files = find_related_files(subtask)
next_fix = create_fix_for_files(alternative_files)

With Orchestrator

class TaskOrchestrator:
def __init__(self):
self.detector = CircularFixDetector()

def execute_subtask(self, subtask):
max_attempts = 5

for attempt_num in range(1, max_attempts + 1):
# Plan next approach
approach = self.plan_fix_approach(subtask)

# Check if circular
result = self.detector.is_circular_fix(subtask.id, approach)

if result.is_circular:
logger.warning(f"Circular pattern: {result.reasoning}")

# Apply suggested strategy
if result.suggested_strategy == AlternativeStrategy.DIFFERENT_METHOD:
approach = self.generate_alternative_approach(subtask)
elif result.suggested_strategy == AlternativeStrategy.ESCALATE_HUMAN:
return self.escalate_task(subtask)

# Execute fix
outcome = self.execute_fix(subtask, approach)

# Record attempt
self.detector.record_attempt(
subtask.id,
approach,
subtask.files,
outcome
)

if outcome == FixOutcome.SUCCESS:
return True

return False

Real-World Scenarios

Scenario 1: Import Error Loop

# Problem: Agent keeps trying to fix import by modifying import statement
# Solution: System detects circular pattern and suggests installing package

Attempt 1: "Added 'import requests' to file"
→ Failed: ModuleNotFoundError

Attempt 2: "Changed to 'from requests import *'"
→ Failed: ModuleNotFoundError
→ Similarity: 25%

Attempt 3: "Modified import to use absolute path"
→ Circular detected! (2 similar attempts)
→ Strategy: analyze_root_cause
→ Suggestion: "Check if requests is installed: pip install requests"

Scenario 2: Authentication Bug

# Problem: Agent repeatedly modifying same auth.py file
# Solution: System suggests trying different files

Attempt 1: "Fixed authentication in auth.py"
→ Failed

Attempt 2: "Updated login logic in auth.py"
→ Failed
→ Similarity: 40%

Attempt 3: "Modified auth validation in auth.py"
→ Failed
→ Similarity: 35%

Attempt 4: "Changed authentication flow in auth.py"
→ Circular detected!
→ Strategy: different_files
→ Suggestion: "Try modifying user.py, session.py, or database.py"

Scenario 3: Too Many Attempts

# Problem: Agent has tried 7 different approaches, all failed
# Solution: Escalate to human

Attempts 1-7: Various different approaches
→ All failed
→ Circular detected!
→ Strategy: escalate_human
→ Reasoning: "Already tried 7 times. Human intervention needed."

Visualization

Similarity Timeline

python3 scripts/circular-fix-detector.py --subtask fix-auth --visualize

Output:

=== Fix Attempt History for fix-auth ===

Total Attempts: 4

Attempt #1:
Approach: Fixed authentication error by updating login method...
Files: src/auth/login.py
Outcome: failed
Timestamp: 2025-12-22T10:00:00Z

Attempt #2:
Approach: Modified login authentication to handle errors...
Files: src/auth/login.py
Outcome: failed
Similarity to previous: [████████░░░░░░░░░░░░] 40.00%
Timestamp: 2025-12-22T10:05:00Z

Attempt #3:
Approach: Changed authentication flow in login module...
Files: src/auth/login.py
Outcome: failed
Similarity to previous: [██████░░░░░░░░░░░░░░] 30.00%
Timestamp: 2025-12-22T10:10:00Z

Attempt #4:
Approach: Updated error handling in login authentication...
Files: src/auth/login.py
Outcome: failed
Similarity to previous: [█████████░░░░░░░░░░░] 45.00%
Timestamp: 2025-12-22T10:15:00Z

Testing

Run Test Suite

# Comprehensive tests
python3 scripts/test-circular-detector.py

# Output:
# ✅ Jaccard similarity calculation
# ✅ Circular pattern detection
# ✅ Alternative strategy suggestions
# ✅ Real-world scenarios
# ✅ Automatic cleanup
# ✅ Summary statistics

Manual Testing

from scripts.circular_fix_detector import CircularFixDetector, FixOutcome

detector = CircularFixDetector()

# Test similarity calculation
similarity = detector.calculate_jaccard_similarity(
"Fixed the import statement",
"Updated import statement"
)
print(f"Similarity: {similarity:.2%}") # ~50%

# Test circular detection
for i in range(3):
detector.record_attempt(
"test-task",
f"Fix attempt {i+1} for the bug",
["bug.py"],
FixOutcome.FAILED
)

result = detector.is_circular_fix("test-task", "Another fix for the bug")
print(f"Circular: {result.is_circular}") # True

Best Practices

1. Tune Thresholds for Your Use Case

# For tight circular detection (avoid false positives):
config = {
"similarity_threshold": 0.40,
"min_similar_attempts": 3
}

# For loose detection (catch more patterns):
config = {
"similarity_threshold": 0.25,
"min_similar_attempts": 2
}

2. Record All Attempts

# Always record attempts, even successes
detector.record_attempt(
subtask_id,
approach,
files,
outcome # FAILED, SUCCESS, PARTIAL, SKIPPED
)

3. Act on Suggestions

result = detector.is_circular_fix(subtask_id, approach)

if result.is_circular:
# Don't ignore! Apply suggested strategy
if result.suggested_strategy == AlternativeStrategy.DIFFERENT_METHOD:
approach = generate_alternative()
elif result.suggested_strategy == AlternativeStrategy.ESCALATE_HUMAN:
notify_human(result.reasoning)

4. Monitor and Adjust

# Periodically review effectiveness
summary = detector.get_summary()

print(f"Circular detections: {summary['circular_count']}")
print(f"Average attempts: {summary['average_attempts_per_subtask']}")

# Tune if needed
if summary['average_attempts_per_subtask'] > 4:
# Lower threshold to detect earlier
config['similarity_threshold'] = 0.25

Troubleshooting

Issue: Too Many False Positives

Symptom: System flagging different approaches as circular

Solution:

// Increase threshold or min_similar_attempts
{
"similarity_threshold": 0.40, // Up from 0.30
"min_similar_attempts": 3 // Up from 2
}

Issue: Missing Circular Patterns

Symptom: Agent loops without detection

Solution:

// Decrease threshold and increase lookback
{
"similarity_threshold": 0.25, // Down from 0.30
"lookback_window": 10 // Up from 5
}

Issue: Stopwords Too Aggressive

Symptom: Important technical terms being removed

Solution:

# Edit data/stopwords.txt
# Remove domain-specific terms that should NOT be ignored
# Example: Remove 'import', 'module', 'package' for import errors

API Reference

CircularFixDetector

Methods

__init__(config_path: Optional[Path] = None)

Initialize detector with optional custom config.

detector = CircularFixDetector()
# or
detector = CircularFixDetector(config_path=Path("custom-config.json"))

calculate_jaccard_similarity(text1: str, text2: str) -> float

Calculate similarity between two text strings.

similarity = detector.calculate_jaccard_similarity(
"Fixed the bug",
"Updated the bug fix"
)
# Returns: 0.33 (33% similarity)

record_attempt(...) -> FixAttempt

Record a fix attempt.

attempt = detector.record_attempt(
subtask_id="task-123",
approach="Fixed by updating import",
files_modified=["main.py"],
outcome=FixOutcome.FAILED,
error_message="Still failing" # Optional
)

is_circular_fix(subtask_id: str, current_approach: str) -> CircularDetectionResult

Check if current approach would be circular.

result = detector.is_circular_fix(
"task-123",
"Modified import statement"
)

if result.is_circular:
print(result.suggested_strategy.value)
print(result.reasoning)

get_visualization(subtask_id: str) -> str

Get text visualization of attempt history.

viz = detector.get_visualization("task-123")
print(viz)

get_summary() -> dict

Get summary statistics.

summary = detector.get_summary()
print(summary['total_attempts'])
print(summary['outcomes'])

cleanup_subtask(subtask_id: str)

Remove history for completed subtask.

detector.cleanup_subtask("task-123")

Enums

FixOutcome

FixOutcome.FAILED   # Fix attempt failed
FixOutcome.SUCCESS # Fix succeeded
FixOutcome.PARTIAL # Partially successful
FixOutcome.SKIPPED # Attempt skipped

AlternativeStrategy

AlternativeStrategy.DIFFERENT_FILES       # Try different files
AlternativeStrategy.DIFFERENT_METHOD # Use different approach
AlternativeStrategy.ESCALATE_HUMAN # Need human help
AlternativeStrategy.SKIP_SUBTASK # Skip this subtask
AlternativeStrategy.ANALYZE_ROOT_CAUSE # Deeper analysis needed


Last Updated: December 22, 2025 Version: 1.0.0 Status: Production Ready Compliance: CODITECT Documentation Standard v1.0.0