Add evolver runtime

This commit is contained in:
Trae AI 2026-05-26 01:37:50 +08:00
parent 2f5c508b33
commit 5cf3a225ed
3 changed files with 330 additions and 0 deletions

View file

@ -0,0 +1,199 @@
import type {
BaselineProfile,
EnvironmentSnapshot,
EvaluationReport,
EvolutionPlan,
GateDecision,
GoalSpec,
MutationType,
PromotionRecord,
ResourceBudget,
TrialResult,
VariantSpec,
} from './types.js';
import type { SandboxRunner } from './sandbox-runner.js';
import type { InMemoryEvolutionMemory } from './evolution-memory.js';
import { applyPromotion } from './promotion-controller.js';
import { decidePromotion } from './safety-gate.js';
import { decideResourceUse } from './resource-manager.js';
import { evaluateVariant } from './metric-evaluator.js';
import { generateVariants } from './variant-generator.js';
import { runSandboxTrial } from './sandbox-runner.js';
export interface EvolverRuntimeConfig {
memory: InMemoryEvolutionMemory;
sandboxRunner: SandboxRunner;
currentParameters: Record<string, unknown>;
allowedMutationTypes?: MutationType[];
}
export interface EvolverRuntimeResult {
promotionRecord: PromotionRecord | null;
gateDecision: GateDecision;
evaluationReport: EvaluationReport;
trialResult: TrialResult;
}
export interface EvolverRuntime {
runEvolution(
goal: GoalSpec,
snapshot: EnvironmentSnapshot,
baseline: BaselineProfile,
): Promise<EvolverRuntimeResult>;
}
export function createEvolverRuntime(config: EvolverRuntimeConfig): EvolverRuntime {
return {
async runEvolution(
goal: GoalSpec,
snapshot: EnvironmentSnapshot,
baseline: BaselineProfile,
): Promise<EvolverRuntimeResult> {
const plan = buildPlan(goal, config.allowedMutationTypes);
const variants = generateVariants(plan, config.currentParameters);
const variant = variants[0];
memoryAdd(config.memory, 'variant', variant.id, goal.id, `Generated ${variant.mutationType} variant`, variant);
const protocol = { id: `${plan.id}-protocol`, maxDurationMs: plan.resourceBudget.maxDurationMs };
const resourceDecision = decideResourceUse(
{
variant,
requestedUsage: {
durationMs: protocol.maxDurationMs,
computeUnits: 1,
apiCostUsd: 0,
storageBytes: 256,
},
activeRuns: 1,
activeHighRiskVariants: variant.riskLevel === 'high' ? 1 : 0,
},
plan.resourceBudget,
snapshot.resourceUsage,
);
if (!resourceDecision.allowed) {
const failedTrial = buildResourceDeniedTrial(variant, protocol);
memoryAdd(config.memory, 'trial', variant.id, goal.id, 'Resource denied', failedTrial);
const report = buildRejectedReport(variant, baseline, 'resource denied');
const gate = decidePromotion(variant, report);
return { promotionRecord: null, gateDecision: gate, evaluationReport: report, trialResult: failedTrial };
}
const trial = await runSandboxTrial(variant, protocol, config.sandboxRunner, plan.resourceBudget);
memoryAdd(config.memory, 'trial', variant.id, goal.id, `Trial completed with ${trial.errors.length} errors`, trial);
const report = evaluateVariant({
id: `evaluation-${variant.id}`,
variant,
baseline,
trial,
targetMetrics: goal.targetMetrics,
budget: plan.resourceBudget,
safetyFindings: extractSafetyFindings(snapshot),
rollbackPassed: variant.mutationType === 'structure' || variant.rollbackPlan.operations.length > 0,
});
memoryAdd(config.memory, 'evaluation', variant.id, goal.id, `Verdict: ${report.verdict}`, report);
const gate = decidePromotion(variant, report);
memoryAdd(config.memory, 'gate', variant.id, goal.id, `Decision: ${gate.decision}`, gate);
let promotionRecord: PromotionRecord | null = null;
if (gate.decision === 'allow') {
promotionRecord = applyPromotion(config.currentParameters, variant, gate);
if (promotionRecord) {
memoryAdd(config.memory, 'promotion', variant.id, goal.id, 'Variant promoted', promotionRecord);
}
}
return { promotionRecord, gateDecision: gate, evaluationReport: report, trialResult: trial };
},
};
}
function buildPlan(goal: GoalSpec, allowedMutationTypes?: MutationType[]): EvolutionPlan {
return {
id: `plan-${goal.id}`,
goalId: goal.id,
hypothesis: `Evolve towards ${goal.objective}`,
targetObject: {
id: `target-${goal.id}`,
kind: 'parameter-set',
description: goal.objective,
},
allowedMutationTypes: allowedMutationTypes ?? ['parameter'],
evaluationProtocolId: `protocol-${goal.id}`,
resourceBudget: defaultBudget(),
};
}
function defaultBudget(): ResourceBudget {
return {
maxDurationMs: 5000,
maxComputeUnits: 100,
maxApiCostUsd: 10,
maxStorageBytes: 10240,
maxConcurrency: 4,
maxHighRiskVariants: 2,
};
}
function extractSafetyFindings(snapshot: EnvironmentSnapshot) {
return snapshot.signals
.filter((s) => s.kind === 'safety')
.map((s) => ({ id: s.id, severity: s.severity, message: s.description }));
}
function buildResourceDeniedTrial(variant: VariantSpec, protocol: { id: string }): TrialResult {
return {
id: `trial-${variant.id}-${protocol.id}`,
variantId: variant.id,
protocolId: protocol.id,
metrics: [],
resourceUsage: { durationMs: 0, computeUnits: 0, apiCostUsd: 0, storageBytes: 0 },
errors: [
{
id: `error-trial-${variant.id}-${protocol.id}`,
type: 'resource-exhaustion',
message: 'resource request denied',
occurredAt: '2026-05-25T00:00:00.000Z',
variantId: variant.id,
},
],
completedAt: '2026-05-25T00:00:00.000Z',
};
}
function buildRejectedReport(variant: VariantSpec, baseline: BaselineProfile, reason: string): EvaluationReport {
return {
id: `evaluation-${variant.id}`,
variantId: variant.id,
baselineId: baseline.id,
metricDeltas: [],
regressions: [],
resourceCost: { durationMs: 0, computeUnits: 0, apiCostUsd: 0, storageBytes: 0 },
safetyFindings: [{ id: `safety-${variant.id}`, severity: 'high', message: reason }],
verdict: 'reject',
};
}
function memoryAdd(
memory: InMemoryEvolutionMemory,
kind: 'variant' | 'trial' | 'evaluation' | 'gate' | 'promotion',
variantId: string,
goalId: string,
summary: string,
payload: unknown,
): void {
memory.add({
id: `mem-${kind}-${variantId}`,
variantId,
goalId,
kind,
summary,
createdAt: '2026-05-25T00:00:00.000Z',
payload,
});
}

