UNPKG

agentfootprint

Version:

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

3,915 lines • 222 kB
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AgentBuilder = void 0;
const instruction_js_1 = require("./findings/instruction.js");
const context_js_1 = require("./findings/context.js");
/**
 * AgentBuilder — fluent builder for Agent. Extracted from Agent.ts in
 * v2.11.2 as part of the core/agent decomposition. Same surface, same
 * behavior; just lives in its own file for readability.
 *
 * Re-exported by Agent.ts so external consumers importing
 * `AgentBuilder` from `'../core/Agent.js'` continue to work.
 */
const footprintjs_1 = require("footprintjs");
const index_js_1 = require("../../answer-validation/index.js");
const outputSchema_js_1 = require("../../answer-validation/outputSchema.js");
const outputEnforcement_js_1 = require("./outputEnforcement.js");
const nativeOutputFormat_js_1 = require("./nativeOutputFormat.js");
const config_js_1 = require("./negativeClaims/config.js");
const gate_js_1 = require("./evidence/gate.js");
const toolsFromActiveSkill_js_1 = require("./toolsFromActiveSkill.js");
const outputFallback_js_1 = require("../outputFallback.js");
const skillGraph_js_1 = require("../../injection-engine/skillGraph.js");
const skillBrains_js_1 = require("./skillBrains.js");
const thinkingSupport_js_1 = require("./thinkingSupport.js");
const defineMenuHint_js_1 = require("../../injection-engine/factories/defineMenuHint.js");
const defineStepsHint_js_1 = require("../../injection-engine/factories/defineStepsHint.js");
const skillSteps_js_1 = require("../../injection-engine/skillSteps.js");
const skillContract_js_1 = require("../../injection-engine/skillContract.js");
const skillGraphCheckup_js_1 = require("../../injection-engine/skillGraphCheckup.js");
const skillVocabulary_js_1 = require("../../injection-engine/skillVocabulary.js");
const skillBodyDelivery_js_1 = require("../../injection-engine/skillBodyDelivery.js");
const defineInstruction_js_1 = require("../../injection-engine/factories/defineInstruction.js");
const messagesSlotRefusal_js_1 = require("../../injection-engine/messagesSlotRefusal.js");
const defineRelevanceHint_js_1 = require("../../injection-engine/factories/defineRelevanceHint.js");
const skillScopedTools_js_1 = require("../../tools/skillScopedTools.js");
const commentaryTemplates_js_1 = require("../../recorders/observability/commentary/commentaryTemplates.js");
const statusTemplates_js_1 = require("../../recorders/observability/status/statusTemplates.js");
const selfExplainPort_js_1 = require("./selfExplainPort.js");
const servingConversation_js_1 = require("./servingConversation.js");
const innerRunRecords_js_1 = require("../innerRunRecords.js");
const traceToolNames_js_1 = require("../../lib/traceToolNames.js");
const Agent_js_1 = require("../Agent.js");
const skillGraphDeclared_js_1 = require("./skillGraphDeclared.js");
const reserved_js_1 = require("./findings/reserved.js");
const armed_js_1 = require("./honesty/armed.js");
const clock_js_1 = require("../../time/clock.js");
const instruction_js_2 = require("../../ontology/instruction.js");
const act_js_1 = require("./act.js");
const options_js_1 = require("./window/options.js");
const summarizeOldest_js_1 = require("./window/strategies/summarizeOldest.js");
const defineAgentRecipe_js_1 = require("../../recipes/defineAgentRecipe.js");
const attachment_js_1 = require("./scripts/attachment.js");
const script_js_1 = require("../../tools/script.js");
const apply_js_1 = require("../../recipes/apply.js");
const provenance_js_1 = require("../../recipes/provenance.js");
/**
 * Fluent builder. `tool()` accepts any Tool<TArgs, TResult> and registers
 * it by its schema.name. Duplicate names throw at build time.
 */
