UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

79 lines (78 loc) • 6.06 kB
/** * The deferral gate's POLICY SEAT as vocabulary: the three states a deployment may declare * (`RunnerDeps.toolSearch`), the word a disclosure reports, and the ONE reader that lowers the seat to the * single number the gate compares against. Pure — no harness, no roster, no I/O. `runner/tool-defer-gate.ts` * owns the gate that consumes the number. */ /** The default threshold percent: with no seat, the candidate set defers when its inlined size reaches this * percent of the model's context window. `DEFER_THRESHOLD_FRACTION` is derived from it. */ export declare const TOOL_SEARCH_DEFAULT_THRESHOLD_PERCENT = 10; /** The typed refusal a `toolSearch` seat outside its domain raises: a bad value never selects an arm * silently and never folds back to the default gate. */ export declare const TOOL_SEARCH_INVALID_CODE = "config.tool_search_invalid"; /** * The deployment's deferral-gate SETTING: `true` = the candidate set defers on every request, `false` = it never defers (and with no caller list no * ToolSearch mounts), `{mode:"auto", thresholdPercent}` = the threshold gate at that percent of the context window (omitted ⇒ * {@link TOOL_SEARCH_DEFAULT_THRESHOLD_PERCENT}). Absent ⇒ that default — the pre-seat behavior. * * @contract defer_gate.one_threshold — the three states are THREE VALUES OF ONE NUMBER, never three code paths: {@link resolveToolSearch} lowers the seat to * one threshold fraction (`true` ⇒ `0`, so every candidate byte is over budget; `false` ⇒ `Infinity`, so no candidate set reaches it; `auto:N` ⇒ `N/100`) and * `deferralGate` keeps its ONE comparison `gatedChars ≥ thresholdChars`; a second arm for a new state breaks this contract rather than extending it. It * governs rule ② ONLY — `TaskSpec.deferTools` still defers under every state, `alwaysLoadTools` still pins first, and `RunnerDeps.deferMode` remains the * orthogonal knob that WIDENS rule ②'s candidate set. */ export type ToolSearchSetting = boolean | { mode: "auto"; thresholdPercent?: number; }; /** The three arms of {@link ToolSearchSetting} as one word — the disclosure face's vocabulary. */ export declare const TOOL_SEARCH_POLICIES: readonly ["always", "never", "auto"]; export type ToolSearchPolicy = (typeof TOOL_SEARCH_POLICIES)[number]; /** The lowered seat: the word a disclosure reports, and the ONE number the gate compares against. */ export interface ResolvedToolSearch { policy: ToolSearchPolicy; /** Present iff `policy === "auto"` — the percent the deployment asked for (or the default). */ thresholdPercent?: number; /** The fraction of the context window the candidate set must reach: `0` (always) / `Infinity` (never) / * `thresholdPercent / 100`. This is the gate's only input from the seat. */ fraction: number; } /** * The ONE reader of {@link ToolSearchSetting} (`@contract defer_gate.one_threshold`). A value outside the domain refuses loudly with * {@link TOOL_SEARCH_INVALID_CODE} — an unparseable knob must not fall back to the default gate while the deployment believes its declaration took effect. * The object form's MEMBER SET is part of that domain (`mode` + `thresholdPercent`, nothing else): an unknown key is the same silent fold one level down, * since a misspelled percent lowers to the default while the declaration looks like it took effect. * `thresholdPercent` must be a finite number in `[0, 100]`, and the two ends are NOT symmetric: `auto:0` is * an exact alias for `true` (threshold 0), while `auto:100` is the FULL-WINDOW threshold, not `false`. Only * `false` never defers. */ export declare function resolveToolSearch(setting: ToolSearchSetting | undefined): ResolvedToolSearch; /** * Describe a REJECTED value for a refusal message — the ONE describer the config seats share — without ever * throwing, and without letting the rejected value NARRATE ITSELF. * * Two failures, one root. `JSON.stringify` is not total (a BigInt and a circular object make it throw, and a * formatter that throws inside a refusal replaces the coded refusal with an uncoded `TypeError`) — and where * it does not throw it calls the VALUE'S OWN `toJSON`, i.e. it asks the thing being rejected to say what it * is. Measured: `{ toJSON: () => "open" }` described itself as `"open"`, so a refusal of an illegal object * read as a refusal of a legal word; `{ toJSON: () => undefined }` fell through to `String(value)` and read * `[object Object]`; a function printed its whole source (unbounded, and equally forgeable through * `toString`); and `NaN` / `Infinity` — the two numbers these seats reject most — both came out as `null`, * the spelling of a value the caller never wrote. * * So: PRIMITIVES are spelled exactly (a number through `String`, which is total and keeps `NaN`, `Infinity` * and the `-0` a caller can only have written deliberately); anything else is DESCRIBED structurally — its * shape, its constructor's name and its own top-level keys, bounded. Reading those may still hit a proxy's * traps (every read is guarded), but NOTHING a read hands back is turned into text unless it is already a * primitive of the expected domain — a formatting hook of the value is never what writes this sentence. A description is worth less to a reader than the value would be; a WRONG value is worth less than * nothing, because it sends them to fix something they did not write. */ export declare function describeRejectedValue(value: unknown): string; /** * The INERT spelling of a lowered policy — the exact seat value that lowers back to it, with no accessor, * proxy or prototype left on the path. A nested accessor (`get thresholdPercent()`) answers every later read * differently, so a phase that runs several passes over one cached object still measures several different * numbers. Round-trip: `resolveToolSearch(toolSearchSettingOf(r))` deep-equals `r` for every `r` this module * mints. */ export declare function toolSearchSettingOf(resolved: ResolvedToolSearch): ToolSearchSetting;