agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
124 lines (123 loc) • 6.76 kB
TypeScript
/**
* rerunWithoutSources — the counterfactual re-run as ONE call: the product
* loop over the existing ablation machinery.
*
* Take a finished run's `ContextBugReport`, name the sources to ignore
* (plain ids — injection id, tool name, or step id), and hand it the SAME
* `AblationRunner` the localizer's causal mode already uses. It resolves the
* ids to their `AblationSpec`s, runs the existing `runAblationProbe` (N ≥ 2
* seeded reruns — never a single-run diff), and returns
* `{ answer, answers, removed, whatChanged, runs }`. Companion
* `removableSources(report)` gives a UI the toggle vocabulary.
*
* No new machinery: this file never calls `applyAblations` itself — the
* resolved specs go to the consumer's runner, whose body applies them at
* agent construction (the documented seam; see `applyAblations`' example).
* Works with mock providers ($0) and real ones alike.
*
* ## Honesty tiers (§B2, unchanged)
*
* Without `checkBaseline: true` the result reports what was OBSERVED
* (`whatChanged.answerFlipped`, similarity, a plain summary) and carries NO
* `verdict` — an observation, not a conviction. With it, the unchanged
* scenario is probed too and `verdictFor` produces the causal-tier claim —
* the same discipline as `localizeContextBug`'s causal mode.
*/
import type { Embedder } from '../influence-core/index.js';
import type { AblationRunner, AblationRunStats, AblationSpec, AblationVerdict, ContextBugReport, OutcomeComparator, SimilarityStats, SuspectKind } from './types.js';
/** A source to remove: a plain id (resolved against the report's suspects) or an explicit spec. */
export type IgnoredSource = string | AblationSpec;
export interface RerunWithoutSourcesOptions {
/** The completed run's localizer report — where the suspects and their ablation specs live. */
readonly report: ContextBugReport;
/**
* The sources to IGNORE on the re-run. A string matches a suspect by
* `detail.injectionId`, then `detail.toolName`, then `source` (runtimeStageId) —
* first ranked match wins. Unknown ids THROW (listing what `removableSources`
* offers) — a silent no-op re-run would lie. An explicit `AblationSpec`
* passes through untouched (escape hatch, incl. `kind: 'arg'` for runners
* that handle input overrides).
*/
readonly ignore: readonly IgnoredSource[];
/**
* The consumer's counterfactual runner — the SAME `AblationRunner` contract
* the localizer's causal mode uses (fresh agent + provider per call, apply
* the specs with `applyAblations` at construction, thread `seed`). Works
* with mock providers ($0) and real ones alike.
*/
readonly runner: AblationRunner;
/** The original run's answer the re-runs are compared against. */
readonly originalAnswer: string;
/** Embedder for the similarity readout + the default comparator. `mockEmbedder()` is fine offline. */
readonly embedder: Embedder;
/** Seeded re-runs. Default 3; clamped to ≥ 2 (never single-run claims). */
readonly samples?: number;
/** Did the answer CHANGE? Default: embedding similarity < `flipThreshold`.
* Strongly recommended with `mockEmbedder`: pass a domain comparator. */
readonly answerChanged?: OutcomeComparator;
/** Similarity floor for the default comparator. Default 0.8. */
readonly flipThreshold?: number;
/**
* Also probe the UNCHANGED scenario (empty specs) to check it reproduces
* stably. Costs `samples` extra runs; unlocks the causal-tier `verdict`.
* Default false — the result then reports observations only, and says so.
*/
readonly checkBaseline?: boolean;
}
/** What changed between the original run and the re-runs — OBSERVED tier. */
export interface WhatChanged {
/** A MAJORITY of the seeded re-runs produced a changed answer. */
readonly answerFlipped: boolean;
/** Re-runs whose answer changed / total re-runs. */
readonly flips: number;
readonly samples: number;
/** Embedding similarity of each re-run answer to the original (variance always reported). */
readonly similarityToOriginal: SimilarityStats;
/** Whether the un-ablated baseline was probed for stability (`checkBaseline`). */
readonly baselineChecked: boolean;
/** Plain-language recap. PRESENTATION ONLY — read the fields as data, never parse this. */
readonly summary: string;
}
export interface RerunWithoutSourcesResult {
/** The re-run's answer — seed 0 of the seeded re-runs. */
readonly answer: string;
/** Every seeded re-run's answer, in seed order (`answers[0] === answer`). */
readonly answers: readonly string[];
/** The resolved ablation specs the runner received — one per distinct ignored source. */
readonly removed: readonly AblationSpec[];
readonly whatChanged: WhatChanged;
/** The raw probe stats behind `whatChanged` (incl. `cost` when the runner reports `RunCost`). */
readonly runs: AblationRunStats;
/** Baseline (nothing-removed) probe stats — present only with `checkBaseline: true`. */
readonly baseline?: AblationRunStats;
/**
* The CAUSAL-tier claim — present only with `checkBaseline: true` (§B2: a
* causal verdict needs seeded reruns AND a stable baseline). Without it the
* result stops at `whatChanged`, honestly observational.
*/
readonly verdict?: AblationVerdict;
}
export declare function rerunWithoutSources(options: RerunWithoutSourcesOptions): Promise<RerunWithoutSourcesResult>;
/** One source a UI can offer as an "ignore" toggle. */
export interface RemovableSource {
/** The id `rerunWithoutSources` accepts in `ignore` (injectionId ?? toolName ?? runtimeStageId). */
readonly id: string;
/** `'tool' | 'injection' | 'memory'` — the library-filterable kinds only. */
readonly kind: SuspectKind;
/** Human label (via `suspectLabel`) for the toggle row. */
readonly label: string;
/** runtimeStageId — drillable with the trace-toolpack tools. */
readonly source: string;
readonly stageName: string;
/** The suspect's proxy score (correlational — sizes an Influence Map node, never convicts). */
readonly score: number;
/** The spec `rerunWithoutSources` will send for this id. */
readonly spec: AblationSpec;
}
/**
* The report's removable sources, ranked order, de-duplicated by id (first =
* highest-ranked occurrence wins). Excludes kind `'stage'` (nothing to remove)
* and `'arg'` (the library cannot filter run input — the runner must override
* it; pass an explicit `kind: 'arg'` spec to `ignore` if yours does).
*/
export declare function removableSources(report: ContextBugReport): readonly RemovableSource[];