UNPKG

agentfootprint

Version:

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

108 lines • 4.96 kB
"use strict"; /** * toolChoice/record — where a classifier's pick and the model's call become record. * * Pattern: one writer for one committed key (`findings/ledger.ts · * recordFindings`, `middleware/ledger.ts · recordDecisions`). * Role: core/ layer leaf. Two call sites, one writer: the tools slot * files the `pick` / `pick-error` row before the model call * (`slots/buildToolsSlot.ts · composeStage`), `stages/callLLM.ts` * files the `outcome` row after the reply. Both go through * `recordToolChoice`, so the key is appended one way and the * matching event fires once per row. * Emits: `agentfootprint.tool_choice.picked` / `.failed` / `.outcome` — * identities (tool names), enums, numbers and a boolean. Never the * descriptions the classifier read, never the user's message. * * ## Append only, a fresh array per write * * A row is never replaced. Every write assigns `[...prior, row]` so the * committed value is a new array (a TypedScope array read is a live proxy * view — spread before use). The tools slot runs in an isolated subflow * whose `toolChoices` input is a FROZEN mount arg named `priorToolChoices`; * it hands the prior rows in explicitly and the mount's outputMapper carries * the fresh list back to the parent key. `callLLM` reads the parent key * itself and passes nothing. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.outcomeRowFor = exports.pickAttemptedFor = exports.pickRowFor = exports.recordToolChoice = void 0; const typedEmit_js_1 = require("../../../recorders/core/typedEmit.js"); /** * Append one row and emit its event. `prior` is the record as the caller's * frame holds it — passed by the tools slot (an isolated subflow that reads * the parent's rows as a frozen arg), defaulted to `scope.toolChoices` by * `callLLM`, which writes on the scope that owns the key. */ function recordToolChoice(scope, row, prior = scope.toolChoices) { scope.toolChoices = [...(prior ?? []), row]; emitRow(scope, row); } exports.recordToolChoice = recordToolChoice; function emitRow(scope, row) { if (row.kind === 'pick') { (0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.tool_choice.picked', { iteration: row.iteration, ...(row.chosen !== undefined && { chosen: row.chosen }), confidence: row.confidence, offered: row.offered.length, served: row.served.length, narrowed: row.narrowed, ...(row.narrowedSkipped !== undefined && { narrowedSkipped: row.narrowedSkipped }), latencyMs: row.latencyMs, ...(row.usage !== undefined && { inputTokens: row.usage.inputTokens, outputTokens: row.usage.outputTokens, }), }); return; } if (row.kind === 'pick-error') { (0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.tool_choice.failed', { iteration: row.iteration, ...(row.status !== undefined && { status: row.status }), latencyMs: row.latencyMs, }); return; } (0, typedEmit_js_1.typedEmit)(scope, 'agentfootprint.tool_choice.outcome', { iteration: row.iteration, called: [...row.called], ...(row.firstAgrees !== undefined && { firstAgrees: row.firstAgrees }), ...(row.miss !== undefined && { missed: [...row.miss.wanted] }), }); } /** The pick or pick-error row filed for `iteration`, or nothing — the call had no pick. */ function pickRowFor(rows, iteration) { let found; for (const row of rows ?? []) { if (row.kind === 'pick' && row.iteration === iteration) found = row; } return found; } exports.pickRowFor = pickRowFor; /** Whether ANY pick was attempted for `iteration` — a `pick` or a `pick-error` row. */ function pickAttemptedFor(rows, iteration) { return (rows ?? []).some((row) => (row.kind === 'pick' || row.kind === 'pick-error') && row.iteration === iteration); } exports.pickAttemptedFor = pickAttemptedFor; /** * The comparison row for one reply. Pure. `firstAgrees` exists only when * both readings do — a pick with a `chosen` and a reply with a call; `miss` * only when the call was narrowed and the model named a tool outside what * was served (a door is served, so it can never be a miss). */ function outcomeRowFor(pick, called, iteration) { const first = called[0]; const wanted = pick?.narrowed === true ? called.filter((name) => !pick.served.includes(name)) : []; return { kind: 'outcome', iteration, called: [...called], ...(pick?.chosen !== undefined && first !== undefined && { firstAgrees: pick.chosen === first }), ...(wanted.length > 0 && { miss: { wanted } }), }; } exports.outcomeRowFor = outcomeRowFor; //# sourceMappingURL=record.js.map