How Evolver's Signal Extraction and Deduplication Process Prevents Repair Loops

Evolver prevents repair loops by analyzing recent event history to detect consecutive repair attempts, suppressing repetitive error signals, and injecting loop-breaking signals that force the system toward fundamentally different solutions.

The EvoMap/evolver repository implements a sophisticated monitoring layer that continuously extracts signals from logs, user input, and session transcripts. By combining multi-layer extraction with history-aware deduplication, Evolver's signal extraction and deduplication process identifies when the system is stuck in a cycle of failed repairs and triggers architectural changes rather than repeated fixes.

Understanding the Signal Extraction Pipeline

Evolver gathers raw signals through three distinct layers: regex pattern matching, keyword scoring, and optional LLM analysis. These layers feed into a unified processing pipeline defined in src/gep/signals.js.

The extraction function merges outputs from all three layers into a single deduplicated set before applying history-aware filters:

// src/gep/signals.js – extractSignals()
var regexSignals = _extractRegex(corpus, lower, errorHit);
var scoreSignals = _extractKeywordScore(lower);
var llmSignals   = _extractLLM(corpus);
var signals = _mergeSignals(regexSignals, scoreSignals, llmSignals);

This merge guarantees no duplicate strings enter the post-processing stage, ensuring that identical error signatures appearing across different extraction layers do not amplify noise in the signal set.

Analyzing Recent History for Loop Detection

Building the History Profile

The core loop detection mechanism relies on analyzeRecentHistory() in src/gep/signals.js, which examines the last 10 events to build a frequency profile and identify consecutive repair patterns:

// src/gep/signals.js – analyzeRecentHistory()
function analyzeRecentHistory(recentEvents) {
  var recent = recentEvents.slice(-10);

  var consecutiveRepairCount = 0;
  for (var i = recent.length - 1; i >= 0; i--) {
    if (recent[i].intent === 'repair') consecutiveRepairCount++;
    else break;
  }

  var signalFreq = {};
  var tail = recent.slice(-8);
  tail.forEach(evt => {
    (Array.isArray(evt.signals) ? evt.signals : []).forEach(s => {
      var key = normalizeSignal(s);
      signalFreq[key] = (signalFreq[key] || 0) + 1;
    });
  });

  var suppressedSignals = new Set();
  Object.entries(signalFreq).forEach(([k, cnt]) => {
    if (cnt >= 3) suppressedSignals.add(k);
  });

  return {
    suppressedSignals,
    recentIntents: recent.map(e => e.intent || 'unknown'),
    consecutiveRepairCount,
  };
}

Identifying Consecutive Repairs

The function calculates consecutiveRepairCount by iterating backward from the most recent event until it encounters a non-repair intent. This counter becomes the primary trigger for loop detection when it reaches three or more consecutive repair attempts.

Deduplication and Signal Suppression

Before the system acts on extracted signals, it applies the suppression set generated from the history analysis. Signals that appeared three or more times in the last eight events are filtered out to prevent the system from repeatedly reacting to the same symptom:

// src/gep/signals.js – post-processing excerpt
if (history.suppressedSignals.size > 0) {
  signals = signals.filter(s => {
    var key = normalizeSignal(s);
    return !history.suppressedSignals.has(key);
  });
}

This deduplication mechanism ensures that persistent but already-addressed error signatures do not dominate the signal set and trigger redundant repair attempts.

Repair Loop Prevention Mechanism

Detecting Repair Loops

When consecutiveRepairCount reaches three or more, the system recognizes that it has entered a repair loop—a cycle where each repair attempt fails and triggers another repair on the same underlying issue. At this threshold, the post-processor strips ordinary error signals and prepares to inject loop-breaking directives:

// src/gep/signals.js – repair-loop detection
if (history.consecutiveRepairCount >= 3) {
  signals = signals.filter(s => !s.startsWith('errsig:') && s !== 'log_error');

  if (signals.length === 0) {
    signals.push('repair_loop_detected');
    signals.push('stable_success_plateau');
  }
  signals.push('force_innovation_after_repair_loop');
}