class AgentBuilder {
    opts;
    systemPromptValue = '';
    /** Whether `.system()` has been called. Separate from the VALUE, because
     *  `.system('')` is a legitimate call (an agent with no instructions) and
     *  `''` is also the default — only a flag can tell the two apart, and the
     *  refusal has to fire on the second call regardless of what was passed. */
    systemPromptSet = false;
    /**
     * Cache policy for the base system prompt. Set via the optional
     * 2nd argument to `.system(text, { cache })`. Default `'always'` —
     * the base prompt is stable per-turn and an ideal cache anchor.
     */
    systemPromptCachePolicy = 'always';
    systemPromptCacheExplicit = false;
    /**
     * Global cache kill switch. Set via `Agent.create({ caching: 'off' })`
     * (handled in `AgentOptions` propagation). Defaults to `false`
     * (caching enabled). When `true`, the CacheGate decider routes to
     * `'no-markers'` every iteration regardless of other rules.
     */
    cachingDisabledValue = false;
    /**
     * Optional explicit CacheStrategy override. Default: undefined, which
     * means the agent chooses from the provider's declared `promptCaching`
     * (`cacheStrategyFor`) at construction. Power users override here for
     * custom backends or test mocks.
     */
    cacheStrategyOverride;
    registry = [];
    injectionList = [];
    /** Captured from `.skillGraph(graph)` — the cursor resolver the Injection
     *  Engine uses to `from`-gate route triggers. Undefined unless a graph with
     *  route edges was mounted. */
    skillGraphNextSkill;
    /** Captured from `.skillGraph(graph)` — the reachable-set resolver the
     *  read_skill gate uses to reject out-of-set skill jumps. Undefined → the gate
     *  is off (plain read_skill agents are unaffected). */
    skillGraphReachable;
    /** Captured from `.skillGraph(graph)` — the relevance entry scorer
     *  (`graph.scoreEntries`), present only with `.entryByRelevance()`. When set, the
     *  PickEntry stage picks the starting skill by relevance once per turn. */
    skillGraphScoreEntries;
    /** Captured from `.skillGraph(graph)` — the `to` end of every declared edge, i.e.
     *  which skills the graph WIRES. Read only by the read_skill gate's open-skill
     *  rule (8.4.0). */
    skillGraphEdgeTargets;
    /** Captured from `.skillGraph(graph)` — the cursor resolver that also reports the
     *  clause that won (`graph.explainNextSkill`, 8.5.0). Optional: a graph built
     *  before it existed still routes, it just cannot narrate the hop. */
    skillGraphExplainNextSkill;
    /** Captured from `.skillGraph(graph)` — the suppression reporter
     *  (`graph.supersededEntries`, 8.15.0). Optional: a graph built before it existed
     *  routes identically, it just cannot name the entries the cursor kept off the
     *  wire. */
    skillGraphSupersededEntries;
    /** Is the mounted graph a decision `tree()`? DERIVED from `graph.nodes` — a tree
     *  is the only shape that draws `predicate` diamonds — so no new field had to be
     *  added to the public `SkillGraph`. Feeds the gate's tree-specific refusal. */
    skillGraphIsTree = false;
    /** Captured from `.skillGraph(graph, options)` (SG-C) — the graph's
     *  turn-routing plan plus the mount's posture/continuity and the node-id
     *  set droppedResume checks against. Undefined for every graph without the
     *  new options → the Agent wires nothing new. */
    skillGraphCascade;
    /** Captured from `.skillGraph(graph)` — the graph's note that it DEFERRED its
     *  body-contract checks (built without `knownTools`, it could not tell a typo
     *  from a baseline tool this agent registers), plus the compiled skills to run
     *  them over. `build()` runs them exactly once, against the full tool registry.
     *  This capture is the FALLBACK for a structurally-typed graph whose skills do
     *  not carry the per-skill note: the primary collection reads
     *  `SKILL_GRAPH_DEFERRED_CONTRACT_KEY` off each injection's metadata, which is
     *  how a graph fed through `.skills({ list: () => graph.skills })` — a door
     *  that never sees the graph object — still gets its deferred checks run.
     *  Skills found by both are deduped by id: nothing re-runs, nothing
     *  double-reports. Undefined when the graph already ran them (`knownTools`
     *  given), switched them off (`check: 'off'`), or predates the note. */
    skillGraphDeferredBodyContract;
    /** Captured from `.skillGraph(graph, options)` (9.19.0) — the three brain
     *  fields, verbatim; folded + validated at `build()` where the FINAL
     *  injection list (the other declaration home) exists. */
    skillGraphBrainOptions;
    /** Captured from `.skillGraph(graph)` (9.19.0) — the graph's node-id set,
     *  captured UNCONDITIONALLY (unlike the cascade's copy, which only exists
     *  when cascade options were asked for): the brains check-up needs it on
     *  a bare mount too. Undefined when the graph object carries no `nodes`. */
    skillGraphNodeIds;
    /** Captured from `.skillGraph(graph)` (9.50.0) — the DECLARED map (nodes +
     *  edges, verbatim), projected once at mount so every run can file it as
     *  `agentfootprint.skill.graph_declared`. Undefined when the graph cannot
     *  state one (a structurally-typed graph without `nodes`) — the event then
     *  never fires, and the recording honestly carries no declared map. */
    skillGraphDeclared;
    /** `.maps()` (9.58.0) — the mount kernel's options; the plan itself is
     *  resolved at build(), where the final injection list and the mounted
     *  graph both exist. Absent = no kernel, zero delta. */
    mapsOptions;
    /** `.claims()` (9.61.0) — the declared claim contract; resolved at build(). */
    claimsContract;
    memoryList = [];
    /**
     * Optional terminal contract — see `outputSchema()`. Stored on the
     * builder, propagated to the Agent at `.build()` time.
     */
    outputSchemaParser;
    answerValidationConfig;
    negativeClaimsConfig;
    /** Corrective re-asks the loop may spend. `0` (the default) means the
     *  schema is judged once, at the caller's boundary, exactly as it always
     *  was — and no enforcement is mounted in the chart at all. */
    /** The evidence gate (9.35.0) — set by `.namesAndNumbersFromEvidence()`,
     *  resolved at the call site so a bad posture throws where it was typed.
     *  Undefined for every agent that did not ask for it, which is what makes
     *  the feature byte-identical when unused. */
    evidenceGate;
    /** The tool posture (9.36.0) — set by `.toolsFromActiveSkill()`. False for
     *  every agent that did not ask for it, and the stamp it gates is the ONLY
     *  thing it does, which is what makes the feature byte-identical when
     *  unused. */
    toolsFromActiveSkillValue = false;
    /** `.limitsTravelWithTheAnswer()` (this release). False for every agent
     *  that did not ask for it, and the ONE thing it gates is which stage
     *  function the final branch mounts — which is what makes the feature
     *  byte-identical when unused. The recording half is unconditional. */
    limitsTravelValue = false;
    /** `.scriptTravelsWithTheAnswer()` — resolved at the call site; undefined
     *  for every agent that did not ask, which keeps it byte-identical. */
    scriptTravelValue;
    /** `.inputsLayer()` (honesty layer 2). False for every agent that did not
     *  ask for it; a REGISTERED ruled tool arms the layer on its own, so this
     *  is for ruled tools a ToolProvider serves — and for declared sources
     *  without the findings ledger (`inputsLayerSourcesValue`). */
    inputsLayerValue = false;
    /** `.inputsLayer({ argumentSources: true })` (honesty layer 2): declared
     *  sources without the findings ledger. False unless asked for. */
    inputsLayerSourcesValue = false;
    /** `.resultsLayer()` (honesty layer 3, step 7b). False for every agent that
     *  did not ask for it; a REGISTERED tool with a `ToolPeriod` arms the layer
     *  on its own, so this is for provider-served tools and for tools that
     *  declare a period only on their results. */
    resultsLayerValue = false;
    /** `.answerLayer()` (honesty layer 4). Undefined for every agent that did
     *  not ask for it — the chart, the rows, the events and the answer are then
     *  byte-identical; `standingLine` is the prose line's own opt-in. */
    answerLayerValue;
    /** `.time()` (the time layer). Undefined for every agent that did not ask —
     *  then no clock is read, no time row is filed and `time` on a run is refused. */
    timeValue;
    outputSchemaRetries = 0;
    outputSchemaStrategy = 'auto';
    outputSchemaJson;
    /** 3-tier output fallback chain — set via `.outputFallback({...})`.
     *  Optional; absent = current throw-on-validation-failure behavior. */
    outputFallbackCfg;
    /**
     * Optional `ToolProvider` set via `.toolProvider()`. Propagated to
     * the Agent's Tools slot subflow + tool-call dispatcher; consulted
     * per iteration so dynamic chains (`gatedTools`, `skillScopedTools`)
     * react to current activation state.
     */
    toolProviderRef;
    /**
     * Optional override for `AgentOptions.maxIterations`. When set via
     * the `.maxIterations()` builder method, takes precedence over the
     * value passed to `Agent.create({ maxIterations })`.
     */
    maxIterationsOverride;
    /**
     * Observers collected via `.watch()`. Attached to the built Agent before `build()` returns
     * (each via `agent.attach(rec)`), in call order.
     */
    recorderList = [];
    // Voice config — defaults until the consumer calls .appName() /
    // .commentaryTemplates() / .thinkingTemplates(). Stored as plain
    // dicts (Record<string, string>) so the builder doesn't depend on
    // the template-engine modules at compile time; the runtime types
    // come from the agentfootprint barrel exports.
    appNameValue = 'Chatbot';
    commentaryOverrides = {};
    thinkingOverrides = {};
    /**
     * Optional rules-based reliability config (v2.11.5+). Set via
     * `.reliability({...})`. Wraps every `CallLLM` execution in a
     * retry/fallback/fail-fast loop driven by `preCheck` and `postDecide`
     * rules. See `ReliabilityConfig` for the rule shape.
     */
    reliabilityConfig;
    /**
     * Optional ThinkingHandler (v2.14+). Three states:
     *   - undefined (default): the provider's declared handler
     *     (`LLMProvider.thinkingHandler`)
     *   - explicit handler: override it
     *   - explicit `null`: opt out (no thinking handler mounted at all)
     *
     * The framework wraps the configured handler in a real footprintjs
     * sub-subflow at chart build time (see `buildThinkingSubflow`).
     * Mounted as a stage AFTER CallLLM inside `sf-call-llm`. Build-time
     * conditional — no stage when no handler resolves.
     */
    thinkingHandlerValue;
    /**
     * v2.14+ — request-side thinking activation. When set, every LLM
     * call carries `LLMRequest.thinking = { budget }`, asking the
     * provider (Anthropic) to emit reasoning blocks. Independent from
     * `.thinkingHandler()` (response-side normalization choice).
     */
    thinkingBudgetValue;
    selfExplainConfig;
    checkInConfig;
    /** Per-run config resolver set via `.configure()`. Undefined = the agent
     *  runs on its build-time model + system prompt, unchanged. */
    runConfigFn;
    /** The agent's one window strategy, from `.window()` or `.compaction()`.
     *  Undefined = no window stage exists, the ReAct loop target is unchanged,
     *  and the run is byte-identical to an agent that never heard of them. */
    windowStrategyValue;
    /** WHICH door set it (8.18.0). Recorded so every "already set" refusal can
     *  name the call the caller has to go and look at, in every direction. */
    windowStrategyDoor;
    /** The tool-dispatch chain, in call order. Empty = no chain, no ledger. */
    toolMiddlewareList = [];
    /** The message chain, in call order. Empty = no chain, no ledger. */
    messageMiddlewareList = [];
    /** `.act()` is the posture block: one per agent. See the method. */
    actCalled = false;
    /** The recipes applied, in DECLARATION ORDER — the order `.recipe()` was
     *  called, which is the order their builder calls ran. Reported verbatim on
     *  the run manifest. Empty for every agent that never called `.recipe()`,
     *  and an empty list puts NO field on the manifest. */
    appliedRecipeList = [];
    /** The recipe application stack while a `configure` is running — OUTERMOST
     *  first, because a recipe may apply another recipe and the innermost one is
     *  the code that literally called `.tool()`. Empty means a direct call. */
    recipeStack = [];
    /** Which source registered each tool NAME, and each injection ID. Two maps
     *  keyed by the raw name, never one map keyed by a composed string: joining a
     *  kind and a name is the separator-donation collision this repo has fixed
     *  seven times, and there is nothing to gain by risking it here. Consulted
     *  only when a duplicate is detected, so an agent with no recipes pays two
     *  `Map.set` calls and nothing else. */
    toolSources = new Map();
    injectionSources = new Map();
    /** The findings ledger's options (9.101.0), set by `.findings()` — the ONE
     *  door — and undefined on every agent that never called it. Composed into
     *  the options `build()` hands `Agent` as `AgentOptions.findings`. */
    findingsValue;
    /** Tool choice by classifier (9.105.0), set by `.toolChoice()` — the ONE
     *  door — and undefined on every agent that never called it. Composed into
     *  the options `build()` hands `Agent` as `AgentOptions.toolChoice`. */
    toolChoiceValue;
    /** The declared ontology (9.106.0), set by `.ontology()` — the ONE door —
     *  and undefined on every agent that never called it. Composed into the
     *  options `build()` hands `Agent` as `AgentOptions.ontology`. */
    ontologyValue;
    constructor(opts) {
        this.opts = opts;
        // Cache layer: opts.caching === 'off' propagates to scope's
        // `cachingDisabled` kill switch read by CacheGate. opts.cacheStrategy
        // overrides the registry-resolved default.
        if (opts.caching === 'off')
            this.cachingDisabledValue = true;
        if (opts.cacheStrategy !== undefined)
            this.cacheStrategyOverride = opts.cacheStrategy;
        // `findings` given as an option goes through the one door (9.101.0), so
        // the instruction piece rides with the arm however it was spelled — an
        // armed agent with no ask would decorate schemas the model was never told
        // about. A later `.findings()` call is then the second call, and refused.
        if (opts.findings !== undefined)
            this.findings(opts.findings);
        // Same door for `toolChoice` (9.105.0): the option form is validated by
        // the one method, so a misconfigured classifier fails here, not per call.
        if (opts.toolChoice !== undefined)
            this.toolChoice(opts.toolChoice);
        // Same door for `ontology` (9.106.0): the option form registers the
        // always-on ask through the one method, so a map the model was never
        // told how to read cannot be mounted.
        if (opts.ontology !== undefined) {
            this.ontology(opts.ontology, opts.ontologyAsk === undefined ? undefined : { ask: opts.ontologyAsk });
        }
        else if (opts.ontologyAsk !== undefined) {
            throw new Error('Agent.create: `ontologyAsk` without `ontology` — the ask is what the model is told to do ' +
                'with a declared map, so it needs the map. Pass `ontology` (defineOntology) beside it.');
        }
    }
    /**
     * Set the base system prompt.
     *
     * @param prompt - The system prompt text. Stable per-turn.
     * @param options - Optional config. `cache` controls how the
     *   CacheDecision subflow treats this prompt block:
     *   - `'always'` (default) — cache the base prompt as a stable
     *     prefix anchor. Highest cache-hit rate; recommended for
     *     production agents whose system prompt rarely changes.
     *   - `'never'` — skip caching. Use if the prompt contains volatile
     *     content (timestamps, per-request user IDs).
     *   - `'while-active'` — semantically equivalent to `'always'` for
     *     the base prompt (it's always active by definition).
     *   - `{ until }` — conditional invalidation (e.g., flush after iter 5).
     */
    system(prompt, options) {
        if (this.systemPromptSet) {
            throw new Error('AgentBuilder.system: already set. Each agent has one base system prompt — a second ' +
                'call used to REPLACE the first silently, so the instructions you wrote first were ' +
                'never sent and nothing said so. For a second block of always-on system content, ' +
                'add it as an injection: .steering(defineSteering({ id, content })) composes into ' +
                'the same slot, is visible in the trace as its own entry, and can be cached ' +
                'separately. To decide the prompt PER RUN, use .configure(({ defaults }) => ' +
                '({ instructions: … })). To build one string from parts, join them yourself and ' +
                'pass the result once.');
        }
        this.systemPromptSet = true;
        this.systemPromptValue = prompt;
        if (options?.cache !== undefined) {
            this.systemPromptCachePolicy = options.cache;
            this.systemPromptCacheExplicit = true;
        }
        return this;
    }
    tool(tool) {
        const name = tool.schema.name;
        if (this.registry.some((e) => e.name === name)) {
            throw new Error(this.duplicateRefusal('tool name', name, this.toolSources, 'Agent.tool()'));
        }
        this.registry.push({ name, tool: tool });
        this.toolSources.set(name, this.currentSource());
        return this;
    }
    /**
     * Who is registering right now: the app itself, or the recipe whose
     * `configure` is on the stack. A fresh object per call so a later push onto
     * the stack cannot rewrite what an earlier registration recorded.
     */
    currentSource() {
        return this.recipeStack.length === 0
            ? provenance_js_1.LOCAL_SOURCE
            : { kind: 'recipe', stack: [...this.recipeStack] };
    }
    /**
     * The sentence a duplicate registration gets.
     *
     * Two sources both being LOCAL keeps the message it has always had, to the
     * byte: an app whose tests read that string should not have them break
     * because a feature it does not use shipped. As soon as either side is a
     * recipe the message names both — which is the whole point, since "I never
     * registered a `search` tool" is true and unhelpful when a composition did.
     */
    duplicateRefusal(what, name, sources, callSite) {
        const existing = sources.get(name);
        const incoming = this.currentSource();
        if (existing?.kind === 'local' && incoming.kind === 'local') {
            return what === 'tool name'
                ? `Agent.tool(): duplicate tool name '${name}'`
                : `Agent.injection(): duplicate id '${name}'`;
        }
        return (0, apply_js_1.duplicateRegistrationRefusal)({ what, name, existing, incoming, callSite });
    }
    /**
     * Apply a **recipe** — a named, versioned composition over the builder
     * methods below (9.48.0).
     *
     * Every capability an agent needs already ships; what did not was a declared,
     * versioned, inspectable unit of CONFIGURATION. So an agent's setup lived as
     * prose in an example, was copy-pasted into an app, drifted there, and
     * afterwards nothing on the run could say which composition produced the
     * agent that answered. A recipe is that missing noun, and each applied one
     * puts an `{ id, version }` row on the run manifest.
     *
     * `configure` runs SYNCHRONOUSLY and immediately — at the position in the
     * chain where you wrote `.recipe()`, so declaration order is application
     * order and a later call still wins the way it always has. There is no
     * deferred phase, nothing to close and nothing registered anywhere: see
     * {@link AgentRecipe} for why that limit is deliberate.
     *
     * **Conflicts.** A tool name or injection id a recipe introduces that is
     * already taken refuses right here, naming BOTH sources — which recipe, or
     * the app itself. `'error'` is the only policy (`{ conflict }`); anything
     * else is refused by name rather than approximated.
     *
     * @example  an app composing two published recipes
     * ```ts
     * import { defineAgentRecipe } from 'agentfootprint/recipes';
     *
     * const agent = Agent.create({ provider, model })
     *   .recipe(supportDesk)   // system prompt + order lookup
     *   .recipe(housePolicy)   // the steering every agent here carries
     *   .tool(escalate)        // and one tool this app adds itself
     *   .build();
     * ```
     */
    recipe(recipe, options) {
        (0, defineAgentRecipe_js_1.assertAgentRecipe)(recipe, 'AgentBuilder.recipe');
        // Resolved before anything is applied: a policy this library does not have
        // must not half-apply a composition before it says so.
        (0, apply_js_1.resolveRecipeConflictPolicy)(options?.conflict, 'AgentBuilder.recipe');
        const applied = { id: recipe.id, version: recipe.version };
        // TWO different facts, two different refusals. On the STACK means the
        // composition is its own ancestor and `configure` would recurse forever; on
        // the LIST means the chain simply names one composition twice. Telling an
        // author their recursion is a duplicate sends them to the wrong line.
        const applying = this.recipeStack.find((r) => r.id === applied.id);
        if (applying) {
            throw new Error((0, apply_js_1.recursiveRecipeRefusal)({
                stack: [...this.recipeStack],
                incoming: applied,
                callSite: 'AgentBuilder.recipe',
            }));
        }
        const already = this.appliedRecipeList.find((r) => r.id === applied.id);
        if (already) {
            throw new Error((0, apply_js_1.duplicateRecipeRefusal)({
                existing: already,
                incoming: applied,
                callSite: 'AgentBuilder.recipe',
            }));
        }
        this.recipeStack.push(applied);
        try {
            const returned = recipe.configure(this);
            // `build()` is synchronous, so nothing would ever await this. Refused by
            // name rather than left to land on an agent that is already built.
            if (typeof returned?.then === 'function') {
                throw new Error((0, apply_js_1.asyncConfigureRefusal)(applied, 'AgentBuilder.recipe'));
            }
        }
        finally {
            // `finally`, not the happy path: a refusal raised inside `configure`
            // (a duplicate tool, say) must not leave the stack claiming a recipe is
            // still applying — the next registration would be attributed to it.
            this.recipeStack.pop();
        }
        // The manifest row is recorded only once `configure` has run CLEANLY. A
        // recipe whose application was refused half-way has not produced this
        // agent, and a row claiming it did would be the manifest asserting
        // something that did not happen. (Recursion is caught by the stack above,
        // so nothing depends on the row being written early.)
        this.appliedRecipeList.push(applied);
        return this;
    }
    /**
     * Register many tools at once. Convenience for tool sources that
     * return a list (e.g., `await mcpClient(...).tools()`). Each tool
     * is registered via `.tool()` so duplicate-name validation still
     * fires per-entry.
     */
    tools(tools) {
        for (const t of tools)
            this.tool(t);
        return this;
    }
    /**
     * Wire a chainable `ToolProvider` (from `agentfootprint/providers`)
     * as the agent's per-iteration tool source.
     *
     * The provider is consulted EVERY iteration via `provider.list(ctx)`
     * with `ctx = { iteration, activeSkillId, identity }`. Tools the
     * provider emits flow into the Tools slot alongside any static
     * tools registered via `.tool()` / `.tools()`. The tool-call
     * dispatcher also consults the provider so dynamic chains
     * (`gatedTools`, `skillScopedTools`) dispatch correctly when their
     * visible-set changes mid-turn.
     *
     * Throws if called more than once on the same builder (avoids
     * silent override surprises).
     *
     * @example  Permission-gated baseline
     *   import { gatedTools, staticTools } from 'agentfootprint/providers';
     *   import { PermissionPolicy } from 'agentfootprint/security';
     *
     *   const policy = PermissionPolicy.fromRoles({
     *     readonly: ['lookup', 'list_skills', 'read_skill'],
     *     admin:    ['lookup', 'list_skills', 'read_skill', 'delete'],
     *   }, 'readonly');
     *
     *   const provider = gatedTools(
     *     staticTools(allTools),
     *     (toolName) => policy.isAllowed(toolName),
     *   );
     *
     *   const agent = Agent.create({ provider: llm, model })
     *     .system('You answer.')
     *     .toolProvider(provider)
     *     .build();
     */
    toolProvider(provider) {
        if (this.toolProviderRef) {
            throw new Error('AgentBuilder.toolProvider: already set. Each agent has at most one external ToolProvider.');
        }
        this.toolProviderRef = provider;
        return this;
    }
    /**
     * Decide this run's model and/or system prompt when the run starts.
     *
     * An agent is built once and run many times, but not every run wants the
     * same model or the same instructions: a long message may deserve the
     * bigger model, a tenant may have its own house rules, a canary may want
     * last week's prompt. Rebuilding the whole agent per request works and is
     * wasteful; reaching in and mutating one is worse, because the trace then
     * describes an agent that no longer exists.
     *
     * The resolver runs ONCE per `run()`, at the start of the run, and what it
     * returns is **committed to the trace** — `resolvedModel` and
     * `resolvedInstructions` land in the run's commit log before the first LLM
     * call, and the LLM call reads them from there. So the recording says which
     * model actually answered instead of which model the agent was built with.
     *
     * Return `{}` (or nothing) to keep the defaults; `ctx.defaults` carries
     * them, so a resolver can decide relative to what was built rather than
     * restating it. Omit `.configure()` entirely and every run behaves — and
     * records — exactly as it did before.
     *
     * This is the RUN axis only. Tools are the iteration axis and already have
     * an owner: `.toolProvider()`, consulted every iteration.
     *
     * Throws if called more than once (same rule as `.toolProvider()` — a
     * silently-overridden resolver is a config that lies).
     *
     * @example  Bigger model for a bigger question
     *   const agent = Agent.create({ provider, model: 'small-model' })
     *     .system('You answer support questions.')
     *     .configure(({ message, defaults }) =>
     *       message.length > 500 ? { model: 'big-model' } : {},
     *     )
     *     .build();
     *
     * @example  Per-tenant house rules
     *   const agent = Agent.create({ provider, model })
     *     .system('You answer support questions.')
     *     .configure(({ identity, defaults }) => ({
     *       instructions: `${defaults.instructions}\n\n${rulesFor(identity?.tenant)}`,
     *     }))
     *     .build();
     */
    configure(fn) {
        if (this.runConfigFn) {
            throw new Error('AgentBuilder.configure: already set. One resolver per agent — a second one would ' +
                'silently override the first.');
        }
        if (typeof fn !== 'function') {
            throw new Error(`AgentBuilder.configure: expected a function (ctx) => ({ model?, instructions? }), got ` +
                `${typeof fn}.`);
        }
        this.runConfigFn = fn;
        return this;
    }
    /**
     * Everything this agent DOES about its own loop, in one block.
     *
     * Tools do the work. `.act()` decides about the work. `watch` remembers
     * both — and nothing can act without being watched.
     *
     * Five keys, one per moment of a turn, each optional and each the exact
     * argument the individual door takes:
     *
     * ```ts
     * const agent = Agent.create({ provider, model })
     *   .act({
     *     input:      [scrubSSNs],        // the message, before the run commits it
     *     beforeTool: [refundCeiling],    // every call, before it is dispatched
     *     afterTool:  [hideRawPII],       // every result, before the model reads it
     *     window:     slidingWindow({ keepRecentTurns: 12 }),
     *     output:     [noInternalCodenames],
     *   })
     *   .build();
     * ```
     *
     * **It is sugar, and provably so.** Each key is forwarded to the door that
     * already owned it — `.messageMiddleware()`, `.toolMiddleware()`,
     * `.window()` — so the agent it builds sends the same request bytes and
     * files the same records as the same rules spelled out one call at a time.
     * That equivalence is pinned per key by tests, the way `.compaction()`'s is.
     *
     * **The keys cannot fall behind the loop.** They are locked at compile time
     * against `LoopMoment`, so a sixth moment cannot ship without a key here.
     *
     * **A rule speaks where its hooks say, not where you filed it.** `beforeTool`
     * and `afterTool` are one chain; an entry with both `onToolCall` and
     * `onToolResult` runs at both moments whichever key you wrote it under —
     * the KEYS are named for the moments, the HOOKS for what they receive — and
     * an entry named
     * under both keys is the same object attached once. A governance rule that
     * silently did not run because it was written in the wrong bucket is exactly
     * the failure this library exists to make impossible — so the bucket is
     * checked for the hook it names, and the hooks decide the rest.
     *
     * **Call it once.** A second `.act()` throws: two posture blocks means the
     * answer to "what does this agent do at each moment?" is in two places, and
     * the second one silently wins. Adding one piece to an agent somebody else
     * built — a plugin, a policy pack — is what the individual doors are for,
     * and they stay open for exactly that.
     *
     * @param options - One key per moment. `input` / `output` take message
     *   middleware, `beforeTool` / `afterTool` take tool middleware, `window`
     *   takes a `WindowStrategy`. Unknown keys throw.
     */
    act(options) {
        if (this.actCalled) {
            throw new Error('AgentBuilder.act: already called. One posture block per agent — a second would put ' +
                'the answer to "what does this agent do at each moment of its loop?" in two places, ' +
                'with the later one silently winning. To ADD one rule to an agent that already has ' +
                'a posture, use the individual door for that moment (.toolMiddleware(), ' +
                '.messageMiddleware(), .window()); they compose incrementally by design.');
        }
        // Resolved BEFORE anything is attached: a bundle with a bad key must
        // leave the builder exactly as it found it, not half-configured. Every
        // entry is validated inside `resolveAct`, and the window — the one key
        // whose validation lives on its own door — goes first for the same
        // reason.
        const resolved = (0, act_js_1.resolveAct)(options);
        if (resolved.window !== undefined) {
            this.assertNoWindowStrategy('act');
            // Noted BEFORE delegating: `.window()` is the setter both doors share,
            // and the refusal a later call meets should name the door this caller
            // actually wrote.
            this.noteWindowStrategyDoor('.act({ window })');
            this.window(resolved.window);
        }
        this.actCalled = true;
        if (resolved.message.length > 0)
            this.messageMiddleware(...resolved.message);
        if (resolved.tool.length > 0)
            this.toolMiddleware(...resolved.tool);
        return this;
    }
    /**
     * Choose how the live context window is kept inside its budget.
     *
     * This is the general door; the strategy decides everything about WHEN it
     * acts and WHAT leaves. Three ship, and they share one turn segmentation
     * and one refusal engine, so a refusal reason means the same thing under
     * all of them:
     *
     *   `summarizeOldest({ thresholdTokens, summarizer, ... })`
     *      fold the oldest span into one summary message. `.compaction()` is
     *      this, spelled shorter.
     *   `slidingWindow({ keepRecentTurns })`
     *      keep the last N turns and drop older ones. No summarizer, no LLM
     *      call, no usage requirement — it runs on any provider.
     *   `tokenBudget({ thresholdTokens })`
     *      the counted-token trigger, dropping instead of summarizing.
     *
     * Never removed by any of them: the system envelope, the recent turns, and
     * any turn holding something unresolved — an unanswered tool call, a paused
     * tool, a pending check-in. Those refuse BY NAME in the record and the
     * strategy takes the next oldest instead. Removing an unanswered question
     * would destroy the referent of the answer that has not arrived yet, and
     * splitting a `tool_use` from its `tool_result` produces a request the
     * vendor rejects.
     *
     * **Whatever leaves the window stays in the ledger.** footprintjs's commit
     * log is append-only, so the turns were committed before the strategy ran
     * and remain byte-identical; every strategy files its own recorded step
     * naming the `runtimeStageId`s whose messages left, and emits one
     * `context.evicted` per message. Removing is not forgetting.
     *
     * Exactly one strategy per agent. Omit this (and `.compaction()`) and
     * nothing changes: no stage, no extra committed key, the same request bytes.
     *
     * @example
     * ```ts
     * import { Agent, slidingWindow } from 'agentfootprint';
     *
     * const agent = Agent.create({ provider, model })
     *   .window(slidingWindow({ keepRecentTurns: 12 }))
     *   .build();
     * ```
     */
    window(strategy) {
        this.assertNoWindowStrategy('window');
        if (strategy === null ||
            typeof strategy !== 'object' ||
            typeof strategy.plan !== 'function' ||
            typeof strategy.name !== 'string' ||
            strategy.name.length === 0) {
            throw new Error(`AgentBuilder.window: expected a WindowStrategy — an object with a non-empty \`name\` ` +
                `and a \`plan(input)\` method — got ${typeof strategy}. The shipped ones are ` +
                `summarizeOldest({ ... }), slidingWindow({ ... }) and tokenBudget({ ... }); call the ` +
                `factory, do not pass it.`);
        }
        this.assertSummarizerIsNotTheAgentItself(strategy.billing, 'AgentBuilder.window');
        this.windowStrategyValue = strategy;
        // `.act({ window })` delegates here, and its own door was noted first —
        // do not overwrite a more specific attribution with this generic one.
        this.noteWindowStrategyDoor(this.windowStrategyDoor ?? '.window()');
        return this;
    }
    /**
     * Refuse a strategy that would bill the agent's OWN provider instance for
     * the agent's OWN model (8.14.0).
     *
     * Not about money — `model` is required now, so nothing is billed quietly.
     * It is about two calls that are configured identically and provably behave
     * differently: the agent's call goes through `reliability`, any provider
     * decorator and the cache subflow; the summarizer's call goes through none
     * of them (see `runSummarizer`). Same object, same model, two behaviours,
     * and nobody typed the difference.
     *
     * Deliberately narrow. A different INSTANCE of the same vendor with the same
     * model is allowed — "use the strong model to write the summary, because a
     * bad summary poisons every turn after it" is a real choice — and a second
     * instance also ends the shared per-instance state (cursors, rate-limit
     * buckets, keep-alive pools) that made this pairing bite in the first place.
     *
     * Checked at every door that can set one. `.compaction({...})` and
     * `.window(summarizeOldest({...}))` are the same policy, and since 9.14.0
     * `.memory(defineMemory({ strategy: { kind: 'summarize', llm, model } }))`
     * is a third — a memory that folds recall makes the same un-decorated call
     * against the same pairing. A rule that only some doors enforce is advice.
     */
    assertSummarizerIsNotTheAgentItself(billing, label) {
        if (billing === undefined)
            return;
        if (billing.provider !== this.opts.provider)
            return;
        if (billing.model !== this.opts.model)
            return;
        throw new Error(`${label}: the summarizer is the agent's own provider INSTANCE and the agent's own model ` +
            `('${billing.model}'). Those two calls now look identical and are not: the agent's call ` +
            `runs through reliability retries, any withRetry/withFallback/withCircuitBreaker ` +
            `decorator and the cache; the summarizer's call runs through none of them — one ` +
            `attempt, no fallback, no cache. A difference nobody typed is the kind this library ` +
            `refuses.\n` +
            `Fix, one word: give the summarizer its OWN instance — \`anthropic()\` written again ` +
            `(as \`summarizer\` on a window strategy, as \`llm\` on a summarize memory) rather than ` +
            `the variable you passed to Agent.create. That also stops the two roles sharing ` +
            `per-instance state. Or name a different (usually cheaper) model.`);
    }
    /**
     * Keep the live context window inside a token budget — without ever losing
     * the record.
     *
     * Sugar for `.window(summarizeOldest(options))`, and byte-for-byte the same
     * agent. It keeps its own name because compaction is what the market calls
     * this and it is the strategy most people want first.
     *
     * At each ReAct iteration boundary, compaction compares the LAST call's
     * **adapter-reported** input tokens against `thresholdTokens`. Over budget,
     * it folds the oldest foldable span of the conversation into one summary
     * message and sends that instead. Counted, never guessed: a provider that
     * reports no usage gets a named refusal
     * (`CompactionUnmeasurableError`) rather than an invented number.
     *
     * **The fold edits the window, not the record.** The turns it folds stay in
     * the run's commit log byte-identical — footprintjs's log is append-only,
     * so a fold cannot erase them even in principle. The summary enters as its
     * own recorded step naming every `runtimeStageId` it folded, plus what was
     * measured and what refused to fold. A compacted run is still a provable
     * run: the lens draws a fold seam, not a hole.
     *
     * Never folded: the system envelope, the last `keepRecentTurns` turns, and
     * any turn holding something unresolved — an unanswered tool call, a paused
     * tool, a pending check-in. Folding an unanswered question would destroy
     * the referent of the answer that has not arrived yet, so those refuse by
     * name and the fold takes the next oldest instead.
     *
     * Omit `.compaction()` and nothing changes: no stage, no extra keys, the
     * same request bytes as before.
     *
     * @example
     * ```ts
     * const agent = Agent.create({ provider: anthropic(), model: 'claude-sonnet-4-5' })
     *   .compaction({
     *     thresholdTokens: 120_000,
     *     summarizer: anthropic(),
     *     model: 'claude-haiku-4-5',   // the cheap one writes the summary
     *   })
     *   .build();
     * ```
     */
    compaction(options) {
        this.assertNoWindowStrategy('compaction');
        // Validated here so the error names the door the caller actually used;
        // `summarizeOldest` re-runs the SAME validator under its own label, so the
        // two doors can never drift into accepting different things.
        const resolved = (0, options_js_1.resolveCompactionOptions)(options, 'AgentBuilder.compaction');
        this.assertSummarizerIsNotTheAgentItself({ provider: resolved.summarizer, model: resolved.model }, 'AgentBuilder.compaction');
        this.windowStrategyValue = (0, summarizeOldest_js_1.summarizeOldest)(options);
        this.noteWindowStrategyDoor('.compaction()');
        return this;
    }
    /**
     * One window strategy per agent, whichever door set it — a second would
     * silently override the first, and a window policy that quietly changed is
     * a policy you cannot audit.
     *
     * **Every refusal names the door that set it (8.18.0).** The three doors are
     * one setting, so a caller who hits this is holding two lines of code and
     * needs to know which one already won. Before, the direction decided how much
     * you were told: `.window()` named the strategy and then talked about
     * `.compaction()` — even when `.act({ window })` was what had set it — while
     * `.act()` said "set by .window() or .compaction()", an `or` that was
     * sometimes neither. `windowStrategyDoor` records the fact once, at the
     * moment it becomes true, and all three sentences read it.
     */
    assertNoWindowStrategy(door) {
        const existing = this.windowStrategyValue;
        if (existing === undefined)
            return;
        const setBy = this.windowStrategyDoor ?? '.window()';
        const law = 'One window strategy per agent, whichever door set it — a second would silently ' +
            'override the first, and a window policy that quietly changed is a policy you cannot ' +
            'audit.';
        if (door === 'act') {
            throw new Error(`AgentBuilder.act: this agent already has a window strategy ('${existing.name}'), set ` +
                `by ${setBy}. ${law} Drop the \`window\` key here, or remove the ${setBy} call.`);
        }
        if (door === 'compaction') {
            throw new Error(`AgentBuilder.compaction: this agent already has a window strategy ` +
                `('${existing.name}'), set by ${setBy}. ${law} \`.compaction()\`, \`.window()\` and ` +
                '`.act({ window })` are three doors into the same setting.');
        }
        throw new Error(`AgentBuilder.window: this agent already has a window strategy ('${existing.name}'), set ` +
            `by ${setBy}. ${law} \`.compaction()\` is this same door with summarizeOldest already ` +
            'in it, and `.act({ window })` is it again inside the posture block.');
    }
    /** Record which door set the window strategy, for the refusal above. */
    noteWindowStrategyDoor(door) {
        this.windowStrategyDoor = door;
    }
    /**
     * Override the ReAct iteration cap set via `Agent.create({
     * maxIterations })`. Convenience for builder-style code that prefers
     * fluent setters over constructor opts. Last call wins.
     *
     * Throws if `n` is not a positive integer or exceeds the hard cap
     * (`clampIterations`'s upper bound).
     */
    maxIterations(n) {
        if (!Number.isInteger(n) || n <= 0) {
            throw new Error(`AgentBuilder.maxIterations: expected a positive integer, got ${n}.`);
        }
        this.maxIterationsOverride = n;
        return this;
    }
    /**
     * Watch this agent. `.act()` says what the agent may do; `.watch()` says
     * who is looking while it does it.
     *
     * Every observer handed here is attached before `build()` returns, so it
     * sees every event from the very first run — there is no window where the
     * agent has run and nobody was watching.
     *
     * Variadic, because observers come in sets:
     *
     * ```ts
     * const agent = Agent.create({ provider, model })
     *   .watch(toolChoiceRecorder(), routeRecorder())
     *   .act({ beforeTool: [budgetGuard] })
     *   .build();
     * ```
     *
     * Build time, not run time. This returns the builder; `agent.attach(o)`
     * attaches to a live agent and returns an `Unsubscribe` you own. Same
     * mechanism underneath — `.watch()` replays through `agent.attach()` at
     * the end of `build()` — so mixing the two is fine and order is preserved.
     *
     * Called more than once, the sets concatenate in call order. Nothing is
     * de-duplicated here; footprintjs's executor dedupes by recorder id at run
     * time, so the same observer handed in twice still fires once.
     */
    watch(...observers) {
        for (const observer of observers)
            this.recorderList.push(observer);
        return this;
    }
    /**
     * Set the agent's display name — substituted as `{{appName}}` in
     * commentary + thinking templates. Same place to brand a tenant
     * ("Acme Bot"), distinguish multi-agent roles ("Triage" vs
     * "Reviewer"), or localize ("Asistente"). Default: `'Chatbot'`.
     */
    appName(name) {
        this.appNameValue = name;
        return this;
    }
    /**
     * Override agentfootprint's bundled commentary templates. Spread on
     * top of `defaultCommentaryTemplates`; missing keys fall back. Same
     * `Record<string, string>` shape with `{{vars}}` substitution as
     * the bundled defaults — see `defaultCommentaryTemplates` for the
     * full key list.
     *
     * Use cases: i18n (`'agent.turn_start': 'El usuario...'`), brand
     * voice ("You: {{userPrompt}}"), per-tenant customization.
     */
    commentaryTemplates(templates) {
        this.commentaryOverrides = { ...this.commentaryOverrides, ...templates };
        return this;
    }
    /**
     * Override agentfootprint's bundled thinking templates. Same
     * contract shape as commentary; different vocabulary — first-person
     * status the chat bubble shows mid-call. Per-tool overrides go via
     * `tool.<toolName>` keys (e.g., `'tool.weather': 'Looking up the
     * weather…'`). See `defaultStatusTemplates` for the full key list.
     */
    thinkingTemplates(templates) {
        this.thinkingOverrides = { ...this.thinkingOverrides, ...templates };
        return this;
    }
    // ─── Injection sugar — context engineering surface ───────────
    //
    // ALL of these push into the same `injectionList`. The Injection
    // primitive is identical across flavors; the methods are just
    // narrative-friendly aliases. Duplicate ids throw at build time.
    /**
     * Register any `Injection`. Use this for power-user / custom flavors;
     * for built-in flavors use the typed sugar (`.skill`, `.steering`,
     * `.instruction`, `.fact`).
     *
     * An Injection carrying `inject.messages` is ROUTED here, not refused
     * (7.19.1 refused it; 7.21.0 delivers it). What still gets refused is the
     * pair the wire cannot take: a `role: 'tool'` message has no tool call to
     * answer, so it is rejected here, at the declaration, on every provider.
     * A role the ATTACHED provider cannot carry is a different question — it
     * depends on the provider, which this builder does not have — so it is
     * refused at run start instead, by name. This is the one funnel every
     * flavor passes through, so a hand-built Injection cannot go around the
     * checks the named factories make.
     */
    injection(injection) {
        if (this.injectionList.some((i) => i.id === injection.id)) {
            throw new Error(this.duplicateRefusal('injection id', injection.id, this.injectionSources, 'Agent.injection()'));
        }
        for (const msg of injection.inject?.messages ?? []) {
            if (msg.role === 'tool') {
                throw new Error((0, messagesSlotRefusal_js_1.messagesToolRoleRefusal)(`Agent.injection('${injection.id}')`));
            }
        }
        assertReadSkillActivation(injection);
        this.injectionList.push(injection);
        this.injectionSources.set(injection.id, this.currentSource());
        return this;
    }
    /**
     * Register a Skill — LLM-activated, system-prompt + tools.
     * Auto-attaches the `read_skill` activation tool to the agent.
     * Skill stays active for the rest of the turn once activated.
     */
    skill(injection) {
        return this.injection(injection);
    }
    /**
     * Bulk-register every Skill in a `SkillRegistry`. Use for shared
     * skill catalogs across multiple Agents — register skills once on
     * the registry; attach the same registry to every consumer Agent.
     *
     * @example
     *   const registry = new SkillRegistry();
     *   registry.register(billingSkill).register(refundSkill);
     *   const supportAgent = Agent.create({ provider }).skills(registry).build();
     *   const escalationAgent = Agent.create({ provider }).skills(registry).build();
     */
    skills(registry) {
        for (const skill of registry.list())
            this.injection(skill);
        return this;
    }
    /**
     * Mount a declarative **skill graph** (proposal 002) — each skill carries a
     * graph-derived trigger (entry → always/rule, deterministic route → rule /
     * on-tool-return), so dynamic token-efficient loading becomes *declared* and
     * *drawable*. Pure sugar over `.injection()` — `graph.toMermaid()` renders the
     * topology.
     *
     * The optional second argument (SG-C, 9.17.0) sets the MOUNT's routing
     * posture and cursor span — see {@link SkillGraphOptions}. Omitted, the
     * agent behaves byte-for-byte as it always has.
     *
     * @example
     *   const graph = skillGraph()
     *     .entry(triage)
     *     .route(triage, sfp, { when: (r) => r.toolName === 'get_counters' && JSON.parse(r.result).crc > 0 })
     *     .build();
     *   Agent.create({ provider }).skillGraph(graph).build();
     *
     * @example
     *   // The conversation keeps its place across turns, and the model may
     *   // route only when the router declared ambiguity:
     *   Agent.create({ provider })
     *     .skillGraph(graph, { continuity: 'conversation', strictness: 'guard' })
     *     .build();
     */
    skillGraph(graph, options) {
        // Classic ReAct caches the system-prompt and tools slots after turn 1 (the
        // Context selector's `includeStatic`), while the injection engine — the loop
        // target — keeps running every iteration. A graph mounted on that mode
        // ADVANCES: routes fire, the cursor moves, `context.evaluated` honestly
        // reports skills activating — and none of it ever reaches the model,
        // because the slots that would carry the activated body and unlocked tools
        // were composed once and never again. Config that lies. Refused at build
        // (9.16.0), like `.selfExplain()` under classic and for the same caching
        // reason; `reactMode` is fixed at `Agent.create`, so this check is
        // order-safe wherever `.skillGraph()` appears in the chain.
        if (this.opts.reactMode === 'classic') {
            throw new Error("AgentBuilder.skillGraph: reactMode 'classic' cannot honor a skill graph. " +
                'Classic caches the system-prompt and tools slots after turn 1, so a route-driven ' +
                "activation moves the cursor and shows up in the trace but the activated skill's " +
                'body and tools never reach the model — the run would record routing that the wire ' +
                "never saw. Use the default 'dynamic' mode (or 'dynamic-grouped'), which recomposes " +
                'the slots every iteration. Classic remains fine WITHOUT a graph: a fixed system ' +
                'prompt, fixed tools, and always-on steering all compose on turn 1 and stay valid.');
        }
        // One agent routes with ONE graph. The second call used to replace the cursor,
        // the reachable set and the entry scorer while the FIRST graph's skills stayed
        // registered and active — so graph 1's route targets could never activate again
        // (their ids are absent from graph 2's reachable set, so even a model pick is
        // refused) and only its unconditional entries survived, as always-on bodies with
        // dead wiring. Nothing about that is recoverable at runtime, so it is refused
        // here (8.4.0).
        if (this.skillGraphNextSkill !== undefined) {
            throw new Error('Agent.skillGraph(): a skill graph is already mounted, and one agent routes with ' +
                'ONE graph. The second call replaces the cursor, the reachable set and the entry ' +
                "scorer — the first graph's skills stay registered and active, so its own routes " +
                'could never fire again. Merge the two graphs into one skillGraph(...) ' +
                'declaration, or build one agent per graph.');
        }
        for (const skill of graph.skills)
            this.injection(skill);
        // Capture the cursor resolver so the Injection Engine can `from`-gate route
        // triggers against the persisted `currentSkillId`. `nextSkill` is REQUIRED
        // (every `skillGraph().build()` supplies it). Pass the full `build()` result
        // here; for a bare skill list use `.skills({ list })` instead.
        this.skillGraphNextSkill = graph.nextSkill;
        // The reachable-set resolver gates read_skill to in-graph jumps (optional for
        // forward-compat with graphs built before reachableSkills existed).
        this.skillGraphReachable = graph.reachableSkills;
        // The relevance entry scorer (present only with `.entryByRelevance()`).
        this.skillGraphScoreEntries = graph.scoreEntries;
        // Which skills the graph WIRES (any declared incoming edge, deterministic or
        // bare). The gate uses it to leave a bare model edge `A → M` from-gated while
        // opening skills the graph never mentions.
        this.skillGraphEdgeTargets = (graph.edges ?? []).map((e) => e.to);
        // The clause-reporting resolver (8.5.0) and the one fact the gate's refusal
        // needs beyond the reachable set: is this a tree?
        this.skillGraphExplainNextSkill = graph.explainNextSkill;
        // The suppression reporter (8.15.0) — what the cursor law kept off the wire.
        this.skillGraphSupersededEntries = graph.supersededEntries;
        this.skillGraphIsTree = (graph.nodes ?? []).some((n) => n.kind === 'predicate');
        // The node-id set (9.19.0) — the brains check-up's map. Captured on every
        // mount; undefined when the graph object carries no `nodes` (a
        // structurally-typed graph), in which case declaring brains is refused
        // at build rather than validated against a map that does not exist.
        if (graph.nodes !== undefined) {
            this.skillGraphNodeIds = new Set(graph.nodes.flatMap((n) => (typeof n.id === 'string' ? [n.id] : [])));
        }
        // The DECLARED map (9.50.0) — nodes + edges verbatim, descriptions from the
        // compiled skills. Projected ONCE here (the graph object is in hand only at
        // mount) so `createExecutor` can file it per run without holding the graph.
        this.skillGraphDeclared = (0, skillGraphDeclared_js_1.buildSkillGraphDeclared)(graph, graph.skills);
        // The brains options (9.19.0) — captured verbatim; folded + validated at
        // build(), where the final injection list (the other home) exists.
        if (options?.providers !== undefined ||
            options?.escalation !== undefined ||
            options?.decider !== undefined) {
            this.skillGraphBrainOptions = {
                ...(options.providers !== undefined && { providers: options.providers }),
                ...(options.escalation !== undefined && { escalation: options.escalation }),
                ...(options.decider !== undefined && { decider: options.decider }),
            };
        }
        // ── The mount options (SG-C): posture + cursor span ────────────────────
        const strictness = options?.strictness ?? 'assist';
        const continuity = options?.continuity ?? 'turn';
        if (!['assist', 'guard', 'rails'].includes(strictness)) {
            throw new Error(`Agent.skillGraph: strictness '${String(strictness)}' is not a posture this library ` +
                `has. The three are 'assist' (today's gate — reachable picks admitted, divergence ` +
                `recorded), 'guard' (picks only from an offered menu) and 'rails' (the model never ` +
                `routes; rules/scorer/routes do).`);
        }
        if (!['turn', 'conversation'].includes(continuity)) {
            throw new Error(`Agent.skillGraph: continuity '${String(continuity)}' is not a span this library ` +
                `has. 'turn' (default) — the cursor is per-run, exactly as always; ` +
                `'conversation' — the turn's final cursor rides the conversation checkpoint and ` +
                `becomes the default entry when that conversation is continued.`);
        }
        if (continuity === 'conversation' && this.skillGraphIsTree) {
            throw new Error("Agent.skillGraph: continuity: 'conversation' cannot be honored by a decision " +
                '.tree() — a tree routes by predicate on every iteration and has no cursor to ' +
                "carry between turns. Use the flat entry/route form, or keep continuity: 'turn'.");
        }
        if (strictness === 'rails' && graph.entrySelection === 'model-read') {
            throw new Error("Agent.skillGraph: strictness: 'rails' cannot honor .entryByRead() — that mode's " +
                'entire entry mechanism IS a model pick, and rails refuses model routing. Rank the ' +
                'entries with .entryBy(keywordScorer()) / .classify(...), declare start rules, or ' +
                "drop to strictness: 'guard' (the model picks only from an offered menu).");
        }
        // The cascade needs the graph's turn-routing plan — a graph built by this
        // version always carries one. A structurally-typed graph without it cannot
        // be judged, and accepting the option while wiring nothing would be config
        // that lies, so it is refused by name.
        const needsPlan = graph.entrySelection === 'classify' || continuity === 'conversation';
        if (needsPlan && graph.turnRouting === undefined) {
            throw new Error(`Agent.skillGraph: ${graph.entrySelection === 'classify'
                ? 'this graph declares a classifier'
                : "continuity: 'conversation' was declared"}, but the graph object carries no \`turnRouting\` plan — the turn-start cascade ` +
                `cannot run without it. Build the graph with this version's skillGraph() (every ` +
                `flat graph it builds carries the plan), or drop the option.`);
        }
        if (strictness !== 'assist' || continuity !== 'turn' || graph.entrySelection === 'classify') {
            // Wired only when something NEW was asked for — a bare `.skillGraph(g)`
            // on a scorer/read/plain graph stores nothing and changes nothing.
            this.skillGraphCascade = {
                ...(graph.turnRouting !== undefined && { turnRouting: graph.turnRouting }),
                strictness,
                continuity,
                nodeIds: new Set((graph.nodes ?? []).flatMap((n) => (typeof n.id === 'string' ? [n.id] : []))),
            };
        }
        // The deferred body-contract note: the graph built without `knownTools`, so its
        // typo-vs-baseline-tool checks wait for `build()`, where the registry is real.
        // Library-built graphs also stamp the note on each skill's metadata (that is
        // what serves the `.skills({ list })` door); this capture is the fallback for
        // a structurally-typed graph without the stamps — `build()` dedupes by id.
        if (graph.deferredBodyContract) {
            this.skillGraphDeferredBodyContract = {
                mode: graph.deferredBodyContract.mode,
                skills: graph.skills,
            };
        }
        return this;
    }
    /**
     * Mount the maps kernel (9.58.0) — the layer that owns ENGAGEMENT, the
     * axis orthogonal to every map's own cursor.
     *
     * A mounted map (today: the skill map; the screen map is the next tenant)
     * keeps sole ownership of its position. What the kernel owns is whether
     * that map's contributions — prompt fragment and tools — ride the next
     * call. An engagement founded on a GUESS (an entry regex, a classifier)
     * is renewed only by concrete evidence: the map's own tool called, a
     * declared route fired, the model asking by name. Without corroboration
     * for `renewalGrace` consecutive passes the map is PARKED — its cursor
     * stays exactly where the map put it, its contribution stops riding, and
     * explicit or structural evidence re-engages it (an accepted `read_skill`
     * pick is the recovery door). Every standing change is a typed event:
     * `agentfootprint.map.engaged` / `agentfootprint.map.parked`.
     *
     * Why: in a recorded 30-call turn, an entry regex matched the word "zone"
     * inside "find the most recent zone redundancy run" — a noun the person
     * wanted to FIND, not a task. The turn stood on an audit skill for all 30
     * calls; its 4 tools were never called; ~7k characters of the wrong map
     * rode every call of a 359k-token turn. Under the kernel that map parks
     * on call four.
     *
     * Requires a mounted skill map — refused at build() otherwise. Zero-delta
     * when absent: no scope key, no events, byte-identical evaluation.
     *
     * @example
     *   const agent = Agent.create({ provider, model })
     *     .skillGraph(myMap)
     *     .maps({ renewalGrace: 3 })
     *     .build();
     */
    maps(options = {}) {
        if (this.mapsOptions !== undefined) {
            throw new Error(`Agent.maps(): the kernel is already configured. One kernel per agent — a second ` +
                `call would silently replace the first's policy. Merge the options into one call.`);
        }
        if (options.renewalGrace !== undefined) {
            if (!Number.isInteger(options.renewalGrace) || options.renewalGrace < 1) {
                throw new Error(`Agent.maps(): renewalGrace must be an integer >= 1, got ` +
                    `${String(options.renewalGrace)}. It is the number of corroboration-free ` +
                    `passes a guessed engagement survives before parking.`);
            }
        }
        this.mapsOptions = options;
        return this;
    }
    /**
     * WHICH ANSWER FIELDS ARE CLAIMS ABOUT WHICH FACTS (9.61.0) — the claim
     * seam's contract.
     *
     * The evidence gate (`.evidence()`) grounds the answer's names and
     * numbers: every value must appear in a tool result. Its own limit is
     * stated in its docs — it cannot catch a false claim assembled from real
     * values ("fc1/3 is healthy" when the data says the port is down uses
     * entirely grounded tokens). This closes that hole for the facts you
     * name: the run's semantic envelopes settle typed readings, and each
     * declared field of the validated answer is compared against the one it
     * claims to report.
     *
     * DECLARED, NEVER INFERRED — the `argumentsFrom` precedent. The library
     * does not guess that an answer field named `nav_count` is about
     * `nav(screen2)`; you say so, and the checker joins. Nothing is blocked:
     * a disagreement files one `agentfootprint.integrity.context_error` at
     * seam `'claim'`, and the answer is returned exactly as it was — unless an
     * enforcing `.answerValidation({ agentChecks: ['claims'] })` lists the
     * check, which then withholds a contradicted answer.
     *
     * Requires `.outputSchema()` — without a validated answer object there is
     * no typed stratum to read, and prose is never checked. Refused at
     * `build()` rather than skipped, because a contract that silently checks
     * nothing is worse than no contract.
     *
     * @example
     *   const agent = Agent.create({ provider, model })
     *     .tool(screenTool)                       // returns describedResult({ facts: [...], provenance })
     *     .outputSchema(AnswerSchema)
     *     .claims({ nav_count: { entity: 'screen2', field: 'nav' } })
     *     .build();
     */
    claims(contract) {
        if (this.claimsContract !== undefined) {
            throw new Error(`Agent.claims(): the claim contract is already declared. One contract per agent — a ` +
                `second call would silently replace the first. Merge the fields into one call.`);
        }
        const entries = Object.entries(contract);
        if (entries.length === 0) {
            throw new Error(`Agent.claims(): the contract names no fields — omitting the call is how "this agent ` +
                `claims nothing checkable" is said.`);
        }
        for (const [answerField, fact] of entries) {
            // An identity edge with a blank half joins the wrong subjects — the
            // same refusal `defineTool({ owner })` makes, for the same reason.
            if (fact === null ||
                typeof fact !== 'object' ||
                typeof fact.entity !== 'string' ||
                fact.entity.length === 0 ||
                typeof fact.field !== 'string' ||
                fact.field.length === 0) {
                throw new Error(`Agent.claims(): '${answerField}' must name a non-empty entity and field — it is the ` +
                    `edge the claim check joins on, and a blank half joins the wrong subjects. Got ` +
                    `${JSON.stringify(fact)}.`);
            }
        }
        this.claimsContract = contract;
        return this;
    }
    /**
     * Register a Steering doc — always-on system-prompt rule.
     * Use for invariant guidance: output format, persona, safety policies.
     */
    steering(injection) {
        return this.injection(injection);
    }
    /**
     * Register an Instruction — rule-based system-prompt guidance.
     * Predicate runs each iteration. Use for context-dependent rules
     * including the "Dynamic ReAct" `on-tool-return` pattern.
     */
    instruction(injection) {
        return this.injection(injection);
    }
    /**
     * Bulk-register many instructions at once. Convenience for consumer
     * code that organizes its instruction set in a flat array (`const
     * instructions = [outputFormat, dataRouting, ...]`). Each element
     * is registered via `.instruction()` so duplicate-id checks still
     * fire per-entry.
     */
    instructions(injections) {
        for (const i of injections)
            this.instruction(i);
        return this;
    }
    /**
     * Register a Fact — developer-supplied data the LLM should see.
     * User profile, env info, computed summary, current time, …
     * Distinct from Skills (LLM-activated guidance) and Steering
     * (always-on rules) in INTENT — the engine treats them all alike.
     */
    fact(injection) {
        return this.injection(injection);
    }
    /**
     * Register a Memory subsystem — load/persist conversation context,
     * facts, narrative beats, or causal snapshots across runs.
     *
     * The `MemoryDefinition` is produced by `defineMemory({ type, strategy,
     * store })`. Multiple memories layer cleanly via per-id scope keys
     * (`memoryInjection_${id}`):
     *
     * ```ts
     * Agent.create({ provider })
     *   .memory(defineMemory({ id: 'short', type: MEMORY_TYPES.EPISODIC,
     *                          strategy: { kind: MEMORY_STRATEGIES.WINDOW, size: 10 },
     *                          store }))
     *   .memory(defineMemory({ id: 'facts', type: MEMORY_TYPES.SEMANTIC,
     *                          strategy: { kind: MEMORY_STRATEGIES.EXTRACT,
     *                                      extractor: 'pattern' }, store }))
     *   .build();
     * ```
     *
     * The READ subflow runs at the configured `timing` (default
     * `MEMORY_TIMING.TURN_START`) and writes its formatted output to the
     * `memoryInjection_${id}` scope key for the slot subflows to consume.
     */
    memory(definition) {
        if (this.memoryList.some((m) => m.id === definition.id)) {
            throw new Error(`Agent.memory(): duplicate id '${definition.id}' — each memory needs a unique id ` +
                'to keep its scope key (`memoryInjection_${id}`) collision-free.');
        }
        // A memory that calls a model on your behalf — today that is the
        // `summarize` strategy — is held to the same separate-instance rule as
        // `.compaction()`. `defineMemory` cannot make this check: it has never
        // heard of an agent. It declares the billing, this reads it (9.14.0).
        this.assertSummarizerIsNotTheAgentItself(definition.billing, `Agent.memory('${definition.id}')`);
        this.memoryList.push(definition);
        return this;
    }
    /**
     * Register a RAG retriever — semantic search over a vector-indexed
     * corpus. Identical plumbing to `.memory()` (RAG resolves to a
     * `MemoryDefinition` produced by `defineRAG()`); this alias exists
     * so the consumer's intent reads clearly:
     *
     * ```ts
     * agent
     *   .memory(shortTermConversation)   // remembers what the USER said
     *   .rag(productDocs)                // retrieves what the CORPUS says
     *   .build();
     * ```
     *
     * Both end up as memory subflows, but the alias separates "user
     * conversation memory" from "document corpus retrieval" in code
     * intent, ids, and Lens chips.
     */
    rag(definition) {
        return this.memory(definition);
    }
    /**
     * Declarative terminal contract. The agent's final answer must be
     * JSON matching `parser`. Auto-injects a system-prompt instruction
     * telling the LLM the shape, and exposes `agent.runTyped()` /
     * `agent.parseOutput()` for parse + validate at the call site.
     *
     * The `parser` is duck-typed: any object with a `parse(unknown): T`
     * method works (Zod, Valibot, ArkType, hand-written). The optional
     * `description` field on the parser drives the auto-generated
     * instruction; consumers can also override via `opts.instruction`.
     *
     * Throws if called more than once on the same builder (avoids
     * silent override surprises).
     *
     * ## What the DEFAULT buys you, and what it does not
     *
     * `.outputSchema(parser)` on its own means **judge, do not re-ask**: the
     * prompt gets the instruction, native structured output is used when the
     * effective provider/model and available JSON Schema support it, and the
     * answer is validated locally in the loop. Unsupported native schemas or
     * providers fall back to instruction plus validation. A
     * failure is recorded (`outputAttempts`), announced
     * (`agentfootprint.agent.output_contract_unmet`), warned about once, and
     * readable afterwards through `agent.outputContractUnmet()`. What it does
     * NOT do is spend a turn fixing the answer — pass `{ retries: 1 }` for the
     * first real correction. Before 8.18.0 the default judged nothing at all
     * inside the run, and a `run()` caller could not tell a contract had been
     * missed.
     *
     * ## The two ways a run with a contract can END
     *
     * `runTyped()` throws **`OutputSchemaError`** when the answer fails the
     * schema — and **`MessageDeniedError`** when an `act({ output })` rule
     * refused to release the answer at all. The second one is not a schema
     * failure and is never re-asked: the answer was withheld on purpose, and
     * asking the model for a better-shaped version of a string nobody is allowed
     * to see would route around the rule. A `catch` block that only knows about
     * `OutputSchemaError` will miss it.
     *
     * `run()` throws neither for a schema failure: it returns the raw answer, as
     * it always has, and says so through the channels above.
     *
     * @param parser  Validation strategy that throws on shape failure.
     * @param opts    Optional `{ name, instruction, retries, strategy, jsonSchema }`.
     *
     * @example
     *   import { z } from 'zod';
     *   const Output = z.object({
     *     status: z.enum(['ok', 'err']),
     *     items: z.array(z.string()),
     *   }).describe('A status enum + an array of strings.');
     *
     *   const agent = Agent.create({...})
     *     .outputSchema(Output, { retries: 1 })
     *     .build();
     *
     *   const typed = await agent.runTyped({ message: '...' });
     *   typed.status; // narrowed to 'ok' | 'err'
     */
    /**
     * Require every **name and number in the final answer** to appear in a tool
     * result the run actually read (9.35.0). If one does not, the model typed it
     * rather than read it.
     *
     * ## What it is — and what it provably is not
     *
     * It is a **fabrication detector, not a correctness judge.** It catches
     * invented values. It CANNOT catch a false claim assembled from real values:
     * *"fc1/3 is healthy"* when the data says the port is down uses entirely
     * grounded tokens, and this check passes it without a murmur. Anyone who
     * reads it as a hallucination check will trust it for the one thing it
     * cannot do. It is also conservative by design (small numbers, all-letters
     * names and units are not examined), because a false accusation costs a real
     * turn and can refuse a good answer.
     *
     * The check is **deterministic** — set membership over normalized tokens. No
     * second model, no embedding, no judge. A guard that needed a bigger model
     * to police a smaller one would invert this library's whole thesis and would
     * fail exactly where the small model is deployed.
     *
     * ## The three postures
     *
     * Same three words as `.skillGraph({ strictness })`, deliberately — and a
     * SEPARATE setting, because routing authority and evidence discipline are
     * different decisions:
     *
     *   • `'assist'` (**default**) — record and flag. The answer goes out
     *     unchanged; you learn how often it happens before you act on it.
     *   • `'guard'` — the unsupported values are named back to the model, which
     *     gets ONE more ordinary turn (tools still on the wire, so it can go and
     *     fetch what it guessed — except under an ENFORCING `.answerValidation()`,
     *     where that turn and every later call of the run are served no tools,
     *     so a tool that writes or sends never runs twice before the answer is
     *     withheld). Survivors ship flagged. **This is the recommended posture
     *     for a weaker model.**
     *   • `'rails'` — the same one revision, then `run()` raises
     *     `UnsupportedValuesError` rather than return an answer that still
     *     carries them.
     *
     * Values the USER supplied — this turn's message, the conversation, the
     * system prompt and skill bodies — are exempt without being declared: the
     * user gave them, so they were not invented.
     *
     * Every judgement lands on the emit channel as
     * `agentfootprint.agent.evidence_checked`, whatever the posture, so a
     * debugger can show the answer, the values and whether the revision fixed
     * them. The terminal verdict is readable after the run with
     * `agent.unsupportedValues()`.
     *
     * `nudge: true` adds the staged-refs nudge — the recency half of grounded
     * numbers. When an iteration's context holds tool results staged by
     * reference (`artifacts.placement` tickets) and a served tool declares
     * `wants` over one of their kinds, ONE late line is appended to that
     * request naming the refs and the spender: derived numbers come from the
     * tool, not from mental arithmetic. Composed from declarations only,
     * request-only (never history), recorded as
     * `agentfootprint.agent.grounding_nudged`. Off by default.
     *
     * @example
     *   const agent = Agent.create({ provider, model })
     *     .tool(showInterface)
     *     .namesAndNumbersFromEvidence({
     *       posture: 'guard',
     *       // The default extractor guesses from digits and punctuation; teach
     *       // it your domain's shapes and they are checked by name.
     *       shapes: [{ name: 'wwn', match: /(?:[0-9a-f]{2}:){7}[0-9a-f]{2}/ }],
     *       exempt: ['v9.35.0'],
     *       // Name staged dataset refs + the compute tool late in the context,
     *       // where recency works FOR the instruction.
     *       nudge: true,
     *     })
     *     .build();
     */
    namesAndNumbersFromEvidence(opts) {
        if (this.evidenceGate) {
            throw new Error('AgentBuilder.namesAndNumbersFromEvidence: already set. Each agent has at most one ' +
                'evidence gate — pass every shape and exemption in the one call.');
        }
        // Resolved (and refused) HERE rather than at build: a posture typo is a
        // mistake in the line you just wrote, and the stack should say so.
        this.evidenceGate = (0, gate_js_1.resolveEvidenceGate)(opts);
        return this;
    }
    /**
     * Make the limits of an answer travel WITH the answer (this release).
     *
     * A tool that returns `coverage(verdict, { checked, notChecked,
     * cannotCover })` — or `absent({ what, checked, … })` — declares the ground
     * its result stands on. With this on, the run's declarations are folded into
     * one block and appended to the final answer, so a reader learns whether
     * *"everything looks fine"* means **verified** or **unexamined**. (A typed
     * answer gets the same limits as data instead — see below.)
     *
     * ## Why appended, and not asked for
     *
     * A limit the model is asked to carry is a limit the model can drop, and
     * dropping it is invisible: an answer with no caveat and an answer whose
     * caveat was omitted read identically. The block is therefore composed by
     * the framework from what the tools declared and concatenated onto the
     * answer — the model does not write it, so the model cannot drop it. The
     * price is that it changes the bytes of the answer, which is why it is off
     * by default.
     *
     * **What it is not.** It does not judge whether the model stated the limits
     * in its own prose, and it does not refuse an answer that did not. Both
     * would need a second model to decide what counts as "stated", which is the
     * one thing this library will not put in a guard (see
     * `.namesAndNumbersFromEvidence()` and `core/agent/evidence/README.md`).
     *
     * Off → byte-identical: nothing is appended and the final branch mounts the
     * stage function it has always mounted. The RECORDING half runs either way
     * (`agentfootprint.tools.coverage_declared` / `.absent`, and
     * `coverageDeclared` in the snapshot), so you can measure how often your
     * tools declare limits before you decide to ship them.
     *
     * ## With an output schema, the limits come back as data
     *
     * A typed answer (`.outputSchema()`) is JSON, and JSON with a block of prose
     * after it is not JSON — so nothing is appended to it. The answer stays
     * exactly what the model sent, `runTyped()` parses it, and the same limits
     * the block would have listed come back beside it:
     * `agent.answerCoverage()` (`{ checked, notChecked, cannotCover }`),
     * `turn_end.answerCoverage`, and `answerCoverage` in the snapshot.
     *
     * @example
     *   const agent = Agent.create({ provider, model })
     *     .tool(replicationHealth)   // returns coverage(verdict, { … })
     *     .limitsTravelWithTheAnswer()
     *     .build();
     *
     * @example A typed answer — the limits as data
     * ```ts
     * const typed = Agent.create({ provider, model })
     *   .tool(replicationHealth)
     *   .outputSchema(Verdict)
     *   .limitsTravelWithTheAnswer()
     *   .build();
     * const verdict = await typed.runTyped({ message: 'is replication healthy?' });
     * const limits = typed.answerCoverage(); // undefined when no tool declared any
     * ```
     */
    limitsTravelWithTheAnswer() {
        // Refused rather than shrugged at, like every other one-per-agent policy
        // (`toolsFromActiveSkill` verbatim): a second call means the author
        // believes there are two of these to set, and the honest answer is that
        // there is one boundary block per answer.
        if (this.limitsTravelValue) {
            throw new Error('AgentBuilder.limitsTravelWithTheAnswer: already set. One agent appends one coverage ' +
                'block to one answer, folded from every declaration its tools made — so a second ' +
                'call has nothing left to say. Drop it.');
        }
        this.limitsTravelValue = true;
        return this;
    }
    /**
     * Make the script a code-running call SUBMITTED travel with the answer,
     * attached by the library from the run record and labelled from the
     * execution record — so "always show the script" holds without the model's
     * cooperation, and the model never retypes a program that would then need
     * checking.
     *
     * A tool is code-running because it DECLARES so (`Tool.script`;
     * `codeRunnerTool` declares `{ argument: 'code', language }`). Each call of
     * one files a `script` row: the program handed to the tool (after any
     * before-tool middleware) and what its runner reported. With this on:
     *
     * - `turn_end.scripts` carries the attached scripts as data on every answer
     *   — a host with its own renderer reads it and never parses the text;
     * - a PROSE answer also carries them as text after its `---` separator, one
     *   block per script: a label line ("Script submitted to run_code on call c1
     *   — completed, exit 0. Attached by the library from the run record:"),
     *   then the program in the library's own fence, byte for byte. A typed
     *   answer (`.outputSchema()`) gets the data only.
     *
     * The label says only what the record proves: `completed, exit 0` needs an
     * explicit exit code; a bare runner verdict reads "runner reported success;
     * no exit code"; a timeout, a cancellation or a service error with no exit
     * code reads "outcome unknown"; a tool that returned without runner facts
     * reads "tool call returned; execution not confirmed". A result withheld
     * over the tool's ceiling does not undo a completed execution — both are
     * said. A program over `maxBytes` (default 4,000 UTF-8 bytes) is cut at a
     * code-point boundary and says it is incomplete.
     *
     * `which: 'last'` (default) attaches the last call of the turn that reached
     * the tool; `'all'` every such call, at most 8, the rest counted on
     * `turn_end.scripts.omitted`. The attached text is part of the answer the
     * app chose to show: `run()` returns it and memory keeps it.
     *
     * Refused at build when no registered tool declares `script` (and no
     * `ToolProvider` could serve one): an arm that could never attach anything
     * is a misconfiguration, not a no-op.
     *
     * @example
     * ```ts
     * const agent = Agent.create({ provider, model })
     *   .tool(codeRunnerTool({ runner: localCodeRunner() }))
     *   .namesAndNumbersFromEvidence({ posture: 'guard', code: 'match' })
     *   .scriptTravelsWithTheAnswer({ which: 'last', maxBytes: 4000 })
     *   .build();
     * ```
     */
    scriptTravelsWithTheAnswer(opts) {
        if (this.scriptTravelValue !== undefined) {
            throw new Error('AgentBuilder.scriptTravelsWithTheAnswer: already set. One agent attaches its scripts ' +
                'one way — pass `which` and `maxBytes` in the one call.');
        }
        this.scriptTravelValue = (0, attachment_js_1.resolveScriptTravel)(opts);
        return this;
    }
    /**
     * Mount the INPUTS LAYER (honesty layer 2) for ruled tools the build cannot
     * see — tools a `ToolProvider` serves.
     *
     * A tool that declares `askOrAssume` (`defineTool({ …, askOrAssume: { window:
     * { assume: '2h' } } })`) arms the layer by itself when it is REGISTERED on
     * the agent (`.tool()`, a skill's tools, an MCP tool registered here): the
     * library fills a value the call leaves out, files it on the findings ledger
     * as `default`, tells the model in a note on the result, and the answer's
     * standing says the value was assumed. A ToolProvider's list is only known
     * per iteration, so the build cannot see its ruled tools — and a ruled tool
     * met on an agent without the layer is REFUSED rather than run unruled,
     * because configured-and-inert looks exactly like configured-and-working.
     * This one line mounts the layer so those tools' rules apply.
     *
     * `argumentSources: true` arms DECLARED SOURCES without the findings
     * ledger: each ruled tool's served schema carries the reserved `_findings`
     * argument with `from` alone — where each argument value came from (the
     * person's words as a `quote`, a result's `id`, an earlier answer, the app,
     * or `'assumed'`) — and the layer CHECKS each claim before the batch runs;
     * under an `ask` rule a value the checks do not trace is asked of the
     * person. The same checks and rows as `.findings({ argumentSources: true })`,
     * without the ledger's `_findings` schema on every tool (about 2,500
     * characters each) — `.findings()` beside it serves both, exactly as that
     * door does.
     *
     * Off → the chart is byte-identical (nothing mounted, decorated, read or
     * written).
     *
     * @example
     *   const agent = Agent.create({ provider, model })
     *     .toolProvider(fleetTools)   // lists a tool that declares askOrAssume
     *     .inputsLayer()
     *     .build();
     *
     * @example
     *   // The model says where each ruled value came from; the library checks it.
     *   const agent = Agent.create({ provider, model })
     *     .tool(searchLogs)           // declares askOrAssume: { window: { ask, choices } }
     *     .inputsLayer({ argumentSources: true })
     *     .build();
     */
    inputsLayer(options) {
        if (this.inputsLayerValue) {
            throw new Error('AgentBuilder.inputsLayer: already set. One agent mounts one inputs layer, over every ' +
                'ruled tool it can call — a second call has nothing left to add. Drop it.');
        }
        if (options !== undefined && (options === null || typeof options !== 'object')) {
            throw new Error(`AgentBuilder.inputsLayer: expected an options object or nothing, got ${typeof options}.`);
        }
        const argumentSources = options?.argumentSources;
        if (argumentSources !== undefined && typeof argumentSources !== 'boolean') {
            throw new Error(`AgentBuilder.inputsLayer: argumentSources must be true or false, got ` +
                `${JSON.stringify(argumentSources)}.`);
        }
        this.inputsLayerValue = true;
        this.inputsLayerSourcesValue = argumentSources === true;
        return this;
    }
    /**
     * Mount the RESULTS LAYER (honesty layer 3) for periods the build cannot
     * see coming — a `ToolPeriod` on a tool a `ToolProvider` serves, or a
     * `period` a tool declares only on its results.
     *
     * A result says what time its read covered: `absent({ …, period })`,
     * `coverage(value, { …, period })` or `describedResult({ …, period })` —
     * the instants it `queried`, and what the store `held` (or `'unknown'`, said
     * out loud). The layer runs at the loop head, reads the batch just run, and
     * files ONE verdict per call on the findings ledger (`kind: 'period'`):
     * `covered`, `partly-held`, `not-held`, `unknown` — or `undeclared`, when
     * the tool declares a `ToolPeriod` and the result said nothing about what its
     * read covered. The answer's standing (`agent.assessment()`) reads "not
     * sure" for every verdict but `covered`. It serves the model nothing: the
     * period is already in the result the model read.
     *
     * A REGISTERED tool that declares a `ToolPeriod` (`defineTool({ …, period:
     * { argument: 'window' } })`) arms the layer by itself. Without the layer, a
     * declared period is still recorded (`coverageDeclared`, the events, the
     * `Period:` line under `.limitsTravelWithTheAnswer()`) — but no verdict is
     * filed, and one dev warning per tool says so.
     *
     * Off → the chart is byte-identical (nothing mounted, read or written).
     *
     * @example
     *   const agent = Agent.create({ provider, model })
     *     .tool(backupRuns)   // returns absent({ …, period }) / describedResult({ …, period })
     *     .resultsLayer()
     *     .build();
     *   await agent.run({ message: 'Any failed backups in the last hour?' });
     *   (await agent.assessment())?.reasons; // [{ reason: 'period-not-held', layer: 3, … }]
     */
    resultsLayer() {
        if (this.resultsLayerValue) {
            throw new Error('AgentBuilder.resultsLayer: already set. One agent mounts one results layer, over every ' +
                'call it runs — a second call has nothing left to add. Drop it.');
        }
        this.resultsLayerValue = true;
        return this;
    }
    /**
     * Mount the ANSWER LAYER (honesty layer 4): every answer gets its standing,
     * folded from the run's committed record — never from how sure the model
     * sounded — and served as data.
     *
     * The standing is one of five words: `known` (a supporting row: a passed
     * `.answerValidation()` report for these exact bytes), `consistent` with the
     * run's record (checks ran and none fired — never "verified"), `not-sure`
     * (a reason fired: an assumed argument, an empty result that did not say
     * what it searched, a declared gap, an unsupported value, a limit that cut
     * the turn short, …), `ask`, or `not-assessed` (nothing on the record could
     * be checked). It is the same fold as `agent.assessment()` and
     * `assessAnswer(recording)`, run once inside the run at the head of the
     * final branch, so the in-run standing equals the one a later reader folds.
     *
     * Armed, three things happen and no others:
     *
     * - `turn_end.answerAssessment` and one `agentfootprint.answer.assessed`
     *   event carry the value, its rendering, the reason kinds and the checks
     *   that ran — never a value from the answer or a quote;
     * - the Route decider files two witness rows on the findings ledger, so the
     *   fold can read them: the evidence gate's clean pass (`grounded` — the
     *   names-and-numbers check RAN; it never supports "known") and an answer
     *   given before a skill's declared steps finished (`steps-unfinished`);
     * - every row the findings ledger gets carries its conversation `turn`.
     *
     * It is a LABEL: it never asks, never refuses and never changes the model's
     * reply. The one exception is opt-in — `{ standingLine: true }` appends ONE
     * line to a prose answer (`Not sure — window = "2h" was assumed by
     * search_logs's rule, not given by you.`), after the limits separator.
     * Refused beside `.answerValidation()` (which judges the exact bytes
     * delivered) and beside `.outputSchema()` (a typed answer is JSON, and JSON
     * followed by prose is not) — through this door and `AgentOptions.answerLayer`
     * alike; the standing still travels as data there. The line, like the
     * limits block, is appended to the answer the run returns — never streamed
     * on `stream.token`.
     *
     * `{ standingLine: { when } }` says the line only when it matters:
     * `'always'` (what `true` means), `'unless-consistent'` (not for
     * "consistent with the run's record"), or `'not-sure-or-ask'` (only "not
     * sure" and "ask" — not "consistent", "not assessed" or "known"). `ask` is
     * said in every mode. The mode decides the LINE only: the standing travels
     * as data in every mode (`turn_end.answerAssessment`, the event), the limits
     * block (`.limitsTravelWithTheAnswer()`) is appended whether or not the line
     * is, and memory keeps exactly the answer that was delivered. **No line is
     * never a check:** under `'not-sure-or-ask'` it means no reason fired, which
     * includes "nothing could be checked" — and an app that hides both the line
     * and the data leaves the person unable to tell "checked" from "not
     * assessed".
     *
     * @example Say it only when it matters
     *   Agent.create({ provider, model })
     *     .tool(searchLogs)
     *     .answerLayer({ standingLine: { when: 'not-sure-or-ask' } })
     *     .build();
     *
     * The in-run fold reads no app declarations: `agent.assessment(declarations)`
     * and the answer account take `rowsAt`, and can say more than the run did.
     *
     * Off → the chart, the committed rows, the events and the answer are
     * byte-identical.
     *
     * @example
     *   const agent = Agent.create({ provider, model })
     *     .tool(searchLogs)
     *     .answerLayer()                       // the standing as data
     *     .build();
     *   agent.on('agentfootprint.answer.assessed', (e) => console.log(e.payload.standing));
     *
     * @example One line to the person, too
     *   Agent.create({ provider, model }).tool(searchLogs).answerLayer({ standingLine: true }).build();
     */
    answerLayer(options) {
        if (this.answerLayerValue !== undefined) {
            throw new Error('AgentBuilder.answerLayer: already set. One agent folds one standing per answer — ' +
                'a second call has nothing left to add. Drop it (pass { standingLine: true } once).');
        }
        if (options !== undefined && (typeof options !== 'object' || options === null)) {
            throw new Error('AgentBuilder.answerLayer: options must be an object — ' +
                "{ standingLine?: boolean | { when: 'always' | 'unless-consistent' | 'not-sure-or-ask' } } " +
                '— or omitted.');
        }
        this.answerLayerValue = answerLayerArm(options ?? {}, 'AgentBuilder.answerLayer');
        return this;
    }
    /**
     * Arm the TIME LAYER's clock: every run declares when "now" is and which
     * zone the person is in, and the run records it.
     *
     * - **The zone is per run.** `run({ message, time: { zone } })` names the
     *   person's zone; `zone` here is an optional FALLBACK for a run that names
     *   none. With neither, the zone is UNKNOWN — the server's zone is never
     *   used and none is guessed: instants are spelled in UTC and said to be,
     *   the person is asked their zone before any time they wrote is read, and
     *   the zone they answer holds for the next turns. A zone is an IANA name
     *   (`'America/Los_Angeles'`, `'UTC'`); an abbreviation (`PST`) or a bare
     *   offset names no zone and is refused.
     * - **`now` is the app's** (`time: { now }` — the message's time), else the
     *   turn's start, recorded as a default (`nowSource: 'default'`).
     * - **The model is told the date.** Every request ends with the library's
     *   one time line, opening with the turn's clock ("This turn's time: Friday
     *   2026-10-09 08:40 America/Los_Angeles (UTC-07:00).").
     * - **Recorded once per turn.** Seed files one `clock` row on the ledger
     *   (`agent.findings()`): `now`, `nowSource`, `zone`, `zoneSource`, and a
     *   window set in a UI (`time: { window }`, recorded `source: 'control'`).
     *   Each dispatched call files a `call` row with `dispatchedAt`.
     * - **Frozen across a pause.** `resume(checkpoint, answer, { time })` keeps
     *   the paused turn's clock; a `time` that differs is recorded as a
     *   `clock-on-resume` row, never applied.
     * - **The limits in the person's zone.** Under `.limitsTravelWithTheAnswer()`,
     *   every `Period:` line is rendered in the clock's zone with the zone named
     *   (`2026-10-09 08:00–08:40 America/Los_Angeles (UTC-07:00)`); the typed
     *   record keeps the instants as declared.
     * - **The person's words, read by YOUR reader** (`reader`, no default). A
     *   `TimeReader` returns zone-less parts (`'10/09/26'` → three numbers, the
     *   order undecided); the library resolves them against the clock into
     *   every candidate window and files one `time-reading` row per mention —
     *   once per turn, only on a message a person wrote, never re-read on a
     *   resume or a retry. `policy` picks among the candidates: `dateOrder`
     *   (`'ask'` default, or `'MDY'` / `'DMY'` / `'YMD'`) and `year` (`'ask'`
     *   default, or `'current'`); a pick is recorded as assumed. A
     *   `kind: 'model'` reader's window is never taken as the person's words:
     *   it waits for the person to confirm it.
     * - **The time ask checks the answer** (a `requestInput` field with a
     *   `format`: `'instant'`, `'time-range'` or `'zone'`). A resume whose answer
     *   has no offset, ends before it starts, names no zone, or — under
     *   `.time()`, which knows the person's zone — writes a wall time the clocks
     *   skip is not taken: the ask comes back with `refused: { answer, reason }`
     *   and `repeat: { count }`, and nothing runs. The reason is a catalog
     *   sentence (`defaultTimeAskMessages`); `messages` overrides any key.
     *
     * Off → nothing is filed, read or rendered differently, and `time` on a run
     * is refused (a door that ignored it would look configured and do nothing).
     *
     * @example
     *   const agent = Agent.create({ provider, model })
     *     .tool(searchLogs)
     *     .time({ zone: 'America/Los_Angeles' }) // the fallback; each run's own zone wins
     *     .limitsTravelWithTheAnswer()
     *     .build();
     *   await agent.run({ message, time: { now: sentAt, zone: session.zone } });
     *
     *   // With a reader: the words are read once per turn, and recorded.
     *   Agent.create({ provider, model }).time({ reader: myReader, policy: { dateOrder: 'MDY' } });
     */
    time(options) {
        if (this.timeValue !== undefined) {
            throw new Error('AgentBuilder.time: already set. One agent has one clock configuration — pass the ' +
                'fallback zone, the reader and its policy once.');
        }
        const read = (0, clock_js_1.readTimeOptions)(options);
        if ('problem' in read)
            throw new Error(`AgentBuilder.time: ${read.problem}.`);
        this.timeValue = read.value;
        return this;
    }
    /**
     * Offer a skill's tools **only while that skill is active** (9.36.0). One
     * line, for every skill on the agent.
     *
     * ## What it fixes
     *
     * By default a skill's `tools` go into the agent's STATIC tool list at build
     * time, so the model can see and call them from iteration 1 — activated or
     * not. Narrowing that was a per-skill field (`defineSkill({ autoActivate:
     * 'currentSkill' })`) you had to remember on every skill; the one you forgot
     * kept its tools on the wire for the life of the agent, and nothing said so.
     * This says it once, for all of them.
     *
     * With it on, a skill's tools enter the request on the iterations where the
     * skill is active — through the same readmission path `autoActivate` has used
     * since v2.5 — and nowhere else. Everything else is untouched: `read_skill`,
     * `list_skills`, your `.tool()` registry, provider tools and every other
     * active skill's tools stay offered, because a scoped agent still has to
     * handle the input nobody imagined.
     *
     * ## Not a posture dial, and why
     *
     * `.skillGraph({ strictness })` and `.namesAndNumbersFromEvidence({ posture })`
     * take three values because there is a real middle there — record it, revise
     * it, refuse it. The wire has no middle: a tool's schema is either in the
     * request or it is not, and "record that we sent it" is just sending it. A
     * three-value dial here would ship one behaviour under two names.
     *
     * ## What it does NOT do
     *
     * It governs the OFFER, not dispatch. A tool stays resolvable by name so an
     * active skill's call lands — the split `autoActivate` has always had. If you
     * need execution itself gated (an inactive skill's tool refused even when the
     * model names it from a restored transcript), that is a `PermissionChecker`
     * or a `gatedTools` provider, and it is a different question: authority to
     * run, not what the model was shown.
     *
     * ## Interaction with the per-skill flag and with `scopeTools`
     *
     * All three stamp the same field, and none can contradict another:
     * `autoActivate` has one legal value, so a skill can ask to be scoped and can
     * never ask to be exempt. A skill that declared its own keeps it; the graph's
     * `scopeTools: true` fills in the skills it wires; this fills in the rest.
     * Turning it on can only remove tools from the static list, never add one.
     *
     * **Opt-in.** The default is unchanged — an agent that never calls this
     * keeps additive tool visibility. Version 10 retires old APIs without
     * changing this active routing policy.
     *
     * @example
     *   const skills = await skillsFromDir('./skills', { tools: [lookupOrder, issueRefund] });
     *   const agent = Agent.create({ provider, model })
     *     .skills({ list: () => skills })
     *     .toolsFromActiveSkill()   // billing's tools appear when billing does
     *     .build();
     */
    toolsFromActiveSkill() {
        // Refused rather than shrugged at, like every other one-per-agent policy:
        // a second call means the author believes there are two of these to set,
        // and the honest answer is that there is one.
        if (this.toolsFromActiveSkillValue) {
            throw new Error('AgentBuilder.toolsFromActiveSkill: already set. It is one posture for the whole ' +
                "agent — every skill's tools follow that skill's activation — so a second call " +
                'has nothing left to say. Drop it.');
        }
        this.toolsFromActiveSkillValue = true;
        return this;
    }
    outputSchema(parser, opts) {
        if (this.outputSchemaParser) {
            throw new Error('AgentBuilder.outputSchema: already set. Each agent has at most one terminal contract.');
        }
        this.outputSchemaParser = parser;
        this.outputSchemaRetries = resolveRetries(opts?.retries);
        this.outputSchemaStrategy = opts?.strategy ?? 'auto';
        if (opts?.jsonSchema !== undefined)
            this.outputSchemaJson = opts.jsonSchema;
        const instructionText = opts?.instruction ?? (0, outputSchema_js_1.buildDefaultInstruction)(parser);
        const id = opts?.name ?? 'output-schema';
        // Always-on system-slot instruction. Activates every iteration so
        // long runs keep the contract present (recency-first redundancy).
        this.injectionList.push((0, defineInstruction_js_1.defineInstruction)({
            id,
            activeWhen: () => true,
            prompt: instructionText,
        }));
        // The SECOND door into the injection list (`.injection()` is the other), so
        // it records provenance too: without this the id would read as
        // "unattributed" in a later collision — an honest answer, but a worse one
        // than the true "the recipe/app that called .outputSchema() mounted it".
        this.injectionSources.set(id, this.currentSource());
        return this;
    }
    /**
     * The findings ledger (9.101.0) — the model's OWN standings on its tool
     * results, on the record, at zero extra calls.
     *
     * WHY. A long tool-using run serves every result back in full on every
     * call, and what the model already stood on, left open, ruled out or found
     * to be noise is nowhere but in its head. With this door every served tool
     * schema carries a reserved optional `_findings` argument: the model
     * declares a `basis` for each call before the result exists ('direct' when
     * it expects the answer, 'exploratory' when it is looking) and, on its next
     * tool call or as a top-level `_findings.previous` on a JSON answer, the
     * STANDING of each previous result by its tool_result id — 'fact' with the
     * assertions it stands on, 'open' with what would settle it, 'ruled-out'
     * with one line, 'noise' with nothing. The library peels the argument off
     * before anything else reads the call (middleware, validation, execute and
     * every pause carrier see the call as if the key were never there), files
     * one flat append-only `findingsLedger` on the run's state through ONE
     * writer, and leaves history verbatim. Read it back with
     * {@link Agent.findings}. Nothing infers: a call with no declaration files
     * no row, and a result nobody named has no standing — never 'open'.
     *
     * WHAT THE ANSWER TURN IS SERVED. Once the model has declared a standing,
     * every later call is served a request-only system piece composed from the
     * folded ledger (`findings/serve.ts · findingsLedgerPiece` — facts,
     * limitations, evidenceRefs and nextSteps, each marked "declared by the
     * model"; results nobody named listed as undeclared, never open), and on
     * the wire only — `history` never changes — a result the model judged
     * `noise` or `ruled-out` is replaced by a ticket (`collapseJudged`).
     * `serve` chooses how much of the pile stays: `'ledger-and-facts'` (the
     * default) keeps fact, open and undeclared results verbatim beside the
     * piece; `'ledger-only'` collapses fact results too and is BENCH-GATED —
     * shipped so `bench/findings-shuffle.mjs` can measure it on a real model,
     * not a recommendation, never a default until that run shows the answer
     * does not drift under shuffled evidence. `answerAsk` (9.103.0) appends
     * the answer-turn ask (`findings/reserved.ts · FINDINGS_ANSWER_ASK`) to
     * that piece under `'quote-facts'` — BENCH-GATED the same way, against
     * the fidelity dip the real-model page's fourth run measured; `'none'`
     * (the default) writes no key and serves the piece exactly as before.
     * `keepLedgerFacts` is the ceiling
     * of fact turns the WINDOW holds under standing-aware eviction (9.102.0) —
     * the same dial as `AgentOptions.keepLedgerFacts` (that field says why and
     * what it costs), this door winning when both are given, resolved once by
     * `Agent` at build and threaded to the window stage on an armed agent with
     * a window strategy, where `stages/window.ts · buildWindowStage` spends it
     * as the `'ledger-fact'` pin ceiling; a negative or non-integer value is
     * refused here.
     *
     * `argumentSources: true` (honesty layer 2, declared sources) — on a tool
     * that declares argument rules (`askOrAssume`), the reserved argument also
     * carries `from`, first and required: the model says where each argument
     * value came from (the person's words as a `quote`, a result's `id`, an
     * earlier answer, the app, or `'assumed'`), and the inputs layer CHECKS each
     * claim before the batch runs and files the verdict on the call's argument
     * rows — under an `ask` rule, a value the checks do not trace is asked of the
     * person, and the rule's sentence names `_findings.from`. No instruction line
     * is added: `from` explains itself in its own property. It needs the inputs
     * layer (a registered ruled tool, or `.inputsLayer()`) and is refused at
     * build without it. The same arm WITHOUT this ledger:
     * `.inputsLayer({ argumentSources: true })`. Off (the default): nothing is
     * served, read or written.
     *
     * Once per agent (a second call is refused, the `.window()` grammar).
     * Registers the always-on `findings-ledger` instruction — the byte-for-byte
     * twin of {@link outputSchema}'s piece, hashed per piece on every receipt,
     * so a reworded ask is a different hash a bench can name. An agent that
     * never calls this is byte-identical to one built before the ledger
     * existed: no decoration, no peel, no key, no piece, no event.
     *
     * @example
     * ```ts
     * const agent = Agent.create({ provider, model })
     *   .tool(search)
     *   .findings()
     *   .build();
     * await agent.run({ message: 'which port is down?' });
     * const ledger = agent.findings() ?? [];
     * const facts = ledger.filter((r) => r.kind === 'standing' && r.standing === 'fact');
     * ```
     */
    findings(options) {
        if (this.findingsValue !== undefined) {
            throw new Error('AgentBuilder.findings: already set. One findings ledger per agent — a second call ' +
                'would register the ask twice and leave two option sets with the later one silently ' +
                'winning. Pass every option in the one call.');
        }
        if (options !== undefined && (options === null || typeof options !== 'object')) {
            throw new Error(`AgentBuilder.findings: expected an options object or nothing, got ${typeof options}.`);
        }
        const maxContextBytes = (0, context_js_1.findingsContextBytes)(options?.maxContextBytes);
        const serve = options?.serve ?? 'ledger-and-facts';
        if (serve !== 'ledger-and-facts' && serve !== 'ledger-only') {
            throw new Error(`AgentBuilder.findings: serve must be 'ledger-and-facts' or 'ledger-only', got ` +
                `${JSON.stringify(serve)}.`);
        }
        const answerAsk = options?.answerAsk;
        if (answerAsk !== undefined && answerAsk !== 'none' && answerAsk !== 'quote-facts') {
            throw new Error(`AgentBuilder.findings: answerAsk must be 'none' or 'quote-facts', got ` +
                `${JSON.stringify(answerAsk)}.`);
        }
        const keep = options?.keepLedgerFacts;
        if (keep !== undefined && keep !== false && !(Number.isInteger(keep) && keep >= 0)) {
            throw new Error(`AgentBuilder.findings: keepLedgerFacts must be a non-negative integer or false, got ` +
                `${JSON.stringify(keep)}.`);
        }
        // The judge (9.104.0) is a port: refused at build unless it has the
        // port's two members, so a misconfigured classifier fails here rather
        // than as an error row on every tool result.
        const judge = options?.judge;
        if (judge !== undefined &&
            (judge === null ||
                typeof judge !== 'object' ||
                typeof judge.name !== 'string' ||
                judge.name === '' ||
                typeof judge.classify !== 'function')) {
            throw new Error('AgentBuilder.findings: judge must be a Classifier — `{ name: string; classify(request, ' +
                'signal?) }` from agentfootprint/classify (`typesafe()`, `mockClassifier()`, or your own).');
        }
        // Declared sources (honesty layer 2): a boolean, or nothing. It needs the
        // inputs layer, which only the whole agent can say is armed — `Agent`
        // refuses it at build when no ruled tool and no `.inputsLayer()` arm one.
        // (`.inputsLayer({ argumentSources: true })` arms the same thing without
        // this ledger.)
        const argumentSources = options?.argumentSources;
        if (argumentSources !== undefined && typeof argumentSources !== 'boolean') {
            throw new Error(`AgentBuilder.findings: argumentSources must be true or false, got ` +
                `${JSON.stringify(argumentSources)}.`);
        }
        // `answerAsk` is stored as given, never normalised: `'none'` is the
        // default and `Agent.ts` threads the dial only under `'quote-facts'`, so
        // an explicit `'none'` and an absent one reach the run as the same thing.
        this.findingsValue = {
            serve,
            maxContextBytes,
            ...(answerAsk !== undefined && { answerAsk }),
            ...(keep !== undefined && { keepLedgerFacts: keep }),
            ...(judge !== undefined && { judge }),
            ...(argumentSources === true && { argumentSources: true }),
        };
        // The always-on ask — the `outputSchema()` twin: a system-slot instruction
        // that activates every iteration, so a long run keeps the vocabulary
        // present, and a system PIECE on the receipt (never an injected turn, so
        // nothing in it is exempt from the evidence gate and nothing persists).
        this.injectionList.push((0, instruction_js_1.findingsInstruction)(false, answerAsk === 'quote-facts'));
        this.injectionSources.set(reserved_js_1.FINDINGS_INSTRUCTION_ID, this.currentSource());
        return this;
    }
    /**
     * Tool choice by classifier (9.105.0) — a SECOND READING of which tool
     * answers the current step, filed beside the model's own call, and
     * optionally a NARROWING of what is served.
     *
     * At every model call the tools slot asks `classifier` one `choice`
     * question over the tools it is about to serve — the merged wire minus the
     * always-served doors, each by its own description — and files a
     * `ToolChoiceRow` under `AgentState.toolChoices`: the provider's ranking
     * (its distribution as sent, an unscored tool absent), its pick, its
     * confidence, its cost, and the names actually served. After the reply
     * `callLLM` files a `ToolChoiceOutcomeRow`: the tools the model called,
     * whether the first is the pick (`firstAgrees`), and any `miss`. The
     * model's call is never overridden; a failed classifier call is a
     * `ToolChoiceErrorRow` and the full set is served (fail open).
     *
     * `serve: 'all'` (the default) is advisory: the wire is byte for byte what
     * it was. `serve: { top: N }` narrows the served list to the top-N plus the
     * doors (`read_skill`, `list_skills`, `skip_step`, `present`, and
     * `alwaysServe`), and serves the full wire with the reason on the row when
     * the classifier failed or scored fewer than N (`unavailable`), fewer than
     * N + 1 candidates were offered (`too-few`), the previous call missed
     * (`after-miss`) or the call is the wrap-up (`wrap-up`). A miss — the model
     * naming a narrowed-away tool — is recorded, answered by the dispatcher's
     * off-wire path as before, and the next call serves the full wire.
     *
     * Once per agent (a second call is refused, the `.findings()` grammar);
     * `reactMode: 'classic'` is refused at build. An agent that never calls
     * this is byte-identical to one built before the option existed: no
     * classifier call, no key, no event.
     *
     * @example
     * ```ts
     * import { mockClassifier } from 'agentfootprint/classify';
     *
     * const agent = Agent.create({ provider, model })
     *   .tool(lookup)
     *   .tool(charge)
     *   .toolChoice({ classifier: typesafe(), serve: { top: 2 } })
     *   .build();
     * await agent.run({ message: 'refund order 42' });
     * agent.getSnapshot()?.sharedState.toolChoices; // pick → served → called, per call
     * ```
     */
    toolChoice(options) {
        if (this.toolChoiceValue !== undefined) {
            throw new Error('AgentBuilder.toolChoice: already set. One classifier per agent — a second call would ' +
                'leave two option sets with the later one silently winning. Pass every option in the ' +
                'one call.');
        }
        if (options === null || typeof options !== 'object') {
            throw new Error(`AgentBuilder.toolChoice: expected an options object, got ${typeof options}.`);
        }
        const classifier = options.classifier;
        if (classifier === null ||
            typeof classifier !== 'object' ||
            typeof classifier.name !== 'string' ||
            classifier.name === '' ||
            typeof classifier.classify !== 'function') {
            throw new Error('AgentBuilder.toolChoice: classifier must be a Classifier — `{ name: string; ' +
                'classify(request, signal?) }` from agentfootprint/classify (`typesafe()`, ' +
                '`mockClassifier()`, or your own).');
        }
        const serve = options.serve ?? 'all';
        if (serve !== 'all') {
            const top = serve !== null && typeof serve === 'object' ? serve.top : undefined;
            if (!(Number.isInteger(top) && top >= 1)) {
                throw new Error(`AgentBuilder.toolChoice: serve must be 'all' or { top: <integer ≥ 1> }, got ` +
                    `${JSON.stringify(serve)}.`);
            }
        }
        const alwaysServe = options.alwaysServe;
        if (alwaysServe !== undefined &&
            (!Array.isArray(alwaysServe) || alwaysServe.some((n) => typeof n !== 'string' || n === ''))) {
            throw new Error('AgentBuilder.toolChoice: alwaysServe must be an array of non-empty tool names, got ' +
                `${JSON.stringify(alwaysServe)}.`);
        }
        this.toolChoiceValue = {
            classifier,
            serve,
            ...(alwaysServe !== undefined && { alwaysServe: [...alwaysServe] }),
        };
        return this;
    }
    /**
     * The declared ontology (9.106.0) — a MAP of what exists and where, never
     * a way to fetch it.
     *
     * WHY. When a tool result does not hold what a question needs, the model
     * has nothing to say about where the need WOULD be met — it guesses a
     * source, or reports the absence as if nothing anywhere held the term. An
     * ontology lets the application declare, once, what each term IS (its
     * meaning and unit — the context contract's `domainDefinitions`), how
     * terms RELATE, which SOURCE holds a term with the author's own coverage
     * sentence, and which registered TOOL reads it from there. The model is
     * served that map as data on every call and asked to name the source, the
     * tool or the neighbouring node a need points at — as a proposal. The
     * library executes nothing through the map, fetches nothing, and never
     * decides that a node "has no data": a node with no declared source is
     * served as `known, not held here`, which is what the declaration says.
     *
     * WHAT IT DOES. Takes the frozen `Ontology` `defineOntology` returned
     * (`agentfootprint/ontology`); at `.build()` every tool name a node's `via`
     * names is checked against the agent's tool registry and a name no
     * registry carries is refused, naming the ontology and the tool. At run
     * `seed` writes the whole spec once as the run constant
     * `AgentState.ontology`, every model call is served ONE request-only
     * system piece composed from that key (`ontology/serve.ts ·
     * ontologyPiece`, joined after the recovery piece and before the findings
     * piece, hashed on the receipt, rebuilt byte-equal by `servedAt`) and
     * `agentfootprint.ontology.served` fires per call with identities and
     * counts only. Registers the always-on `ontology` instruction
     * (`ONTOLOGY_INSTRUCTION`, the `outputSchema()` twin: a system piece on
     * the receipt, never an injected turn).
     *
     * Once per agent (a second call is refused, the `.findings()` grammar). An
     * agent that never calls this is byte-identical to one built before the
     * map existed: no key, no piece, no instruction, no event.
     *
     * `options.ask` (9.107.0, the `answerAsk` grammar — a named value, never a
     * boolean): `'use-the-map'` (the default) registers the versioned
     * `ONTOLOGY_INSTRUCTION` — read the question in the map's terms, look where
     * a source and its tool are declared, walk a relation from a term held to
     * a term needed, and when a need is unmet name where it would be met,
     * never a value off a definition, never the map itself to the person.
     * `'none'` serves the map as data and registers no ask of the library's,
     * for an application that writes its own through `.instruction()` — the
     * map is served either way; only the ask changes, and the served view and
     * the receipt show which (the `ontology` injection present or absent).
     *
     * @example
     * ```ts
     * import { defineOntology } from 'agentfootprint/ontology';
     *
     * const map = defineOntology({
     *   id: 'fleet',
     *   version: '1',
     *   sources: { inventory: { meaning: 'the switch inventory export', configured: true } },
     *   nodes: {
     *     port: { meaning: 'a physical switch port', sources: [{ source: 'inventory', via: ['lookup_port'] }] },
     *     port_error_rate: { meaning: 'CRC errors per minute on a port', unit: 'errors/min' },
     *   },
     *   edges: [{ from: 'port_error_rate', to: 'port', relation: 'measured-on' }],
     * });
     * const agent = Agent.create({ provider, model }).tool(lookupPort).ontology(map).build();
     * ```
     */
    ontology(ontology, options) {
        const ask = options?.ask;
        if (ask !== undefined && ask !== 'none' && ask !== 'use-the-map') {
            throw new Error(`AgentBuilder.ontology: ask must be 'none' or 'use-the-map', got ${JSON.stringify(ask)}.`);
        }
        if (this.ontologyValue !== undefined) {
            throw new Error('AgentBuilder.ontology: already set. One map per agent — a second call would register ' +
                'the ask twice and leave two maps with the later one silently winning. Compose one ' +
                'declaration and pass it once.');
        }
        if (ontology === null ||
            typeof ontology !== 'object' ||
            typeof ontology.id !== 'string' ||
            typeof ontology.version !== 'string' ||
            typeof ontology.hash !== 'string' ||
            ontology.hash.length === 0 ||
            !Object.isFrozen(ontology) ||
            ontology.nodes === null ||
            typeof ontology.nodes !== 'object' ||
            ontology.sources === null ||
            typeof ontology.sources !== 'object' ||
            !Array.isArray(ontology.edges)) {
            throw new Error('AgentBuilder.ontology: expected the frozen Ontology `defineOntology(spec)` returns ' +
                '(agentfootprint/ontology), not a bare spec — the definition is where the declaration ' +
                'is validated and fingerprinted.');
        }
        this.ontologyValue = ontology;
        // `'none'`: the map as data, no ask of the library's — the application
        // registers its own through `.instruction()`. Nothing else differs.
        if (ask === 'none')
            return this;
        // The always-on ask — the `outputSchema()` twin: a system-slot instruction
        // that activates every iteration, so a long run keeps the vocabulary
        // present, and a system PIECE on the receipt (never an injected turn).
        this.injectionList.push((0, defineInstruction_js_1.defineInstruction)({
            id: instruction_js_2.ONTOLOGY_INSTRUCTION_ID,
            activeWhen: () => true,
            prompt: instruction_js_2.ONTOLOGY_INSTRUCTION,
        }));
        this.injectionSources.set(instruction_js_2.ONTOLOGY_INSTRUCTION_ID, this.currentSource());
        return this;
    }
    /**
     * 3-tier degradation for output-schema validation failures. Pairs
     * with `.outputSchema()` — an agent that has one and not the other is
     * refused at `.build()`, in either call order.
     *
     * Three tiers:
     *
     *   1. **Primary** — LLM emitted schema-valid JSON. Caller gets it.
     *   2. **Fallback** — `OutputSchemaError` thrown. The async
     *      `fallback(error, raw)` runs; its return is re-validated.
     *   3. **Canned** — static safety-net value. NEVER throws when set.
     *
     * `canned` is validated against the schema at `.build()` — fail-fast on
     * misconfig (a `canned` that doesn't validate would defeat the fail-open
     * guarantee at the exact moment it is needed).
     *
     * ## The tiers run at the TYPED boundary — `run()` does not reach them
     *
     * `runTyped()` and `parseOutputAsync()` engage the chain. **`run()` does
     * not**, and cannot: these tiers produce a typed value `T`, and `run()`
     * resolves to the raw answer string — substituting a fallback there would
     * hand a caller a different answer than the model gave, invisibly. So an
     * agent consumed through `run()` (a server route, a queue worker,
     * `standingAgent`) gets NO fallback, and until 8.18.0 nothing said so. Now
     * the unmet-contract warning and
     * `agentfootprint.agent.output_contract_unmet` both carry
     * `fallbackConfigured: true` — the signal that a safety net exists and this
     * caller is not standing under it.
     *
     * Two typed events fire on tier transitions for observability:
     *   - `agentfootprint.resilience.output_fallback_triggered`
     *   - `agentfootprint.resilience.output_canned_used` — carries
     *     `retriesSpent`, and warns when the canned value lands after re-asks
     *     that were billed. With `canned` set, `runTyped()` is structurally
     *     unable to throw, so nothing else would report that spend.
     *
     * @example
     * ```ts
     * import { z } from 'zod';
     * const Refund = z.object({ amount: z.number(), reason: z.string() });
     *
     * const agent = Agent.create({...})
     *   .outputSchema(Refund)
     *   .outputFallback({
     *     fallback: async (err, raw) => ({ amount: 0, reason: 'manual review' }),
     *     canned:   { amount: 0, reason: 'unable to process' },
     *   })
     *   .build();
     * ```
     */
    outputFallback(options) {
        if (this.outputFallbackCfg) {
            throw new Error('AgentBuilder.outputFallback: already set. Each agent has at most one fallback chain.');
        }
        this.outputFallbackCfg = {
            fallback: options.fallback,
            ...(options.canned !== undefined && { canned: options.canned }),
            hasCanned: options.canned !== undefined,
        };
        // The coherence check moved to `build()` in 8.18.0 — see
        // `assertOutputFallbackCoherent`. What it needs (a parser) is a fact about
        // the FINISHED agent, and the builder is a bag of settings in whatever
        // order they were written.
        return this;
    }
    /**
     * `.outputFallback()` needs `.outputSchema()`, and it needs it by the time
     * the agent exists — not by the time the call is written (8.18.0).
     *
     * The requirement is set MEMBERSHIP: a fallback is degradation for a
     * contract, so an agent with one and not the other is incoherent. Order is
     * not the requirement, and refusing on order made a builder whose lines
     * could not be reordered without reading an error message to find out —
     * `.outputFallback().outputSchema()` threw while `.outputSchema()
     * .outputFallback()` was fine, and both end with the same agent.
     *
     * The `canned` value is validated here for the same reason: validating it
     * needs the parser, so it belongs wherever the parser is guaranteed.
     */
    assertOutputFallbackCoherent() {
        const cfg = this.outputFallbackCfg;
        if (cfg === undefined)
            return;
        if (!this.outputSchemaParser) {
            throw new Error('AgentBuilder.build: .outputFallback() is configured but .outputSchema(parser) is not. ' +
                'A fallback is what happens when the terminal contract is not met, so without a ' +
                'contract there is nothing for it to catch and its tiers can never run. Add ' +
                '.outputSchema(parser) — in either order, they are one setting made of two calls — ' +
                'or drop the fallback.');
        }
        // The safety net must always validate; a `canned` value that does not is a
        // misconfiguration that would only surface AFTER the agent loop had
        // already failed, which is the one moment it exists to survive.
        if (cfg.hasCanned) {
            (0, outputFallback_js_1.validateCannedAgainstSchema)(cfg.canned, this.outputSchemaParser);
        }
    }
    /**
     * Wire rules-based reliability around every `CallLLM` execution.
     * The framework wraps the LLM call in a retry/fallback/fail-fast
     * loop driven by `preCheck` and `postDecide` rules.
     *
     * Decision verbs the rules can emit (see `ReliabilityDecision` for
     * the full list):
     *
     *   • `continue`    — pre-check OK, proceed to the call
     *   • `ok`          — post-call OK, commit and return
     *   • `retry`       — re-call same provider (bumps `attempt`)
     *   • `retry-other` — advance to next provider in `providers[]`
     *   • `fallback`    — invoke `config.fallback(req, lastError)`
     *   • `fail-fast`   — throw `ReliabilityFailFastError` at `agent.run()`
     *
     * **Streaming + reliability semantics — first-chunk arbitration:**
     * Pre-first-chunk failures (connection/headers/breaker-open) honor
     * the full rule set (retry, retry-other, fallback, fail-fast).
     * Post-first-chunk failures (mid-stream) honor only `ok` and
     * `fail-fast`; rules wanting `retry`/`retry-other`/`fallback` are
     * escalated to fail-fast with kind `'mid-stream-not-retryable'`.
     * This matches LangChain's `RunnableWithFallbacks` pattern and
     * the prevailing industry default — see the streaming + reliability
     * design memo for the full discussion.
     *
     * Throws if called more than once on the same builder.
     *
     * @example
     *   import { Agent } from 'agentfootprint';
     *   import { ReliabilityFailFastError } from 'agentfootprint/resilience';
     *
     *   const agent = Agent.create({ provider, model: 'mock' })
     *     .system('Triage support tickets.')
     *     .reliability({
     *       postDecide: [
     *         { when: (s) => s.errorKind === '5xx-transient' && s.attempt < 3,
     *           then: 'retry', kind: 'transient-retry' },
     *         { when: (s) => s.error !== undefined,
     *           then: 'fail-fast', kind: 'unrecoverable' },
     *       ],
     *       circuitBreaker: { failureThreshold: 3 },
     *     })
     *     .build();
     *
     *   try {
     *     await agent.run({ message: 'help' });
     *   } catch (e) {
     *     if (e instanceof ReliabilityFailFastError) {
     *       console.log(e.kind, e.reason);
     *     }
     *   }
     */
    reliability(config) {
        if (this.reliabilityConfig) {
            throw new Error('AgentBuilder.reliability: already set. Each agent has at most one reliability config.');
        }
        this.reliabilityConfig = config;
        return this;
    }
    /**
     * Wire a thinking handler (v2.14+). Omitted, the agent uses the handler
     * the provider declares (`LLMProvider.thinkingHandler` — `anthropic()`
     * declares one, and a wrapper that forwards the field keeps it). Pass a
     * handler to override it, or `null` to mount no thinking stage at all.
     * Calling twice throws.
     *
     * @example
     *   // Custom handler that redacts thinking content
     *   Agent.create({...}).thinkingHandler(myRedactingHandler).build();
     *
     * @example
     *   // Opt out of thinking parsing entirely
     *   Agent.create({ provider: anthropic({...}), model: '...' })
     *     .thinkingHandler(null)
     *     .build();
     */
    thinkingHandler(handler) {
        if (this.thinkingHandlerValue !== undefined) {
            throw new Error('AgentBuilder.thinkingHandler: already set. Each agent has at most one thinking-handler choice.');
        }
        this.thinkingHandlerValue = handler;
        return this;
    }
    /**
     * v2.14+ — REQUEST-side thinking activation. Tells the provider to
     * emit reasoning blocks alongside its response.
     *
     * **What this does:** every LLM call carries
     * `LLMRequest.thinking = { budget }` — the intent. The adapter sends the
     * shape the MODEL takes, from what it declares per model
     * (`LLMProvider.thinkingMode`):
     *   - `'budget'` (Claude 4.6 and earlier): `thinking: { type: 'enabled',
     *     budget_tokens: budget }` — a target for the thinking, not a hard cap
     *     (`max_tokens` is the cap);
     *   - `'adaptive'` (Claude 4.7 and later, Mythos Preview, and any id the
     *     table does not know): `thinking: { type: 'adaptive', display:
     *     'summarized' }` — the model decides whether and how much to think and
     *     the budget is NOT sent (dev mode says so once per model); it only
     *     keeps `max_tokens` above it. An adapter's `thinkingMode` option
     *     changes the answer for one id (an alias, a profile ARN, or Opus 4.6
     *     wanted adaptive);
     *   - `'none'` (Claude 3.x except 3.7 Sonnet): refused here at `build()`
     *     with an `UnsupportedThinkingError`.
     *
     * **Distinct from `.thinkingHandler()`:**
     *   - `.thinking({ budget })` = ASK the model to think (request side)
     *   - `.thinkingHandler(h)`   = NORMALIZE the response (response side)
     *
     * Most consumers want both; auto-wired handler covers the response
     * side automatically when `.thinking()` is set on a thinking-capable
     * provider. Setting `.thinking()` without `.thinkingHandler(null)`
     * is the typical happy path.
     *
     * **Provider compatibility:**
     *   - Anthropic: per model, as above. A budget model needs a whole
     *     `budget` of at least 1024, and no thinking model takes a
     *     `temperature` other than the default — both are refused with an
     *     `UnsupportedThinkingError` before the request is sent.
     *   - OpenAI: ignores. o1/o3 reasoning is selected at the model id
     *     level; this field is a no-op for OpenAIProvider.
     *
     * **`max_tokens`:** thinking counts toward it. When the request's
     * `maxTokens` (default 4096 on the Anthropic adapters) is not above the
     * budget, the adapter raises it to `budget + 1024`.
     *
     * Calling twice throws — same shape as `.reliability()` /
     * `.outputSchema()`.
     *
     * @example
     *   Agent.create({ provider: anthropic({...}), model: 'claude-opus-5-5' })
     *     .system('You are a careful reasoning agent.')
     *     .thinking({ budget: 5000 })   // adaptive on Opus 5.5; budget_tokens on Haiku 4.5
     *     .build();
     */
    thinking(opts) {
        if (this.thinkingBudgetValue !== undefined) {
            throw new Error('AgentBuilder.thinking: already set. Each agent has at most one thinking-budget choice.');
        }
        if (!Number.isFinite(opts.budget) || opts.budget <= 0) {
            throw new Error(`AgentBuilder.thinking: budget must be a positive finite number, got ${String(opts.budget)}.`);
        }
        this.thinkingBudgetValue = opts.budget;
        return this;
    }
    /**
     * Let this agent answer why-questions about its OWN previous completed
     * turn, from its recorded trace. Mounts one skill: day to day the tool
     * catalog carries only the skill's activation row; when the user asks
     * "why did you…", the LLM activates it and that iteration alone gets
     * the trace tools (inline mode) or a single `explain_run` tool that
     * runs a nested trace debugger on a cheaper model (delegate mode).
     *
     * Evidence binds LATE — always to the previous COMPLETED run, never
     * the in-flight one — and includes control edges (a per-run
     * control-dependence recorder is attached automatically).
     *
     * @example
     *   Agent.create({ provider, model })
     *     .system('You are a refunds assistant.')
     *     .tool(lookupOrder)
     *     .selfExplain()                                 // inline, zero config
     *     .build();
     *
     * @example
     *   .selfExplain({ delegate: { provider: anthropic(), model: 'claude-haiku-4-5' } })
     */
    /**
     * Configure the evidence pack that rides a check-in ask. A tool DEMANDS a
     * check-in by declaring `checkIn: 'always'` or a `(args, ctx) => boolean`
     * predicate ({@link defineTool}); this method controls WHAT the human sees
     * when it trips. Optional — a tool with `checkIn` works without it (default:
     * `'standard'` evidence + the deterministic lexical scorer, zero LLM calls).
     *
     * - `evidence: 'standard'` (default) — the full pack: `willDo` (plain-words
     *   claim), `read` (what context the run consumed), `drivers` (which context
     *   drove the pick, ranked), `trail` (compact run-so-far summary).
     * - `evidence: 'minimal'` — just `willDo` (zero cost).
     * - `evidence: <assembler>` — bring your own {@link CheckInAssembler}.
     * - `scorer` — swap the `drivers` ranker (default is zero-LLM lexical; pass
     *   an embedding-backed one wrapping `explainChoice` for semantic ranking).
     *
     * @example
     *   Agent.create({ provider, model })
     *     .tool(defineTool({ name: 'issue_refund', description: 'Refund a charge',
     *       inputSchema: { type: 'object', properties: { amount: { type: 'number' } } },
     *       checkIn: (args) => (args.amount as number) > 1000,   // ask only for big refunds
     *       execute: async ({ amount }) => `refunded ${amount}` }))
     *     .checkIn({ evidence: 'standard' })
     *     .build();
     */
    /**
     * Wrap every tool dispatch in a governance chain.
     *
     * Each middleware answers with one of three verbs — `allow()`, `deny(reason)`
     * or `ask({ question })` — and there is deliberately no fourth. In
     * particular there is no way to return a result: whatever the chain decides,
     * the answer the model finally reads is the real tool's output or a refusal.
     * A rule cannot quietly become the tool.
     *
     * - **`allow()`** passes the call through. **`allow(args, why)`** replaces
     *   the args and the run commits BOTH versions with your `why` beside them,
     *   so a slice taken later can find the moment they changed and who changed
     *   them.
     * - **`deny(reason)`** refuses. The reason reaches the model verbatim, as
     *   the tool result, and the loop continues — the agent adapts in-flight. A
     *   denial is data, not a crash.
     * - **`ask({ question })`** suspends the run for a person, on the same
     *   checkpoint machinery `checkIn` and `askHuman` use. The answer is a
     *   DECISION, not a result: approve and the chain resumes and the real tool
     *   runs; decline and it becomes a denial the model reads.
     *
     * Order is call order, and each middleware sees the previous one's output.
     * The first non-allow answer wins and the rest of the chain does not run. A
     * middleware that throws is a denial carrying the error as its reason —
     * never a silent pass.
     *
     * A link may also carry an **`onToolResult`** hook, which decides about the
     * RESULT once the tool has run and before the model reads it — `allow()`,
     * `allow(value, why)` or `deny(reason)`, and no `ask`, because the tool has
     * already run and there is nothing left for a person to prevent. That half
     * of the chain is walked BACKWARDS, so the first-declared rule has the first
     * word about the call and the last word about the answer. A link with only
     * `onToolResult` takes no part in dispatch at all.
     *
     * `.act({ beforeTool, afterTool })` is the same chain, named by moment.
     *
     * An existing `PermissionChecker` still decides FIRST: it is not part of
     * this chain, it runs ahead of it, so a call it denies never reaches a
     * middleware. `gatedTools` is a different layer again — it decides which
     * tools the model can SEE; this decides what happens when one is called.
     *
     * Omit this and nothing changes: no chain walk, no committed ledger key, the
     * same request bytes.
     *
     * @example
     * ```ts
     * import { Agent, allow, deny } from 'agentfootprint';
     *
     * const agent = Agent.create({ provider, model })
     *   .toolMiddleware({
     *     name: 'no-prod-writes',
     *     onToolCall: (call) =>
     *       call.args.env === 'prod' ? deny('writes to prod need a change ticket') : allow(),
     *   })
     *   .build();
     * ```
     */
    toolMiddleware(...middleware) {
        for (const mw of middleware)
            this.assertToolMiddleware(mw);
        this.toolMiddlewareList = [...this.toolMiddlewareList, ...middleware];
        return this;
    }
    /**
     * A tool link needs a name and at least one hook. Both are optional
     * INDIVIDUALLY — a rule about calls, a rule about results, or both — but a
     * link with neither is a governance rule that can never run, and finding
     * that out from a quiet run rather than from `build()` is the whole disease.
     */
    assertToolMiddleware(mw) {
        const shaped = mw !== null &&
            typeof mw === 'object' &&
            typeof mw.name === 'string' &&
            mw.name.length > 0;
        const record = mw;
        const hooks = shaped &&
            (typeof record?.onToolCall === 'function' || typeof record?.onToolResult === 'function');
        if (!hooks) {
            throw new Error(`AgentBuilder.toolMiddleware: expected an object with a non-empty \`name\` and at ` +
                `least one of \`onToolCall(call)\` (decides about the call) or ` +
                `\`onToolResult(call)\` ` +
                `(decides about the result), got ${typeof mw}. The name is what every ledger row ` +
                `and event says decided the call, so it cannot be blank.`);
        }
    }
    /**
     * Wrap the message boundary in a governance chain — the input before the
     * model sees it, the output before the caller receives it.
     *
     * Same verbs as {@link toolMiddleware} minus one: there is no `ask` here,
     * and the type says so. Tool dispatch runs inside a pausable stage, so it
     * has a checkpoint to suspend on; the message boundary is a plain stage, and
     * inventing a second pause to give it one would be a worse answer than not
     * offering it.
     *
     * The `'input'` half runs at the very top of the run, BEFORE the message is
     * committed. That placement is the point: everything downstream reads
     * `scope.history` — the window strategies, the injections, all three slots,
     * the bytes on the wire and every slice taken afterwards — so the
     * transformed text is what the whole run agrees was said. The `'output'`
     * half runs before final capture. Configuring this door withholds provider
     * draft tokens and `stream.llm_end.content` (marked `contentWithheld: true`).
     * An accepted run emits its captured final answer once; a refused output
     * emits no answer or `turn_end` and makes no final memory write. The API
     * boundary emits `error.fatal` so public streams can close.
     *
     * This door may inspect both phases. For input-only rules with immediate
     * draft streaming, declare `.act({ input: [rule] })`. Output policy is not
     * audit erasure: raw model content and rewrite evidence remain in the run.
     * Coverage/standing text is composed after the output rule; policy approval
     * is not answer validation.
     *
     * `deny(reason)` at either phase raises a `MessageDeniedError` rather than
     * returning. At `'input'` there is no model to tell; at `'output'` the
     * middleware has just refused to release an answer, and handing the caller a
     * string in its place is the one substitution they must never make without
     * noticing. The error carries the reason, the phase and the middleware's
     * name — never the refused content.
     *
     * @example
     * ```ts
     * import { Agent, allow } from 'agentfootprint';
     *
     * const agent = Agent.create({ provider, model })
     *   .messageMiddleware({
     *     name: 'mask-card-numbers',
     *     onMessage: (msg) => {
     *       const clean = msg.content.replace(/\b(?:\d[ -]?){13,16}\b/g, '[card]');
     *       return clean === msg.content ? allow() : allow(clean, 'masked a card number');
     *     },
     *   })
     *   .build();
     * ```
     */
    messageMiddleware(...middleware) {
        for (const mw of middleware)
            this.assertMiddleware(mw, 'messageMiddleware', 'onMessage');
        this.messageMiddlewareList = [...this.messageMiddlewareList, ...middleware];
        return this;
    }
    /** Shared shape check — a chain built from a typo fails at build time, not
     *  as a silent no-op on the one call that needed governing. */
    assertMiddleware(mw, method, hook) {
        if (mw === null ||
            typeof mw !== 'object' ||
            typeof mw.name !== 'string' ||
            mw.name.length === 0 ||
            typeof mw[hook] !== 'function') {
            throw new Error(`AgentBuilder.${method}: expected an object with a non-empty \`name\` and a ` +
                `\`${hook}(...)\` method, got ${typeof mw}. The name is what every ledger row and ` +
                `event says decided the call, so it cannot be blank.`);
        }
    }
    checkIn(opts = {}) {
        if (this.checkInConfig !== undefined) {
            throw new Error('AgentBuilder.checkIn: already configured. Call it at most once.');
        }
        this.checkInConfig = opts;
        return this;
    }
    selfExplain(opts = {}) {
        if (this.selfExplainConfig !== undefined) {
            throw new Error('AgentBuilder.selfExplain: already enabled.');
        }
        if (this.opts.reactMode === 'classic') {
            // Classic ReAct caches the tools slot on turn 1 — a mid-turn skill
            // activation could never surface the trace tools. Fail loud at
            // build-time rather than silently never answering why-questions.
            throw new Error('AgentBuilder.selfExplain: requires per-iteration slot recomposition — ' +
                "reactMode 'classic' caches the tools slot, so the activated trace tools " +
                "would never reach the model. Use the default 'dynamic' mode (or " +
                "'dynamic-grouped'), or use traceDebugAgent() as a separate session instead.");
        }
        this.selfExplainConfig = opts;
        return this;
    }
    /**
     * Resolve what the loop will enforce about the output — or `undefined` when
     * nothing is mounted, which is the default and the byte-identical path.
     *
     * Both refusals live here rather than in `.outputSchema()` because both
     * depend on the WHOLE agent: the tools are registered by other calls that
     * may come after, and `.selfExplain()` attaches its own.
     */
    /**
     * Resolve `.maps()` into the kernel's plan (9.58.0). Undefined when the
     * kernel is not mounted — the zero-delta path. Refuses loudly when there
     * is nothing to manage: the kernel owns engagement, and with no map
     * mounted there is nothing to engage or park.
     */
    resolveMapsPlan(injections) {
        if (this.mapsOptions === undefined)
            return undefined;
        if (this.skillGraphNextSkill === undefined) {
            throw new Error(`Agent.maps(): nothing is mounted that the kernel could manage — mount a skill ` +
                `map first (.skillGraph(defineSkillMap(...).build())). The kernel owns ` +
                `ENGAGEMENT (whether a map's prompt and tools ride the next call); with no ` +
                `map mounted there is nothing to engage or park.`);
        }
        if (this.skillGraphNodeIds === undefined || this.skillGraphNodeIds.size === 0) {
            throw new Error(`Agent.maps(): the mounted graph carries no node ids (a structurally-typed ` +
                `graph). The kernel needs the member list to know exactly which contributions ` +
                `parking suppresses — mount the full defineSkillMap(...).build() result.`);
        }
        // ── A graph that cannot explain its moves (9.59.0) ─────────────────
        // The kernel judges an engagement by WHY the cursor is where it is. With
        // no `explainNextSkill` no clause ever arrives, so every pass founds
        // `assumed` — nobody said why — and the kernel is reduced to a timer over
        // a fact it never learns. Before the `assumed` rung existed this was
        // worse and silent: an absent clause was written down as `structural`,
        // the strongest NON-DECAYING category, so such a graph could never park
        // anything at all, with no event and no warning.
        //
        // Refused rather than warned. Mounting a kernel whose whole job is to
        // weigh evidence, onto a map that produces none, is decoration — and the
        // refusal is reachable only for a hand-written structurally-typed graph
        // (every `skillGraph().build()` supplies the hook), so nothing that works
        // today starts failing.
        if (this.skillGraphExplainNextSkill === undefined) {
            throw new Error(`Agent.maps(): the mounted graph cannot explain its cursor moves — it carries no ` +
                `\`explainNextSkill\`. The kernel decides engagement by WHY the cursor is where ` +
                `it is (a declared route is system evidence; a keyword match is a guess), so ` +
                `without that clause every pass reads "nobody said why" and the kernel is a ` +
                `timer over a fact it never learns. Build the map with this version's ` +
                `skillGraph() (every graph it builds supplies the clause), or drop .maps().`);
        }
        const memberIds = [...this.skillGraphNodeIds];
        const memberSet = new Set(memberIds);
        // The renewal feed's join key: tool NAMES contributed by this map's
        // members. Names, not references — the feed joins against the previous
        // batch's tool results, which carry names.
        const toolNames = [
            ...new Set(injections
                .filter((inj) => memberSet.has(inj.id))
                .flatMap((inj) => (inj.inject.tools ?? []).map((t) => t.schema.name))),
        ];
        return {
            maps: [
                {
                    id: 'skill-map',
                    memberIds,
                    toolNames,
                    // MANDATORY mount (9.59.0) — documented as shipped since the kernel
                    // landed and, until now, unreachable through the mount path: the
                    // kernel's data model carried `nonParkable` per map and no builder
                    // option ever set it. Value-conditional, so an agent that does not
                    // ask for it hands the kernel the exact object it always did.
                    ...(this.mapsOptions.nonParkable === true && { nonParkable: true }),
                },
            ],
            renewalGrace: this.mapsOptions.renewalGrace ?? 3,
        };
    }
    /**
     * Validate a JSON answer against host-owned evidence before delivering it.
     * Requires outputSchema. Enforce (default) refuses failed or unverified
     * checks; observe returns the answer with a recorded verdict. Both modes
     * withhold draft tokens until this boundary. The callback cannot rewrite
     * the answer; the schema's JSON-safe output is serialized once for delivery.
     * `.outputFallback()` and `.limitsTravelWithTheAnswer()` are refused beside it
     * in this version.
     *
     * `agentChecks: ['claims', 'names-and-numbers']` makes the agent's own
     * `.claims()` and `.namesAndNumbersFromEvidence()` verdicts rows of the same
     * report, so a contradicted field or an invented value is withheld rather
     * than only recorded (see {@link AnswerValidationOptions.agentChecks}).
     */
    answerValidation(options) {
        if (this.answerValidationConfig !== undefined) {
            throw new Error('AgentBuilder.answerValidation: already configured.');
        }
        this.answerValidationConfig = (0, index_js_1.resolveAnswerValidation)(options);
        return this;
    }
    /**
     * Observe ordinary prose with `{ prose: true }`, preserving the answer and live
     * streaming. Warnings never confirm or block a claim. No schema or tool list is
     * required; optional `phrases` extends the grammar with bounded literal data.
     * `.outputFallback()` is unsupported because it substitutes text after observation.
     *
     * Or deliver typed negative claims as library-written statements of the searches
     * they cite. `field` is a required top-level array in `.outputSchema()`'s JSON
     * Schema; entries contain only `kind`, required `about`, and `restsOn` handles.
     * The output adds `statement`, `delivery: 'record-composed'` and `version`.
     *
     * An unestablished citation withholds the answer by default. `state-searched`
     * delivers what was searched while retaining its failed or unverified standing.
     * Bounded prose observation files warnings; other answer text remains unchecked.
     * The structured path is unmeasured: typed enforcement is available, not recommended
     * until its benchmark gates pass. Declaration order with `.outputSchema()` does
     * not matter; the complete contract is checked at build.
     *
     * @example
     * ```ts
     * builder.negativeClaims({ field: 'negatives', tools: ['list_incidents'] });
     * ```
     */
    negativeClaims(options) {
        if (this.negativeClaimsConfig !== undefined) {
            throw new Error('Agent.negativeClaims: already configured.');
        }
        this.negativeClaimsConfig = (0, config_js_1.resolveNegativeClaims)(options);
        return this;
    }
    resolveOutputEnforcement(servedSchema) {
        const parser = this.outputSchemaParser;
        if (!parser)
            return undefined;
        let schemaTool;
        if (this.outputSchemaStrategy === 'tool-forced') {
            // Refusal 1 — the strategy and the agent's tools cannot both be true.
            // Forcing the choice BY NAME is what makes the shape a guarantee, and
            // it also means no other tool can be called on any turn: a tool-using
            // agent would quietly become single-shot. That is config that lies in
            // the other direction, so it is refused rather than allowed to happen.
            const toolNames = this.registry.map((entry) => entry.name);
            const skillCount = this.injectionList.filter((i) => i.flavor === 'skill').length;
            const reasons = [];
            if (toolNames.length > 0)
                reasons.push(`registers ${toolNames.length} tool(s) (${toolNames.join(', ')})`);
            if (this.toolProviderRef !== undefined)
                reasons.push('has a .toolProvider()');
            if (skillCount > 0)
                reasons.push(`has ${skillCount} skill(s), which reach the model as tools`);
            if (this.selfExplainConfig !== undefined)
                reasons.push('has .selfExplain(), which attaches trace tools');
            if (reasons.length > 0) {
                throw new Error(`AgentBuilder.outputSchema: strategy 'tool-forced' forces every answer through ` +
                    `the '${outputEnforcement_js_1.SCHEMA_TOOL_NAME}' tool by name, so NO other tool can be called on any ` +
                    `turn — and this agent ${reasons.join('; ')}. Rather than let those tools go ` +
                    `silently unusable, two honest paths: drop the tools and keep the forced shape, ` +
                    `or keep the tools and use { strategy: 'instruct', retries: N } — the corrective ` +
                    `loop works on every provider and leaves the agent an agent.`);
            }
            // Refusal 2 — a tool needs its shape, and the library will not invent one.
            const jsonSchema = (0, outputEnforcement_js_1.resolveJsonSchema)(parser, servedSchema ?? this.outputSchemaJson);
            if (jsonSchema === undefined) {
                throw new Error(`AgentBuilder.outputSchema: strategy 'tool-forced' puts the schema on the wire as ` +
                    `a tool, and a tool carries its shape as JSON Schema. This parser does not offer ` +
                    `one (no \`toJsonSchema()\` method), and a \`parse()\` function is not something ` +
                    `the library gets to guess a shape from. Pass it: .outputSchema(parser, ` +
                    `{ strategy: 'tool-forced', jsonSchema: { type: 'object', properties: { … } } }).`);
            }
            schemaTool = (0, outputEnforcement_js_1.buildSchemaTool)(jsonSchema, parser.description);
        }
        // `'native'` — the provider's structured output. No tools refusal: the
        // format constrains the TEXT answer only, so the model may still call a
        // tool first and the agent stays an agent. The shape comes from the same
        // place, and is refused the same way when there is none; what the API
        // cannot express is refused by `nativeOutputFormat`, naming the node.
        let outputFormat;
        if (this.outputSchemaStrategy === 'native' || this.outputSchemaStrategy === 'auto') {
            const jsonSchema = (0, outputEnforcement_js_1.resolveJsonSchema)(parser, servedSchema ?? this.outputSchemaJson);
            if (jsonSchema === undefined && this.outputSchemaStrategy === 'native') {
                throw new Error(`AgentBuilder.outputSchema: strategy 'native' sends the schema to the provider as ` +
                    `JSON Schema (Anthropic's output_config.format). This parser does not offer one (no ` +
                    `\`toJsonSchema()\` method), and a \`parse()\` function is not something the library ` +
                    `gets to guess a shape from. Pass it: .outputSchema(parser, { strategy: 'native', ` +
                    `jsonSchema: { type: 'object', properties: { … } } }).`);
            }
            // The answer may carry `_findings` when the ledger is armed — from
            // either door, the same test `Agent` arms it by.
            const findingsArmed = this.findingsValue !== undefined ||
                this.opts.findings !== undefined;
            if (jsonSchema !== undefined) {
                try {
                    outputFormat = (0, nativeOutputFormat_js_1.nativeOutputFormat)(jsonSchema, { findings: findingsArmed });
                }
                catch (error) {
                    // Auto is best available transport, not a stricter schema contract.
                    // Known wire-subset refusals retain instruction + local parsing.
                    // Explicit native and unexpected lowering failures still fail.
                    if (this.outputSchemaStrategy !== 'auto' || !(error instanceof nativeOutputFormat_js_1.NativeOutputSchemaError))
                        throw error;
                }
            }
        }
        // Mounted whenever a parser exists (8.18.0). Until then, `retries: 0` —
        // the DEFAULT, and what `.outputSchema(parser)` means on its own — returned
        // `undefined` here: no judging in the loop, no `outputAttempts` row, no
        // event, a chart byte-identical to an agent with no contract at all. The
        // declaration bought a prompt sentence and a `runTyped()` parse, and a
        // caller on `run()` could not tell from the record that a contract had ever
        // been declared, let alone missed.
        //
        // `0` still mounts no retry BRANCH (see buildAgentChart) and still spends
        // no extra turn. It means judge, do not re-ask.
        return {
            parser,
            retries: this.negativeClaimsConfig?.mode !== 'typed'
                ? this.outputSchemaRetries
                : Math.min(this.outputSchemaRetries, 1),
            ...(schemaTool !== undefined && { schemaTool }),
            ...(outputFormat !== undefined && {
                outputFormat,
                ...(this.outputSchemaStrategy === 'auto' && { nativeOutputAuto: true }),
            }),
            hasFallback: this.outputFallbackCfg !== undefined,
            ...(this.negativeClaimsConfig?.mode === 'typed' && { negativeClaimsEnforced: true }),
            ...((this.answerValidationConfig?.mode === 'enforce' ||
                this.negativeClaimsConfig?.mode === 'typed') && {
                answerValidationEnforced: true,
            }),
        };
    }
    build() {
        // Settings that are only coherent (or not) once the whole agent exists.
        this.assertOutputFallbackCoherent();
        const negativeClaims = this.negativeClaimsConfig;
        let negativeSchema;
        if (negativeClaims?.mode === 'prose' && this.outputFallbackCfg !== undefined) {
            throw new Error('Agent.negativeClaims({ prose: true }) does not support .outputFallback(): a fallback runs after the observed answer.');
        }
        if (negativeClaims?.mode === 'typed') {
            if (this.outputSchemaParser === undefined) {
                throw new Error('Agent.negativeClaims requires .outputSchema(parser, { jsonSchema }).');
            }
            if (this.outputFallbackCfg !== undefined || this.limitsTravelValue) {
                throw new Error('Agent.negativeClaims does not support .outputFallback() or .limitsTravelWithTheAnswer(): the library must own the exact delivered answer.');
            }
            negativeSchema = (0, config_js_1.negativeClaimsSchema)(negativeClaims, (0, outputEnforcement_js_1.resolveJsonSchema)(this.outputSchemaParser, this.outputSchemaJson));
        }
        if (this.answerValidationConfig !== undefined) {
            if (this.outputSchemaParser === undefined) {
                throw new Error('AgentBuilder.answerValidation requires .outputSchema(parser).');
            }
            if (this.outputFallbackCfg !== undefined || this.limitsTravelValue) {
                throw new Error('AgentBuilder.answerValidation does not support .outputFallback() or .limitsTravelWithTheAnswer(): a fallback can replace the checked answer after validation, and the validated delivery has no place for the declared limits.');
            }
            // A listed check with nothing to judge is refused, never left to pass.
            const listed = this.answerValidationConfig.agentChecks;
            if (listed.includes('negative-claims') && negativeClaims?.mode !== 'typed') {
                throw new Error("AgentBuilder.answerValidation({ agentChecks: ['negative-claims'] }) requires typed .negativeClaims({ field, tools }).");
            }
            if (listed.includes('claims') && this.claimsContract === undefined) {
                throw new Error("AgentBuilder.answerValidation({ agentChecks: ['claims'] }) needs a .claims() contract: " +
                    'it names which answer field claims which fact, and without one the claim check has ' +
                    "nothing to judge. Declare the contract, or drop 'claims' from agentChecks.");
            }
            if (listed.includes('names-and-numbers') && this.evidenceGate === undefined) {
                throw new Error("AgentBuilder.answerValidation({ agentChecks: ['names-and-numbers'] }) needs " +
                    '.namesAndNumbersFromEvidence(): its verdict is what the row reads. Add the gate, or ' +
                    "drop 'names-and-numbers' from agentChecks.");
            }
        }
        // The answer layer (honesty layer 4), resolved ONCE from both doors — the
        // builder's `.answerLayer()` wins; otherwise `AgentOptions.answerLayer`,
        // checked by the same rules — so the refusals below and the arm `Agent`
        // receives can never read two different values.
        const answerLayer = this.answerLayerValue ?? answerLayerFromOptions(this.opts.answerLayer);
        // The answer layer's line appends prose to the answer — refused where the
        // answer's bytes are judged or must parse. The standing itself still
        // travels as data on both (`turn_end.answerAssessment`).
        if (answerLayer?.standingLine !== undefined) {
            if (this.answerValidationConfig !== undefined) {
                throw new Error('AgentBuilder.answerLayer({ standingLine: true }) cannot be combined with ' +
                    '.answerValidation(): the answer checks judge the exact bytes delivered, and the line ' +
                    'would change them. Keep .answerLayer() without the line — the standing still travels ' +
                    'as data, on turn_end.answerAssessment and agentfootprint.answer.assessed.');
            }
            if (this.outputSchemaParser !== undefined) {
                throw new Error('AgentBuilder.answerLayer({ standingLine: true }) cannot be combined with ' +
                    '.outputSchema(): a typed answer is JSON, and JSON followed by a line of prose is not. ' +
                    'Keep .answerLayer() without the line — the standing still travels as data, on ' +
                    'turn_end.answerAssessment and agentfootprint.answer.assessed.');
            }
        }
        // Resolve the voice config: bundled defaults + consumer overrides.
        // Templates flow through the same barrel exports the rest of the
        // library uses, so a future locale-pack swap is a single import.
        const voice = {
            appName: this.appNameValue,
            commentaryTemplates: { ...commentaryTemplates_js_1.defaultCommentaryTemplates, ...this.commentaryOverrides },
            thinkingTemplates: { ...statusTemplates_js_1.defaultStatusTemplates, ...this.thinkingOverrides },
        };
        // The SAME options object the builder was given unless a door overrode
        // something — `maxIterations`, or the findings ledger (9.101.0), whose
        // normalised value lands as `AgentOptions.findings`. An agent with neither
        // hands `Agent` its options by reference, exactly as before.
        const opts = this.maxIterationsOverride !== undefined ||
            this.findingsValue !== undefined ||
            this.toolChoiceValue !== undefined ||
            this.ontologyValue !== undefined ||
            this.inputsLayerValue ||
            this.resultsLayerValue ||
            answerLayer !== undefined ||
            this.timeValue !== undefined
            ? {
                ...this.opts,
                ...(this.maxIterationsOverride !== undefined && {
                    maxIterations: this.maxIterationsOverride,
                }),
                ...(this.findingsValue !== undefined && { findings: this.findingsValue }),
                // Tool choice by classifier (9.105.0), the same door grammar.
                ...(this.toolChoiceValue !== undefined && { toolChoice: this.toolChoiceValue }),
                // The declared ontology (9.106.0), the same door grammar.
                ...(this.ontologyValue !== undefined && { ontology: this.ontologyValue }),
                // The inputs layer (honesty layer 2), the same door grammar — its
                // object form only when it also arms declared sources. The door
                // MERGES with an `inputsLayer` option given to `Agent.create`: sources
                // armed by either stay armed, and a malformed option is still refused
                // by the one reader rather than silently replaced by the door's value.
                ...(this.inputsLayerValue && {
                    inputsLayer: this.inputsLayerSourcesValue ||
                        (0, armed_js_1.readInputsLayerOption)(this.opts.inputsLayer)?.argumentSources === true
                        ? { argumentSources: true }
                        : true,
                }),
                // The results layer (honesty layer 3), the same door grammar.
                ...(this.resultsLayerValue && { resultsLayer: true }),
                // The answer layer (honesty layer 4), the same door grammar — the
                // value resolved above, whichever door set it.
                ...(answerLayer !== undefined && {
                    answerLayer: answerLayer.standingLine === undefined
                        ? true
                        : answerLayer.standingLine === 'always'
                            ? { standingLine: true }
                            : { standingLine: { when: answerLayer.standingLine } },
                }),
                // The time layer, the same door grammar — the builder's value wins
                // over an `AgentOptions.time` given to `Agent.create`.
                ...(this.timeValue !== undefined && { time: this.timeValue }),
            }
            : this.opts;
        // .selfExplain(): a fresh binding per build() — two built agents never
        // share evidence. One mounted skill (methodology body only) + the trace
        // tools on a skill-scoped ToolProvider composed with the consumer's own,
        // so the catalog gains them ONLY on the activated iteration.
        //
        // Name reservation (mirrors the read_skill rule): the tools slot dedupes
        // by name with first-occurrence-wins and the static registry merges
        // FIRST — a consumer tool named like a trace tool would silently shadow
        // it, and the skill body would instruct the model into the wrong tool.
        //
        // The pieces come through the port (`selfExplainPort.ts`): the trace
        // toolpack sits above the agent, so it registers itself and this line
        // only asks — refusing with one sentence when nothing registered.
        const selfExplain = this.selfExplainConfig ? (0, selfExplainPort_js_1.requireSelfExplain)() : undefined;
        if (this.selfExplainConfig) {
            // Inline mode reserves whatever the pack can mount — read from
            // `TRACE_TOOL_NAMES` rather than retyped here, so a tool added to the
            // pack is reserved the same day it ships. Delegate mode mounts one
            // tool and reserves one name.
            const reserved = this.selfExplainConfig.delegate
                ? ['explain_run']
                : traceToolNames_js_1.TRACE_TOOL_NAMES;
            const clash = this.registry.find((entry) => reserved.includes(entry.name));
            if (clash) {
                throw new Error(`AgentBuilder.selfExplain: tool name '${clash.name}' is reserved by .selfExplain() ` +
                    `(${this.selfExplainConfig.delegate ? 'delegate' : 'inline'} mode). The tools slot ` +
                    `dedupes by name (first wins), so your tool would silently shadow the trace tool. ` +
                    `Rename it, or reserved names: ${reserved.join(', ')}.`);
            }
        }
        // THE CONTINGENT LINE (9.110.0) — the findings instruction gains one
        // line when the evidence gate is armed beside the ledger, and only then:
        // `findings/contingent.ts` runs under both doors, so the sentence that
        // names what it records is registered under both doors. Composed HERE
        // because the two doors may be called in either order; the injection
        // registered by `.findings()` is rebuilt in place, same id, same
        // activation, so an agent with the ledger alone keeps the exact bytes
        // `.findings()` registered.
        // Declared sources (honesty layer 2) add NO line: `from` is explained
        // once, in its own property on the ruled tools that carry it.
        if (this.findingsValue !== undefined && this.evidenceGate !== undefined) {
            const at = this.injectionList.findIndex((i) => i.id === reserved_js_1.FINDINGS_INSTRUCTION_ID);
            if (at >= 0) {
                this.injectionList[at] = (0, instruction_js_1.findingsInstruction)(true, this.findingsValue.answerAsk === 'quote-facts');
            }
        }
        const selfExplainBinding = selfExplain && this.selfExplainConfig
            ? selfExplain.binding(this.selfExplainConfig)
            : undefined;
        let injections = selfExplain && selfExplainBinding
            ? [...this.injectionList, selfExplain.skill(this.selfExplainConfig)]
            : this.injectionList;
        if (negativeClaims?.mode === 'typed' && negativeSchema !== undefined) {
            injections = [
                ...injections,
                (0, defineInstruction_js_1.defineInstruction)({
                    id: 'negative-claims',
                    activeWhen: () => true,
                    prompt: (0, config_js_1.negativeClaimsInstruction)(negativeClaims, negativeSchema),
                }),
            ];
        }
        // The tier-3 menu envelope's system-prompt half (SG-C): auto-registered
        // whenever the mounted graph can produce a MENU verdict — a menu with no
        // envelope would be a decision the model was never told about, the
        // accepted-and-silently-wrong kind. Not under `'rails'` (the model may
        // not act on a menu there), and not when the consumer registered their
        // own (the metadata marker, never the id — a renamed hint still counts).
        // Zero cost when the trigger is false: it reads `ctx.turnRoute`, which
        // only cascade graphs ever write.
        const cascadeForHint = this.skillGraphCascade;
        if (cascadeForHint !== undefined &&
            cascadeForHint.strictness !== 'rails' &&
            cascadeForHint.turnRouting !== undefined &&
            (cascadeForHint.turnRouting.scorer !== undefined ||
                cascadeForHint.continuity === 'conversation') &&
            !injections.some((i) => i.metadata?.[defineMenuHint_js_1.MENU_HINT_METADATA_KEY])) {
            injections = [...injections, (0, defineMenuHint_js_1.defineMenuHint)()];
        }
        // ── The tool posture (9.36.0) — `.toolsFromActiveSkill()` ──────────
        // Applied to the FINAL injection list, for the reason every fold on this
        // line uses: skills arrive through `.skill()`, `.skills()`, `.injection()`,
        // `.skillGraph()` and `.selfExplain()`, and only here is the whole agent
        // visible. From this point on there is exactly ONE list, so the registry,
        // the slots, the projection and every recorder read the same stamp.
        // Untouched when the posture was never asked for — the `false` branch does
        // not even walk the list.
        if (this.toolsFromActiveSkillValue) {
            injections = (0, toolsFromActiveSkill_js_1.scopeToolsToActiveSkill)(injections);
        }
        // ── Steps-as-data check-up + advisory (9.18.0) ─────────────────────
        // Judged on the FINAL injection list (the delivery-refusal reasoning:
        // skills arrive through `.skill()`, `.skillGraph()`, `.skills()` and
        // `.selfExplain()`, and only this line sees all of them). Nothing here
        // runs for an agent without a stepped skill.
        const stepPlans = (0, skillSteps_js_1.foldStepPlans)(injections);
        if (stepPlans.size > 0) {
            // Classic caches the tools slot after turn 1, so the per-step
            // narrowing and the banner would FREEZE at whatever step was current —
            // sequence enforcement that stops tracking the sequence. Refused, the
            // classic+graph sentence pattern (9.16.0).
            if (this.opts.reactMode === 'classic') {
                const ids = [...stepPlans.keys()].join("', '");
                throw new Error(`Agent.build(): reactMode 'classic' cannot honor \`steps\` (declared by '${ids}'). ` +
                    `Classic composes the tools slot once on turn 1, so the per-step narrowing and ` +
                    `the step banner would freeze at the first step while the procedure moved on — ` +
                    `the offer would enforce a sequence it stopped tracking. Use the default ` +
                    `'dynamic' mode (or 'dynamic-grouped'), which recomposes the slots every ` +
                    `iteration, or drop \`steps\`.`);
            }
            // A decision .tree() never writes a cursor (per-iteration predicate
            // routing — skillGraph's resolver stays the no-op there), and a step
            // procedure's tenure begins only under the cursor. A stepped tree
            // LEAF would slip past the wiring check below — its compiled trigger
            // is 'rule' and it has a predicate edge, so the open-skill clause
            // never fires — and then activate with its FULL toolset: no pointer,
            // no narrowing, no banner, no skip_step, no step event, nothing
            // saying why (accepted-and-silently-wrong, the cardinal sin). Same
            // fate for a stepped skill registered BESIDE the tree: no cursor
            // exists anywhere on a tree agent. Refused for all of them, the
            // continuity×tree sentence pattern (9.17.0).
            if (this.skillGraphIsTree) {
                const ids = [...stepPlans.keys()].join("', '");
                throw new Error(`Agent.build(): a decision .tree() cannot honor \`steps\` (declared by '${ids}'). ` +
                    `A tree routes by predicate on every iteration and never writes a cursor, and ` +
                    `a step procedure's tenure begins only under the cursor — the steps would ` +
                    `never engage: no pointer, no narrowing, no banner, no step event. Use the ` +
                    `flat entry/route form, register the skill on an agent without a graph, or ` +
                    `drop \`steps\`.`);
            }
            const edgeTargets = new Set(this.skillGraphEdgeTargets ?? []);
            for (const [skillId] of stepPlans) {
                const skill = injections.find((i) => i.id === skillId);
                if (!skill)
                    continue;
                if (this.skillGraphNextSkill !== undefined) {
                    // A graph is mounted: the tenant is the CURSOR, and the cursor
                    // never lands on an OPEN skill (llm-activated + no incoming edge —
                    // `Agent.openSkillIds`'s two clauses). Steps on one would activate
                    // a body whose procedure never engages: no pointer, no narrowing,
                    // no banner, no event, nothing saying why. Refused instead
                    // (accepted-and-silently-wrong is the cardinal sin).
                    const isOpen = skill.trigger.kind === 'llm-activated' && !edgeTargets.has(skillId);
                    if (isOpen) {
                        throw new Error(`Agent.build(): skill '${skillId}' declares \`steps\`, but the mounted skill ` +
                            `graph does not wire it (no entry, no route edge) — it is an OPEN skill, ` +
                            `activated by read_skill without ever receiving the cursor, and a step ` +
                            `procedure's tenure begins only under the cursor. Its steps would never ` +
                            `engage. Wire it into the graph (an entry or a route), register it on an ` +
                            `agent without a graph, or drop \`steps\`.`);
                    }
                }
                else if (skill.trigger.kind !== 'llm-activated') {
                    // No graph: the tenant is the most recent read_skill activation
                    // (the tail of activatedInjectionIds), and ONLY read_skill writes
                    // that array — a rule/always-triggered skill would never tenure.
                    throw new Error(`Agent.build(): skill '${skillId}' declares \`steps\`, but its trigger is ` +
                        `'${skill.trigger.kind}' and this agent mounts no skill graph — without a ` +
                        `cursor, a procedure's tenure begins at a read_skill activation, which only ` +
                        `an 'llm-activated' skill ever gets. Use defineSkill (its trigger is ` +
                        `llm-activated), mount a graph that routes to it, or drop \`steps\`.`);
                }
            }
            // Warn about an iteration budget too small for the declared procedure.
            if ((0, footprintjs_1.isDevMode)()) {
                const effectiveMax = opts.maxIterations ?? 10;
                for (const plan of stepPlans.values()) {
                    // Warn, not refuse: adjacent same-tool steps can advance twice in
                    // one batch, so a tight budget CAN still complete a procedure.
                    if (plan.steps.length + 1 > effectiveMax) {
                        // eslint-disable-next-line no-console
                        console.warn(`agentfootprint Agent: skill '${plan.skillId}' declares ${plan.steps.length} ` +
                            `steps but maxIterations is ${effectiveMax} — the procedure cannot complete ` +
                            `in one turn unless steps share a batch. Raise maxIterations, or expect ` +
                            `steps_unfinished { action: 'cut-short' } on the record.`);
                    }
                }
            }
            // The steps advisory (the menu-hint pattern, one block up): a narrowed
            // offer with no explanation would be sequence enforcement the model was
            // never told about. Marker-detected, so a consumer's own hint (any id)
            // stands the default down.
            if (!injections.some((i) => i.metadata?.[defineStepsHint_js_1.STEPS_HINT_METADATA_KEY])) {
                injections = [...injections, (0, defineStepsHint_js_1.defineStepsHint)()];
            }
        }
        const toolProvider = selfExplain && selfExplainBinding
            ? selfExplain.toolProvider(selfExplainBinding, this.selfExplainConfig, this.toolProviderRef)
            : this.toolProviderRef;
        // A skill may claim the `read_skill` delivery channel only if `read_skill` is
        // what activates it — otherwise its body reaches the model through no channel at
        // all (8.5.0). Checked HERE, on the final list, for the same reason
        // `resolveOutputEnforcement` lives here: the answer depends on the WHOLE agent.
        // `.skillGraph()` compiles the triggers, `.skill()` may add more after it, and
        // `.selfExplain()` adds one right above — only this line sees all of them.
        const deliveryRefusal = (0, skillBodyDelivery_js_1.toolOnlyDeliveryRefusal)(injections);
        if (deliveryRefusal)
            throw new Error(deliveryRefusal);
        // Two governance refusals that need the WHOLE agent (8.13.0), for the same
        // reason `resolveOutputEnforcement` and the delivery refusal live here:
        // observers arrive across many `.watch()` calls, and a `checkIn` demand can
        // come from `.tool()` OR from a skill's own tools, which only this line sees
        // assembled.
        assertNoCollidingObserverIds(this.recorderList);
        assertCheckInHasADeclaringTool(this.checkInConfig !== undefined, this.registry, injections, toolProvider);
        // Two dev-mode warnings about wiring that is inert rather than wrong (8.7.0).
        // Both need the WHOLE agent — one asks whether a scorer exists, the other pairs a
        // provider against an injection — so both live here, beside the refusal above.
        warnInertRelevanceHint(injections, this.skillGraphScoreEntries !== undefined);
        warnRedundantSkillScopedTools(this.toolProviderRef, injections);
        // The skill graph's DEFERRED body-contract checks run HERE — the one build point
        // that can see the full tool registry. At graph build (without `knownTools`) a
        // body's `lookup_order(id)` is indistinguishable from a typo, because `.tool()`
        // has not run yet; the graph said nothing it could not prove and left its note.
        // The note is collected from the FINAL injection list (each deferred graph skill
        // carries it in its metadata), so the checks run whichever door the skills came
        // through — `.skillGraph(graph)` or `.skills({ list: () => graph.skills })`.
        // The `.skillGraph()` capture is folded in as a fallback for a structurally-typed
        // graph without per-skill stamps; skills found by both are deduped by id, so one
        // problem is reported once. The known set checked now: the agent's registered
        // tool names ∪ `read_skill` (auto-attached whenever a skill exists, so a hand-off
        // hint naming it is not a typo) ∪ every NON-deferred skill's tool names (they
        // exist on this agent; calling them nonexistent would be false) — plus, inside
        // checkSkillContracts, the checked skills' own tools. Runs at most once per built
        // agent, and never when the graph already ran its checks with an explicit
        // `knownTools`: one problem, one report. Baseline `ToolProvider` tools are
        // per-iteration and cannot be enumerated at build time — bodies naming only
        // provider-delivered tools should pass the names via the graph's `knownTools`.
        const deferredByMode = new Map();
        const deferredIds = new Set();
        const collectDeferred = (skill, mode) => {
            if (deferredIds.has(skill.id))
                return; // metadata + graph note = ONE check
            deferredIds.add(skill.id);
            const group = deferredByMode.get(mode);
            if (group)
                group.push(skill);
            else
                deferredByMode.set(mode, [skill]);
        };
        for (const i of injections) {
            const note = i.metadata?.[skillGraph_js_1.SKILL_GRAPH_DEFERRED_CONTRACT_KEY];
            if (note?.mode === 'throw' || note?.mode === 'warn')
                collectDeferred(i, note.mode);
        }
        if (this.skillGraphDeferredBodyContract) {
            const { mode, skills: graphSkills } = this.skillGraphDeferredBodyContract;
            for (const s of graphSkills)
                collectDeferred(s, mode);
        }
        if (deferredByMode.size > 0) {
            // Tools on skills OUTSIDE the deferred set exist on this agent — baseline,
            // not typos. Tools on deferred skills are added per group by
            // checkSkillContracts itself (own vs known matters for body-foreign-tool).
            const baseKnownTools = [
                ...this.registry.map((entry) => entry.name),
                'read_skill',
                ...injections
                    .filter((i) => i.flavor === 'skill' && !deferredIds.has(i.id))
                    .flatMap((s) => (0, skillContract_js_1.skillToolNames)(s)),
            ];
            // One group per deferring graph's `check` mode — two graphs fed via
            // `.skills()` may disagree, and each is judged by its OWN declared severity.
            for (const [mode, group] of deferredByMode) {
                const groupIds = new Set(group.map((s) => s.id));
                const knownTools = [
                    ...baseKnownTools,
                    ...[...deferredByMode.values()]
                        .flat()
                        .filter((s) => !groupIds.has(s.id))
                        .flatMap((s) => (0, skillContract_js_1.skillToolNames)(s)),
                ];
                const problems = (0, skillContract_js_1.checkSkillContracts)(group, { knownTools });
                if (problems.length === 0)
                    continue;
                const report = { ok: !problems.some((p) => p.kind === 'error'), problems };
                // Severity per the GRAPH's own check mode. Both contract checks are
                // warnings today, so `'throw'` and `'warn'` both surface as a dev-mode
                // warning — but an error-kind problem, should one ever exist, honors
                // `'throw'` here exactly as it would have at graph build.
                if (mode === 'throw' && !report.ok) {
                    throw new Error(`Agent.build(): skill-body ↔ tool-contract check-up failed ` +
                        `(deferred from graph build; checked against this agent's full tool ` +
                        `registry):\n${(0, skillGraphCheckup_js_1.formatCheckup)(report)}`);
                }
                if ((0, footprintjs_1.isDevMode)()) {
                    // eslint-disable-next-line no-console
                    console.warn(`agentfootprint Agent: skill-graph body-contract check-up (deferred from ` +
                        `graph build; checked here against the agent's full tool registry — pass ` +
                        `knownTools to skillGraph to run it at graph build instead):\n` +
                        (0, skillGraphCheckup_js_1.formatCheckup)(report));
                }
            }
        }
        // ── The artifact vocabularies (SG-F, 9.25.0) ──────────────────────
        // Runs on the FINAL injection list, for the reason every check on this
        // line does: skills arrive through `.skill()`, `.skills()` AND
        // `.skillGraph()`, and only here is the whole agent visible — which is
        // exactly what the satisfiability rule needs ("does ANYTHING on this
        // agent declare it produces this kind?"). A graph that already ran its
        // own checkup re-reports here only if a producer outside the graph was
        // what silenced it, which is the answer the author wants either way.
        //
        // Always a dev-mode WARNING, never a throw, on ANY check mode: the rule
        // reads declarations only and cannot see a store seeded by another agent
        // or an earlier run (`skillVocabulary.ts` states the whole boundary), so
        // failing a build on it would be claiming more than it knows.
        const vocabularyProblems = (0, skillVocabulary_js_1.checkArtifactVocabularies)(injections);
        if (vocabularyProblems.length > 0 && (0, footprintjs_1.isDevMode)()) {
            // eslint-disable-next-line no-console
            console.warn(`agentfootprint Agent: artifact-vocabulary check-up:\n` +
                (0, skillGraphCheckup_js_1.formatCheckup)({ ok: true, problems: vocabularyProblems }));
        }
        // ── The brains fold (9.19.0) — both declaration homes, one check-up ──
        // Runs on the FINAL injection list for the same reason the delivery
        // refusal does: `defineSkill({ provider })` can arrive through
        // `.skill()`, `.skills()` or `.skillGraph()`, and only this line sees
        // all of them. Undefined — nothing declared anywhere — and the Agent
        // wires nothing new.
        const skillBrains = (0, skillBrains_js_1.foldSkillBrains)({
            injections,
            ...(this.skillGraphBrainOptions?.providers !== undefined && {
                providers: this.skillGraphBrainOptions.providers,
            }),
            ...(this.skillGraphBrainOptions?.escalation !== undefined && {
                escalation: this.skillGraphBrainOptions.escalation,
            }),
            ...(this.skillGraphBrainOptions?.decider !== undefined && {
                decider: this.skillGraphBrainOptions.decider,
            }),
            graphMounted: this.skillGraphNextSkill !== undefined,
            ...(this.skillGraphNodeIds !== undefined && { nodeIds: this.skillGraphNodeIds }),
            agentProviderName: opts.provider.name,
        });
        // ── Thinking, against what each provider DECLARES for its model ──
        // After the brains fold, because a brain's model is a model the thinking
        // request reaches too. A model that cannot think is refused HERE, by name
        // (`thinkingSupport.ts`), not as a vendor 400 on the first call.
        if (this.thinkingBudgetValue !== undefined) {
            (0, thinkingSupport_js_1.checkThinkingSupport)({
                budget: this.thinkingBudgetValue,
                provider: opts.provider,
                model: opts.model,
                ...(skillBrains !== undefined && { brains: skillBrains }),
                ...(this.reliabilityConfig?.providers !== undefined && {
                    reliabilityProviders: this.reliabilityConfig.providers,
                }),
            });
        }
        // A decider with no menu to resolve: only a graph that RUNS the
        // turn-start cascade (a classifier, or `continuity: 'conversation'`)
        // ever produces an outstanding menu, so a decider on any other mount
        // would be config that never runs — refused rather than silently inert.
        if (skillBrains?.decider !== undefined) {
            const cascade = this.skillGraphCascade;
            const runsCascade = cascade !== undefined &&
                cascade.turnRouting !== undefined &&
                (cascade.turnRouting.scorer !== undefined || cascade.continuity === 'conversation');
            if (!runsCascade) {
                throw new Error(`Agent.build(): a routing decider is declared, but this mount never runs the ` +
                    `turn-start cascade — no menu can ever be outstanding, so the decider would ` +
                    `never be consulted. Give the graph a classifier (.classify(...) / start rules ` +
                    `with intents) or mount with continuity: 'conversation', or drop \`decider\`.`);
            }
        }
        // The maps kernel's plan (9.58.0) — resolved HERE because it needs both
        // the mounted graph (member ids) and the final injection list (each
        // member's contributed tool names). Refusals live in the resolver.
        const mapsPlan = this.resolveMapsPlan(injections);
        // The script record (`.scriptTravelsWithTheAnswer()`, `code: 'match'`) needs
        // a tool that DECLARES it runs a program — never inferred from a name.
        const declaresScript = [
            ...this.registry.map((entry) => entry.tool),
            ...injections.flatMap((injection) => injection.inject?.tools ?? []),
        ].some((tool) => (0, script_js_1.scriptOf)(tool) !== undefined);
        if (this.scriptTravelValue !== undefined &&
            !declaresScript &&
            this.toolProviderRef === undefined) {
            throw new Error('AgentBuilder.scriptTravelsWithTheAnswer: no registered tool declares `script`, so no ' +
                'call of this agent can ever file a program to attach. Register a code-running tool ' +
                "that declares one (`codeRunnerTool` does; a hand-built tool declares `script: { argument: 'code' }`), " +
                'or drop this option.');
        }
        if (this.evidenceGate?.code === 'match' &&
            !declaresScript &&
            this.toolProviderRef === undefined &&
            (0, footprintjs_1.isDevMode)()) {
            // eslint-disable-next-line no-console
            console.warn("agentfootprint: .namesAndNumbersFromEvidence({ code: 'match' }) on an agent where no " +
                'registered tool declares `script` — every fenced block the model shows is then a miss, ' +
                'read as prose. Register a code-running tool that declares `script` (codeRunnerTool ' +
                "does), or drop `code: 'match'`.");
        }
        // The claim contract (9.61.0) — refused here rather than at `.claims()`,
        // because `.outputSchema()` may legitimately be declared after it.
        // A contract with no typed stratum to read would check nothing, and a
        // check that silently checks nothing is the decay this family exists
        // to prevent.
        if (this.claimsContract !== undefined && this.outputSchemaParser === undefined) {
            throw new Error(`Agent.claims(): a claim contract needs a validated answer to read — add ` +
                `.outputSchema(schema). The claim seam compares TYPED fields; prose is never ` +
                `checked, so without a schema this contract would check nothing.`);
        }
        const claimContract = this.claimsContract === undefined
            ? undefined
            : Object.entries(this.claimsContract).map(([answerField, fact]) => ({
                answerField,
                entity: fact.entity,
                field: fact.field,
            }));
        if (negativeClaims?.mode === 'typed') {
            (0, config_js_1.assertNegativeClaimsTools)(negativeClaims, [
                ...this.registry.map((entry) => entry.tool),
                ...injections.flatMap((injection) => injection.inject?.tools ?? []),
            ]);
        }
        const agent = new Agent_js_1.Agent(opts, this.systemPromptValue, this.registry, voice, injections, this.memoryList, this.outputSchemaParser, toolProvider, this.systemPromptCachePolicy, this.cachingDisabledValue, this.cacheStrategyOverride, this.outputFallbackCfg, this.reliabilityConfig, this.thinkingHandlerValue, this.thinkingBudgetValue, this.skillGraphNextSkill, this.skillGraphReachable, this.skillGraphScoreEntries, this.checkInConfig, this.runConfigFn, this.windowStrategyValue, this.toolMiddlewareList, this.messageMiddlewareList, this.resolveOutputEnforcement(negativeSchema), this.skillGraphEdgeTargets, this.skillGraphExplainNextSkill, this.skillGraphIsTree, this.skillGraphSupersededEntries, this.skillGraphCascade, skillBrains, this.evidenceGate, this.limitsTravelValue, 
        // Declaration order, and a COPY: the manifest reports what this build
        // applied, and a later `.recipe()` on the same builder (a second
        // `build()`) must not retroactively edit the agent already handed out.
        // Undefined rather than `[]` when none — the manifest's "absent means not
        // configured" law, and what keeps an agent with no recipes byte-identical.
        this.appliedRecipeList.length > 0 ? [...this.appliedRecipeList] : undefined, this.skillGraphDeclared, mapsPlan, claimContract, this.answerValidationConfig, this.systemPromptCacheExplicit, negativeClaims, this.scriptTravelValue);
        // Attach the observers collected by `.watch()` so they receive events
        // from the very first run. Mirrors what consumers would do post-build
        // via `agent.attach(rec)`; the builder method is purely sugar over it,
        // which is what makes `.watch()` provably the same attachment.
        for (const observer of this.recorderList) {
            agent.attach(observer);
        }
        if (selfExplainBinding) {
            // Late binding: capture fires at each run's terminal flush, when
            // getLastSnapshot() IS the just-completed run (never in-flight).
            //
            // All THREE sources in one call, because a turn's evidence is three
            // things that a snapshot alone does not carry: what happened
            // (snapshot), how it read in English (narrative), and when each tool
            // call started and ended (events — the commit log has no clock).
            //
            // …plus a FOURTH when any mounted tool keeps a record of its own run
            // (`flowchartAsTool({ keepRecord: true })`). Collected here rather
            // than resolved per call because the store is a LIVE object owned by
            // the tool: the binding holds the lookup, and every record the tool
            // files after this point is visible through it.
            const innerRuns = collectInnerRuns(this.registry, injections);
            selfExplainBinding.bindTo({
                getSnapshot: () => agent.getLastSnapshot(),
                getNarrative: () => agent.getLastNarrativeEntries(),
                // Only the explained run's own events (`Agent.ownsEvent`): on an
                // instance serving several sessions the hosting door emits facts for
                // OTHER sessions while this run is in flight.
                on: (type, listener) => agent.on(type, (event) => {
                    if (agent.ownsEvent(event))
                        listener(event);
                }),
                ...(innerRuns !== undefined && { getInnerRuns: () => innerRuns }),
                // …and the evidence as a whole is kept PER CONVERSATION (R2-11): on a
                // shared `standingAgent({ agent })` the previous completed run of the
                // INSTANCE is whoever ran last, so the binding files each finished
                // turn under its conversation and serves the asking run's only —
                // snapshot, narrative, events and the tools' inner runs alike
                // (`agent/servingConversation.ts` says what the key is).
                getServing: () => (0, servingConversation_js_1.servingConversationOf)(agent),
            });
            agent.attach(selfExplainBinding.recorder());
            // …and the agent holds the binding, so `agent.canExplain()` answers
            // from the SAME fact the trace tools answer from rather than from a
            // second guess about whether a turn has completed.
            agent.bindSelfExplain(selfExplainBinding);
        }
        return agent;
    }
}
exports.AgentBuilder = AgentBuilder;
/**
 * The line's modes, as the option doors check them — keyed by the type the
 * layer's composer decides with (`assessment/compose.ts` · `StandingLineWhen`),
 * so a mode added there does not compile until it is listed here. A type-only
 * import: the composer stays off a plain agent's module graph.
 */
