/** * Evolution Run Controller — 薄编排器 * * 每个 Run 只进化一个 Skill,互不干涉。 * * Phase 1: 初始化 (Forage → Seed → Benchmark 生成 → 初始适应度) * Phase 2: 自然选择循环 (Evaluate → Select → Reproduce → BenchmarkEvolve) */ const path = require('path'); const fs = require('fs'); const evolutionQueries = require('./queries').evolutionQueries; const steps = require('./steps'); const { EvolutionLogger } = require('./evolution-logger'); const { setLogSink, setAgentManager } = require('./evaluator'); class RunController { constructor(options = {}) { this.skillsDir = options.skillsDir; this.populationSize = options.populationSize || 5; this.convergenceThreshold = options.convergenceThreshold || 0.9; if (!options.agentManager) { throw new Error('RunController: options.agentManager is required'); } this._agentManager = options.agentManager; } /** * Phase 1: 初始化单个 Skill 的进化 * * @param {string} skillName - 要进化的 Skill 名称 (如 "code-review") */ async runPhase1(skillName) { if (!skillName) throw new Error('必须指定 skillName — 每个 Run 只进化一个 Skill'); const logger = new EvolutionLogger(null, skillName); setLogSink((event, data) => logger.log(event, data)); setAgentManager(this._agentManager); const log = (msg) => console.log(`[RunController] ${msg}`); logger.phase('Phase 1: 初始化', { skillName }); // 1. Forage — 只扫描指定 Skill logger.step('Forage', { skillName }); const { forageOneSkill } = require('./forager'); const forageResult = await forageOneSkill(skillName, this.skillsDir); if (!forageResult) { logger.error('Forage', { message: `未找到 Skill: ${skillName}` }); logger.end({ success: false }); return { success: false, reason: 'skill-not-found' }; } // 获取该 Skill 关联的 Run const { runs } = await evolutionQueries.getRuns({ pageSize: 100 }); const run = runs.find(r => r.skill_name === skillName) || runs[0]; if (!run) { logger.end({ success: false }); return { success: false, reason: 'no-run-created' }; } logger.runId = run.id; logger.log('run_created', { runId: run.id, skillName }); // 2. Seed — 创建策略变体 logger.step('Seed', { populationSize: this.populationSize }); let seededVariants = 0; try { const { seedRunPopulation } = require('./genome/seeder'); seededVariants = await seedRunPopulation(run.id, this.populationSize); logger.log('seed_done', { variants: seededVariants }); } catch (err) { logger.error('Seed', err); } // 3. Benchmark 生成 — 为该 Skill 生成专属测试场景 logger.step('Benchmark 生成'); try { const existing = await evolutionQueries.getBenchmarks(skillName); if (existing.length === 0) { logger.llmCall('生成 Benchmark 场景', { backend: 'evaluator' }); const { generateRunBenchmarks } = require('./benchmark/generator'); await generateRunBenchmarks(run.id, `${skillName} 能力评估`); const created = await evolutionQueries.getBenchmarks(skillName); logger.llmResult('Benchmark 生成', { success: true, count: created.length }); } else { logger.log('benchmark_exists', { count: existing.length }); } } catch (e) { logger.error('Benchmark 生成', e); } // 4. 初始适应度评估 logger.step('初始适应度评估'); try { logger.llmCall('6 维适应度评估', { backend: 'evaluator' }); const { evaluateRunFitness } = require('./operators/fitness-calculator'); const fitnessResults = await evaluateRunFitness(run.id); for (const r of fitnessResults) { logger.genome('fitness', { id: r.genomeId, fitness: r.overall, dimensions: r.dimensionScores }); } logger.llmResult('适应度评估', { success: true, evaluated: fitnessResults.length }); } catch (e) { logger.error('适应度评估', e); } // 更新状态 const bestGenome = await evolutionQueries.getBestGenome(run.id); await evolutionQueries.updateRun(run.id, { status: 'initialized', current_generation: 0, best_fitness: bestGenome?.overall_fitness || null, }); const elapsed = ((Date.now() - logger.startTime) / 1000).toFixed(1); const summary = { success: true, runId: run.id, skillName, variants: seededVariants, bestFitness: bestGenome?.overall_fitness, elapsedSeconds: parseFloat(elapsed), }; logger.phase('Phase 1 完成', summary); logger.end(summary); log(`Phase 1 完成 | ${skillName} | ${elapsed}s | bestFitness=${summary.bestFitness || '—'}`); return summary; } /** * Phase 2: 自然选择循环 * * 每代: Evaluate → Select → Reproduce → BenchmarkEvolve */ async runPhase2(runId, generations = 5) { const run = await evolutionQueries.getRun(runId); if (!run) throw new Error(`Run ${runId} not found`); const logger = new EvolutionLogger(runId, run.skill_name); setLogSink((event, data) => logger.log(event, data)); setAgentManager(this._agentManager); const log = (msg) => console.log(`[RunController] ${msg}`); logger.phase('Phase 2: 自然选择', { runId, skillName: run.skill_name, generations }); await evolutionQueries.updateRun(runId, { status: 'running', max_generations: run.current_generation + generations, started_at: new Date(), }); const generationLog = []; for (let gen = run.current_generation + 1; gen <= run.current_generation + generations; gen++) { logger.phase(`Generation ${gen}`); const ctx = { log, run, generation: gen, logger }; const result = { generation: gen }; // Evaluate logger.step('Evaluate (Arena + Benchmark + Fitness)'); Object.assign(result, await steps.stepEvaluate(runId, ctx)); if (result.fitness) { for (const f of (result.fitness.results || [])) { logger.genome('fitness', { id: f.genomeId, fitness: f.overall }); } } // Select logger.step('Select'); Object.assign(result, await steps.stepSelect(runId, ctx)); if (result.selection) { logger.log('selection', { elites: result.selection.elites, survivors: result.selection.survivors, killed: result.selection.killed, }); } // Reproduce logger.step('Reproduce (Crossover + Mutation)'); Object.assign(result, await steps.stepReproduce(runId, ctx)); if (result.crossover) logger.log('crossover', { children: result.crossover.count || 0 }); if (result.mutation) logger.log('mutation', { children: result.mutation.count || 0 }); // BenchmarkEvolve logger.step('Benchmark 自进化'); Object.assign(result, await steps.stepBenchmarkEvolve(runId, ctx)); const best = await evolutionQueries.getBestGenome(runId); await evolutionQueries.updateRun(runId, { current_generation: gen, best_fitness: best?.overall_fitness || null, }); result.bestFitness = best?.overall_fitness; generationLog.push(result); const aliveCount = result.selection?.survivors ?? '?'; logger.log('generation_done', { gen, bestFitness: result.bestFitness, alive: aliveCount }, `📈 Gen ${gen} | best=${result.bestFitness ? (result.bestFitness * 100).toFixed(1) + '%' : '—'} | alive=${aliveCount}`); if (result.bestFitness && result.bestFitness >= this.convergenceThreshold) { logger.log('converged', { fitness: result.bestFitness, threshold: this.convergenceThreshold }); await evolutionQueries.updateRun(runId, { status: 'converged' }); break; } } await evolutionQueries.updateRun(runId, { status: 'completed', completed_at: new Date() }); const elapsed = ((Date.now() - logger.startTime) / 1000).toFixed(1); const summary = { success: true, runId, generations: generationLog.length, elapsedSeconds: parseFloat(elapsed) }; logger.phase('Phase 2 完成', summary); logger.end(summary); log(`Phase 2 完成 | ${generationLog.length} 代 | ${elapsed}s`); return { ...summary, generationLog }; } /** * 部署进化结果: 将最佳基因组写回 SKILL.md * * @param {string} runId - Evolution Run ID * @returns {object} 部署结果 */ async deploy(runId) { const log = (msg) => console.log(`[RunController] ${msg}`); const run = await evolutionQueries.getRun(runId); if (!run) throw new Error(`Run ${runId} not found`); log('═══════════════════════════════════════'); log(` Deploy: ${run.skill_name}`); log('═══════════════════════════════════════\n'); const bestGenome = await evolutionQueries.getBestGenome(runId); if (!bestGenome) { log(' ❌ 没有可部署的基因组'); return { success: false, reason: 'no-genome' }; } log(` 🏆 最佳: ${bestGenome.id.slice(0, 8)} (Gen ${bestGenome.generation}, fitness=${bestGenome.overall_fitness ? (bestGenome.overall_fitness * 100).toFixed(1) + '%' : '—'})`); const { writeGenomeToSkill } = require('./genome/deserializer'); const path = require('path'); const targetDir = path.join(this.skillsDir, run.skill_name); const result = writeGenomeToSkill(bestGenome, targetDir, { name: run.skill_name, }, { backup: true }); log(` ✅ 部署完成: ${result.path}${result.backedUp ? ' (原文件已备份)' : ''}`); return { success: true, skillName: run.skill_name, genomeId: bestGenome.id, fitness: bestGenome.overall_fitness, generation: bestGenome.generation, deployedTo: result.path, backedUp: result.backedUp, }; } } // CLI if (require.main === module) { const args = process.argv.slice(2); const controller = new RunController(); if (args.includes('--phase2')) { const runId = args.find(a => !a.startsWith('--')); const gens = parseInt(args.find(a => a.startsWith('--gens='))?.split('=')[1]) || 5; if (!runId) { console.error('Usage: node run-controller.js --phase2 [--gens=5]'); process.exit(1); } controller.runPhase2(runId, gens) .then(r => { console.log('\n📋 Result:', JSON.stringify(r, null, 2)); process.exit(0); }) .catch(e => { console.error('Fatal:', e); process.exit(1); }); } else { const skillName = args.find(a => !a.startsWith('--')); if (!skillName) { console.error('Usage: node run-controller.js '); process.exit(1); } controller.runPhase1(skillName) .then(r => { console.log('\n📋 Result:', JSON.stringify(r, null, 2)); process.exit(0); }) .catch(e => { console.error('Fatal:', e); process.exit(1); }); } } module.exports = { RunController };