UNPKG

agentfootprint

Version:

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

974 lines • 53.4 kB
/**
 * Tool types — Agent's tool-call contract.
 *
 * Pattern: Strategy (GoF) — each Tool is a strategy for "how to execute
 *          this named operation given these args".
 * Role:    Consumer-facing shape. Agent.tool(...) accepts these.
 * Emits:   N/A (types only).
 */
import type { RedactionPolicy } from 'footprintjs';
import type { LLMToolSchema, ToolCapability } from '../provider-contract/types.js';
import type { ToolArtifacts } from '../artifacts/capability.js';
import type { ArtifactMeta } from '../artifacts/types.js';
import { type ToolWants } from '../artifacts/wants.js';
import type { Credential, CredentialNeed, CredentialProvider } from '../identity/types.js';
import type { MemoryIdentity } from '../memory/identity/types.js';
import { type ToolResultClass } from '../semantics/types.js';
import { assertResultColumns, COLUMN_TYPES, type ColumnDeclaration, type ColumnType, type ToolResultColumns } from '../integrity/column-types/types.js';
import { type AskComponent } from './askComponent.js';
import { type AskOrAssume, type ToolPeriod } from './declare.js';
import { type ToolScript } from './script.js';
import type { ToolSearch } from '../semantics/coverage/search/types.js';
import type { TimeContext } from '../time/wire.js';
import type { CheckInDemand } from '../core/checkin.js';
import type { TeardownOptions, TeardownScope } from './toolSessions.js';
/**
 * One executable tool the Agent can call.
 *
 * - `schema` is what the LLM sees (name, description, JSON schema).
 * - `execute` runs when the LLM requests this tool with the given args.
 *   Returns anything JSON-serializable; the framework forwards it back
 *   to the LLM as the tool result.
 */