const STANDING_LINE_MODES = Object.freeze({
    always: true,
    'unless-consistent': true,
    'not-sure-or-ask': true,
});
/**
 * THE ANSWER LAYER'S ARM (honesty layer 4), checked by ONE rule for both of
 * its doors — `.answerLayer(options)` and `AgentOptions.answerLayer` — so a
 * value one door refuses, the other refuses too: an unknown key, a
 * `standingLine` that is neither a boolean nor `{ when }`, or a `when` outside
 * the three modes, is refused by name. Returns the arm normalised — the line's
 * mode (`true` → `'always'`), or `{}` for no line.
 */
function answerLayerArm(options, door) {
    const unknown = Object.keys(options).filter((k) => k !== 'standingLine');
    if (unknown.length > 0) {
        throw new Error(`${door}: unknown option(s) ${unknown.map((k) => `'${k}'`).join(', ')}. ` +
            "The one option is standingLine (boolean, or { when: 'always' | 'unless-consistent' | " +
            "'not-sure-or-ask' }).");
    }
    const line = options.standingLine;
    if (line === undefined || line === false)
        return {};
    if (line === true)
        return { standingLine: 'always' };
    const modes = Object.keys(STANDING_LINE_MODES)
        .map((w) => `'${w}'`)
        .join(', ');
    if (typeof line !== 'object' || line === null || Array.isArray(line)) {
        throw new Error(`${door}: standingLine must be a boolean or { when } — when is one of ${modes}.`);
    }
    const extra = Object.keys(line).filter((k) => k !== 'when');
    if (extra.length > 0) {
        throw new Error(`${door}: standingLine has unknown option(s) ${extra.map((k) => `'${k}'`).join(', ')}. ` +
            `The one option is when: ${modes}.`);
    }
    const when = line.when;
    if (typeof when !== 'string' ||
        !Object.prototype.hasOwnProperty.call(STANDING_LINE_MODES, when)) {
        throw new Error(`${door}: standingLine.when must be one of ${modes} (got ${JSON.stringify(when) ?? 'nothing'}).`);
    }
    return { standingLine: when };
}
/**
 * `AgentOptions.answerLayer` as the arm `build()` resolves: `undefined` when
 * it is absent or `false`, `{}` for `true`, an object through
 * `answerLayerArm` — so the options door meets every refusal the builder's
 * door does. Anything else is refused by name.
 */
