From 5f09f19726c57b0d67552c5e9ca7bb4b1511f538 Mon Sep 17 00:00:00 2001 From: Trae AI Date: Mon, 25 May 2026 23:52:49 +0800 Subject: [PATCH] Add evolver resource manager --- gitnexus/src/core/evolver/index.ts | 1 + gitnexus/src/core/evolver/resource-manager.ts | 100 +++++++++ .../unit/evolver/resource-manager.test.ts | 191 ++++++++++++++++++ 3 files changed, 292 insertions(+) create mode 100644 gitnexus/src/core/evolver/resource-manager.ts create mode 100644 gitnexus/test/unit/evolver/resource-manager.test.ts diff --git a/gitnexus/src/core/evolver/index.ts b/gitnexus/src/core/evolver/index.ts index ce5cb8234..464c7840a 100644 --- a/gitnexus/src/core/evolver/index.ts +++ b/gitnexus/src/core/evolver/index.ts @@ -1,2 +1,3 @@ export * from './types.js'; export * from './metric-evaluator.js'; +export * from './resource-manager.js'; diff --git a/gitnexus/src/core/evolver/resource-manager.ts b/gitnexus/src/core/evolver/resource-manager.ts new file mode 100644 index 000000000..caaa312a7 --- /dev/null +++ b/gitnexus/src/core/evolver/resource-manager.ts @@ -0,0 +1,100 @@ +import type { ResourceBudget, ResourceUsage, VariantSpec } from './types.js'; + +export interface ResourceUseRequest { + variant: VariantSpec; + requestedUsage: ResourceUsage; + activeRuns: number; + activeHighRiskVariants: number; +} + +export interface ResourceDegradedPlan { + reduceCandidateCount: boolean; + pauseLowPriorityGoals: boolean; + stopNewExploration: boolean; +} + +export interface ResourceDecision { + allowed: boolean; + reason: + | 'within-budget' + | 'duration-budget-exceeded' + | 'compute-budget-exceeded' + | 'api-cost-budget-exceeded' + | 'storage-budget-exceeded' + | 'concurrency-limit-reached' + | 'high-risk-variant-quota-exhausted'; + degradedPlan?: ResourceDegradedPlan; +} + +export function decideResourceUse( + request: ResourceUseRequest, + budget: ResourceBudget, + currentUsage: ResourceUsage, +): ResourceDecision { + if (request.activeRuns >= budget.maxConcurrency) { + return deny('concurrency-limit-reached', { + reduceCandidateCount: false, + pauseLowPriorityGoals: true, + stopNewExploration: false, + }); + } + + if ( + request.variant.riskLevel === 'high' && + request.activeHighRiskVariants >= budget.maxHighRiskVariants + ) { + return deny('high-risk-variant-quota-exhausted', { + reduceCandidateCount: false, + pauseLowPriorityGoals: false, + stopNewExploration: true, + }); + } + + const projectedUsage = addResourceUsage(currentUsage, request.requestedUsage); + + if (projectedUsage.durationMs > budget.maxDurationMs) { + return deny('duration-budget-exceeded', budgetDegradation()); + } + + if (projectedUsage.computeUnits > budget.maxComputeUnits) { + return deny('compute-budget-exceeded', budgetDegradation()); + } + + if (projectedUsage.apiCostUsd > budget.maxApiCostUsd) { + return deny('api-cost-budget-exceeded', budgetDegradation()); + } + + if (projectedUsage.storageBytes > budget.maxStorageBytes) { + return deny('storage-budget-exceeded', budgetDegradation()); + } + + return { + allowed: true, + reason: 'within-budget', + }; +} + +function deny(reason: ResourceDecision['reason'], degradedPlan: ResourceDegradedPlan): ResourceDecision { + return { + allowed: false, + reason, + degradedPlan, + }; +} + +function addResourceUsage(currentUsage: ResourceUsage, requestedUsage: ResourceUsage): ResourceUsage { + return { + durationMs: currentUsage.durationMs + requestedUsage.durationMs, + computeUnits: currentUsage.computeUnits + requestedUsage.computeUnits, + apiCostUsd: currentUsage.apiCostUsd + requestedUsage.apiCostUsd, + storageBytes: currentUsage.storageBytes + requestedUsage.storageBytes, + }; +} + +function budgetDegradation(): ResourceDegradedPlan { + return { + reduceCandidateCount: true, + pauseLowPriorityGoals: false, + stopNewExploration: false, + }; +} diff --git a/gitnexus/test/unit/evolver/resource-manager.test.ts b/gitnexus/test/unit/evolver/resource-manager.test.ts new file mode 100644 index 000000000..1c1a41eb6 --- /dev/null +++ b/gitnexus/test/unit/evolver/resource-manager.test.ts @@ -0,0 +1,191 @@ +import { describe, expect, it } from 'vitest'; +import { decideResourceUse } from '../../../src/core/evolver/index.js'; +import type { ResourceBudget, ResourceUsage, VariantSpec } from '../../../src/core/evolver/index.js'; + +const budget: ResourceBudget = { + maxDurationMs: 1000, + maxComputeUnits: 10, + maxApiCostUsd: 1, + maxStorageBytes: 2048, + maxConcurrency: 2, + maxHighRiskVariants: 1, +}; + +const currentUsage: ResourceUsage = { + durationMs: 100, + computeUnits: 2, + apiCostUsd: 0.1, + storageBytes: 256, +}; + +function variant(overrides: Partial = {}): VariantSpec { + return { + id: 'variant-parameter-1', + planId: 'plan-1', + mutationType: 'parameter', + description: 'Adjust topK', + patch: { + operations: [ + { + op: 'setParameter', + path: 'retrieval.topK', + previousValue: 5, + nextValue: 8, + }, + ], + }, + expectedGain: [ + { + metricId: 'task_success_rate', + expectedDelta: 0.05, + direction: 'increase', + }, + ], + riskLevel: 'low', + rollbackPlan: { + strategy: 'restore-previous-parameters', + operations: [ + { + op: 'setParameter', + path: 'retrieval.topK', + previousValue: 8, + nextValue: 5, + }, + ], + }, + provenance: [ + { + source: 'synthetic-benchmark', + reference: 'run-1', + capturedAt: '2026-05-25T00:00:00.000Z', + }, + ], + ...overrides, + }; +} + +describe('decideResourceUse', () => { + it('allows a request when projected resource use stays within budget', () => { + const decision = decideResourceUse( + { + variant: variant(), + requestedUsage: { + durationMs: 300, + computeUnits: 3, + apiCostUsd: 0.2, + storageBytes: 512, + }, + activeRuns: 1, + activeHighRiskVariants: 0, + }, + budget, + currentUsage, + ); + + expect(decision).toEqual({ + allowed: true, + reason: 'within-budget', + }); + }); + + it('denies a request when projected API cost exceeds budget', () => { + const decision = decideResourceUse( + { + variant: variant(), + requestedUsage: { + durationMs: 100, + computeUnits: 1, + apiCostUsd: 1, + storageBytes: 128, + }, + activeRuns: 0, + activeHighRiskVariants: 0, + }, + budget, + currentUsage, + ); + + expect(decision).toEqual({ + allowed: false, + reason: 'api-cost-budget-exceeded', + degradedPlan: { + reduceCandidateCount: true, + pauseLowPriorityGoals: false, + stopNewExploration: false, + }, + }); + }); + + it('denies a request when concurrency limit is reached', () => { + const decision = decideResourceUse( + { + variant: variant(), + requestedUsage: { + durationMs: 100, + computeUnits: 1, + apiCostUsd: 0, + storageBytes: 128, + }, + activeRuns: 2, + activeHighRiskVariants: 0, + }, + budget, + currentUsage, + ); + + expect(decision).toEqual({ + allowed: false, + reason: 'concurrency-limit-reached', + degradedPlan: { + reduceCandidateCount: false, + pauseLowPriorityGoals: true, + stopNewExploration: false, + }, + }); + }); + + it('denies a high-risk request when high-risk quota is exhausted', () => { + const decision = decideResourceUse( + { + variant: variant({ + id: 'variant-structure-1', + mutationType: 'structure', + riskLevel: 'high', + patch: { + operations: [ + { + op: 'proposeStructureChange', + planPath: 'docs/aegis/plans/structure-change.md', + summary: 'Split evaluator into a plugin pipeline', + }, + ], + }, + rollbackPlan: { + strategy: 'manual-review-required', + operations: [], + }, + }), + requestedUsage: { + durationMs: 100, + computeUnits: 1, + apiCostUsd: 0, + storageBytes: 128, + }, + activeRuns: 0, + activeHighRiskVariants: 1, + }, + budget, + currentUsage, + ); + + expect(decision).toEqual({ + allowed: false, + reason: 'high-risk-variant-quota-exhausted', + degradedPlan: { + reduceCandidateCount: false, + pauseLowPriorityGoals: false, + stopNewExploration: true, + }, + }); + }); +});