From 35d0116128410aa8d8a326bd6327473beb7bd5ea Mon Sep 17 00:00:00 2001 From: Trae AI Date: Tue, 26 May 2026 00:00:27 +0800 Subject: [PATCH] Add evolver variant generator --- gitnexus/src/core/evolver/index.ts | 1 + .../src/core/evolver/variant-generator.ts | 172 ++++++++++++++++++ .../unit/evolver/variant-generator.test.ts | 81 +++++++++ 3 files changed, 254 insertions(+) create mode 100644 gitnexus/src/core/evolver/variant-generator.ts create mode 100644 gitnexus/test/unit/evolver/variant-generator.test.ts diff --git a/gitnexus/src/core/evolver/index.ts b/gitnexus/src/core/evolver/index.ts index 464c7840a..22af70b5c 100644 --- a/gitnexus/src/core/evolver/index.ts +++ b/gitnexus/src/core/evolver/index.ts @@ -1,3 +1,4 @@ export * from './types.js'; export * from './metric-evaluator.js'; export * from './resource-manager.js'; +export * from './variant-generator.js'; diff --git a/gitnexus/src/core/evolver/variant-generator.ts b/gitnexus/src/core/evolver/variant-generator.ts new file mode 100644 index 000000000..6c96fc60b --- /dev/null +++ b/gitnexus/src/core/evolver/variant-generator.ts @@ -0,0 +1,172 @@ +import type { + EvolutionPlan, + MetricExpectation, + MutationType, + RollbackPlan, + VariantPatch, + VariantProvenance, + VariantSpec, +} from './types.js'; + +export type CurrentParameters = Record; + +const generatedAt = '2026-05-25T00:00:00.000Z'; + +export function generateVariants( + plan: EvolutionPlan, + currentParameters: CurrentParameters, +): VariantSpec[] { + return plan.allowedMutationTypes.map((mutationType) => + buildVariant(plan, currentParameters, mutationType), + ); +} + +function buildVariant( + plan: EvolutionPlan, + currentParameters: CurrentParameters, + mutationType: MutationType, +): VariantSpec { + const expectedGain = defaultExpectedGain(); + + return { + id: `${plan.id}-${mutationType}-variant`, + planId: plan.id, + mutationType, + description: describeVariant(plan, mutationType), + patch: buildPatch(currentParameters, mutationType), + expectedGain, + riskLevel: riskForMutationType(mutationType), + rollbackPlan: buildRollbackPlan(currentParameters, mutationType), + provenance: buildProvenance(plan, mutationType), + }; +} + +function describeVariant(plan: EvolutionPlan, mutationType: MutationType): string { + if (mutationType === 'parameter') { + return `Parameter variant for ${plan.targetObject.id}`; + } + + if (mutationType === 'algorithm') { + return `Algorithm variant for ${plan.targetObject.id}`; + } + + return `Structure review variant for ${plan.targetObject.id}`; +} + +function buildPatch(currentParameters: CurrentParameters, mutationType: MutationType): VariantPatch { + if (mutationType === 'parameter') { + const previousValue = currentParameters['retrieval.topK'] ?? 5; + const nextValue = typeof previousValue === 'number' ? previousValue + 1 : 6; + + return { + operations: [ + { + op: 'setParameter', + path: 'retrieval.topK', + previousValue, + nextValue, + }, + ], + }; + } + + if (mutationType === 'algorithm') { + const previousAlgorithm = String(currentParameters['ranking.algorithm'] ?? 'baseline-ranker'); + + return { + operations: [ + { + op: 'selectAlgorithm', + policyId: 'ranking-policy', + previousAlgorithm, + nextAlgorithm: 'candidate-ranker', + }, + ], + }; + } + + return { + operations: [ + { + op: 'proposeStructureChange', + planPath: 'docs/aegis/plans/structure-change.md', + summary: 'Review structure-level evolution before applying changes', + }, + ], + }; +} + +function buildRollbackPlan( + currentParameters: CurrentParameters, + mutationType: MutationType, +): RollbackPlan { + if (mutationType === 'parameter') { + const previousValue = currentParameters['retrieval.topK'] ?? 5; + const candidateValue = typeof previousValue === 'number' ? previousValue + 1 : 6; + + return { + strategy: 'restore-previous-parameters', + operations: [ + { + op: 'setParameter', + path: 'retrieval.topK', + previousValue: candidateValue, + nextValue: previousValue, + }, + ], + }; + } + + if (mutationType === 'algorithm') { + const previousAlgorithm = String(currentParameters['ranking.algorithm'] ?? 'baseline-ranker'); + + return { + strategy: 'disable-candidate', + operations: [ + { + op: 'selectAlgorithm', + policyId: 'ranking-policy', + previousAlgorithm: 'candidate-ranker', + nextAlgorithm: previousAlgorithm, + }, + ], + }; + } + + return { + strategy: 'manual-review-required', + operations: [], + }; +} + +function riskForMutationType(mutationType: MutationType): VariantSpec['riskLevel'] { + if (mutationType === 'parameter') { + return 'low'; + } + + if (mutationType === 'algorithm') { + return 'medium'; + } + + return 'high'; +} + +function defaultExpectedGain(): MetricExpectation[] { + return [ + { + metricId: 'task_success_rate', + expectedDelta: 0.05, + direction: 'increase', + }, + ]; +} + +function buildProvenance(plan: EvolutionPlan, mutationType: MutationType): VariantProvenance[] { + return [ + { + source: 'evolver-variant-generator', + reference: `${plan.id}:${mutationType}`, + capturedAt: generatedAt, + }, + ]; +} diff --git a/gitnexus/test/unit/evolver/variant-generator.test.ts b/gitnexus/test/unit/evolver/variant-generator.test.ts new file mode 100644 index 000000000..40eaec5db --- /dev/null +++ b/gitnexus/test/unit/evolver/variant-generator.test.ts @@ -0,0 +1,81 @@ +import { describe, expect, it } from 'vitest'; +import { generateVariants } from '../../../src/core/evolver/index.js'; +import type { EvolutionPlan, ResourceBudget } from '../../../src/core/evolver/index.js'; + +const resourceBudget: ResourceBudget = { + maxDurationMs: 1000, + maxComputeUnits: 10, + maxApiCostUsd: 1, + maxStorageBytes: 2048, + maxConcurrency: 2, + maxHighRiskVariants: 1, +}; + +const plan: EvolutionPlan = { + id: 'plan-1', + goalId: 'goal-1', + hypothesis: 'Adjust retrieval and ranking strategy to improve task success rate', + targetObject: { + id: 'retrieval-policy', + kind: 'parameter-set', + description: 'Retrieval policy parameters and selection strategy', + }, + allowedMutationTypes: ['parameter', 'algorithm', 'structure'], + evaluationProtocolId: 'protocol-1', + resourceBudget, +}; + +describe('generateVariants', () => { + it('generates deterministic parameter, algorithm, and structure variants with risk and rollback data', () => { + const variants = generateVariants(plan, { + 'retrieval.topK': 5, + 'ranking.algorithm': 'baseline-ranker', + }); + + expect(variants).toHaveLength(3); + expect(variants.map((variant) => variant.id)).toEqual([ + 'plan-1-parameter-variant', + 'plan-1-algorithm-variant', + 'plan-1-structure-variant', + ]); + expect(variants.map((variant) => variant.mutationType)).toEqual([ + 'parameter', + 'algorithm', + 'structure', + ]); + expect(variants.map((variant) => variant.riskLevel)).toEqual(['low', 'medium', 'high']); + expect(variants.map((variant) => variant.rollbackPlan.strategy)).toEqual([ + 'restore-previous-parameters', + 'disable-candidate', + 'manual-review-required', + ]); + expect(variants.every((variant) => variant.provenance.length === 1)).toBe(true); + expect(variants.every((variant) => variant.provenance[0]?.source === 'evolver-variant-generator')).toBe( + true, + ); + }); + + it('only generates mutation types allowed by the plan', () => { + const variants = generateVariants( + { + ...plan, + allowedMutationTypes: ['parameter'], + }, + { + 'retrieval.topK': 5, + 'ranking.algorithm': 'baseline-ranker', + }, + ); + + expect(variants).toHaveLength(1); + expect(variants[0]?.mutationType).toBe('parameter'); + expect(variants[0]?.patch.operations).toEqual([ + { + op: 'setParameter', + path: 'retrieval.topK', + previousValue: 5, + nextValue: 6, + }, + ]); + }); +});