function answerLayerFromOptions(value) {
    if (value === undefined || value === false)
        return undefined;
    if (value === true)
        return {};
    if (typeof value !== 'object' || value === null || Array.isArray(value)) {
        throw new Error('AgentOptions.answerLayer must be a boolean or an object — ' +
            "{ standingLine?: boolean | { when: 'always' | 'unless-consistent' | 'not-sure-or-ask' } }.");
    }
    return answerLayerArm(value, 'AgentOptions.answerLayer');
}
/**
 * Refuse two DIFFERENT observers that share one id (8.13.0).
 *
 * footprintjs de-duplicates attached recorders by id — `attachScopeRecorder` and
 * `attachFlowRecorder` both `filter(r => r.id !== recorder.id)` before pushing —
 * so of two objects carrying one id, only the LAST ever fires. The first is
 * removed before the run starts and reports nothing at all, which is
 * indistinguishable from an observer whose events simply never happened.
 *
 * Keyed on OBJECT IDENTITY, not on the id alone. Handing the same reference to
 * `.watch()` twice is
 * harmless and stays one attachment — the fp dedupe is doing exactly what it is
 * for. It is two DIFFERENT observers under one name that loses a whole observer.
 *
 * Deliberately does NOT reserve the `agentfootprint.` id prefix: the factories in
 * `agentfootprint/observe` carry ids in that namespace and consumers are meant to
 * `.watch()` them.
 */
function assertNoCollidingObserverIds(observers) {
    const byId = new Map();
    for (const observer of observers) {
        const id = observer.id;
        const seen = byId.get(id);
        if (seen === undefined) {
            byId.set(id, observer);
            continue;
        }
        if (seen === observer)
            continue; // same reference — one attachment, by design
        throw new Error(`AgentBuilder.watch: two different observers were given the id '${id}'. Only the LAST ` +
            `one ever fires — footprintjs de-duplicates attached recorders by id, so the earlier ` +
            `one is dropped before the first run and reports nothing. Rename one of them; passing ` +
            `the SAME object twice is fine (it stays one attachment).`);
    }
}
/**
 * Refuse `.checkIn({...})` on an agent where no tool can ever trip the gate (8.13.0).
 *
 * `.checkIn()` configures HOW the ask is assembled — the evidence preset, the
 * drivers scorer. What MAKES an ask is a tool declaring `checkIn`. Resolution
 * already defaults (standard evidence + the lexical scorer), so a declaring tool
 * works with no `.checkIn()` call at all; the reverse — `.checkIn()` with nothing
 * that declares — configures the shape of a question that is never asked.
 *
 * Scans BOTH sources the dispatch map is built from (`buildToolRegistry`): the
 * `.tool()` registry and every skill's `inject.tools`, since a skill tool with a
 * `checkIn` demand is a real gate.
 *
 * NOT refused when a `.toolProvider()` is wired. Its tools are resolved per
 * iteration and may declare `checkIn`; build time cannot know, and refusing what
 * it cannot know would break a correct agent.
 */
