UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

1,313 lines • 96.7 kB
/**
 * The TOOL contract: the side-effect word (`ToolEffect`), the content-origin word
 * (`ToolContentOrigin`), the reversibility verdict, the spec itself (`ToolSpec`), what a call returns
 * (`ToolReturn`), the nested-usage rollup a delegating tool reports (`NestedUsage` /
 * `NestedUsageAccum`) and the execute context every call receives. `ToolExecuteContext` lives here
 * rather than in a module of its own because it is the parameter of `ToolSpec.execute` — splitting the
 * pair would put one contract on two sides of a module boundary. Layer 0 vocabulary; `types.ts`
 * re-exports every name below, so no consumer's import changes.
 */
import type { TSchema } from "typebox";
import type { ThinkingLevel, ToolInputValidationContext, ToolInputVerdict } from "../internal/harness.js";
import type { DocumentContent, ImageContent, Model, TextContent } from "../internal/llm.js";
import type { AgentDefinition } from "./agent-definition.js";
import type { TaskEvent } from "./task-event.js";
import type { TaskSpec } from "./task-spec.js";
import type { BackgroundChildEvent } from "./delegation-frames.js";
import type { ModelRef } from "./model-seat.js";
import type { ToolFamily, ToolPathTarget, ToolRenderHints, ToolRuleFace } from "./tool-face.js";
/**
 * Side-effect class of a tool. Used by wake/resume reconciliation: when a call was interrupted
 * before its result was recorded, the outcome is unknown, so core must decide whether re-doing it
 * is safe. See {@link reconcileInterruptedSession}.
 * - `read` — no side effects (a query/lookup). Safe to call again.
 * - `idempotent` — has a side effect, but repeating it with the same args is equivalent to doing it
 *   once (e.g. `PUT` a known key, set-to-value). Safe to call again.
 * - `write` — a non-idempotent side effect (delete, append, charge, send). **Unsafe to repeat
 *   blindly.** This is the conservative default when `effect` is omitted.
 */
export type ToolEffect = "read" | "write" | "idempotent";
/** design/178 §3 — the content-origin classes (see {@link ToolSpec.contentOrigin}). */
export type ToolContentOrigin = "external" | "execution" | "local";
/** A native tool the model can call. `parameters` is a typebox schema; `execute` does the work. */
/**
 * design/77 §4 — what a {@link ToolSpec.reversibilityProbe} returns for ONE call.
 *
 * `reversible` alone decides: the gate tightens a surviving `allow` to `ask` unless it is exactly `true`,
 * and no optional member can widen that. They exist so the resulting approval card can say WHY, a
 * thing the probe knows at the moment it decides and nothing downstream can re-derive.
 *
 * Supply `cause` when the reason has parts a consumer should render itself; `reason` when it is prose
 * this engine cannot interpret. All three are optional and independent, so a probe written against the
 * original bare `{ reversible }` shape remains valid.
 */
export interface ReversibilityVerdict {
    reversible: boolean;
    /** Free-text account, neutralized and length-capped by the gate before it reaches any card. */
    reason?: string;
    /** Structured account — see {@link import("./checkpoint-store.js").ProbeCause}. Validated by the gate;
     *  a malformed value costs the DISCLOSURE only, never the ask. */
    cause?: import("./checkpoint-store.js").ProbeCause;
    /**
     * This demotion is STRUCTURAL, not the probe hesitating: the call crosses a
     * boundary the deployment declared, so a persisted allow rule must not retire the resulting ask.
     * The gate folds it into the #144 mandate family ("allow rules silence the CLASSIFIER's questions,
     * never a MANDATED one") beside an operator `shellGate:"always"` and the tool's own
     * egress/irreversibility marks: a matching rule is DISCLOSED as shadowed instead of clearing the
     * ask, and the card's "stop asking me this" offer is withheld rather than offered and then refused.
     *
     * Read on the demotion arm only — a `reversible: true` verdict ends the gate with no ask to mandate,
     * and this member cannot make one. It is the one member a DEPLOYMENT-supplied probe may set that the
     * engine acts on, and it is safe for the same reason the probe itself is: the effect is strictly
     * TIGHTENING (one more question survives one more configuration), so an over-claiming probe costs
     * its own deployment a re-confirmation and can never turn an ask into an allow.
     *
     * The built-in shell probe sets it for exactly one family: a listed reader naming a path outside the
     * session's allowed directories. That demotion HAS a clearing channel — widening the declared read
     * directories retires it — and where the probe can name the directory, {@link readRootCandidate} carries
     * it. A mandated ask WITHOUT that member has no configuration behind it: the rest of the family (an
     * operator `shellGate:"always"`, the tool's egress/irreversibility marks, an org demand, an out-of-root
     * arm with no named directory) is confirm-or-refuse, and must not be worded as though a grant answers it.
     */
    mandated?: boolean;
    /**
     * The WEAKER rung of the same ladder as {@link mandated}: a read boundary the deployment declared was
     * consulted and DECLINED TO JUDGE where this command reads — it named no path, so it neither declared an
     * objection nor vouched anything. The resulting ask is ORDINARY on purpose: every ANSWERER may settle it (a
     * person, the auto-mode classifier, a person's stored allow rule). What it excludes is the other kind of
     * clearing — an engine table that answers nothing and merely recognises a command SHAPE
     * (`readOnlyShellVerdict`, whose tables read program names and never paths). A mandate would take the
     * answerers out too, a trade this class was deliberately not given. TIGHTENING-ONLY like {@link mandated}:
     * one more question surviving one more configuration, never an ask becoming an allow, read on the demotion
     * arm alone. Read PRESENCE, never absence.
     */
    boundaryDeclined?: boolean;
    /**
     * The directory-scoped GRANT that would clear THIS ask, when the probe can name one: PRESENT ⇒ the only
     * thing between this call and a plain `reversible: true` verdict is the session's declared read
     * containment, and adding {@link ReadRootGrantCandidate.dir} to it makes the same call run with no card.
     *
     * ABSENT is the ordinary world and no claim that nothing would clear the ask — it covers every demotion
     * with no directory answer (a deny row, a read the walk could not read, an unenumerable expansion, a
     * shape refused whatever the roots say) and every probe silent on this member. Read presence, never
     * absence. ADVISORY display metadata, never adjudication input.
     */
    readRootCandidate?: ReadRootGrantCandidate;
}
/**
 * A directory-scoped grant a surface can offer on an approval card — see
 * {@link ReversibilityVerdict.readRootCandidate} and `AskRequest.readRootCandidate`.
 *
 * @contract shell.read_boundary.grant_candidate_is_the_grant — {@link dir} is the string to ADD, not a
 * string to show: a consumer that renders a shortened or home-relative form must send this exact spelling
 * back when the person accepts. Core mints one spelling for both errands, and the promise is over THAT
 * spelling reaching the directories the boundary compares against: a deployment that REWRITES a declared
 * directory — canonicalizing a symlinked one included — must rewrite the command's spelling the same way,
 * or it will compare a rewritten root against an unrewritten operand and ask again. Where the two differ
 * the ask was unclearable before this seat existed and still is; the offer names the directory the command
 * spelled, which is the only one derivable from the text.
 */
