agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
102 lines (101 loc) • 5.28 kB
TypeScript
/**
* runbookAsTool — turn a written operational procedure into a tool whose
* every answer is EVIDENCE: who decided what, against which threshold, on
* which rule version, with the recorded walk to check it against.
*
* The standard bridge from a footprintjs chart to the Agent's tool surface.
* Where `flowchartAsTool` returns bare `JSON.stringify(values)`,
* runbookAsTool supplies the missing middle — the honesty envelope every
* hand-rolled triage tool was building for itself:
*
* THE MANDATORY SPINE (every runbook, whatever its shape):
* - `af_coverage` — the three-list ledger, with every INNER tool's own
* ledger folded upward through the run's dispatch, plus the sentence
* naming the rule set and version;
* - `result.af_provenance` — re-emitted FIRST (a carried `LOCAL SEED`
* confession survives composition);
* - `result.rule_version` — the declared rules' version, or the honest
* `'undeclared'`;
* - `result.walk` — the recorded walk's descriptor. The walk itself
* ships as an artifact ticket (kind `recording/chart-walk`), never as
* bytes; when it does not fit, the CONTROL FLOW survives and the
* projection is declared. Opt into `walk: { recording: true }` and the
* inner chart's own `{ snapshot, events, structure }` is filed beside
* it (kind `recording/run`) and its ref rides the SAME descriptor as
* `walk.recording_ref` — the row projection cannot be drawn, and this
* is what makes the walk mountable as the flowchart it ran.
*
* THE OPTIONAL PROJECTION (selected by `resultKind: 'verdict/*'`):
* verdict rows off the chart's `verdicts` state key, capped with
* truthful counters, a pre-rendered table over the SAME rows, and
* `verdict_meanings` GENERATED from the decider's declared branches +
* the rule labels this run's decide() evidence carried.
*
* WHO RENDERS THE ROWSET (`presentation`, default `'prose'`): the bridge
* cannot see which client it is in, so the caller says. Under `'prose'`
* the model's words are the rowset's only surface — the table ships
* pre-rendered and is output verbatim. Under `'panel'` the host draws
* the rowset itself — no table ships, and the note says not to
* reproduce rows the reader is already looking at.
*
* THREE OUTCOMES, honestly: a clean envelope; an inner ABSENCE passed
* through verbatim (the framework still reads it as an absence); and
* DECLINED rows counted into the ledger as not-checked ground.
*
* The procedure is a FACTORY invoked per call with the run's own tool
* dispatch (`ctx.tools`) — fresh chart every run, stages close over the
* dispatch, and every inner tool's honesty ledger folds into this tool's
* answer.
*
* Reserved state keys the bridge reads off the final scope:
* - `verdicts` — the rowset (verdict projection only);
* - `coverage` — chart-declared coverage entries (`{checked?, not_checked?,
* cannot_cover?}`);
* - `report` — the app's own result fields, spread into `result` verbatim
* BESIDE the spine, never over it: the envelope's own names (the spine
* plus the projection this run assembled) are reserved, and a report
* field spelling one of them is discarded and NAMED in `report_note`.
*
* Pause: NOT yet bridged. A paused inner chart throws with the checkpoint
* attached, exactly like `flowchartAsTool` — the approval-gate integration
* is the next phase of the runbook program.
*
* @example the smallest legal call — still yields the honest spine
* const tool = runbookAsTool({
* name: 'restart_check',
* description: 'Run the restart-safety procedure.',
* procedure: () =>
* flowChart<{ safe: boolean }>('restart-safety', (scope) => {
* scope.safe = true;
* }, 'check').build(),
* });
*
* @example a triage runbook with rules, verdicts, and an inner tool
* const triage = runbookAsTool({
* name: 'backup_triage',
* description: 'Assess backup protection posture for every subject.',
* resultKind: 'verdict/backup-posture',
* rules: { name: 'health-signal', version: 'v1' },
* verdicts: { decider: 'posture' },
* composedOf: ['backup_inventory'],
* procedure: (tools) =>
* flowChart<TriageState>('backup-triage', async (scope) => {
* const inner = (await tools.call('backup_inventory', {})) as InventoryResult;
* scope.subjects = inner.result.rows;
* }, 'inventory')
* .addDeciderFunction('Posture', postureDecider, 'posture')
* .addFunctionBranch('protected', 'Protected', landProtected)
* .addFunctionBranch('unprotected', 'Unprotected', landUnprotected)
* .end()
* .build(),
* });
*/
import { type Tool } from '../../tools/tools.js';
import type { RunbookAsToolOptions } from './types.js';
/**
* Wrap a footprintjs procedure as a `Tool` whose every answer carries the
* honesty spine. See the module header for the envelope; see
* {@link RunbookAsToolOptions} for the full options bag. The smallest legal
* call is `{ name, description, procedure }`.
*/
export declare function runbookAsTool(opts: RunbookAsToolOptions): Tool;