SPB Git

Repository Public

governors.ts 4.1 KB 77 lines typescript

1/**2 * Governors (CONTRACT §5.4 / §5.6): turn, wall-clock and money limits, and the context-window guard.3 * Pure functions of replay-deterministic inputs (`ctx.budgetLeft()`, `ctx.usage()`, journaled usage).4 *5 * The kernel hard-stops model calls on usd / tokens / mission budget / daily cap; turns and wall time6 * are advisory there and enforced here.7 */8import type { Budget, Usage } from '@aos/shared';910export type LimitKind = 'usd' | 'turns' | 'wallSec' | 'tokens';1112export interface BudgetView {13  /** Largest used fraction over all limits (≥ 1 ⇒ exhausted). */14  fraction: number;15  /** The limit that is closest to (or past) its end. */16  limit: LimitKind;17  exhausted: boolean;18  /** One line for notes and summaries, e.g. "turns: 18 of 20 used". */19  describe: string;20}2122export interface BudgetInput {23  budget: Budget;24  left: { usd: number; turns: number; wallSec: number };25  usage: Usage;26  /** Seconds spent suspended in owner waits (approvals, questions): not charged against the wall limit. */27  waitedSec: number;28}2930const pct = (f: number): string => `${Math.min(999, Math.round(f * 100))} %`;3132export function budgetView(i: BudgetInput): BudgetView {33  const { budget, left, usage } = i;34  const turnsLimit = budget.turns ?? 200;35  const wallLimit = budget.wallSec ?? 6 * 3600;36  const wallUsed = Math.max(0, wallLimit - left.wallSec - i.waitedSec);37  const parts: { limit: LimitKind; fraction: number; describe: string }[] = [38    { limit: 'usd', fraction: (budget.usd - left.usd) / budget.usd, describe: `money: USD ${(budget.usd - left.usd).toFixed(4)} of ${budget.usd.toFixed(2)} used` },39    { limit: 'turns', fraction: (turnsLimit - left.turns) / turnsLimit, describe: `turns: ${turnsLimit - left.turns} of ${turnsLimit} used` },40    { limit: 'wallSec', fraction: wallUsed / wallLimit, describe: `time: ${Math.round(wallUsed)} s of ${wallLimit} s used` },41  ];42  if (budget.tokens) {43    const used = usage.inputTokens + usage.outputTokens;44    parts.push({ limit: 'tokens', fraction: used / budget.tokens, describe: `tokens: ${used} of ${budget.tokens} used` });45  }46  let worst = parts[0] as (typeof parts)[number];47  for (const p of parts) if (p.fraction > worst.fraction) worst = p;48  return { fraction: worst.fraction, limit: worst.limit, exhausted: worst.fraction >= 1 - 1e-9, describe: `${worst.describe} (${pct(worst.fraction)})` };49}5051/**52 * The single final turn is due when a limit is ≥ `finalFraction` used, or when only one turn is left53 * (so a small turn limit still gets its summarising turn inside the limit).54 */55export function finalTurnDue(view: BudgetView, left: { turns: number }, finalFraction: number): boolean {56  return view.fraction >= finalFraction - 1e-9 || left.turns <= 1;57}5859export const FINAL_TURN_NOTE = (view: BudgetView): string =>60  `Budget nearly exhausted — ${view.describe}. This is your FINAL turn: no further tool call will be executed except \`finish\`. ` +61  'Call `finish` now: status "done" only if every done criterion already passes with evidence, otherwise "partial" with the reason "budget exhausted". ' +62  'In the summary, state precisely what is finished, what is committed, what is in progress and the next step, so the work can be resumed.';6364export type ContextLevel = 'ok' | 'warn' | 'stop';6566/** Context guard: `nextPromptTokens` = prompt tokens of the last call + its output (what the next call will at least carry). */67export function contextLevel(nextPromptTokens: number, windowTokens: number | null, warn: number, stop: number): { level: ContextLevel; fraction: number } {68  if (!windowTokens || windowTokens <= 0) return { level: 'ok', fraction: 0 };69  const fraction = nextPromptTokens / windowTokens;70  return { level: fraction >= stop ? 'stop' : fraction >= warn ? 'warn' : 'ok', fraction };71}7273export const CONTEXT_WARN_NOTE = (fraction: number): string =>74  `The conversation now fills about ${pct(fraction)} of the model's context window and cannot be compacted in this version. Wrap up: ` +75  'finish the unit of work in progress, commit it, record verified criteria with update_criteria, then call `finish` ("partial" if work remains, with the exact next steps). ' +76  'Avoid large reads and verbose command output from here on.';77
Files main
Menu

Theme