export interface ReadRootGrantCandidate {
    /** An absolute, lexically folded path in the boundary's own spelling; never a pattern. A DIRECTORY
     *  unless {@link covers} says otherwise. */
    readonly dir: string;
    /** `"exact"` ⇒ {@link dir} is the path the command NAMED, offered verbatim rather than a directory
     *  containing it. A consumer ADDS the string itself: expanding it into a pattern grants nothing. */
    readonly covers?: "exact";
    /** Always the literal `true` — a shape, not a value, so the seat cannot be widened into a "maybe this
     *  helps" hint without a visible type change. It claims THIS ask: not the deny layer (judged by pattern
     *  rather than by root, and unmoved by any grant), and not another ask the same call may raise. */
    readonly clearsThisAsk: true;
}
export interface ToolSpec<TParams extends TSchema = TSchema> {
    /**
     * Model-facing tool name. MUST NOT contain `__` — that separator is reserved by the PROTOCOL TABLE
     * (`protocol-table.ts`), the umbrella over every namespaced family the engine speaks: `mcp__<server>__<tool>`,
     * `a2a__<peer>__<skill>`, and whatever a third protocol appends. A caller tool name carrying it is rejected
     * at prepare-time (`config.tool_name_invalid`).
     *
     * The live reason (not the retired durable-resume name shim, RB-476-A): every table-driven site — the
     * namespace `parse`, the grouped display key, the permission-rule lanes, the tool-manifest origin
     * projection — decides "which protocol owns this name, and which peer inside it" from the prefix and the
     * FIRST separator. A caller tool named through that shape would be attributed to a remote peer that does
     * not exist, on surfaces where attribution is what a policy or an operator acts on.
     */
    name: string;
    /**
     * Backward-compatible model-facing names that should dispatch to this tool. New transcripts and tool rosters
     * should use {@link name}; aliases exist only so old model calls / SDK payloads / resumed sessions don't strand
     * across a canonical rename.
     */
    aliases?: string[];
    description: string;
    /**
     * R2 双形轴(2026-07-18 裁):the CLASSIC-profile variant of {@link description}. CC 2.1.212
     * keys its tool descriptions on `LT(model_id)` (simple short form vs classic long form); sema's
     * counterpart is {@link TaskSpec.promptProfile} — default "simple" (what CC serves every BYOM
     * model id). When the run resolves to "classic" and this field is present, it REPLACES
     * `description` at mount time. Absent = the tool has one form (description serves both profiles).
     */
    descriptionClassic?: string;
    /**
     * Human label for UIs. Defaults to `name`.
     *
     * ⚠️ Currently INERT (design/71 liveness sweep, 2026-06-12): mapped onto the tool object but no
     * runtime path or emitted event reads it — the model and TaskStream consumers only ever see
     * `name`. Kept for API stability; wire it into tool events or drop it before building on it.
     */
    label?: string;
    /**
     * G1 通告层 (CC `agent_listing_delta` parity): the delegation tool's agent-type roster (name + one-line
     * description), filled by `createSubagentTool` so the Runner's announcer can surface the
     * "Available agent types" reminder without reaching into the tool's closure. Deployment-authored tools
     * normally leave it unset. [c209-C]: this is now the roster's ONLY model-facing carrier (the tool
     * description/schema carry a static pointer sentence instead — cache-prefix immunity), delivered on
     * the FIRST user turn plus boundary drift deltas; `TaskSpec.attachments.agentListing` is default-ON
     * (explicit `false` opts out, with a config warning when a roster is mounted).
     */
    agentListing?: ReadonlyArray<{
        name: string;
        description: string;
    }>;
    /**
     * design/178 §3 (ruled 2026-08-08) — the MACHINE-readable twin of {@link agentListing}'s display
     * `(Tools: …)` note: per offered agent type, the allow/deny narrowing that decides which of the
     * offered pool a child of that type actually mounts. Filled by `createSubagentTool` beside
     * `agentListing`; a type with neither list gets the whole offered pool.
     *
     * Read by ONE consumer: the memory content-origin face, which classifies a delegation RESULT by the
     * child's tool face. A child that can reach a network or protocol tool returns content from outside
     * the trust boundary just as surely as calling that tool here would, and the parent's transcript is
     * where it lands. Static by construction — the child's face is known from its definition, so no
     * runtime tracking of what the child actually did is involved (that is the v2 ticket).
     */
    agentToolFaces?: ReadonlyArray<{
        name: string;
        allowTools?: readonly string[];
        denyTools?: readonly string[];
        canRedelegate?: boolean;
    }>;
    /**
     * design/178 §3 (ruled 2026-08-08) — the POOL a delegated child draws from, as the delegation tool
     * captured it. Read together with {@link agentToolFaces} by the memory content-origin face: the face
     * narrows, this is what it narrows FROM. It is the CHILD'S pool, deliberately: a delegation tool
     * carries its own tool set, and a parent that mounts nothing but the delegation tool can still hand
     * its children a network tool, so the parent's roster is the wrong thing to look at.
     *
     * ABSENT ⇔ the pool is not statically knowable — a spawn-time tool factory can inject tools whose
     * identity does not exist until the call happens — and the reader fails closed on that.
     */
    agentToolPool?: ReadonlyArray<{
        name: string;
        aliases?: readonly string[];
        contentOrigin?: ToolContentOrigin;
    }>;
    /** approval display projection (see AgentTool.approvalPreview): pure args→display value
     *  for human approval surfaces; clamped + throw-swallowed by the gate; never adjudication input. */
    approvalPreview?: (args: unknown) => unknown;
    /**
     * OPTIONAL input pre-validation — runs BEFORE the permission ask (and before every other tool-call
     * gate station: PreToolUse hooks, policy, the auto-mode classifier), AFTER schema validation. A
     * refusal (`{ ok: false, message }`) is returned to the model as a typed error result and NO
     * approval card is shown for the call; `{ ok: true }` / `undefined` lets the call proceed to the
     * gate unchanged. Upstream form (CC 2.1.250 `validateInput` → `{result:false, message, errorCode}`,
     * consulted ahead of the permission prompt): a call the tool would refuse on its own precondition —
     * an Edit/Write/NotebookEdit whose target file was never Read this session — used to reach the
     * operator as a card that could only ever fail; the model then retried the same failing call under
     * auto mode until the turn cap. A validator MUST be pure (read tracking state, path grammar; no
     * side effects, no writes, nothing the run observes) and MUST refuse with the SAME text its
     * execution would have produced for that input, so the model sees one refusal either way. A
     * validator that THROWS is read as "no verdict": the call proceeds to the gate and the fault is
     * disclosed via `RunnerDeps.onError` (phase `"hook"`) — a broken validator must not refuse tools.
     * The execution-time check stays in place (validate and execute are two reads; a direct
     * `AgentTool.execute` caller — durable replay — never passes this seat).
     */
    validateInput?: (args: unknown, ctx: ToolInputValidationContext) => Promise<ToolInputVerdict | undefined> | ToolInputVerdict | undefined;
    /**
     * [c209-C] Q4 — the model catalog names (`SubagentToolOptions.models` keys), filled by
     * `createSubagentTool` alongside {@link agentListing}. Rendered as the agent_listing INITIAL frame's
     * trailing "Models available for the 'model' parameter: …" line — the announce-face replacement for
     * the `(available: …)` / `(one of: …)` interpolations the tool description/schema used to carry
     * (no CC anchor: CC's model face is a static tier enum; sema's catalog is deployment-dynamic).
     */
    agentModels?: readonly string[];
    /**
     * core half — PER-TASK agent-roster rebuild seam, filled ONLY by `createSubagentTool`
     * (deployment-authored tools leave it unset). The delegation tool's roster is a mount-time SNAPSHOT
     * baked into its parameter enum/description (see the `SubagentToolOptions` snapshot note), so per-task
     * additions ({@link TaskSpec.agents}) cannot mutate the mounted instance — `prepareTask` instead calls
     * this to obtain a REBUILT ToolSpec whose roster is the UNION of the mount-time roster and the given
     * per-task definitions (a same-name per-task definition WINS over the mount-time one; built-in
     * shadowing/fork semantics re-derive over the merged set). Everything else about the tool
     * (pool/limits/depth/background lanes) rebuilds from the same options snapshot. The rebuild re-runs the
     * full assembly-time validation (duplicate/reserved names, string `model` refs against the catalog) —
     * fail-loud at task start, never deep inside a run. POLICY-NEUTRAL: this is a roster LIST injection
     * only — the child's effective policy chain (tighten-only parent inheritance via
     * `RunInternals.inheritedGate`) applies at spawn time, entirely downstream of the roster.
     */
    withAgents?: (agents: ReadonlyArray<AgentDefinition>) => ToolSpec;
    /**
     * design/136 §2.1.c — OPTIONAL pre-validation argument adapter (mirrors the engine
     * `AgentTool.prepareArguments` seam it is forwarded onto): map an alternative argument shape onto the
     * current schema BEFORE schema validation. MUST be idempotent and identity for already-canonical args
     * (the engine path applies it before validation AND the defineTool wrapper applies it again before its
     * own check). RB-479 note: its original LEGACY-replay purpose (pre-rename durable calls resolved via
     * the retired canonicalizer) is dead with RB-476-A; what remains is a generic third-party arg-migration
     * capability — the first-party legacy adapters riding it are booked for removal (RB-479-A③).
     */
    prepareArguments?: (args: unknown) => unknown;
    /**
     * Side-effect class for wake/resume safety. Defaults to `write` (conservative) when omitted:
     * an interrupted call of unknown effect is treated as possibly-applied and never auto-repeated.
     */
    effect?: ToolEffect;
    /**
     * design/70: marks a tool whose execution is an EXTERNAL write (egress) — an outward-visible,
     * effectively-irreversible action (open a PR, push a branch, send) as opposed to an internal side
     * effect. **Orthogonal to {@link effect}**: `effect` is the repeat-safety axis (wake/resume
     * reconciliation), `egress` is the blast-radius axis (the design/37 gate); reconcile/wake ignore it.
     * The gate NEVER auto-allows an egress tool: a surviving `allow` (default or policy) is tightened to
     * `ask`, so execution always passes an explicit ask-resolution — synchronous `onAsk`, a durable
     * suspend (design/45), or a deployment-authored approver. Headless stays deny (an external write
     * never happens without explicit confirmation). An egress tool must be repeat-unsafe: declaring
     * `effect: "read"`/`"idempotent"` with `egress: true` is a configuration error (fail-fast at
     * prepare) — omitting `effect` is fine (the default IS `write`).
     */
    egress?: true;
    /**
     * The tool's PERMISSION SEAT. `"passthrough"`: the tool states no permission of its own — under an asking permission mode
     * (`default` / `acceptEdits` / `plan` / `auto` / `dontAsk`) a call to it runs only when some layer STATES an allow (an allow
     * rule or allow-list entry naming it, an approver's yes, a PreToolUse hook's own `allow`); a policy fold in which nobody
     * stated it ("no objection") gets the mode's question (class `unstated`, origin `policy`), which dontAsk refuses, auto's
     * classifier may answer, and `bypassPermissions` never raises. The MCP and A2A materializers set it on a remote peer's
     * tool. Absent: the tool answers for itself (the engine's own stations and the deployment's declarations). Tightening only —
     * it can make a tool ask, never let one run.
     */
    permissionSeat?: "passthrough";
    /**
     * design/77 §4 (Gate 4 — irreversibility axis): how reversible this tool's effect is. A THIRD axis,
     * orthogonal to {@link effect} (repeat-safety) and {@link egress} (blast-radius). For office/finance
     * primitives (send money, file a return, send an email) where a wrong action cannot be undone, this
     * forces a durable human-approval suspend BEFORE the tool runs:
     *  - `"never"` — fully reversible; the gate leaves a surviving `allow` untouched (no ask).
     *  - `"maybe"` — reversibility depends on the args; the gate calls {@link reversibilityProbe} (when one is
     *     declared) on a surviving `allow` and tightens to `ask` UNLESS the probe reports reversible. A tool
     *     that declares a `reversibilityProbe` but omits `irreversibility` defaults to `"maybe"` (so it always
     *     enters the activation set and is gated). Since #502 the probe ALSO runs over a surviving `ask`
     *     — see {@link reversibilityProbe} for what that arm reads (only {@link ReversibilityVerdict.mandated})
     *     and what it costs; it tightens nothing there, because an ask cannot be tightened into an ask.
     *  - `"always"` — irreversible; the gate ALWAYS tightens a surviving `allow` to `ask` (like {@link egress}).
     *
     * The tighten mirrors the egress tighten (it only ever tightens allow→ask; a `deny`/`ask` is untouched),
     * runs immediately AFTER it, and routes through the SAME ask-resolution: synchronous `onAsk`, a durable
     * suspend (a new `{kind:"irreversible_ask"}` checkpoint → `status:"suspended"`), or a deployment approver.
     * Headless with no approver SUSPENDS durably (never a model-facing deny — that would be a reward-hack
     * retry surface), so an irreversible action never runs unattended without explicit confirmation.
     *
     * **Note (deliberate redundancy):** `egress` already means "effectively-irreversible external write"; an
     * `egress:true` + `irreversibility:"always"` tool is DOUBLE-tightened. Harmless (allow→ask is idempotent
     * under the deny>ask fold) — the two axes overlap on purpose, they are NOT strictly disjoint.
     */
    irreversibility?: "never" | "maybe" | "always";
    /**
     * design/77 §4: optional deployment-injected probe for an `irreversibility:"maybe"` tool. Given the call's
     * (post-hook) args, it reports whether THIS specific call is reversible. It is time-bounded (the
     * approval timeout when configured, a 30s default when absent — the probe wait is always finite) and
     * **fail-closed**: a non-`reversible` verdict, a timeout, or a throw all tighten to
     * `ask`. Declaring this probe defaults `irreversibility` to `"maybe"`. It is read from the spec at
     * prepare-time and captured in a closure — NOT a tool argument — so the model cannot monkey-patch it.
     *
     * **WHEN THE GATE CALLS IT — two arms, and the second is newer than this paragraph's first draft**
     * (#502; `irreversibility` must resolve to `"maybe"` for either):
     *  1. the surviving decision is `allow` — the TIGHTEN arm: the verdict decides allow→ask, and a
     *     tightening verdict's `reason`/`cause` ride the minted ask;
     *  2. the surviving decision is already `ask` — the MANDATE arm: the probe is the only source of
     *     {@link ReversibilityVerdict.mandated}, and an ask another layer raised needed it too. (Before
     *     #502 a policy that asked FIRST skipped this block entirely, so an out-of-root read never
     *     acquired its mandate and a covering allow rule retired the question — measured, not theorised.)
     *     Here the `reversible` verdict is DISCARDED (a probe may never un-ask another layer's question),
     *     `reason`/`cause` are not merged onto somebody else's ask, and only `mandated` is read.
     * The TIGHTEN is still `allow`→`ask` only — a `deny`/`ask` is never re-minted — but the INVOCATION
     * set is wider than the tighten's, and that difference is the deployment's to budget for: on an
     * ask-first policy the probe is called (and awaited, to the bound above) on every `"maybe"`-tier
     * call, and a probe that throws or times out reports through `onError` with `phase: "hook"` on
     * those calls too. A probe should therefore be a cheap, side-effect-free inspection of the args.
     *
     * **`reason` (optional, additive both ways)** — WHY this call was not proven reversible, in the probe's
     * own words, minted in the same pass that reached the verdict (so nothing downstream re-derives it).
     * Read ONLY on a tightening verdict: a `reversible: true` return ends the gate, and a timeout/throw
     * produces no verdict at all, so the cause travels exactly when there is an approval card to put it on.
     * The gate neutralizes and length-caps it, then carries it to both approval routes — the synchronous
     * `AskRequest.probeReason` and the durable park's `RiskDescriptor.probeReason`. Compatible in BOTH
     * directions by construction: a probe written against the older bare `{ reversible }` shape stays
     * type-correct and simply supplies no cause, and an engine that does not know the member ignores it.
     *
     * **`cause` (optional)** — the STRUCTURED alternative, for a probe whose verdict is machine-describable:
     * a `code` plus operand families as arrays with honest totals ({@link ProbeCause}), carried to
     * `AskRequest.probeCause` / `RiskDescriptor.probeCause`. Prefer it wherever the cause has parts. Prose
     * has to be written before the edge cases are known — a sentence about what a command DOES is false
     * wherever the check over-fires, and a multi-part disclosure flattened into one capped string loses
     * whichever part sorts last — whereas a code cannot be false and an array cannot be truncated into a
     * lie. `reason` remains for deployments whose cause is genuinely just prose this engine cannot read.
     * Both may be supplied; each is validated and carried independently.
     *
     * TRUST: the text is DEPLOYMENT-authored (and, for the built-in shell probe, derived from the model's
     * own command), so it is DISPLAY/TRIAGE metadata only — never adjudication input, and never a channel
     * that can widen a verdict. A probe cannot use it to auto-allow: `reversible` alone decides.
     *
     * That property is STRUCTURAL, and keeping it so constrains where the cause may be written. It is
     * carried as a member and deliberately NOT folded into the ask's `message`, because the message is
     * what an auto-mode classifier is handed as its `askMessage` — and that classifier may answer an ask
     * with `allow`. Text on the prose channel is therefore steering input to a decider that can clear the
     * very ask the probe raised, which is exactly the authority a probe must not have. Anything added
     * later that renders this cause must keep it on display surfaces only.
     */
    reversibilityProbe?: (args: unknown) => ReversibilityVerdict | Promise<ReversibilityVerdict>;
    /**
     * design/178 §3 — CONTENT-ORIGIN class of what this tool can bring into the session, a fourth axis
     * orthogonal to {@link effect} (repeat-safety), {@link egress} (blast-radius) and
     * {@link irreversibility}. It feeds long-term-memory write governance only — no gate/policy/roster
     * behavior reads it, and on a task without an engine-memory session it is fully inert:
     *  - `"external"` — the tool takes content from outside the deployment's trust boundary (network
     *    fetch/search families, external protocol channels). Invoking it marks the session's memory
     *    EXTERNALLY EXPOSED (one-way, durable). What the mark does at harvest is the deployment's
     *    {@link RunnerDeps.memoryProvenance} mode's question (design/336): under `"carry"` (default)
     *    ordinary entries commit WITH an engine-minted `origin` marker and instruction-form files are
     *    withheld; under `"off"` the harvest quarantines instead of committing — either way,
     *    third-party text never rides the automatic path into long-term memory UNMARKED.
     *  - `"execution"` — general execution (shell/exec families). NOT polluting by default — an
     *    execution tool CAN reach external content indirectly, but excluding every shell session from
     *    memory would disable memory for the main coding scenario; the write-side scans/fences remain
     *    as compensating controls, and `TaskSpec.memory.execIsExternalContent` upgrades this class to
     *    `"external"` for high-assurance deployments.
     *  - `"local"` — purely local reads/computation.
     * Classification is single-sourced with the tool definition: core built-ins declare theirs here;
     * a HOST tool that omits it is classified fail-closed as `"external"` (unknown = external) unless
     * the deployment exempts the name via `TaskSpec.memory.trustedTools`. A declaration BEATS that
     * allowlist (the allowlist is the channel for UNDECLARED tools, never a lever to re-grade a tool
     * that classified itself), and only a member of the closed vocabulary counts as one — an unreadable
     * value classifies `"external"` rather than exempting anything.
     *
     * Tools core mints on the deployment's behalf get the same seat one level up: an MCP server's whole
     * tool set is declared at its entry ({@link McpServerSpec.contentOrigin}, design/378), because the
     * host never touches those tool objects.
     */
    contentOrigin?: ToolContentOrigin;
    /**
     * Batch execution mode (design/64 §1 P1, CC reads-parallel/writes-sequential partition). When omitted,
     * `defineTool` derives it FAIL-CLOSED from `effect` (CC 2.1.206 `TOOL_DEFAULTS.isConcurrencySafe → false`):
     * `read` → `"parallel"`; `write`/`idempotent`/UNDECLARED → `"sequential"` — an unannotated tool never
     * silently rides a parallel batch, and two writes in one batch never race. Set explicitly to override.
     */
    executionMode?: "sequential" | "parallel";
    /**
     * design/120 P1.5 (CC `isConcurrencySafe(parsedInput)` parity): input-level concurrency
     * refinement for a non-`"parallel"` tool — return `true` to let THIS CALL run in a parallel batch
     * (and, under `streamingToolExecution`, start in-stream). An explicit `executionMode:"parallel"`
     * needs no refinement (always safe); a throw = false (fail-closed). In the batch/stream paths the
     * hook receives schema-VALIDATED (coerced) args — CC 206 `parsed.data` parity; a schema-invalid
     * call is judged unsafe before the hook runs. Still treat defensively (direct callers may pass raw).
     * The one real consumer is Bash's read-only classifier.
     */
    isConcurrencySafe?: (args: unknown) => boolean;
    /** typebox schema for the tool arguments. */
    parameters: TParams;
    /**
     * Large-result offload (design/30): set `false` to NEVER offload this tool's output (e.g. a file-read
     * tool whose output IS the deliverable — offloading then immediately re-reading would just churn).
     * Default: offload when the result exceeds the resolved threshold.
     */
    offload?: boolean;
    /** Per-tool offload threshold (chars), overriding the global {@link RunnerDeps.toolResultThresholdChars}.
     *  Same contract as that global knob (CLS-A-2): `0`, `Infinity` or any non-finite value DISABLES offloading
     *  for this tool — it is not a "threshold so low that everything offloads". */
    offloadThresholdChars?: number;
    /**
     * Deferred disclosure (design/36): when `true`, this tool is a CANDIDATE of the engine's deferral gate.
     * When the gate opens (the candidate set's inlined size reaching 10% of the model's context window; see
     * {@link RunnerDeps.deferMode}) it is NOT inlined with its full JSON Schema: it appears as a lightweight
     * placeholder (`{name, one-line hint, empty params}`) so the model knows it exists; when the gate stays
     * shut it ships inline in full. (A caller that wants a tool deferred on every request names it in
     * {@link TaskSpec.deferTools} — that list is literal.) Activation is a DISCLOSURE fact (has the model seen the schema?), not an
     * execution precondition: the model activates the tool via the injected ToolSearch, or by making a call
     * whose arguments already match the real parameters (see {@link TaskSpec.deferSelfResolve}, on by
     * default). Where the schema then lands depends on {@link TaskSpec.toolMaterializeStrategy} — the next
     * request's `tools[]` under `"swap"` (the default, cache prefix undisturbed), or the ToolSearch result
     * text under `"static"`, where the placeholder is never swapped. Use for large/MCP tool sets where
     * inlining hundreds of schemas blows up turn-1 tokens and risks prefix-cache breakage. Default: not
     * deferred (full schema inlined). See {@link RunnerDeps.deferMode}.
     *
     * SCOPE: this flag reaches the classifier through CALLER specs (`TaskSpec.tools`) only — a
     * built-in/injected tool's spec never enters that list, so setting `defer` on a spec that shadows a
     * built-in name does nothing. To defer an already-mounted tool (built-ins included), list its wire
     * name in {@link TaskSpec.deferTools}. Also inert when the tool ends up excluded (exclusion wins)
     * or pinned ({@link alwaysLoad} / {@link TaskSpec.alwaysLoadTools}).
     */
    defer?: boolean;
    /**
     * Inline-keep override, judged FIRST in the deferral chain (CC 220 counterpart:
     * `alwaysLoad`, declared per MCP tool via `_meta["anthropic/alwaysLoad"]`, whose `isDeferredTool`
     * checks it before every deferral arm): `true` pins this tool's full schema inline in every
     * request — it is never deferred, regardless of its own {@link defer}, {@link TaskSpec.deferTools},
     * or the deferral gate. The deferral sources only ever ADD; this is the explicit subtract valve.
     * Exclusion still wins ({@link TaskSpec.excludeTools} unmounts — nothing left to keep inline).
     */
    alwaysLoad?: boolean;
    /**
     * design/277 — model-gate CLASS tag (open vocabulary; the built-in table is
     * {@link import("./tool-model-gate.js").TOOL_MODEL_GATE_CLASSES}, v1 vocabulary =
     * `"task-scaffold"`). A tagged entry declares "I am a default-mounted scaffold of this class":
     * at prepare, when the task's RESOLVED model id matches the class's rule, THIS entry is dropped
     * from the roster (true unmount, entry-level — a same-name untagged entry is untouched; the
     * removed entry stops occupying its name on every downstream surface). Untagged = never gated.
     * The default-bundle assemblers stamp their default arms only; an EXPLICITLY composed tool is
     * not tagged (user asked ⇒ user gets) — so don't stamp hand-mounted factories. FAIL-OPEN: an id
     * the merged table knows nothing about is never gated (BYOM open set — the table encodes
     * positive knowledge only), and a tag naming an unknown class is inert + announced
     * (`config.tool_model_gate_unknown_class`). Restore channels: explicit composition (no tag),
     * {@link TaskSpec.restoreGatedTools}, env `SEMA_TOOL_MODEL_GATE=off`,
     * `RunnerDeps.toolModelGate: false`; {@link TaskSpec.excludeTools} always wins regardless.
     * The gate DECISION completes on the ToolSpec face inside `prepareConfigDoors`, before any
     * conversion; `defineTool` then forwards the class onto the materialized face (design/388: the
     * effect roster reports `modelGate` on a row that survived the gate, so an operator can read which
     * class a mounted scaffold belongs to — the forwarded value is a read face, never a second gate).
     */
    modelGate?: string;
    /**
     * Tool contract identity (stage S2, prompt-assembly protocol §7): declares the EXECUTION
     * CONTRACT this tool implements, independent of its presentation text. `defineTool` attaches it
     * to the materialized tool via the catalog side-table (identity follows the OBJECT, never the
     * wire name — a caller tool shadowing a built-in name never inherits the core contract). Omitted
     * ⇒ the tool reports a deterministic `legacy:<shapeDigest>` contract in the assembly manifest.
     */
    contract?: {
        /** Stable versioned id, e.g. `core.read@2` (built-ins) or `acme.deploy@1` (caller tools). */
        contractId: string;
        /** Declared behavior revision — bump when execute-visible behavior changes. */
        implementationRevision: string;
    };
    /**
     * design/388 §2.2 ① — the DISPLAY / DISPATCH family of this tool (closed set {@link TOOL_FAMILIES}).
     * Input to grouping, card pre-selection and the rule editor's completion — never an authorization
     * source: no gate reads it. Absent on a caller/MCP tool ⇒ the roster reports `"other"`; every
     * built-in definition in the static catalog declares one (the catalog completeness gate reds a
     * missing declaration rather than defaulting it).
     */
    family?: ToolFamily;
    /**
     * design/388 §2.2 ② — the parameter that names this tool's filesystem TARGET and how it is touched.
     * Present ⇔ the tool can be confirmed BY PATH: write confinement (`allowDirs`, the fs-write gate,
     * the sensitive-path guard, frozen-path denies), edited-file tracking and the transcript-directory
     * gate all read it off the call's face instead of a name table. ABSENT ⇒ the tool is handled as a
     * write whose target cannot be confirmed (fail-closed under every path-confining face) — exactly how
     * a tool outside the retired name tables was handled. `param` outranks `aliases` (NotebookEdit's
     * `notebook_path` beats a decoy `file_path`). `skillScopeEligible` is a SEPARATE grant whose one reader (the
     * skill-scope write fence) is retired (Write/Edit set it; NotebookEdit deliberately does not; a caller
     * tool that merely takes a built-in's NAME inherits nothing).
     */
    pathTarget?: ToolPathTarget;
    /**
     * design/388 §2.2 ③ — the parameter vocabulary a permission rule (`Tool(param:pattern)`) may name.
     * DERIVED from the schema when absent (the scalar top-level parameters plus the path target's
     * aliases; object/array parameters never enter — a rule on them can never match). A declaration can
     * only NARROW the derived set. A tool whose `prepareArguments` renames keys must declare its face
     * explicitly (the schema keys are not what a call carries).
     */
    ruleFace?: ToolRuleFace;
    /**
     * design/388 §2.2 ④ — declarative render hints for a shell that has no local implementation of
     * this tool (an MCP / caller / external tool). Every string is UNTRUSTED display input: the
     * roster caps lengths, the shell strips control and bidi characters. `approvalCard` is a
     * pre-filter, never a compatibility proof — the shell validates the call's args against its own
     * card schema before mounting a specialised card. `resultCards` may only name this tool's own
     * `cardId` namespace; a declaration naming an engine card (`builtin:*`, `mcp`) is refused at prepare.
     */
    renderHints?: ToolRenderHints;
    /**
     * design/388 §2.2 ⑤ — this tool's results are CLEARABLE by the context-edit micro-compaction
     * (the CC `microCompact` whitelist, previously the name table `COMPACTABLE_TOOLS`). Default
     * false: an undeclared tool's results survive to the harder defenses.
     */
    compactable?: true;
    /**
     * design/388 §2.2 ⑤ — this tool's results are EXEMPT from the per-batch aggregate result budget
     * because they are self-bounded (Read pages at its own cap; the offload reader pages small
     * slices). Default false: every undeclared tool counts against the budget.
     */
    budgetExempt?: true;
    /**
     * Execute the tool. Return a string (becomes text content) or a structured result.
     * Throw to signal failure (the loop encodes it as an error tool result).
     */
    execute: (args: unknown, ctx: ToolExecuteContext) => Promise<ToolReturn> | ToolReturn;
}
/** What a tool's execute may return. */
export type ToolReturn = string | {
    content: Array<TextContent | ImageContent | DocumentContent> | string;
    /** Arbitrary structured details for logs/UI (not shown to the model unless put in content).
     *  Producer side of the tool-result 双面契约: `content` is the MODEL face (may carry model-directed
     *  framing/trailers), `details` is the HOST face (per-tool structured Output). Hosts render from the
     *  structured face and never re-parse the model face — see the `tool_end.structured` JSDoc
     *  (CC 2.1.207 @765495, CC207-CORE-SWEEP P2-3) for the full contract. */
    details?: unknown;
    /** Hint the agent to stop after this tool batch. */
    terminate?: boolean;
    /**
     * A `ToolSpec.execute` can RETURN a failure
     * receipt instead of throwing (teaching errors that carry structured `details`, e.g. an
     * unrecoverable-but-informative rejection). Before this field existed, `ToolReturn` had NO
     * isError channel at all — `defineTool`'s wrapper builds a fresh `{content,details,terminate}`
     * object from whatever `execute` returns, so ANY extra property (including a caller trying to
     * set `isError` via an unchecked/cast return) was silently discarded before it ever reached the
     * wire. Confirmed empirically: a `ToolSpec.execute` explicitly returning `isError:true` still
     * produced `isError:false` on the model-facing `toolResult`, in BOTH the live per-turn path and
     * the durable-resume path — a returned rejection was structurally unable to look like anything
     * other than success. Omitted/false = success (matches `AgentToolResult.isError`'s own
     * convention exactly — this is that SAME field, finally reachable from a `ToolSpec`).
     */
    isError?: boolean;
};
/**
 * REF-D15 — DELEGATED (nested) sub-run usage: the ONE object that flows tool → runner → result. A
 * delegation tool reports it via {@link ToolExecuteContext.reportUsage}, the Runner folds it into its
 * accumulator, and it surfaces as {@link TaskResult.stats}`.nested`. Named because all four sites carry
 * the same quantity and this is money (`costMicroUsd` is micro-USD, the billing figure).
 *
 * `costMicroUsd` is OPTIONAL here by the RB-368 absence contract: a run whose model had no price entry
 * reports NO cost rather than a fabricated `0`, so "unpriced" stays distinguishable from "declared free".
 * The RUNNING accumulator uses {@link NestedUsageAccum} instead, where it is required.
 *
 * NOT a general `UsageStats` base type, deliberately: the own-usage field sets on `Stats` (runner-internal,
 * mostly REQUIRED so a new accumulation site that forgets a leg fails the type check) and on
 * `TaskResult.stats` (public, all OPTIONAL because a gateway may report no usage at all) differ ON PURPOSE
 * — folding them into one shape would delete that type check.
 */
