agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
100 lines • 5.61 kB
TypeScript
/**
* artifacts/placement — the placement threshold (Phase 2, Leg 3).
*
* An OPTIONAL operator dial on the agent's artifacts wiring:
* `Agent.create({ artifacts: { store, placement: { maxInlineChars: N } } })`.
* A tool result whose finalized text exceeds the threshold is checked into
* the store (kind `tool-result/<toolName>`, or the tool's declared
* `resultKind` — see `placedResultKind`) and the model receives the claim
* ticket instead — a SHORT substitute naming ref + meta + how to consume it.
* The whole 879,073-token failure class, retired by configuration.
*
* PRECEDENCE with the other two ceilings, stated once and owned here:
* 1. the tool's own `resultCeiling` (the AUTHOR's refusal) is judged FIRST,
* at the execute boundary — a refused result is a short teaching
* sentence, so placement never sees the payload at all;
* 2. placement (the OPERATOR's ref-ing) is judged next, on what the
* after-tool governance chain let through — a rule that already
* summarized a huge result is measured on what it produced;
* 3. the agent-level `maxToolResultChars` truncation net runs LAST — with
* placement on it measures the ticket, so it should now rarely fire.
*
* A placed result is a TICKET, not a refusal: declared effects are still
* judged and procedure steps advance normally — the result ARRIVED; it just
* travels by reference. The decision is never silent: the mint lands as
* `agentfootprint.artifacts.minted` (origin joining it to the tool call) and
* the substitution states itself in the result the model reads.
*
* This module is the PURE half — the dial shape, its build-time refusals,
* the measure rule (shared with both ceilings: strings as-is, everything
* else `safeStringify`d by the caller), and the substitute composer. The
* dispatch layer owns the mint and the events.
*/
import type { ArtifactMeta } from './types.js';
/** The dial. One field today; a plain object so field number two (a
* per-kind override, say) can arrive additively. */
export interface ArtifactPlacement {
/**
* The threshold, in characters of the finalized result text. Over it, the
* result is checked into the store and the model reads the claim ticket.
* Positive whole number; anything else is refused at build.
*
* **It moves what routing predicates read.** The substitute replaces the
* result string everywhere downstream — history, `lastToolResult`,
* `toolResults` — which is where skill-graph `when` edges and `rule`
* triggers look. Turning placement on, or changing this number, can
* therefore change which edge fires for a graph that matches on result
* TEXT. That is the layering (routing judges what the model was told, not a
* string the conversation never contained), stated here because it is not
* guessable from a number: an edge that must survive the dial should key on
* the tool name (`onToolReturn`) or a declared `status` (`onToolStatus`).
*/
readonly maxInlineChars: number;
}
/**
* Refuse a placement dial this library cannot honor, where it is configured
* — never at the first oversized result of the first run.
*/
export declare function assertArtifactPlacement(site: string, placement: ArtifactPlacement | undefined): void;
/**
* The kind vocabulary a placement mint declares — THE one decision, and the
* only place it is made.
*
* Default: `tool-result/<toolName>`. Honest — it says exactly what the payload
* is and which tool produced it — and it is what a `wants` declaration or a
* `present` call names to consume the placed result.
*
* `declared` is the tool's own `Tool.resultKind` (9.70.0), and when a tool
* declares one it WINS. The reason is the exact-match law on the consuming
* end: `wants` matches kinds by exact string equality — no wildcards, no
* hierarchy — so the framework's default vocabulary is a ticket a
* `wants: { dataset: 'dataset/rows' }` argument must refuse. Rather than
* loosen the matcher (a ticket would stop being a promise) or make consumers
* re-mint at the seam (the framework declining to carry its own ref), the
* MINT speaks the author's vocabulary. Absent → today's bytes exactly.
*/
export declare function placedResultKind(toolName: string, declared?: string): string;
/**
* The substitute the model reads in place of the payload — ONE shape, always
* the object (the `TruncatedToolResult` law: a consumer branches on
* `.placed` without parsing prose, and the model reads it as JSON on the
* `role: 'tool'` message).
*/
export interface PlacedToolResult {
/** Always `true`. The field a consumer branches on. */
readonly placed: true;
readonly ref: string;
/** The minted kind — what a consumer names to want it. The tool's declared
* `Tool.resultKind` when it has one, `tool-result/<toolName>` otherwise. */
readonly kind: string;
readonly mediaType: string;
/** The stored payload's true size — the chars the window did NOT pay. */
readonly bytes: number;
/** What happened and what to do next: route the ref, never retype. */
readonly reason: string;
}
/** Type guard for consumers reading `tool_end.result` or a tool message. */
export declare function isPlacedToolResult(value: unknown): value is PlacedToolResult;
/** Compose the ticket from the mint the dispatch layer performed. */
export declare function placedToolResult(toolName: string, meta: ArtifactMeta, sizeChars: number, maxInlineChars: number): PlacedToolResult;
//# sourceMappingURL=placement.d.ts.map