UNPKG

agentfootprint

Version:

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

114 lines (113 loc) • 5.61 kB
/** * ontology/score — did an answer name the declared gap, and where the need * would be met? Scored off the record, by declared strings, with no judge. * * Pattern: pure functions over the served spec and one turn's own facts * (the answer text, the tool names called, the library's own * unsupported-values verdict). No model, no clock, no I/O. * Role: Lens, for a BENCH. It is the measuring stick the design page * promised (docs/design/2026-09-ontology.md § Measured): the first * number for "the answer names the source" was a regex over prose; * this is the rule that replaces it, so every wording change is * measured the same way. Exported on the `agentfootprint/ontology` * door because the map's consumer runs the bench on ITS host with * ITS questions, and the rules must be the library's, not re-derived * there. * Emits: N/A. * * THE LAW OF THE SCORE * A check matches DECLARED STRINGS ONLY — a term's or a source's id and * aliases, a tool's name — whole-word, case-insensitive, an `_` in an * id standing for a space or a hyphen, a plural `s` allowed. Nothing fuzzier. So an answer that * says "UCS Manager" when the source's id is `influx_ucs` and no alias says * "UCS Manager" scores NO, and that is the point: the declaration must * carry the words people use, because those are the words the model * reads too. The scorer never decides an answer is good; it counts what * the answer names. * * THE EXPECTATION IS THE BENCH AUTHOR'S, DECLARED BEFORE THE RUN * `expected.gap` names the term or source the question turns on — the * `_findings.expect` precedent: declared before the call, judged after. A * gap that names nothing the spec declares is refused, naming it: a bench * whose oracle lies must fail loudly, as a map naming an unregistered tool * does at build. */ import type { OntologySpec } from './types.js'; /** What a bench question declares about itself, before the run. */ export interface AbsenceExpectation { /** * The declared gap the answer should name: a term id or a source id of * the served map. Absent when the question expects DATA, not a gap — then * only the counts are read and the two naming checks are `undefined`. */ readonly gap?: string; } /** One turn's own facts, read off the record by the caller. */ export interface AbsenceTurn { /** The final answer, as returned. */ readonly answer: string; /** The tool names the model called this turn, in order. */ readonly toolCalls: readonly string[]; /** * The library's own verdict on values the answer stated that no tool * result carried (`AgentState.unsupportedValues.values.length`), when the * evidence gate was armed and the record carries it. Absent otherwise. */ readonly unsupportedValues?: number; } /** The score of one turn — every field a count, a yes/no, or the strings that matched. */ export interface AbsenceScore { /** The expected gap, as declared. */ readonly gap: string | undefined; /** What the spec says the gap is. `'none'` when no gap was expected. */ readonly gapKind: 'term' | 'source' | 'none'; /** The answer contains the gap's id or one of its declared aliases. `undefined` when no gap was expected. */ readonly namedGap: boolean | undefined; /** * The answer contains a declared neighbour of the gap — a source that holds * the term, a tool that reads it, a term one relation away; for a source, * a term it holds or a tool that reads through it. `undefined` when no gap * was expected or the spec declares no neighbour for it. */ readonly namedWhere: boolean | undefined; /** The neighbours the answer named, by declared id — the evidence for `namedWhere`. */ readonly where: readonly string[]; /** The neighbours the spec declares for the gap — what `namedWhere` was checked against. */ readonly neighbours: readonly string[]; /** Tool calls this turn. */ readonly toolCalls: number; /** The library's unsupported-values count, as handed in; `undefined` when the record carried none. */ readonly unsupportedValues: number | undefined; /** * The words of the map the answer used on the person — `ontology` and its * forms, `map` and `maps`: the header's own vocabulary the ask says to keep * from the person. A wording metric, reported as the words found. */ readonly mapWords: readonly string[]; } /** The tally over many turns — `k` of `n` per check, counts summed. */ export interface AbsenceSummary { readonly turns: number; readonly namedGap: { readonly k: number; readonly n: number; }; readonly namedWhere: { readonly k: number; readonly n: number; }; readonly citedMap: { readonly k: number; readonly n: number; }; readonly toolCalls: number; /** Summed over the turns whose record carried a verdict; `undefined` when none did. */ readonly unsupportedValues: number | undefined; } /** * Score one turn against the served map and the bench's expectation. * Throws when `expected.gap` names nothing the spec declares. */ export declare function scoreAbsence(spec: OntologySpec, turn: AbsenceTurn, expected?: AbsenceExpectation): AbsenceScore; /** The tally: `k` of `n` per yes/no check over the turns that had one; counts summed. */ export declare function summarizeAbsence(scores: readonly AbsenceScore[]): AbsenceSummary;