UNPKG

agentfootprint

Version:

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

522 lines • 24.3 kB
"use strict"; /** * Row "It understood" — how the question was routed to a skill, and whether the * decided skill reached the model. Also check 1, `decided-delivered`. * * Two vouchers, two lines: the SCORES are the app's (its scorer produced them), * the VERDICT is the library's (`skill.turn_routed`). Neither `decisive` nor * `relevance` is a confidence — confidence stays "not recorded" in v1. * * "Not delivered" is a claim of ABSENCE, so it needs proof: every system-prompt * composition of iterations 1..N is recorded and COMPLETE (the recorded skill * rows of that compose stage number exactly `sourceBreakdown.skill.count`). * Anything less and the check is unreachable — a missing row is never read as * "the skill was not given". * * The verdict has ONE reader, {@link routingVerdictOf}: the `skill.turn_routed` * event — or, on a RESUMED leg (whose recording holds that leg's events only), * the verdict the RouteTurn stage committed before the pause: `turnRoute` (by, * from, to, the rule's witness, the offered menu) and the scorer's * `entryScores` / `entryScorer`, carried in the state the run paused with * (`snapshot.initialState`) and on into the committed state the pointers land * in. Take 3 of the demo video: a resumed answer said "The routing happened * before the pause and is not in this record" while its record held the * verdict — the fold read the event only. What the state does NOT carry is * said by the lines that need it: the decider's model (the `noModel` line) and * whether the verdict was decisive (absent from the fact). */ Object.defineProperty(exports, "__esModule", { value: true }); exports.readUnderstood = exports.routingVerdictOf = exports.MAX_REFUSAL_LINES = exports.MAX_REFUSALS = void 0; const render_js_1 = require("../render.js"); const view_js_1 = require("../view.js"); const calls_js_1 = require("./calls.js"); const checked_js_1 = require("./checked.js"); const common_js_1 = require("./common.js"); /** Refusals listed in `facts.routing.refusals`; refusal LINES (distinct rules) in the row. */ exports.MAX_REFUSALS = 12; exports.MAX_REFUSAL_LINES = 3; const LIBRARY_DECISIONS = new Set(['entry', 'intent', 'continuity', 'decider']); /** The event's verdict, or `undefined` when its `by` cannot be read. */ function eventVerdict(routed) { const p = routed.payload; const by = (0, view_js_1.str)(p.by); if (by === undefined) return undefined; const decider = (0, view_js_1.isRecord)(p.decider) ? (0, view_js_1.str)(p.decider.model) : undefined; const witness = (0, view_js_1.isRecord)(p.witness) ? (0, view_js_1.str)(p.witness.text) : undefined; return { by, ...((0, view_js_1.str)(p.from) !== undefined && { from: (0, view_js_1.str)(p.from) }), ...((0, view_js_1.str)(p.to) !== undefined && { to: (0, view_js_1.str)(p.to) }), ...(typeof p.decisive === 'boolean' && { decisive: p.decisive }), ...((0, view_js_1.str)(p.scorer) !== undefined && { scorer: (0, view_js_1.str)(p.scorer) }), ...(witness !== undefined && { witness }), ...(Array.isArray(p.offered) && { offered: p.offered.length }), ...(decider !== undefined && { deciderModel: decider }), scores: Array.isArray(p.scores) ? p.scores.filter(view_js_1.isRecord) : [], at: (field) => field === 'witness' ? (0, common_js_1.at)(routed, 'witness', 'text') : field === 'deciderModel' ? (0, common_js_1.at)(routed, 'decider', 'model') : (0, common_js_1.at)(routed, field), scoreAt: (i, field) => (0, common_js_1.at)(routed, 'scores', i, field), held: false, }; } const sameValue = (a, b) => JSON.stringify(a) === JSON.stringify(b); /** * The verdict RouteTurn committed BEFORE a pause (`turnRoute`, `entryScores`, * `entryScorer`): present in the state the run paused with and carried * unchanged into the committed state, where the pointers land. `undefined` * when either state lacks it or they disagree — never guessed. */ function heldVerdict(ctx) { const paused = ctx.view.pausedWith; const state = ctx.view.state; if (!ctx.resumedLeg || paused === undefined || state === undefined) return undefined; const route = state.turnRoute; if (!(0, view_js_1.isRecord)(route) || !sameValue(route, paused.turnRoute)) return undefined; const by = (0, view_js_1.str)(route.by); if (by === undefined) return undefined; const scoresHeld = Array.isArray(state.entryScores) && sameValue(state.entryScores, paused.entryScores); const scorer = scoresHeld && sameValue(state.entryScorer, paused.entryScorer) ? (0, view_js_1.str)(state.entryScorer) : undefined; const witness = (0, view_js_1.isRecord)(route.witness) ? (0, view_js_1.str)(route.witness.text) : undefined; return { by, ...((0, view_js_1.str)(route.from) !== undefined && { from: (0, view_js_1.str)(route.from) }), ...((0, view_js_1.str)(route.to) !== undefined && { to: (0, view_js_1.str)(route.to) }), ...(scorer !== undefined && { scorer }), ...(witness !== undefined && { witness }), ...(Array.isArray(route.offered) && { offered: route.offered.length }), scores: scoresHeld ? state.entryScores.filter(view_js_1.isRecord) : [], at: (field) => field === 'witness' ? (0, common_js_1.stateAt)('turnRoute', 'witness', 'text') : (0, common_js_1.stateAt)('turnRoute', field === 'deciderModel' ? 'by' : field), scoreAt: (i, field) => (0, common_js_1.stateAt)('entryScores', i, field), held: true, }; } // FOLD · the one reader of the routing verdict for the account // consumers read this and never re-derive it: readUnderstood (the row, check 1) /** * The routing verdict this record holds: the `skill.turn_routed` event of * this leg, else — on a resumed leg — the verdict committed before the pause * ({@link heldVerdict}). `undefined` when neither holds one. An event whose * `by` cannot be read is passed over and counted, never read as "none". */ function routingVerdictOf(ctx) { const routedRow = ctx.view.first('skill.turn_routed'); if (routedRow !== undefined) { const verdict = eventVerdict(routedRow); if (verdict !== undefined) return verdict; ctx.noteUnread(); } return heldVerdict(ctx); } exports.routingVerdictOf = routingVerdictOf; function deliveredSkills(view) { const firstRow = new Map(); for (const e of view.ofType('context.injected')) { if (e.payload.slot !== 'system-prompt' || e.payload.source !== 'skill') continue; const id = (0, view_js_1.str)(e.payload.sourceId); if (id !== undefined && !firstRow.has(id)) firstRow.set(id, e); } return { ids: [...firstRow.keys()], firstRow }; } /** Every system-prompt composition of iterations 1..N is recorded and complete. */ function deliveryComplete(view, lastIteration) { if (lastIteration === undefined || lastIteration < 1) return false; const composed = view .ofType('context.slot_composed') .filter((e) => e.payload.slot === 'system-prompt'); for (let i = 1; i <= lastIteration; i++) { const compose = composed.filter((e) => e.payload.iteration === i).pop(); if (compose === undefined) return false; const breakdown = compose.payload.sourceBreakdown; if (!(0, view_js_1.isRecord)(breakdown)) return false; const skill = breakdown.skill; const expected = skill === undefined ? 0 : (0, view_js_1.isRecord)(skill) ? (0, view_js_1.num)(skill.count) : undefined; if (expected === undefined) return false; const rows = view .ofType('context.injected') .filter((e) => e.meta.runtimeStageId === compose.meta.runtimeStageId && e.payload.slot === 'system-prompt' && e.payload.source === 'skill' && typeof e.payload.sourceId === 'string').length; if (rows !== expected) return false; } return true; } function refusalsOf(ctx, to) { const refusals = []; for (const e of ctx.view.ofType('skill.rejected')) { const id = (0, view_js_1.str)(e.payload.requestedId); if (id !== undefined) refusals.push({ requestedId: id, by: 'skill-graph', kind: 'rule', event: e, named: false }); } for (const e of ctx.view.ofType('permission.check')) { const target = (0, view_js_1.str)(e.payload.target); // Only `deny` / `halt` refuse; `gate_open` lets the read proceed. if (e.payload.capability !== 'skill_read' || target === undefined || !calls_js_1.PERMISSION_REFUSALS.has(e.payload.result)) continue; const id = target.startsWith('skill:') ? target.slice('skill:'.length) : target; const rule = (0, view_js_1.str)(e.payload.policyRuleId); refusals.push({ requestedId: id, by: rule ?? 'permission', kind: 'rule', event: e, named: rule !== undefined, }); } return { all: refusals, ofDecided: refusals.filter((r) => r.requestedId === to) }; } const notRecordedFact = (missing = 'no-event') => ({ value: null, source: 'library', status: 'not-recorded', pointers: [], missing, }); function readUnderstood(ctx) { const { view, say } = ctx; const configuredEvent = view.first('agent.run_configured'); // A verdict with no readable `by` is passed over (counted), never read as "none". const routed = routingVerdictOf(ctx); const delivered = deliveredSkills(view); const appDecides = ctx.declarations.routing?.appDecides === true; const graph = configuredEvent?.payload.skillGraph; const configured = configuredEvent === undefined ? notRecordedFact() : (0, view_js_1.isRecord)(graph) ? { value: { ...((0, view_js_1.str)(graph.routing) !== undefined && { routing: (0, view_js_1.str)(graph.routing) }), ...((0, view_js_1.str)(graph.continuity) !== undefined && { continuity: (0, view_js_1.str)(graph.continuity) }), ...((0, view_js_1.str)(graph.scorer) !== undefined && { scorer: (0, view_js_1.str)(graph.scorer) }), }, source: 'library', status: 'recorded', pointers: [(0, common_js_1.at)(configuredEvent, 'skillGraph', 'routing')], } : { value: 'none', source: 'library', status: 'recorded', pointers: [(0, common_js_1.derived)(configuredEvent, '#meta/runId')], }; const confidenceLine = () => say('understood.confidence.none', { status: 'not-recorded', missing: 'not-built', chips: [(0, render_js_1.chip)('not-recorded', 'chip.notRecorded')], }); const appLines = () => appDecides ? [ say('understood.app.notRecorded', { status: 'not-recorded', missing: 'not-built', pointers: [(0, common_js_1.declarationAt)(ctx.declarations, 'routing.appDecides')], chips: [(0, render_js_1.chip)('not-recorded', 'chip.notRecorded')], }), ] : []; const refusals = refusalsOf(ctx, routed?.to); const deliveredFact = { value: delivered.ids.slice(0, exports.MAX_REFUSALS).map((id) => id.slice(0, common_js_1.FACT_TEXT_CHARS)), source: 'library', status: 'recorded', pointers: [...delivered.firstRow.values()].slice(0, exports.MAX_REFUSALS).map((e) => (0, common_js_1.at)(e, 'sourceId')), }; const base = { configured, confidence: notRecordedFact('not-built'), appDecision: appDecides ? notRecordedFact('not-built') : { value: null, source: 'app', status: 'not-applicable', pointers: [] }, delivered: deliveredFact, // Listed ≤ MAX_REFUSALS, ids cut at 200 — the model chooses both how many and how long. refusals: refusals.all.slice(0, exports.MAX_REFUSALS).map(({ requestedId, by, kind }) => ({ requestedId: requestedId.slice(0, common_js_1.FACT_TEXT_CHARS), by: by.slice(0, common_js_1.FACT_TEXT_CHARS), kind, })), ...(refusals.all.length > exports.MAX_REFUSALS && { refusalsOmitted: refusals.all.length - exports.MAX_REFUSALS, }), }; // ── a resumed leg of a routed agent: the routing happened before the pause ── const notConfigured = configuredEvent !== undefined && !(0, view_js_1.isRecord)(graph); if (ctx.resumedLeg && routed === undefined && !notConfigured) { return { routing: { ...base, verdict: notRecordedFact(), scores: notRecordedFact() }, lines: [ say('understood.resumed', { status: 'not-recorded', missing: 'before-pause', chips: [(0, render_js_1.chip)('before-pause', 'chip.beforePause')], }), ...appLines(), ], status: 'not-recorded', check: 'unreachable', unreachable: say('unreachable.decided', { status: 'not-recorded', missing: 'before-pause' }), checkPointers: [], beforePause: 'lost', }; } // ── no verdict on the record ── if (routed === undefined) { if (notConfigured && configuredEvent !== undefined) { return { routing: { ...base, verdict: notRecordedFact(), scores: notRecordedFact() }, lines: [ say('understood.notConfigured', { status: 'not-applicable', pointers: [(0, common_js_1.derived)(configuredEvent, '#meta/runId')], }), ...appLines(), ], status: 'not-applicable', check: 'not-applicable', checkPointers: [], }; } return { routing: { ...base, verdict: notRecordedFact(), scores: notRecordedFact() }, lines: [ say('understood.notRecorded', { status: 'not-recorded', missing: 'no-event', chips: [(0, render_js_1.chip)('not-recorded', 'chip.notRecorded')], }), ...appLines(), ], status: 'not-recorded', check: 'unreachable', unreachable: say('unreachable.decided', { status: 'not-recorded', missing: 'no-event' }), checkPointers: [], }; } // ── the verdict ── const by = routed.by; const to = routed.to; const toVars = (name) => to === undefined ? {} : (0, common_js_1.skillVars)(ctx, name, to, routed.at('to')); const lines = []; const byPointer = routed.at('by'); // A verdict read from the state the run paused with says so on each of its lines. const held = routed.held ? { chips: [(0, checked_js_1.heldChip)()] } : {}; if (by === 'entry' && to !== undefined) { const witness = routed.witness; lines.push(witness !== undefined ? say('understood.rule.witness', { vars: { ...toVars('skill'), witness: (0, render_js_1.v)(witness, 'person', routed.at('witness')), }, pointers: [byPointer], ...held, }) : say('understood.rule', { vars: toVars('skill'), pointers: [byPointer], ...held })); } else if (by === 'intent' && to !== undefined) { lines.push(say('understood.intent', { vars: toVars('skill'), pointers: [byPointer], ...held })); } else if (by === 'continuity' && to !== undefined) { lines.push(say('understood.continuity', { vars: toVars('skill'), pointers: [byPointer], ...held })); } else if (by === 'decider' && to !== undefined) { const model = routed.deciderModel; lines.push(model !== undefined ? say('understood.decider', { vars: { ...toVars('skill'), model: (0, render_js_1.v)(model, 'library', routed.at('deciderModel')), }, pointers: [byPointer], ...held, }) : say('understood.decider.noModel', { vars: toVars('skill'), pointers: [byPointer], ...held, })); } else if (by === 'menu') { const offered = routed.offered ?? 0; lines.push(offered > 0 ? say('understood.menu', { vars: { offered: (0, render_js_1.n)(offered) }, pointers: [byPointer], ...held }) : say('understood.menu.bare', { pointers: [byPointer], ...held })); const opened = delivered.ids[0]; if (opened !== undefined) { const row = delivered.firstRow.get(opened); lines.push(say('understood.menu.picked', { vars: (0, common_js_1.skillVars)(ctx, 'skill', opened, (0, common_js_1.at)(row, 'sourceId')), })); } } else { lines.push(say('understood.none', { pointers: [byPointer], ...held })); } // ── the scores: the app's numbers, when the pick is their top ── const scores = routed.scores; const top = scores[0]; const next = scores[1]; let scoresFact = notRecordedFact(); if (top !== undefined && next !== undefined && top.id === to && (0, view_js_1.num)(top.score) !== undefined && (0, view_js_1.num)(next.score) !== undefined && (0, view_js_1.str)(next.id) !== undefined) { const allOthersEqual = scores.slice(1).every((s) => s.score === next.score); const topVar = (0, render_js_1.v)((0, view_js_1.num)(top.score), 'app', routed.scoreAt(0, 'score')); const nextVar = (0, render_js_1.v)((0, view_js_1.num)(next.score), 'app', routed.scoreAt(1, 'score')); scoresFact = { value: { top: topVar.value, next: nextVar.value, nextId: (0, view_js_1.str)(next.id), allOthersEqual, }, source: 'app', status: 'recorded', pointers: [routed.scoreAt(0, 'score'), routed.scoreAt(1, 'score')], }; lines.push(allOthersEqual ? say('understood.scores.allOthers', { vars: { top: topVar, next: nextVar } }) : say('understood.scores', { vars: { top: topVar, next: nextVar, ...(0, common_js_1.skillVars)(ctx, 'nextSkill', (0, view_js_1.str)(next.id), routed.scoreAt(1, 'id')), }, })); } // ── check 1: was the decided skill given to the model? ── let check = 'not-applicable'; let unreachable; let signal; const checkPointers = [routed.at('to')]; if (LIBRARY_DECISIONS.has(by) && to !== undefined) { // One line per distinct refusing rule, within the shared item budget; the rest counted. const distinct = [ ...new Map(refusals.ofDecided.map((r) => [`${r.event.type}\u0000${r.by}`, r])).values(), ]; let listed = 0; for (const r of distinct) { if (listed >= exports.MAX_REFUSAL_LINES || !(0, common_js_1.takeItem)(ctx, Math.min(r.by.length, common_js_1.FACT_TEXT_CHARS))) { break; } listed += 1; lines.push(r.event.type.endsWith('skill.rejected') ? say('understood.rejected', { vars: toVars('skill'), pointers: [(0, common_js_1.at)(r.event, 'requestedId')], }) : r.named ? say('understood.refused', { vars: { ...toVars('skill'), by: (0, render_js_1.v)(r.by, 'library', (0, common_js_1.at)(r.event, 'policyRuleId'), common_js_1.FACT_TEXT_CHARS), }, pointers: [(0, common_js_1.at)(r.event, 'target')], }) : say('understood.refused.unnamed', { vars: toVars('skill'), pointers: [(0, common_js_1.at)(r.event, 'target')], })); } const unlisted = refusals.ofDecided.length - listed; if (unlisted > 0) { lines.push(say('understood.refused.more', { vars: { n: (0, render_js_1.n)(unlisted) }, pointers: refusals.ofDecided .slice(listed, listed + 1) .map((r) => (0, common_js_1.at)(r.event, r.event.type.endsWith('skill.rejected') ? 'requestedId' : 'target')), })); } const row = delivered.firstRow.get(to); if (row !== undefined) { check = 'reachable'; checkPointers.push((0, common_js_1.at)(row, 'sourceId')); lines.push(say('understood.delivered', { pointers: [routed.at('to'), (0, common_js_1.at)(row, 'sourceId')], chips: [(0, render_js_1.chip)('decided-delivered', 'chip.decidedDelivered', 'ok')], })); } else if (deliveryComplete(view, ctx.answeringIteration)) { check = 'reachable'; const other = delivered.ids[0]; const composes = view .ofType('context.slot_composed') .filter((e) => e.payload.slot === 'system-prompt') .map((e) => (0, common_js_1.at)(e, 'slot')); lines.push(other !== undefined ? say('understood.notDelivered.other', { vars: { ...toVars('skill'), ...(0, common_js_1.skillVars)(ctx, 'other', other, (0, common_js_1.at)(delivered.firstRow.get(other), 'sourceId')), }, pointers: composes, chips: [(0, render_js_1.chip)('decided-not-delivered', 'chip.decidedNotDelivered', 'bad')], }) : say('understood.notDelivered', { vars: toVars('skill'), pointers: composes, chips: [(0, render_js_1.chip)('decided-not-delivered', 'chip.decidedNotDelivered', 'bad')], })); signal = say('signal.decidedNotDelivered', { vars: toVars('skill'), pointers: composes }); } else { check = 'unreachable'; // A held verdict: what the model was given before the pause is that part's events. const missing = routed.held ? 'before-pause' : 'no-event'; lines.push(say('understood.delivery.unknown', { status: 'not-recorded', missing, chips: [(0, render_js_1.chip)('not-recorded', 'chip.notRecorded')], })); unreachable = say('unreachable.delivery', { status: 'not-recorded', missing }); } } lines.push(confidenceLine(), ...appLines()); const verdict = { value: { by, ...(routed.from !== undefined && { from: routed.from }), ...(to !== undefined && { to }), ...(routed.decisive !== undefined && { decisive: routed.decisive }), ...(routed.scorer !== undefined && { scorer: routed.scorer }), }, source: 'library', status: 'recorded', pointers: [byPointer, ...(to !== undefined ? [routed.at('to')] : [])], }; return { routing: { ...base, verdict, scores: scoresFact }, lines, status: 'recorded', check, ...(unreachable !== undefined && { unreachable }), ...(signal !== undefined && { signal }), checkPointers, ...(routed.held && { beforePause: 'held' }), }; } exports.readUnderstood = readUnderstood; //# sourceMappingURL=understood.js.map