agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
59 lines (58 loc) • 3.23 kB
TypeScript
/**
* resultCeiling — the refusing ceiling a tool declares on its OWN result
* (9.20.0). One owner of the measurement and the refusal sentence; the
* tool-dispatch loop (`stages/toolCalls.ts`) calls it at every boundary where
* a handler's return becomes final, so a resumed call is refused exactly as an
* inline one.
*
* Pattern: measured refusal at the dispatch boundary — the sibling of
* `toolResultCap.ts`, with the OPPOSITE answer to overflow, because
* the two serve different owners:
*
* • `maxToolResultChars` (agent-level, 9.11.0) TRUNCATES: the operator caps
* tools they did not write, so the most honest deliverable is a marker
* plus a verbatim head of whatever came back.
* • `resultCeiling` (per-tool, this module) REFUSES: the AUTHOR declared
* what a sane result is and which parameters (`narrowBy`) make a retry
* smaller. A truncated result reads as a complete one — the model cannot
* tell the data ends where the cut happened and fabricates from the part
* it saw — while a refusal that says "No data was returned" and names the
* narrowing parameters produces a clean retry (field-verified on a
* ~191k-char return).
*
* The oversized payload never leaves the dispatch: not into history, not into
* `stream.tool_end`, not into any recorder. The RECORD keeps the truth as the
* typed `agentfootprint.tools.result_refused` event (true size, ceiling,
* suggestions), emitted by the caller beside the replacement; the delivered
* result carries status `'invalid'` — the closed-set member whose corrective
* action is "fix the call" — so `onToolStatus` edges can route on it.
*
* Emits: nothing itself (pure measurement); zero-cost-when-unused — no
* declared ceiling means one `undefined` check and byte-identical behavior.
*/
import type { ToolResultCeiling } from '../../tools/tools.js';
/** What an over-ceiling measurement hands the dispatch loop: the sentence the
* model reads and the true size for the record. `undefined` from
* {@link applyResultCeiling} means no ceiling or under it — untouched path. */
export interface ResultCeilingRefusal {
/** The teaching refusal — the ONLY thing the model (and every downstream
* channel) receives in place of the oversized payload. */
readonly refusal: string;
/** The stringified result's true length — the record's fact, carried by the
* `tools.result_refused` event, never by any content channel. */
readonly sizeChars: number;
}
/**
* Measure one handler result against the tool's declared ceiling.
*
* Returns `undefined` when there is no ceiling or the result fits — the caller
* keeps every byte of today's path. Over the ceiling, returns the refusal to
* deliver INSTEAD of the payload. What is measured is the string the model
* would have read (strings as-is, everything else `safeStringify`d) — the same
* rule `toolResultCap.ts` measures by, so the two ceilings always argue about
* the same number.
*/
export declare function applyResultCeiling(value: unknown, opts: {
readonly toolName: string;
readonly ceiling: ToolResultCeiling | undefined;
}): ResultCeilingRefusal | undefined;