tasq/node_modules/@claude-flow/guidance/dist/coherence.js

372 lines
14 KiB
JavaScript

/**
* Coherence Scheduler & Economic Governor
*
* Detects drift in agent behavior and enforces resource budgets.
*
* CoherenceScheduler:
* - Computes a coherence score from violation rate, rework, and intent drift
* - Maps scores to privilege levels (full, restricted, read-only, suspended)
* - Tracks score history and provides human-readable recommendations
*
* EconomicGovernor:
* - Tracks token usage, tool calls, storage, and time
* - Checks budgets and emits alerts when thresholds are crossed
* - Estimates remaining capacity and costs
*
* @module @claude-flow/guidance/coherence
*/
// ============================================================================
// Default Configurations
// ============================================================================
const DEFAULT_THRESHOLDS = {
readOnlyThreshold: 0.3,
warningThreshold: 0.5,
healthyThreshold: 0.7,
privilegeEscalationThreshold: 0.9,
};
const DEFAULT_ECONOMIC_CONFIG = {
tokenLimit: 1_000_000,
toolCallLimit: 10_000,
storageLimit: 1_073_741_824, // 1 GiB
timeLimit: 3_600_000, // 1 hour
costPerToken: 0.000003, // $3 per million tokens
costPerToolCall: 0.0001,
costLimit: 10, // $10 USD
};
/**
* Computes coherence scores from run metrics and events, determines privilege
* levels, and provides recommendations when drift is detected.
*/
export class CoherenceScheduler {
thresholds;
windowSize;
checkIntervalMs;
scoreHistory = [];
static MAX_HISTORY = 100;
constructor(config = {}) {
this.thresholds = { ...DEFAULT_THRESHOLDS, ...config.thresholds };
this.windowSize = config.windowSize ?? 20;
this.checkIntervalMs = config.checkIntervalMs ?? 30_000;
}
/**
* Compute a coherence score from optimization metrics and recent events.
*
* Components:
* - violationComponent: 1 - (violationRate / 10) clamped to [0, 1]
* - reworkComponent: 1 - (reworkLines / 100) clamped to [0, 1]
* - driftComponent: intent consistency (fewer unique intents relative to window = higher)
* - overall: weighted average (0.4 * violation + 0.3 * rework + 0.3 * drift)
*/
computeCoherence(metrics, recentEvents) {
const window = recentEvents.slice(-this.windowSize);
const windowLen = window.length;
// Violation component: fewer violations per 10 tasks = better
const violationComponent = clamp(1 - metrics.violationRate / 10, 0, 1);
// Rework component: fewer rework lines on average = better
const reworkComponent = clamp(1 - metrics.reworkLines / 100, 0, 1);
// Drift component: consistent intents = better
// A single unique intent across N events means perfect consistency
let driftComponent;
if (windowLen === 0) {
driftComponent = 1; // No events, assume no drift
}
else {
const uniqueIntents = new Set(window.map(e => e.intent)).size;
// 1 unique intent / N events = score 1; N unique / N events = score approaches 0
driftComponent = clamp(1 - (uniqueIntents - 1) / Math.max(windowLen - 1, 1), 0, 1);
}
const overall = 0.4 * violationComponent +
0.3 * reworkComponent +
0.3 * driftComponent;
const score = {
overall,
violationComponent,
reworkComponent,
driftComponent,
timestamp: Date.now(),
windowSize: windowLen,
};
this.scoreHistory.push(score);
if (this.scoreHistory.length > CoherenceScheduler.MAX_HISTORY) {
this.scoreHistory.shift();
}
return score;
}
/**
* Determine the privilege level from a coherence score.
*
* - overall >= healthyThreshold (0.7): 'full'
* - overall >= warningThreshold (0.5): 'restricted'
* - overall >= readOnlyThreshold (0.3): 'read-only'
* - below readOnlyThreshold: 'suspended'
*/
getPrivilegeLevel(score) {
if (score.overall >= this.thresholds.healthyThreshold) {
return 'full';
}
if (score.overall >= this.thresholds.warningThreshold) {
return 'restricted';
}
if (score.overall >= this.thresholds.readOnlyThreshold) {
return 'read-only';
}
return 'suspended';
}
/**
* Return the last 100 coherence scores (most recent last).
*/
getScoreHistory() {
return [...this.scoreHistory];
}
/**
* Whether the score indicates healthy coherence.
*/
isHealthy(score) {
return score.overall >= this.thresholds.healthyThreshold;
}
/**
* Whether the score indicates drift (below warning threshold).
*/
isDrifting(score) {
return score.overall < this.thresholds.warningThreshold;
}
/**
* Whether the score warrants restricting agent actions.
*/
shouldRestrict(score) {
return score.overall < this.thresholds.warningThreshold;
}
/**
* Produce a human-readable recommendation based on the coherence score.
*/
getRecommendation(score) {
const level = this.getPrivilegeLevel(score);
const parts = [];
switch (level) {
case 'full':
parts.push(`Coherence is healthy at ${(score.overall * 100).toFixed(1)}%.`);
if (score.overall >= this.thresholds.privilegeEscalationThreshold) {
parts.push('Privilege escalation is permitted.');
}
break;
case 'restricted':
parts.push(`Coherence is degraded at ${(score.overall * 100).toFixed(1)}%. Agent privileges are restricted.`);
break;
case 'read-only':
parts.push(`Coherence is critically low at ${(score.overall * 100).toFixed(1)}%. Agent is limited to read-only operations.`);
break;
case 'suspended':
parts.push(`Coherence has collapsed to ${(score.overall * 100).toFixed(1)}%. Agent operations are suspended.`);
break;
}
// Add component-specific advice
if (score.violationComponent < 0.5) {
parts.push(`High violation rate detected (component: ${(score.violationComponent * 100).toFixed(0)}%). Review and strengthen enforcement gates.`);
}
if (score.reworkComponent < 0.5) {
parts.push(`Excessive rework detected (component: ${(score.reworkComponent * 100).toFixed(0)}%). Consider more prescriptive guidance or smaller task scopes.`);
}
if (score.driftComponent < 0.5) {
parts.push(`Intent drift detected (component: ${(score.driftComponent * 100).toFixed(0)}%). Agent is switching between too many task types. Focus on a single objective.`);
}
return parts.join(' ');
}
/**
* Get the configured check interval in milliseconds.
*/
get interval() {
return this.checkIntervalMs;
}
/**
* Get the configured thresholds.
*/
getThresholds() {
return { ...this.thresholds };
}
}
/**
* Tracks resource consumption (tokens, tool calls, storage, time, cost)
* and enforces budget limits with alerts.
*/
export class EconomicGovernor {
config;
tokensUsed = 0;
toolCallsUsed = 0;
storageUsed = 0;
toolCallLog = [];
startTime;
periodStart;
static ALERT_THRESHOLDS = [0.75, 0.9, 0.95, 1.0];
constructor(config = {}) {
this.config = {
tokenLimit: config.tokenLimit ?? DEFAULT_ECONOMIC_CONFIG.tokenLimit,
toolCallLimit: config.toolCallLimit ?? DEFAULT_ECONOMIC_CONFIG.toolCallLimit,
storageLimit: config.storageLimit ?? DEFAULT_ECONOMIC_CONFIG.storageLimit,
timeLimit: config.timeLimit ?? DEFAULT_ECONOMIC_CONFIG.timeLimit,
costPerToken: config.costPerToken ?? DEFAULT_ECONOMIC_CONFIG.costPerToken,
costPerToolCall: config.costPerToolCall ?? DEFAULT_ECONOMIC_CONFIG.costPerToolCall,
costLimit: config.costLimit ?? DEFAULT_ECONOMIC_CONFIG.costLimit,
};
this.startTime = Date.now();
this.periodStart = Date.now();
}
/**
* Record token consumption.
*/
recordTokenUsage(count) {
this.tokensUsed += count;
}
/**
* Record a tool call with its name and duration.
*/
recordToolCall(toolName, durationMs) {
this.toolCallsUsed++;
this.toolCallLog.push({
toolName,
durationMs,
timestamp: Date.now(),
});
}
/**
* Record storage usage in bytes.
*/
recordStorageUsage(bytes) {
this.storageUsed += bytes;
}
/**
* Check whether current usage is within budget limits.
* Returns a summary with alerts for any limits that are near or exceeded.
*/
checkBudget() {
const usage = this.getUsageSummary();
const alerts = [];
// Check each dimension against alert thresholds
const dimensions = [
{ name: 'tokens', percentage: usage.tokens.percentage },
{ name: 'tool calls', percentage: usage.toolCalls.percentage },
{ name: 'storage', percentage: usage.storage.percentage },
{ name: 'time', percentage: usage.time.percentage },
{ name: 'cost', percentage: usage.cost.percentage },
];
let withinBudget = true;
for (const dim of dimensions) {
if (dim.percentage >= 100) {
alerts.push(`BUDGET EXCEEDED: ${dim.name} at ${dim.percentage.toFixed(1)}% of limit`);
withinBudget = false;
}
else if (dim.percentage >= 95) {
alerts.push(`CRITICAL: ${dim.name} at ${dim.percentage.toFixed(1)}% of limit`);
}
else if (dim.percentage >= 90) {
alerts.push(`WARNING: ${dim.name} at ${dim.percentage.toFixed(1)}% of limit`);
}
else if (dim.percentage >= 75) {
alerts.push(`NOTICE: ${dim.name} at ${dim.percentage.toFixed(1)}% of limit`);
}
}
return { withinBudget, usage, alerts };
}
/**
* Get a full usage summary across all tracked dimensions.
*/
getUsageSummary() {
const elapsedMs = Date.now() - this.periodStart;
const costEstimate = this.getCostEstimate();
return {
tokens: {
used: this.tokensUsed,
limit: this.config.tokenLimit,
percentage: safePercentage(this.tokensUsed, this.config.tokenLimit),
},
toolCalls: {
used: this.toolCallsUsed,
limit: this.config.toolCallLimit,
percentage: safePercentage(this.toolCallsUsed, this.config.toolCallLimit),
},
storage: {
usedBytes: this.storageUsed,
limitBytes: this.config.storageLimit,
percentage: safePercentage(this.storageUsed, this.config.storageLimit),
},
time: {
usedMs: elapsedMs,
limitMs: this.config.timeLimit,
percentage: safePercentage(elapsedMs, this.config.timeLimit),
},
cost: {
totalUsd: costEstimate.totalCost,
limitUsd: this.config.costLimit,
percentage: safePercentage(costEstimate.totalCost, this.config.costLimit),
},
};
}
/**
* Reset all counters for a new billing/tracking period.
*/
resetPeriod() {
this.tokensUsed = 0;
this.toolCallsUsed = 0;
this.storageUsed = 0;
this.toolCallLog.length = 0;
this.periodStart = Date.now();
}
/**
* Estimate remaining capacity before hitting limits.
*/
estimateRemainingCapacity() {
const elapsedMs = Date.now() - this.periodStart;
return {
tokensRemaining: Math.max(0, this.config.tokenLimit - this.tokensUsed),
callsRemaining: Math.max(0, this.config.toolCallLimit - this.toolCallsUsed),
timeRemainingMs: Math.max(0, this.config.timeLimit - elapsedMs),
};
}
/**
* Compute a cost estimate with a breakdown by category.
*/
getCostEstimate() {
const tokenCost = this.tokensUsed * this.config.costPerToken;
const toolCallCost = this.toolCallsUsed * this.config.costPerToolCall;
const breakdown = {
tokens: tokenCost,
toolCalls: toolCallCost,
};
return {
totalCost: tokenCost + toolCallCost,
breakdown,
};
}
/**
* Get the raw tool call log.
*/
getToolCallLog() {
return this.toolCallLog;
}
}
// ============================================================================
// Factory Functions
// ============================================================================
/**
* Create a CoherenceScheduler with optional configuration.
*/
export function createCoherenceScheduler(config) {
return new CoherenceScheduler(config);
}
/**
* Create an EconomicGovernor with optional configuration.
*/
export function createEconomicGovernor(config) {
return new EconomicGovernor(config);
}
// ============================================================================
// Helpers
// ============================================================================
function clamp(value, min, max) {
return Math.min(max, Math.max(min, value));
}
function safePercentage(used, limit) {
if (limit <= 0)
return 0;
return (used / limit) * 100;
}
//# sourceMappingURL=coherence.js.map