export interface Tool<TArgs = Record<string, unknown>, TResult = unknown> {
    readonly schema: LLMToolSchema;
    /** Declare-and-push: a credential this tool needs. The framework resolves it
     *  BEFORE invoking and injects `ctx.credential`; it is NOT in `schema`, so the
     *  LLM never sees or fills it. */
    readonly needs?: CredentialNeed;
    /**
     * Declared artifact ARGUMENTS (9.22.0) — argument name → the artifact
     * `kind` it must resolve to (e.g. `wants: { dataset: 'dataset/rows' }`).
     *
     * The `needs` precedent applied to data: the MODEL passes the ~26-char
     * `art_…` ref as the argument (declare it `type: 'string'` in
     * `inputSchema`), and at dispatch — BEFORE `execute` — the framework
     * redeems it under the run's own scope and kind-checks the meta. The
     * handler receives the RESOLVED DATA in `args` (and the claim tickets on
     * `ctx.wanted`); a stale, unknown, or wrong-kind ref never reaches the
     * tool — the model reads a teaching refusal listing the live refs of the
     * wanted kind. Resolution rides `agentfootprint.artifacts.resolved`;
     * refusals ride `artifacts.refused` with `op: 'dispatch'`.
     *
     * **Whether the model MAY omit it is your `inputSchema`'s to say.** Name
     * the argument in `required` and dispatch refuses the call by name when no
     * ref arrives — the handler is never entered believing the framework
     * resolved something it did not. Leave it out and an omitted argument is
     * the model choosing not to use one: the tool runs, `args` carries no such
     * key, and `ctx.wanted` has no entry for it.
     *
     * Requires an attached store: an Agent refuses at BUILD when a statically
     * registered tool declares `wants` with no `artifacts` configured (config
     * that lies otherwise); other dispatch doors refuse at dispatch, by name.
     * Omitted → byte-identical behavior (nothing resolved, nothing measured).
     */
    readonly wants?: ToolWants;
    /**
     * Declarative demand for a human check-in BEFORE this tool runs — consent
     * for a consequential action, with an evidence pack riding the ask.
     * `'always'` trips on every call; a `(args, ctx) => boolean` predicate trips
     * selectively (e.g. only refunds over $1000). When it trips the tool-dispatch
     * loop pauses BEFORE execute and surfaces a `CheckInRequest`; the human
     * answers with `checkInApproved` / `checkInDeclined`. Omitted → byte-identical
     * behavior (no gate, no events, no pause). See `.checkIn()` on the Agent
     * builder to configure the evidence pack. Ordered AFTER the permission gate
     * and arg-validation, BEFORE credential resolution.
     *
     * Non-generic here (a `Tool` widens into `Tool[]` registries); `defineTool`
     * exposes a predicate typed to the tool's args at the CALL site.
     */
    readonly checkIn?: CheckInDemand;
    /**
     * Which REGISTERED screen component collects this tool's check-in decision
     * (9.24.0) — ids and props only, never markup. Rides the `CheckInRequest`
     * when the gate trips, so the answering screen renders its own registered
     * component instead of prose. Meaningless without `checkIn` and refused
     * beside its absence at `defineTool` — a component for a gate that never
     * fires is configuration that lies. A `propsRef` here must resolve in the
     * RUN's artifact scope when the gate trips (validated at raise time); a
     * check-in fires BEFORE `execute`, so the tool cannot mint it mid-call —
     * static declarations usually want inline `props`.
     */
    readonly checkInComponent?: AskComponent;
    /**
     * Where this tool came from — the name of the MCP server that served it.
     *
     * **Absent means "this agent's own".** A tool you wrote with `defineTool`
     * carries nothing here, and that absence is the fact: nobody else supplied
     * it. A tool that arrived over MCP carries the client's `name`
     * (`mcpClient({ name: 'aws-mcp' })`), because the same tool NAME can come
     * from two servers and a policy that cannot tell them apart governs both.
     *
     * It travels to the decision point as `ToolMiddlewareContext.toolSource` —
     * the tool-dispatch chain and `mcpServe`'s serving-side chain read the same
     * field.
     *
     * Set by `mcpClient` / `mockMcpClient`. `defineTool` never sets it, so it
     * cannot be spoofed by accident; a hand-built `Tool` may set it deliberately
     * when it is genuinely relaying another source's tool.
     */
    readonly source?: string;
    /**
     * What this tool touches, DECLARED by whoever wrote it (9.11.0).
     *
     * The framework never infers this. A tool's capabilities are not knowable
     * from its name, its schema or its description, and classifying them by guess
     * would rest a policy decision on a heuristic — so a tool that says nothing
     * gets nothing asked about it, exactly as before.
     *
     * **Enforced when both sides speak.** When a tool declares a capability AND
     * the configured `PermissionChecker` declares it `governs` that capability,
     * the dispatch loop asks once per declared capability, right after the
     * `'tool_call'` check allows — `check({ capability: 'external_net', target:
     * '<tool name>' })`. Either side silent → not asked, not refused. A denial
     * lands like every other refusal in the loop: the tool does not run and the
     * model reads a result it can adapt to.
     *
     * @example a tool the operator wants governed as a network egress
     *   defineTool({
     *     name: 'fetch_invoice',
     *     description: 'Fetch an invoice PDF from the billing service',
     *     capabilities: ['external_net', 'user_data'],
     *     inputSchema: { … },
     *     execute: async ({ id }) => …,
     *   });
     */
    readonly capabilities?: readonly ToolCapability[];
    /**
     * The refusing ceiling on THIS tool's result (9.20.0): when the handler's
     * stringified return exceeds `maxChars`, the model reads a teaching refusal
     * naming the true size, the ceiling and how to narrow — and the oversized
     * payload never enters context, history or any event. See
     * {@link ToolResultCeiling} for why refusal, not truncation. Omitted →
     * byte-identical behavior (nothing measured, nothing emitted).
     */
    readonly resultCeiling?: ToolResultCeiling;
    /**
     * The declared CLASS of this tool's results (9.53.0) — what kind of answer
     * it gives (`'triage'` — a health/fault verdict; `'inventory'` — a
     * population listing). Declared, never inferred (the `capabilities` law),
     * and validated at definition against the closed set. The
     * `check:semantics` gate keys its per-class rules on it — a `'triage'`
     * tool whose sample result declares no coverage fails the build by name.
     * Omitted → no class rules; the semantic-envelope rules still apply to any
     * result that carries the `af_semantics` marker.
     */
    readonly resultClass?: ToolResultClass;
    /**
     * THE ARTIFACT KIND A PLACED RESULT IS MINTED UNDER (9.70.0) — this tool's
     * result in the CONSUMER's vocabulary (`'dataset/rows'`), not the
     * framework's.
     *
     * Artifact PLACEMENT (`artifacts: { store, placement }`) checks an oversized
     * result into the store and hands the model a claim ticket. Absent this
     * field it mints under `tool-result/<toolName>` — honest, and unspendable:
     * `wants` is exact-match on kind BY LAW (no wildcards, no hierarchy), so a
     * downstream `wants: { dataset: 'dataset/rows' }` refuses the very ticket
     * the framework just minted, as a kind mismatch. Field-verified: the
     * consumer had to re-mint by hand at the seam, which is the framework
     * declining to carry its own ref.
     *
     * The fix is DECLARED, never inferred (the `capabilities` / `resultClass`
     * law) and never a loosening of the matcher: only the author knows what
     * their tool actually produces, and `wants` staying exact is what makes a
     * ticket a promise. Declaring `resultKind` makes the MINT speak the
     * consumer's vocabulary instead.
     *
     * A non-empty string; an empty or blank one is refused at `defineTool`,
     * because a kind is what a ticket is redeemed against and a blank kind
     * redeems against nothing. Omitted → exactly today's bytes
     * (`tool-result/<toolName>`, and no measurement at all without placement).
     *
     * @example a tool whose placed result a `wants` consumer can spend
     *   defineTool({
     *     name: 'get_rows',
     *     description: 'Fetch the rows of a dataset',
     *     resultKind: 'dataset/rows',
     *     inputSchema: { … },
     *     execute: async () => …,
     *   });
     *   // elsewhere: defineTool({ name: 'chart', wants: { dataset: 'dataset/rows' }, … })
     */
    readonly resultKind?: string;
    /**
     * WHAT THIS TOOL'S ROWS CONTAIN (9.78.0) — column name to type, the
     * sibling of {@link Tool.resultKind}. `resultKind` says what the result IS;
     * this says what it CONTAINS.
     *
     * THE MEASURED FAILURES, all three the same shape — a number became
     * something else, and nothing noticed at the seam:
     *
     *   1. `str(m.get("logical_unit_number") or "")` — LUN 0 is falsy, so LUN 0
     *      was stored as an EMPTY STRING on 2,094 mappings, and a host group
     *      missing the LUN an initiator probes first became indistinguishable
     *      from one that had it.
     *   2. `round(mib / 1024, 1)` rendered an 8 MiB disk as `0.0 GB`, which
     *      reads as NO DISK during a live incident.
     *   3. A family of tools returned their numbers as quoted strings
     *      (`"1240"`), which silently blanked every chart, because nothing
     *      downstream could tell a measure from a label.
     *
     * Declaring the columns gives the library something to check the rows
     * against. It catches 1 and 3. It cannot catch 2, and says so: the check
     * judges TYPE, never MEANING (see `COLUMN_TYPE_CEILING`, quoted verbatim
     * into every finding).
     *
     * A promise about what it NAMES, never a closed schema — an unlisted column
     * is allowed and never judged. Two spellings: a bare type, or the object
     * form when a column may legitimately hold nothing.
     *
     * ARMED BY TWO HALVES, like every write-seam check: this declaration AND
     * the operator's `checkColumnTypes` dial (default `'off'`). Omitted, or
     * with the dial off → exactly today's bytes; nothing is measured, no
     * finding is filed, and the model reads the rows the tool returned.
     *
     * @example the LUN report that lost its zeroes
     *   defineTool({
     *     name: 'host_group_mappings',
     *     description: 'The LUN mappings of a host group',
     *     resultKind: 'dataset/rows',
     *     resultColumns: {
     *       logical_unit_number: 'number',
     *       host_group: 'string',
     *       comment: { type: 'string', nullable: true },
     *     },
     *     inputSchema: { … },
     *     execute: async () => [{ logical_unit_number: 0, host_group: 'vdi-a' }],
     *   });
     */
    readonly resultColumns?: ToolResultColumns;
    /**
     * WHO OWNS THIS TOOL (9.60.0) — the identity edge, stamped at the one
     * moment the code demonstrably knows both ends: registration. Before
     * this field, ownership was only DERIVABLE (from the per-pass
     * InjectionRecord, or the maps kernel's MountedMap) — a checker asking
     * "who owns get_zones" between registration and the first tools-slot
     * pass had no answer, and a static `.tool()` registration's sourceId
     * was just the tool's own name. The Context Integrity checks read this
     * stamp and never infer identity; a tool without one is `unreachable`
     * to subject-joined checks, which the disposition ledger counts.
     * Omitted → exactly today's bytes (`source: 'registry'`).
     */
    readonly owner?: ToolOwner;
    /**
     * WHERE THIS TOOL'S ARGUMENTS COME FROM (9.60.0) — the names of tools
     * whose RESULTS ground what a caller passes here (`screen_fire` fires at
     * ids that `whats_here` listed). Declared by the author, never inferred:
     * only the author knows the dependency. The dangling-reference check
     * reads it at composition — when a declared ground's results have left
     * the window (`WindowRecord.droppedObservations`) and were not
     * re-established, offering this tool files a finding. Omitted → this
     * tool is never that check's subject, byte-identical.
     */
    readonly argumentsFrom?: readonly string[];
    /**
     * THE NAMED INGREDIENT TOOLS THIS TOOL IS COMPOSED OF (9.76.0) — the
     * registered tools its body calls through the run's own dispatch
     * (`ctx.tools`), declared by the author, never inferred.
     *
     * Consumer-side readers, which is what earns it a place here (the
     * `resultKind` / `argumentsFrom` law — a declaration rails read, nothing
     * that governs execution): the agent-build drift gate asserts every named
     * ingredient is a registered tool, so a runbook whose inventory tool was
     * renamed fails the BUILD by name instead of failing its first run; and it
     * joins the MCP `_meta` declaration list so a composed tool served over the
     * wire says what it is made of.
     *
     * Checked at AGENT BUILD, not at definition — the ingredients need not
     * exist before this tool is defined, and the catalog is only complete once
     * every `.tool()` registration has landed. Tools delivered by a
     * `ToolProvider` are invisible to the check (there is no build-time list);
     * with a provider configured the gate warns instead of refusing.
     * Omitted → nothing is checked, byte-identical.
     */
    readonly composedOf?: readonly string[];
    /**
     * WHETHER THIS TOOL'S PROCEDURE CAN RAISE AN APPROVAL GATE (9.76.0) — a
     * mid-run pause that asks a human before continuing. Declared, never
     * inferred (the `capabilities` law): the framework cannot see through a
     * tool boundary into an inner chart that gates.
     *
     * Consumer-side readers: composition-time checks that must refuse a gating
     * tool where a pause cannot be resumed (a fan-out branch — the runbook
     * grammar's compiler is the named reader). It does not govern execution;
     * the runtime pause refusal remains the backstop for a tool that omits it.
     * `false` is a declaration too ("this procedure never gates"), distinct
     * from saying nothing. Omitted → byte-identical.
     */
    readonly gates?: boolean;
    /**
     * FINGERPRINT THE REPEATED-CALL LEDGER ON ARGUMENTS ALONE (9.62.0) —
     * `'arguments'` tells `core/agent/repeatedCall.ts` that this tool's own
     * RESULT is not evidence of repetition and must not be folded into the
     * match key.
     *
     * The repeated-call nudge exists to catch a model calling the same tool
     * with the same arguments and getting nowhere. By default it proves "got
     * nowhere" by also requiring the RESULT to match — a `check status` call
     * returning a different status is progress, not a loop, and the default
     * rule (correctly) says nothing about it. That default quietly breaks for
     * a tool whose result is not a function of its arguments on purpose: a
     * screen/UI tool that stamps a fresh version number, timestamp, or cursor
     * into every answer so a human or a downstream cache can tell which
     * render is current. Call a tool like that twice with byte-identical
     * arguments and the default fingerprint never matches — the detector is
     * silently inert for it, forever. This was found in a real recorded
     * failure: an agent re-fired a completed navigation sequence and nothing
     * noticed, because each fire's fresh stamp made it look like new
     * information.
     *
     * Declared, never inferred — the `capabilities` / `resultClass` law. Only
     * the tool's author knows whether its result is signal or a stamp;
     * guessing from a name or a response shape would rest a detector on a
     * heuristic the framework cannot verify. The note's wording changes to
     * match when this fires (it stops claiming the result matched, because it
     * did not) — see `repeatedCall.ts` for both sentences.
     *
     * **This never suppresses execution.** The ledger only ever appends a
     * teaching sentence to a result the tool already returned, strictly AFTER
     * `execute` ran — the same anti-guarantee `runCheckpoint.ts` and
     * `Agent.ts` document for tool calls generally (durability replay,
     * resumed runs, and every retry of this kind still execute the tool; there
     * is no dedup here or anywhere else in this library).
     *
     * Omitted → byte-identical behavior: the ledger keeps folding the result
     * into the key exactly as it always has, for every tool that does not
     * declare this.
     *
     * @example a screen tool whose result always carries a fresh version stamp
     *   defineTool({
     *     name: 'render_screen',
     *     description: 'Render the named screen',
     *     repeatedWhen: 'arguments',
     *     inputSchema: { … },
     *     execute: async ({ view }) => `rendered ${view} @v${Date.now()}`,
     *   });
     */
    readonly repeatedWhen?: 'arguments';
    /**
     * PER-ARGUMENT RULES (the inputs layer, honesty layer 2) — what the library
     * does when a call leaves an argument out. `{ assume: value }`: the library
     * FILLS the value (the tool never does) and files it on the findings ledger
     * as `default`, so the answer's standing says the value was assumed.
     * `{ ask: question, choices? }`: the PERSON gives the value — the library
     * asks ONCE for everything a batch of calls left out, before anything in the
     * batch runs, through the typed input pause (`isInputPause`,
     * `agent.resume(checkpoint, { requestId, values })`), and files the answer
     * as `answered`. A present value runs as sent, filed as the model's own.
     *
     * The served copy of the schema drops a ruled argument from `required` and
     * says the rule in the property's description; the registry schema — which
     * `validateToolArgs` judges and `mcpServe` serves — is never edited.
     * Carried over MCP in `_meta.agentfootprint`. Declaring it arms the inputs
     * layer's mount (`sf-inputs`) on an agent that registers the tool; a ruled
     * tool that only a ToolProvider serves needs `AgentBuilder.inputsLayer()`,
     * and without it the call is refused rather than run unruled.
     * Omitted → byte-identical.
     *
     * @example
     * ```ts
     * askOrAssume: { window: { assume: '2h' } }
     * askOrAssume: { window: { ask: 'Which period should the search cover?', choices: ['1h', '24h'] } }
     * ```
     */
    readonly askOrAssume?: AskOrAssume;
    /**
     * WHICH ARGUMENTS SET THE PERIOD the answer covers, how their values are
     * spelled, and the facts about the source. Today's single argument
     * (`argument` + `spelling`: `lookback` `24h`, `signed-lookback` `-24h`,
     * `iso-range`, `wall-range` with a `zoneArgument`; or `accepts` for
     * several) is sugar over `forms` — every shape the tool takes: `bounds`
     * (two arguments, each an `iso` instant, `epoch-ms`, `epoch-s`, a `date` or
     * a `wall` time), `joined`, `object`, `day`, `lookback` with its `units`.
     * Facts: `direction`, `retention`, `maxRange`, `granularity`,
     * `filtersToAsked`. Each argument must carry an `askOrAssume` rule (an
     * `object` form's argument excepted — a ruled argument is flat). Rows for
     * them carry `period: true`. Under `.time()` the library fills a period
     * the model left out from the turn's one window, binds a sent one to the
     * person's window, and hands the tool `ctx.time`. Carried over MCP in
     * `_meta.agentfootprint.period`. Omitted → byte-identical.
     */
    readonly period?: ToolPeriod;
    /**
     * WHAT ONE CALL OF THIS TOOL SEARCHES — declared once, as typed data, so the
     * library composes each call's coverage from the arguments the call actually
     * ran with (`semantics/coverage/search/`). Every `inputSchema` property is
     * classified by role (`population`, `filter`, `period`, `source`, `cap`,
     * `page`, `inert`), with a default where `execute` applies one
     * (`whenOmitted`: a value, or `{ any: true }`); `fixed` names the qualifiers
     * the tool always applies; `gaps` the ground it never reaches, each with the
     * facet it `bears` on; `complete: 'within-arguments'` lets a call stand for
     * the whole population, filters and period in effect. The result says
     * `absent({ what, checked: 'declared' })` (or `coverage(rows, { checked:
     * 'declared' })`) and the funnel composes the rest: composition can only
     * under-claim, relative to a true declaration. Refused at `defineTool`, and
     * again at the agent's registration, when malformed. Carried over MCP in
     * `_meta.agentfootprint.search`, where it composes rows marked
     * `composed: 'remote'` that establish nothing. Omitted → byte-identical.
     */
    readonly search?: ToolSearch;
    /**
     * `'read'` — this tool changes nothing (design R6). A declaration a later
     * check reads, never one that governs execution: only an `effects: 'read'`
     * written in `defineTool`, on a LOCAL tool, counts
     * (`readOnlyByDeclaration`); a hand-set field, an MCP annotation or a remote
     * `readOnlyHint` never does, and a remote tool carrying it is refused at
     * registration (`effects-on-remote`). It never crosses MCP. Omitted →
     * byte-identical.
     */
    readonly effects?: 'read';
    /**
     * THIS TOOL RUNS A PROGRAM, and `argument` is the ONE string argument that
     * carries it (`language` is the program's language, the info string of the
     * fence it is shown in). Declared, never inferred: the library never reads
     * it off a tool's name, a property named `code` or the description, and a
     * tool that runs code without declaring it has no submissions on the record.
     *
     * While `.namesAndNumbersFromEvidence({ code: 'match' })` or
     * `.scriptTravelsWithTheAnswer()` is on, every call of the tool files one
     * `script` row on the findings ledger: the program SUBMITTED to the tool
     * (after any before-tool middleware) and what the runner reported. A fenced
     * block the model shows is then matched, byte for byte, against this turn's
     * submissions; the program itself is never evidence for a value.
     * `codeRunnerTool` declares `{ argument: 'code', language }`. Refused at
     * `defineTool` (and at the dispatch and MCP doors, `tools/script.ts`) when
     * `argument` is not a declared string property of the input schema.
     * Carried over MCP in `_meta.agentfootprint.script`. Omitted →
     * byte-identical.
     *
     * @example
     * ```ts
     * script: { argument: 'query', language: 'sql' }
     * ```
     */
    readonly script?: ToolScript;
    /**
     * THE APP'S OWN WORDS for what a call of this tool does — "loading the
     * attached data" — read by a PERSON. The registered name (`schema.name`) is
     * the model's vocabulary, and an app may forbid showing it, so the standing
     * line (`.answerLayer({ standingLine: true })`) names a reason's calls ONLY
     * by this — "Not sure — loading the attached data reported no matching
     * results" — and uses it in its assumed clause; the limits block marks the
     * items this call declared with it and names its `Period:` / "In progress"
     * lines by it. A call whose tool declares none is counted ("and 1 other
     * call"), never named there. Not every surface uses it yet: the time
     * layer's limits lines and the answer account's rows still print the
     * registered name (a known follow-up).
     *
     * One plain line of at most 60 characters (`lib/displayName.ts` — no control,
     * line separator or invisible format character), stored trimmed; refused at
     * `defineTool` and again at registration. Write it to read as the subject of
     * a sentence ("loading the attached data", "the order lookup"). It names the
     * call and changes nothing else: not the standing, not what the model sees.
     * Omitted → byte-identical.
     */
    readonly displayName?: string;
    execute(args: TArgs, ctx: ToolExecutionContext): Promise<TResult> | TResult;
}
/**
 * Refuse a `displayName` a person should not read, at definition time — the
 * rule (`lib/displayName.ts` · `displayNameProblem`) asked for a tool built by
 * `defineTool` and, at registration, for one assembled by hand.
 */
