agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
78 lines (77 loc) • 4.52 kB
TypeScript
/**
* extractSequence — derive the in-flight tool-call sequence from
* `scope.history` for `PermissionChecker.check()`.
*
* Pattern: Pure function over conversation history.
* Role: Single source of truth — sequence is reconstructed on
* demand from `LLMMessage[]` instead of maintained as
* parallel state in scope. Survives `agent.resumeOnError`
* correctly because the history IS the durable artifact.
* Emits: N/A (pure compute).
*
* The sequence reads the assistant turns' `toolCalls` blocks in order.
* Calls that were denied at the gate (synthetic tool_results in history
* but no `tool.execute()` invocation) are NOT included — the sequence
* reflects what actually dispatched, not what was attempted. Nor are the
* calls a paused batch never dispatched (9.113.0): the resume settles each
* with a fixed sentence and no gate ever saw it, so counting it would let a
* precondition policy ("verify before transfer") be met by a call that never
* ran.
*
* Detection of "did this call dispatch?" — the `tool` messages are read two
* ways, each with the pairing its question needs:
*
* • IN FLIGHT or DENIED — by id (`resultsById`, the rule this file always
* had): the latest RESULT for the id decides. None yet → the call is
* still in flight; the synthetic deny prefix → it was refused at the
* gate. A settled message is no result, so it answers neither question.
* Kept by id on purpose, limits included: a call denied at the gate still
* counts once a later call that runs reuses its id, and a call that ran
* drops out once a later reuse of its id is denied. The in-flight check
* has the same limit: a call never answered — a halt's later siblings, a
* paused turn continued after `abandonPause()` — counts once a later call
* that runs reuses its id, because the id then has a result. Pairing the
* deny and the in-flight checks by position too would change the
* sequence of runs that never met a settlement, which this release does
* not do; settling those two doors with the marker is the named follow-up
* (`src/core/README.md` · "Not covered yet").
* • SETTLED — by position (`settledProposals`, 9.113.0): a settled message
* carries `LLMMessage.notDispatched` — the marker, never its sentence —
* and it settles the ONE proposal it answers. An id alone cannot say
* which proposal that is: a provider may reuse an id across turns, and
* the library's own fallback ids are minted per provider INSTANCE
* (`adapters/llm/OllamaProvider.ts` · `nextToolCallId`, and its
* Foundry Local and Gemini twins), so a fresh process that resumes a
* stored checkpoint mints the settled call's id again for a call that
* really runs. Paired by id, that real call would bring the settled one
* back into the sequence — a "verify before transfer" policy met by a
* verify that never ran — and a settled reuse would drop a call that
* did run. Paired by position, each proposal is judged by its own answer.
*
* A history without a settled message pairs exactly as before 9.113.0.
*/
import type { LLMMessage, ToolCallEntry } from '../provider-contract/types.js';
/** Prefix the framework writes on synthetic deny tool_results. Used to
* distinguish "denied but in history" from "actually dispatched". */
export declare const SYNTHETIC_DENY_PREFIX = "[permission denied:";
export interface ExtractSequenceOptions {
/**
* Resolver: tool name → providerId. When the tool was registered via
* `staticTools(...)` / `.tool(...)`, returns `'local'` (or the resolver's
* choice). When registered via a `discoveryProvider`, returns the
* provider's `id`. Lets policies match cross-hub patterns.
*/
readonly resolveProviderId?: (toolName: string) => string | undefined;
}
/**
* Walk `history` in order, collect each dispatched tool call into the
* sequence. Only calls that produced a non-denied tool_result are
* included.
*
* @param history Conversation history at check time.
* @param iteration Current ReAct iteration (used to tag the proposed
* call's iteration if you append it).
* @param options Optional resolver for `providerId`.
* @returns The dispatched-call sequence, in chronological order.
*/
export declare function extractSequence(history: readonly LLMMessage[], iteration: number, options?: ExtractSequenceOptions): ToolCallEntry[];