diff --git a/gitnexus/src/core/evolver/evolver-runtime.ts b/gitnexus/src/core/evolver/evolver-runtime.ts new file mode 100644 index 000000000..13a350fc2 --- /dev/null +++ b/gitnexus/src/core/evolver/evolver-runtime.ts @@ -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; + 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; +} + +export function createEvolverRuntime(config: EvolverRuntimeConfig): EvolverRuntime { + return { + async runEvolution( + goal: GoalSpec, + snapshot: EnvironmentSnapshot, + baseline: BaselineProfile, + ): Promise { + 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, + }); +} diff --git a/gitnexus/src/core/evolver/index.ts b/gitnexus/src/core/evolver/index.ts index 5b4dc683d..f3a0c7b28 100644 --- a/gitnexus/src/core/evolver/index.ts +++ b/gitnexus/src/core/evolver/index.ts @@ -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'; diff --git a/gitnexus/test/unit/evolver/evolver-runtime.test.ts b/gitnexus/test/unit/evolver/evolver-runtime.test.ts new file mode 100644 index 000000000..dced1d9d3 --- /dev/null +++ b/gitnexus/test/unit/evolver/evolver-runtime.test.ts @@ -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); + }); +});