Skip to main content

Overview

Loop mode runs the evolver continuously in a daemon-like process, automatically analyzing history, applying changes, and adapting to system load. It includes built-in protection against memory leaks, singleton enforcement, and intelligent backoff when the system is idle or saturated.

What is Loop Mode?

Loop mode transforms the evolver from a one-shot command into a long-running background process that:
  • Runs evolution cycles continuously
  • Adapts sleep intervals based on system activity
  • Prevents duplicate daemon instances (singleton lock)
  • Automatically restarts itself to prevent memory leaks
  • Increases sleep time when evolution saturates
  • Respects pending solidify operations (Ralph-loop gating)
Loop mode is designed for fully automated operation. It bypasses review mode and applies changes immediately. Only use in isolated environments or with robust validation in your Genes.

Starting Loop Mode

Direct Execution

Run the evolver with the --loop flag:
node index.js --loop
This starts an internal daemon that:
  1. Acquires a singleton lock (evolver.pid)
  2. Registers signal handlers for graceful shutdown
  3. Starts the A2A heartbeat (keeps node alive in network)
  4. Begins the evolution cycle loop

Alternative Flag

The --mad-dog flag is an alias for --loop:
node index.js --mad-dog
The name “mad dog” reflects the aggressive, fully-automated nature of loop mode.

Daemon Behavior

Singleton Lock

Only one evolver loop can run at a time. The daemon creates evolver.pid with its process ID:
[Singleton] Evolver loop already running (PID 12345). Exiting.
If the lock file is stale (process no longer running), the new instance takes over:
[Singleton] Stale lock found (PID 12345). Taking over.

Evolution Cycle Loop

1

Check Pending Solidify

Skip the cycle if the previous run is awaiting solidification:
[Daemon] Pending solidify detected. Sleeping 120s...
This prevents Ralph-loop conflicts where multiple cycles try to modify the same state.
2

Run Evolution

Execute evolve.run() to analyze history and apply changes:
[Evolver] Starting cycle 42...
[Signals] Extracted 2 signals: repair_needed, optimization_opportunity
[Selector] Selected Gene: performance-tuning-v1 (score: 0.78)
[Evolver] Changes applied. Run ID: evo_20260309_143022
3

Adaptive Sleep

Calculate sleep time based on cycle duration:
  • Fast cycle (less than 500ms): Double sleep time (idle detection)
  • Normal cycle: Reset to minimum sleep
  • Saturation signal: Multiply sleep by 5x or 10x
[Daemon] Approaching saturation. Reducing evolution frequency (5x sleep).
[Daemon] Sleeping 10000ms...
4

Suicide Check

After each cycle, check if restart is needed:
  • Cycle count exceeds EVOLVER_MAX_CYCLES_PER_PROCESS (default: 100)
  • Memory usage exceeds EVOLVER_MAX_RSS_MB (default: 500MB)
[Daemon] Restarting self (cycles=100, rssMb=523)
Spawns a new process with the same arguments, then exits.

Adaptive Sleep and Backoff

Base Configuration

Control sleep intervals with environment variables:
VariableDefaultDescription
EVOLVER_MIN_SLEEP_MS2000Minimum sleep between cycles (2s)
EVOLVER_MAX_SLEEP_MS300000Maximum sleep (5 minutes)
EVOLVER_IDLE_THRESHOLD_MS500Cycles faster than this trigger backoff
EVOLVE_PENDING_SLEEP_MS120000Sleep when awaiting solidify (2 minutes)

Backoff Algorithm

if (cycle_duration < IDLE_THRESHOLD_MS || cycle_failed) {
  // Idle or failed: exponential backoff
  sleep_ms = min(MAX_SLEEP_MS, sleep_ms * 2);
} else {
  // Active evolution: reset to minimum
  sleep_ms = MIN_SLEEP_MS;
}

Saturation Detection

When signals indicate evolution saturation, sleep time is multiplied:
  • evolution_saturation signal: 5x sleep
  • force_steady_state signal: 10x sleep
[Daemon] Saturation detected. Entering steady-state mode (10x sleep).
[Daemon] Sleeping 20000ms...
Saturation signals trigger automatic strategy switching to steady-state. See Strategy Presets for details.

Jitter

A random jitter (0-250ms) is added to each sleep to prevent lockstep restarts:
const jitter = Math.floor(Math.random() * 250);
await sleep((sleep_ms + jitter) * saturation_multiplier);

Suicide and Restart Mechanism

Why Suicide?

Long-running Node.js processes can accumulate memory leaks from:
  • Loaded modules and closures
  • Cached file buffers
  • Internal V8 heap fragmentation
The suicide mechanism proactively restarts the process before leaks become critical.

Restart Triggers

# Cycle limit
EVOLVER_MAX_CYCLES_PER_PROCESS=100  # Default

# Memory limit
EVOLVER_MAX_RSS_MB=500  # Default: 500MB RSS

Restart Process

1

Detect Threshold

Check after each cycle:
const memMb = process.memoryUsage().rss / 1024 / 1024;
if (cycleCount >= maxCycles || memMb > maxRssMb) {
  // Trigger restart
}
2

