mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-06 02:49:56 +00:00
Add evolver runtime
This commit is contained in:
parent
2f5c508b33
commit
5cf3a225ed
3 changed files with 330 additions and 0 deletions
199
gitnexus/src/core/evolver/evolver-runtime.ts
Normal file
199
gitnexus/src/core/evolver/evolver-runtime.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
|
|
@ -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';
|
||||
|
|
|
|||
130
gitnexus/test/unit/evolver/evolver-runtime.test.ts
Normal file
130
gitnexus/test/unit/evolver/evolver-runtime.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue