agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
1,817 lines • 102 kB
TypeScript
/**
* Agent — ReAct primitive (LLM + tools + iteration loop).
*
* Pattern: Builder (GoF) → produces a Runner backed by a footprintjs FlowChart.
* Role: Layer-5 primitive (core/). Assembles the 3-slot context
* pipeline + callLLM + route decider + tool-calls subflow +
* loopTo. Composition nestable anywhere that accepts a Runner.
* Emits: Via internal recorders:
* agentfootprint.agent.turn_start / turn_end
* agentfootprint.agent.iteration_start / iteration_end
* agentfootprint.agent.route_decided
* agentfootprint.stream.llm_start / llm_end
* agentfootprint.stream.tool_start / tool_end
* agentfootprint.context.* (via ContextRecorder)
*/
import { type CombinedNarrativeEntry, type FlowchartCheckpoint, type ObserverDrainResult, type RedactionPolicy, type RuntimeSnapshot } from 'footprintjs';
import type { RunOptions } from './runner.js';
import type { CachePolicy, CacheStrategy } from '../cache/types.js';
import type { ReliabilityConfig } from '../reliability/types.js';
import { type RunnerPauseOutcome } from './pause.js';
import type { ArtifactStore } from '../artifacts/types.js';
import type { AgentfootprintEvent } from '../events/registry.js';
import type { SkillGraphDeclaredMap } from './agent/skillGraphDeclared.js';
import type { AppliedRecipe } from '../recipes/types.js';
import type { WindowStrategy } from './agent/window/strategy.js';
import type { AnswerAssessment, AssessmentDeclarations } from './agent/assessment/types.js';
import { type CheckInBuilderOptions } from './checkin.js';
import type { MemoryDefinition } from '../memory/define.types.js';
import type { MemoryIdentity } from '../memory/identity/types.js';
import type { SelfExplainBinding } from '../trace-toolpack/selfExplain.js';
import { type ChildOutcome } from './terminalVerdict.js';
import type { ResolvedEvidenceGate } from './agent/evidence/types.js';
import type { ResolvedScriptTravel } from './agent/scripts/types.js';
import type { Injection, InjectionContext } from '../injection-engine/types.js';
import type { CursorMove, EntryScoring, TurnRoutingPlan } from '../injection-engine/skillGraph.js';
import { type ResolvedOutputFallback } from './outputFallback.js';
import { type AgentRunCheckpoint } from './runCheckpoint.js';
import { type OutputSchemaParser } from '../answer-validation/outputSchema.js';
import { type RunTime } from '../time/clock.js';
import type { ResolvedOutputEnforcement } from './agent/outputEnforcement.js';
import { RunnerBase } from './RunnerBase.js';
import type { ToolRegistryEntry } from '../tools/tools.js';
import type { ToolProvider } from '../tools/toolProvider.js';
import type { AgentArtifactsOptions, AgentInput, AgentOptions, AgentOutput, AgentRecordingsOptions, AgentState, ObserverDeliveryOptions, RunConfig, RunConfigContext, RunConfigFn, WriteProvenanceMode } from './agent/types.js';
import type { ResolvedNegativeClaims } from './agent/negativeClaims/config.js';
import type { AnswerNegatives } from './agent/negativeClaims/delivery.js';
import type { AnswerCoverage } from '../semantics/coverage/answer.js';
import { type AnswerValidationReport, type ResolvedAnswerValidation } from '../answer-validation/index.js';
import type { MessageMiddleware, ToolMiddleware } from './agent/middleware/types.js';
import { AgentBuilder } from './agent/AgentBuilder.js';
export type { SkillGraphOptions } from './agent/AgentBuilder.js';
export type { ProviderChoice, EscalationPolicy } from './agent/skillBrains.js';
import type { ThinkingHandler } from '../thinking/types.js';
export { AgentBuilder };
export type { AgentArtifactsOptions, AgentInput, AgentOptions, AgentOutput, AgentRecordingsOptions, ObserverDeliveryOptions, RunConfig, RunConfigContext, RunConfigFn, WriteProvenanceMode, };
/**
* `RunOptions` (footprintjs) + agentfootprint-domain correlation fields.
*
* `correlationId`/`traceId` are NOT footprintjs concepts — footprintjs's
* `RunOptions.env` is an intentionally closed infra bag (signal/timeoutMs/
* traceId only; see footprintjs's `ExecutionEnv`). These two ride separately
* into `Agent.currentRunContext` and from there into every emitted event's
* `EventMeta` via `buildEventMeta` (`RunContext` already declares both —
* `../bridge/eventMeta.ts`), so a caller can join agentfootprint's event
* stream against an external system (a upstream request id, an OTEL trace,
* a cross-tier why() join key) without threading it through tool args.
*
* `traceId` here wins over `env.traceId` when both are set; `env.traceId`
* remains a fallback since footprintjs already threads it to subflows.
*/
export interface AgentRunOptions extends RunOptions {
/** Domain correlation id — forwarded onto every emitted event's `EventMeta.correlationId`. */
correlationId?: string;
/** OTEL-style trace id — forwarded onto every emitted event's `EventMeta.traceId`. Falls back to `options.env?.traceId` when unset. */
traceId?: string;
/**
* The hosting CONVERSATION this run belongs to (9.4.0) — forwarded onto
* every emitted event's `EventMeta.sessionId`, and from there into whatever
* an observability strategy ships (the CloudWatch/AgentCore adapters
* serialize the whole envelope, so it arrives without their knowing).
*
* `runId` is per run; a session spans many. Without this, a shipped event
* stream can answer "what happened in this run?" and not "what happened in
* this conversation?", which is the question a session-oriented host is
* built around. `standingAgent` sets this for you from the request's own
* session id, on both `run()` and `resume()`.
*
* **It also decides the memory namespace when you named no identity
* (9.10.0):** a run with a session and no `identity` scopes its memory to
* `{ conversationId: sessionId }`, because a hosting session IS a
* conversation. An `identity` you pass always wins. See
* {@link AgentInput.identity} for the full ladder.
*
* Omit it for an unhosted run. It is never derived, guessed, or defaulted to
* the runId: an absent session and an invented one are different facts.
*/
sessionId?: string;
/**
* Who this run is for — the same tuple as `run({ identity })`, reachable
* from the doors whose input is a stored conversation rather than a
* message bag: `resumeOnError(checkpoint, { identity })` and
* `followUp(message, { identity })` (9.2.0).
*
* Before this existed, `resumeOnError` could not carry an identity at all,
* so every continued turn silently re-namespaced its memory under a fresh
* runId. Omitted, the conversation's own stored `identity` is used; given,
* it wins. On `run()` this is a second spelling of `run({ identity })` and
* the one on the input wins, since that is where the caller looked first.
*/
identity?: MemoryIdentity;
/**
* THIS TURN'S CLOCK, on the doors whose input is not a message bag —
* `followUp(message, { time })`, `resumeOnError(checkpoint, { time })` and
* `resume(checkpoint, answer, { time })` — read only by an agent with
* `.time()`, refused on one without. On `run()` it is a second spelling of
* `run({ time })`, and the one on the input wins. On `resume()` the paused
* turn's clock is KEPT (its words were resolved against it): a `time` that
* differs is recorded as a `clock-on-resume` row, never applied.
*/
time?: RunTime;
/**
* A redaction policy the CALLER adds for this run, joined with the agent's
* own `redact` — it can only add names, never remove one. This is how a
* policy reaches an agent run on someone else's behalf: a tool that runs
* another agent hands it the policy its own run is covered by, exactly as it
* hands `ctx.signal`:
*
* ```ts
* execute: (args, ctx) => specialist.run({ message: args.text }, {
* ...(ctx.signal && { env: { signal: ctx.signal } }),
* ...(ctx.redact && { redact: ctx.redact }),
* })
* ```
*
* Validated like `Agent.create({ redact })`. A pause carries it: the run
* commits it to its own state (`runRedaction`: its names, and each pattern
* as a reference), so `resume()` covers the resumed leg by it without being
* handed it again — except a pattern of your own, which is never compiled
* from a checkpoint: pass the same `redact` to `resume()` (or declare it on
* the agent), or the resume is refused (`ResumeRedactionError`,
* `'unknown-pattern'`). A `redact` passed to `resume()` adds to it. A
* conversation continued from the run (`followUp`, `continueFrom`) is a new
* run, covered by what it is given.
*/
redact?: RedactionPolicy;
}
export declare class Agent extends RunnerBase<AgentInput, AgentOutput> {
readonly name: string;
readonly id: string;
private readonly provider;
private readonly model;
private readonly temperature?;
private readonly maxTokens?;
private readonly maxIterations;
private readonly systemPromptValue;
/**
* Cache policy for the base system prompt (set via
* `.system(text, { cache })`). Default `'always'` — base prompt is
* stable per-turn, ideal cache anchor. CacheDecision subflow reads
* this when computing the SystemPrompt slot's cache markers.
*/
private readonly systemPromptCachePolicy;
/**
* Global cache kill switch from `Agent.create({ caching: 'off' })`.
* Threaded into agent scope at seed-time as `scope.cachingDisabled`;
* read by the CacheGate decider every iteration (highest-priority rule).
*/
private readonly cachingDisabledValue;
/**
* The CacheStrategy this agent runs. Chosen at build time from the
* provider's declared `promptCaching` (`cacheStrategyFor`) unless the
* consumer passes one explicitly.
*/
private readonly cacheStrategy;
private readonly cacheStrategyIsOverride;
private readonly cacheUnsupported;
private readonly registry;
/**
* The Injection list — Skills, Steering, Instructions, Facts (and
* RAG, Memory). Evaluated each iteration by the
* InjectionEngine subflow; active set is filtered by slot subflows.
*/
private readonly injections;
/** Skill-graph cursor resolver (`graph.nextSkill`), set when built via
* `.skillGraph(graph)`. Plumbed into the Injection Engine so route triggers
* are `from`-gated against the persisted `currentSkillId`. */
private readonly skillGraphNextSkill?;
/** Skill-graph reachable-set resolver (`graph.reachableSkills`), set when built
* via `.skillGraph(graph)`. Plumbed into the tool-calls handler so `read_skill`
* is gated to in-graph jumps. Undefined → gate off. */
private readonly skillGraphReachable?;
/** Skill-graph relevance entry scorer (`graph.scoreEntries`), set when built via
* `.skillGraph(graph)` with `.entryByRelevance()`. Drives the PickEntry stage.
* Undefined → no relevance entry routing (cold-start entry as before). */
private readonly skillGraphScoreEntries?;
/** The `to` end of every edge the mounted graph declares — which skills the graph
* WIRES. Empty for a graph-less agent. Read only by `openSkillIds()`. */
private readonly skillGraphEdgeTargets;
/** Skill-graph cursor resolver that also reports WHICH CLAUSE won
* (`graph.explainNextSkill`, 8.5.0). Threaded to the Injection Engine, which
* stamps the result on `context.evaluated` as `cursorMove`. Optional — a graph
* built before it existed falls back to `nextSkill` and emits no `cursorMove`. */
private readonly skillGraphExplainNextSkill?;
/** Skill-graph suppression reporter (`graph.supersededEntries`, 8.15.0) — the
* conditional entries whose `when` matched while the cursor was elsewhere.
* Threaded to the Injection Engine, which stamps them on `context.evaluated` as
* `supersededIds`. Optional — a graph built before it existed routes identically
* and simply emits no `supersededIds`. */
private readonly skillGraphSupersededEntries?;
/** Is the mounted graph a decision `tree()`? Derived at build time from
* `graph.nodes` (a tree is the only shape with `predicate` nodes) — no new
* public field on `SkillGraph`. Read for ONE thing: the read_skill gate's
* refusal has to explain that a tree has no cursor to jump (8.5.0). */
private readonly skillGraphIsTree;
/** The turn-start cascade wiring (SG-C, 9.17.0) — the graph's routing plan,
* the mount's posture/continuity, and the node-id set droppedResume checks
* against. Absent (every graph without the new options) → RouteTurn never
* mounts, the gate never postures, seed never restores a cursor, and the
* checkpoint shape is byte-identical. */
/** The folded per-skill brains + escalation + decider (9.19.0) —
* validated at `AgentBuilder.build()`. Undefined = no brain anywhere:
* callLLM, the gate, seed and RouteTurn wire nothing new. */
private readonly skillBrains?;
/**
* The evidence gate (9.35.0) — `.namesAndNumbersFromEvidence()`, resolved by
* the builder. Undefined for every agent that did not ask for it, and that
* undefined is the whole zero-cost guarantee: no decider change, no branch,
* no scope key, no event.
*/
private readonly evidenceGate?;
/**
* `.limitsTravelWithTheAnswer()` — whether the run's declared coverage
* travels with the final answer: appended as a block to a prose answer, or,
* when an output schema makes the answer TYPED, committed beside it as data
* (`answerCoverage()`), because JSON followed by prose is not JSON. False for
* every agent that did not ask for it; the RECORDING half of the coverage
* primitives does not consult it.
*/
private readonly limitsTravelWithTheAnswerValue;
/**
* `.scriptTravelsWithTheAnswer()` — the resolved arm, or undefined for every
* agent that did not ask. With `code: 'match'` on the gate it arms the
* script record (`scriptRecordArmed`); alone it also picks the final stage.
*/
private readonly scriptTravel?;
private readonly skillGraphCascade?;
/** Side channel for the conversation's inherited skill cursor (SG-C) —
* stashed by `applyContinuation`, consumed-and-cleared by seed. */
private pendingResumeSkillCursor?;
private readonly pricingTable?;
/** Normalized at construction: a bare number is `{ usd, onExceed: 'warn' }`. */
private readonly costBudget?;
/** Per-slot character budgets (8.11.0). Absent keys keep the slot default. */
private readonly contextBudget?;
private readonly permissionChecker?;
private readonly toolArgValidation?;
/** See AgentOptions.findings (9.101.0). Set by `.findings()` and undefined
* on every other agent — the one value every findings gate below is
* conditioned on, so an unarmed agent hands each stage exactly the deps
* it always did. `serve` is threaded to seed (the run constant
* `findingsServe`) and to call-llm (`findingsServe` in its deps) on an
* armed agent only; `answerAsk` (9.103.0) rides the same two threads
* as `findingsAnswerAsk`, and only when it is `'quote-facts'` — the
* default `'none'` threads nothing. `keepLedgerFacts` is resolved once, here in the
* constructor (this door over `AgentOptions.keepLedgerFacts`), into the
* `keepLedgerFacts` field below and threaded to the window stage on an
* armed agent with a window, where `stages/window.ts · buildWindowStage`
* spends it as the `'ledger-fact'` pin ceiling. */
private readonly findingsOptions?;
/** `.inputsLayer()` (honesty layer 2): mount the inputs layer even when no
* registered tool declares `askOrAssume` — for ruled tools only a
* ToolProvider serves, which the build cannot see. */
private readonly inputsLayerOption?;
/** DECLARED SOURCES (honesty layer 2): `.findings({ argumentSources: true })`
* or `.inputsLayer({ argumentSources: true })` — resolved ONCE, in the
* constructor, from either door; every declared-sources gate below reads
* this one value, so the two doors can never arm different halves. The
* findings ledger's own serve stays `findingsOptions`'s: without
* `.findings()` the arm plants `_findings.from` on ruled tools only. */
private readonly argumentSourcesArmed?;
/** `AgentOptions.argumentAskContext` (honesty layer 2): the host's own
* context for the inputs layer's batch ask, handed to the dispatch stage
* only when the layer is mounted. */
private readonly argumentAskContext?;
/** Whether this agent's chart mounts the inputs layer (`sf-inputs`) —
* decided ONCE, in `buildChart`: a registered tool declares rules, or
* `.inputsLayer()` was set. Gates the findings event bridge and the
* widened ledger restore. */
private inputsLayerArmed;
/** `.resultsLayer()` (honesty layer 3, step 7b): mount the results layer
* even when no registered tool declares a `ToolPeriod` — for tools a
* ToolProvider serves, and for tools that declare a period only on their
* results. */
private readonly resultsLayerOption?;
/** Whether this agent's chart mounts the results layer (`sf-results`) —
* decided ONCE, in `buildChart`: a registered tool declares a `ToolPeriod`,
* or `.resultsLayer()` was set. Gates the findings event bridge and the
* widened ledger restore, as the inputs layer's flag does. */
private resultsLayerArmed;
/** `.answerLayer()` (honesty layer 4): the answer's standing folded at the
* head of the final branch and served as data; `standingLine` — also one
* line appended to a prose answer, for the standings its mode names
* (`'always'` for `standingLine: true`). Undefined on every agent that did not
* ask, and that undefined is the whole zero-cost guarantee: no stage, no
* witness row, no stamp, no event, no bridge. */
private readonly answerLayerOption?;
/** `.time()` (the time layer, `time/`): the fallback zone, as read.
* Undefined on every agent that did not ask — then no clock is read, no
* time row is filed and `time` on a run is refused. */
private readonly timeOptions?;
/** The clock the CURRENT `run()`'s seed stamps — set by `run()` (a run with
* no zone gets an unknown one, never the server's), read by seed through `SeedStageDeps.timeClock`, and
* cleared when `run()` ends, so a later turn this agent did not start (its
* chart mounted in a composition) never stamps a clock nobody declared for
* it (`seedClockDraft`). */
private runClockDraft;
/** The incoming request each of this agent's OWN scopes serves — recorded
* by the executor that made the scope (`createExecutor`), so a scope keeps
* ITS run's request for as long as it lives: a stage still running after
* its run was aborted can never be served the next run's request. A stage
* mounted in another runner's executor reads its run's request by link
* (`memberRun.ts`). Never written to scope. */
private readonly scopeRequests;
/** The `time` the CURRENT resume passed, as read — TAKEN once by the first
* ToolCalls pass of the resumed leg (`ToolCallsHandlerDeps.time`), and
* cleared when `run()` or `resume()` ends. */
private resumePassedTime;
/** Tool choice by classifier (9.105.0, `.toolChoice()`): the classifier,
* the serve dial and the app's own doors. Threaded to the tools slot (the
* pick and the narrowing), to call-llm (`toolChoice: true`, the outcome
* row) and to both chart builders (`hasToolChoice`, the mount args) on an
* armed agent only — an unarmed agent hands each stage exactly the deps it
* always did. */
private readonly toolChoiceOptions?;
/** The declared ontology (9.106.0, `.ontology()`): the frozen map. Threaded
* to seed (the whole map, written once as the run constant `ontology`),
* to call-llm (`ontology: true`, the gate on every read of that key) and
* to both chart builders (`hasOntology`, the grouped boundary's crossing)
* on an armed agent only — an unarmed agent hands each stage exactly the
* deps it always did. The tool names its `via` edges name are checked
* against the registry in `buildChart`, beside `buildToolRegistry`. */
private readonly ontology?;
/** The map's tool → declaring-skills join (9.108.0), read off the registry
* in `buildChart` beside the `via` check; `undefined` when no `via` name
* is a skill's. Seed reads it through a thunk (the stage is built first). */
private ontologyTools?;
/** The opt-in tool-result ceiling in characters (9.11.0). Absent → results
* are never measured. See {@link AgentOptions.maxToolResultChars}. */
private readonly maxToolResultChars?;
/** Resolved check-in config (evidence-carrying human consent). Always
* present — defaults to `standard` evidence + the lexical scorer, so a tool
* that declares `checkIn` works even without a `.checkIn()` builder call. */
private readonly checkInConfig;
/** Where answered consent requests are spent (`AgentOptions.decisionLedger`).
* Undefined → this process's ledger, shared by every agent that names none. */
private readonly decisionLedger?;
/** Per-run config resolver from `.configure()`. Undefined for every agent
* that never called it — and the chart is then built exactly as before,
* with no scope writes and no scope reads added. */
private readonly runConfigFn?;
/** The agent's one window strategy. Undefined = no window stage, loop
* target unchanged, run byte-identical to an agent without it. */
private readonly windowStrategy?;
/** The tool-dispatch chain (`.toolMiddleware()`), in declaration order.
* Empty for every agent that never called it — and then the dispatch loop
* never walks a chain, never writes the ledger key, and never emits. */
private readonly toolMiddleware;
/** The message chain (`.messageMiddleware()`), in declaration order. Empty
* keeps seed synchronous and prepare-final untouched. */
private readonly messageMiddleware;
/** The instrument the window stage reads mid-run (adapter-reported usage +
* per-message provenance). Only ever created alongside a strategy. */
private readonly compactionMeterHandle?;
/** The window stage's seam to the receipt (9.93.0): what left the window at
* this iteration's head, read by the call-llm mint in the same iteration.
* Only ever created alongside a strategy, like the meter. */
private readonly evictedTurnsHandle?;
/** Snapshot read-tracking policy (#18/#14) — forwarded to the internal
* executor. Agent default is `'summary'` (cheap markers), NOT
* footprintjs's `'full'`. See AgentOptions.readTracking. */
private readonly readTracking;
/** Commit-log value encoding (#13c-B) — forwarded to the internal
* executor. Agent default is `'delta'` (append/delete verbs; growing
* arrays like `history` record only their tails — lossless, linear
* retained memory), NOT footprintjs's `'full'`. See
* AgentOptions.commitValues. */
private readonly commitValues;
/** Per-write read provenance (#P1) — forwarded to the internal executor.
* Default `'off'` (footprintjs's own): recordings stay byte-identical
* unless a consumer opts in. See AgentOptions.writeProvenance. */
private readonly writeProvenance;
private readonly credentialProvider?;
/** The claim-check store (9.21.0). When set, every tool's `ctx.artifacts`
* is this store bound to the run's scope. See AgentOptions.artifacts. */
private readonly artifactStore?;
/** The placement threshold (9.22.0) — the operator's ref-ing dial from the
* object form of AgentOptions.artifacts. Only ever set beside a store. */
private readonly artifactPlacement?;
/** Recordings-as-artifacts (9.26.0) — the operator's dial from the object
* form of AgentOptions.artifacts. Only ever set beside a store; absent
* means no recorder is ever attached and no run is ever recorded. */
private readonly artifactRecordings?;
/** The repeated-call nudge (9.26.0) — `false` only when the operator turned
* it off. See AgentOptions.repeatedCallNudge. */
private readonly repeatedCallNudge?;
/** The out-of-budget wrap-up (9.56.0) — `false` only when the operator
* turned it off. See AgentOptions.wrapUpAtMaxIterations. */
private readonly wrapUpAtMaxIterations?;
/** The last-tool-result pin (9.57.0) — set only when the operator named a
* value other than the default 2. See AgentOptions.keepLastToolResults. */
private readonly keepLastToolResults?;
/** The ledger-fact hold's ceiling (9.102.0), RESOLVED — set exactly when
* `.findings()` is on: `findings({ keepLedgerFacts })` over
* `AgentOptions.keepLedgerFacts`, `false` → `0`, nothing named →
* `DEFAULT_KEEP_LEDGER_FACTS`. Undefined on every unarmed agent, so the
* thread into the window stage reads as the decision it is. See
* AgentOptions.keepLedgerFacts. */
private readonly keepLedgerFacts?;
/** See AgentOptions.integrityPosture (9.60.0). Default 'observe'. */
private readonly integrityPosture;
/**
* The per-run disposition ledger, shared with the check sites by
* REFERENCE through build-time closures (the ProviderToolCache pattern —
* plumbing, never scope state). `run()` resets it; the run boundary
* files its report and clears it.
*/
private readonly integrityLedgerHolder;
/** Set at chart build: whether any tool in the FULL declared catalog
* (static registry + skill-carried tools) declared `argumentsFrom`. */
private integrityDanglingPresent;
/** See AgentOptions.noticeEmptyLookups (9.77.0). Default false — absent is
* byte-identical, save for the registered not-applicable ledger row. */
private readonly noticeEmptyLookups;
/** See AgentOptions.noticePriorTurnEvidence (9.83.0). Default false —
* absent is byte-identical, save for the registered not-applicable ledger
* row. */
private readonly noticePriorTurnEvidence;
/** Set at chart build: whether any tool in the FULL declared catalog
* declared `resultColumns` (9.78.0) — the other half of the column-type
* contract's arming. */
private integrityColumnsPresent;
/** See AgentOptions.checkColumnTypes (9.78.0). Default 'off' — absent is
* byte-identical, save for the registered not-applicable ledger rows. */
private readonly checkColumnTypes;
/** See AgentOptions.externalGrounds (9.72.0). Absent = door closed,
* byte-identical behavior. */
private readonly externalGrounds?;
/** What a run does when a declared credential needs 3LO consent (8.6.0).
* Default `'pause'`. See AgentOptions.onAuthorizationRequired. */
private readonly onAuthorizationRequired;
/**
* Consent blocks outstanding in THIS run, keyed by service — the
* `'tell-model'` honesty ledger, and the only place the authorization URL
* lives between the block and the caller.
*
* It is a plain instance field rather than a scope key on purpose: tracked
* state is the commit log, which is the snapshot, the narrative and every
* recording, and this value is a bearer capability. Cleared at the top of
* every `run()` / `resume()` so one run can never raise on another's block,
* and cleared per service the moment that credential is issued.
*/
private readonly consentOutstanding;
/** Evidence bridge (#5) — present iff a CAUSAL memory is mounted. */
private readonly causalEvidence?;
/** Observer delivery tier (RFC-001 Block 10). `'inline'` (default) is
* byte-identical to pre-10 releases; `'deferred'` routes the bridge
* recorders + consumer attachments through footprintjs's bounded
* capture queue. See AgentOptions.observerDelivery. */
private readonly observerDelivery;
/** Queue dials forwarded on every deferred attach (first attach
* configures the executor's single dispatcher). */
private readonly observerDeliveryOptions?;
/**
* Voice config — shared by viewers (Lens, ChatThinkKit, CLI tail).
* `appName` is the active actor in narration ("Chatbot called…").
* `commentaryTemplates` drives Lens's third-person panel.
* `thinkingTemplates` drives chat-bubble first-person status.
* Defaults to bundled English; consumer overrides via builder.
*/
readonly appName: string;
readonly commentaryTemplates: Readonly<Record<string, string>>;
readonly thinkingTemplates: Readonly<Record<string, string>>;
private currentRunContext;
/**
* The typed ask the CURRENT run's resume answered — set by
* `resume()` when a person's answer completed an `awaitingInput`, cleared
* by every other run start, so a pooled agent never marks one session's
* ask as another's re-ask. Read by the dispatch door through
* `ToolCallsHandlerDeps.answeredAsk`. The count crosses checkpoints on the
* awaiting-input shape itself (`repeat.count`), not here.
*/
private answeredAsk;
/**
* Memory subsystems registered via `.memory()`. Each definition mounts
* its `read` subflow before the InjectionEngine on every turn; per-id
* scope keys (`memoryInjectionKey(id)`) keep multi-memory layering
* collision-free.
*/
private readonly memories;
/**
* Optional terminal contract. Set via the builder's `.outputSchema()`.
* When present, `agent.runTyped()` parses + validates the final
* answer against this parser. `agent.run()` keeps returning the
* raw string; consumers opt into typed mode explicitly.
*/
private readonly outputSchemaParser?;
/**
* What the LOOP enforces about the output (7.26) — resolved by the builder
* when `.outputSchema()` was given `retries` or a `'tool-forced'` strategy.
* Undefined otherwise, and undefined is the whole of the byte-identical
* path: no branch is mounted, the decider is the function it always was,
* and the request is the one 7.25 sent.
*/
private readonly outputEnforcement?;
/**
* Optional 3-tier degradation for output-schema validation
* failures. Set via the builder's `.outputFallback({...})`. When
* present, `parseOutput()` and `runTyped()` fall through:
* primary → fallback → canned (in order; canned guarantees no-throw).
*/
private readonly outputFallbackCfg?;
/** Side-channel for `run({ continueFrom })` and `resumeOnError(...)` —
* when set, the seed function restores `scope.history` from this
* instead of starting fresh, and appends this turn's user entry when
* `appendsUserTurn` says so. Cleared on first read so subsequent runs
* start clean. */
private pendingResumeHistory?;
/** Retried request's repair state; never inherited by a new human turn. */
private pendingEvidenceRecovery?;
/** Its sibling for the folded spans. A restored conversation that dropped
* them would carry summaries nobody could unpack — the evidence would be
* destroyed by the act of continuing, which is the one thing retention
* exists to prevent. Cleared on first read, exactly like the history. */
private pendingResumeFolded?;
/** Its sibling for the findings ledger (9.101.0) — the model's standings
* on results this process never saw, carried by the checkpoint and
* restored as a stored record. Cleared on first read, like the history. */
private pendingResumeFindingsLedger?;
/** The last completed run's final answer — see `checkpoint()` for why it is
* kept here rather than read back from the recording. Undefined after a run
* that failed or paused. */
private lastRunAnswer?;
/** Last run's checkpoint owner, including explicit replay edits from a failed child stage. */
private checkpointTracker?;
/** Whether any run on this instance got past input admission — so a
* refused FIRST turn is reported as 'never-run', never as "continue from
* your earlier accepted checkpoint" when there is none. */
private hadAdmittedRun;
/** The id the CONSUMER chose, or undefined when they took the default.
* `this.id` cannot answer that question — it is `'agent'` either way — and
* the stored-conversation fingerprint refuses only on ids somebody picked
* (see `AgentRunCheckpoint.agent`). */
private readonly explicitId?;
/** The identity the caller gave the last run, or undefined when they gave
* none. Only an EXPLICIT identity is carried onto `checkpoint()`: the
* default is derived from a runId, and storing that would pin a whole
* conversation to the id of the one run that started it.
*
* Written by BOTH run doors, before the executor exists: `run()` from the
* call and the continued conversation, `resume()` from the call and the
* paused run's own record (`agent/callerIdentity.ts · callerIdentityOf`).
* A resume that inherited it instead stored another person's identity on
* a shared agent and none on a rebuilt pooled one. */
private lastRunIdentity?;
/** Run ids this instance minted, most recent last (bounded) — evidence that a
* checkpoint's `{ conversationId: '<runId>' }` is the per-run default
* (`agent/callerIdentity.ts`). */
private readonly mintedRunIds;
/** The self-explain evidence key of the run in flight (or served last):
* its session, else a host's per-request key, else undefined
* (`agent/servingConversation.ts`). */
private servingConversation?;
/** How long ONE tool teardown may take before the runner stops waiting.
* See `AgentOptions.toolTeardownTimeoutMs`. */
private readonly toolTeardownTimeoutMs;
/** The run in flight, by id — the whole of the one-turn-at-a-time guard.
* Set before the executor is built and cleared in `finally`, so a run that
* throws does not leave the agent permanently refusing. */
private inFlightRunId?;
/** The scopes this agent's OWN executors made — see the `isOwnRun` seed dep. */
private readonly ownScopes;
/** The question a person still owes this agent an answer to. Set when a run
* ends paused, cleared by `resume()`, `abandonPause()`, or a run that
* completes. Read by the `run()` guard — see `PendingQuestionError`. */
private pendingQuestion?;
/** The `.selfExplain()` binding, when the builder mounted one. Held so
* `canExplain()` can answer the same question the trace tools answer, from
* the same fact. Undefined on every agent that never called `.selfExplain()`. */
private selfExplainBinding?;
/**
* Optional `ToolProvider` set via the builder's `.toolProvider()`.
* When present, the Tools slot subflow consults it per iteration
* (Block A5 follow-up) — the provider's tools land alongside any
* tools registered statically via `.tool()` / `.tools()`. The
* tool-call dispatcher also consults it for per-iteration execute
* lookup so dynamic chains (`gatedTools`, `skillScopedTools`)
* dispatch correctly when their visible-set changes mid-turn.
*/
private readonly externalToolProvider?;
/**
* Optional rules-based reliability config (v2.11.5+). Set via the
* builder's `.reliability({...})`. When present, every CallLLM
* execution is wrapped in a retry/fallback/fail-fast loop driven
* by `preCheck` and `postDecide` rules. Consumed by `buildCallLLMStage`.
*/
private readonly reliabilityConfig?;
/**
* Resolved ThinkingHandler (v2.14+). The provider's DECLARED one
* (`thinkingHandlerFor`) UNLESS the builder explicitly set one
* (or `null` to opt out). When undefined, the NormalizeThinking
* sub-subflow is NOT mounted at chart build time — zero overhead
* for non-thinking agents.
*/
private readonly thinkingHandler?;
/**
* v2.14+ — request-side thinking budget. When set, every LLMRequest
* carries `thinking: { budget }`. AnthropicProvider translates to the
* wire format. Undefined = no thinking activation (default behavior).
*/
private readonly thinkingBudget?;
/** Threaded to footprintjs `flowChart()` so every node the Agent
* builder creates is observed by these recorders at build time. Set
* from `opts.structureRecorders`; undefined when consumer didn't
* attach any. */
private readonly structureRecorders?;
/** Per-COMPOSITION translator (L1b). Set from `opts.groupTranslator`;
* undefined when consumer didn't attach one. */
private readonly agentGroupTranslator?;
/** ReAct loop mode — 'dynamic' (default, re-engineer all slots each turn,
* flat chart), 'classic' (engineer context once, loop→Messages only, flat
* chart), or 'dynamic-grouped' (dynamic semantics + LLM turn wrapped in an
* sf-llm-call subflow for richer Lens grouping). Set from `opts.reactMode`.
* See AgentOptions. */
private readonly reactMode;
/** The recipes `.recipe()` applied, in declaration order. Held for ONE
* purpose: the run manifest's `recipes` rows, so a recording can say which
* composition produced the agent that answered. Undefined — never `[]` — on
* every agent built without one, which is what keeps the manifest of an
* agent that uses no recipes byte-identical to the one it emitted before
* they existed. */
private readonly appliedRecipes?;
/** The DECLARED skill map (9.50.0) — nodes + edges verbatim, captured by
* `AgentBuilder.skillGraph()`. Filed once per run as
* `agentfootprint.skill.graph_declared`; undefined = no graph, or a graph
* that could not state its map (the event then never fires). */
private readonly skillGraphDeclared?;
/** The maps kernel's plan (9.58.0) — present only when built with `.maps()`. */
private readonly mapsPlan?;
/** `.claims()` (9.61.0) — the declared claim contract, or undefined. */
private readonly claimContract?;
/** `AgentOptions.recordSystemPrompt` (9.50.0) — OFF by default. When true,
* every `stream.llm_start` carries the assembled system prompt verbatim as
* `systemPromptText`. */
private readonly recordSystemPromptValue;
/** `AgentOptions.recordReceipt` (9.88.0) — ON by default. `false` declines
* the mint at `callLLM`; nothing else about the run changes. */
private readonly recordReceiptValue;
/** The receipt memo every call of a run mints through — one per agent,
* rebinding itself to each run id (`core/agent/receipt/receiptDigests.ts`). */
private readonly receiptDigests;
private readonly answerValidationConfig?;
private readonly negativeClaimsConfig?;
private readonly typedNegativeClaimsConfig?;
private readonly applicationAnswerValidation;
constructor(opts: AgentOptions, systemPromptValue: string, registry: readonly ToolRegistryEntry[], voice: {
readonly appName: string;
readonly commentaryTemplates: Readonly<Record<string, string>>;
readonly thinkingTemplates: Readonly<Record<string, string>>;
}, injections?: readonly Injection[], memories?: readonly MemoryDefinition[], outputSchemaParser?: OutputSchemaParser<unknown>, toolProvider?: ToolProvider, systemPromptCachePolicy?: CachePolicy, cachingDisabled?: boolean, cacheStrategy?: CacheStrategy, outputFallbackCfg?: ResolvedOutputFallback<unknown>, reliabilityConfig?: ReliabilityConfig, thinkingHandlerValue?: ThinkingHandler | null, thinkingBudgetValue?: number, skillGraphNextSkill?: (ctx: InjectionContext) => string | undefined, skillGraphReachable?: (currentSkillId?: string) => readonly string[], skillGraphScoreEntries?: (ctx: InjectionContext, signal?: AbortSignal) => Promise<EntryScoring>, checkInOptions?: CheckInBuilderOptions, runConfigFn?: RunConfigFn, windowStrategy?: WindowStrategy, toolMiddleware?: readonly ToolMiddleware[], messageMiddleware?: readonly MessageMiddleware[], outputEnforcement?: ResolvedOutputEnforcement, skillGraphEdgeTargets?: readonly string[], skillGraphExplainNextSkill?: (ctx: InjectionContext) => CursorMove, skillGraphIsTree?: boolean, skillGraphSupersededEntries?: (ctx: InjectionContext) => readonly string[], skillGraphCascade?: {
readonly turnRouting?: TurnRoutingPlan;
readonly strictness: 'assist' | 'guard' | 'rails';
readonly continuity: 'turn' | 'conversation';
readonly nodeIds: ReadonlySet<string>;
}, skillBrains?: import('./agent/skillBrains.js').FoldedSkillBrains, evidenceGate?: ResolvedEvidenceGate, limitsTravelWithTheAnswer?: boolean, recipes?: readonly AppliedRecipe[], skillGraphDeclared?: SkillGraphDeclaredMap, mapsPlan?: import('../maps/engagement/types.js').EngagementPlan, claimContract?: readonly import('../integrity/unsupported-claim/check.js').DeclaredClaim[], answerValidationConfig?: ResolvedAnswerValidation, systemPromptCacheExplicit?: boolean, negativeClaimsConfig?: ResolvedNegativeClaims, scriptTravel?: ResolvedScriptTravel);
static create(opts: AgentOptions): AgentBuilder;
/**
* Cache policy for the base system prompt. Read by the CacheDecision
* subflow (v2.6 Phase 4) to know how to treat the SystemPrompt slot's
* cache markers. Exposed as a method (not direct field access) so
* the Agent's encapsulation boundary stays clean.
*/
getSystemPromptCachePolicy(): CachePolicy;
/**
* The artifact store this agent was built with, or `undefined` when none
* was attached (9.23.0).
*
* For COMPOSERS that resolve refs on the agent's behalf — the hosting
* layer's `artifact-head` / `artifact-get` wire operations redeem a
* screen's claim tickets against exactly this store. It is the store, not
* a scope-bound capability: whoever calls it owns composing the resolution
* scope (the hosting layer composes the requesting session's identity, the
* same tuple the run's own tools resolved under). Tools never touch this —
* `ctx.artifacts` is already bound to the run's scope, and that remains
* their only door.
*/
getArtifactStore(): ArtifactStore | undefined;
/**
* Start recording this run — or do nothing at all (9.26.0).
*
* Zero-cost when unused is not a claim here, it is the control flow: with
* `recordings` unset this returns `undefined` before touching the agent, so
* no listener is subscribed, no boundary recorder is attached, and the run
* is byte-identical to every earlier release.
*
* It is deliberately the SAME recorder a consumer would call by hand
* (`recordRun`'s own body, `recordRunWhere`). Nothing about this feature is a
* second recording implementation — the three connections that a
* hand-rolled version gets wrong (attach, subscribe, getCommitCount) are
* wired in exactly one place in this package, and this is a caller of it.
* The one difference is the timeline's filter: a recording filed FOR a
* person keeps only the events of that person's run.
*/
private startRunRecording;
/**
* Does `event` belong to the run this agent is serving (or served last)? The
* run-membership rule (`bridge/eventMeta.ts · eventBelongsToRun`) against
* this instance's run context — asked by the per-run recording and by the
* self-explain evidence, the two collectors that keep one run's events.
*
* @internal
*/
ownsEvent(event: AgentfootprintEvent): boolean;
/**
* File the finished recording into the artifact store.
*
* ── When ────────────────────────────────────────────────────────────────
* After the answer is composed, and only for a run that COMPLETED. A pause
* is not a finished run (the turn continues, and the resume mints its own);
* a throw never reaches here at all.
*
* ── Why it is awaited ───────────────────────────────────────────────────
* The answer is final before this begins and this cannot change it — but
* `run()` does return after the write rather than before, and that is a
* choice rather than an oversight. A fire-and-forget write is a recording
* lost whenever the process exits with the reply, which is precisely the
* serverless deployment that wants recordings most. The cost is one store
* write per turn, stated on the option.
*
* ── Why it can never fail the run ───────────────────────────────────────
* A full store, an unserializable snapshot, a bucket that 500s — none of
* them are facts about the ANSWER, which is already correct and already
* paid for. Turning "your recording was not filed" into "your request
* failed" would be the library deciding that its observability matters more
* than the user's turn. So every failure degrades to today's path: the
* answer is returned unchanged and the reason lands on the record as
* `agentfootprint.artifacts.refused`, where a sink can count it.
*
* The recording is FROZEN before the mint, so it can never contain the
* `artifacts.minted` event describing itself.
*/
private fileRunRecording;
/** One refusal fact for a recording that could not be filed. The message is
* the store's own, which never carries a payload — only what went wrong. */
private reportRecordingRefused;
/**
* The run's artifact scope, read from the finished run's own state.
*
* The SAME tuple `ctx.artifacts` bound during the run (`scope.runIdentity`,
* composed by seed from the caller's identity or derived from the session).
* Read rather than recomposed: a second derivation here could disagree with
* the one the run's tools used, and a recording filed in a different scope
* from the artifacts it describes is a recording nobody can find.
*/
private runArtifactScope;
/**
* The footprintjs `RuntimeSnapshot` from the most recent `run()` /
* `resume()`. Feeds Lens's Trace tab (ExplainableShell `runtimeSnapshot`
* prop) so consumers can scrub the execution timeline post-run without
* threading a recorder through the call site.
*
* `undefined` until a run has STARTED. After that it is the most recent
* run's snapshot — including across multiple turns of the same instance.
*
* **It is LIVE during a run, not a completed-runs-only view.** The executor
* is assigned at run start, so calling this from an event listener, a tool,
* or any other mid-run vantage point returns the IN-FLIGHT run, partially
* filled. That is deliberate (Lens scrubs a running agent through it), and
* it is why `.selfExplain()` captures at the terminal flush instead of
* resolving through this: evidence that is supposed to describe a FINISHED
* turn cannot be read from a getter that also answers about an unfinished
* one.
*
* **Under `redact` it is the SERVED snapshot** — footprintjs's redacted view
* (`RunnerBase.getLastSnapshot`): the placeholder wherever the policy
* selected a value, and no `initialState`. The agent's own logic never reads
* this getter; `checkpoint()`, `followUp()` and the crash checkpoint read the
* live run, so redaction never changes what the agent continues from.
*/
getLastSnapshot(): RuntimeSnapshot | undefined;
/**
* Structured narrative entries from the most recent run. Pairs with
* `getLastSnapshot()` for ExplainableShell's `narrativeEntries` prop.
* Empty array (not `undefined`) when no run has completed — matches
* the prop's expected shape so consumers can wire it directly without
* a defensive guard.
*
* Under `redact`, footprintjs wrote these lines through the run's rule: a
* read or write of a selected key reads `[REDACTED]`, and an `[emit]` line
* shows the event's served payload (`src/redaction/runRedaction.ts`).
*/
getLastNarrativeEntries(): readonly CombinedNarrativeEntry[];
/**
* The FlowChart compiled for the most recent run (or a freshly-built
* one if no run has happened yet). Feeds ExplainableShell's `spec`
* prop. Returning the cached chart matters: the spec must match what
* `getLastSnapshot()` traced, otherwise the Trace view's stage tree
* desyncs from the snapshot's runtime tree.
*/
protected getGroupTranslator(): import('./translator.js').GroupTranslator | undefined;
/** Agent has no nested-runner members (tools are function executors,
* not Runner instances). Slot ids + tool names live in `extra` so
* Lens can render an Agent card with slot rows + a tool list without
* inspecting `buildTimeStructure`.
*
* Memories are NOT included as members — they're an internal
* mechanism, not a composition-level concept. Consumers who need
* memory visibility should listen for `agentfootprint.memory.*`
* events at runtime. */
protected buildUIGroupMetadata(): import('./translator.js').GroupMetadata;
/**
* Parse + validate a raw agent answer against the agent's
* `outputSchema` parser. Throws `OutputSchemaError` on JSON parse
* or schema validation failure (the rawOutput is preserved on the
* error for triage). Throws a plain `Error` if the agent has no
* outputSchema set.
*
* Use this when you need to keep `agent.run()` returning the raw
* string for logging/observability and validate at a different
* layer; otherwise prefer `agent.runTyped()`.
*/
parseOutput<T = unknown>(raw: string): T;
/**
* Async sister of `parseOutput()`. When the agent is configured
* with `.outputFallback({...})`, this is the version that engages
* the 3-tier degradation chain on validation failure (the sync
* `parseOutput` always throws on failure for back-compat).
*
* Without `outputFallback`, behaves identically to `parseOutput`
* — returns sync-style on the happy path, throws OutputSchemaError
* on validation failure.
*/
parseOutputAsync<T = unknown>(raw: string): Promise<T>;
/**
* A typed answer that failed: rethrow, or — with `.outputFallback()` and an
* `OutputSchemaError` — hand it to the 3-tier fallback. The one settle for
* `parseOutputAsync`'s parse failure and `runTyped`'s native stop failure.
*/
private settleTypedFailure;
/**
* Corrective re-asks the agent's LAST run paid for, read off its own ledger.
*
* `undefined` when there is no last run to read — `parseOutputAsync` accepts
* any string, including one that never came from this agent, and reporting
* `0` for "I do not know" would be an invented fact in an event payload.
*/
private lastRunRetriesSpent;
/**
* Run the agent and return the schema-validated typed output.
* Convenience over `parseOutputAsync(await agent.run({...}))`.
*
* Throws `OutputSchemaError` on parse / validation failure UNLESS
* `.outputFallback({...})` is configured, in which case the
* 3-tier degradation chain (primary → fallback → canned) engages.
*
* Throws if the agent has no outputSchema set or if the run
* pauses (use `run()` directly when pauses are expected).
*
* With `.limitsTravelWithTheAnswer()`, nothing is appended to a typed
* answer: the limits its tools declared come back as data, from
* `agent.answerCoverage()`.
*/
runTyped<T = unknown>(input: AgentInput | string, options?: AgentRunOptions): Promise<T>;
/**
* Answer one turn.
*
* **`run()` is ONE turn, and it starts a new conversation every time.** The
* chart seeds its history from this call's `message` alone, so a second
* `run()` on the same agent does not continue the first: the model is shown
* one user message and will honestly tell your user it has not spoken to
* them before. That is deliberate — a primitive that quietly accumulated
* state across calls could never be used for one-shot work, and a hidden
* transcript is the most expensive thing an agent can carry.
*
* To continue a conversation, name it:
*
* - `agent.followUp(message)` — continue THIS agent's own last completed
* run. The one-liner, and what most callers want.
* - `run({ message, continueFrom })` — continue a conversation you are
* holding: `agent.checkpoint()` from an earlier turn, persisted anywhere
* and handed back. Works across a restart, a deploy, or a different
* machine, and is what `standingAgent` uses per session.
*
* Passing the same `identity.conversationId` to two `run()` calls does NOT
* continue anything — see {@link AgentInput.identity}. What a registered
* memory adds is *recall* of prior turns into the system-prompt slot, which
* is a different thing from the conversation itself.
*
* Two refusals guard the per-instance state this agent keeps; both replace
* behavior that used to succeed while quietly being wrong (9.2.0):
* {@link RunInFlightError} when a run is already in flight, and
* {@link PendingQuestionError} when the last run paused to ask a person
* something that nobody has answered.
*
* @example One turn, then a follow-up
* ```ts
* await agent.run({ message: 'Book me a table for two.' });
* await agent.followUp('Make it three.'); // remembers the table
* ```
*/
run(input: AgentInput | string, options?: AgentRunOptions): Promise<AgentOutput | RunnerPauseOutcome>;
/**
* Continue this agent's own last completed conversation.
*
* The one-liner for turn two and after. `run()` is one turn and starts a new
* conversation each time (see {@link Agent.run}); this reads the
* conversation off the last completed run, appends `message` as the next
* user turn, and runs from there — so the model sees what was actually said.
*
* Sugar over `run({ message, continueFrom: this.checkpoint() })` and nothing
* more: one restoration path, so the convenience cannot drift from the
* mechanism. Reach for `run({ continueFrom })` directly when the
* conversation comes from somewhere other than this instance's last run — a
* store, another process, a different machine.
*
* Refuses rather than guessing: {@link NoConversationError} when this agent
* has no completed run to continue (a "follow-up" that quietly became a
* first turn would be exactly the confusion this door exists to remove),
* and — through `run()` — {@link PendingQuestionError} when the last run
* paused to ask a person something, because a pause has its own door:
* `resume(checkpoint, decision)`.
*
* The conversation grows every turn and nothing here trims it; bounding what
* the model is shown is `.window()` / `.compaction()` / `.memory()`, not a
* silent cap on the way through.
*
* @example
* ```ts
* await agent.run({ message: 'Book me a table for two.' });
* await agent.followUp('Make it three.');
* await agent.followUp('And move it to 8pm.');
* ```
*/
followUp(message: string, options?: AgentRunOptions): Promise<AgentOutput | RunnerPauseOutcome>;
/**
* Drop the question this agent's last run paused to ask, on the record.
*
* A paused run is waiting on a person. Sending a different message while one
* is outstanding is refused ({@link PendingQuestionError}) because silently
* discarding a pending question makes a consent gate something any later
* message can walk around. When the question really is being dropped —
* the user changed the subject, the session timed out, the approval is no
* longer wanted — say so with this, and the next `run()` proceeds.
*
* Returns what was dropped (`undefined` when nothing was pending), so a
* caller can log or audit the abandonment rather than perform it blind. It
* does not touch the paused run's checkpoint: if you still hold that, it
* remains resumable.
*/
abandonPause(): {
readonly toolName?: string;
readonly toolCallId?: string;
readonly question?: string;
} | undefined;
/**
* Whether {@link Agent.selfExplain}'s why-questions have a run to answer
* from right now.
*
* `false` for two different reasons, both honest: this agent was not built
* with `.selfExplain()`, or it was and no turn has completed yet (evidence
* binds at the END of a run, never to the one in flight). Either way there
* is nothing to explain, which is what a caller routing a why-question needs
* to know before it routes.
*
* The model is told the same thing by the same fact — the trace tools answer
* "No completed run is available yet" and the skill body says to say so
* plainly. This is that answer, for the program.
*
* Evidence is kept per conversation, so on an agent serving several sessions
* this answers for the session of the run in flight — or, between runs, of
* the run served last.
*/
canExplain(): boolean;
/**
* Resume an agent run from a checkpoint produced by a prior
* `RunCheckpointError`. Unlike `agent.resume()` (which takes a
* `FlowchartCheckpoint` from an intentional pause), this takes
* an `AgentRunCheckpoint` (conversation-history snapshot) and
* replays the agent run with that history restored.
*
* The next iteration retries the call that originally failed —
* with the latest provider state (circuit breaker may have
* closed, vendor may have recovered, etc.).
*
* **Resume = REPLAY from the last completed iteration boundary,
* not exact-state restore.** Only the conversation history is
* restored; everything else re-seeds fresh:
*
* - **Tool re-execution / idempotency**: tool side effects from
* the FAILED iteration are not in the checkpoint. The model
* re-decides from the restored history and may re-issue those
* tool calls — they WILL execute again (there is no built-in
* toolCallId dedup). Mutating tools (payments, emails, DB
* writes) must be idempotent — key on stable call content, not
* `ctx.toolCallId` (a re-issued call gets a new id).
* - **Fresh `runId`**: the resumed run's events carry a new
* `runId`; use `checkpoint.runId` to correlate back to the
* failing run.
* - **Iteration counter + budget reset**: the resumed run starts
* at iteration 1 with a full `maxIterations` budget
* (`checkpoint.lastCompletedIteration` is diagnostic only).
* Token/cost accumulators also restart at zero.
*
* @example
* ```ts
* try {
* const result = await agent.run({ message: 'long task' });
* } catch (err) {
* if (err instanceof RunCheckpointError) {
* await checkpointStore.put(sessionId, err.checkpoint);
* // hours / restart later:
* const checkpoint = await checkpointStore.get(sessionId);
* const result = await agent.resumeOnError(checkpoint);
* }
* }
* ```
*/
resumeOnError(checkpoint: AgentRunCheckpoint | unknown, options?: AgentRunOptions): Promise<AgentOutput | RunnerPauseOutcome>;
/**
* Which identity a continued turn runs under: the caller's if they named
* one, otherwise the conversation's own.
*
* @internal
*/
private identityFor;
/**
* A run's `time`, read into the turn's clock draft (the time layer) — or
* refused, before the turn starts: `time` on an agent without `.time()` or
* a malformed value. No zone anywhere (the run's, else the builder's) is no
* refusal: the zone is UNKNOWN (`zoneSource: 'unknown'`, G15) — never the
* server's, never guessed; the person is asked it before any time they
* wrote is read. `undefined` on an agent without the layer.
*
* @internal
*/
private clockDraftFor;
/**
* The clock draft seed stamps (the time layer): the one `run()` read — or,
* for a turn this agent's `run()` did not start (its chart mounted in a
* composition, which passes no `time`), the builder's fallback zone with
* the turn's start as `now` (`zoneSource: 'builder'`, `nowSource:
* 'default'` — the same record a `run()` with no `time` files). With no
* fallback zone that turn's zone is unknown, as a `run()`'s is (G15): the
* server's zone is never used.
*
* @internal
*/
private seedClockDraft;
/**
* A `time` passed to a run or a resume, read — or refused by name: passed to
* an agent without `.time()` (a door that ignored it would look configured
* and do nothing), or malformed.
*
* @internal
*/
private passedTimeFor;
/**
* Install a per-run checkpoint tracker. Listens for the agent's own
* iteration and stream brackets and snapshots the conversation history into
* the tracker. Returns a stop function.
*
* It listens on the REAL-value path (`EventDispatcher · onRealEvent`), never
* on the served one: the crash checkpoint it builds is what
* `resumeOnError` replays to the model, and a resume checkpoint keeps real
* values — a redaction policy that names `history` must not turn the
* conversation a crash resumes into placeholders (`src/redaction/`).
*
* @internal
*/
private installCheckpointTracker;
/**
* Continue a run that paused to ask a person something, with their answer.
*
* WHO the resumed run is for is the paused run's caller, read off the
* checkpoint (`agent/callerIdentity.ts`) — never whatever this instance ran
* last; which SESSION, the call's `sessionId`, else the paused run's. A
* checkpoint names who it is for; it is not proof: a host that lets
* checkpoints leave its trust boundary signs them or keeps them
* server-side, and passes the identity it verified as `options.identity`.
*
* @throws ResumeIdentityConflictError when `options.identity` is not the
* paused run's caller identity — before anything runs.
* @throws DecisionRequiredError when a consent gate is answered with
* something other than a decision.
* @throws ResumeDecisionError when the decision does not answer the request
* this run is paused on: it names no request, another request, a call the
* checkpoint no longer holds (refused before anything runs), or a request
* a resume already answered (refused before the tool runs). The tool never
* runs on a refusal.
* @throws RunInFlightError when a run is already in flight.
*
* @example
* ```ts
* const outcome = await agent.run({ message: 'refund me', identity }, { sessionId });
* if (isPaused(outcome)) await agent.resume(outcome.checkpoint, 'yes', { identity, sessionId });
* ```
*/
resume(checkpoint: FlowchartCheckpoint, input?: unknown, options?: AgentRunOptions): Promise<AgentOutput | RunnerPauseOutcome>;
/**
* Fire `'run'`-scoped tool teardown — IF this run really ended.
*
* **Not on `finally`, and that is the whole point.** `finally` runs on every
* exit including a pause, and a pause exits TWO ways: a returned
* `RunnerPauseOutcome` and a thrown `PauseSignal`. A check-in on a
* code-interpreter call pauses the run so a person can approve the code —
* tearing the sandbox down there destroys the exact state the resume needs,
* and it fails QUIETLY, as a resumed run that "just re-ran everything".
* Both shapes are discriminated here and both are skipped.
*
* An error IS a terminal: the run is over, nobody is coming back, and a
* sandbox held by a run that crashed is the clearest kind of leak. Only a
* pause survives.
*
* Fired for the TURN, not for `currentRunContext.runId` — `resume()` mints a
* fresh run id, so a pause and its resume are one turn across two runs, and
* filtering on the id would leave everything a paused turn opened alive
* forever. Hosted turns are scoped to their session, preserving other pauses. See `ToolSessionTier.fireRun`.
*
* @param outcome what `run()`/`resume()` is about to return, or about to throw.
*/
private endRunToolSessions;
/**
* The conversation this agent's LAST completed run leaves behind, packed as
* the same `AgentRunCheckpoint` that `resumeOnError(...)` accepts. Store it,
* hand it back next turn, and the agent continues where it left off — across
* a restart, a deploy, or a different machine.
*
* Returns `undefined` before any run has completed or when the latest run
* was refused by input middleware. A refused input is not an admitted
* conversation. Keep an earlier accepted checkpoint to continue explicitly;
* `followUp()` after input denial raises `NoConversationError`.
*
* **Read from the run's own committed state, not from a second copy.** The
* history comes from the LIVE snapshot's `sharedState.history` — the state
* the run actually committed, never the served record (an agent's `redact`
* keeps the conversation out of `getLastSnapshot()`, never out of the
* conversation it continues) — cloned on the way out so a persistence layer
* can never mutate the live heap. The final assistant turn is appended from the answer
* `run()` returned, because nothing ever writes it back into `history`: the
* loop appends assistant turns only when they carry tool calls, and the turn
* that ends the run carries none. An agent that stored this conversation
* without that append would drop its own reply every turn and answer the next
* one having forgotten what it just said.
* Provider replay evidence and its normalized thinking travel with that
* accepted answer, including a terminal proof with empty text. The answer
* remains the delivered text, including output-policy edits; raw provider
* text is never substituted back. Evidence comes from live state, not the
* redacted observation. Denied, failed and paused runs append no final turn.
*
* Explicit thinking retirements recorded before transport also apply here:
* a failed grouped call cannot restore reasoning omitted from its request.
* This copies no other failed-stage state or tool effects into the history.
* Adds no events, no scope writes and no capture: every recording is
* byte-identical to an agent that never calls this.
*
* After a run that **paused**, this is the conversation as of the pause, with
* no answer appended — a pause is unfinished work, and pause/resume has its
* own carrier (`FlowchartCheckpoint`) that holds engine state this shape
* cannot.
*
* The conversation grows every turn and nothing here trims it. Bounding what
* the model is shown is the memory subsystem's job (`.memory(...)`), not a
* silent cap applied on the way to storage.
*
* @example
* ```ts
* await agent.run({ message: 'Book me a table for two.' });
* const conversation = agent.checkpoint(); // persist anywhere
* // …a restart later, on a fresh Agent:
* await agent.run({ message: 'Make it three.', continueFrom: conversation });
* // `continueFrom` admits the new turn through the 'input' middleware chain.
* // `resumeOnError` rechecks originalInput but replays stored history as-is;
* // it does not rewrite entries manually appended to that history.
* ```
*/
checkpoint(): AgentRunCheckpoint | undefined;
/** Called as each run/resume ends: remember a run that got past input admission. */
private noteAdmittedRun;
/**
* Whether the evidence gate's revision calls no tools: a posture that can
* revise (`'guard'`, `'rails'`) under an ENFORCING answer validation. There
* the answer may yet be withheld, so a revision must not run a tool that
* writes or sends again — and the library cannot tell one that only reads.
*/
private revisionWithholdsTools;
/** Whether input middleware refused the last run's message — the one reading both doors ask. */
private lastInputRefused;
/** Both checkpoint doors keep the repair budget separately from conversation text. */
private evidenceRecoveryOf;
/**
* The graph cursor a conversation carrier stores — the run's final
* committed `currentSkillId`, read from the SNAPSHOT (the state the run
* actually committed; both chart shapes map the advanced cursor onto it
* every iteration), and only when the mounted graph declared
* `continuity: 'conversation'`.
*
* ONE reader for BOTH carriers — `checkpoint()` (the conversation door
* `followUp()` walks through) and the crash checkpoint
* `RunCheckpointError` carries — the `foldedSpansOf`/`conversationOwner`
* rule: a conversation must not keep its place on one path and silently
* lose it on the other.
*
* @internal
*/
private continuityCursorOf;
/**
* The folded spans this run has committed, cloned on the way out.
*
* One reader for both carriers — `checkpoint()` and the crash checkpoint
* `RunCheckpointError` carries — so a conversation cannot keep its spans on
* one path and silently lose them on the other. The clone is the same
* promise `checkpoint()` makes about history: a persistence layer never gets
* a reference into the live heap.
*
* @internal
*/
private foldedSpansOf;
/**
* What the resume door checks a time field's answer with (the time design
* § 6.2, step T4): the paused turn's clock zone — the kept `clock` row, so
* a resume that passes another `time` changes nothing — and the app's
* catalog overrides. `undefined` without `.time()`: a `format` field is
* then judged for shape, order and offset only, in the default words.
*
* @internal
*/
private timeAnswerContextOf;
/**
* The findings ledger for a checkpoint (9.101.0) — `foldedSpansOf`'s twin:
* one reader for `checkpoint()` and the crash carrier, `undefined` when the
* run recorded no rows (so the key stays absent), and a detached copy
* otherwise, never a reference into the live heap.
*
* @internal
*/
private findingsLedgerOf;
/**
* The turn a checkpoint's history ends on — `findingsLedgerOf`'s twin, one
* reader for `checkpoint()` and the crash carrier. Only while an honesty
* layer is armed (the run constant `honestyLayers`, read from the same
* snapshot): that is when every ledger row carries its `turn` and the next
* run's stamp must continue from it (`AgentRunCheckpoint.turnNumber`).
* `undefined` otherwise, so the key stays absent.
*
* @internal
*/
private turnNumberOf;
/**
* The two owner facts every conversation carrier stamps — who the run was
* for, and which agent ran it (9.2.0).
*
* One reader for `checkpoint()` and the crash checkpoint, the same rule
* `foldedSpansOf` follows: a fact kept on one carrier and lost on the other
* is worse than a fact kept on neither. Both are absent unless the caller
* chose them, which is what keeps the fingerprint refusal narrow and the
* default `conversationId` out of storage.
*
* @internal
*/
private conversationOwner;
/**
* Restore a stored conversation onto the side channel `seed` reads.
*
* THE one restoration path — `run({ continueFrom })` and `resumeOnError()`
* both come through here, so the conversation door and the error door cannot
* disagree about what continuing means. It checks the agent fingerprint,
* restores history + folded spans, and adopts the conversation's identity so
* the continued turn writes its memory where the earlier turns are.
*
* `appendsUserTurn` is the difference between the two callers, and it is the
* whole difference. Continuing a conversation ADDS this turn's user message
* to the stored history; resuming after an error does NOT, because there the
* message is already the last user turn in that history and appending it
* would ask the same question twice.
*
* Only the FLAG is stashed, never the entry (9.112.2). The entry is written
* by `seed.ts · historyForTurn` from the message the `'input'` middleware
* chain let through; an entry built here, before that chain has run, is the
* caller's raw text — which is what a continued turn served the model
* through 9.112.1, while the record said the rewrite had happened.
*
* @internal
*/
private applyContinuation;
/** One turn at a time — see `RunInFlightError`. @internal */
private assertNotRunning;
/** A person's unanswered question outranks a new message — see
* `PendingQuestionError`. @internal */
private assertNoPendingQuestion;
/**
* Remember (or forget) the question this run ended on.
*
* A paused outcome sets it; anything else clears it, because a run that
* reached an answer has no outstanding question by definition. Reads the
* same `pauseData` fields `standingAgent.describePause` reads — the tool
* name and question the dispatch loop stamped — and invents nothing.
*
* @internal
*/
private recordPendingQuestion;
/**
* Hand the `.selfExplain()` binding to the agent that owns it.
*
* Called once by `AgentBuilder.build()`, immediately after `bindTo`. The
* binding stays the tool provider's to read; the Agent holds it only so
* `canExplain()` answers from the same fact the trace tools answer from,
* rather than from a second guess about whether a run has completed.
*
* @internal
*/
bindSelfExplain(binding: SelfExplainBinding): void;
/**
* Refuse, at run start, any declared messages-slot role this provider
* cannot carry inside its message list (7.21, D2).
*
* Run start rather than build time because the answer depends on the
* provider — and a decorated provider (`withFallback`, `withRetry`) is only
* the thing it is once composed. Run start rather than delivery time
* because a declaration that can never work should fail on the first call,
* not three iterations into a paid run. The delivery stage re-checks each
* message anyway, which is what catches roles that only exist at run time
* (a hand-built memory-recall subflow's formatted output).
*
* Called from the ONE place `run()` and `resume()` share, so no entry point
* can slip past it.
*/
/**
* The OPEN skills — the ones `read_skill` may reach from anywhere, whatever the
* graph's cursor says (8.4.0). Two clauses, both load-bearing:
*
* • `trigger.kind === 'llm-activated'` — the trigger that reads
* `activatedInjectionIds`, which is the ONLY thing a `read_skill` call writes.
* It is exactly "read_skill can really activate this", so admitting anything
* else (a hand-built `rule` injection, say) would replace one lie with another:
* the tool would answer "activated" and nothing would activate.
* • the graph declares no incoming edge to it — a bare model edge `.route(a, m)`
* is a declared, drawn, `from`-gated affordance ("from a, the model may hop to
* m"), and opening every such target would silently globalize it. What is left
* is skills the graph never mentions at all.
*
* That covers three shapes that were all dead before: `.selfExplain()`'s debug
* skill under a graph, a `.skill()`/`.skills()` registration beside a graph, and a
* skill listed in `skills[]` and wired to nothing (whose own check-up warning says
* "it can only be reached by the model via read_skill" — true again now).
*
* An open pick ACTIVATES but never moves the cursor — see the tool-calls gate.
* Computed once per chart build; the injection list is fixed at construction.
*/
/**
* The identity facts this run hands `tool.execute` (9.7.0).
*
* Read through an ACCESSOR from the chart (see `ToolCallsHandlerDeps.currentRun`)
* because the chart is built once and this changes every run.
*
* `identity` is `lastRunIdentity` — what the CALLER passed — and deliberately
* NOT `scope.runIdentity`, which is always populated and defaults to
* `{ conversationId: '<runId>' }` (or, on a session-bound run since 9.10.0,
* to `{ conversationId: sessionId }`). Handing a tool a synthesized
* conversation as "the identity" would let it key an isolated session on a
* fiction, and would make "absent" unrepresentable at exactly the layer that
* most needs to see it. The session-derived namespace is synthesized too, and
* is withheld here for that reason — `sessionId` beside it is the fact the
* transport really delivered.
*/
private toolRunFacts;
/**
* The teardown tier, built on FIRST registration.
*
* An agent whose tools never hold a session never allocates one, and its
* terminals stay a single `undefined` check.
*/
private toolSessions;
/**
* Turn one TEARDOWN report into a typed `agentfootprint.tools.session_*` event.
*
* Only the two closing events come through here. A start and a reuse happen
* inside `tool.execute`, where the dispatch loop still holds the scope, so
* those ride the ordinary emit channel and carry the stage they really
* happened in. These two fire after the run's last stage committed, and this
* is the one place that has to answer "from where?" without a stage to point
* at.
*
* **Built with `buildEventMeta`, never `minimalMeta()`.** `minimalMeta()`
* hardcodes `runId: 'consumer-scope'`, and a teardown event stamped that way
* cannot be joined to the run that OPENED the session — the exact
* unjoinability 9.4.0 spent a release fixing for credential events. So the
* meta comes from the registration-time run context, with a STATED pseudo-stage, the same
* move as the `'<stageId>#paused'` stamp at the pause boundary.
*/
private emitLateFact;
private emitToolSessionReport;
private openSkillIds;
/**
* The per-iteration `read_skill` offer builder — or `undefined` to leave the tool
* exactly as it has always been (8.5.0).
*
* `read_skill` enumerated every registered skill while the gate admitted only
* `reachableSkills(cursor) ∪ open`, so under a graph the model was handed ids it
* would be refused, every iteration, and could spend a whole run re-asking. The
* OFFER is rebuilt here from the same two functions the gate itself calls — one
* source of truth, so the menu cannot drift from the verdict.
*
* Two guards:
*
* • no graph AND no per-role skill visibility → `undefined`. A plain
* `read_skill` agent has no cursor and no gate; every registered skill
* really is reachable, and the tool keeps its byte-identical description.
* • `reactMode: 'classic'` → `undefined` for the GRAPH menu, plus a dev-mode
* warning. Classic composes the tools slot on turn 1 ONLY (see the Context
* selector's `includeStatic`), so a cursor-scoped menu would freeze at the
* cold-start cursor and keep advertising it for the rest of the run — a
* worse lie than the honest full catalog. `.selfExplain()` refuses under
* classic for exactly this caching reason; here the full catalog is a
* correct fallback, so this warns instead of refusing.
*
* Per-role VISIBILITY (9.11.0) survives classic, and that is not an
* inconsistency: a cursor moves every iteration, but who is asking does not
* change inside one run. A filter computed on turn 1 is still exactly right
* on turn 9.
*/
private readSkillOfferFor;
/**
* Does the configured checker ask to decide which skills this caller sees?
* (9.11.0)
*
* Absence is NO — see `PermissionChecker.governs`. This is the ONE switch:
* false and the menu, the resolver and the activation gate are all inert, so
* an agent with a checker that predates 9.11.0 composes the same prompt it
* always did.
*/
private governsSkillVisibility;
/**
* Which skills the caller's role may NOT see, asked once per iteration
* (9.11.0).
*
* Per iteration rather than per run because the checker is a port: a
* hub-backed one can legitimately answer differently as a grant is revoked
* mid-conversation, and caching the first answer would keep a withdrawn skill
* on the menu for the rest of the run. The cost is one `check()` per skill per
* iteration, paid only by agents that opted in.
*
* A skill is hidden when the checker returns anything other than `'allow'` /
* `'gate_open'` — and when the checker THROWS, which is the fail-closed half:
* a policy that did not answer did not say yes, and the same sentence governs
* the tool gate.
*/
private hiddenSkillIdsNow;
private assertDeliverableRoles;
/**
* Refuse, at run start, a `'tool-forced'` output strategy on a provider
* that does not put a forced tool choice on its wire (7.26).
*
* Run start rather than build time for the reason `assertDeliverableRoles`
* gives one method up: a decorated provider (`withFallback`, `withRetry`,
* a breaker) is only the thing it is once composed, and `withFallback`
* publishes the AND of its pair.
*
* Refusal rather than a quiet fall back to `'instruct'`, because the two
* are not interchangeable: one constrains the shape at generation, the
* other asks for it in prose. An agent that silently got the second while
* its config said the first would be a promise the recording could not
* even show was broken.
*/
private assertForcedToolChoiceSupported;
/**
* Refuse, at run start, a `'native'` output strategy on a provider that
* does not put a structured-output format on its wire, or on a model that
* does not take one — the `assertForcedToolChoiceSupported` twin, for the
* same reasons: run start because a decorated provider is only what it is
* once composed; a refusal because `'instruct'` asks in prose for what this
* strategy constrains at decoding, and a quiet swap would be config that
* lies.
*/
private assertNativeOutputSchemaSupported;
private createExecutor;
/**
* File the run-configuration manifest (9.41.0) — which adapters and
* strategies this run is about to use, stamped with the runId every other
* event of the run already carries.
*
* **Why it lives in `createExecutor` and not in `run()`.** `run()` and
* `resume()` both come through here, and both mint a fresh runId (a resumed
* run is a new run to every consumer joining on `meta.runId`, so a resume
* with no manifest would be a run whose arm nobody can name). One funnel is
* also how the next entry point cannot forget — the `beginIngress` lesson.
*
* **Why a direct dispatch rather than `typedEmit`.** There is no stage: the
* chart has not started. So it is built with `buildEventMeta` and a STATED
* pseudo-stage, exactly like the tool-teardown reports at the other end of
* the run — never `minimalMeta()`, whose hardcoded `runId: 'consumer-scope'`
* would make the one event whose whole job is to BE joinable the one event
* that cannot be joined.
*
* **Why it is gated on a listener.** Every typed event in this library is:
* the dispatcher drops what nobody subscribed to, and `EmitBridge` does the
* same upstream. The gate is what keeps an unwatched agent at one map
* lookup per run. It also means the manifest is not "always on" but "always
* there when anything is watching" — including `recordRun`, which subscribes
* with `'*'` before the run starts, so every recording carries one.
*/
/** Fresh per-run ledger — see AgentOptions.integrityPosture (9.60.0). */
private beginIntegrityLedger;
/**
* Dev posture only: throw {@link CheckerDeadError} on a run whose
* registered checkers demonstrably never ran, or whose canary went
* uncaught. Called on the SUCCESS path before the recording files, so the
* failure is the run's result, never a masked afterthought.
*/
private assertIntegrityAlive;
/**
* Did this run reach work a registered check should have seen?
*
* MEASURED, not asserted — and measured from a signal the integrity code
* does not write. `llmLatestContent` is committed by the LLM stage's own
* core path on every completed call, so its presence proves a call
* happened; its absence proves the run died or paused before one, and a
* checker that filed nothing THERE is not rot, it is a run that never got
* started. Deriving this from the checks' own encounter counts would be
* circular: an unhooked check would report "no work existed" and silence
* the very alarm it should be tripping.
*/
private integrityWorkExisted;
/**
* File the run's disposition rows as ONE `integrity.disposition` event and
* clear the ledger. On the finally path of both run doors — every exit,
* before the recording stops. Listener-gated like every typed event, and
* deliberately throw-proof: accounting must never change a run's outcome.
*/
private fileIntegrityDisposition;
private emitRunManifest;
/**
* File the DECLARED skill map (9.50.0) — `agentfootprint.skill.graph_declared`,
* once per run, right after the run-configuration manifest.
*
* Same funnel, same dispatch discipline, same listener gate as the manifest
* (see `emitRunManifest` above): `run()` and `resume()` both come through
* `createExecutor`, both mint a fresh runId, and a resumed run's consumers
* deserve the topology under the runId they are joining on. The payload is
* the map `AgentBuilder.skillGraph()` projected at mount — the author's
* nodes and edges VERBATIM, never inferred from runtime hops — so a
* recording carries the complete declared topology rather than the
* fired-edges lower bound that `context.evaluated.routing[]` names.
*
* No graph, or a graph that could not state its map ⇒ no event — absent,
* never guessed.
*/
private emitSkillGraphDeclared;
/**
* Flush the deferred-observer backlog of the most recent run's executor,
* then await async listener completions under a deadline (RFC-001 §11 —
* the serverless / graceful-shutdown pattern). Resolves immediately with
* zeros before the first run or when `observerDelivery` is `'inline'`
* and no recorder opted into `'deferred'` itself.
*
* `pending === 0` means a full drain; non-zero honestly reports
* continuations still outstanding at the deadline — never silent loss.
*
* @example Lambda-style handler
* ```ts
* export const handler = async (event) => {
* const reply = await agent.run({ message: event.message });
* // settle "one beat behind" observer work BEFORE the freeze:
* await agent.drainObservers({ timeoutMs: 5_000 });
* return reply;
* };
* ```
*/
drainObservers(opts?: {
timeoutMs?: number;
}): Promise<ObserverDrainResult>;
/**
* Did the last turn stop because a LIMIT cut it short — and if so, which?
*
* `undefined` on every normal finish, including a turn that used its whole
* `maxIterations` budget and then genuinely finished. It is set only when
* the model was still asking for tools and the run refused to run them:
* `maxIterations` was reached, or a `costBudget: { onExceed: 'halt' }` was
* crossed.
*
* ## Why this is a method and not part of the answer
*
* `run()` resolves to a bare string. There is nowhere in a string to write
* "…and three tool calls never ran", which is the same wall 8.6.0 hit with
* an outstanding credential consent — and there the turn raises, because
* handing back a plausible answer for work a tool never did is a silent
* success. This is not that. A limit you configured firing is the limit
* working, and the answer is sometimes real (a model can return content AND
* tool calls). So it does not raise; it records, in committed state, where
* it is provable after the fact — `getLastSnapshot().sharedState.stoppedEarly`
* is the same value (unless the agent's `redact` names it: this accessor
* reads the live run), and this is the short way to it.
*
* When the answer came back EMPTY the library also warns once on the
* console, because an empty string reaching a user is indistinguishable
* from a bug.
*
* @example
* ```ts
* const answer = await agent.run({ message: 'audit every log file' });
* const cut = agent.stoppedEarly();
* if (cut) {
* console.log(`stopped at iteration ${cut.iteration}: ${cut.reason}`);
* console.log(`${cut.pendingToolCalls} tool call(s) never ran`);
* }
* ```
*/
stoppedEarly(): AgentState['stoppedEarly'];
/**
* Did the last turn's answer FAIL this agent's `outputSchema` — and how (8.18.0)?
*
* `undefined` when the answer satisfied the contract, and on any agent with
* no `.outputSchema()`. Set on every run whose final answer was judged and
* rejected, including the default `retries: 0` case where the first answer is
* the only one there was.
*
* ## Why a method, when `runTyped()` already throws
*
* Because `run()` does not, and `run()` is what a server, a queue worker and
* `standingAgent` call. Before this existed, that caller received a string
* that violated a contract they had declared, with nothing anywhere saying
* so: the retries were billed, the ledger row was written under `retries > 0`
* and absent under `retries: 0`, and the answer looked exactly like a good
* one. `runTyped()` still throws `OutputSchemaError` — that is the caller
* ASKING to be raised at, and it is unchanged.
*
* `brokenBy` is the case worth a dashboard: the model's answer PASSED and one
* of your own `act({ output })` rules rewrote it into one that fails. The run
* stops re-asking when that happens — a deterministic rule breaks the next
* answer identically, so the retries would be bought for nothing.
*
* @example
* ```ts
* const answer = await agent.run({ message: 'summarise ticket 91' });
* const unmet = agent.outputContractUnmet();
* if (unmet) {
* log.warn({ stage: unmet.stage, error: unmet.error, brokenBy: unmet.brokenBy });
* return safeDefault; // …rather than shipping `answer` as typed data
* }
* ```
*/
outputContractUnmet(): AgentState['outputContractUnmet'];
/**
* Did the last turn's answer state names or numbers that appear in NO tool
* result (9.35.0)?
*
* `undefined` when every value was grounded — and on any agent without
* `.namesAndNumbersFromEvidence()`. Set on a turn that shipped flagged
* (`'assist'` / `'guard'`) and on one that was refused (`'rails'`, where
* `run()` also raised `UnsupportedValuesError`, so this is what a caller
* reads in the `catch`).
*
* `revised: true` means the model was asked once to correct the values and
* they survived that turn — the fact worth alerting on, because it is a
* model that cannot ground its own claims rather than one that slipped.
*
* Remember what the verdict does NOT say: values were invented. It cannot
* tell you whether a claim built from real values is true.
*
* @example
* ```ts
* const answer = await agent.run({ message: 'which port is down?' });
* const bad = agent.unsupportedValues();
* if (bad) log.warn({ values: bad.values.map((v) => v.value), revised: bad.revised });
* ```
*/
unsupportedValues(): AgentState['unsupportedValues'];
/**
* The last run's findings ledger (9.101.0) — the model's OWN standings on
* its tool results, as `.findings()` recorded them: `basis` rows (what a
* call was for, declared before it ran), `standing` rows (`fact` with the
* assertions stood on, `open`, `ruled-out`, `noise` — the LAST row per
* `toolCallId` is the current reading; earlier ones are quotable history)
* and `conflict` rows (two stood-on readings that disagree, witnesses by
* identity). Undefined when nothing filed a row — never an empty array
* standing in for "no findings", and an id with no standing row is
* undeclared, never `open`.
*
* THE INPUTS LAYER files here too (honesty layer 2): an agent whose tools
* declare `askOrAssume` gets `argument` rows — the library's verdict on each
* ruled argument of each call (`source: 'default'` when a tool's rule
* assumed the value, `'model'` when the model sent one the record does not
* trace), each stamped with its conversation `turn` — WITHOUT `.findings()`.
* A reader that switches over every row kind must skip one it does not know.
*
* THE RESULTS LAYER files here too (honesty layer 3, step 7b): `period` rows
* — one per call whose result declared the period its read covered, or whose
* tool declares a period argument: `covered`, `partly-held`, `not-held`,
* `unknown`, or `undeclared` (the result said nothing about its period).
*
* Detached from the execution record (`structuredClone`), so a caller may
* keep or mutate it without touching the run's state.
*
* @example
* ```ts
* await agent.run({ message: 'which port is down?' });
* const current = new Map<string, string>();
* for (const row of agent.findings() ?? []) {
* if (row.kind === 'standing') current.set(row.toolCallId, row.standing);
* }
* ```
*/
findings(): AgentState['findingsLedger'];
/** The library-composed negative statements and their recorded limits.
* Detached, in-process data for the caller. Record redaction applies to the
* snapshots and emitted events, not this caller-owned value. */
answerNegatives(): AnswerNegatives | undefined;
/** The last run's answer checks, detached from its execution record.
* Undefined means no terminal validation ran, never an implicit pass. */
answerValidation(): AnswerValidationReport | undefined;
/**
* The limits of the last run's TYPED answer, as data — what its tools
* declared they `checked`, did not check (`notChecked`) and can never see
* (`cannotCover`), folded across the run: duplicates said once, every entry
* kept.
*
* Set when the agent has an output schema AND `.limitsTravelWithTheAnswer()`:
* a typed answer is JSON, and the limits block that option appends to a
* prose answer would stop it being JSON, so for a typed answer the limits
* travel beside it instead — here, on `turn_end.answerCoverage`, and in
* `getLastSnapshot().sharedState.answerCoverage` (served, under the agent's
* `redact`), one value three ways. The
* answer string, and so `runTyped()`, is exactly the model's.
*
* On an agent whose inputs layer is armed (a tool declares `askOrAssume`),
* `assumed` lists the values a tool's rule filled this turn — the same rows,
* read the same way, that a prose answer's "Assumed" block prints — each in
* the tool's own argument view (`'REDACTED'` with `hidden` when that view
* hides it).
*
* `undefined` when the run's tools declared no limits and nothing was
* assumed, on a prose answer (the block is in the answer string there;
* `sharedState.coverageDeclared` holds the raw rows either way), and before
* the first run. Detached from the execution record, so a caller may keep
* or mutate it.
*
* `periods` (honesty step 7b) — present only when a call's result declared
* the period its read covered: one entry per declaring call, `{ toolName,
* toolCallId, queried, held, readAt? }`, the period as the tool declared it
* — the data twin of the prose block's `Period:` lines.
*
* @example
* ```ts
* const agent = Agent.create({ provider, model })
* .tool(replicationHealth) // returns coverage(verdict, { … })
* .outputSchema(Verdict)
* .limitsTravelWithTheAnswer()
* .build();
*
* const verdict = await agent.runTyped({ message: 'is replication healthy?' });
* const limits = agent.answerCoverage();
* for (const item of limits?.cannotCover ?? []) {
* console.log(`never covered: ${item.what} (${item.why})`);
* }
* ```
*/
answerCoverage(): AnswerCoverage | undefined;
/**
* How far the last run's answer stands — folded from its COMMITTED record,
* never from how sure the model sounded: `known` · `consistent` (checks ran,
* none fired — never "verified") · `not-sure` (with the reasons) · `ask` (the
* run paused on a question still waiting for a person) · `not-assessed`
* (nothing on the record could be checked).
*
* The same pure fold as `assessAnswer` on `agentfootprint/observe`, over this
* agent's last snapshot; it reads committed state, never events, so a later
* reader of the same recording folds the same standing. (This call folds the
* LIVE run. Under `redact` a recording holds the placeholder wherever the
* policy selected a value, and a fold of it stands only on what the policy
* left — the same standing unless a selected key is one the fold reads.) It resolves
* asynchronously because the fold is loaded through `import()` on first use —
* an agent that never asks does not carry it.
*
* Resolves to `undefined` when there is no answer to assess: before the first
* run, while a run is in flight, and after a run that threw or whose answer a
* rule refused (the typed error carries that verdict).
*
* `declarations` is the answer account's object, accepted as it is: the fold
* reads `tools[name].rowsAt`, where an object result keeps its rows.
*
* @example
* ```ts
* await agent.run({ message: 'Which ports on switch A are down?' });
* const a = await agent.assessment();
* if (a?.standing === 'not-sure') {
* for (const r of a.reasons) console.log(r.reason); // e.g. 'empty-undeclared'
* }
* ```
*/
assessment(declarations?: AssessmentDeclarations): Promise<AnswerAssessment | undefined>;
/**
* @internal What a mount of this agent's chart handed back
* (`core/terminalVerdict.ts` · `ReadsItsOwnOutcome`, read by every
* composition): the ANSWER, or the terminal verdict the chart ended on. A
* string, or the validated answer's `{ finalContent, answerValidationCommitted }`,
* is the answer; a verdict is read ONLY from the chart's state — handed over
* when the chart returned no result — with the one translation this agent's
* own run boundary (`finalizeResult`) uses. The `'tell-model'` consent record
* travels off tracked state, so it is raised by the run boundary only.
*/
outcomeOf(sfOutput: unknown): ChildOutcome;
/** The records a run ends on in place of an answer, in the order the boundary raises them. */
private refusalVerdictOf;
/** Answer validation's verdict on a run that reached the end (7.24): blocked, or never committed. */
private validationVerdictOf;
private finalizeResult;
private buildChart;
}