Spawn Replacement

Spawn a new process with the same arguments:
const child = spawn(process.execPath, [__filename, '--loop'], {
  detached: true,
  stdio: 'ignore',
  env: process.env,
});
child.unref();
3

Release Lock and Exit

Release singleton lock and exit gracefully:
releaseLock();  // Delete evolver.pid
process.exit(0);

Disable Suicide

For debugging or special environments:
EVOLVER_SUICIDE=false node index.js --loop
Disabling suicide can lead to memory exhaustion in long-running daemons. Only use for short-term debugging.

Cron and External Runners

Best Practices

When running loop mode via cron or external process managers, use simple commands: Recommended:
# Cron entry
0 */6 * * * cd /path/to/evolver && bash -lc 'node index.js --loop'
With pm2:
pm2 start "bash -lc 'node index.js --loop'" --name evolver
Use bash -lc to ensure your environment (PATH, nvm, etc.) is loaded correctly.

What to Avoid

Don’t compose multiple shell segments inside cron payloads:
# BAD: Nested quotes break serialization
0 */6 * * * cd /path && node index.js --loop; echo "EXIT:$?"
Nested quotes can break after passing through multiple escaping layers in cron/systemd/Docker.

Process Manager Integration

For production deployments, use a process manager:
pm2 start index.js --name evolver -- --loop
pm2 save
pm2 startup  # Enable on boot

Lifecycle Management

For production deployments, use the operations module:
node src/ops/lifecycle.js start   # Start daemon
node src/ops/lifecycle.js stop    # Graceful stop
node src/ops/lifecycle.js status  # Check running state
node src/ops/lifecycle.js check   # Health check + auto-restart
See Operations for detailed lifecycle commands.

Monitoring Loop Mode

Check Process Status

ps aux | grep "node.*index.js.*--loop"

View Live Logs

tail -f memory/evolver_loop.log

Check Memory Usage

ps -p $(cat evolver.pid) -o pid,rss,vsz,cmd

Inspect State File

cat memory/evolution/evolution_solidify_state.json | jq .

Stopping Loop Mode

Graceful Shutdown

Send SIGTERM to the process:
kill $(cat evolver.pid)
Or use the lifecycle manager:
node src/ops/lifecycle.js stop

Force Kill

If the process doesn’t exit:
kill -9 $(cat evolver.pid)
rm evolver.pid  # Clean up lock file

Configuration Reference

Sleep and Timing

# Minimum sleep between cycles (milliseconds)
EVOLVER_MIN_SLEEP_MS=2000

# Maximum sleep (5 minutes)
EVOLVER_MAX_SLEEP_MS=300000

# Idle detection threshold
EVOLVER_IDLE_THRESHOLD_MS=500

# Sleep when pending solidify
EVOLVE_PENDING_SLEEP_MS=120000

Restart Thresholds

# Restart after N cycles
EVOLVER_MAX_CYCLES_PER_PROCESS=100

# Restart when RSS exceeds N MB
EVOLVER_MAX_RSS_MB=500

# Disable suicide mechanism
EVOLVER_SUICIDE=false

Strategy Control

# Evolution strategy (see Strategy Presets)
EVOLVE_STRATEGY=balanced

# CPU load threshold
EVOLVE_LOAD_MAX=2.0

Troubleshooting

Loop Won’t Start

Problem: “Evolver loop already running (PID 12345). Exiting.” Solution: Check if process is actually running:
ps -p $(cat evolver.pid)

# If not running, remove stale lock:
rm evolver.pid

Loop Restarts Too Frequently

Problem: Daemon restarts every few cycles. Solution: Increase restart thresholds:
EVOLVER_MAX_CYCLES_PER_PROCESS=500 \
EVOLVER_MAX_RSS_MB=1000 \
node index.js --loop

High Memory Usage

Problem: RSS grows beyond expected levels. Solution: Lower restart threshold or investigate memory leaks:
# Force restart at 300MB
EVOLVER_MAX_RSS_MB=300 node index.js --loop

# Profile memory usage
node --inspect index.js --loop

Loop Becomes Idle

Problem: Evolver sleeps indefinitely. Solution: Check for saturation signals:
cat memory/evolution/evolution_solidify_state.json | jq '.last_run.signals'
If force_steady_state or evolution_saturation are present, the system is in low-frequency mode. This is intentional to avoid thrashing a mature system.

Best Practices

  1. Use process managers: pm2, systemd, or Docker for production
  2. Monitor logs: Set up log rotation and alerting
  3. Set CPU limits: Use EVOLVE_LOAD_MAX to prevent overload
  4. Start conservatively: Begin with high sleep times, then tune down
  5. Enable health checks: Run lifecycle.js check periodically
  6. Version control: Always run in a git repository with remote backups

Next Steps

Operations

Lifecycle commands and health monitoring

Strategy Presets

Control evolution focus in loop mode

Review Mode

Alternative: manual approval workflow

Running Evolver

Single-run execution basics

Build docs developers (and LLMs) love