agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
99 lines (98 loc) • 4.23 kB
TypeScript
/**
* CheckInRecorder — the audit record for evidence-carrying human consent.
*
* Two exports, both for `agentfootprint.checkin.*`:
*
* • `checkInEventsBridge(...)` — an {@link EmitBridge} factory (same shape as
* `costRecorder` / `permissionRecorder`). Forwards `checkin.request` /
* `checkin.decision` `$emit`s to the EventDispatcher so
* `agent.on('agentfootprint.checkin.*')` fires. Attached ALWAYS by the
* Agent (zero-cost until a `checkIn`-declaring tool trips) — this is pure
* wiring, not a store.
*
* • `CheckInRecorder` — a compose-a-STORE recorder (no base class). Attach it
* with `agent.attach(new CheckInRecorder())` to CAPTURE every ask + decision
* as a queryable record — the "the decision is a record" pillar. It reads
* the same `$emit` stream (footprintjs calls `onEmit` for every emit), so it
* works whether or not any `agent.on(...)` listener is attached.
*
* Pattern: Factory over EmitBridge (wiring) + compose-a-store CombinedRecorder
* (capture). Telemetry rides the EMIT channel — never CommitBundle.
*/
import type { CombinedRecorder, EmitEvent } from 'footprintjs';
import { EmitBridge, type EmitBridgeOptions } from './EmitBridge.js';
import type { CheckInRequest } from '../../core/checkin.js';
export type CheckInRecorderBridgeOptions = Omit<EmitBridgeOptions, 'id' | 'prefix'> & {
readonly id?: string;
};
/**
* The always-on dispatcher bridge for `agentfootprint.checkin.*`. Wiring only:
* forwards the emits so typed `agent.on(...)` listeners fire.
*/
export declare function checkInEventsBridge(options: CheckInRecorderBridgeOptions): EmitBridge;
/** One captured check-in ask. */
export interface CheckInRequestRecord {
readonly toolName: string;
readonly toolCallId: string;
readonly iteration: number;
/** The typed ask + evidence pack. */
readonly request: CheckInRequest;
}
/** One captured human decision. */
export interface CheckInDecisionRecord {
readonly toolName: string;
readonly toolCallId: string;
readonly iteration: number;
/** The request this decision answered (`CheckInRequest.id`). */
readonly requestId: string;
readonly approved: boolean;
readonly by: string;
readonly note?: string;
/** The registered component that collected this decision (9.24.0), when the
* ask carried one — which SURFACE the person answered through. */
readonly componentId?: string;
}
/** Roll-up counts across a run (or the recorder's lifetime). */
export interface CheckInStats {
readonly requested: number;
readonly approved: number;
readonly declined: number;
/** Asks with no decision yet (paused, awaiting a human). */
readonly pending: number;
}
/**
* A queryable store of every check-in ask + decision. Attach once; read after
* the run (or between runs — it accumulates across `agent.run()` calls until
* you detach or make a fresh one).
*
* @example
* const checkins = new CheckInRecorder();
* agent.attach(checkins);
* await agent.run({ message });
* // ...human approves...
* await agent.resume(
* outcome.checkpoint,
* checkInApproved({ request: outcome.checkIn, by: 'alice' }),
* );
* checkins.getStats(); // { requested: 1, approved: 1, declined: 0, pending: 0 }
* checkins.getDecisions(); // [{ toolName, approved: true, by: 'alice', ... }]
*/
export declare class CheckInRecorder implements CombinedRecorder {
readonly id: string;
private readonly requests;
private readonly decisions;
/**
* @param id Stable recorder id for idempotent attach/detach. Give distinct
* ids if you attach more than one to the same agent.
*/
constructor(id?: string);
onEmit(event: EmitEvent): void;
/** Every check-in ask captured, oldest first. */
getRequests(): readonly CheckInRequestRecord[];
/** Every human decision captured, oldest first. */
getDecisions(): readonly CheckInDecisionRecord[];
/** Roll-up counts. `pending` = asks still awaiting a decision. */
getStats(): CheckInStats;
/** Drop all captured records (e.g. between runs when reusing the recorder). */
reset(): void;
}