export declare function assertDisplayName(toolName: string, displayName: unknown): void;
/**
 * Whether the APP declared this tool read-only: `effects: 'read'` written in
 * `defineTool`, on a local tool (no `source`). Tool metadata, an MCP
 * annotation or a remote `readOnlyHint` never answer yes.
 */
export declare function readOnlyByDeclaration(tool: Tool): boolean;
/** Carry an app's read-only declaration through a library-owned, behavior-preserving wrapper. */
export declare function inheritReadOnly(from: Tool, to: Tool): void;
/**
 * Refuse an `effects` this library cannot honour — any value but `'read'` at
 * `defineTool`, and any `effects` on a remote tool at registration
 * (`effects-on-remote`: a remote tool's word never relaxes a check).
 */
export declare function assertToolEffects(toolName: string, effects: unknown, remote?: boolean): void;
/**
 * The stamped identity edge a tool carries (9.60.0): which subsystem it
 * belongs to, in the vocabulary the record already uses for sources.
 */
export interface ToolOwner {
    readonly kind: import('../events/types.js').ContextSource;
    readonly id: string;
}
/**
 * A declared cap on ONE tool's result that REFUSES instead of truncating
 * (9.20.0).
 *
 * WHY refusal: a truncated result reads as a complete one — the model cannot
 * tell the data ends where the cut happened, so it fabricates from the part it
 * saw. A refusal that names the size, the ceiling and the parameters to narrow
 * produces a clean retry instead (field-verified on a ~191k-char return). The
 * agent-level `maxToolResultChars` remains the OTHER answer — truncate with a
 * verbatim head and a marker — for operators capping tools they did not write;
 * this one is the TOOL AUTHOR's contract, and only the author knows which
 * parameters (`narrowBy`) make the retry smaller.
 *
 * The record keeps the truth: a typed `agentfootprint.tools.result_refused`
 * event carries the true size, and the delivered result carries status
 * `'invalid'` so `onToolStatus` edges can route on it.
 */