View file

@ -7,3 +7,4 @@ export * from './safety-gate.js';
export * from './promotion-controller.js';
export * from './evolution-memory.js';
export * from './error-corrector.js';
export * from './evolver-runtime.js';

View file

@ -0,0 +1,130 @@
import { describe, expect, it } from 'vitest';
import { createEvolverRuntime } from '../../../src/core/evolver/index.js';
import type {
BaselineProfile,
EnvironmentSnapshot,
GoalSpec,
InMemoryEvolutionMemory,
SandboxRunner,
} from '../../../src/core/evolver/index.js';
const capturedAt = '2026-05-25T00:00:00.000Z';
const goal: GoalSpec = {
id: 'goal-1',
name: 'Improve retrieval accuracy',
objective: 'Increase task success rate by adjusting retrieval parameters',
priority: 1,
targetMetrics: [
{
metricId: 'task_success_rate',
direction: 'increase',
minDelta: 0.03,
maxRegression: 0.02,
},
],
constraints: [],
stopConditions: ['task_success_rate >= 0.90'],
};
const baseline: BaselineProfile = {
id: 'baseline-1',
goalId: 'goal-1',
capturedAt,
metrics: [
{ metricId: 'task_success_rate', value: 0.72, capturedAt },
],
resourceUsage: { durationMs: 0, computeUnits: 0, apiCostUsd: 0, storageBytes: 0 },
};
const snapshot: EnvironmentSnapshot = {
id: 'snapshot-1',
capturedAt,
signals: [],
performance: [{ metricId: 'task_success_rate', value: 0.72, capturedAt }],
errors: [],
resourceUsage: { durationMs: 0, computeUnits: 0, apiCostUsd: 0, storageBytes: 0 },
};
function successRunner(): SandboxRunner {
return async (variant, protocol) => ({
metrics: [
{ metricId: 'task_success_rate', value: 0.82, capturedAt },
],
resourceUsage: { durationMs: 120, computeUnits: 1, apiCostUsd: 0, storageBytes: 256 },
});
}
function regressionRunner(): SandboxRunner {
return async (variant, protocol) => ({
metrics: [
{ metricId: 'task_success_rate', value: 0.60, capturedAt },
],
resourceUsage: { durationMs: 120, computeUnits: 1, apiCostUsd: 0, storageBytes: 256 },
});
}
describe('createEvolverRuntime', () => {
it('promotes a safe parameter variant and updates target parameters', async () => {
const memory: InMemoryEvolutionMemory = new (await import('../../../src/core/evolver/index.js')).InMemoryEvolutionMemory();
const currentParameters = { retrieval: { topK: 5 } };
const runtime = createEvolverRuntime({
memory,
sandboxRunner: successRunner(),
currentParameters,
});
const result = await runtime.runEvolution(goal, snapshot, baseline);
expect(result.promotionRecord).not.toBeNull();
expect(result.promotionRecord!.variantId).toContain('parameter');
expect(currentParameters.retrieval.topK).toBe(6);
expect(result.gateDecision.decision).toBe('allow');
const memoryRecords = memory.queryByKind('variant');
expect(memoryRecords.length).toBeGreaterThanOrEqual(1);
});
it('rejects a regressive variant without mutating target', async () => {
const { InMemoryEvolutionMemory: Mem } = await import('../../../src/core/evolver/index.js');
const memory = new Mem();
const currentParameters = { retrieval: { topK: 5 } };
const runtime = createEvolverRuntime({
memory,
sandboxRunner: regressionRunner(),
currentParameters,
});
const result = await runtime.runEvolution(goal, snapshot, baseline);
expect(result.gateDecision.decision).toBe('reject');
expect(currentParameters.retrieval.topK).toBe(5);
});
it('returns needs-review for structure variant', async () => {
const { InMemoryEvolutionMemory: Mem } = await import('../../../src/core/evolver/index.js');
const memory = new Mem();
const currentParameters = { retrieval: { topK: 5 } };
const structureGoal: GoalSpec = {
...goal,
id: 'goal-structure',
constraints: [],
stopConditions: [],
};
const runtime = createEvolverRuntime({
memory,
sandboxRunner: successRunner(),
currentParameters,
allowedMutationTypes: ['structure'],
});
const result = await runtime.runEvolution(structureGoal, snapshot, baseline);
expect(result.gateDecision.decision).toBe('needs-review');
expect(currentParameters.retrieval.topK).toBe(5);
});
});