run-controller.js
10.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
/**
* 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 <runId> [--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 <skillName>'); 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 };