export interface ToolResultCeiling {
    /** The ceiling, in characters of the stringified result. Positive whole
     *  number; anything else is refused at `defineTool`. */
    readonly maxChars: number;
    /** Parameter names the refusal suggests narrowing by (e.g. `['limit',
     *  'fields']`). Optional; when present it must name at least one — an empty
     *  list is refused, because omitting the field is how "no suggestions" is
     *  said. */
    readonly narrowBy?: readonly string[];
}
/**
 * Refuse a `resultCeiling` this library cannot honor, at definition time —
 * naming the tool and the fix, never failing at the first oversized result of
 * the first run. Exported beside `lib/toolName.ts` · `assertValidToolName` for consumers
 * assembling `Tool` objects by hand.
 */
export declare function assertResultCeiling(toolName: string, ceiling: ToolResultCeiling | undefined): void;
/**
 * Refuse a `resultClass` outside the closed set, at definition time — naming
 * the tool, the value and the whole vocabulary (the `assertResultCeiling`
 * law: a declaration this library cannot honor fails HERE, never at the
 * first gate run of the first CI pipeline). Exported beside it for consumers
 * assembling `Tool` objects by hand.
 */
export declare function assertResultClass(toolName: string, resultClass: ToolResultClass | undefined): void;
/**
 * The COLUMN-TYPE CONTRACT's rule and vocabulary, re-exported HERE.
 *
 * They are DEFINED in `src/integrity/column-types/types.ts`, because
 * `src/integrity/` is a strict leaf that imports nothing outside itself and
 * the check has to be able to read the words. They are re-exported here
 * because `defineTool` and the MCP ingest both reach for their rules through
 * `tools/tools.ts` — one door for every declaration's rule, and no second copy
 * of any of them anywhere.
 */