Injecting Break Signals

The system introduces three specific signals to force a strategic pivot:

  • repair_loop_detected – Flags the current state as a known loop condition
  • stable_success_plateau – Indicates that current success metrics have stagnated
  • force_innovation_after_repair_loop – Directs downstream components to abandon incremental fixes and seek architectural alternatives

These signals replace the suppressed error noise, ensuring that the next evolution cycle addresses the meta-problem rather than retrying the failed repair.

Downstream Impact on Question Generation

The signal set including repair_loop_detected and force_innovation_after_repair_loop is consumed by src/gep/questionGenerator.js to formulate targeted interventions. When these signals are present, the generator creates questions that explicitly request fundamentally different approaches:

// src/gep/questionGenerator.js – repair-loop strategy
if (signalSet.has('repair_loop_detected') ||
    signalSet.has('force_innovation_after_repair_loop')) {
  var recentGenes = extractRecentGeneIds(recentEvents, 6);
  candidates.push({
    question: 'Agent is stuck in a repair loop (repair->fail->repair cycle) '
            + 'with genes: [' + recentGenes.join(', ') + ']. '
            + 'What fundamentally different approach could break this cycle?',
    amount: 0,
    signals: ['repair_loop', 'architectural_help_needed'],
    priority: 3,
  });
}

This integration ensures that the detection of repair loops translates directly into actionable requests for architectural innovation, breaking the cycle of incremental and failing repairs.

Summary

  • Multi-layer extraction in src/gep/signals.js gathers signals via regex, keyword scoring, and LLM analysis, merging them into a deduplicated set before processing.
  • History-aware analysis via analyzeRecentHistory() tracks the last 10 events, counting consecutive repair intents and building frequency maps to identify over-processed signals.
  • Signal suppression removes any signal appearing three or more times in the last eight events, preventing the system from reacting repeatedly to the same symptom.
  • Repair-loop detection triggers when consecutiveRepairCount reaches three, stripping ordinary error signals and injecting repair_loop_detected and force_innovation_after_repair_loop.
  • Architectural pivot occurs when src/gep/questionGenerator.js detects these loop signals, generating questions that request fundamentally different approaches rather than incremental fixes.

Frequently Asked Questions

What constitutes a repair loop in Evolver?

A repair loop occurs when the system generates three or more consecutive repair intents without intervening success states or alternative strategies. As implemented in src/gep/signals.js, the analyzeRecentHistory() function counts backward from the most recent event to determine how many sequential repair attempts have occurred, triggering loop detection at the threshold of three.

How does Evolver differentiate between legitimate repeated repairs and harmful loops?

Evolver uses frequency analysis alongside consecutive counting. While two consecutive repairs might indicate a legitimate retry, the system examines the signalFreq map to see if the same normalized signals appear three or more times within the last eight events. When combined with a consecutiveRepairCount of three or higher, the system classifies the pattern as a harmful loop requiring architectural intervention rather than continued incremental repair.

What happens when the force_innovation_after_repair_loop signal is triggered?

When this signal appears in the post-processing stage of src/gep/signals.js, it propagates through the signal set to downstream components like src/gep/questionGenerator.js. The generator recognizes this directive and formulates questions that explicitly ask for "fundamentally different approaches" rather than variations of the previous failed repair. This forces the evolutionary process to pivot toward novel strategies, breaking the cycle of repetitive fixes.

Can the repair loop detection threshold be configured?

The current implementation in src/gep/signals.js hardcodes the threshold at three consecutive repairs (consecutiveRepairCount >= 3) and three signal occurrences (cnt >= 3) for suppression. While the source code does not expose external configuration parameters for these values in the provided analysis, the modular structure of analyzeRecentHistory() suggests that these thresholds could be parameterized in future iterations or through environment-specific configuration overrides.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →