Skip to main content

Overview

The lifecycle module provides commands to manage the Evolver loop process. It supports starting, stopping, restarting, checking status, viewing logs, and performing health checks. Location: src/ops/lifecycle.js

CLI Usage

node src/ops/lifecycle.js [start|stop|restart|status|log|check]

Functions

start()

Start the Evolver loop in detached mode. Location: src/ops/lifecycle.js:58
function start(options)
options.delayMs
number
default:0
Delay before starting (in milliseconds)
status
string
started or already_running
pid
number
Process ID of started loop
pids
number[]
Array of existing PIDs (if already running)
Example:
const { start } = require('./src/ops/lifecycle');

const result = start({ delayMs: 2000 });
console.log(result);
// { status: 'started', pid: 12345 }

stop()

Stop all running Evolver loop processes. Location: src/ops/lifecycle.js:88
function stop()
status
string
stopped or not_running
killed
number[]
Array of PIDs that were stopped
Example:
const { stop } = require('./src/ops/lifecycle');

const result = stop();
console.log(result);
// { status: 'stopped', killed: [12345, 12346] }

restart()

Restart the Evolver loop (stop + start). Location: src/ops/lifecycle.js:116
function restart(options)
options.delayMs
number
default:2000
Delay before restarting (in milliseconds)
status
string
started
pid
number
Process ID of restarted loop
Example:
node src/ops/lifecycle.js restart

status()

Check if Evolver loop is running. Location: src/ops/lifecycle.js:121
function status()
running
boolean
Whether the loop is running
pids
object[]
Array of running processes with pid and cmd
log
string
Path to log file (relative to workspace)
Example:
const { status } = require('./src/ops/lifecycle');

const result = status();
console.log(result);
// {
//   running: true,
//   pids: [{ pid: 12345, cmd: 'node skills/feishu-evolver-wrapper/index.js --loop' }],
//   log: 'memory/evolver.log'
// }

tailLog()

View recent log entries. Location: src/ops/lifecycle.js:129
function tailLog(lines)
lines
number
default:20
Number of lines to display
file
string
Path to log file
content
string
Log content
error
string
Error message (if no log file exists)
Example:
node src/ops/lifecycle.js log

checkHealth()

Check if the loop is healthy and responsive. Location: src/ops/lifecycle.js:138
function checkHealth()
healthy
boolean
Whether the loop is healthy
reason
string
Reason if unhealthy: not_running or stagnation
silenceMinutes
number
Minutes since last log update (if stagnant)
pids
number[]
Array of running PIDs (if healthy)
Stagnation Detection: Location: src/ops/lifecycle.js:13
const MAX_SILENCE_MS = 30 * 60 * 1000; // 30 minutes

if (fs.existsSync(LOG_FILE)) {
  const silenceMs = Date.now() - fs.statSync(LOG_FILE).mtimeMs;
  if (silenceMs > MAX_SILENCE_MS) {
    return {
      healthy: false,
      reason: 'stagnation',
      silenceMinutes: Math.round(silenceMs / 60000)
    };
  }
}
Example:
node src/ops/lifecycle.js check
# Automatically restarts if unhealthy

Process Discovery

Location: src/ops/lifecycle.js:25
function getRunningPids() {
  const out = execSync('ps -e -o pid,args', { encoding: 'utf8' });
  const pids = [];
  
  for (const line of out.split('\n')) {
    const trimmed = line.trim();
    if (!trimmed || trimmed.startsWith('PID')) continue;
    
    const parts = trimmed.split(/\s+/);
    const pid = parseInt(parts[0], 10);
    const cmd = parts.slice(1).join(' ');
    
    if (pid === process.pid) continue; // Skip self
    
    // Match evolver loop processes
    if (cmd.includes('node') && cmd.includes('index.js') && cmd.includes('--loop')) {
      if (cmd.includes('feishu-evolver-wrapper') || cmd.includes('skills/evolver')) {
        pids.push(pid);
      }
    }
  }
  
  return [...new Set(pids)].filter(isPidRunning);
}

Loop Script Selection

Location: src/ops/lifecycle.js:15
function getLoopScript() {
  // Prefer wrapper if exists, fallback to core evolver
  if (process.env.EVOLVER_LOOP_SCRIPT) {
    return process.env.EVOLVER_LOOP_SCRIPT;
  }
  
  const wrapper = path.join(WORKSPACE_ROOT, 'skills/feishu-evolver-wrapper/index.js');
  if (fs.existsSync(wrapper)) {
    return wrapper;
  }
  
  return path.join(getRepoRoot(), 'index.js');
}

Environment Setup

Location: src/ops/lifecycle.js:73
const env = Object.assign({}, process.env);
const npmGlobal = path.join(process.env.HOME || '', '.npm-global/bin');
if (env.PATH && !env.PATH.includes(npmGlobal)) {
  env.PATH = npmGlobal + ':' + env.PATH;
}

const child = spawn('node', [script, '--loop'], {
  detached: true,
  stdio: ['ignore', out, err],
  cwd: WORKSPACE_ROOT,
  env: env
});

child.unref();

Complete Example

const lifecycle = require('./src/ops/lifecycle');

// Start the loop
const startResult = lifecycle.start();
if (startResult.status === 'started') {
  console.log('Evolver loop started:', startResult.pid);
}

// Check status
const statusResult = lifecycle.status();
if (statusResult.running) {
  console.log('Running processes:', statusResult.pids);
  console.log('Log file:', statusResult.log);
}

// Check health
const healthResult = lifecycle.checkHealth();
if (!healthResult.healthy) {
  console.error('Unhealthy:', healthResult.reason);
  if (healthResult.reason === 'stagnation') {
    console.log('Silence:', healthResult.silenceMinutes, 'minutes');
  }
  
  // Auto-restart
  lifecycle.restart();
}

// View logs
const logResult = lifecycle.tailLog(50);
if (logResult.content) {
  console.log(logResult.content);
}

// Stop the loop
const stopResult = lifecycle.stop();
if (stopResult.status === 'stopped') {
  console.log('Stopped PIDs:', stopResult.killed);
}

Build docs developers (and LLMs) love