@sema-agent/core
Version:
Stateless, task-oriented AI agent core
104 lines (103 loc) • 7.86 kB
TypeScript
/**
* The deployment's per-model CONTEXT-WINDOW overlay — the one deployment seat an IN-FLIGHT leg re-reads.
*
* WHY IT IS NOT ON THE MODEL. `Model.contextTokens` / `Model.autoCompactTokens` are two things at once: the
* geometry a leg measures itself against, and part of the immutable routing card a leg was handed at its
* prepare. The hot-swap law (`swappable-deps.ts`) is right about the card — nothing is re-routed, re-priced or
* re-fenced mid-run, because a running request must reach the endpoint it was priced and credentialed for.
* It was wrong about the geometry: an operator who lowers a model's usable window is not re-routing anything,
* they are changing a SETTING, and a leg that keeps filling the retired window until it ends is the one thing
* they were trying to prevent. The reference implementation keeps that setting off the model card for exactly
* this reason and re-reads it on every request, clamping it to the model's physical window.
*
* So the overlay lives here, keyed by CATALOG WORD — the same word `ModelRef` already speaks. A leg carries
* the ref that chose its model (`ResolvedRole.modelRef` → the harness → `ToolExecuteContext.modelRef`), so
* the lookup is a plain read with no reverse search: an id→word search cannot answer (one model is reachable
* under several words, two words may route to one id, and an object-pinned model has no word at all), and a
* leg that knows its own word needs no search.
*
* WHAT IT DOES NOT DO. It carries no routing, credential, price or fence field, and never will: those are the
* card, and the card stays snapshotted. An OBJECT-pinned leg (a spec that named a `Model`, a `select` criteria)
* has no word and therefore no overlay — the pin is the caller saying "this exact model, as declared".
*/
import type { Model } from "../internal/llm.js";
import type { ModelRef } from "./model-seat.js";
/**
* One model's overlay. Every member is a DECLARATION the deployment may change between requests; an absent
* member means "whatever the model card says", never a value.
* · `contextTokens` — the usable physical window (the model card's `contextTokens ?? contextWindow`).
* · `autoCompactTokens` — the trigger-side window (dual-window semantics: lowers the compaction trigger and
* the context-editing frontier without touching the physical request budget). Clamped to the physical
* window by the one resolver, exactly as a model-card declaration is.
* · `enabled` — compaction for legs on this model, the deployment's twin of `TaskSpec.compaction.enabled`
* and with the same reach (the routine boundary pass, the recovery pass and the end-of-task summary —
* one gate, inside `maybeCompact`, fed by one settings assembly). A request that states its own
* `compaction.enabled` outranks it: the request is the author, this seat is the deployment's default —
* the same "the spec's own value wins, the deployment fills an unset one" law every other seat follows.
*/
export interface ContextWindowOverlay {
readonly contextTokens?: number;
readonly autoCompactTokens?: number;
readonly enabled?: boolean;
}
/**
* The seat: catalog word → overlay. Words absent from the table are "nothing declared".
*
* @contract context_settings.geometry_is_read_live — this is the ONE deployment seat a leg re-reads while it
* runs: every window number a leg is measured against (the compaction trigger, the sanitize clamp, the
* context-editing frontier, the request guard, the disclosed `contextUsage`) is taken from the overlay in
* force at the moment of THAT question, keyed by the word that chose the leg's current model, so a swap
* reaches boundaries of legs already in flight; the leg's ROUTING — endpoint, credentials, price, fence, the
* `Model` object itself — stays the generation the leg prepared under, and this seat can never carry any of
* it. A leg whose model was pinned as an object has no word and is never overlaid.
*/
export type ContextSettings = Readonly<Record<string, ContextWindowOverlay>>;
/**
* The overlay in force for a leg, read at the moment of the question. `ref` is the leg's own catalog word
* (`AgentHarness.getModelRef()` / `ResolvedRole.modelRef`); a `Model` OBJECT answers `undefined` because a
* pinned model has no word for the table to key on. OWN-property read: a word like `"constructor"` must mean
* "a key this deployment declared", never `Object.prototype`'s answer.
*
* The leg's own word first; when it declares nothing, the words it STANDS IN FOR — a tier word the default
* role answers with, a CC alias, `best` — as the tier expansion recorded them ({@link tierAliasVia}): only
* words that name the very model the leg's word resolves to, so a deployment key that a tier word passes by
* but that names ANOTHER model is never read. Nothing is found by searching for a model.
*/
export declare function contextOverlayFor(settings: ContextSettings | undefined, ref: ModelRef | undefined, catalog?: {
models?: Record<string, Model>;
}): ContextWindowOverlay | undefined;
/**
* Every word the overlay declares must be a word of the model catalog (the deployment's keys, the tier words
* and aliases its `tiers` expanded) — `config.context_settings_unknown_word`. A misspelled word keys a leg
* nobody can run, so it would govern nothing while reading as in force. Checked at construction and on every
* swap against the catalog the swap leaves in force, so a catalog swap that would orphan a declared word is
* refused too. Returns `settings`.
*/
export declare function assertContextSettingsWords<T extends ContextSettings | undefined>(settings: T, models: Record<string, Model> | undefined, where: string): T;
/**
* The seat's VALUE GATE — the same one the hot-swap door and the construction path apply, so a value the door
* admits is a value every later read admits. A malformed overlay is refused LOUDLY rather than repaired
* quietly: a window silently dropped back to the model card's is the misconfiguration this seat exists to make
* visible. `undefined` (the seat cleared) passes through.
*
* `where` names the caller in the message (`"swapDeps"` / `"RunnerDeps"`), since the two paths are the two
* moments an operator can get it wrong.
*/
export declare function assertContextSettingsValue(v: unknown, where: string): ContextSettings | undefined;
/** Does a REQUEST's own `compaction.enabled` stand, or does the deployment's overlay decide? One law, stated
* once: the request is the author, the deployment fills an unset one, and nothing declared means ON. */
export declare function compactionEnabled(specEnabled: boolean | undefined, overlay: ContextWindowOverlay | undefined): boolean;
/**
* The overlay's view of a model's PHYSICAL window — the model card's chain with the deployment's live
* declaration CLAMPED over it. The single expression `guardBudget` and `resolveTriggerWindow` share.
*
* The clamp is the whole difference between a setting and a claim. An overlay says how much of a model a
* deployment wants used; it cannot say what the endpoint will accept, and the endpoint a leg talks to is the
* one it prepared under. Letting a declaration RAISE the window would let an operator publishing a larger
* model and its larger window in one step hand the bigger number to legs still routed at the smaller
* endpoint — the guard would stop protecting the limit it exists for. So: lower freely, never raise. (Same
* clamp the reference implementation applies to its configured window: `min(model window, configured)`.)
* A card with no usable window of its own has nothing to clamp against, and the declaration is then the only
* number there is.
*/
export declare function physicalWindowOf(model: Model, overlay: ContextWindowOverlay | undefined): number;