/** * [WHO]: Daily reservation ledger for the extension's only model call, refused before the call is made and serialized across processes by an identity-checked exclusive lock * [FROM]: Depends on Node fs/path and no model or network access * [TO]: Consumed by evolution-refiner, the only caller of completeSimple in this extension * [HERE]: extensions/optional/evolution/evolution-budget.ts - reserve-before-call budget guard * * The point of reserving rather than checking is the ordering. If the cap is consulted and only * charged afterwards, two callers in the same tick both read a state that still has room, both * spend, and the cap is exceeded by exactly the amount it was supposed to prevent. So the * reservation is written first and the call follows it; a caller that cannot reserve never reaches * the model at all. * * A reservation that is never spent — the call throws, or the process dies — stays charged. That is * deliberate. This ledger measures attempts, not successes, because whether a failed completion * billed anything is not knowable from here, and under-counting is the direction that lets a runaway * loop run up a bill. Over-counting a failed call is recoverable the next day; an uncounted one is * not. * * Token and cost figures here are estimates reserved against a daily cap, never measurements. They * exist to bound a loop, not to bill anyone. */ export interface EvolutionBudgetPolicy { schemaVersion: 1; /** Calls allowed per UTC day. The hard cap; the estimate below is a second, softer limit. */ dailyCallBudget: number; estimatedTokensPerCall: number; dailyEstimatedTokenBudget: number; } export interface EvolutionBudgetState { schemaVersion: 1; day: string; calls: number; estimatedTokens: number; } /** * The two caps are kept in agreement on purpose: 5 calls at 8,000 estimated tokens each is exactly * the 40,000-token daily reservation the source-side policy already documents. An earlier version set * the call cap to 20 against the same token budget, which made 20 unreachable and left a reader to * work out which limit actually binds. A cap nobody can reason about is a cap nobody trusts. */ export declare const DEFAULT_EVOLUTION_BUDGET: Readonly; export type BudgetReservation = { reserved: true; day: string; calls: number; remainingCalls: number; } | { reserved: false; reason: "budget_exhausted" | "budget_locked"; day: string; calls: number; message: string; }; /** * Reserves one model call, or refuses. * * Synchronous on purpose: the read-decide-write must be atomic with respect to the caller's next * await, which is what stops two refinements in one process from both seeing the same headroom. * Across processes the same job is done by `withBudgetLock`, so the cap holds for cooperating * processes too — with one deliberate exception stated where it is enforced: a lock left behind by a * process that died is *not* taken over, because POSIX has no atomic compare-and-remove and a * recovery that guesses would delete a live owner's replacement. That case ends in a visible * refusal, not an overspend. */ export declare function reserveEvolutionModelCall(agentDir: string, policy?: Readonly, now?: Date): BudgetReservation; export interface EvolutionBudgetReport { day: string; /** null when the ledger is unreadable. Never a fabricated zero. */ calls: number | null; estimatedTokens: number | null; corrupt: boolean; file: string; } /** * A view for reporting, and nothing else: it reserves nothing and touches no file. * * `corrupt` is reported as its own field and the usage fields become null, because a reader that * cannot parse the ledger must not present "0 calls used" as fact. That number is what a * corruption-blocked budget looks like, and a status display that prints it is indistinguishable * from a fresh allowance. */ export declare function readEvolutionBudget(agentDir: string, now?: Date): EvolutionBudgetReport;