export { assertResultColumns, COLUMN_TYPES, type ColumnDeclaration, type ColumnType, type ToolResultColumns, };
/**
 * Refuse a `resultKind` that could never be redeemed, at definition time —
 * naming the tool and the fix (the `assertResultCeiling` / `assertResultClass`
 * law: a declaration this library cannot honor fails HERE, never at the first
 * oversized result of the first run). Exported beside them for consumers
 * assembling `Tool` objects by hand.
 *
 * The one rule is non-blankness, and it is not a formality: the kind is what a
 * `wants` argument is matched against by exact string equality, so a blank or
 * whitespace-only kind mints a ticket no declaration can ever name — the same
 * refusal `assertToolWants` raises on the consuming end, raised on the
 * producing end too. There is deliberately NO charset or shape rule: the kind
 * is the consumer's vocabulary, and the library does not own it.
 */
export declare function assertResultKind(toolName: string, resultKind: string | undefined): void;
/**
 * Refuse an `owner` stamp with a blank half, at definition time — naming the
 * tool and what a blank half would do.
 *
 * An identity edge with a blank half is worse than none: the Context Integrity
 * checks JOIN on it, so a blank `kind` or `id` joins the wrong subjects rather
 * than no subjects. Extracted from `defineTool` (9.71.0) so the MCP ingest
 * boundary can hold the SAME line on a declaration that arrived over the wire —
 * one rule, one message, two doors.
 *
 * Deliberately NOT a closed-set check on `kind`: the union is enforced by the
 * compiler for anyone calling `defineTool`, and the value is only ever read
 * into a prose reason (`owned by <kind> '<id>'`), never switched on.
 */
export declare function assertToolOwner(toolName: string, owner: ToolOwner | undefined): void;
/**
 * Refuse an `argumentsFrom` edge that could never join, at definition time.
 *
 * The dangling-reference and unsupported-argument checks join on these names,
 * so a blank one — or a tool grounded by itself — would join the wrong subjects
 * or none at all. Extracted from `defineTool` (9.71.0) for the same reason as
 * {@link assertToolOwner}: the MCP ingest boundary enforces the identical rule
 * on a declaration a remote server sent.
 */