function assertCheckInHasADeclaringTool(configured, registry, injections, toolProvider) {
    if (!configured || toolProvider !== undefined)
        return;
    const declares = (tool) => tool.checkIn !== undefined;
    if (registry.some((entry) => declares(entry.tool)))
        return;
    for (const injection of injections) {
        for (const tool of injection.inject?.tools ?? []) {
            if (declares(tool))
                return;
        }
    }
    throw new Error('AgentBuilder.checkIn: configured, but no registered tool declares `checkIn` — so the gate ' +
        'is never consulted, this agent will never pause for consent, and the evidence settings ' +
        'here decide nothing. `.checkIn()` configures HOW the ask is assembled; a tool declaring ' +
        "`checkIn: 'always'` (or a predicate) is what MAKES the ask. Add it to the tool that " +
        "needs consent — defineTool({ …, checkIn: 'always' }) — or drop `.checkIn()`.");
}
/**
 * Gather the inner-run stores of every tool that keeps one (8.17.0).
 *
 * A `flowchartAsTool({ keepRecord: true })` carries its store on the tool
 * object under a registry symbol. This finds them so `.selfExplain()` can
 * hand one merged lookup to the trace artifacts — the consumer wires
 * nothing; mounting the tool and calling `.selfExplain()` is the whole
 * configuration.
 *
 * Two sources, both known at build time: the static registry (`.tool()` /
 * `.tools()`) and skill-declared tools. A tool arriving from a
 * `.toolProvider()` is deliberately NOT collected — provider tools are
 * resolved per iteration against a live context, so there is no build-time
 * moment at which the list exists. `inspect_tool_run` then answers with the
 * honest-absence arm, which names `keepRecord`; a consumer in that position
 * should register the chart tool statically as well.
 */
