UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

77 lines (76 loc) • 4.03 kB
/** * window/strategies/drop — the mechanic both drop strategies share. * * Pattern: One pure function over a `WindowStrategyInput`. * Role: core/ layer. `slidingWindow` and `tokenBudget` differ ONLY in when * they engage and what they file; what actually leaves the window is * decided here, once, so a refusal reason means the same thing under * both — and under compaction, which asks the same `planRemoval`. * Emits: N/A. * * A drop makes no LLM call and writes no summary. What it must still do is * keep the request valid and keep the record honest: * * • the span comes from the shared refusal engine, so the CURRENT REQUEST, * an unanswered tool call, the paused tool, a pending check-in and the * recent turns never leave, and each refusal is named; * • when the span reaches the front of what may leave, an authored notice * takes that position — the window must open on a user turn (see * notice.ts), and a message we are forced to author should tell the * truth. That position is the window's HEAD in the ordinary case, and the * slot just after the kept request when the request was the head; * • when it does not, nothing is inserted: the original opening turn is * still there, so there is no wire problem to solve. * * ## The notice is owed to the WIRE, not to politeness (9.57.0) * * That distinction used to be missing here, and it produced a livelock. The * notice was authored whenever the removal reached the front of what may * leave, and the whole removal was abandoned when the notice was not smaller * than the span — but the obligation being guarded is narrower: **the window * must open on a user turn.** When the message that would BECOME the head is * already a user turn (the kept request, or an older user turn from a restored * conversation), no notice is owed at all, and its 245–358 characters had no * business vetoing a legitimate drop. * * Because the span is the longest CONTIGUOUS removable run, a blocker one turn * along caps it at the same small span at every boundary — so the same verdict * came back for ever and the window grew without bound. Two locally-correct * rules composing into non-progress. * * So the decision is a LADDER: the rich notice, else the plain one, else no * notice at all — and `'replacement-not-smaller'` only when the wire genuinely * needs a message in that position and none of them is smaller than the span. */ import type { LLMMessage } from '../../../../provider-contract/types.js'; import type { WindowEviction, WindowStrategyInput } from '../strategy.js'; import type { WindowRefusal } from '../types.js'; /** What a drop attempt did, in the terms a record needs. */ export interface DropOutcome { /** Every turn that refused to leave, named. */ readonly refusals: readonly WindowRefusal[]; /** The new window; absent when nothing was dropped. */ readonly window?: readonly LLMMessage[]; readonly rebase?: { readonly headCount: number; readonly keptTailCount: number; readonly insertedAtMs?: number; }; readonly removedStageIds: readonly string[]; readonly removedMessageCount: number; readonly evictions: readonly WindowEviction[]; readonly windowCharsBefore: number; readonly windowCharsAfter: number; /** Turns in the window before / after. Counted from the segmentation. */ readonly turnsBefore: number; readonly turnsAfter: number; } /** * Drop the oldest contiguous removable span, if there is one. * * @param input the strategy's input, with the bound refusal engine * @param keepRecentTurns how many trailing turns are off-limits * @param strategyName named in the authored notice, so a reader of the * window itself can tell which policy removed things */ export declare function dropOldestSpan(input: WindowStrategyInput, keepRecentTurns: number, strategyName: string): DropOutcome;