agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
104 lines • 5.68 kB
JavaScript
;
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.placedToolResult = exports.isPlacedToolResult = exports.placedResultKind = exports.assertArtifactPlacement = void 0;
/**
* Refuse a placement dial this library cannot honor, where it is configured
* — never at the first oversized result of the first run.
*/
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.`);
}
}
exports.assertArtifactPlacement = assertArtifactPlacement;
/**
* 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.
*/
function placedResultKind(toolName, declared) {
return declared ?? `tool-result/${toolName}`;
}
exports.placedResultKind = placedResultKind;
/** Type guard for consumers reading `tool_end.result` or a tool message. */
function isPlacedToolResult(value) {
return (typeof value === 'object' &&
value !== null &&
value.placed === true &&
typeof value.ref === 'string' &&
typeof value.reason === 'string');
}
exports.isPlacedToolResult = isPlacedToolResult;
// 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. */
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.`,
};
}
exports.placedToolResult = placedToolResult;
//# sourceMappingURL=placement.js.map