function collectInnerRuns(registry, injections) {
    const found = [];
    const take = (candidate) => {
        const store = (0, innerRunRecords_js_1.innerRunsOf)(candidate);
        if (store !== undefined && !found.includes(store))
            found.push(store);
    };
    for (const entry of registry)
        take(entry.tool);
    for (const injection of injections) {
        for (const tool of injection.inject?.tools ?? [])
            take(tool);
    }
    return (0, innerRunRecords_js_1.mergeInnerRuns)(found);
}
/**
 * Refuse an `llm-activated` trigger whose `viaToolName` is not `'read_skill'` (8.7.0).
 *
 * `viaToolName` reads as a promise the library never kept: name a tool, and that tool
 * activates the skill. No such tool has ever been created. The one consumer of an
 * `llm-activated` trigger is the evaluator, which matches on
 * `ctx.activatedInjectionIds.includes(inj.id)` and never looks at the field
 * (`evaluator.ts`); the only writer of that array is `read_skill`. So a skill carrying
 * `viaToolName: 'open_playbook'` activated by `read_skill` exactly like every other
 * skill, and the declaration meant nothing at all — a configuration that was never
 * read is worse than one that is refused, because it looks like it worked.
 *
 * Refused HERE because `injection()` is the one funnel every flavor passes through:
 * `defineSkill`, `skillsFromDir`, `.skill()`, `.skills(registry)`, `.skillGraph()`
 * and a hand-built Injection all arrive at this line.
 *
 * 9.0.0 removed the `viaToolName` OPTION from `defineSkill` and `skillsFromDir`
 * (both now refuse it by name at the factory), but the TRIGGER field stays and so
 * does this check: a hand-built `Injection` literal can still carry an
 * `llm-activated` trigger with any string in it, and that is the one path with no
 * factory in front of it.
 */
