UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

97 lines • 5.3 kB
/** * 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. */ /** * Refuse a placement dial this library cannot honor, where it is configured * — never at the first oversized result of the first run. */ export function assertArtifactPlacement(site, placement) { if (placement === undefined) return; const value = placement.maxInlineChars; if (!Number.isFinite(value) || !Number.isInteger(value) || value <= 0) { throw new Error(`${site}: artifacts.placement.maxInlineChars must be a positive whole number of ` + `characters, and ${String(value)} is not one. It is the threshold over which a tool ` + `result is checked into the artifact store and the model reads the claim ticket ` + `instead. To turn placement OFF, omit \`placement\` — absent means results are never ` + `measured and never placed, exactly as before.`); } } /** * 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 function placedResultKind(toolName, declared) { return declared ?? `tool-result/${toolName}`; } /** Type guard for consumers reading `tool_end.result` or a tool message. */ export function isPlacedToolResult(value) { return (typeof value === 'object' && value !== null && value.placed === true && typeof value.ref === 'string' && typeof value.reason === 'string'); } // LENS · tool-result · persistent-history // reads: ref/kind/mediaType/bytes ← the ArtifactMeta the dispatch layer minted; size and threshold ← its measure // it SUBSTITUTES the result the model would have read, so the ticket and the reason travel together. // law: may omit, never deny; every clause anchored to the call it was composed on. /** Compose the ticket from the mint the dispatch layer performed. */ export function placedToolResult(toolName, meta, sizeChars, maxInlineChars) { return { placed: true, ref: meta.ref, kind: meta.kind, mediaType: meta.mediaType, bytes: meta.bytes, reason: `${toolName} returned ${sizeChars} chars, over the ${maxInlineChars}-char placement ` + `threshold, so the full result was stored as artifact '${meta.ref}' ` + `(${meta.kind}) instead of entering this conversation. Route the ref: pass the ` + `string '${meta.ref}' to a tool whose argument wants '${meta.kind}', or call ` + `present({ ref: '${meta.ref}', as: … }) to hand it to the screen. Do not retype or ` + `summarize content you have not read.`, }; } //# sourceMappingURL=placement.js.map