Ограничивайте каждую ветвь и весь run
Max turns недостаточно. Нужны независимые лимиты calls, tokens, cost, wall-clock time, fan-out depth и side effects. Multi-agent run может закончиться логически (пройден acceptance gate), технически (fatal error или cancellation) или экономически (следующий шаг больше не укладывается в budget) - и все три исхода должны быть предусмотрены.
import type { BudgetLimits, BudgetSnapshot, Usage } from "./types.js";
export class BudgetExceededError extends Error {
public constructor(message: string) {
super(message);
this.name = "BudgetExceededError";
}
}
export class BudgetLedger {
readonly #limits: BudgetLimits;
#usedCalls = 0;
#usedTokens = 0;
#usedCostUnits = 0;
#usedSimulatedLatencyMs = 0;
public constructor(limits: BudgetLimits) {
this.#limits = limits;
}
public consume(usage: Usage): void {
const next = {
calls: this.#usedCalls + usage.calls,
tokens: this.#usedTokens + usage.tokens,
costUnits: this.#usedCostUnits + usage.costUnits,
latency: this.#usedSimulatedLatencyMs + usage.simulatedLatencyMs
};
if (next.calls > this.#limits.maxCalls) {
throw new BudgetExceededError("Agent call budget exceeded");
}
if (next.tokens > this.#limits.maxTokens) {
throw new BudgetExceededError("Token budget exceeded");
}
if (next.costUnits > this.#limits.maxCostUnits) {
throw new BudgetExceededError("Cost budget exceeded");
}
if (next.latency > this.#limits.maxSimulatedLatencyMs) {
throw new BudgetExceededError("Latency budget exceeded");
}
this.#usedCalls = next.calls;
this.#usedTokens = next.tokens;
this.#usedCostUnits = next.costUnits;
this.#usedSimulatedLatencyMs = next.latency;
}
public remainingCalls(): number {
return this.#limits.maxCalls - this.#usedCalls;
}
public snapshot(): BudgetSnapshot {
return {
...this.#limits,
usedCalls: this.#usedCalls,
usedTokens: this.#usedTokens,
usedCostUnits: this.#usedCostUnits,
usedSimulatedLatencyMs: this.#usedSimulatedLatencyMs
};
}
}Лимиты стоит расставить по уровням, чтобы одна плохая ветвь не утащила весь run.
Уровень · Лимит · Зачем
- Tool call - Timeout, output cap, retry count; Один плохой tool не удерживает worker
- Worker - Calls, tokens, time, local attempts; Specialist не расходует весь run
- Fan-out - Workers, concurrency, depth; Planner не создает рекурсивный взрыв
- Run - Total cost, deadline, side-effect count; Глобальная верхняя граница
- User or tenant - Rate и daily spend; Один actor не истощает сервис
Anthropic описывает ранний failure mode, где lead agent мог породить около 50 subagents для простого запроса, и рекомендует масштабировать effort по сложности; AutoGen предоставляет явные termination conditions и max turns для teams. Конкретные числа зависят от домена, но наличие policy не зависит от framework.
Не возвращайте пустое "не успел". При budget stop сохраняйте completed artifacts, unresolved work items, причины остановки и безопасный resume point. Частичный результат должен быть явно помечен partial.
Лимиты защищают один run. Но длинная задача переживает крах процесса, только если orchestration отделён от agent loop и durable.