export declare function assertArgumentsFrom(toolName: string, argumentsFrom: readonly string[] | undefined): void;
/**
 * Refuse a `composedOf` list that could never be drift-checked, at definition
 * time — the {@link assertArgumentsFrom} law applied to composition: the
 * agent-build gate joins on these names, and a blank one — or a tool composed
 * of itself — would join the wrong subjects or none. The REGISTRATION check
 * (is every named ingredient actually registered?) deliberately does NOT
 * happen here: the ingredients need not exist before this tool is defined,
 * and only the agent build sees the complete catalog.
 */
export declare function assertComposedOf(toolName: string, composedOf: readonly string[] | undefined): void;
/**
 * Refuse a `gates` declaration that is not a boolean, at definition time.
 * Trivial for anyone the compiler vets; load-bearing at the MCP ingest
 * boundary, where a foreign server can put anything under the key and a
 * truthy string would silently declare a gate nobody wrote.
 */
export declare function assertGates(toolName: string, gates: boolean | undefined): void;
/** Options for one {@link ToolDispatch.call}. */
export interface ToolDispatchCallOptions {
    /** Abort signal for the inner call. Defaults to the outer call's own. */
    readonly signal?: AbortSignal;
    /**
     * Declare an inner ABSENCE survivable (9.76.0). By default a dispatch
     * consumer that composes answers (runbookAsTool) propagates an inner
     * `absent()` as its own answer — "the inventory found nothing" IS the
     * runbook's result, and pretending to a verdict over it would be the
     * confident-partial-answer failure. Pass `true` when the caller can carry
     * on without this source and will state the gap itself (usually as a
     * coverage entry). The raw dispatch delivered on `ctx.tools` returns every
     * result untouched either way — the propagation policy belongs to the
     * consumer that wraps it.
     */
    readonly allowAbsent?: boolean;
}
/**
 * The run's own tool dispatch, delivered at execute time as `ctx.tools`
 * (9.76.0) — how one tool's body calls ANOTHER registered tool through the
 * same map the model dispatches by, instead of importing its module and
 * building a second query stack.
 *
 * What it sees: the agent's static catalog (`.tool()` registrations plus
 * skill-carried tools) — the same dispatch map the tool-calls handler uses.
 * Tools delivered by a `ToolProvider` are NOT visible (there is no build-time
 * list), a stated caveat, not an accident.
 *
 * What an inner call gets: the outer call's own facts (credentials, signal,
 * progress) with `hasArtifacts: false` — an inner tool must not mint claim
 * tickets competing with the composed answer's own — and a derived
 * `toolCallId` naming the outer call it belongs to. A declared `needs` is
 * resolved before the inner execute (fail-closed: a service that requires
 * interactive consent refuses by name — an inner call cannot pause).
 */
