agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
79 lines (78 loc) • 4.46 kB
TypeScript
/**
* requestEvidence — the door a tool call's REQUEST attempts enter its run by.
*
* Pattern: a per-dispatch port (the `LLMCallHooks` precedent) + one allow-list.
* Role: The seam between a retry series that runs BELOW a tool — inside
* `mcpClient`'s transport, built before any run existed — and the run
* that dispatched the call. The dispatcher builds the port for each
* call (`requestEvidenceFor`) and puts it on the call's context under
* an internal symbol; the MCP tool hands it down to the series that
* sends its request (`mcp/requestLedger.ts`); the series files ONE
* record when the request ends. Nothing here retries anything.
* Emits: agentfootprint.tools.request_attempts
*
* Why a port and not a scope: a decorator constructed by the consumer before
* any run exists can never reach a scope (`resilienceHooks` says the same of
* the provider decorators). The call is the one thing both sides share, so the
* port rides the call — and the identity on the event (`toolCallId`,
* `toolName`, `iteration`, the run's ids in the meta) is stamped HERE, from the
* dispatch, never by whatever files: a filing cannot claim another call.
*
* Why an internal symbol and not a public `ctx` field: this is the evidence
* path for the library's own retry series, not a retry API. A public member
* would invite every tool to file attempts the library cannot vouch for. The
* symbol is enumerable, so a wrapper that copies the context (`{ ...ctx }`)
* keeps it; a filing from anyone else still passes the allow-list below.
*
* The port only FILES. What cancels a call is the call's own context signal
* (`ctx.signal`), read by the MCP tool whether or not a port rides along — a
* cancellation must never depend on evidence being attributable.
*
* THE ALLOW-LIST is the whole record: numbers, the library's own words, the
* endpoint label the app declared or else the endpoint's ORIGIN. A filing with
* anything else in it — a field nobody listed, a word outside its set, a URL
* with a path, an array that is not a plain one — is refused whole, never
* trimmed into something it did not say. Values are read as DATA, through own
* property descriptors, so no getter, `toJSON` or array subclass a filing
* carries ever runs or reaches the record.
*/
import type { ToolsRequestAttemptsPayload } from '../events/payloads.js';
import { type EmitableScope } from '../recorders/core/typedEmit.js';
/** At most this many attempts are listed per request; the rest are counted (`omitted`). */
export declare const MAX_REQUEST_ATTEMPT_ROWS = 100;
/** What a request's series files: the event's payload, less the identity the dispatcher stamps. */
export type RequestEvidence = Omit<ToolsRequestAttemptsPayload, 'toolCallId' | 'toolName' | 'iteration'>;
/** Where one dispatched call's request evidence goes. */
export interface RequestEvidenceSink {
/** File the request's record. Never throws; a filing outside the allow-list is refused whole. */
file(evidence: RequestEvidence): void;
}
/** @internal The context key the dispatcher puts a call's sink under. */
export declare const REQUEST_EVIDENCE: unique symbol;
/** What the dispatcher spreads into a call's context. */
export interface RequestEvidencePort {
readonly [REQUEST_EVIDENCE]: RequestEvidenceSink;
}
/** The call the sink files for — the dispatch's own identity. */
export interface RequestEvidenceCall {
readonly toolName: string;
readonly toolCallId: string;
readonly iteration: number;
}
/**
* The port for one dispatch: files `agentfootprint.tools.request_attempts`
* mid-stage through `typedEmit` (so the run's redaction rule serves it, and the
* meta carries the real `runtimeStageId`), stamped with `call`.
*/
export declare function requestEvidenceFor(scope: EmitableScope, call: RequestEvidenceCall): RequestEvidencePort;
/**
* The sink a call's context carries, or `undefined` — a context from a door with
* no run (`mcpServe`, a direct `execute`), or one a wrapper rebuilt without it.
* Never throws, whatever `ctx` is.
*/
export declare function requestEvidenceOf(ctx: unknown): RequestEvidenceSink | undefined;
/**
* `raw` rebuilt from allow-listed fields — a fresh plain object — or
* `undefined` when anything in it is outside the list.
*/
export declare function servedRequestEvidence(raw: unknown): RequestEvidence | undefined;