UNPKG

agentfootprint

Version:

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

122 lines • 6.35 kB
"use strict"; /** * artifacts/present — the hand-to-the-screen verb (Phase 2, Leg 2). * * `present({ ref, as, label? })` is how the model finishes an analytics * answer WITHOUT serializing what the screen will show: it names the ticket * (`ref`), the consumer vocabulary for how to render it (`as: 'bar-chart'`, * `'table'`, …), and optionally the human title. The framework `head`s the * ref under the run's own scope and the RESULT carries a DESCRIPTION * SNAPSHOT — `{ kind, mediaType, bytes, label }` — so the claim ticket * describes the parcel: a conversation reloaded after the artifact expired * can still render an honest placeholder from history alone ("Chart — * 'Q3 sales by region' (bar-chart, 41 KB) — expired; re-run to regenerate"), * because the snapshot lives INSIDE the tool result, the one thing provider * message history keeps typed and durable. Never a blank pane. * * `as` is deliberately NOT validated against a component registry — that * registry is a later phase (the frontend leg); until then the value is * stored as data and travels on the `artifacts.presented` event exactly as * spoken. * * This module is the PURE half — the tool's name/schema constants, the * snapshot shape, and the resolution over the five-verb port. The tool SHELL * (a placeholder whose result the agent loop overwrites — the `skip_step` * pattern) lives in core, which is also where the typed * `agentfootprint.artifacts.presented` event is emitted. Core wires * artifacts; never the reverse. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.presentArtifact = exports.PRESENT_TOOL_NAME = void 0; /** * The reserved name of the auto-attached tool. Reserved ONLY when a store is * attached (the `read_skill` seam): a storeless agent may keep its own * `present`, because the framework attaches nothing there. */ exports.PRESENT_TOOL_NAME = 'present'; /** How many live refs a miss refusal lists. A lesson, not a catalog. */ const PRESENT_LIST_LIMIT = 10; /** `art_h7Kq… [chart/spec · 41210 bytes · 'Q3 sales']` — one listed ref. */ function describeRef(meta) { return `${meta.ref} [${meta.kind} · ${meta.bytes} bytes${meta.label !== undefined ? ` · '${meta.label}'` : ''}]`; } // LENS · tool-result · persistent-history // reads: the artifact's meta ← store.head(scope, ref); the live refs ← store.list(scope, {limit:10}) // both from the ArtifactStore port, the sole owner of those facts // the source is LIVE, so every clause is past tense and bound to the named call: a census rendered into a // result is re-read after the store has changed. // law: may omit, never deny; every clause anchored to the call it was composed on. /** * Resolve one `present` call over the port — ONE law for every dispatch * door. `head`, never `get`: presenting is the render-by-ref decision, and * the screen pays for the bytes later, under its own identity. */ async function presentArtifact(store, scope, args) { const ref = args.ref; const as = args.as; if (typeof ref !== 'string' || ref.trim().length === 0) { return { ok: false, refusal: `present needs \`ref\` — the art_… ticket of the artifact to hand to the screen. ` + `Store the data first (a tool that mints, or the placement threshold), then present ` + `its ref.`, }; } if (typeof as !== 'string' || as.trim().length === 0) { return { ok: false, refusal: `present needs \`as\` — the consumer vocabulary for how the screen should render ` + `'${ref}' (e.g. 'bar-chart', 'table', 'image'). Say what it is; the screen picks ` + `the renderer.`, }; } const meta = await store.head(scope, ref); if (meta === null) { const page = await store.list(scope, { limit: PRESENT_LIST_LIMIT }); // ANCHORED TO THE CALL, NOT TO THE MOMENT — BOTH ARMS. This refusal is a // TOOL RESULT: it is written into `history` and re-read on every later call // of the turn, including calls made after the model has stored something. // "Nothing is live … right now" was therefore a forecast — true when // composed, false the moment the run had an artifact — and a model // re-reading it is being told its own stored data does not exist. The // opening clause names the call (`present('<ref>')`), so the listing binds // to that call and stays true wherever it is read. // // The STOCKED arm needed the same repair for the mirror-image reason, and // did not get it until the sentence had a rule to fail: a bare "Live refs // in scope: …" is a census, and a census goes wrong in both directions — // a ref it names can be swept before the model re-reads it, and a ref // minted since will be missing from it. Neither error is visible to the // model, which reads a list and believes it is looking at the scope. Past // tense plus the call it was taken for is the whole fix: an inventory of a // finished moment cannot go stale. const listing = page.artifacts.length === 0 ? `Nothing was live in this run's scope when you made that call — store the data ` + `first, then present its ref.` : `These were live in this run's scope when you made that call: ` + `${page.artifacts.map(describeRef).join(', ')}.`; return { ok: false, missedRef: ref, refusal: `present('${ref}') found nothing under that ref in this run's scope — expired, ` + `swept, or never stored here. Nothing was presented. ${listing}`, }; } const label = typeof args.label === 'string' && args.label.trim().length > 0 ? args.label : meta.label; return { ok: true, result: { presented: true, ref: meta.ref, as, snapshot: { kind: meta.kind, mediaType: meta.mediaType, bytes: meta.bytes, ...(label !== undefined && { label }), }, }, }; } exports.presentArtifact = presentArtifact; //# sourceMappingURL=present.js.map