docs: Add overlap analysis for non-AI coordinator patterns
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Detailed comparison showing: - Existing doc addresses L-015 (premature completion) - New doc addresses context exhaustion (multi-issue orchestration) - ~20% overlap (both use non-AI coordinator, mechanical gates) - 80% complementary (different problems, different solutions) Recommends merging into comprehensive document (already done). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
256
apps/api/src/token-budget/token-budget.service.ts
Normal file
256
apps/api/src/token-budget/token-budget.service.ts
Normal file
@@ -0,0 +1,256 @@
|
||||
import { Injectable, Logger, NotFoundException } from "@nestjs/common";
|
||||
import { PrismaService } from "../prisma/prisma.service";
|
||||
import type { TokenBudget } from "@prisma/client";
|
||||
import type { TaskComplexity, BudgetAnalysis } from "./interfaces";
|
||||
import { COMPLEXITY_BUDGETS, BUDGET_THRESHOLDS } from "./interfaces";
|
||||
import type { AllocateBudgetDto } from "./dto";
|
||||
import { BudgetAnalysisDto } from "./dto";
|
||||
|
||||
/**
|
||||
* Token Budget Service
|
||||
* Tracks token usage and prevents premature done claims with significant budget remaining
|
||||
*/
|
||||
@Injectable()
|
||||
export class TokenBudgetService {
|
||||
private readonly logger = new Logger(TokenBudgetService.name);
|
||||
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
/**
|
||||
* Allocate budget for a new task
|
||||
*/
|
||||
async allocateBudget(dto: AllocateBudgetDto): Promise<TokenBudget> {
|
||||
this.logger.log(`Allocating ${String(dto.allocatedTokens)} tokens for task ${dto.taskId}`);
|
||||
|
||||
const budget = await this.prisma.tokenBudget.create({
|
||||
data: {
|
||||
taskId: dto.taskId,
|
||||
workspaceId: dto.workspaceId,
|
||||
agentId: dto.agentId,
|
||||
allocatedTokens: dto.allocatedTokens,
|
||||
estimatedComplexity: dto.complexity,
|
||||
},
|
||||
});
|
||||
|
||||
return budget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update usage after agent response
|
||||
*/
|
||||
async updateUsage(
|
||||
taskId: string,
|
||||
inputTokens: number,
|
||||
outputTokens: number
|
||||
): Promise<TokenBudget> {
|
||||
this.logger.debug(
|
||||
`Updating usage for task ${taskId}: +${String(inputTokens)} input, +${String(outputTokens)} output`
|
||||
);
|
||||
|
||||
// Get current budget
|
||||
const budget = await this.prisma.tokenBudget.findUnique({
|
||||
where: { taskId },
|
||||
});
|
||||
|
||||
if (!budget) {
|
||||
throw new NotFoundException(`Token budget not found for task ${taskId}`);
|
||||
}
|
||||
|
||||
// Calculate new totals
|
||||
const newInputTokens = budget.inputTokensUsed + inputTokens;
|
||||
const newOutputTokens = budget.outputTokensUsed + outputTokens;
|
||||
const newTotalTokens = newInputTokens + newOutputTokens;
|
||||
|
||||
// Calculate utilization
|
||||
const utilization = newTotalTokens / budget.allocatedTokens;
|
||||
|
||||
// Update budget
|
||||
const updatedBudget = await this.prisma.tokenBudget.update({
|
||||
where: { taskId },
|
||||
data: {
|
||||
inputTokensUsed: newInputTokens,
|
||||
outputTokensUsed: newOutputTokens,
|
||||
totalTokensUsed: newTotalTokens,
|
||||
budgetUtilization: utilization,
|
||||
},
|
||||
});
|
||||
|
||||
return updatedBudget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze budget for suspicious patterns
|
||||
*/
|
||||
async analyzeBudget(taskId: string): Promise<BudgetAnalysis> {
|
||||
this.logger.debug(`Analyzing budget for task ${taskId}`);
|
||||
|
||||
const budget = await this.prisma.tokenBudget.findUnique({
|
||||
where: { taskId },
|
||||
});
|
||||
|
||||
if (!budget) {
|
||||
throw new NotFoundException(`Token budget not found for task ${taskId}`);
|
||||
}
|
||||
|
||||
const usedTokens = budget.totalTokensUsed;
|
||||
const allocatedTokens = budget.allocatedTokens;
|
||||
const remainingTokens = allocatedTokens - usedTokens;
|
||||
const utilizationPercentage = (usedTokens / allocatedTokens) * 100;
|
||||
|
||||
// Detect suspicious patterns
|
||||
const suspiciousPattern = this.detectSuspiciousPattern(budget);
|
||||
|
||||
// Determine recommendation
|
||||
let recommendation: "accept" | "continue" | "review";
|
||||
if (suspiciousPattern.triggered) {
|
||||
if (suspiciousPattern.severity === "high") {
|
||||
recommendation = "continue";
|
||||
} else {
|
||||
recommendation = "review";
|
||||
}
|
||||
} else {
|
||||
recommendation = "accept";
|
||||
}
|
||||
|
||||
return new BudgetAnalysisDto({
|
||||
taskId,
|
||||
allocatedTokens,
|
||||
usedTokens,
|
||||
remainingTokens,
|
||||
utilizationPercentage,
|
||||
suspiciousPattern: suspiciousPattern.triggered,
|
||||
suspiciousReason: suspiciousPattern.reason ?? null,
|
||||
recommendation,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if done claim is suspicious (>20% budget remaining)
|
||||
*/
|
||||
async checkSuspiciousDoneClaim(
|
||||
taskId: string
|
||||
): Promise<{ suspicious: boolean; reason?: string }> {
|
||||
this.logger.debug(`Checking done claim for task ${taskId}`);
|
||||
|
||||
const budget = await this.prisma.tokenBudget.findUnique({
|
||||
where: { taskId },
|
||||
});
|
||||
|
||||
if (!budget) {
|
||||
throw new NotFoundException(`Token budget not found for task ${taskId}`);
|
||||
}
|
||||
|
||||
const suspiciousPattern = this.detectSuspiciousPattern(budget);
|
||||
|
||||
if (suspiciousPattern.triggered && suspiciousPattern.reason) {
|
||||
return {
|
||||
suspicious: true,
|
||||
reason: suspiciousPattern.reason,
|
||||
};
|
||||
}
|
||||
|
||||
if (suspiciousPattern.triggered) {
|
||||
return {
|
||||
suspicious: true,
|
||||
};
|
||||
}
|
||||
|
||||
return { suspicious: false };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get budget utilization percentage
|
||||
*/
|
||||
async getBudgetUtilization(taskId: string): Promise<number> {
|
||||
const budget = await this.prisma.tokenBudget.findUnique({
|
||||
where: { taskId },
|
||||
});
|
||||
|
||||
if (!budget) {
|
||||
throw new NotFoundException(`Token budget not found for task ${taskId}`);
|
||||
}
|
||||
|
||||
const utilizationPercentage = (budget.totalTokensUsed / budget.allocatedTokens) * 100;
|
||||
|
||||
return utilizationPercentage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark task as completed
|
||||
*/
|
||||
async markCompleted(taskId: string): Promise<void> {
|
||||
this.logger.log(`Marking budget as completed for task ${taskId}`);
|
||||
|
||||
const budget = await this.prisma.tokenBudget.findUnique({
|
||||
where: { taskId },
|
||||
});
|
||||
|
||||
if (!budget) {
|
||||
throw new NotFoundException(`Token budget not found for task ${taskId}`);
|
||||
}
|
||||
|
||||
await this.prisma.tokenBudget.update({
|
||||
where: { taskId },
|
||||
data: {
|
||||
completedAt: new Date(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get complexity-based budget allocation
|
||||
*/
|
||||
getDefaultBudgetForComplexity(complexity: TaskComplexity): number {
|
||||
return COMPLEXITY_BUDGETS[complexity];
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect suspicious patterns in budget usage
|
||||
* @private
|
||||
*/
|
||||
private detectSuspiciousPattern(budget: TokenBudget): {
|
||||
triggered: boolean;
|
||||
reason?: string;
|
||||
severity: "low" | "medium" | "high";
|
||||
recommendation: "accept" | "continue" | "review";
|
||||
} {
|
||||
const utilization = budget.totalTokensUsed / budget.allocatedTokens;
|
||||
const remainingPercentage = (1 - utilization) * 100;
|
||||
|
||||
// Pattern 1: Very low utilization (<10%)
|
||||
if (utilization < BUDGET_THRESHOLDS.VERY_LOW_UTILIZATION) {
|
||||
return {
|
||||
triggered: true,
|
||||
reason: `Very low budget utilization (${(utilization * 100).toFixed(1)}%). This suggests minimal work was performed.`,
|
||||
severity: "high",
|
||||
recommendation: "continue",
|
||||
};
|
||||
}
|
||||
|
||||
// Pattern 2: Done claimed with >20% budget remaining
|
||||
if (utilization < 1 - BUDGET_THRESHOLDS.SUSPICIOUS_REMAINING) {
|
||||
return {
|
||||
triggered: true,
|
||||
reason: `Task claimed done with ${remainingPercentage.toFixed(1)}% budget remaining (${String(budget.allocatedTokens - budget.totalTokensUsed)} tokens). This may indicate premature completion.`,
|
||||
severity: "medium",
|
||||
recommendation: "review",
|
||||
};
|
||||
}
|
||||
|
||||
// Pattern 3: Extremely high utilization (>95%) - might indicate inefficiency
|
||||
if (utilization > BUDGET_THRESHOLDS.VERY_HIGH_UTILIZATION) {
|
||||
return {
|
||||
triggered: true,
|
||||
reason: `Very high budget utilization (${(utilization * 100).toFixed(1)}%). Task may need more budget or review for efficiency.`,
|
||||
severity: "low",
|
||||
recommendation: "review",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
triggered: false,
|
||||
severity: "low",
|
||||
recommendation: "accept",
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user