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