export interface ToolDispatch {
    /** Is this name in the dispatch map? Provider-delivered tools answer false. */
    has(name: string): boolean;
    /**
     * Execute a registered tool and return its result exactly as returned —
     * a coverage envelope arrives as the envelope, an absence as the absence.
     */
    call(name: string, args: unknown, opts?: ToolDispatchCallOptions): Promise<unknown>;
}
/** Runtime context passed to tool.execute(). */
export interface ToolExecutionContext {
    /** Unique id of THIS tool invocation (matches stream.tool_start.toolCallId). */
    readonly toolCallId: string;
    /** Current iteration number of the ReAct loop. */
    readonly iteration: number;
    /**
     * The call's time (the time layer, step T5a — `time/wire.ts`): the
     * half-open range the call asks for (`asked`, edge `'exclusive'` — the
     * person's window when the library filled or bound it, the sent value read
     * back otherwise), the person's zone, the turn's frozen `now` and the
     * moment the library dispatched the call. Present only under `.time()` on a
     * call to a tool whose `period` declares forms, so the tool can declare the
     * `period.queried` its read covered without parsing its own argument. Over
     * MCP it travels in the call's `_meta.agentfootprint.time`.
     */
    readonly time?: TimeContext;
    /** Abort signal propagated from run({ env: { signal } }). */
    readonly signal?: AbortSignal;
    /**
     * The bound credential provider — the PULL escape hatch for dynamic needs.
     * Always present: when none is attached it's a fail-closed provider that
     * THROWS, so it never silently no-ops via optional chaining. Prefer the
     * declarative `needs` + `ctx.credential` for the common case.
     */
    readonly credentials: CredentialProvider;
    /** True when a real provider is attached. Branch on this for intentional
     *  degraded (no-credential) mode instead of relying on `undefined`. */
    readonly hasCredentials: boolean;
    /**
     * The claim-check store, bound to THIS run's scope (9.21.0) — shaped
     * exactly like `credentials`. Always present: with no store attached every
     * method throws a teaching refusal naming how to attach one
     * (`Agent.create({ ..., artifacts })`), so a missing store can never read
     * as an empty one. The scope (tenant/principal/conversation) is composed by
     * the framework from the run's identity/session and closed over — a tool
     * cannot name, widen, or replace it. `put` stamps `origin`
     * (`{ runId, toolCallId }`) from the run's own facts.
     */
    readonly artifacts: ToolArtifacts;
    /** True when a real artifact store is attached. Branch on this for an
     *  intentional no-store (degraded) mode instead of catching the refusal. */
    readonly hasArtifacts: boolean;
    /**
     * The claim tickets behind this call's resolved `wants` arguments (9.22.0)
     * — argument name → the `ArtifactMeta` whose data replaced the ref in
     * `args`. Present ONLY when the tool declared `wants` and at least one
     * declared argument resolved; absent otherwise (absent and empty are
     * different facts). The data itself is already in `args`.
     */
    readonly wanted?: Readonly<Record<string, ArtifactMeta>>;
    /** The credential resolved for this tool's declared `needs` (declare-and-push).
     *  Present only when the tool declared a need and it resolved successfully. */
    readonly credential?: Credential;
    /**
     * The run's own tool dispatch (9.76.0) — see {@link ToolDispatch}. Present
     * on the agent's dispatch paths; ABSENT at doors with no dispatch map
     * (`mcpServe`, the offline trace context, a hand-built context in a test).
     * Absent and empty are different facts: branch on the absence rather than
     * optional-chaining past it, and prefer a fail-closed refusal (the
     * `credentials` law) when your tool cannot work without it.
     */
    readonly tools?: ToolDispatch;
    /**
     * Report progress from INSIDE a long-running tool — "hop 3 of 12 done", said
     * mid-`execute`, while the call is still running.
     *
     * A tool call is otherwise ATOMIC on the record: `stream.tool_start` fires,
     * the handler runs for as long as it runs, and `stream.tool_end` carries the
     * result. For a forty-second twelve-hop walk that is one long silence — the
     * person watching cannot tell working from hung, and neither can an operator
     * reading the archive afterwards.
     *
     * Each call files one `agentfootprint.stream.tool_progress` event, in call
     * order, BEFORE this call's `tool_end`. The framework stamps `toolCallId`,
     * `toolName` and `iteration`: identity facts are never the tool's to state,
     * so a report cannot claim to be from another call. `payload` is the tool
     * author's own data, forwarded untouched.
     *
     * **Always present, never fatal.** With nothing listening it is a no-op that
     * drops the report; it never throws, never blocks (nothing is awaited), and
     * never changes what `execute` returns or what the model reads. A tool that
     * calls it zero times behaves exactly as it did before this existed.
     *
     * **`payload` must survive `structuredClone`** — it rides the ordinary emit
     * channel into every event sink and every recording, so plain data only (no
     * class instances, no live handles, no functions). Progress is TELEMETRY: it
     * never enters the tool result, the history, or the model's view.
     *
     * **What a PERSON sees (9.54.0).** One call, two faces. The structured
     * payload rides to the record untouched, exactly as above — and the live
     * status surfaces (`agent.enable.liveStatus(...)`, `attachStatus`) now show
     * the report too, so a long call is no longer silent in the browser. Because
     * `payload` is yours and typed `unknown`, the display contract is narrow and
     * literal:
     *
     *   - A top-level **string field named `message`** is shown to the person
     *     VERBATIM, trimmed, cut at **120 characters** with the cut stated
     *     (`… (+N more)`). `message` is the field MCP's own progress
     *     notification uses; nothing else in the payload is read.
     *   - **Anything else** — no `message`, a non-string one, an empty one, a
     *     bare string payload — shows the generic line instead:
     *     `` `<toolName>` reported progress (N so far)… ``. Your payload is
     *     never pretty-printed into that sentence. A status line is prose, and
     *     a tool's JSON is not a sentence anyone wrote.
     *
     * So `ctx.progress({ done: 3, total: 12 })` keeps its numbers on the record
     * and says "reported progress (3 so far)" on screen; adding
     * `message: 'Hop 3 of 12'` puts your own words there instead — and keeps the
     * numbers. Consumers override the wording by template key
     * (`tool.progress`, `tool.progress.generic`, or per tool).
     *
     * Doors with no event stream to file on — `mcpServe`, the offline
     * `callTraceTool` context — supply the no-op. A tool must not have to know
     * which door it is behind to be safe to call this from.
     *
     * @example a twelve-hop walk that says where it is
     *   execute: async (args, ctx) => {
     *     for (const [i, hop] of hops.entries()) {
     *       await walk(hop);
     *       // `message` is what the person reads; the rest is what the record
     *       // keeps. Drop `message` and the screen still says a report landed.
     *       ctx.progress({
     *         message: `Hop ${i + 1} of ${hops.length} — ${hop.id}`,
     *         done: i + 1,
     *         total: hops.length,
     *         hop: hop.id,
     *       });
     *     }
     *     return summary;
     *   }
     */
    progress(payload: unknown): void;
    /**
     * The run this call belongs to.
     *
     * **Absent when there is no run.** A call served over `mcpServe` is one call,
     * not a turn in a conversation, and minting a synthetic run id there would
     * fabricate a run that never existed. Branch on the absence.
     */
    readonly runId?: string;
    /**
     * The hosting conversation this run is bound to, when it is bound to one —
     * `HostRequest.sessionId`, threaded through `agent.run({ sessionId })`.
     *
     * Never derived, never defaulted to `runId`, never the anonymous latch.
     *
     * **It is caller data, not identity.** Anyone who can reach the host can put
     * any string here, including someone else's. Never key a live session on it
     * alone — compose it with tenant and principal via {@link toolSessionKey}.
     */
    readonly sessionId?: string;
    /**
     * The identity the CALLER supplied — `run({ identity })`, the same tuple
     * memory and the permission gate scope on.
     *
     * **Absent when the caller passed none.** Deliberately NOT the run's internal
     * `runIdentity`, which is always populated (it defaults to
     * `{ conversationId: '<runId>' }`, or to `{ conversationId: sessionId }` on a
     * session-bound run since 9.10.0): handing either of those to a tool would
     * publish a SYNTHESIZED conversation as if somebody had named one. A tool
     * that wants the session has `ctx.sessionId` for it, which is the fact the
     * transport actually delivered.
     */
    readonly identity?: MemoryIdentity;
    /**
     * The redaction policy the calling run is covered by — the agent's
     * `redact`, joined with any a composition or an outer caller added.
     * **Absent when the run has none.**
     *
     * A run this tool starts is a NESTED run, and footprintjs's law is that a
     * run's policy covers the runs nested in it. The library's own nested runs
     * take it for you — `flowchartAsTool`, `runbookAsTool` and the
     * `.selfExplain({ delegate })` debugger join it with their own. A tool that
     * starts another agent hands it on, 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 }),
     * })
     * ```
     *
     * It names what to keep out of RECORDS. It never touches the arguments this
     * tool receives: the tool runs on real values.
     */
    readonly redact?: RedactionPolicy;
    /**
     * Register cleanup for work THIS call started — a code-interpreter session, a
     * browser context, a lease.
     *
     * The tool learns its isolation key at execute time and registers cleanup for
     * exactly that key in the same breath; there is no other seam where both are
     * in hand. Registering twice under one `(tool, scope, key)` is a no-op that
     * keeps the FIRST cleanup (it holds the live handle) and refreshes liveness,
     * so calling this on every execute is the intended shape for a reused
     * session.
     *
     * Throws, naming the door, when `scope` is not in {@link teardownScopes} — a
     * capability nobody implements is a promise the library cannot keep.
     *
     * @example a session that lives as long as the run
     *   const key = toolSessionKey(ctx, 'run');
     *   const session = await runner.start({ key });
     *   ctx.onTeardown?.(() => session.stop(), { scope: 'run', key });
     */
    onTeardown?(cleanup: () => void | Promise<void>, options?: TeardownOptions): void;
    /**
     * Which teardown scopes this door can actually honour — `[]` means none ever
     * fires here.
     *
     * A FACT to branch on, exactly like `hasCredentials`, rather than an
     * `undefined` to optional-chain past: a tool that wants a run-scoped session
     * needs to know it is talking to a door that has no runs BEFORE it opens one.
     */
    readonly teardownScopes?: readonly TeardownScope[];
}
/**
 * Internal: registry entry keyed by tool name.
 * Consumer never sees this shape.
 */
