UNPKG

agentfootprint

Version:

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

173 lines (172 loc) • 9.28 kB
/** * arguments/subflow — the four stages of the inputs layer's subflow * (`sf-inputs`): Declare → Verify → Record → Resolve. * * Pattern: thin stage bodies over the pure steps in `resolve.ts`. Each reads * what the stage before it staged on the subflow's own (isolated) * scope and stages its own output; the mount's output mapper * (`honesty/mounts.ts`) carries the rows and the resolutions out. * Role: core/ layer, the inputs layer (honesty layer 2). Loaded through * `import()` by the mount's stage wrappers — never on a plain agent's * graph. Everything that is not this folder's arrives as `deps` * (the dispatch resolver, Route's predicate, the ledger's emit half), * so `arguments/` stays a leaf that never imports `findings/` or * `stages/`. * Emits: `agentfootprint.findings.argument` — one per row, from the Record * stage, through the ledger's emit half (`deps.emitRows`). * * ## Only a batch that will dispatch * * Declare asks Route's own predicate (`deps.willDispatch`) first: a reply with * no tool call, a run out of iterations, a halting cost budget — the batch is * going to the final branch, so nothing is planned and every later stage finds * an empty plan. The layer never files a row about a call Route will not run. * * ## Declared sources (`.inputsLayer({ argumentSources: true })`, `.findings({ argumentSources: true })`) * * Present only under the arm: `deps.sources` reads each call's `from` entries * through the ONE reader of `_findings` (`findings/reserved.ts` · * `readDeclaration`, handed in as a closure, so this folder still never * imports `findings/`), and Verify builds the corpora the checks read from the * raw pieces the mount handed in (`sourceInputs`) — loaded on first use, never * on a plain agent's graph. Every stage re-reads the entries rather than * staging a second copy of the model's quotes. */ import type { TypedScope } from 'footprintjs'; import type { CallWindowRow, ClockRow, TimeAnswerRow, TimeReadingRow } from '../../../time/rows.js'; import type { ZoneName } from '../../../time/zone.js'; import type { SourceCorpus } from './checks.js'; import type { KeptAnswer } from './kept.js'; import type { ArgumentRow } from './rows.js'; import { type ArgumentResolution, type BatchCall, type CheckedArgument, type PlannedCall, type ToolOf } from './resolve.js'; import type { CallSources } from './sources.js'; /** * The raw pieces the declared-sources corpora are built from — what the mount * hands the layer under the arm (`honesty/mounts.ts` · `mountInputsLayer`), * read by `honesty/sourceCorpus.ts` · `sourceCorpusOf`. Never the whole * ledger: only the rows the checks read. */ export interface SourceInputs { /** The history the model was served for this batch — the person's, the results', the app's words. */ readonly history: readonly unknown[]; /** The composed system prompt's records — the app's own text. */ readonly systemPromptInjections?: readonly unknown[]; /** The ledger's standing rows and `answered` argument rows — never the whole ledger. */ readonly ledger?: readonly unknown[]; /** The previous batch's results, by id and tool name (`AgentState.toolResults`). */ readonly previousBatch?: readonly { readonly toolCallId: string; readonly toolName?: string; }[]; /** The run's message came from a composition, not a person (`AgentInput.messageFrom`). */ readonly composed?: true; } /** * Under `.time()`: the rows of THIS turn the time layer reads — the turn's * `clock` row, its `time-reading` rows and its `time-answer` rows (the * windows the person settled in the time ask — `time/bind.ts` · * `turnWindowsOf`). Handed by the mount only under the arm, for a batch with * calls, and only once the turn has a clock. */ export interface TimeInputs { readonly clock: ClockRow; readonly readings: readonly TimeReadingRow[]; /** Absent on a checkpoint an earlier version paused: no answer was filed then. */ readonly answers?: readonly TimeAnswerRow[]; } /** The subflow's own state — inputs frozen by the mount, then one key per stage. */ export interface InputsLayerState { readonly calls: readonly BatchCall[]; readonly iteration: number; readonly maxIterations: number; readonly costBudgetHit?: boolean; readonly costBudgetOnExceed?: 'warn' | 'halt'; /** Handed in only when the run's tools are off (`AgentState.toolsOffAfterRevision`). */ readonly toolsOff?: true; readonly turnNumber: number; /** * This turn's answers KEPT for a call the batch that asked could not finish * (`kept.ts`) — handed in only when there are some, so a run that never * keeps one never carries the key. */ readonly argumentAnswersKept?: readonly KeptAnswer[]; /** Under declared sources only: the raw pieces the corpora are built from. */ readonly sourceInputs?: SourceInputs; /** Under `.time()` only: this turn's clock and readings. */ readonly timeInputs?: TimeInputs; argumentPlan?: readonly PlannedCall[]; argumentChecks?: readonly CheckedArgument[]; argumentRows?: readonly ArgumentRow[]; /** Under `.time()` only: one `call-window` row per call to a tool whose period declares forms. */ argumentWindowRows?: readonly CallWindowRow[]; argumentResolutions?: readonly ArgumentResolution[]; } /** What the stages are handed from outside the folder — closures, never scope. */ export interface InputsLayerDeps { /** The implementation that will answer a name — the shared dispatch resolver's. */ readonly toolOf: ToolOf; /** The evidence gate's revision turns the tools off — present only then; * only then is the `toolsOff` input read. */ readonly toolsOffAfterRevision?: true; /** Route's dispatch test (`stages/route.ts` · `willDispatch`). */ readonly willDispatch: (values: { readonly callCount: number; readonly iteration: number; readonly maxIterations: number; readonly costBudgetHit?: boolean; readonly costBudgetOnExceed?: 'warn' | 'halt'; readonly toolsOff?: boolean; }) => boolean; /** The ledger's emit half — one `findings.argument` event per row. */ readonly emitRows: (scope: TypedScope<InputsLayerState>, rows: readonly ArgumentRow[]) => void; /** * Present exactly under `.time()`: the stages read `timeInputs` and a tool * whose period declares forms is filled, bound or recorded against the * turn's windows (`resolve.ts` · `timeDecisionsOf`). */ readonly time?: { /** The app's `.time({ zone })` — the zone of a form declaring `wallZone: 'app'`. */ readonly appZone?: ZoneName; }; /** Present exactly under declared sources (either door: `.inputsLayer({ argumentSources: true })` * or `.findings({ argumentSources: true })`). */ readonly sources?: { /** * A tool the agent registers carries an arguments view — present only * then: no quote is shown on a row or an ask (`resolve.ts` · `quotesMayShow`). */ readonly argumentViews?: true; /** Each call's `from` entries, through the one reader of `_findings`. */ readonly declaredOf: (calls: readonly BatchCall[]) => readonly CallSources[]; /** The corpora the checks read, from the pieces the mount handed in (loaded on first use). */ readonly corpusOf: (inputs: SourceInputs, calls: readonly BatchCall[], turn: number) => Promise<SourceCorpus>; }; } /** * DECLARE — which calls of a DISPATCHING batch carry rules, read off the * implementation that will answer each name, and which ruled values are * missing. Stages `argumentPlan` (identities and a flag — no value); an empty * plan when the batch will not dispatch or no call is ruled. */ export declare function declareArgumentsStage(scope: TypedScope<InputsLayerState>, deps: InputsLayerDeps): void; /** * VERIFY — where each planned ruled value came from (`resolve.ts` · * `verifyPlan`); under declared sources, each present value checked against * the source the model declared (`checks.ts` · `checkSource`) over corpora * built from the mount's `sourceInputs`. Stages `argumentChecks` (identities, * enums and the checks' verdicts — no value, no quote). */ export declare function verifyArgumentsStage(scope: TypedScope<InputsLayerState>, deps: InputsLayerDeps): Promise<void>; /** * RECORD — one row per checked argument, in the tool's own argument view, * stamped with the conversation turn; one event per row, fired HERE (inside the * subflow, before the call dispatches). Stages `argumentRows`; the mount's * output mapper merges them into the ledger in ONE write. */ export declare function recordArgumentsStage(scope: TypedScope<InputsLayerState>, deps: InputsLayerDeps): void; /** * RESOLVE — the fills and refusals ToolCalls applies (`resolve.ts` · * `resolutionsOf`). Stages `argumentResolutions`, each entry stamped with the * batch's iteration. */ export declare function resolveArgumentsStage(scope: TypedScope<InputsLayerState>, deps: InputsLayerDeps): void;