export interface NestedUsage {
    tokens: number;
    turns: number;
    tasks: number;
    /** ABSENT when the delegated spend was unpriced (RB-368) — never a fabricated 0. */
    costMicroUsd?: number;
}
/**
 * REF-D15 — the RUNNING accumulator form of {@link NestedUsage}: `costMicroUsd` is REQUIRED because an
 * accumulator is seeded at 0 and summed into (`+= u.costMicroUsd ?? 0`), so it always holds a number.
 * Used by the live `Prepared.nestedStats` and by the checkpoint's `nestedStats` snapshot, which must
 * round-trip a concrete total across a suspend/resume (an absent field there would lose pre-suspend
 * child cost — design/38 §4.bis/Q7).
 */
export interface NestedUsageAccum {
    tokens: number;
    turns: number;
    tasks: number;
    costMicroUsd: number;
    /** batch-4 F3 (RB-368 reaches the nested face): true once ANY delegated leg reported no cost —
     *  the summed `costMicroUsd` is then a priced-subtotal, not a total, and the publish site must go
     *  ABSENT instead of impersonating one. REQUIRED so every seeding/snapshot site is tsc-forced to
     *  carry knownness alongside the number it qualifies. */
    anyUnpriced: boolean;
}
export interface ToolExecuteContext {
    toolCallId: string;
    signal?: AbortSignal;
    /**
     * design/147 S1 — the SPAWNING run's taskId when THIS run is a delegated child (Runner-filled from
     * the trusted internals chain, absent on a top-level run). Read-only context: the Agent tool's
     * hierarchy clamp and SendMessage's "main" uplink judge child-ness on it. NOT a TaskSpec field —
     * the untrusted caller surface cannot fake parentage into extra capability (forging it only
     * TIGHTENS: the clamp fires and the uplink routes to a parent that ignores unknown children).
     */
    parentTaskId?: string;
    /** design/148 S1 — this run's adopted center prompt artifact (Runner-filled, trusted; same seat
     *  as {@link parentTaskId}): delegation tools thread it into child RunInternals so the whole
     *  agent tree composes one closure. Absent on bundled-only runs. */
    centerArtifactDigest?: string;
    /** Publish provenance companion of {@link centerArtifactDigest}. */
    centerSourceRevision?: string;
    /**
     * design/147 S2a — the SPAWNING run's sessionId (trusted internals chain, same seat as
     * {@link parentTaskId}). Sibling resolution/delivery needs the parent's SESSION axis too: a
     * session-scoped sibling registers with owner = the parent's sessionId, which the parent's taskId
     * alone cannot satisfy when the two differ.
     */
    parentSessionId?: string;
    /** A-3 — the ROOT host session of the delegation tree (see RunInternals.rootSessionId:
     *  fixed point `ctx.rootSessionId ?? ctx.sessionId` at every spawn). Absent for a top-level run
     *  (its OWN sessionId is the root). */
    rootSessionId?: string;
    /** design/380 O1② (C12) — the spawning run's EXPLICIT placement fixed point
     *  (`RunInternals.placementRoot`), present only when its internals carried one (cascade rungs /
     *  verification legs after the first, or a caller-declared root). A delegation tool threads it
     *  VERBATIM into child RunInternals so every descendant keeps the ladder/gate placement; absent ⇒
     *  the `rootSessionId` chain above IS the placement root through prepare's mint middle segment
     *  (`placementRoot ?? rootSessionId ?? sessionId`) and nothing extra is threaded. */
    placementRoot?: string;
    /**
     * design/151 §7 S3c — TRUSTED tier-3 revival claim (same trusted-internals seat as
     * {@link parentTaskId}; never a model argument). Filled ONLY by the auto-mounted SendMessage's
     * revival leg after it WON the durable row's claim-CAS: the Agent tool's execute then re-launches
     * the claimed row — register-with-id on its ORIGINAL handle, `requireExisting` on its ORIGINAL
     * transcript session, identity fields inherited VERBATIM from `row` (never re-derived from the
     * reviving caller's ctx) — instead of minting a fresh agent. `rev` is the row's CURRENT revision
     * (post-claim); the registration seeds its durable write lane from it so the first settle CASes
     * against the claim, not a stale pre-claim rev.
     */
    reviveClaim?: {
        row: import("./background-agent-store.js").BackgroundAgentRecord;
        rev: number;
        /**
         * design/153 §7.2d (件3c) — the PARKED-RESUME drive: present iff this re-launch redeems a
         * parked approval (the caller — server /decide — already won a {@link import("./task-registry.js").ParkedClaimTicket}
         * reservation). The Agent tool's bg lane then: registers the handle PARKED-born (checkpoint
         * arbitration stays live pre-consume), preflights the transcript session (requireExisting),
         * drives `runner.resumeStream(ticket.token, outcome, …)` with the consume-flip hook, and on ANY
         * failure converges through `rollbackParkedClaim` (checkpoint-truth dispositions). `rev` above
         * must equal `ticket.reservedRev` (the lane seeds from the reserved row).
         */
        parkedResume?: {
            ticket: import("./task-registry.js").ParkedClaimTicket;
            outcome: import("./checkpoint-store.js").ResumeOutcome;
            /**
             * the CROSS-PROCESS re-supply seat for the checkpoint's opaque parent constraints.
             * A checkpoint minted under inherited parent-policy constraints records only
             * `requiresParentConstraint` (live ToolPolicy/onAsk closures cannot persist); the same-process
             * drive is re-supplied automatically from the Runner's in-memory `parentConstraintRegistry`,
             * but a FRESH Runner (server restart / rolling update — the production shape where an operator
             * decides hours after the park) has an empty registry, so the resume is rejected pre-CAS
             * (`resume.parent_constraint_missing`) and the row becomes permanently unredeemable. A
             * deployment that can REBUILD the same-semantics constraint chain (its policies are
             * config-driven) passes it here; the drive threads it into `resumeStream(..., internals)`,
             * where the recorded-count shape check still applies verbatim (a partial/mismatched re-supply
             * is rejected — this seat never widens, it only reopens the documented re-supply channel to
             * the parked-decide lane). Absent ⇒ exactly the previous behavior.
             *
             * One entry kind is NOT a policy the deployment authored: a hook-wired parent contributes a
             * PreToolUse SCREENING entry (issue #33), whose policy the engine mints. Rebuild it with the
             * exported `createPreToolUseConstraintPolicy(hook, env)` — same callback, same installed env —
             * or the chain handed back here will be short and rejected on the count check. A face that only
             * observes should carry `Hooks.preToolUseObservational` instead, and then never enters the chain
             * at all; a park under a screening entry announces the requirement on `onError`
             * (classification `screening-constraint-in-durable-chain`) at park time.
             */
            inheritedGate?: import("./runner/contracts.js").InheritedGate;
        };
        /**
         * design/176 — the peer-chain SEED for a message-driven (tier-3) revival: the hop chain of the
         * LAST message in the leased mailbox batch (upstream's "most recent peer-origin message" rule).
         * The spawn chain plants it as the revived cycle's initial inbound chain, so a woken agent's
         * first forward continues the chain that woke it (delivery itself never appends — a revival is
         * not a hop). Absent ⇒ the revived cycle starts with an empty chain.
         */
        peerSeed?: {
            hopChain: string[];
        };
    };
    /**
     * design/147 S1 — the explicit name THIS run was spawned under (`Agent({name})`), when any
     * (trusted internals chain, same seat as {@link parentTaskId}). A named child is sema's teammate:
     * the CC hierarchy clamp ("teammates cannot spawn other teammates") keys on exactly this.
     */
    spawnedAgentName?: string;
    /**
     * design/176 — THIS run's late-bound peer-identity carrier (Runner-filled trusted seat, same
     * chain as {@link parentTaskId}; never a model argument). Delegation tools pair it with the
     * child's uplink binding (`RunInternals.parentPeerRef`) so the child's "main" sends are judged
     * against the identity the callback actually reaches; SendMessage reads it for its own sender
     * key and hop token.
     */
    peerSelfRef?: import("../agents/peer-admission.js").PeerSelfRef;
    /**
     * design/176 — THIS run's inbound peer-chain ref (Runner-filled trusted seat, written at the
     * harness consumption boundary): the hop chain of the most recently CONSUMED peer message, which
     * outbound SendMessage calls inherit (upstream `Zbr` semantics on sema's carrier).
     */
    peerInboundChainRef?: import("../agents/peer-admission.js").PeerInboundChainRef;
    /**
     * design/147 S1c — the deployment's durable roster ({@link RunnerDeps.rosterStore}), Runner-filled
     * read-only. The Agent tool records named background spawns here (advisory try/catch — a store
     * fault never fails the spawn); never a model argument.
     */
    roster?: import("../agents/roster-store.js").RosterStore;
    /**
     * fork source-store fix — the HOST Runner's session-fork face, Runner-filled read-only.
     * The fork arm's source session lives in the store of the Runner RUNNING this task; `opts.runner`
     * is the CHILD-EXECUTION runner, which a deployment may deliberately split (server's subRunner) —
     * forking through it queried an empty store and every fork missed ("source session not found").
     * The fork arm prefers this face and falls back to `opts.runner.sessions.fork` (single-runner
     * deployments unchanged).
     *
     * ZERO-ARG by design: the source session id AND principal are bound in the Runner's
     * closure at injection — a mounted third-party tool holding this context can only branch THIS task's
     * own session under THIS task's principal, never copy a foreign session by supplying its id. The
     * returned handle's `release` is scoped to the just-created branch through the same closure, so
     * failure cleanup releases through the store that OWNS the branch (`opts.runner.sessions.release`
     * may be a different store entirely).
     *
     * Store contract: the branch is created in the HOST store, but the fork child still
     * executes on `opts.runner` with `requireExistingSession: true` — if the two runners do not share
     * session backing, the child run fails LOUD ("refusing a silent fresh run") instead of silently
     * running an empty session under the forked id. Split-runner deployments must share session backing
     * (same store instance or same durable backend) for fork to complete.
     */
    hostSessionFork?: () => Promise<{
        sessionId: string;
        release: () => Promise<void>;
    } | null>;
    /**
     * The RUNNING task's effective working root (worktree-aware), Runner-filled — read-only context a
     * delegation tool threads into its child's trusted internals so the child's env can inherit the
     * parent's cwd (CC parity: a Task sub-agent works in the main session's directory). Undefined when
     * the tool runs outside a Runner task.
     */
    parentCwd?: string;
    /**
     * The RUNNING task's file-history LINEAGE (Runner-filled, read-only, never a model/tool argument;
     * present iff the run records file history — a `RunnerDeps.fileHistoryStore` wired, capture not `"off"`, not `"disabled"`): the history scope this run
     * records its first-touch edits into and the TREE those records' keys are minted against — the
     * canonical root spelling plus the env's filesystem identity (`fs`: a remote workspace's handle,
     * `"host"` for the control-plane host's filesystem, else a per-env-instance token).
     *
     * WHICH children are threaded it, exactly: the Agent tool's FRESH spawn legs (sync / steer /
     * background / fork) pass it VERBATIM into the child's trusted internals
     * (`RunInternals.fileHistoryLineage`), which is how a same-process delegation tree keeps ONE
     * history: a child on the SAME tree (both coordinates equal) records into the lineage's scope (the
     * root session's), so the root session's `rewindFilesTo` reaches the child's edits; a child on a
     * different tree (worktree isolation, an explicit `cwd`, a fresh per-task sandbox) starts its own
     * lineage, because root-relative keys re-joined against a different tree would name files that run
     * never edited. Three delegated legs deliberately carry NO lineage and keep their own scope: the
     * Agent tool's REVIVE arm (a revival's lineage would be the REVIVER's tree, which says nothing
     * about the revived row's), and the workflow / team orchestration spawn legs (`run_workflow`,
     * `runTeamDiscussion` — they build their children's internals without this seat). Edits made by
     * those children are therefore NOT reachable from the root session's rewind, and a restore request
     * on one of them is not refused either — it converges that child's own scope.
     */
    fileHistoryLineage?: {
        scope: string;
        root: string;
        fs: string;
    };
    /**
     * design/319 (A ticket) — the RUNNING task's reminder provenance mark, Runner-filled, read-only,
     * NEVER a model/tool argument. The Agent tool's FORK route threads it into the forked child's
     * trusted internals (`RunInternals.reminderMark` — the fork runs under the parent's byte-identical
     * system-prompt declaration, so its engine mints must carry the PARENT's mark); spawn routes
     * deliberately do NOT copy it (a spawned context mints its own — "one declaration, one mark").
     * Undefined when the tool runs outside a Runner task.
     */
    reminderMark?: string;
    /**
     * design/319 (B ticket) — the RUNNING task's reminder-disclosure trigger counters
     * ({@link import("./reminder-disclosure.js").ReminderDisclosureCounts}), Runner-filled on the same
     * trusted seat as {@link reminderMark}. Caller-mounted verbatim outlets that run the
     * detect-and-disclose pipeline (the web tools' exact-mark defuse arm) bump their `<outlet>.<form>`
     * keys here; the Runner folds the non-zero result into
     * `TaskResult.stats.mechanisms.reminderDisclosures`. Mutable by design (a counter seat), but only
     * ever additive — never a decision input. Undefined outside a Runner task.
     */
    reminderDisclosureCounts?: import("./reminder-disclosure.js").ReminderDisclosureCounts;
    /**
     * Subagent transcript persistence — the RESOLVED delegation entry caps for this run
     * ({@link RunnerDeps.delegationEntryCaps} after prepare's loud validation; both members always
     * present). Runner-filled trusted seat, never a model argument — the Agent tool's spawn gate reads
     * it on EVERY local lane (background, background-fork, sync fork, sync spawn), each at its own
     * launch point. Undefined outside a Runner task (the lane then applies the
     * exported defaults itself, so a directly-driven tool is bounded too).
     */
    delegationEntryCaps?: {
        maxConcurrent: number;
        maxCumulativePerSession: number;
    };
    /**
     * Report usage spent in a nested run this tool spawned (e.g. a sub-agent). The Runner accumulates
     * it into the parent task's `TaskResult.stats.nested`, so delegated cost — the multi-agent "~15×"
     * — is visible at the top level. `createSubagentTool` calls this automatically.
     */
    reportUsage?: (usage: NestedUsage) => void;
    /**
     * The model the PARENT task is currently running on, **snapshotted at the moment this tool is called**
     * (design/38 1B). A delegation tool reads it to let a sub-agent `inherit` the caller's model. If the
     * parent later degrades, an already-started child is unaffected (it kept the snapshot). Undefined when
     * the tool runs outside a Runner task.
     */
    model?: Model;
    /**
     * WHICH REF chose {@link model} — the catalog word the parent leg is running under, or the `Model`
     * object itself when no word exists (an object-pinned spec, a `select`-criteria role). Snapshotted at
     * the same call-time instant as {@link model}, and its companion in the same sense {@link thinkingLevel}
     * is: one read, one moment.
     *
     * A delegation tool inheriting the caller's model reads THIS, not {@link model}: a word resolves against
     * the catalog generation in force at the CHILD's own spawn, so a child spawned after a catalog hot-swap
     * lands on the current generation instead of on the object its parent has been holding since its own
     * prepare (and a word the swap retired refuses loudly, exactly as a caller-written retired word does).
     * Undefined when the tool runs outside a Runner task — the same absence {@link model} has there.
     */
    modelRef?: ModelRef;
    /**
     * The PARENT task's current thinking level, snapshotted at call time — the {@link model} companion.
     * A delegation tool threads it into the child so a sub-agent inherits the caller's thinking depth by
     * default (an explicit agent-definition/script `thinking` wins). Undefined outside a Runner task.
     */
    thinkingLevel?: ThinkingLevel;
    /**
     * The running task's authenticated end-user {@link TaskSpec.principal} (design/62), **read-only** — the
     * Runner fills it; a tool cannot change it. A delegation tool (`createSubagentTool`) propagates it
     * verbatim to its child task so the end-user identity is inherited down the delegation tree (the worker
     * cannot substitute a different principal — it is not a tool argument). Undefined when the task has no
     * principal or the tool runs outside a Runner task.
     */
    principal?: string;
    /**
     * ruled 2026-08-04 — the RUNNING task's EFFECTIVE synchronous approver (`spec.onAsk ?? deps.onAsk`,
     * the exact value this task's own gate resolves an `ask` at), Runner-filled read-only on the same
     * trusted seat as {@link principal}: never a model/tool argument, so a worker can neither substitute
     * nor suppress it. Absent when the run has no approver at all (headless) — the seat is never caller-declared.
     *
     * A delegation tool (`createSubagentTool`) forwards it into every child it spawns (sync / background /
     * fork), wrapped so the approver learns which delegation raised the ask ({@link
     * import("./tool-policy.js").AskDelegationProvenance}); the workflow lane forwards the SAME frozen
     * value into every workflow-spawned agent (backlog #342: `RunWorkflowToolDeps.parentOnAsk`, folded
     * onto the governed baseline's base slot with the same provenance wrapper — the auto-mounted tool's
     * execute ctx is minimal, so the value rides a mount dep there; a deployment-pinned `base.onAsk`
     * wins). Without this seat, a deployment that wires its
     * approver per TASK (one closure per attached connection — the common shape) had it reach the host run
     * only: the child's own asks resolved at `deps.onAsk`, i.e. the headless auto-deny, while a live
     * operator sat attached to the parent. The ancestor-constraint chain
     * ({@link inheritedGateForChildren}) already routed asks raised by an ANCESTOR's policy to that
     * ancestor's frozen approver; this seat covers the asks the chain never sees — the child's own
     * egress/irreversibility safety tightens, its session-rule asks, its hooks.
     *
     * Not a widening of what may run: an ask still has to be answered, and every ancestor constraint is
     * folded first. It replaces "silently denied without anyone being asked" with "asked".
     */
    onAsk?: import("./tool-policy.js").OnAsk;
    /**
     * The RUNNING task's EFFECTIVE content-ask seam (`spec.onQuestion ?? deps.onQuestion` — the exact
     * value this task's own `AskUserQuestion` resolves at), Runner-filled read-only on the same trusted
     * seat as {@link onAsk}: never a model/tool argument, so a worker can neither substitute nor suppress
     * it. Absent when the run has no live question face at all — the seat is never caller-declared.
     *
     * A delegation tool (`createSubagentTool`) forwards it into every child it spawns (sync / background /
     * fork), because the tool's own MOUNT predicate is keyed on it: without the seat, a deployment that
     * wires its question face per TASK (`spec.onQuestion`, one closure per attached connection — the same
     * shape {@link onAsk} covers for permissions) gave the child neither the face NOR the tool. The child
     * did not fall back to the headless "no human available" text; `AskUserQuestion` was simply absent
     * from its roster, so a question it needed to ask silently became a guess.
     *
     * Forwarded VERBATIM, unlike `onAsk`'s provenance-stamped wrapper: a content ask carries its issuing
     * identity in the request itself (`AskQuestionRequest.principal` + `sourceTaskId`, which the child's
     * own prepare fills from the child's inherited principal and its Runner-minted session id), and
     * nothing in the question path does approver-identity collapse (the reason the permission wrapper
     * exists). Wrapping here would add a field no consumer reads and a second function identity for one
     * decision-maker.
     *
     * Not a widening: a question still has to be answered by the human the deployment wired, and the
     * child's questions were never anyone else's to answer.
     */
    onQuestion?: import("./ask-question.js").OnQuestion;
    /**
     * The running task's WRITE-HANDS CLAMP ({@link TaskSpec.handsReadOnly} — the read-only band the
     * Runner mounts instead of the write hands), Runner-filled read-only on the same trusted seat as
     * {@link principal}; present ONLY when the clamp is on, so an ordinary run's ctx gains no key.
     *
     * A delegation tool (`createSubagentTool`) copies it onto every child spec it builds: the clamp is a
     * tighten-only safety axis (`tightenTaskSpec`: `true → false` is a loosening that throws), but the
     * delegation lane never carried it, so a clamped parent could derive a FULL-WRITE child and the
     * read-only boundary ended at one level. Strictly a capability REMOVAL — it can never widen a child.
     */
    handsReadOnly?: true;
    /**
     * design/199 — the READ-face clamp carrier ({@link TaskSpec.readFace}): present (as "roots") ONLY
     * when this run RESOLVED to the roots face, so a delegation tool pins every child spec to roots —
     * without it a roots-narrowed parent's children would resolve the shared deployment seat (possibly
     * "open") and read wider than the parent could. Strictly a tightening carrier: an open parent adds
     * no key, and nothing in the delegation lane can spell "open". F10 ruling (v1): this seat exists
     * ONLY as the delegation carrier — no tool adapts its model-facing behavior on it.
     */
    readFace?: "roots";
    /**
     * The WRITE-face clamp carrier ({@link TaskSpec.writeFace}), the read carrier's twin: present (as
     * "roots") ONLY when this run RESOLVED to the roots write face, so a delegation tool pins every
     * child spec to roots — without it a roots-narrowed parent's children would resolve the shared
     * deployment seat (possibly "open") and write wider than the parent could. Strictly a tightening
     * carrier, and independent of {@link readFace}.
     */
    writeFace?: "roots";
    /**
     * design/199 件B — the parent task's OWN deny-set additions ({@link TaskSpec.readDenyPatterns}),
     * traveling the delegation tree add-only (a child judges at least every entry its parent judged).
     * Runner-filled trusted seat, never a model argument.
     */
    readDenyPatterns?: readonly import("../tools/fs/read-deny.js").ReadDenyEntry[];
    /**
     * The HARD-HEADLESS clamp ({@link TaskSpec.interactiveTools} set to `false` — "never mount a
     * human-facing tool on this run, whatever faces exist"), Runner-filled read-only on the same trusted
     * seat as {@link principal}; present ONLY when the clamp is on, so every other run's ctx gains no key.
     *
     * A delegation tool copies it onto every child spec, for the same reason as {@link handsReadOnly}: a
     * clamp that stops at one level is not a clamp. Without it a child of a hard-headless parent both
     * inherits the live {@link onQuestion} face and has no clamp of its own, so prepare's automatic
     * criterion mounts `AskUserQuestion` in the child — re-opening in the subtree exactly the interaction
     * channel the task disabled. Only the `false` value travels (a forced-mount `true` is a widening and
     * is deliberately NOT inherited).
     */
    interactiveTools?: false;
    /**
     * Read-only mirror of {@link "./types.js".TaskSpec.oneShot} (same trust tier/seat as
     * `principal`): this RUN has no later turn for an async background notification to land in (a
     * headless `-p` process exits once the turn ends). A delegation tool (`createSubagentTool`) reads
     * it to branch its background-launch RECEIPT toward an active block-wait instruction instead of
     * "end your turn and wait" — the Workflow tool branches its launch receipt the same way off the seat it is
     * mounted with (`RunWorkflowToolDeps.parentSpecSeat.oneShot`). The CHILDREN's copy rides {@link parentSpecSeat}
     * (only `true` travels there). Undefined when the tool runs outside a Runner task.
     */
    oneShot?: boolean;
    /** Does this run mount THIS instance in `spec.tools` as handed, pre-gate (Runner-filled)? The parent bound on `SubagentToolOptions.inheritedTools`. */
    mountsCallerTool?: (tool: ToolSpec) => boolean;
    /**  — the parent's tool-face controls ({@link TaskSpec.excludeTools} /
     *  {@link TaskSpec.deferTools}), inherited down the delegation tree like `principal` (Runner-filled,
     *  read-only): a scenario-wide roster must not be escapable by delegating to a child that remounts
     *  the excluded tool. Children merge these with their own spec values (union — tighten-only). */
    excludeTools?: readonly string[];
    deferTools?: readonly string[];
    /** DD-3 — the INLINE-PIN half of the same tool-face control ({@link TaskSpec.alwaysLoadTools}),
     *  inherited on the same seat and by the same rule as {@link deferTools} (Runner-filled, read-only).
     *  The two are one decision expressed in two lists: "defer this batch, but keep X inlined". Passing
     *  only the deferral half down the delegation tree silently rewrites the scenario for every child —
     *  X arrives deferred, the exemption having been dropped in transit — which is the one outcome the
     *  parent explicitly ruled out. Children merge this with their own spec value. */
    alwaysLoadTools?: readonly string[];
    /** design/277 — the model-gate restore selector ({@link TaskSpec.restoreGatedTools}), inherited
     *  down the delegation tree on the same trusted seat as the three tool-face controls above
     *  (Runner-filled, read-only, frozen snapshot). Restoration is a TASK-TREE intent ("this work
     *  wants the scaffold back") and each child re-judges the gate under its OWN resolved model —
     *  dropping the selector in transit would trim a same-model child's roster in a way nobody
     *  chose (the gate is a default, not a policy; there is no tighten-only axis to protect). */
    restoreGatedTools?: readonly string[] | true;
    /** R2 双形轴 — the parent's resolved prompt profile, inherited down the delegation tree like the
     *  tool-face controls (Runner-filled): a classic-profile parent's children speak classic too
     *  unless the child spec says otherwise (child spec wins — profile is presentation, not policy). */
    promptProfile?: "simple" | "classic";
    /**
     * The parent SPEC seat this run hands its children ({@link import("./parent-spec-seat.js").ParentSpecSeat}):
     * `principal`, `oneShot` (only `true`), `clientContext` (the user's TZ/email facts), `onQuestion` (the FROZEN
     * question face — `AskUserQuestion` mounts on it), `additionalDirectories` / `additionalReadDirectories` (the
     * fs-fence widening; the read-only half stays read-only in transit), `envFacts` (the sandbox facts), and the
     * DECLARED `memoryPersistenceCapable`. Runner-filled, read-only, NEVER a model/tool argument; minted ONCE per
     * prepare from the run's rebound spec + frozen question face (arrays and the facts object frozen copies). ABSENT
     * when the tool runs outside a Runner task.
     *
     * A delegation tool applies it onto every child spec it builds with `applyParentSpecSeat` — the ONE law both
     * delegation lanes (Task and Workflow) share: a value the child spec already carries WINS (an agent definition's
     * own declaration, a deployment baseline, a trusted script), the seat FILLS an unset one, and the two REMOVAL
     * values (`oneShot: true`, `memoryPersistenceCapable: false`) land unconditionally. A deployment that widened the
     * fs fence widened it for the TASK, a per-request question face is the child's face too, a one-shot process is
     * one-shot for the whole tree — none of that stops at one level.
     */
    parentSpecSeat?: import("./parent-spec-seat.js").ParentSpecSeat;
    /** This run's per-request question face was STRIPPED by the engine (`RunInternals.questionFaceStripped` — the leg outlives
     *  the request that spawned it), Runner-filled read-only. A delegation tool forwards it onto every descendant's internals:
     *  the descendants are faceless by the same engine act, so the posture door's exemption travels with them. */
    questionFaceStripped?: true;
    /**
     * The parent task's DECLARED {@link TaskSpec.memoryPersistenceCapable} as THIS run's own fact — present ONLY when
     * the spec set it (an inferred run's ctx gains no key), Runner-filled on the same trusted seat as {@link principal}:
     * never a model/tool argument. Read by the revival spawner as the waking run's own clamp (a declared `false` forces
     * the read-only-memory disclosure onto the cycle it wakes). The CHILDREN's copy rides {@link parentSpecSeat}.
     */
    memoryPersistenceCapable?: boolean;
    /**
     * design/383 §2.5 — TRUE ⇔ the spawning session is under a memory-capture OPT-OUT at the moment
     * a delegation tool reads this seat (declared at its prepare, a standing record, or a mid-run
     * flip — the seat is live, not a prepare-time snapshot). The delegation lanes forward it into
     * every child's trusted internals as the capture FLOOR (`memoryCaptureFloor`): a child of an
     * opted-out session captures nothing, whatever its chosen AgentDefinition says — the opt-out is
     * a floor no selection loosens (the `memoryPersistenceCapable:false` floor's exact law, on the
     * privacy axis). Trusted Runner-filled seat, never a model/tool argument.
     *
     * DUAL FORM (#511 件2): on a deployment whose capture record store is synchronous (the default
     * file trio) this is a plain boolean, byte-identical to before; a Promise-form
     * {@link RunnerDeps.memoryCaptureRecordStore} makes the live read answer a `Promise<boolean>`.
     * Consumers must `await` (identity on the boolean arm) — a bare `=== true` on the Promise arm
     * would coin `false`, the exact un-floored escape this seat closes.
     */
    memoryCaptureOptedOut?: boolean | Promise<boolean>;
    /**
     * design/383 §2.5 — the floor seat's THIRD state: TRUE ⇔ the spawning
     * session's capture opt-out state is INDETERMINATE at the moment a delegation tool reads this
     * seat (its capture record store is faulting, and no genuine record is known in-process). The
     * delegation lanes forward it into the child's trusted internals as the floor's indeterminate
     * twin (`memoryCaptureFloorIndeterminate`): the child's prepare re-resolves it against the live
     * record query — an ancestor record found takes the ordinary floor arm; a still-faulting store
     * runs the child under the same fail-closed suppression an own-store outage imposes (no
     * irreversible record is ever minted off an unreadable state); a readable-and-clean answer
     * proceeds clean. Never TRUE beside {@link memoryCaptureOptedOut} — a known opt-out is
     * determinate. Trusted Runner-filled seat, never a model/tool argument.
     * Dual form like its twin (#511 件2): `boolean` over a sync store, `Promise<boolean>` over a
     * Promise-form store — consumers `await`.
     */
    memoryCaptureIndeterminate?: boolean | Promise<boolean>;
    /** design/383 §2.5 — the spawning session's write-plane control dir (the coordinate its capture
     *  record is keyed under), forwarded beside the floor bit so a cross-plane child's record-query
     *  leg reads the PARENT's carrier, not its own plane's. Trusted Runner-filled seat. */
    memoryCaptureControlDir?: string;
    /** design/383 §2.5 (codex round 3) — the WHOLE ancestor chain's capture-record coordinates
     *  ({sessionId, controlDir?} per generation, root first), appended one row per spawn. The
     *  record-query leg walks ALL of it: a root that flips after spawning an already-running child
     *  AND grandchild is invisible to the grandchild's one-hop parent query (the clean middle
     *  session has no record of its own to find), while the chain reaches the root directly.
     *  Trusted Runner-filled seat, never a model argument. */
    memoryCaptureAncestors?: ReadonlyArray<{
        sessionId: string;
        controlDir?: string;
    }>;
    /**
     * the parent task's per-model auth hook ({@link TaskSpec.getApiKeyAndHeaders}), inherited
     * verbatim down the delegation tree like `principal`/`clientContext` (Runner-filled, read-only, NEVER
     * a model/tool argument). Without this seat a per-task agent pinned to a cross-provider model (its own
     * apiKeyEnv) spawned a child whose run had NO auth hook — the brain silently fell back to the global
     * key and the child 401'd (or worse, ran on the wrong account). Undefined when the parent spec carries
     * no hook or the tool runs outside a Runner task.
     */
    getApiKeyAndHeaders?: TaskSpec["getApiKeyAndHeaders"];
    /**
     * A read-only CAPTURE of this (parent) task's skill grant rows for the run — the tools its active skills list in
     * `allowed-tools` / `disallowed-tools` ({@link import("./skill-grant.js").SkillGrantRecord}: skill, list, entry),
     * filled by the Runner. A delegation tool (`createSubagentTool`) reads it AT SPAWN TIME and threads it into the child,
     * which holds the rows until it ends. A copy: the parent's later activations never reach it. An empty array = no active
     * skill lists (the common case). The model can neither read nor set it (it is not a tool argument). Undefined when the
     * tool runs outside a Runner task.
     */
    activeSkillGrants?: () => readonly import("./skill-grant.js").SkillGrantRecord[];
    /**
     * A read-only CAPTURE of this (parent) task's request allow entries for the run ({@link import("./task-spec.js").TaskSpec.allowedTools},
     * its own and any it inherited), filled by the Runner beside {@link activeSkillGrants}: a delegation tool reads it AT SPAWN
     * TIME and threads it into the child, which holds them as its own request rows until it ends. A copy; the model can
     * neither read nor set it. Undefined when the tool runs outside a Runner task.
     */
    activeRequestAllowedTools?: () => readonly string[];
    /**
     * Parent effective-policy inheritance (tighten-only) — the Runner-filled chain accessor a delegation tool
     * (`createSubagentTool` / the `run_workflow` spawn legs) calls AT SPAWN TIME and threads verbatim into the
     * child's trusted `RunInternals.inheritedGate`. Returns the upstream inherited chain PLUS this (parent)
     * task's own contribution: its session-rule snapshot (data half), its RESOLVED caller policy
     * (`spec.toolPolicy ?? deps.toolPolicy` — the exact slot this task's own gate enforces) with the frozen
     * task onAsk (opaque half), and its effective shellGate. The child's prepare folds these as
     * ADDITIONAL constraint layers — inheritance can only narrow a child, never widen it. Runner-filled,
     * read-only, NEVER a model/tool argument (same posture as {@link activeSkillGrants}). Undefined when the
     * tool runs outside a Runner task.
     */
    inheritedGateForChildren?: () => import("./runner/contracts.js").InheritedGate;
    /**
     * design/180 half A — the delegation runtime-provenance ARMING face. Runner-filled; a delegation
     * tool calls it at spawn time: a non-undefined return means this (parent) run is armed (it mounts
     * a memory session, or is itself recording for its own parent) and carries the chain's FROZEN
     * content-safety snapshot — the tool then mints the child's recorder ref and threads both into
     * the child's trusted `RunInternals.delegationProvenance`. Undefined return / absent field ⇒ the
     * child spawns without a recorder (its deliveries read `unknown` and every judgment stays on the
     * static floor — whose MARK action follows the judging run's deployment evidence standard,
     * {@link RunnerDeps.memoryDelegationEvidence}; under the `"static-face"` default this is v1
     * behavior byte-identical). Same trust posture as
     * {@link inheritedGateForChildren}: never a model/tool argument, never a TaskSpec field.
     */
    delegationProvenanceForChildren?: () => import("./memory-engine/delegation-provenance.js").DelegationContentSafety | undefined;
    /**
     * design/336 §3.3 — the delegation-settlement account's coordinates (the WRITE plane's memory
     * control dir + this session's id), Runner-filled when the run mounts a memory session under
     * `memoryProvenance: "carry"`. A delegation tool's BACKGROUND lane writes its launch write-ahead
     * (pending row BEFORE registration and invoke) and its terminal settle through these — as pure
     * data, because the terminal observation can run after this task leg returned and must rebuild
     * its write handle from the coordinates alone. Undefined ⇒ no settlement account (memory-less
     * run, or `"off"`): the background lane launches without rows, the pre-336 accepted-cost shape.
     * Same trust posture as {@link delegationProvenanceForChildren}: never a model/tool argument.
     */
    delegationSettlement?: () => {
        controlDir: string;
        sessionId: string;
    } | undefined;
    /**
     * RB-201 FO-3 (form-one audit, CC 220 `Ipd`/`ein` parity) — the auto-mode classifier decider ARMED
     * for THIS task (auto-mode intent ∧ `RunnerDeps.autoMode` present ∧ `RuntimeCaps.autoMode !== false`
     * — see {@link TaskSpec.permissionMode}; the same
     * instance `runToolGate`'s per-call ask review already consults, carrying its own live breaker
     * state — not a fresh one built from raw config). Runner-filled, trusted, undefined when auto-mode
     * is not armed or the tool runs outside a Runner task.
     *
     * CC's classifier covers two more scenes beyond a single tool-call ask that sema had NOT ported: a
     * delegation tool reviews the CHILD's prompt+toolset BEFORE spawning it (CC `Ipd` — a main agent
     * blocked from a dangerous action directly could otherwise write it into a sub-agent's prompt and
     * have the child execute it, a delegation-based classifier circumvention), and reviews the child's COMPLETED
     * work when it hands control back (CC `ein` — flags the main agent with a security warning before it
     * acts on unreviewed sub-agent output). `createSubagentTool`/the workflow spawn legs read this at
     * BOTH points and thread it into the child (never a model/tool argument), same posture as
     * {@link inheritedGateForChildren}.
     */
    autoModeReview?: {
        decider: import("./auto-mode.js").AutoModeDecider;
    };
    /**
     * design/153 §7.4 (件4) — the parent task's `durableApproval` opt-in (value copy), Runner-filled so a
     * delegation tool can FORWARD it into an eligible background child's spec (named non-fork child + durable
     * row + checkpoint store + `ensureChildSessionDurable` attested — the §7.3 park-eligibility family). With
     * it forwarded, a child's plain policy `ask` suspends durably and the settle watcher PARKS the row instead
     * of the pre-153 `unexpected.suspended` failure. Tighten-only: forwarding grants the child a PARK
     * capability, never a permission widening — the park's resume decision stays with the operator, and the
     * ancestor mandate chain (frozen at chain assembly) is unchanged by the child's own spec. Runner-filled,
     * read-only, NEVER a model/tool argument. Undefined when the parent did not opt in (children keep the
     * pre-153 lifecycle byte-for-byte). Side effect worth noting (件4 复审): forwarding also flips the
     * child's `durableQuestionFace` true (a wired checkpoint store + this opt-in), so an eligible child
     * additionally mounts `AskUserQuestion` — a deliberate consequence (a question the child can't resolve
     * synchronously should park too), not a separate grant.
     */
    durableApprovalForChildren?: {
        scope: string;
        ttlMs?: number;
    };
    /**
     * The host run's RESOLVED checkpoint seat, for its children — the store object the run parks in (the ONE value
     * `resolveCheckpointStore(spec, deps)` produced), or the word `"disabled"` when the caller disarmed the run.
     * Runner-filled, read-only, NEVER a model/tool argument. ABSENT when the tool runs outside a Runner task and
     * when the host has no seat at all (unset spec, no deployment store): nothing to inherit, so the child's own
     * door resolves its runner's deployment — a split child-execution runner may carry a store the host has none of.
     *
     * A delegation tool copies this seat VERBATIM onto every child spec it builds (sync, background and fork
     * alike) — one rule: a store the parent resolved at SPEC level reaches the child (a child that re-resolved
     * `RunnerDeps.checkpointStore` on its own found no store on a deployment that wires it per task, and its ask
     * fell to the fail-closed deny while the parent could park), and `"disabled"` presses on the whole subtree (a
     * capability REMOVAL, never re-armed one level down by a deployment store — a machine-started run must not
     * park in its subtree). Same discipline as `principal`: inherit verbatim off the trusted seat.
     */
    checkpointStoreForChildren?: import("./checkpoint-store.js").CheckpointStore | "disabled";
    /**
     * design/99 (nested-subagent live observability): the host run's stable taskId (`spec.taskId ?? sessionId`),
     * filled by the Runner. A delegation tool (`createSubagentTool`) reads it AT SPAWN TIME and threads it into the
     * child as the child's `parentTaskId`, so a UI can build the live nested-agent tree (child.parentTaskId === this
     * taskId). Undefined when the tool runs outside a Runner task.
     */
    taskId?: string;
    /** design/129: the host run's SESSION id (Runner-filled, read-only) — a session-scoped background child
     *  registers under it so later turns (fresh taskId) still reach the entry. */
    sessionId?: string;
    /** design/129: the host spec's `backgroundScope`, threaded read-only so delegation tools apply the
     *  deployment's background-lifetime policy (never a model argument). */
    backgroundScope?: "task" | "session";
    /**
     * design/173 §8.3 (review fold r2-F1) — the host run's RESOLVED interaction posture
     * (`spec ?? parent ?? deps`), threaded read-only so delegation/workflow lanes carry it into their
     * children's TRUSTED internals (`RunInternals.parentInteractionPosture`). Without this carry, a
     * root that overrode a deps-level `"interactive"` with `"headless"` spawned children that fell
     * back to the deps default and were refused at their own door. Engine children still never get
     * the posture COPIED onto their spec (the §8.3 rule) — it rides internals, the engine channel.
     */
    interactionPosture?: "interactive" | "headless";
    /** design/129-B: the process-level background-child observer ({@link RunnerDeps.onBackgroundChildEvent}),
     *  threaded read-only so the Agent tool's background lane reports spawn/tick/terminal for the child's
     *  whole lifetime (a per-leg sink goes stale when the leg settles; this one never does). */
    onBackgroundChildEvent?: (event: BackgroundChildEvent) => void;
    /**
     * design/115 P3 — the run-local task-notification sink (the SAME lane runtask wires for workflow/bash
     * completions). Injected by prepare-task's spec.tools wrapper so a caller-mounted delegation tool can
     * fire background-agent completion notifications into the SOURCE task's next turn. TRUSTED, run-scoped.
     */
    onTaskNotification?: (n: import("./task-notification.js").TaskNotificationPayload, opts?: {
        priority?: import("./task-notification.js").SystemInjectionPriority;
    }) => unknown;
    /**
     * design/99 (nested-subagent live observability): the host run's OPT-IN display sink — a deployment sets it (via
     * `RunInternals.onForwardEvent`) to receive a SUBAGENT's live `task_progress` ticks that otherwise stay in the
     * child's ISOLATED stream. A delegation tool threads it to the child so nested progress bubbles to one sink. This
     * is a DISPLAY channel ONLY — the child stream is NEVER merged into the parent's MODEL context, and nothing
     * security-relevant consumes a forwarded event. Present ONLY when the deployment opted in. The tool-ctx wrapper
     * passes `task_progress` unconditionally and — when the deployment sets `forwardSubagentEvents: true` — the
     * transcript classes too (text_delta/text_end/reasoning_delta/tool_start/tool_end); other event types never cross it.
     * The delegation lane's OWN tap is trusted and forwards the child's FULL event stream (bg frames tagged
     * with bgAgentId). ⚠️ Forwarded ticks are UNTRUSTED display hints — any
     * tool holding this ctx could self-declare one, so a consumer validates `parentTaskId` against its known runs.
     */
    forwardEvent?: (event: TaskEvent) => void;
    /**
     * Subagent steer verb (2026-07-03): the host run's opt-in SUBAGENT-STEER-HANDLE sink
     * (threaded from `RunInternals.onSubagentSpawn`). When present, `createSubagentTool` runs each sync
     * child via `runTaskStream` and emits a steer handle here; the deployment routes a human steer into
     * the running child. The handle never reaches the model. Absent ⇒ children run non-steerable.
     */
    onSubagentSpawn?: (handle: import("../agents/subagent.js").SubagentSteerHandle) => void;
    /**
     * design/122 D1 — the parent run's subagent-retain LEDGER, present ONLY when the parent's
     * {@link TaskSpec.retainSubagentSessions} is enabled. TRUSTED run-scoped state the Runner fills (never a
     * tool argument): `createSubagentTool` registers each retained child here (pre-minted sessionId + a
     * FROZEN plain spec snapshot — never the live ctx/spec-builder closures, r1-m5) and the Runner disposes
     * the whole ledger (abort in-flight resumes + unpin + release every retained session) when the parent
     * run ends (D4).
     */
    subagentRetain?: import("../agents/retain-ledger.js").SubagentRetainLedger;
    /**
     * design/135 §0 — the TRUSTED per-call worktree-isolation lane for the Agent tool (CC
     * `isolation: "worktree"`). The Runner fills it (prepare-task builds it over the run's ExecutionEnv +
     * task root via `createSubagentWorktreeHelper`) for write-capable runs; a tool cannot self-declare it and the
     * model only REQUESTS isolation via the Agent tool's `isolation` parameter — the capability itself is
     * never a model argument. Absent ⇒ the deployment cannot mint worktrees (read-only run / outside a
     * Runner) and the Agent tool reports isolation as honestly unavailable.
     */
    worktreeIsolation?: import("../agents/subagent.js").SubagentWorktreeIsolation;
    /**
     * design/135 §0 — TRUE when THIS run is itself a forked child ({@link RunInternals.insideFork}),
     * threaded read-only so the Agent tool's built-in `subagent_type:"fork"` route can refuse nested forks
     * (CC: "fork is not available inside a forked worker") even for a deployment-mounted Agent tool that
     * flowed into the forked child via its tool list. Runner-filled; never a model argument.
     */
    insideFork?: boolean;
    /**
     * OBS-2 (CC 2.1.206 observer parity) — present iff this run's principal resolved
     * {@link RuntimeCaps.allowObservers}` === true` (EXPLICIT opt-in, default OFF — see the polarity
     * note there). The delegation tool honors {@link AgentDefinition.observer} declarations only under
     * this flag; absent ⇒ declarations resolve to unobserved with NO warn (the gate, not a mistake).
     * Runner-filled at call time (same closure posture as `forkAccess`); never a model argument.
     */
    observersAllowed?: true;
    /**
     * design/136 §6 盲点① — the Runner-computed fork-GOVERNANCE verdict for THIS run (from
     * `forkGovernanceDenial(spec.enableFork, runtimeCaps.allowFork)`), threaded read-only so the Agent
     * tool's built-in `subagent_type:"fork"` route enforces the SAME per-task/per-principal authorization
     * as the standalone Fork tool's mount gate (pre-1.256, `allowFork:false` only prevented mounting Fork
     * while Agent-fork ran ungoverned). Present ONLY when governance DENIES (`denied:"task"` =
     * `enableFork:false`; `denied:"principal"` = `runtimeCaps.allowFork:false`) — absent means governance
     * does not object (or the tool runs outside a Runner ⇒ capability checks only). Runner-filled; never a
     * model argument.
     */
    forkAccess?: {
        denied: "task" | "principal";
    };
    /**
     * design/80 D-B — the general "a tool yields to a HUMAN review gate" primitive. A tool calls this to request
     * that, at the next SAFE turn boundary, the engine pause with a durable `plan_review` checkpoint
     * (`status:"needs_review"`) so a human approves/edits/rejects before the run continues. The tool still returns
     * its `content` normally first (the plan/diff lives there → reaches the reviewer via the transcript). HONORED
     * only when the deployment can actually pause (a `checkpointStore` is wired); otherwise the request is a silent
     * no-op (read-only/headless degrade). The first-party `present_plan` tool (CC `ExitPlanMode` parity) is a thin
     * caller of this; any tool may use it to gate a high-blast step on human review. Idempotent within a batch (the
     * first request wins; the suspend fires once at the boundary). The Runner fills it; not a tool arg.
     */
    requestReview?: (opts?: {
        reason?: string;
    }) => void;
    /**
     * design/105 — request a CLEAN stop of THIS run after the current turn (NOT an abort: the loop finishes the
     * turn, then exits gracefully — no orphan-[INTERRUPTED] reconcile). A thin binding to the harness's existing
     * one-way `requestStopAfterTurn` flag (the same the human-suspend saga uses). Typical use: a tool that
     * persisted a delayed self-wake intent (e.g. CronCreate with kind:"delay") ends the run cleanly so the
     * daemon owns the re-wake ("end now, re-wake" — NOT "suspend-continue", see design/105 §3.4). Idempotent
     * one-way flag; a no-op for tools that don't call it. The Runner fills it; not a tool arg. Absent outside a Runner.
     */
    requestStopAfterTurn?: () => void;
    /**
     * Enter plan mode mid-run: a steer of the leg's ONE permission-mode seat into `plan` — the same mode the spec's
     * `permissionMode` and `TaskStream.setPermissionMode` set. For the rest of THIS run the mode gate asks before every
     * covered write and every call to a tool the engine does not define that is not read-only (the plan sentence; refused
     * where nobody can answer), while read tools and `ExitPlanMode` run. The word it left is recorded: an approve-resume
     * returns to it; a rejected plan steers the resumed leg back into `plan`. The first-party `EnterPlanMode`
     * tool is the only caller. Idempotent. The Runner fills it; not a tool arg. Absent outside a Runner.
     */
    enterPlanMode?: () => Promise<void>;
}