/** Attention Compiler (plan 074 Tasks 2–3): frozen contracts, the ratio gate, and the two * sticky stages (thinking strip, then old tool-result stubs). Creation is synchronous and * fails closed; `compileAttention` measures the assembled request once, rewrites only the * `history` / `toolResults` groups, and never touches the store, the om ledger, or the * frozen prefix. The assembly branch lives in `input.ts`; opt-in agent wiring (Task 5) is not * part of this module. */ import { type ContextBudgetMessageGroups } from "./context-budget.js"; import type { AttentionCompiler, AttentionCompilerContext, AttentionCompilerOptions, AttentionCompilerSetting, AttentionInputCapOptions, AttentionReport, AttentionTrigger, AttentionTriggerDecision, AttentionTriggerState, ContextBlock, Skill, ToolDefinition } from "./contracts.js"; import { type CompactionTrigger } from "./contracts-core/compaction.js"; import type { SecretRedactor } from "./redaction.js"; import { type ResolvedToolResultFoldOptions } from "./tool-result-fold.js"; export declare const ATTENTION_BUDGET_ERROR_CODE: "attention_budget_exceeded"; /** C9: still over `triggerRatio` after every eligible stage — host should compact, not delete. */ export declare class AttentionBudgetError extends Error { readonly code: "attention_budget_exceeded"; constructor(message?: string); } export declare function isAttentionBudgetError(error: unknown): error is AttentionBudgetError; export declare const DEFAULT_ATTENTION_TRIGGER_RATIO = 0.75; export declare const DEFAULT_ATTENTION_COMPACT_RATIO = 0.9; export declare const DEFAULT_ATTENTION_THINKING_KEEP_TURNS = 1; export declare const DEFAULT_ATTENTION_KEEP_LAST = 3; export declare const DEFAULT_ATTENTION_RESERVE_TOKENS = 1024; export declare function resolveAttentionReserveTokens(value: unknown): number; /** C2: host `maxInputTokens` wins; otherwise `contextWindow - (maxOutputTokens ?? 0) - reserve`. * Shared with the `input_ratio` compaction trigger (Task 4). Throws when no cap can be derived. */ export declare function resolveInputCap(options?: AttentionInputCapOptions, model?: AttentionCompilerContext["model"]): number; /** * Build the frozen per-turn state the axes read: the compiler supplies the cap and the run budget * (so a caller cannot silently drop the budget and fall back to the input cap), the caller * supplies what only the turn knows. */ export declare function attentionTriggerState(compiler: Pick, turn: { readonly estimatedInputTokens: number; readonly runInputTokens?: number; readonly turn?: number; }): AttentionTriggerState; /** * Evaluate the fold axes in order against one turn's state; the first axis that fires wins, and * its `targetTokens` (when it has one) is what the stages fold to. Called at most twice per turn — * once at turn start, once after the stages — so a host predicate never runs per row. */ export declare function evaluateAttentionTrigger(axes: readonly AttentionTrigger[], state: AttentionTriggerState): AttentionTriggerDecision; /** Validate compiler options + resolve the input cap. No provider I/O; unknown or * unresolvable input throws here rather than on the first turn. */ export declare function createAttentionCompiler(options?: AttentionCompilerOptions, context?: AttentionCompilerContext): AttentionCompiler; /** Resolve the run's compiler from the agent setting plus an optional run overlay, validating * both eagerly (no provider I/O) so a typo fails at run start, not on some later turn (C12). * Returns `undefined` when the compiler is off — the assembly path then allocates nothing. */ export declare function resolveRunAttentionCompiler(agent: AttentionCompilerSetting | undefined, run: AttentionCompilerSetting | undefined, model: AttentionCompilerContext["model"], runInputBudget?: number | null): AttentionCompiler | undefined; /** Caller-owned sticky frontier: what this session leaf already mutated (C10). Mutations are * monotonic, so a stubbed call stays stubbed and stripped thinking stays stripped even on a * later under-ratio turn — restoring either would rewrite the prompt-cache prefix. */ export interface AttentionStickyFrontier { /** SHA-256 keys of assistant messages whose thinking blocks were stripped. */ readonly thinking: Set; /** Tool call ids whose results were stubbed. */ readonly toolCallIds: Set; } export declare function createAttentionStickyFrontier(): AttentionStickyFrontier; export interface AttentionCompileOptions { readonly compiler: AttentionCompiler; /** The exact groups the assembler will send; only `history` and `toolResults` are rewritten. */ readonly groups: ContextBudgetMessageGroups; readonly context?: readonly ContextBlock[]; readonly skills?: readonly Skill[]; readonly tools?: readonly ToolDefinition[]; /** Host fold: its `summarize` wins for the rows the compiler picks, and its age/byte gates * decide fold-eligibility. Omitted → the deterministic stub and `keepLast` alone. */ readonly fold?: ResolvedToolResultFoldOptions; readonly frontier?: AttentionStickyFrontier; /** Session-owned folded bodies (plan 086 T3): a body stored here is re-applied instead of * re-summarized, so a sticky row stays byte-identical and the host `summarize` runs once per * row instead of once per turn. Omit for one-shot assemblies. */ readonly attentionFold?: AttentionFoldLedger; readonly redactor?: SecretRedactor; readonly signal?: AbortSignal; readonly turn?: number; /** Run input tokens already charged this run; the `run_input_ratio` axis projects onto it. */ readonly runInputTokens?: number; readonly sessionId?: string; readonly runId?: string; } export interface AttentionCompilation { /** The input groups unchanged when nothing was mutated; otherwise a new object with the * rewritten `history` / `toolResults` arrays. The input groups are never mutated. */ readonly groups: ContextBudgetMessageGroups; readonly mutated: boolean; readonly report: AttentionReport; } /** * Measure once, then mutate oldest-first until back under `triggerRatio` (C4), or throw * `AttentionBudgetError` when every eligible row is exhausted (C9). Front-of-frozen-prefix * (instructions, summaries, input) is never touched, and neither are the caller's arrays. */ export declare function compileAttention(options: AttentionCompileOptions): Promise; /** Serialized sticky frontier (plan 074 P3). Hashes and tool-call ids only: no model output, * no payload, nothing that needs redaction, so a durable resume can restore it verbatim. */ export interface PersistedAttentionStickyFrontier { readonly v: 1; /** Newest-last, so a restored frontier is the tail of the mutations the session made. */ readonly thinking: readonly string[]; readonly toolCallIds: readonly string[]; } /** Bounded snapshot of a live frontier; caller-owned (the runtime persists it, the compiler * never writes anywhere). */ export declare function serializeAttentionStickyFrontier(frontier: AttentionStickyFrontier): PersistedAttentionStickyFrontier; /** Validate a persisted frontier from an untrusted store (plan 074 P3). Malformed *entries* are * dropped one by one — a key the compiler cannot trust simply re-decides on the next turn — * while a malformed *shape* yields `undefined` so the caller starts from an empty frontier. * Never throws: a resume must not fail because a checkpoint was hand-edited. */ export declare function parseAttentionStickyFrontier(value: unknown): PersistedAttentionStickyFrontier | undefined; /** Rebuild a frontier from a restored snapshot; callers hold the session that owns it. */ export declare function restoreAttentionStickyFrontier(persisted: PersistedAttentionStickyFrontier): AttentionStickyFrontier; /** Session-owned folded bodies, keyed by tool call id. A row is summarized once: every later * turn re-applies the stored body, so a sticky row stays byte-identical for the provider cache * and the host `summarize` is not called again for it. Bodies are already redacted and capped by * the fold that produced them, so nothing here needs redaction at persistence time. */ export interface AttentionFoldLedger { /** toolCallId → stub body (never the payload); insertion order is fold order, oldest first. */ readonly bodies: Map; } export declare function createAttentionFoldLedger(): AttentionFoldLedger; /** Serialized fold ledger (plan 086 T3). Stub bodies only — no payload, and every entry was * already capped and redacted when it was folded, so a durable resume restores it verbatim. */ export interface PersistedAttentionFoldLedger { readonly v: 1; /** Oldest-first, so a restored ledger is the tail of the folds the session made. */ readonly bodies: readonly { readonly id: string; readonly body: string; }[]; } /** Bounded snapshot of a live ledger; caller-owned (the runtime persists it, the compiler never * writes anywhere). */ export declare function serializeAttentionFoldLedger(ledger: AttentionFoldLedger): PersistedAttentionFoldLedger; /** Validate a persisted ledger from an untrusted store (plan 086 T3). Malformed *entries* are * dropped one by one — a body the compiler cannot trust simply re-summarizes on the next turn — * while a malformed *shape* yields `undefined` so the caller starts from an empty ledger. * Never throws: a resume must not fail because a checkpoint was hand-edited. */ export declare function restoreAttentionFoldLedger(value: unknown): AttentionFoldLedger | undefined; /** Consecutive `truncated` turns that arm compaction by default. */ export declare const DEFAULT_ATTENTION_TRUNCATION_THRESHOLD = 2; export interface AttentionTruncationTriggerOptions { /** Truncated turns in a row before the trigger fires (default 2). */ readonly threshold?: number; } /** * Host-side follow-up policy for `truncated` turns (plan 074 P4). `truncated: true` means the * gate ran out of *eligible* rows: stubs cannot hold the request under the ratio, so the honest * answer is a new prefix at the next task boundary rather than a silent eviction. * * Wire it by feeding every `attention_compiled` event to `observe` and handing `trigger` to * `CompactionOptions.trigger` (agent config or run options). It fires **once per armed streak**, * so a branch is compacted once and then left alone until new truncated turns arrive; a mutated * turn that was not truncated clears the streak because the pressure was relieved. */ export interface AttentionTruncationTrigger { /** Drop-in `CompactionOptions.trigger`. */ readonly trigger: CompactionTrigger; /** Feed an `attention_compiled` event or an `AttentionReport`; returns the resulting streak. */ observe(report: { readonly truncated?: unknown; }): number; /** Truncated turns in a row since the last fire or relief. */ readonly streak: () => number; /** Clear the streak (host compacted for its own reasons). */ reset(): void; } export declare function createAttentionTruncationTrigger(options?: AttentionTruncationTriggerOptions): AttentionTruncationTrigger;