function assertReadSkillActivation(injection) {
    const trigger = injection.trigger;
    if (trigger.kind !== 'llm-activated')
        return;
    if (trigger.viaToolName === 'read_skill')
        return;
    throw new Error(`Agent.injection('${injection.id}'): viaToolName is '${trigger.viaToolName}', but ` +
        `'read_skill' is the only activation tool this library builds — nothing reads the field, ` +
        `and no tool named '${trigger.viaToolName}' is ever offered to the model. This skill would ` +
        `have activated through read_skill like every other one, so the name was decoration on a ` +
        `door that does not exist. Drop \`viaToolName\` (skills already share one activation tool ` +
        `and the model picks WHICH skill by id), or gate the skill on something the engine does ` +
        `read — a \`rule\` trigger, or a skillGraph() edge. The \`viaToolName\` OPTION on ` +
        `defineSkill()/skillsFromDir() was removed in 9.0.0; this trigger field is all that ` +
        `is left, and only a hand-built Injection can still set it.`);
}
/**
 * Dev-warn a `defineRelevanceHint()` mounted where no scorer can feed it (8.7.0).
 *
 * The hint's trigger reads `ctx.entryScores`, and only the PickEntry stage writes it
 * — which runs only when the graph was built with `.entryBy()` / `.entryByRelevance()`
 * / `start: { entries, scoredBy | byRelevance }`. Without one, the injection mounts,
 * evaluates false on every iteration for the life of the agent, and reports nothing:
 * a feature that is configured and inert looks exactly like a feature that is working
 * and simply has not been needed yet.
 *
 * Keyed on the `readsEntryScores` metadata marker, not on the id — the id is the
 * caller's to rename.
 */
function warnInertRelevanceHint(injections, hasScorer) {
    if (hasScorer || !(0, footprintjs_1.isDevMode)())
        return;
    for (const inj of injections) {
        const meta = inj.metadata;
        if (meta?.[defineRelevanceHint_js_1.READS_ENTRY_SCORES_METADATA_KEY] !== true)
            continue;
        // eslint-disable-next-line no-console
        console.warn(`agentfootprint Agent: injection '${inj.id}' reads ctx.entryScores, which only an ENTRY ` +
            `SCORER produces — and this agent's skill graph has none, so the injection can never ` +
            `activate. Build the graph with .entryBy(keywordScorer()) or .entryByRelevance(embedder) ` +
            `(object form: start: { entries, scoredBy }), or drop the injection. Note .entryByRead() ` +
            `does NOT score — there the model picks, so there is no near-tie to report.`);
    }
}
/**
 * Dev-warn a `skillScopedTools(id, …)` pointed at a skill that already scopes its own
 * tools (8.7.0).
 *
 * `defineSkill({ tools, autoActivate: 'currentSkill' })` already narrows the LLM's
 * tool list to the active skill, with no provider at all — that is what
 * `skillScopedTools`' own header means by "you probably don't need this". Wiring both
 * gives one skill two tool sources keyed on two DIFFERENT signals (the graph's active
 * set vs. the last `read_skill` call), which is how a tool ends up visible on
 * iterations nobody expected — and it is the exact pairing that put a shadowed tool
 * name into a field agent.
 */
function warnRedundantSkillScopedTools(provider, injections) {
    if (!(0, footprintjs_1.isDevMode)())
        return;
    const target = (0, skillScopedTools_js_1.skillScopedToolsTarget)(provider?.id);
    if (target === undefined)
        return;
    const skill = injections.find((i) => i.id === target);
    if (!skill)
        return;
    const meta = skill.metadata;
    if (meta?.autoActivate !== 'currentSkill')
        return;
    if ((skill.inject?.tools ?? []).length === 0)
        return;
    // eslint-disable-next-line no-console
    console.warn(`agentfootprint Agent: skillScopedTools('${target}') is wired, but skill '${target}' already ` +
        `carries autoActivate: 'currentSkill' with its own tools:[] — which narrows the tool list ` +
        `to that skill on its own. The two gates key on DIFFERENT signals (the provider on the ` +
        `last read_skill call, the skill on the graph's active set), so a tool can appear on ` +
        `iterations you did not expect, and a name declared on both sides shadows (the model ` +
        `reads the provider's description and the skill's implementation runs). Keep one: the ` +
        `skill's tools:[] if the tools belong to the skill, the provider if they are assembled ` +
        `elsewhere.`);
}
/**
 * Validate `.outputSchema(…, { retries })`.
 *
 * The ceiling is stated rather than left open: a model that has missed the
 * shape ten times in a row is not going to find it on the eleventh, and the
 * run would be spending real money and real iterations discovering that. When
 * ten is not enough the answer is a different schema or a different model,
 * not a longer loop.
 */
function resolveRetries(retries) {
    if (retries === undefined)
        return 0;
    if (!Number.isInteger(retries) || retries < 0) {
        throw new Error(`AgentBuilder.outputSchema: { retries } must be a non-negative integer, got ${String(retries)}. It counts corrective re-asks — 0 (the default) means the first answer is the only one.`);
    }
    if (retries > MAX_OUTPUT_RETRIES) {
        throw new Error(`AgentBuilder.outputSchema: { retries: ${retries} } exceeds the ceiling of ` +
            `${MAX_OUTPUT_RETRIES}. Each retry is a real turn — a real request, real tokens, real ` +
            `money — and a model that has missed the shape ${MAX_OUTPUT_RETRIES} times running is ` +
            `not about to find it. If that is genuinely not enough, the fix is a simpler schema, a ` +
            `clearer instruction, or a different model.`);
    }
    return retries;
}
/** The stated ceiling on corrective re-asks. See {@link resolveRetries}. */
const MAX_OUTPUT_RETRIES = 10;
//# sourceMappingURL=AgentBuilder.js.map