UNPKG

agentfootprint

Version:

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

586 lines • 30.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.startFinalBranch = exports.answerFoldReads = exports.RESULTS_LOOP_TARGET = exports.mountResultsLayer = exports.timeOfBatch = exports.batchToJudge = exports.buildResultsSubflow = exports.mountInputsLayer = exports.buildInputsSubflow = void 0; const admission_js_1 = require("../findings/admission.js"); const reserved_js_1 = require("../findings/reserved.js"); /** * honesty/mounts — where the honesty layers' subflows mount, in ONE place both * chart builders call, so the twin builders cannot drift. * * Pattern: a conditional mount (the WrapUp precedent): a layer that is not * armed adds nothing — no stage, no key, no event — so the chart is * byte-identical to the one the agent always built. * Role: core/agent. The one module that imports BOTH `findings/` (the * ledger's merge and emit halves) and `arguments/` — which is how * `arguments/` stays a leaf that never imports `findings/`. * * ## The inputs layer — `sf-inputs`, after the LLM call and before Route * * A footprintjs decider branch can loop only to a stage declared before the * decider and has no continuation of its own (footprintjs's builder, * `DeciderList._applyBranchLoop`), so "between CallLLM and ToolCalls" can only * mean after the LLM call and before Route. The layer runs once per batch and acts only when Route's own * predicate (`stages/route.ts` · `willDispatch`) says the batch dispatches. * * Handed (the input mapping, frozen inside the subflow): the batch * (`llmLatestToolCalls`, raw), the dispatch predicate's values and * `turnNumber` — and, only when this turn KEPT an answer the batch that asked * could not use (`arguments/kept.ts`), those answers. Never the ledger, never * a tool — tools are closures, read through the shared dispatch resolver * (`stages/toolResolver.ts`) so the rules checked are the rules of the * implementation that will run. * * Returned (the output mapping, `arrayMerge: Replace` — the loop-crossed mount * law): the rows, merged into the ledger in ONE write by the ledger's pure half * (`findings/ledger.ts` · `appendRows`), and `argumentResolutions`, one entry * per call to fill or refuse. Nothing to return → nothing is written. * * The stage bodies (`arguments/subflow.ts`) load through `import()` on first * use — the optional-family law — so a plain agent's graph never carries them. * * ## Declared sources (`.findings({ argumentSources: true })`, `.inputsLayer({ argumentSources: true })`) * * Under the arm the mount also hands the layer the RAW pieces its checks read * (`sourceInputs`): the served history, the composed system prompt's records, * the ledger's standing rows and `answered` argument rows (never the whole * ledger), the previous batch's result ids and the run's `userMessageFrom` * constant — and two closures, both from `honesty/sourceCorpus.ts` (loaded on * the first armed batch): the calls' `from` entries through the ONE reader of * `_findings` (`sourceCorpus.ts` · `declaredSourcesOf`, over * `findings/reserved.ts` · `readDeclaration`), and the corpora * (`sourceCorpus.ts` · `sourceCorpusOf`). * * ## The answer layer — the head of the final branch * * A footprintjs decider branch is one node with no continuation, so the answer * layer cannot sit BETWEEN Route and Final; it heads the final branch instead * (adopted Q3). The final branch is a subflow, and footprintjs starts every * chart with a function stage, so the layer is that first stage: * `assess-answer`, then PrepareFinal (`startFinalBranch`). It reads the * committed keys the fold reads — only those the agent's arms can write * (`answerFoldReads`, decided at build) — and hands PrepareFinal the standing * as data inside the branch. It files no rows of its own: the witness rows it * folds are filed by the Route decider (`assessment/witness.ts`), and the * branch mount's output mapping receives the branch's RESULT (the answer * string every composition that mounts an agent reads), never its scope — so * the mapping stays byte-identical when the layer is armed. */ const advanced_1 = require("footprintjs/advanced"); const footprintjs_1 = require("footprintjs"); const conventions_js_1 = require("../../../lib/conventions.js"); const declare_js_1 = require("../../../tools/declare.js"); const kept_js_1 = require("../arguments/kept.js"); const rows_js_1 = require("../../../time/rows.js"); const period_js_1 = require("../../../semantics/coverage/period.js"); const ledger_js_1 = require("../findings/ledger.js"); const route_js_1 = require("../stages/route.js"); /** * This turn's clock and readings, off the parent's ledger — `undefined` when * the turn has no clock (a turn paused by a runtime without the layer). */ function timeInputsOf(parent) { const ledger = parent.findingsLedger; const clock = (0, rows_js_1.clockOf)(ledger); if (clock === undefined) return undefined; const turn = parent.turnNumber; return { clock, readings: [...(0, rows_js_1.readingsOf)(ledger, turn)], answers: [...(0, rows_js_1.answersOf)(ledger, turn)], }; } let corpusModule; /** The corpus builder, loaded once per process on first use (the optional-family law). */ function loadCorpus() { corpusModule ??= Promise.resolve().then(() => __importStar(require('./sourceCorpus.js'))); return corpusModule; } /** * The raw pieces the declared-sources corpora are built from — read off the * parent's committed state by the input mapping, only under the arm and only * for a batch with calls. Never the whole ledger: its standing rows (the * model's current reading of each result) and its `answered` argument rows * (the person's earlier answers). */ function sourceInputsOf(parent) { const ledger = (parent.findingsLedger ?? []).filter((row) => row.kind === 'standing' || (row.kind === 'argument' && row.source === 'answered')); const injections = parent.systemPromptInjections ?? []; const previous = (parent.toolResults ?? []) .filter((r) => typeof r?.toolCallId === 'string') .map((r) => ({ toolCallId: r.toolCallId, ...(typeof r.toolName === 'string' && { toolName: r.toolName }), })); return { history: [...(parent.history ?? [])], ...(injections.length > 0 && { systemPromptInjections: [...injections] }), ...(ledger.length > 0 && { ledger }), ...(previous.length > 0 && { previousBatch: previous }), ...(parent.userMessageFrom === 'composed' && { composed: true }), }; } let stageModule; /** The four stage bodies, loaded once per process on first use. */ function loadStages() { stageModule ??= Promise.resolve().then(() => __importStar(require('../arguments/subflow.js'))); return stageModule; } /** The ledger's emit half, one event per row, from inside the subflow. */ function emitRows(scope, rows) { for (const row of rows) (0, ledger_js_1.emitRow)(scope, row); } /** * The `sf-inputs` subflow: Declare → Verify → Record → Resolve, four thin * stages over the pure steps of `arguments/resolve.ts`. */ function buildInputsSubflow(deps) { const sources = deps.sources; const base = { toolOf: deps.toolOf, willDispatch: route_js_1.willDispatch, emitRows, ...(deps.time !== undefined && { time: deps.time }), ...(deps.toolsOffAfterRevision === true && { toolsOffAfterRevision: true }), }; // Declared sources: the reader of each call's `from` and the corpora the checks // read live in `sourceCorpus.ts`, loaded on the first armed batch — so neither // is on a plain agent's graph. let armed; const layerOf = () => { if (sources === undefined) return base; armed ??= loadCorpus().then((corpus) => ({ ...base, sources: { ...(sources.argumentViews === true && { argumentViews: true }), declaredOf: (calls) => corpus.declaredSourcesOf(calls, deps.toolOf, sources.basisRows === true ? { basisRows: true } : undefined), corpusOf: async (inputs, calls, turn) => corpus.sourceCorpusOf(inputs, calls, turn, { toolOf: deps.toolOf, ...(sources.externalGrounds !== undefined && { externalGrounds: sources.externalGrounds, }), }), }, })); return armed; }; const declare = async (scope) => (await loadStages()).declareArgumentsStage(scope, await layerOf()); const verify = async (scope) => (await loadStages()).verifyArgumentsStage(scope, await layerOf()); const record = async (scope) => (await loadStages()).recordArgumentsStage(scope, await layerOf()); const resolve = async (scope) => (await loadStages()).resolveArgumentsStage(scope, await layerOf()); return (0, footprintjs_1.flowChart)('DeclareArguments', declare, conventions_js_1.STAGE_IDS.DECLARE_ARGUMENTS, { description: 'Which calls of a dispatching batch carry argument rules, and which values are missing', }) .addFunction('VerifyArguments', verify, conventions_js_1.STAGE_IDS.VERIFY_ARGUMENTS, sources === undefined ? 'Where each ruled value came from: the declared default, or the model' : 'Where each value came from: the source the model declared, checked — or the declared default') .addFunction('RecordArguments', record, conventions_js_1.STAGE_IDS.RECORD_ARGUMENTS, 'One argument row per ruled argument per call, in the tool’s own argument view') .addFunction('ResolveArguments', resolve, conventions_js_1.STAGE_IDS.RESOLVE_ARGUMENTS, 'The values the library fills, and the calls it refuses, for ToolCalls to apply') .build(); } exports.buildInputsSubflow = buildInputsSubflow; /** * Mount the inputs layer after the LLM call and before Route — or return the * builder untouched when the layer is not armed (`deps === undefined`), so an * agent without it builds the chart it always built. Both chart builders call * this, at the same place. */ function mountInputsLayer(builder, deps) { if (deps === undefined) return builder; return builder .addSubFlowChartNext(conventions_js_1.SUBFLOW_IDS.INPUTS, buildInputsSubflow(deps), 'Inputs', { inputMapper: (parent) => { // Read under Route's own short-circuit: the budget policy only once a // budget was hit (the key exists only on an agent that configured one). const costBudgetHit = parent.costBudgetHit; const costBudgetOnExceed = costBudgetHit === true ? parent.costBudgetOnExceed : undefined; // The answers this turn kept for a call it could not finish — the key // exists only after such a refusal, so every other run hands nothing. const kept = (0, kept_js_1.keptThisTurn)(parent.argumentAnswersKept, parent.turnNumber); const proposed = parent.llmLatestToolCalls ?? []; const calls = deps.findings === true ? proposed.filter((call) => { const schema = deps.toolOf(call.name)?.schema; if (schema !== undefined && (0, reserved_js_1.ownsReservedArgument)({ name: schema.name, description: '', inputSchema: schema.inputSchema ?? {}, })) return true; const declaration = (0, reserved_js_1.splitFindings)(call.args); return ((0, admission_js_1.findingsReferenceRefusal)(declaration.findings, parent.findingsRequest?.[0], parent.iteration, declaration.malformed) === undefined); }) : proposed; // The time layer: this turn's clock and readings — only under the arm, for a batch with calls. const timeInputs = deps.time !== undefined && calls.length > 0 ? timeInputsOf(parent) : undefined; return { calls, iteration: parent.iteration, maxIterations: parent.maxIterations, ...(costBudgetHit !== undefined && { costBudgetHit }), ...(costBudgetOnExceed !== undefined && { costBudgetOnExceed }), // Route's sixth dispatch value: the key exists only on a run whose // tools went off after the evidence revision, so every other run // hands nothing new. ...(deps.toolsOffAfterRevision === true && parent.toolsOffAfterRevision === true && { toolsOff: true }), turnNumber: parent.turnNumber, ...(kept.length > 0 && { argumentAnswersKept: kept }), // Declared sources: the pieces the checks read — only under the arm, // and only for a batch with calls to check. ...(deps.sources !== undefined && calls.length > 0 && { sourceInputs: sourceInputsOf(parent) }), ...(timeInputs !== undefined && { timeInputs }), }; }, outputMapper: (sf, parent) => { const rows = [ ...(sf.argumentRows ?? []), // The time layer's `call-window` rows ride the same ONE write; they fire no event. ...(sf.argumentWindowRows ?? []), ]; const resolutions = sf.argumentResolutions ?? []; return { // ONE committed copy per layer run — the rows this batch filed, // merged by the ledger's pure half; the events already fired inside. ...(rows.length > 0 && { findingsLedger: (0, ledger_js_1.appendRows)([...(parent.findingsLedger ?? [])], rows).ledger, }), ...(resolutions.length > 0 && { argumentResolutions: [...resolutions] }), }; }, arrayMerge: advanced_1.ArrayMergeMode.Replace, }) .tag(...(0, conventions_js_1.milestoneTagsFor)(conventions_js_1.SUBFLOW_IDS.INPUTS)); } exports.mountInputsLayer = mountInputsLayer; let resultsModule; /** The results layer's stage bodies, loaded once per process on first use. */ function loadResultsStages() { resultsModule ??= Promise.resolve().then(() => __importStar(require('../results/subflow.js'))); return resultsModule; } /** The ledger's emit half, one `findings.period` event per row, from inside the subflow. */ function emitPeriodRows(scope, rows) { for (const row of rows) (0, ledger_js_1.emitRow)(scope, row); } /** * The argument a tool's `ToolPeriod` names — read off the implementation that * answered the name, by the inputs layer's own reader (`tools/declare.ts` · * `rulesOf`): a `ToolPeriod` is that layer's declaration, and this layer only * reads it. A tool whose rules cannot be read declares nothing here (its calls * were refused, never run). */ function periodArgumentOf(toolOf) { return (toolName) => { const rules = (0, declare_js_1.rulesOf)(toolOf(toolName)); return rules === undefined || (0, declare_js_1.isRefused)(rules) ? undefined : (0, declare_js_1.periodArgumentOf)(rules.period); }; } /** * The facts a tool's `ToolPeriod` declares about its source (`retention`, * `granularity`, …) — read by the same reader, for the time layer's result * checks (step T8). A tool whose rules cannot be read declares none. */ function periodFactsOf(toolOf) { return (toolName) => { const rules = (0, declare_js_1.rulesOf)(toolOf(toolName)); return rules === undefined || (0, declare_js_1.isRefused)(rules) ? undefined : (0, declare_js_1.periodFactsOf)(rules.period); }; } /** * The forms a tool's `ToolPeriod` declares — read by the same reader, so the * result checks know whether a call SENT a look-back (§ 7.4, step T8). */ function periodFormsOf(toolOf) { return (toolName) => { const rules = (0, declare_js_1.rulesOf)(toolOf(toolName)); return rules === undefined || (0, declare_js_1.isRefused)(rules) ? undefined : (0, declare_js_1.periodFormsOf)(rules.period); }; } /** * The `sf-results` subflow: Declare → Verify → Record → Resolve, four thin * stages over the pure steps of `results/subflow.ts`. */ function buildResultsSubflow(deps) { const layer = { periodArgumentOf: periodArgumentOf(deps.toolOf), periodFactsOf: periodFactsOf(deps.toolOf), periodFormsOf: periodFormsOf(deps.toolOf), emitRows: emitPeriodRows, }; const declare = async (scope) => (await loadResultsStages()).declareResultsStage(scope, layer); const verify = async (scope) => (await loadResultsStages()).verifyResultsStage(scope); const record = async (scope) => (await loadResultsStages()).recordResultsStage(scope, layer); const resolve = async () => (await loadResultsStages()).resolveResultsStage(); return (0, footprintjs_1.flowChart)('DeclareResults', declare, conventions_js_1.STAGE_IDS.DECLARE_RESULTS, { description: 'Which calls of the batch just run declared a period, or come from a tool that declares one', }) .addFunction('VerifyResults', verify, conventions_js_1.STAGE_IDS.VERIFY_RESULTS, 'The verdict on each period: covered, partly held, not held, unknown — or undeclared') .addFunction('RecordResults', record, conventions_js_1.STAGE_IDS.RECORD_RESULTS, 'One period row per judged call, on the one ledger') .addFunction('ResolveResults', resolve, conventions_js_1.STAGE_IDS.RESOLVE_RESULTS, 'Flag — a result that already ran is never asked about, filled or refused') .build(); } exports.buildResultsSubflow = buildResultsSubflow; const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value); /** * WHICH BATCH the loop head hands the results layer on this visit — the * iteration that dispatched it, or `undefined` when there is none to judge. * * `stamp` is the iteration ToolCalls wrote beside the batch it dispatched * (`AgentState.toolResultsIteration`, under the arm); `iteration` is the loop * head's. ToolCalls advances the iteration by exactly one and loops straight * here; every other way back — the schema re-ask, the step nudge, the evidence * recheck, the wrap-up — runs no tool, leaves the batch in place and advances * the iteration again. So the batch is new on this visit exactly when * `iteration === stamp + 1`: each batch judged ONCE per run. * * Never by call id. A provider's synthetic counter restarts with each provider * instance (`<prefix>-call-${++toolCallSeq}`), so a resumed leg repeats the ids * of the leg that failed — whose rows the conversation checkpoint carries — and * nothing stops a provider reusing an id across batches of one run. A stamp is * per run: no conversation checkpoint carries it, so a resumed leg starts with * none. Were the invariant above ever broken, a batch would be judged twice — * an over-report, never a hidden verdict. */ function batchToJudge(iteration, stamp) { return typeof stamp === 'number' && iteration === stamp + 1 ? stamp : undefined; } exports.batchToJudge = batchToJudge; /** * What the loop head hands the results layer — the batch's identities, the * periods ITS results declared (the coverage rows of the batch's iteration, so * a call id an earlier batch also used cannot lend it a period), and the * stamps. Only on the first visit after ToolCalls ran the batch * (`batchToJudge`); every other visit is handed an empty batch and reads * nothing else, so the layer never reads a key the run has not written. */ function resultsLayerInput(parent) { const batchIteration = batchToJudge(parent.iteration, parent.toolResultsIteration); if (batchIteration === undefined) return { calls: [] }; const batch = parent.toolResults ?? []; const calls = []; for (const entry of batch) { if (!isRecord(entry)) continue; if (typeof entry.toolCallId !== 'string' || typeof entry.toolName !== 'string') continue; calls.push({ toolCallId: entry.toolCallId, toolName: entry.toolName }); } if (calls.length === 0) return { calls }; const ids = new Set(calls.map((c) => c.toolCallId)); const periods = []; for (const row of parent.coverageDeclared ?? []) { if (!isRecord(row) || row.iteration !== batchIteration || row.period === undefined) continue; if (typeof row.toolCallId !== 'string' || !ids.has(row.toolCallId)) continue; periods.push({ toolCallId: row.toolCallId, period: (0, period_js_1.copyPeriod)(row.period) }); } const time = timeOfBatch(parent, calls); return { calls, batchIteration, turnNumber: parent.turnNumber, ...(periods.length > 0 && { periods }), ...time, }; } /** * Under `.time()` (step T8): each call's time rows — its `call-window` row * and its `call` row's `drift` — and the turn's clock `now`, for the result * checks (`time/check.ts` · `periodTimeCheck`). Nothing when the turn has * no clock (an agent without `.time()`), so an unarmed run hands the layer the * input it always did. "This turn" is checked, not assumed: a ledger carried * forward (a checkpoint's `findingsLedger` from an agent that had `.time()`, * resumed and then run on by one that has not) holds an EARLIER turn's clock, * and that clock's `now` must not judge this turn's calls. Exported for that * test only. */ function timeOfBatch(parent, calls) { const ledger = parent.findingsLedger; const clock = (0, rows_js_1.clockOf)(ledger); const turn = parent.turnNumber; if (clock === undefined || clock.turn !== turn) return {}; const times = []; for (const call of calls) { const window = (0, rows_js_1.callWindowOfCall)(ledger, call.toolCallId, turn); const drift = (0, rows_js_1.callRowOfCall)(ledger, call.toolCallId, turn)?.drift; if (window === undefined && drift === undefined) { times.push({ toolCallId: call.toolCallId }); continue; } times.push({ toolCallId: call.toolCallId, ...(window !== undefined && { window: windowForCheck(window) }), ...(drift !== undefined && { drift }), }); } return { times, now: clock.now }; } exports.timeOfBatch = timeOfBatch; /** The fields of a `call-window` row the result checks read — plain data. */ function windowForCheck(row) { return { how: row.how, ...(row.form !== undefined && { form: row.form }), ...(row.asked !== undefined && { asked: { from: row.asked.from, to: row.asked.to } }), ...(row.person !== undefined && { person: row.person }), ...(row.sent !== undefined && { sent: { from: row.sent.from, to: row.sent.to } }), ...(row.trimmedByTool === true && { trimmedByTool: true }), ...(row.partlyBeyondRetention === true && { partlyBeyondRetention: true }), ...(row.refused !== undefined && { refused: row.refused }), }; } /** * Mount the results layer at the LOOP HEAD — or return the builder untouched * when the layer is not armed (`deps === undefined`), so an agent without it * builds the chart it always built. Both chart builders call this at the same * place — immediately before the window strategy's `Compact` stage (or the * loop target that stands there) — and make the mount the loop target * (`RESULTS_LOOP_TARGET`), the `Compact` precedent: the layer reads the batch * just run before any window strategy folds it away. * * Returned (the output mapping, `arrayMerge: Replace` — the loop-crossed mount * law): the rows, merged into the ledger in ONE write by the ledger's pure * half. Nothing to return → nothing is written. */ function mountResultsLayer(builder, deps) { if (deps === undefined) return builder; return builder .addSubFlowChartNext(conventions_js_1.SUBFLOW_IDS.RESULTS, buildResultsSubflow(deps), 'Results', { inputMapper: resultsLayerInput, outputMapper: (sf, parent) => { const rows = sf.periodRows ?? []; return rows.length === 0 ? {} : { // ONE committed copy per layer run — the rows this batch filed, // merged by the ledger's pure half; the events already fired inside. findingsLedger: (0, ledger_js_1.appendRows)([...(parent.findingsLedger ?? [])], rows).ledger, }; }, arrayMerge: advanced_1.ArrayMergeMode.Replace, }) .tag(...(0, conventions_js_1.milestoneTagsFor)(conventions_js_1.SUBFLOW_IDS.RESULTS)); } exports.mountResultsLayer = mountResultsLayer; /** The loop target the results layer's mount becomes when armed — `tool-calls` and every re-ask loop back to it. */ exports.RESULTS_LOOP_TARGET = conventions_js_1.SUBFLOW_IDS.RESULTS; /** * THE READ LIST — which committed keys the answer layer's fold reads, from * the agent's arms: a key no arm of this agent can write is never read (a * tracked read of a key a run never writes is a phantom context source — * honesty law 9). Over-approximates on purpose: a key an arm CAN write is * read whether or not this run wrote it, so the in-run fold never misses a row * the read-after fold sees (the equality law). * * - always: `history`, `turnNumber`, `pausedToolCallId` (seed writes them), * `findingsLedger` (the witness rows, and any rows a continued conversation * restores — the restore is wired while a layer is armed) and * `stoppedEarly` — the Route decider writes it on EVERY agent * (`stages/route.ts` · `recordEarlyStop`): a limit cuts a turn short * whenever the model asked for calls, whether or not this agent registered * a tool that could answer them; * - a tool surface: `coverageDeclared` (only a tool that ran declares * coverage); * - the evidence gate: `unsupportedValues`; * - `.answerValidation()`: `answerValidation`; * - the inputs layer: `argumentAsk`, and `middlewareDecisions` when a * before-tool chain can rewrite a filled value. * * Every key's writers were checked against this list: a writer that no arm * gates puts its key in the always group — decide by the writer, never by the * usual path to it. * * @example * ```ts * answerFoldReads({ tools: true, evidenceGate: false, answerValidation: false, inputs: false, toolMiddleware: false }); * // ['history', 'turnNumber', 'pausedToolCallId', 'findingsLedger', 'stoppedEarly', 'coverageDeclared'] * ``` */ function answerFoldReads(arms) { return [ 'history', 'turnNumber', 'pausedToolCallId', 'findingsLedger', 'stoppedEarly', ...(arms.tools ? ['coverageDeclared'] : []), ...(arms.evidenceGate ? ['unsupportedValues'] : []), ...(arms.answerValidation ? ['answerValidation'] : []), ...(arms.inputs ? ['argumentAsk'] : []), ...(arms.inputs && arms.toolMiddleware ? ['middlewareDecisions'] : []), ]; } exports.answerFoldReads = answerFoldReads; let answerStageModule; /** The answer layer's stage body, loaded once per process on the first armed answer. */ function loadAnswerStage() { answerStageModule ??= Promise.resolve().then(() => __importStar(require('../assessment/stage.js'))); return answerStageModule; } /** PrepareFinal's description — one string, whichever stage heads the branch. */ const PREPARE_FINAL_DESCRIPTION = 'Capture turn payload (finalContent + newMessages)'; /** * Start the final branch: the answer layer's stage first when it is armed, * then PrepareFinal — or PrepareFinal alone when it is not, built exactly as * both chart builders always built it (same name, stage id, options and tags), * so an agent without the layer builds a byte-identical branch. Both builders * call this, so the twins cannot drift. */ function startFinalBranch(answer, prepareFinal, structureRecorders) { const recorders = structureRecorders !== undefined && { structureRecorders: [...structureRecorders], }; if (answer === undefined) { return (0, footprintjs_1.flowChart)('PrepareFinal', prepareFinal, conventions_js_1.STAGE_IDS.PREPARE_FINAL, { ...recorders, description: PREPARE_FINAL_DESCRIPTION, tags: (0, conventions_js_1.milestoneTagsFor)(conventions_js_1.STAGE_IDS.PREPARE_FINAL), }); } const assess = async (scope) => (await loadAnswerStage()).assessAnswerStage(scope, answer); return (0, footprintjs_1.flowChart)('AssessAnswer', assess, conventions_js_1.STAGE_IDS.ASSESS_ANSWER, { ...recorders, description: "The answer's standing, folded from the run's committed record", }) .addFunction('PrepareFinal', prepareFinal, conventions_js_1.STAGE_IDS.PREPARE_FINAL, PREPARE_FINAL_DESCRIPTION) .tag(...(0, conventions_js_1.milestoneTagsFor)(conventions_js_1.STAGE_IDS.PREPARE_FINAL)); } exports.startFinalBranch = startFinalBranch; //# sourceMappingURL=mounts.js.map