export interface ToolRegistryEntry {
    readonly name: string;
    readonly tool: Tool;
}
/**
 * Convenience input for `defineTool` — flatter than `Tool` itself.
 * Consumers describe the tool inline; the helper assembles `schema`.
 *
 * `inputSchema` is a JSON Schema object (the same one the LLM will
 * see). For tools that take no arguments, pass `{ type: 'object',
 * properties: {} }` or omit and we'll default to that.
 */
export interface DefineToolOptions<TArgs, TResult> {
    readonly name: string;
    readonly description: string;
    readonly inputSchema?: Readonly<Record<string, unknown>>;
    /** Declare a credential this tool needs (declare-and-push). Resolved by the
     *  framework before `execute` and injected as `ctx.credential`. */
    readonly needs?: CredentialNeed;
    /** Declare artifact arguments: arg name → required artifact kind (see
     *  {@link Tool.wants}). The model passes the `art_…` ref; the framework
     *  resolves it before `execute` and the handler reads the data. */
    readonly wants?: ToolWants;
    /** Demand a human check-in before this tool runs (see {@link Tool.checkIn}).
     *  `'always'` or a `(args, ctx) => boolean` predicate. */
    readonly checkIn?: CheckInDemand<TArgs>;
    /** The registered screen component that collects the check-in decision
     *  (see {@link Tool.checkInComponent}). Requires `checkIn`. */
    readonly checkInComponent?: AskComponent;
    /** Declare what this tool touches (see {@link Tool.capabilities}). Consulted
     *  only when the configured checker declares it governs them. */
    readonly capabilities?: readonly ToolCapability[];
    /** Refuse (never truncate) a result over this many chars, teaching the model
     *  to narrow (see {@link ToolResultCeiling}). Omitted → byte-identical. */
    readonly resultCeiling?: ToolResultCeiling;
    /** The declared class of this tool's results — `'triage'` or `'inventory'`
     *  (see {@link Tool.resultClass}). Keys the `check:semantics` per-class
     *  rules. Omitted → no class rules. */
    readonly resultClass?: ToolResultClass;
    /** The artifact kind a PLACED result is minted under, in the consumer's
     *  vocabulary (see {@link Tool.resultKind}). Omitted →
     *  `tool-result/<name>`, byte-identical. */
    readonly resultKind?: string;
    /** What this tool's ROWS contain — column name to type (see
     *  {@link Tool.resultColumns}). Needs the `checkColumnTypes` dial too.
     *  Omitted → nothing measured, byte-identical. */
    readonly resultColumns?: ToolResultColumns;
    /** The stamped identity edge — see {@link Tool.owner}. */
    readonly owner?: ToolOwner;
    /** The declared argument grounds — see {@link Tool.argumentsFrom}. */
    readonly argumentsFrom?: readonly string[];
    /** The named ingredient tools this tool calls through `ctx.tools` — see
     *  {@link Tool.composedOf}. Drift-checked at agent build, when the catalog
     *  is complete. Omitted → nothing checked, byte-identical. */
    readonly composedOf?: readonly string[];
    /** Whether this tool's procedure can raise an approval gate — see
     *  {@link Tool.gates}. Omitted → nothing declared, byte-identical. */
    readonly gates?: boolean;
    /** Fingerprint the repeated-call ledger on arguments alone, ignoring this
     *  tool's own result — see {@link Tool.repeatedWhen}. Omitted →
     *  byte-identical (the ledger keeps comparing results, as always). */
    readonly repeatedWhen?: 'arguments';
    /** Per-argument rules — what the library does when a call leaves an argument
     *  out (see {@link Tool.askOrAssume}). Omitted → byte-identical. */
    readonly askOrAssume?: AskOrAssume;
    /** Which argument sets the period the answer covers (see {@link Tool.period}).
     *  Requires a rule on that argument. Omitted → byte-identical. */
    readonly period?: ToolPeriod;
    /** What one call of this tool searches, classified (see {@link Tool.search}).
     *  Refused here when malformed. Omitted → byte-identical. */
    readonly search?: ToolSearch;
    /** `'read'` — this tool changes nothing (see {@link Tool.effects}). Omitted →
     *  byte-identical. */
    readonly effects?: 'read';
    /** This tool runs a program, carried by the one string argument `argument`
     *  (see {@link Tool.script}). Refused here when that argument is not a
     *  declared string property. Omitted → byte-identical. */
    readonly script?: ToolScript;
    /** The app's own words for what a call of this tool does, read by a person
     *  (see {@link Tool.displayName}). Omitted → byte-identical. */
    readonly displayName?: string;
    execute(args: TArgs, ctx: ToolExecutionContext): Promise<TResult> | TResult;
}
/**
 * Ergonomic builder for `Tool`. Equivalent to constructing an object
 * literal with `schema` + `execute`, but flatter and safer — the name
 * + description live alongside the executor so they can't drift.
 *
 * @example
 *   const weather = defineTool<{ city: string }, string>({
 *     name: 'weather',
 *     description: 'Get current weather for a city',
 *     inputSchema: {
 *       type: 'object',
 *       properties: { city: { type: 'string' } },
 *       required: ['city'],
 *     },
 *     execute: async ({ city }) => `${city}: 72°F sunny`,
 *   });
 *
 *   const agent = Agent.create({ provider }).tool(weather).build();
 */
export declare function defineTool<TArgs = Record<string, unknown>, TResult = unknown>(options: DefineToolOptions<TArgs, TResult>): Tool<TArgs, TResult>;