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
TypeScript
/**
* 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;