UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

85 lines (84 loc) • 6.09 kB
import type { AgentMessage } from "../internal/harness.js"; import type { Model } from "../internal/llm.js"; import { type ContextWindowOverlay } from "./context-settings.js"; /** * Last-resort, in-task context trim. Proper compaction (LLM summary) runs *between* tasks; * this prevents a single task whose internal tool-loop balloons from overflowing the window. * * Keeps all summary messages (compaction/branch) and the most recent messages that fit the * budget, dropping older regular messages at a safe boundary (never starting on an orphan * toolResult). Returns the original array when already within budget. * * design/123 D3 (= fable-M5) — anchored accounting: when `anchoredTotalTokens` * (= `estimateContextTokens(messages, charsPerToken).tokens` for the SAME array) is provided, the * trigger gate and the internal bookkeeping start from the usage-anchored value instead of the * structural sum (which underestimates code/CJK and lets over-window requests through). Dropping a * message AFTER the usage anchor reduces the accounted total by its structural estimate; dropping a * message AT/BEFORE the anchor reduces it by ZERO (the anchor usage was billed with that content — * a request-view drop only pays off on the NEXT request), so the trim keeps cutting deeper until * the anchored total meets the budget or only the minimal tail is left. Absent ⇒ legacy structural * behavior, byte-identical. `charsPerToken` (design/123 D2): structural coefficient, default 3 (design/374 slice 4). */ export declare function trimToBudget(messages: AgentMessage[], budgetTokens: number, anchoredTotalTokens?: number, charsPerToken?: number): AgentMessage[]; /** * In-band notice for a request view whose oldest messages were dropped by the guard trim. Every * other history-reducing pass in the pipeline announces itself to the model (compaction injects a * summary); a silent trim leaves the model unaware that earlier instructions or constraints are no * longer visible. MUST remain a single static string: the notice is re-inserted on every trimmed * request, and a byte-identical prefix keeps provider prefix caching effective. */ export declare const CONTEXT_TRIM_NOTICE: string; /** * Insert the static trim notice ahead of the kept tail of a trimmed request view (after any * always-kept summary messages, which lead the rebuilt array). Call ONLY when the trim actually * dropped messages. Request-view only — the synthetic message is never persisted to the session * (fixed timestamp keeps it fully deterministic). */ export declare function insertTrimNotice(trimmed: AgentMessage[]): AgentMessage[]; /** R2-3 (falsification round 2) — does the message's ENGINE-AUTHORED region cover [start, start+len)? * The carrier scan must never crown a counterfeit: a tool result or a user paste can reproduce the * wrapped frame bytes verbatim, but only ENGINE metadata (engineMinted whole-message, the * enginePrefixChars head, or an engineSegments range) proves the engine put them there. */ export declare function engineRegionCovers(m: AgentMessage, start: number, len: number): boolean; /** Outcome of {@link protectGitFrame} — `over_budget` means the irreducible core (summaries + the * protected frame carrier) exceeds the budget even after the one-shot degraded shrink: the caller * must fail the request LOUDLY (`irreducible_core_over_budget`) instead of pretending the trim * succeeded or looping compaction/re-assertion. */ export type GitFrameGuardOutcome = { messages: AgentMessage[]; action: "kept" | "absent" | "reinserted" | "shrunk"; } | { action: "over_budget"; }; /** * env-tail migration — the git frame's UNCLIPPABLE-CORE protection over a trimmed request view. * The frame's "value unchanged ⇒ never re-sent" contract makes a request-only drop uniquely * dangerous for it: the commit receipt has already landed, so a trim that cuts the carrier makes * the announced hash suppress every future re-send while the model never saw the frame. Rules: * - REPLACE-BY-KEY SLOT: exactly ONE carrier is protected — the newest message whose flat text CONTAINS * `protectedText` (the ENGINE-HELD wrapped frame segment; set at delivery/re-assert AND * re-seeded on value-unchanged legs, so the protection survives legs that send nothing). * Older frames trim like ordinary history. * - Re-insert a dropped carrier after the leading summaries/trim-notice (its original relative * position is always ≥ there — the carrier predates every kept tail message). * - IRREDUCIBLE-CORE arc: when the core (always-kept summaries + the carrier) alone exceeds the * budget, substitute the carrier's frame segment with the deterministic DEGRADED rendering — * exactly once per run (`alreadyShrunk` latch, request-view only; the run loop re-announces the * shrunk view through its own receipt machinery). Still over after the shrink ⇒ `over_budget`. * - A total overshoot whose core FITS is left alone (turn-alignment overshoot is a pre-existing, * tolerated trade — validity beats budget; this guard must not convert it into a failure). */ export declare function protectGitFrame(before: AgentMessage[], trimmed: AgentMessage[], budgetTokens: number, frame: { protectedText: string; substitute?: { find: string; replace: string; }; }, // protectedText = the wrapped frame SEGMENT (see the carrier scan below) charsPerToken?: number): GitFrameGuardOutcome; /** Budget the in-task guard targets for a model — CC-aligned headroom numbers with a 0.85 * small-window floor, sema-owned drop-oldest action (see {@link GUARD_HEADROOM_TOKENS}); always * above the compaction/clearStale frontier. Dual-window semantics: deliberately the PHYSICAL * window (`contextTokens ?? contextWindow`), NOT `autoCompactTokens` — the guard protects the * real request limit; a lowered autocompact window only moves the trigger-side geometry. */ export declare function guardBudget(model: Model, overlay?: ContextWindowOverlay): number;