UNPKG

agentfootprint

Version:

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

207 lines (206 loc) • 10.6 kB
/** * assessment/compose — the answer's standing as ONE line for the person, under * its own opt-in arm (`.answerLayer({ standingLine: true })`). * * Pattern: Lens. A pure composer over the standing as data * (`AnswerAssessmentData`, the projection the answer layer files on * `turn_end` and on `agentfootprint.answer.assessed`) and the values * a tool's rule assumed this turn (the same `AssumedLine`s the * "Assumed" block prints — `arguments/serve.ts` · `assumedLinesFor`). * One closed, versioned table of words; nothing here reads the run. * Role: core/ layer leaf of the answer layer (honesty layer 4). Loaded only * by the layer's stage (`assessment/stage.ts`), which is itself * loaded through `import()` under the arm — a plain agent's graph * never carries it. * Emits: N/A. * * ## What the line may say * * The owner's five words, in precedence order — ask > not sure > known > * consistent > not assessed — each rendered from the fold's value and reasons * alone (`assessment/assess.ts` · `assessAnswer`), never from how sure the * model sounded. "Consistent with the run's record" is never "known" and never * "verified". A reason is named by what the record holds, in the past tense of * the run that holds it. The ONE place the line prints a value is an assumed * argument, and then only in the tool's own argument view (a value that view * hides is said to be hidden, never printed) — the "Assumed" block's rule, so * "the standing line owns the assumed sentence" whenever both arms are on: * one composer for one fact. * * ## What it never does * * It never edits the model's reply. The line is appended after it by the * final branch's one composer (`semantics/coverage/answer.ts` · * `composeAnswerWithCoverage`), under the separator the limits block uses, and * only on a PROSE answer: a typed answer (`.outputSchema()`) is JSON, and the * builder refuses the line beside it — as it refuses the line beside * `.answerValidation()`, which judges exact bytes. */ import type { AnswerAssessment, AssessmentCheck, AssessmentReason, AssessmentReasonSource } from './types.js'; /** * Bumped whenever any word of the tables below changes. 2: the named clauses * (`NAMED_REASON_WORDS`) and the assumed clause's named form — said only for a * call whose tool declares a `displayName`; every version-1 line is unchanged. * 3: the `script-unmatched` clause and the `script-match` check's word; every * version-2 line is unchanged. */ export declare const STANDING_LINE_VERSION = 3; /** * WHEN the line is said — `.answerLayer({ standingLine: { when } })`: * * - `'always'` — on every prose answer (`standingLine: true` is this); * - `'unless-consistent'` — every standing but `consistent`; * - `'not-sure-or-ask'` — only `not-sure` and `ask`. * * `ask` is said in every mode. The mode decides only whether the LINE is * appended: the standing travels as data in every mode * (`turn_end.answerAssessment`, `agentfootprint.answer.assessed`), the limits * block is appended whether or not the line is, and the answer the run * returns, saves and emits is one string — so memory keeps exactly what was * delivered. No line is never a verdict: under `'not-sure-or-ask'` it means * no reason fired, which includes "nothing could be checked". */ export type StandingLineWhen = 'always' | 'unless-consistent' | 'not-sure-or-ask'; /** * Whether `when` appends the line for an answer of `standing`. Never decides * anything else: the standing, its data and the limits block are the same in * every mode. * * @example * ```ts * standingLineSaid('not-sure-or-ask', 'consistent'); // false — no line; the data still travels * standingLineSaid('not-sure-or-ask', 'ask'); // true — `ask` is said in every mode * ``` */ export declare function standingLineSaid(when: StandingLineWhen, standing: AnswerAssessment['standing']): boolean; /** * The line's first words, one per standing — the owner's words. They are * plain words a model can write too, so a reader of the answer text never * matches on them alone: the answer account finds the line by rebuilding the * WHOLE line from the run's record (`answer-account/account.ts` · * `readAnswer`, through `standingLineOf`). */ export declare const STANDING_LINE_OPENINGS: Readonly<Record<AnswerAssessment['standing'], string>>; /** The answer's standing as data — the projection filed on `turn_end` and on the event. */ export interface AnswerAssessmentData { readonly assessment: AnswerAssessment['assessment']; readonly standing: AnswerAssessment['standing']; readonly reasons: readonly AssessmentReason[]; readonly checked: readonly { readonly layer: AnswerAssessment['checked'][number]['layer']; readonly check: AssessmentCheck; readonly ran: number; readonly of: number; }[]; } /** One value a tool's rule assumed this turn — the "Assumed" block's line, as data. */ export interface StandingAssumedValue { readonly toolName: string; readonly argument: string; readonly value: string; readonly hidden: boolean; } /** * The projection of one fold — names, enums and counts only: the value, its * rendering, the reason KINDS and the checks that ran. No witness pointer, no * digest, no value. */ export declare function assessmentDataOf(a: AnswerAssessment): AnswerAssessmentData; /** * The standing as data read back off a record — `turn_end.answerAssessment` * as a recording holds it — or `undefined` unless every field is in this * version's vocabulary (a value, a standing with an opening, known reason * kinds, checks with counts). A fresh copy, field by field. */ export declare function assessmentDataFrom(value: unknown): AnswerAssessmentData | undefined; /** * Whether a before-tool rewrite with no declared origin fired * `argument-assumed` — the fold's witness that points at `middlewareDecisions` * rather than at a row. The line then names it once, without a value. */ export declare function rewrittenBehind(assessed: AnswerAssessment): boolean; /** * Where this turn's assumed values are read from: the ledger, the turn, and * the before-tool rewrites a row may have been superseded by — handed to the * "Assumed" block's own reader (`arguments/serve.ts` · `assumedLinesFor`), * which the caller loads: the answer layer's stage through `import()` (the * optional-family law), the answer account statically. */ export interface AssumedValuesSource { readonly ledger: readonly { readonly kind: string; }[]; readonly turn: number; readonly readDecisions: (() => readonly unknown[]) | undefined; } /** * The source of the line's assumed values in `record` (committed state) — * `undefined` when the line names none: no `argument-assumed` reason, no * turn, or no `argument` row. Rows that are not objects are passed over, so a * damaged record is read, never thrown on. */ export declare function assumedValuesSourceOf(reasons: readonly AssessmentReason[], record: Readonly<Record<string, unknown>>): AssumedValuesSource | undefined; /** * What the line may NAME — given only when a tool the reasons rest on * declares a `displayName`: each reason's source (`AnswerAssessment` · * `reasons[].source`, the fold's own projection) and the app's words for each * tool (registered name → display name). Without it the line is the bytes it * always was. */ export interface StandingNames { readonly sources: ReadonlyMap<AssessmentReason, AssessmentReasonSource>; readonly displayNames: ReadonlyMap<string, string>; } /** * The words a record kept for the line — registered tool name → display name, * as `turn_end.standingLineNames` holds them — or `undefined` unless every * entry is one a person may read, exactly as the stage filed it (the rule * `defineTool` holds a `displayName` to, trimmed). A fresh map, entry by * entry. */ export declare function standingNamesFrom(value: unknown): ReadonlyMap<string, string> | undefined; /** The fold's sources keyed by reason — what {@link StandingNames} carries. */ export declare function sourcesByReason(a: AnswerAssessment): ReadonlyMap<AssessmentReason, AssessmentReasonSource>; /** * The display names the line may use for `a`: for every tool a `not-sure` * reason's calls name, and every tool whose rule assumed a value, the app's * words when its tool declares them — read through `displayNameOf`, the * agent's own tools. Empty unless the standing is `not-sure` (only that line * names reasons) and a tool declared a name: the line then says what it always * said. */ export declare function standingDisplayNames(a: AnswerAssessment, assumed: readonly StandingAssumedValue[], displayNameOf: (toolName: string) => string | undefined): ReadonlyMap<string, string>; /** * The standing line for one answer — `''` never: every standing has words. * * `assumed` is this turn's assumed values (empty unless the inputs layer filed * `default` rows); `rewritten` says a before-tool middleware rewrote a ruled * argument without declaring where the value came from (the fold's other * `argument-assumed` witness). `names` lets a `not-sure` line name the calls a * reason rests on in the app's words — only calls whose tool declares a * `displayName`, the rest counted; never the registered name. Without it, or * when no such call is behind a reason, the clause is today's, word for word. * Naming never changes the standing: the opening is read from `data` alone. * * @example * ```ts * standingLineOf( * { assessment: 'unknown', standing: 'not-sure', reasons: ['argument-assumed'], checked: [] }, * [{ toolName: 'search_logs', argument: 'window', value: '2h', hidden: false }], * false, * ); * // 'Not sure — window = "2h" was assumed by search_logs\'s rule, not given by you.' * * standingLineOf( * { assessment: 'unknown', standing: 'not-sure', reasons: ['declared-absent'], checked: [] }, * [], * false, * { * sources: new Map([['declared-absent', { kind: 'calls', calls: [{ toolCallId: 'c1', toolName: 'load_attachment' }] }]]), * displayNames: new Map([['load_attachment', 'loading the attached data']]), * }, * ); * // 'Not sure — loading the attached data reported no matching results.' * ``` */ export declare function standingLineOf(data: AnswerAssessmentData, assumed: readonly StandingAssumedValue[], rewritten: boolean, names?: StandingNames): string;