import type { TurnRecorder } from '../../manager/session/turn-recorder.js'; import type { StopReason } from '../../types/session/index.js'; export interface GuardConfig { tokenBudget: number; timeoutMs: number; costLimitUsd?: number; maxIterations?: number; /** * Wall-clock already consumed by this turn before the current process * picked it up, from the checkpoint's `guards.elapsedMs`. * * The timeout budget is a property of the TURN, not of the process * hosting it. Without the offset a turn resumed from a checkpoint got a * fresh clock every time, so N resumes bought N x `timeoutMs`. */ elapsedMsOffset?: number; } export interface GuardCheckResult { shouldStop: boolean; forceFinalize: boolean; stopReason?: StopReason; isCancelled: boolean; } export declare class GuardCoordinator { private limitConfig; private startTime; constructor(config: GuardConfig); /** * Adopt a checkpoint's elapsed time after construction. * * The guard is built before the checkpoint is read (restore is async and * happens inside the turn generator), so the resume path cannot pass * `elapsedMsOffset` to the constructor. Rather than reorder setup around * one field, let the resume branch hand it over. */ restoreElapsed(elapsedMs: number): void; /** * Wall-clock left before this turn is asked to start finishing. * * NOT the time left before the deadline, and the difference is the whole * point. The checks above run BETWEEN iterations, so anything that waits * inside one cannot be stopped by them, and a caller sizing such a wait * needs a number the turn actually owns: a fixed two-minute hold measured * against a turn configured for twenty seconds kept it open for 120,267 ms. * * Measuring to the DEADLINE was the first attempt and it was wrong. The * binding constraint is `budgetWarningThreshold`, the point at which this * guard stops asking for more work and asks for a closing summary — that * last slice exists so the turn can produce an answer, and a wait sized * against the deadline eats into it. Half of the time-to-deadline, started * just under the threshold, ends at 95% of the budget: half the closing * reserve spent waiting for a result the closing answer was supposed to * use. * * Zero once the threshold has passed, which is also how a caller gets the * re-evaluation it needs: `forceFinalize` is sampled at the top of an * iteration and this is read when the wait is about to start, so a long * iteration that crossed the line in between is told to wait for nothing. * * Reads through the same `startTime` the limit checks use, so a turn * resumed from a checkpoint (see `restoreElapsed`) reports the time left * on the TURN rather than on the process now hosting it. */ remainingBeforeFinalizeMs(): number; /** * Wall-clock left before the turn's hard timeout. * * Setup work that happens before the first iteration cannot rely on * {@link beforeIteration}: there is no iteration boundary to sample while * that work is pending. Callers use this value to put the same turn-owned * deadline around such work instead of inventing a second clock. */ remainingUntilTimeoutMs(): number; beforeIteration(recorder: TurnRecorder, abortSignal: AbortSignal): GuardCheckResult; } //# sourceMappingURL=guard.d.ts.map