UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

104 lines (103 loc) • 7.86 kB
/** * 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;