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
Delay before starting (in milliseconds)
started or already_running
Process ID of started loop
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
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)
Delay before restarting (in milliseconds)
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
Whether the loop is running
Array of running processes with pid and cmd
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
Number of lines to display
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
Whether the loop is healthy
Reason if unhealthy: not_running or stagnation
Minutes since last log update (if stagnant)
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);
}