From 9c298d0596899042435f95f90b5fa5f960321d18 Mon Sep 17 00:00:00 2001 From: Fusion Date: Thu, 14 May 2026 07:31:48 -0700 Subject: [PATCH] =?UTF-8?q?feat(FN-4396):=20complete=20Step=202=20?= =?UTF-8?q?=E2=80=94=20add=20token=20budget=20types=20and=20defaults?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fusion-Task-Id: FN-4396 Fusion-Task-Lineage: 3839210b-b443-410d-a476-aa81128c739a --- packages/core/src/settings-schema.ts | 3 +++ packages/core/src/types.ts | 30 ++++++++++++++++++++++++++++ packages/engine/src/notifier.ts | 1 + 3 files changed, 34 insertions(+) diff --git a/packages/core/src/settings-schema.ts b/packages/core/src/settings-schema.ts index 45316c8da..fa3ed07ca 100644 --- a/packages/core/src/settings-schema.ts +++ b/packages/core/src/settings-schema.ts @@ -37,8 +37,10 @@ export const DEFAULT_GLOBAL_SETTINGS = { "gridlock", "fallback-used", "memory-dreams-processed", + "token-budget", ], ntfyDashboardHost: undefined, + taskTokenBudget: undefined, failureNotificationDelayMs: 30000, failureNotificationMode: "sticky-only", webhookEnabled: false, @@ -303,6 +305,7 @@ export const DEFAULT_PROJECT_SETTINGS = { memoryDreamsEnabled: false, memoryDreamsSchedule: "0 4 * * *", tokenCap: undefined, + taskTokenBudget: undefined, runStepsInNewSessions: false, maxParallelSteps: 2, missionStaleThresholdMs: 600_000, diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index d1b071094..5c9e49a78 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -298,6 +298,7 @@ export type NtfyNotificationEvent = | "gridlock" | "fallback-used" | "memory-dreams-processed" + | "token-budget" | "message:agent-to-user" | "message:agent-to-agent" | "message:room"; @@ -313,6 +314,7 @@ export const NOTIFICATION_EVENTS = [ "gridlock", "fallback-used", "memory-dreams-processed", + "token-budget", "message:agent-to-user", "message:agent-to-agent", "message:room", @@ -1127,6 +1129,24 @@ export interface TaskTokenUsage { lastUsedAt: string; } +export interface TaskTokenBudget { + /** Total-token soft cap. When reached, emits one notification and continues. */ + soft?: number; + /** Total-token hard cap. When reached, pauses the task with pausedReason="token_budget_exceeded". */ + hard?: number; + /** Optional per-size overrides keyed by Task.size (S/M/L). Falls back to soft/hard when absent. */ + perSize?: { S?: { soft?: number; hard?: number }; M?: { soft?: number; hard?: number }; L?: { soft?: number; hard?: number } }; +} + +export interface TaskTokenBudgetOverride { + soft?: number; + hard?: number; + /** Optional ISO timestamp recording when an operator widened the cap on unpause. */ + raisedAt?: string; + /** Optional free-text justification recorded with the override. */ + reason?: string; +} + /** Thrown when a checkout is attempted on a task already checked out by another agent. */ export class CheckoutConflictError extends Error { constructor( @@ -1207,6 +1227,12 @@ export interface Task { paused?: boolean; /** Optional machine-readable reason for automated pauses (for example dispatch-storm). */ pausedReason?: string; + /** ISO timestamp set when the task first crossed the soft token budget cap. */ + tokenBudgetSoftAlertedAt?: string; + /** ISO timestamp set when the task first crossed the hard token budget cap. */ + tokenBudgetHardAlertedAt?: string; + /** Optional per-task budget override set by an operator on resume. */ + tokenBudgetOverride?: TaskTokenBudgetOverride; /** Dispatch-storm cycle counter tracked by scheduler for todo↔in-progress loop detection. */ dispatchStormCount?: number; /** ISO timestamp of the most recent dispatch-storm cycle increment. */ @@ -1740,6 +1766,8 @@ export interface GlobalSettings { * ?project=&task= so the dashboard opens the correct project first. * Example: "http://localhost:3000" or "https://fusion.example.com" */ ntfyDashboardHost?: string; + /** Optional global fallback per-task token budget defaults. */ + taskTokenBudget?: TaskTokenBudget; /** How long a task must remain in `status='failed'` before a push notification fires. * Set to 0 to dispatch immediately (legacy behavior). Default: 30000 ms. */ failureNotificationDelayMs?: number; @@ -2626,6 +2654,8 @@ export interface ProjectSettings { * each prompt and proactively compacts context when the token count reaches * this threshold. */ tokenCap?: number; + /** Optional per-task token budget defaults (soft/hard with optional size overrides). */ + taskTokenBudget?: TaskTokenBudget; /** When true, each task step runs in its own fresh agent session instead of a * single session for the entire task. Enables per-step error recovery and * optional parallel execution when steps have non-overlapping file scopes. diff --git a/packages/engine/src/notifier.ts b/packages/engine/src/notifier.ts index c4527aed8..2e7063a5c 100644 --- a/packages/engine/src/notifier.ts +++ b/packages/engine/src/notifier.ts @@ -29,6 +29,7 @@ export const DEFAULT_NTFY_EVENTS: readonly NtfyNotificationEvent[] = [ "planning-awaiting-input", "gridlock", "fallback-used", + "token-budget", "message:agent-to-user", "message:agent-to-agent", "message:room",