@sema-agent/core
Version:
Stateless, task-oriented AI agent core
85 lines (84 loc) • 6.09 kB
TypeScript
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;