/** * Request budget for decision providers — a token bucket shared by every CLEO * process on the machine. * * The provider allows 600 requests/minute per key. Every CLEO process (CLI * invocations, the sentient daemon, spawned agents) draws from ONE bucket whose * state lives in `/decide/budget.json`, guarded by a `proper-lockfile` * lock — the same cross-process state-file pattern as * `llm/rate-limit-guard.ts`, with much shorter lock retries because a decision * has a sub-second budget. * * ## Why capacity 60 + 540/min, not 600 + 600/min * * A bucket of capacity `C` refilled at `r` admits at most `C + r·W` requests in * any window `W`. The defaults (60 + 540/min) keep every 60-second window at or * under 600 even after an idle period, so the budget itself never triggers a * 429. A 429 still happens if the key is shared with non-CLEO clients; then * {@link DecisionBudget.penalize} empties the bucket and blocks every process * until the server's `retry-after` has passed. * * The bucket fails CLOSED: if the lock cannot be taken quickly or the state * cannot be read, the request is denied and the caller falls back to its * heuristic. Exceeding the provider's limit is worse than one fallback. * * @task T12490 * @epic T12486 */ /** Default burst capacity (tokens). */ export declare const DEFAULT_BUDGET_CAPACITY = 60; /** Default refill rate (tokens per minute). Capacity + refill ≤ 600 per minute. */ export declare const DEFAULT_BUDGET_REFILL_PER_MINUTE = 540; /** Cool-down applied after a 429 that carried no usable `retry-after`. */ export declare const DEFAULT_RATE_LIMIT_COOLDOWN_MS = 10000; /** * Longest cool-down any 429 may impose. The bucket is machine-wide, so an * uncapped `retry-after: 999999999` would stop every decision on the machine * for decades. */ export declare const MAX_RATE_LIMIT_COOLDOWN_MS = 60000; /** Result of {@link DecisionBudget.tryAcquire}. */ export type BudgetGrant = { readonly granted: true; } | { readonly granted: false; readonly reason: 'exhausted' | 'cooling_down' | 'unavailable'; }; /** A request budget. Implementations never throw. */ export interface DecisionBudget { /** Take one token if available. */ tryAcquire(): Promise; /** Empty the bucket and block all acquisitions for `retryAfterMs` (after a 429). */ penalize(retryAfterMs?: number): Promise; } /** Bucket parameters. */ export interface TokenBucketOptions { /** Burst capacity. Default {@link DEFAULT_BUDGET_CAPACITY}. */ readonly capacity?: number; /** Refill rate per minute. Default {@link DEFAULT_BUDGET_REFILL_PER_MINUTE}. */ readonly refillPerMinute?: number; /** Wall clock in epoch ms (shared across processes). Default `Date.now`. */ readonly now?: () => number; } /** * A process-local bucket. Useful for tests and for callers that must not touch * the filesystem; it does NOT coordinate with other processes. * * @param opts - Bucket parameters. * @returns A {@link DecisionBudget}. */ export declare function createMemoryTokenBucket(opts?: TokenBucketOptions): DecisionBudget; /** Options for {@link createFileTokenBucket}. */ export interface FileTokenBucketOptions extends TokenBucketOptions { /** State-file path. Default {@link defaultBudgetStatePath}. */ readonly statePath?: string; } /** * Default location of the shared bucket state: `/decide/budget.json`. * * @returns Absolute path. */ export declare function defaultBudgetStatePath(): string; /** * A bucket whose state is shared by every process on the machine through a * locked JSON file. * * @param opts - Bucket parameters and state path. * @returns A {@link DecisionBudget}; `tryAcquire` resolves `unavailable` when * the lock or state file cannot be used. */ export declare function createFileTokenBucket(opts?: FileTokenBucketOptions): DecisionBudget; //# sourceMappingURL=budget.d.ts.map