UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

1,531 lines • 121 kB
/**
 * The HOST wiring seat: `RunnerDeps` — every store, sink, hook and policy provider a deployment hands
 * the engine — plus the three structured shapes only its own callbacks produce (`ProjectMemoryLoad`,
 * `ResumePreflightInfo`, `ResumePreflightVerdict`). Layer 0 vocabulary; `types.ts` re-exports all four,
 * so no consumer's import changes.
 */
import type { Model } from "../internal/llm.js";
import type { AgentDefinition } from "./agent-definition.js";
import type { ContextSettings } from "./context-settings.js";
import type { BackgroundChildEvent, DelegationLifecycleEvent } from "./delegation-frames.js";
import type { EngineNotice } from "./engine-notice.js";
import type { HandsBandOptions } from "./hands-band.js";
import type { OnElicit } from "./mcp-server-spec.js";
import type { Brain, ModelRef, ModelRoles } from "./model-seat.js";
import type { RuntimeCaps, WorkflowGovernanceBaseline } from "./runtime-caps.js";
/**
 * parity-204 — structured `RunnerDeps.loadProjectMemory` return (backward-compatible: a bare
 * `string | null` keeps its exact historical meaning). Adds the CC 2.1.204 `seededFromContext`
 * seam (cc204-bundle @17917159: CC preloads readFileState for CLAUDE.md/nested-memory files it
 * placed in context, and a DEFAULT whole-file Read of an unchanged seeded file answers with the
 * already-in-context reminder instead of re-transmitting the body — token economy, GA in 204).
 */
export interface ProjectMemoryLoad {
    /** The project-context text to inject — same semantics as returning a bare string (null/blank ⇒ no injection). */
    content: string | null;
    /**
     * Files whose FULL, disk-verbatim text the deployment has placed in the model's context (e.g. the
     * CLAUDE.md that `content` embeds). The Runner pre-seeds the hands' read state (through the same
     * resolveKey containment as a real Read — an out-of-root path is silently skipped), so a default
     * whole-file Read of an UNCHANGED seeded file returns the CC-verbatim reminder
     * (`This file is already in your context (see "Contents of <path>" above) and has not changed on
     * disk…`) instead of the body. `content` here MUST be the file's exact disk text at load time —
     * the unchanged check hashes it; never declare a truncated/annotated variant (CC's `isPartialView`
     * exemption). A file that later changes on disk always reads normally (hash mismatch bypasses the
     * dedup and disarms it). Requires hands (an executionEnv); ignored on a hands-less task.
     */
    seededFiles?: ReadonlyArray<{
        path: string;
        content: string;
    }>;
    /**
     * design-A C-3 (AGENTS.md×ingest, [A1]) — the deployment DECLARES which instruction files this
     * snapshot was built from and their fingerprints (e.g. `AGENTS.md` / `CLAUDE.md`, hash over the
     * raw pre-cap file bytes). Core does NOT interpret the hash algorithm or re-derive it — the pair
     * is an OPAQUE comparison baseline (`path` + verbatim `contentHash` equality only), kept so the
     * mid-session change probe can answer "did the file behind my snapshot move?" without core
     * ever learning a file-name policy (types.ts seam rule: core never hardcodes instruction file
     * names). [A2]: this list now FEEDS the C-4 `instructions_change` lane — when the deployment also
     * wires {@link RunnerDeps.probeInstructionSources}, the run loop probes these paths at eligible
     * turn boundaries and a moved hash is announced via a fixed tail attachment. Optional and inert
     * when absent (today's loaders keep their exact behavior; no sources ⇒ the lane never runs).
     */
    instructionSources?: ReadonlyArray<{
        path: string;
        /** (additive widening): `null` = the source is DECLARED but currently
         *  ABSENT from disk — the change probe then watches for its APPEARANCE (renders "absent → hash").
         *  Runtime already handled null (collectInstructionsChange's from===null arm); this widening
         *  only aligns the declared type with the shipped behavior. */
        contentHash: string | null;
    }>;
}
/**
 * design/380 O2 — the facts handed to a deployment's {@link RunnerDeps.resumePreflight}: the row's
 * own recorded identity + placement record, read off the persisted checkpoint (never off the resume
 * caller's bag), so the hook judges the SAME row the CAS is about to consume.
 */
export interface ResumePreflightInfo {
    token: import("./checkpoint-store.js").CheckpointToken;
    sessionId: string;
    /** design/380 O1③ — the row's recorded placement root, when stamped. */
    placementRootSessionId?: string;
    /** The row's recorded identity ({@link import("./checkpoint-store.js").Checkpoint.principal}), when stamped —
     *  the resume entry's identity-continuity rung already refused a contradicting supplied principal
     *  before this hook runs, so a present value IS the resumed leg's identity. */
    principal?: string;
    /** provider / deviceId / mountPath — the placement facts. */
    workspaceHandle?: import("./remote-env.js").WorkspaceHandle;
    gateKind: import("./checkpoint-store.js").CheckpointGate["kind"];
}
/** design/380 O2 — a {@link RunnerDeps.resumePreflight} answer. */
export type ResumePreflightVerdict = {
    ok: true;
}
/** Transient obstacle (device offline, dependency briefly down): the checkpoint stays `pending`,
 *  the SAME token is redeemable later; retryAfterMs threads the wait hint (the #449 G1 carrier). */
 | {
    ok: false;
    disposition?: "retry_later";
    message: string;
    retryAfterMs?: number;
}
/** PERMANENT obstacle (binding revoked, placement identity gone): the row is settled TERMINALLY —
 *  core CASes it out of `pending` before answering, so a dead binding cannot
 *  be redialed forever (prose-terminal + mechanically-retriable = unbounded redeem). The `expire`
 *  CAS is a pure STATUS flip — the row records no reason (the store seam has no seat for one);
 *  the REASON travels on the typed `resume.preflight_rejected` refusal (the hook's `message`
 *  inlined), the `onError` disclosure sink, and the deployment hook's own audit plane — it
 *  authored the verdict and owns the durable record of why. The exact
 *  terminal CAS form is the single-shot `expire` CAS (reaper parity — exactly one settler wins; a
 *  LOST race is re-read and reported as concurrent movement, never claimed as this refusal's
 *  settle); the CONTRACT is: terminal verdict ⇒ atomic single-shot settle, never a silent
 *  pending-forever. PHYSICAL reclamation of a suspended workspace stays with the DEPLOYMENT that
 *  ruled the binding dead — core holds no env pre-restore and a factory cannot reconnect to a
 *  revoked target; the session pin follows the store contract's documented reap posture. Trust
 *  grant is acceptable: the hook lives on RunnerDeps, the same deployment plane that owns the
 *  checkpoint store itself. */
 | {
    ok: false;
    disposition: "terminal";
    message: string;
};
/** Runtime dependencies shared across tasks. */
export interface RunnerDeps {
    brain: Brain;
    /** Catalog used to resolve string ModelRefs to Model objects. */
    models?: Record<string, Model>;
    /**
     * The deployment's per-model CONTEXT-WINDOW overlay, keyed by CATALOG WORD ({@link ContextSettings}):
     * `contextTokens` / `autoCompactTokens` / `enabled` over what the model card declares.
     *
     * It is the ONE hot-swappable seat an IN-FLIGHT leg re-reads. Every other seat follows the snapshot law
     * (a running task keeps the deps object it prepared under) because those seats are the routing card —
     * endpoint, credentials, price, fence — and a request in flight must reach what it was priced for. The
     * usable window is not the card: it is a setting, and an operator who lowers it is asking the legs
     * running RIGHT NOW to compact sooner, which is exactly what they were unable to do while the number
     * lived on the model object. Read at every turn boundary, every recovery pass, the end-of-task summary
     * and every request build; clamped to the physical window by the one resolver.
     *
     * A leg that was handed a `Model` OBJECT (or resolved one through a `select` criteria) has no catalog
     * word and therefore no overlay — the pin is the caller saying "this exact model, as declared".
     */
    contextSettings?: ContextSettings;
    /**
     * design/338 (mid-turn MCP revocation) — the HOST's revocation ledger, probed synchronously at
     * every MCP dispatch (tool call + the three resource tools) BEFORE the transport. The engine
     * never caches the answer: the ledger's one authority lives on the host (a cached copy would be
     * a split-state second authority). A revoked server's calls settle as the coded refusal
     * `mcp.server_revoked` with known-not-executed wording; in-flight calls a revocation raced are
     * deliberately not chased (the threat shape is "new calls after removal"). Absent seat = the
     * pre-338 semantics. A THROWING probe fails open (revocation is a tightening face — a broken
     * probe must not brick every MCP call) with a once-per-MATERIALIZATION `mcp.revocation_probe_failed`
     * notice (a resume re-materializes and may announce again — the standing condition is re-news at
     * each fresh mount, never per-call). The notice is a deployment wiring fact: `detail: { message }`
     * only, no session attribution, `"operator"` audience by its explicit {@link NOTICE_AUDIENCE}
     * row (#433 made the registry total over the catalog — no engine-minted code is
     * audience-defaulted any more) — a
     * wire projector forwards it operator-tier and needs no per-session de-duplication of its own.
     * The probe's second argument, `{ fingerprint }`, is passed exactly when the server's declaration carries one.
     */
    mcpRevocations?: import("./mcp-server-spec.js").McpRevocationLedger;
    /**
     * design/147 S1c (ruled 2026-07-18) — the DURABLE name→agent roster behind explicit-name
     * addressing, a storage-tier seam like the checkpoint store: core bundles `MemoryRosterStore`
     * (default when omitted... none — omitted means NO durable roster; the in-memory task registry
     * still resolves live/recent names exactly as before) and `FileRosterStore`; a server deployment
     * plugs pg/tidb behind the same interface. Named background-agent spawns are recorded here
     * (advisory — a throwing store never faults a spawn), and SendMessage consults it AFTER the live
     * registry misses, re-applying the caller's access axes (owner/scope) before disclosing anything.
     */
    rosterStore?: import("../agents/roster-store.js").RosterStore;
    /**
     * The center prompt-artifact source (artifact v3 only — a v2 body is refused `schema`, INTEGRATION-CORE §12.11).
     * `current()` supplies the verified
     * candidate a top-level session adopts at session start and at a compaction; `get(digest)` resolves a PINNED center
     * layer on resume (a miss is fail-loud `prompt.snapshot_unavailable` — core never recomposes a hybrid from newer
     * content; a copy the engine refuses is told apart from a missing one, in the failure's sentence and on `onError`);
     * `sourceState()` is the tri-state every adoption decision keys on. What a source hands a run is re-checked
     * before it is adopted. Core bundles `CenterPromptSource` (+ memory/file artifact & source-state stores); a server
     * feeds it published artifacts via `applySnapshot`. Omitted = no center layer.
     */
    promptSource?: import("../prompt-assembly/artifact-store.js").PromptArtifactSource;
    /**
     * 模型档位绑定(2026-07-11):sema 档位词(`max`/`pro`/`flash`/`lite`,`ultra` 预留)→ 具体模型
     * (catalog 名或 Model)。Runner 构造时经 `expandTiers` 增广进 catalog,使档位词与 CC alias
     * (`fable`/`mythos`/`opus`/`sonnet`/`haiku`,内置固定映射)在全部模型寻址面直接可用(TaskSpec.model /
     * roles / Agent 工具 model / workflow agent() / `@model` mention)。无绑定档沿 DEFAULT_TIER_ORDER 降档;
     * 与部署 catalog 同名键时 catalog WINS(SHADOW)。center 经配置域下发/覆写此表。
     */
    tiers?: Record<string, ModelRef>;
    /**
     * F4 agentType 注册表(2026-07-11):部署声明的 {@link AgentDefinition} 集,workflow `agent(…,{agentType})`
     * 经此解析(内置 Explore/Plan 之上 SHADOW)。design/141 件A:Agent 工具 lane(`createSubagentTool`)未显式
     * 传 `agents` 时经 `Runner.agentCatalog` 自动取此表——同一 Runner 两 lane 单源;显式传参仍 WINS。
     */
    agents?: AgentDefinition[];
    /** `false` 从 workflow agentType 注册表移除内置 Explore/Plan(镜像 createSubagentTool 同名开关)。 */
    builtinAgents?: boolean;
    /**
     * design/141 件2 — hands 工具带(Read/Write/Edit/Bash…)的部署方窄配置面。刻意不导出
     * `createHandsToolkit` 整厂(部署自装配塞 `spec.tools` 会被 Runner 的自动装配同名覆盖=反模式;
     * CC 亦无 options-bag 开放面,走单例+hook)。只开放安全子集,Runner 装配时合成:
     * - `beforeWrite` 与引擎的 MemoryEngine 写扫描门 **COMPOSE 而非替换**(部署 hook 先判,拒即拒;
     *   放行后引擎门仍全量生效——写保护不可能被部署配置规避)。
     * Runner 私有编排字段(taskRegistry/detachHub/execClamp/cwdRef/…)不在此面,永不开放。
     */
    hands?: HandsBandOptions;
    /**
     * design/199 件B — DEPLOYMENT-layer ADDITIONS to the built-in sensitive-path READ deny set
     * ({@link import("../tools/fs/read-deny.js").READ_FACE_DEFAULT_DENY_ENTRIES}); see
     * {@link TaskSpec.readDenyPatterns} for the judged surfaces. INDEPENDENT of the built-in switch. Add-only on
     * THIS seat: unions with the active built-ins and any task-layer additions, and `[]` ≡ absent — narrowing the built-in
     * set is the sibling knobs' job ({@link readDenyBuiltinTiers} / {@link readDenyBuiltinExclude},
     * #245 revision of D-4), not a replacement escape hatch here. Bad entry shapes refuse loudly at
     * prepare (#123).
     */
    readDenyPatterns?: readonly import("../tools/fs/read-deny.js").ReadDenyEntry[];
    /**
     * The deployment's built-in deny-table TIER selection — the switch for the built-in table. EXACTLY the
     * listed tiers of {@link import("../tools/fs/read-deny.js").READ_FACE_BUILTIN_DENY_TABLE} are active (`[]` =
     * none — explicit and legal: nothing from that table refuses a read); ABSENT =
     * {@link import("../tools/fs/read-deny.js").READ_DENY_DEFAULT_TIERS} (`credentials` and the home-anchored
     * `agent-config`). A leg's active tiers are REPORTED on its wiring manifest
     * ({@link import("./wiring-manifest.js").WiringManifest.readDeny}). Composes with
     * {@link readDenyBuiltinExclude} (tiers first, then row removal); never touches the additions
     * seats or the write faces. Unknown tier names refuse loudly at prepare (#123). DEPLOYMENT seat
     * ONLY: deliberately not a TaskSpec key and not in the governed workflow whitelist — a task or
     * governed script can only ADD deny entries, never widen the built-in face below its deployment.
     * Not frozen into checkpoints: the checkpoint face section persists ADDITIONS only, so a resumed
     * row's built-in face follows the CURRENT deployment configuration (the operator's live
     * authority), while frozen additions stay stricter-wins.
     */
    readDenyBuiltinTiers?: readonly string[];
    /**
     * Per-row removal from the built-in deny table by STABLE NAME (= the row's canonical
     * pattern text, e.g. `".ssh"`, `".config/gcloud"`; the admin delete channel), applied after
     * {@link readDenyBuiltinTiers} selection. Naming a row of an inactive tier is a satisfied intent
     * (no-op); a name matching NO table row refuses loudly at prepare (#123 — never silently
     * ignored). Same deployment-only seat, clamp argument and checkpoint posture as the tiers key.
     */
    readDenyBuiltinExclude?: readonly string[];
    /**
     * Where the host keeps the agent's configuration directory when it is not `<home>/.sema` — an ABSOLUTE path of an
     * existing directory, resolved by the host (the engine reads no environment variable for it). The rows of the
     * `agent-config` tier that name that directory (`~/.sema/settings.json`, `~/.sema/settings.local.json`,
     * `~/.sema/engine-data/.env`, `~/.sema/config-lkg-*.json`) are then ALSO placed at the same files below this root, so the model can neither read
     * nor (through the write-protection table and a wired `createSensitivePathPolicy({ agentConfigDir })`) rewrite the
     * relocated settings; the default `~/.sema` location stays refused. Rows of the home's dotted family (`~/.sema.*`)
     * and the external tools' files are not part of the directory and stay anchored at the home. A symlink the root is
     * reached through is followed (the target is guarded too). ABSENT = today's behaviour, the literal `~/.sema` under
     * the declared and account homes. A value that is not a string, is empty or relative, or names no existing directory
     * refuses `new Runner` with `config.agent_config_dir_invalid` — never read as absent. DEPLOYMENT seat only (no
     * TaskSpec twin) and fixed at construction: `swapDeps` does not carry it.
     */
    agentConfigDir?: string;
    /**
     * backlog #286 (#279 — CC 2.1.233 `DANGEROUS_FILES`/`DANGEROUS_DIRECTORIES`/
     * `DANGEROUS_DIRECTORY_PATHS` parity): the WRITE-protection table. DEFAULT-ON: a path-confinable
     * write (Write/Edit/NotebookEdit) whose target lands on a table row has a surviving `allow`
     * demoted to `ask` at the tool gate (`decisionReason: "safety"`; a deny/ask verdict is untouched;
     * approval flows the ordinary ask-resolution chain — classifier, blanket `onAsk`, durable park —
     * with no `requiresRealApproval` mandate). Absent =
     * {@link import("./write-protect.js").WRITE_PROTECTED_DEFAULT_TABLE} (the CC triple verbatim +
     * the two argued sema rows). This key IS the whole-table escape hatch, deployment seat ONLY — the
     * task layer's twin is add-only ({@link TaskSpec.writeProtectedAdditions}: its rows UNION this
     * table and can remove none), and no governed-workflow channel reaches either: `[]` = no table (explicit and legal); a non-empty
     * list REPLACES the built-in table whole (compose additions as
     * `[...WRITE_PROTECTED_DEFAULT_TABLE, …]`; drop rows by filtering the exported table — the
     * visible/deletable admin face). Bad values refuse loudly at prepare (#123): garbage shapes,
     * glob metacharacters (the table speaks LITERAL names — glob semantics live in
     * `createSensitivePathPolicy`), unknown kinds, impossible kind/name combinations. Matching is
     * lexical over the spelled target with one case fold (ı/ſ included) — a symlink alias evades it
     * by construction; the canonicalizing opt-in deny policy remains the hard layer. Not frozen into
     * checkpoints: a resumed task follows the CURRENT deployment table (an approved parked call
     * bypasses the gate as always — the human already adjudicated it).
     */
    writeProtectedPaths?: readonly import("./write-protect.js").WriteProtectedEntry[];
    /**
     * design/324 (#324 ruling ① containment) — the EVIDENCE STANDARD the delegation arm of the
     * content-origin wrap applies when deciding whether a delegation call marks THIS session's memory
     * polluted (design/178 §3 / design/180 half A):
     *  - `"static-face"` (absent ≡ this; the default) — today's behavior, byte-identical: a delegation
     *    whose delivered attestation is missing/unknown and whose static tool face can reach external
     *    content marks the session (the capability over-approximation: possibility counts as exposure).
     *  - `"attested-only"` — exactly that ONE static-face mark is waived, and each prepared leg
     *    announces the first waiver (`"memory.delegation_static_mark_waived"`). Everything else is
     *    unchanged: a delivered `"external"` attestation still marks, the chain's `incomplete`
     *    recording still happens, a non-delegation polluting-class tool still marks, and a delegation
     *    tool that is ITSELF classified polluting still marks pre-call.
     * ACCEPTED COST (the deployment's to own, stated as mechanism, not as absence of risk): under
     * `"attested-only"` a BACKGROUND child's real external contact does not mark this session — its
     * content re-enters through the TaskOutput result, the task-notification injection, or the
     * AgentTranscript step summaries, none of which carries an attestation — and a foreground child
     * that ended abnormally (crash/salvage) is likewise not marked on its face alone (the chain still
     * records `incomplete`). Already-marked sessions are never retroactively cleaned; the key only
     * governs NEW marks.
     * DEPLOYMENT seat ONLY (same posture as {@link readDenyBuiltinTiers}): deliberately no TaskSpec
     * twin and not in the governed workflow whitelist — a task author or governed script gets no
     * channel to loosen the evidence standard below its deployment. Not frozen into checkpoints: a
     * resumed leg follows the CURRENT deployment configuration. Each run reads the deps of the Runner
     * that PREPARES it — a multi-runner assembly should configure every runner with the same value
     * (drift is the deployment's own configuration hazard; recorder/attestation semantics are
     * value-independent, so the chain's evidence quality never varies with this key). Any other value
     * refuses loudly at prepare (`config.memory_delegation_evidence`, #123 — exact spellings only,
     * never truthiness).
     */
    memoryDelegationEvidence?: "static-face" | "attested-only";
    /**
     * design/336 §13-3 — the memory-provenance master switch (F-020 深修):
     *  - `"carry"` (absent ≡ this; the default) — an externally exposed session's ORDINARY memory
     *    writes are committed WITH an engine-minted `origin` marker that travels with the entry
     *    (backend/sync/bundle); instruction-form files (type: feedback / pinned/triggers/applies-when
     *    hints) from exposed sessions are withheld and quarantined; the derived index's session prose
     *    still rolls back; the content scan gates run unchanged (a marker is not an exemption).
     *  - `"off"` — the pre-336 behavior: no origin marker is minted and an exposed session's harvest
     *    admits nothing (full containment, quarantined for host review). Standing committed markers
     *    still carry forward on edits — "off" stops minting, it never strips facts already recorded.
     * ORTHOGONAL to {@link memoryDelegationEvidence} on purpose: that key decides WHEN a delegation
     * marks the session exposed (the evidence standard); this key decides WHAT the mark does to the
     * session's writes (tag-admit vs containment). All four combinations are legal and pinned.
     * DEPLOYMENT seat ONLY (same posture as {@link memoryDelegationEvidence}): no TaskSpec twin and
     * not in the governed workflow whitelist — a task author or governed script gets no channel to
     * change the provenance posture below its deployment; an org administers it through this seat.
     * Not frozen into checkpoints: a resumed leg follows the CURRENT deployment configuration. Any
     * other value refuses loudly at prepare (`config.memory_provenance`, #123 — exact spellings,
     * never truthiness).
     */
    memoryProvenance?: "off" | "carry";
    /**
     * design/383 §3.1 — the deployment's memory-capture POSTURE (the frame the per-principal
     * {@link RuntimeCaps.allowMemoryOptOut} verdict is read inside). Closed spellings, screened at
     * the config doors (`config.memory_capture_policy` — a garbage value refuses the prepare, never
     * folds to any posture):
     *  - absent / `"open"` — an opt-out declaration takes effect at face value; only a resolver's
     *    EXPLICIT `false` denies it (`memory.capture_optout_denied`, terminal). Resolver fault
     *    (throw / non-boolean / absent-on-fault) ⇒ the opt-out STILL TAKES EFFECT, with a named
     *    `onError({phase:"config"})` disclosure — the polarity-reversal arm: on this posture the
     *    irreversible failure is capturing a session whose user said no, so the fail-safe faces the
     *    other way from the caps family's deny-on-outage degrade. Under the ABSENT posture the first
     *    opt-out actually honored additionally announces one operator line (once per process): the
     *    switch is in use and no policy was ever configured.
     *  - `"governed"` — the per-principal verdict is MANDATORY (the mixed-fleet compliance
     *    deployment: some principals are retention-required). A verdict of `true` allows, `false`
     *    denies; resolver fault / absent verdict ⇒ the task is REFUSED (fail-closed): this posture
     *    is the deployment explicitly declaring the compliance side's irreversibility (a
     *    retention-required user acquiring an opt-out during an outage window) the heavier one.
     *  - `"capture-required"` — every opt-out declaration is refused (deployment-wide forced
     *    retention, one line, resolver-independent).
     * The two fault polarities are BOTH deliberate and BOTH pinned; the cost table lives beside the
     * adjudicator. Authority vs record separation (§3.3): this posture governs whether a NEW
     * declaration (or a resumed run) may proceed — it never retroactively re-enables capture for a
     * session whose one-way opt-out record already stands (record wins for capture; under
     * `"governed"`/`"capture-required"` a standing record whose entitlement has been revoked refuses
     * to CONTINUE instead — zero capture and zero out-of-policy running, availability paid).
     */
    memoryCapturePolicy?: "open" | "governed" | "capture-required";
    /**
     * design/383 §2.1b — an optional host-supplied carrier for the session capture opt-out record
     * (per memory plane, keyed by that plane's control dir). Absent = the control-plane file trio
     * (single-process/single-host deployments: byte-identical behavior). A deployment whose control
     * plane does not survive into resume replicas (the stateless multi-replica server form) supplies
     * a durable store here; see {@link import("./memory-engine/layout.js").SessionCaptureRecordStore}
     * for the per-leg fail-closed contract. Also the §2.1b capability signal: on a REMOTE-execution
     * deployment with no store supplied here, a `capture:"off"` declaration is refused
     * (`config.memory_capture_unsupported`) rather than accepted onto a carrier that the next resume
     * cannot see — a false privacy promise is worse than a loud one.
     */
    memoryCaptureRecordStore?: (plane: {
        controlDir: string;
    }) => import("./memory-engine/layout.js").SessionCaptureRecordStore;
    /**
     * design/339 §6.1 — the v3 memory-consolidation switch, threaded verbatim to the engine seat
     * ({@link import("../core/memory-engine/engine.js").MemoryEngineOptions.consolidation} — same
     * transport as {@link memoryProvenance}). ABSENT = OFF (the shipped default): no gate state is
     * ever written, no recommendation is ever minted, and the consolidation verbs on an engine the
     * host constructs over the same store refuse coded. PRESENT = enabled: the engine counts
     * distinct terminal-harvest sessions per scope and surfaces the advisory
     * `memory.consolidation_recommended` notice when the configured thresholds cross — EXECUTION
     * stays host-owned (the four verbs are engine host API; nothing in a task can trigger a run).
     * `provenance: "off"` beside this refuses loudly at prepare
     * (`config.memory_consolidation_provenance_off` — the fold law must be able to mint);
     * `multiNode: true` without a lease refuses (`config.memory_consolidation_lease_required`);
     * every other bad value refuses under `config.memory_consolidation` (#123). DEPLOYMENT seat
     * ONLY (no TaskSpec twin, not in the governed workflow whitelist), same law as
     * {@link memoryProvenance}.
     */
    memoryConsolidation?: import("../core/memory-engine/consolidation.js").MemoryConsolidationOptions;
    /**
     * design/376 片② — the memory-consolidation DRIVER seat (the LLM half), ORTHOGONAL to
     * {@link memoryConsolidation} on purpose: that key is the write PROTOCOL's parameters (fuse,
     * caps, cadence — engine-side), this key is who thinks (model seat + mint knobs — driver-side).
     * The ENGINE itself keeps zero model seats (design/339 §1.1: engine adjudicates, driver
     * proposes; an engine-held model would put distillation authority on the wrong side of the
     * trust boundary), so the seat lives here and is consumed by the host driver verb
     * (`runMemoryConsolidationDriver` + `resolveMemoryConsolidationDriver(deps)`).
     *
     * `chat` — the explicit model seat: an endpoint triple (screened loudly: explicit model id, no
     * alias spellings, key present) or any JSON-only completion function. ABSENT ⇒ the driver
     * resolves the `consolidate` role (`consolidate → summarize → coded refusal`; deliberately
     * never `default` — see the {@link ModelRole} doc). Every other knob mirrors the driver verb's
     * options and is screened there (#123: bad values refuse coded, never fold to defaults).
     * DEPLOYMENT seat ONLY, same law as {@link memoryConsolidation}: no TaskSpec twin — a task
     * author gets no channel to point the library's consolidation at a model of its choosing.
     * Absent alongside absent {@link memoryConsolidation} ⇒ nothing runs and nothing changes
     * (the v3 default posture).
     *
     * ⚠️ EGRESS — read this before configuring the seat (design/383 §1.5). Turning consolidation on
     * SENDS MEMORY CONTENT TO A MODEL PROVIDER. Specifically:
     *  - WHAT: that cycle's ELIGIBLE CANDIDATES — not the whole library (entries already superseded,
     *    of an ineligible type, or held out by a governance gate are not in the set) — each rendered
     *    as its `name`, `description` and `body`. The bodies are the user's own notes, verbatim.
     *  - WHERE: the provider this seat resolves to. When `chat` is absent that is whatever the
     *    `consolidate → summarize` role chain lands on, which MAY BE THE SAME PROVIDER AS THE MAIN
     *    MODEL and may equally be a different vendor — the resolution is the deployment's, and the
     *    driver announces the model id and endpoint it actually used at the moment of the first call.
     *  - MARKED CONTENT: entries carrying an external-origin (or repo-ingest `trust`) marker are
     *    withheld from the CLEAN products' prompts and minted through a separate marked-only call.
     *    "Withheld from the clean arm" is NOT "not sent" — such rows still go to the provider in
     *    their own call, except a lone marked row, which is sent nowhere. The run receipt reports the
     *    two counts separately (`withheldFromCleanArm` / `withheldNeverSent`) and so should any UI.
     *  - IRREVERSIBILITY: bytes that have already crossed to a provider cannot be recalled by any
     *    later configuration change. Disabling the seat stops FUTURE egress and nothing else.
     */
    memoryConsolidationDriver?: {
        chat?: {
            baseUrl: string;
            apiKey: string;
            model: string;
        } | import("../core/memory-engine/distiller.js").DistillerChatFn;
        /** The audit model id — REQUIRED when `chat` is a bare function (a function carries no id and
         *  the run/archive must not record a guess); derived from the endpoint triple or the resolved
         *  role otherwise. */
        model?: string;
        contract?: import("../core/memory-engine/distiller.js").LlmDistillerContract;
        maxCycles?: number;
        mintRepairBudget?: number;
    };
    /**
     * The advisory-to-execution BRIDGE: when `true`, a terminal harvest that crosses
     * the consolidation threshold (the same edge that mints `memory.consolidation_recommended`) fires
     * ONE host consolidation run for the recommended scope at the post-task tail — fire-and-forget,
     * result-first, never on the harvest tail itself. Explicit `false` = OFF (the deployment's own off
     * switch; it beats the default). ABSENT = the named default `AUTO_RUN_ON_RECOMMENDATION_DEFAULT` (ON)
     * (`memory-consolidation-driver.ts`, read through `autoRunOnRecommendationEffective` — the ONE
     * resolver), applied ONLY where a run could actually happen (the protocol switch wired AND a model
     * seat resolvable): a default-on value never arms a deployment that wired no consolidation.
     *
     * DEPLOYMENT seat ONLY (no TaskSpec twin), the same law as {@link RunnerDeps.memoryConsolidation} /
     * {@link RunnerDeps.memoryConsolidationDriver}: arming this is an EGRESS decision — memory content crosses
     * to the consolidation model automatically after every recommending task, never a task author's call.
     *
     * `true` with nothing to run (no {@link RunnerDeps.memoryConsolidation}, or no resolvable driver model
     * seat), or a non-boolean, is refused at prepare (`config.auto_consolidation`) — never folded to off.
     *
     * Reported on the effective wiring manifest as `WiringManifest.autoConsolidation`, PRESENT IFF armed
     * (absent and fingerprint-identical when off).
     */
    autoRunOnRecommendation?: boolean;
    /**
     * design/199 件A — the DEPLOYMENT's read-face declaration
     * ({@link import("../tools/fs/containment-face.js").ContainmentFace}; see {@link TaskSpec.readFace} for the
     * task layer and the resolution order). An interactive single-user product declares
     * `readFace: "open"` here in one line; the engine default stays "roots" (D-1b). Under org
     * governance this seat may still open (it is the deployment's own declaration); the task layer
     * may not. Beside a read-only (verifier) mount this seat's "open" silently CLAMPS to roots (a
     * deployment default cannot override a load-bearing containment wall) — TaskSpec.readFace's own
     * "open" there is the genuine per-task contradiction, and that one still refuses loudly (#123).
     *
     * A LIVE seat: `Runner.swapDeps({ readFace })` replaces it without a restart (the same natural
     * snapshot the model catalog has — a leg reads the seat at its prepare and keeps that reading; every
     * later prepare reads the new declaration; announced as `config.read_face_swapped`). A host whose
     * declaration arrives late (a governance source read after boot) swaps it in rather than restarting.
     */
    readFace?: import("../tools/fs/containment-face.js").ContainmentFace;
    /**
     * The DEPLOYMENT's WRITE-face declaration
     * ({@link import("../tools/fs/containment-face.js").ContainmentFace}; see {@link TaskSpec.writeFace}
     * for the task layer and the resolution order — the SAME order the read seat runs, one body). The
     * engine default stays "roots". Under org governance this seat may still open (it is the deployment's
     * own declaration); the task layer may not. Beside a read-only (verifier) mount this seat's "open"
     * silently CLAMPS to roots and is ANNOUNCED (`config.write_face_deployment_clamped`); TaskSpec.writeFace's
     * own "open" there refuses loudly. INDEPENDENT of {@link readFace}: neither seat reads the other. A LIVE
     * seat — `Runner.swapDeps({ writeFace })` replaces it without a restart (announced as
     * `config.write_face_swapped`).
     */
    writeFace?: import("../tools/fs/containment-face.js").ContainmentFace;
    /**
     * design/96 §20 (P1a) — DEPLOYMENT-DECLARED training knowledge cutoff per model id (e.g.
     * `{ "qwen-3.5-35b": "2025-01", "deepseek-pro": "2024-07" }`). When the resolved model id is present, the
     * `# Environment` block shows `Knowledge cutoff: <value>` so the agent reasons about what it may not know.
     * Deployment-owned ON PURPOSE: core hardcodes no model→date map (a stale cutoff = a §25 honesty breach, and
     * the model catalog is a deployment/profile concern). Omitted ⇒ no cutoff line (honest absence). Optional.
     */
    modelKnowledgeCutoffs?: Record<string, string>;
    /**
     * Default model roles: declare your models once and let every subsystem (main task, compaction,
     * subagent, team) resolve its role instead of repeating a model name. `TaskSpec.roles` overrides
     * per task/scenario. e.g. `{ default: "strong", summarize: "cheap", subagent: "cheap" }`.
     */
    roles?: ModelRoles;
    /**
     * Session lifecycle/persistence backend. Defaults to `TtlSessionStore` (in-memory, 7-day idle TTL
     * — lost on restart). For **durable** sessions (a "session center" that survives restarts and is
     * shared across stateless replicas), supply a `TtlSessionStore` over a durable `SessionRepo`
     * (e.g. TiDB-backed), or a fully custom `SessionStore`. See `design/10-会话持久化与会话中心.md`.
     */
    sessionStore?: import("./session.js").SessionStore;
    /**
     * Durable-checkpoint backend for suspend/resume (design/45). A task with `durableApproval` set
     * persists its awaiting-human checkpoints here and ends `status:"suspended"`; `runner.resume()` reads
     * them back — cross-replica when this is durable (service's approval/run-store). A task's own
     * `checkpointStore` overrides this. Omitted → no durable suspension (policy `ask` stays synchronous).
     */
    checkpointStore?: import("./checkpoint-store.js").CheckpointStore;
    /**
     * design/380 O2 — deployment-supplied resume preflight, called INSIDE the pre-CAS ladder (after the
     * row-integrity rungs, immediately before the CAS) with a bounded deadline
     * ({@link resumePreflightTimeoutMs}). Refusal / throw / timeout ⇒ typed `resume.preflight_rejected`
     * (CheckpointError closed-set addition); a wait ended by the RESUME SIGNAL is the caller's own
     * cancellation and answers `checkpoint.resume_aborted` instead (one code at the resume entrance);
     * the DEFAULT/absent disposition is `retry_later` (the safe
     * arm — fail-closed hooks that just throw can never accidentally terminalize a row) — the
     * entrance-screen posture of the wake-message hook (design/373 D2), generalized from "screen the
     * wake message" to "screen the resume". A deployment binding runs to targets (a device lane) checks
     * its placement/binding tables here: a transient obstacle answers `retry_later` (+`retryAfterMs`,
     * the #449 G1 wait-hint carrier — the row stays `pending`, the SAME token redeems later); a
     * PERMANENT one answers `terminal` (the row is settled by the single-shot `expire` CAS — never a
     * silent pending-forever; bounded waiting for a target to come back also belongs HERE, inside the
     * deadline, never in the env factory). Absent ⇒ no preflight, byte-identical resume behavior.
     */
    resumePreflight?: (info: ResumePreflightInfo, signal: AbortSignal) => Promise<ResumePreflightVerdict>;
    /**
     * design/380 O2 — the {@link resumePreflight} deadline in ms. Default 10s (its own bound,
     * deliberately its own: a resume preflight sits on every redeem attempt of a
     * parked row and must answer promptly or get out of the way). Bad values (non-finite, ≤ 0,
     * non-number) are DISCLOSED loudly through `onError` and fall back to the default (the bad-value
     * loudness default: announce, never silently absorb).
     */
    resumePreflightTimeoutMs?: number;
    /**
     * design/381 — the per-edited-file rewind history backend (CC FileHistory topology; replaces the
     * E19 whole-tree `fileSnapshotStore`). WIRING IS THE OPT-IN (DV-9, the reference's own SDK
     * posture): with a store present (and {@link fileHistoryCapture} not `"off"`), every Edit/Write/NotebookEdit first-touch is backed up before
     * the byte changes, every turn mints a history boundary keyed by its own initial session entry
     * ("file state when this turn began"), and a {@link TaskSpec.resumeAt}+{@link TaskSpec.restoreFiles}
     * (or {@link TaskSpec.rewindFilesTo}) run converges the tracked set back to a boundary. Omitted ⇒
     * rewind is OFF: zero track calls, zero boundary work, zero disclosure noise; a restore request
     * then fails loud (`rewind.store_unconfigured`). The reference impl is
     * {@link InMemoryFileHistoryStore}; a deployment supplies a durable backend behind the same seam
     * (backends MUST reuse the exported env-half helpers — see the contract JSDoc). Works against ANY
     * ExecutionEnv (local or remote): bytes are read/written through the env's FileSystem face and
     * stored host-side, so an env rebuild does not lose history. `"disabled"` = the deployment DECIDED
     * not to capture: the omitted-key semantics minus the once-per-Runner `onError` advisory an omitted
     * key earns on a filesystem-capable run (`classification: "file-history-no-store"`).
     */
    fileHistoryStore?: import("./file-history-store.js").FileHistoryStore | "disabled";
    /** The deployment's CAPTURE switch, apart from the store seat: `"off"` (the only word, and only beside a wired store; anything else refuses prepare, `config.invalid_file_history_capture`) ⇒ no run records — no first-touch record, no turn boundary, no fork carry, no liveness advisory, manifest `fileHistory: "disabled"` — while a wired {@link fileHistoryStore} still serves {@link TaskSpec.restoreFiles} / {@link TaskSpec.rewindFilesTo} from what it already holds. */
    fileHistoryCapture?: "off";
    /**
     * design/381 DV-14 (终审落锤) — what an Edit/Write/NotebookEdit does when its FIRST-TOUCH history
     * record cannot be persisted (`FileHistoryStore.trackEdit` answered `{ok:false}` = NO durable
     * first-touch state exists). Default **"refuse"**: the edit is refused with a typed tool error
     * naming the history-store failure — proceeding would leave the path indistinguishable from
     * never-touched, and the store's post-recovery retry would then mint already-modified bytes as
     * pristine v1 (the fake-pristine hole this default welds shut).
     *
     * `"proceed-unprotected"` is the availability concession arm: the edit proceeds and the failure is
     * disclosed via `onError` (phase "rewind"). ITS RESIDUAL, stated so the choice is informed: during
     * a store outage NO durable trace of the touch can be written, so "the pristine promise is void"
     * lives only in this process's memory — after a process restart the risk is exactly the default
     * arm's nightmare again (the store recovers, the next edit mints already-modified bytes as v1).
     * A deployment choosing this arm owns that window and is expected to record the disclosed events
     * in its own audit surface. Values outside the two-member set refuse prepare loudly.
     */
    onTrackFailure?: "refuse" | "proceed-unprotected";
    /**
     * design/99 §E6 — DURABLE per-session permission rules (a deny-narrowing folded into the task-time tool
     * policy). Omitted ⇒ the feature is OFF (no rules read, zero behavior change). A deployment supplies a
     * cross-replica backend whose CAS-rev + tighten-only semantics match the core
     * {@link import("./session-policy-store.js").InMemorySessionPolicyStore}. Rules are read at prepare time for a
     * task carrying its own `sessionId` (a delegated subagent inherits none) and composed BEFORE the caller
     * policy, so they can only SUBTRACT capability. core does NOT verify owner/operator identity — the store
     * honors the `operator` flag the (service) caller freezes from a verified principal.
     */
    sessionPolicyStore?: import("./session-policy-store.js").SessionPolicyStore;
    /**
     * design/389 — the ONE permission-rule seam: the unified store, partitioned by source
     * (`org | user | project | session`), built by `createPermissionRuleStoreProvider({ durable?, org?, session? })`.
     *
     * It is the one seam in this file that LOOSENS, and it is shaped so it can only do so within limits the
     * engine holds. The provider hands out a store whose ONE read query answers every partition with each
     * row's source and effective status; the write face is an exported BACKEND CONTRACT for the durable
     * partition (an out-of-repo store twin builds against the same definitions instead of mirroring them),
     * but the ENGINE reaches it only on the consent lanes — redemption of a confirmed human decision, the
     * tighten-delete, and the sync join — a wiring invariant pinned by a registered-caller scan
     * (test/permission-rule-writer-callers); no exported convenience mints a rule around consent. Personal
     * rules are consumed post-fold, in the gate's ask branch, and never resolve an ask carrying
     * `requiresRealApproval` or one a PreToolUse hook raised.
     *
     * The ORG partition (present iff the provider was composed with an `org` config — the governance
     * declaration, design/182 §7.4) is the outermost TIGHTENING authority: every tool call is adjudicated
     * against the org's published deny/ask snapshot BEFORE the ask-resolution chain (org deny > org ask >
     * personal allow rule > bare ask), and while the org partition cannot adjudicate the WHOLE decision
     * boundary fails closed — every terminal allow becomes a real-approval ask and both ask→allow seams are
     * disarmed. The SESSION partition (present iff composed with one) is where `{kind:"session"}` grants
     * land and are read from, spliced in front of the durable rows on every adjudication of that session.
     *
     * Omitted ⇒ no rule lane, no org layer, no session source: no rules are read, no field is added to any
     * ask, and the decision path is byte-identical to a build without it. An unauthenticated task (no
     * `principal`) resolves to zero personal rules rather than to a shared bucket (the org partition still
     * adjudicates), and a store whose durable partition cannot be read yields zero rules plus a disclosure
     * — a loosening face fails toward asking.
     */
    permissionRuleStore?: import("./permission-rule-provider.js").PermissionRuleStoreProvider;
    /**
     * design/182 §4.5 (F-011) — declare that this deployment keeps its permission rules in the
     * IDENTITY-LESS local bucket: a task with no `principal` resolves rules through the provider's
     * `forLocalOwner()` face instead of resolving to zero rules.
     *
     * A DECLARATION, never an inference: the local bucket is "this machine's owner", a fact only the
     * deployment knows. Omitted/`false` ⇒ an unauthenticated task reads no rules. Declared `true` while the
     * wired provider has no local-owner face (or while no provider is wired at all) is a configuration
     * contradiction and is refused loudly at prepare, rather than silently degrading to "the rules this
     * person approved stopped applying".
     *
     * A local-owner bucket cannot cloud-sync (syncing is an authenticated act); it is adopted into a
     * principal bucket by `adoptFilePermissionRuleStore`, after which the local-owner face resolves the
     * adopted principal's bucket forever.
     */
    localOwnerRules?: boolean;
    /**
     * The DEPLOYMENT's allow rules — standing yeses its operator writes for every run of this Runner, in the grammar of
     * {@link import("./task-spec.js").TaskSpec.allowedTools} (the same compiler, matcher and auto-mode strip). One of the allow
     * layer's members, read beside the request's list, a person's stored rules and a skill's list under one predicate: deny
     * and ask rules still win, a question no allow rule clears is still put to a person (the entry disclosed as the
     * deployment's), and a call it clears is attributed to it (`decisionReason: "deployment_rule"`). Lives for the Runner —
     * read once at construction: a value that is not a list of strings refuses construction (`config.invalid_permission_rules`,
     * with its issues), and the entries that read as nothing are announced once, when the Runner is built
     * (`permission.allowed_tool_not_granted`, `detail.entries[].source: "deployment"`).
     */
    allowedTools?: readonly string[];
    /**
     * design/182 §9 — DECLARE that this deployment drives cloud sync (`syncPermissionRules`) for the
     * wired rule store. Purely a disclosure input: it is reported as `permissionRules.syncWired` on the
     * wiring manifest and changes no decision. It exists because the sync loop is HOST-driven (core
     * bundles no fetch and owns no timer), so there is nothing for the engine to infer — and "are this
     * machine's standing approvals shared with other devices and a server?" is exactly the trust-domain
     * fact an operator must be able to read off the manifest instead of guessing.
     */
    permissionRuleSyncWired?: boolean;
    /**
     * design/99 §K — resolve the **per-principal runtime ENTITLEMENTS** the engine
     * ENFORCES server-side, keyed by `spec.principal`. A deployment (the SERVICE) implements it over center's
     * `GET /api/config/effective?principal=` `runtimeCaps`. Resolved once at prepare time.
     *
     * 🔐 This is a TRUSTED deployment seam, NOT a caller-facing `TaskSpec` field: entitlements are purely
     * server-governed — an untrusted caller must never be able to self-grant a capability. `undefined` (no
     * resolver, or an unset cap) = NO per-principal restriction (TIGHTEN-ONLY: a cap can only ever DENY or
     * FORCE; absence falls back to the deployment-level default). Symmetric with {@link sessionPolicyStore}.
     *
     * core enforces the six caps it CAN enforce in the engine (polarity per member on {@link RuntimeCaps}):
     * `allowWorkflows` (the third stage of the workflows gate — task opt-in ∧ deployment capability ∧ this),
     * `allowFork` (the Agent-fork route), `allowObservers` (explicit opt-in for observer auto-spawn),
     * `autoMode` (the per-principal deny bit of the user-enabled auto mode), `allowMemoryOptOut` (may this
     * principal declare a capture opt-out), and `forceDurableGate` (forces a run onto the durable-approval
     * path so a policy `ask` suspends to the wire — where a checkpoint store is wired; a store-less leg
     * announces `config.durable_gate_unavailable`), and `bypassPermissions` (the deny bit of the
     * `bypassPermissions` mode — a denied request folds to `default`, announced). `allowUltracode` is a shell/UX
     * concern (reasoning-tier, not a core primitive).
     */
    runtimeCapsResolver?: (principal: string | undefined) => RuntimeCaps | undefined | Promise<RuntimeCaps | undefined>;
    /**
     * Administrator lock layer (ruled 2026-08-05) — the deployment's config-time declaration of which
     * lockable keys are locked (closed registry: `mcp` / `toolPolicy` / `compliancePosture` /
     * `retentionPolicy`; see {@link import("./locked-config.js").LOCKED_KEY_REGISTRY}). A task spec
     * carrying a value for a locked key is refused wholesale at the prepare door
     * (`config.locked_key`); an unknown key in this declaration is itself refused
     * (`config.locked_registry` — a security control that doesn't parse must not half-arm).
     *
     * 🔐 TRUSTED deployment seam, never a `TaskSpec` field. Locks are minted only by the deployment's
     * own configuration plane (env / central governance domain) under that plane's existing
     * authentication — "who can unlock" ≡ "who can change the deployment config"; there is no
     * request-time unlock.
     */
    lockedConfig?: import("./locked-config.js").LockedConfig;
    /**
     * Compliance capability veto (ruled 2026-08-05) — resolve the running principal's compliance
     * POSTURE (closed profile + closed additional denies; effective denies =
     * `BUILTIN_COMPLIANCE_DENIES[profile] ∪ additionalDenies`, supply can only tighten). Mirrors
     * {@link runtimeCapsResolver}: a 🔐 TRUSTED deployment seam keyed by `spec.principal`, resolved
     * ONCE per prepare; the verdict is frozen for the run and follows the delegation tree through the
     * inherited principal. Fail-closed: a resolver that throws (or returns an unparseable posture)
     * denies EVERY managed capability for the run and surfaces via `onError` (phase `"config"`); a
     * denied capability that the spec explicitly requests refuses the prepare loudly
     * (`config.compliance_denied`, or `config.compliance_required` when the refusal came from the
     * resolver fault rather than an adjudicated posture). `undefined` (no resolver / no posture for
     * the principal) = no compliance restriction from the deployment — existing deployments are untouched. The run's deny
     * set is this posture's UNION the request's own denies ({@link TaskSpec.complianceAdditionalDenies}, add-only).
     */
    compliancePostureResolver?: (principal: string | undefined) => import("./compliance.js").CompliancePosture | undefined | Promise<import("./compliance.js").CompliancePosture | undefined>;
    /**
     * Org-memory tenant admission (ruled 2026-08-05) — the fail-closed adjudicator for REQUEST-origin
     * `org:` memory scopes (`TaskSpec.memory` / a subagent's `AgentDefinition.memory`, under the v2
     * scope contract). Runs at prepare, after `normalizeMemorySpec` and before any memory-backend I/O.
     * Verdicts are two-state ({@link import("./memory-admission.js").MemoryAdmissionVerdict}): any
     * request-origin org scope not admitted refuses the WHOLE prepare (`memory.admission_denied`) —
     * silent narrowing would turn mount visibility into an org-membership probe. Absent resolver +
     * request-origin org scope ⇒ `memory.admission_required` (fail-closed, transient — the control
     * plane may be mid-rollout); resolver throw ⇒ same code. Deployment-origin scopes
     * ({@link deploymentMemoryScopes}) pass WITHOUT this resolver; when present it may still narrow
     * them (disclosed via `onError`, phase `"memory"`).
     *
     * 🔐 TRUSTED deployment seam, never a `TaskSpec` field (an untrusted caller must never adjudicate
     * its own tenant admission). The admitted org set is frozen into the run's child chain — a
     * delegated child's request plane can only narrow it, never exceed it.
     */
    memoryScopeAdmission?: import("./memory-admission.js").MemoryScopeAdmission;
    /**
     * The deployment's SELF-CERTIFIED org memory scopes (ruled 2026-08-05): org keys the operator
     * pinned in the deployment's own configuration (env / registry defaultScopes). A scope listed here
     * is `origin: "deployment"` at the admission door — the operator writing the config IS the
     * authorization: no principal required, no {@link memoryScopeAdmission} resolver required (the
     * single-user zero-migration path). Everything not listed here that requests an org plane is
     * `origin: "request"` and must pass fail-closed admission.
     */
    deploymentMemoryScopes?: readonly string[];
    /**
     * Managed retention policy (ruled 2026-08-05, retention.ts). Core consumes it ONLY for the
     * startup/prepare capability validation: when the locked-config plane LOCKS the `retentionPolicy`
     * key AND this policy is configured, every wired store (session/checkpoint/tool-result) must
     * declare `retention: "managed"` — otherwise the deployment refuses to start
     * (`config.retention_capability`; the prepare preflight is the per-leg backstop). The engine
     * itself never deletes data on the task path — retention EXECUTION (scheduling, retries, audit)
     * is the deployment scheduler's job against the `ManagedRetentionCapability` contract.
     */
    retentionPolicy?: import("./retention.js").RetentionPolicy;
    /**
     * design/143 批2b — the auto-mode classifier's DEPLOYMENT assembly face. This is the
     * CAPABILITY arm of the three-arm arming: a run arms auto mode only when the task carries the
     * auto-mode INTENT (`TaskSpec.permissionMode: "auto"`, or the mode inherited on the chain by an
     * engine-spawned child) AND this face is present AND the per-principal DENY bit is not set
     * ({@link RuntimeCaps.autoMode}` !== false` — absent is not a denial).
     *
     * 🔐 Trust gate (the CC 2.1.207 three-source invariant): classifier RULES enter EXCLUSIVELY
     * here — a deployment-constructed object, never a `TaskSpec` field, never a repo-file plane. The
     * prompt is assembled from the SHA-locked CC 2.1.207 assets (`buildAutoModePrompt`) with these
     * rules merged CC-exactly (user rules replace a section; the `"$defaults"` sentinel splices the
     * stock rules back in). The classify leg runs on the `classifier` role's model (fallback
     * `summarize → default`; tier default `flash`) via the brain's simple-completion pipe — same
     * lane as compaction/consolidation, no tools, no session writes.
     */
    /**
     * The deployment's A2A peer switches. `isolatePeers: true` makes every A2A peer skill's egress default an
     * OPERATOR mandate: its ask is `operator_forced` (kept under `bypassPermissions`; `dontAsk` refuses it like every ask)
     * instead of the releasable `tool_marks_a2a`. Absent ⇒ the default (a permission mode may release it, disclosed).
     */
    a2a?: {
        isolatePeers?: true;
    };
    autoMode?: {
        /** Rule overrides for the four permissions sections (omitted section = CC defaults verbatim). */
        rules?: import("./auto-mode-prompt.js").AutoModeRules;
        /** Settings-plane deny rules rendered into the `<settings_deny_rules>` slot (circumvention catch). */
        settingsDenyRules?: readonly string[];
        /** Extra `## Session Context` facts (e.g. the user-identity line). */
        sessionContext?: readonly string[];
        /** Transcript-window bounds for the classify prompt (defaults: 40 entries / 2000 chars each). */
        window?: import("./auto-mode-prompt.js").AutoModeWindowOptions;
        /** Classify round-trip cap, ms — ONE ladder rung (see `classifierFallback`), not the whole classification;
         *  default `AUTO_MODE_DEFAULT_TIMEOUT_MS` (180 000, sized for a self-hosted gateway's queue + prefill; worst
         *  case two rungs = 360 s). A rung past the cap did not rule: the walk moves to the next candidate; with none
         *  left the classification is `unavailable` — the call goes on to a person where the leg has an approval seat
         *  (a live approver or a durable park), and is DENIED with a sentence that says so where it has none; the
         *  user hears it either way (`classifier.unavailable`). The next call is classified afresh. A present value
         *  that is not a number of milliseconds in [1, 2147483647] is REFUSED loudly at every leg's prepare while this face
         *  is present, whatever mode the leg starts in (`config.auto_mode_timeout_invalid`) — never clamped, never read as the
         *  default; there is no OFF. */
        timeoutMs?: number;
        /**
         * What a classification does when the classifier SEAT does not rule (the leg threw / rejected, the cap
         * elapsed, or the reply carried no verdict):
         *  - `"main"` (DEFAULT) — re-ask the SAME prompt on this run's own main model, once, and act on what it
         *    says; only if that rung does not rule either does the call reach the fail-closed deny. The record
         *    says which model answered (`GateDisposition.classifier`) and the first move per (rung, cause)
         *    announces `classifier.fallback` to the operator.
         *  - `"none"` — one rung: a classifier that does not rule denies the call exactly as it did before this
         *    knob existed, at exactly one model call.
         *
         * COST of the default, stated: while the seat is broken every gated call pays a second round-trip
         * (worst case two `timeoutMs` caps); `"none"` buys that back for the deny.
         *
         * A value outside the closed set ({@link import("./auto-mode.js").AUTO_MODE_CLASSIFIER_FALLBACKS}) is
         * REFUSED loudly at every leg's prepare while this face is present, whatever mode the leg starts in
         * (`config.auto_mode_classifier_fallback_invalid`) — never clamped, never read as the default.
         */
        classifierFallback?: import("./auto-mode.js").AutoModeClassifierFallback;
        /**
         * The classifier DENIAL LIMIT (CC 2.1.250 `FO`/`AKe`): a run whose classifier keeps blocking falls
         * back to a PERSON instead of being denied without end. Per run: a `block` first increments the
         * consecutive and total counts and then judges `consecutive >= maxConsecutive || total >= maxTotal`
         * — the block that reaches a bound is itself the one that becomes an `ask` (the 3rd consecutive
         * block asks). That ask carries `requiresRealApproval: true` (no automatic lane may clear it — not
         * a sandbox admission, not an inherited resolver, not a blanket `onAsk:"allow"`) plus the additive
         * `denialLimitFallback` member with the counts and its own auto-deny window; an ask handed to a LIVE
         * function approver and left unanswered auto-denies after `autoDenyAfterMs` (default 120s; `0` = no
         * window) — the window is armed at that hand-out only, so a durably PARKED fallback carries `0`
         * (nothing counts down on that lane). A classifier allow, or a
         * person's allow of the fallback ask, zeroes the consecutive count; reaching the total bound
         * zeroes everything. With no approver wired at all (headless), the fallback has nowhere to go and
         * the run STOPS with `TaskResult.errorCode = "classifier.denial_limit"` (a notice of the same
         * code is minted).
         *
         * Every member optional (defaults 3 / 20 / 120_000). A present member with a bad value, a member that is
         * not one of the three, or a value that is not a record, is refused loudly at every leg's prepare while this face is present, whatever mode the
         * leg starts in (`config.auto_mode_denial_limit_invalid`) — never clamped, never silently read as the default.
         * See {@link import("./auto-mode.js").AutoModeDenialLimitOptions}.
         */
        denialLimit?: import("./auto-mode.js").AutoModeDenialLimitOptions;
        /**
         * OPT IN to recording this arming's serializable criteria (an
         * {@link import("./auto-mode-arming.js").AutoModeArmingRecipe}) on the constraint-chain entries a
         * durable park freezes, so a CROSS-PROCESS redemption can rebuild an equivalent classifier over its
         * own model leg ({@link import("./auto-mode-rebuild.js").rebuildAutoModeDecider}) instead of
         * answering `unavailable` forever and parking the same approval for a human on every redemption.
         *
         * DEFAULT OFF, and the default is the safety story: a row minted without it is byte-identical to
         * the pre-#503 shape, so a mixed-version fleet (a worker that predates this field resuming a row a
         * newer worker minted) keeps the digest it can compute. Turn it on fleet-wide, not per-worker.
         *
         * What gets written down: the rule overrides, the settings-deny rules, the session-context facts,
         * the window bounds and the two knobs — i.e. deployment-authored configuration, on a durable row.
         * `sessionContext` is the one field that routinely carries per-session facts (a user-identity
         * line); a deployment that treats those as sensitive at rest should keep them out of the arming
         * face rather than out of this flag.
         *
         * A `true` here with a non-serializable face announces once and records nothing (`phase:"config"`).
         */
        persistArming?: boolean;
        /**
         * The deployment's auto-mode SETTINGS GENERATION (a config revision, a settings hash, a
         * publish id — the string is opaque to the engine). Recorded in the arming recipe and compared at
         * rebuild time against the REDEEMING deployment's own: it is a declaration axis that can only make
         * the rebuild stricter (see {@link import("./auto-mode-arming.js").foldAutoModeArming}). Absent =
         * undeclared, which the fold treats as "cannot assume unchanged".
         */
        settingsEpoch?: string;
    };
    /**
     * design/129-B — PROCESS-level observer for background delegation children (Agent/Fork
     * `run_in_background`): `spawn` (at registry registration) / `tick` (every forwarded `task_progress`)
     * / `terminal` (after the registry settle), for the child's WHOLE lifetime. Motivation: every per-leg
     * sink (`RunInternals.onForwardEvent` etc.) carries the SPAWNING turn's lifecycle — correct for sync
     * children, structurally wrong for a session-scoped background child that outlives the turn (the
     * deployment rightly freezes per-leg publishers at leg settle, which silences the child). This seam is
     * keyed to nothing but the process: wire it once at Runner construction and publish fleet rows /
     * completion pushes from it. Observability only — a throwing observer is swallowed, never faults a run.
     */
    onBackgroundChildEvent?: (event: BackgroundChildEvent) => void;
    /**
     * #281 件B — PROCESS-level observer for EVERY delegated child leg's lifecycle (`spawn`/`terminal`),
     * all delegation lanes at once: synchronous and steer-handle delegations (whose spawn/settle
     * previously reached only the trusted `RunInternals.onSubagentSpawn` third parameter — and whose
     * `settled` is a void promise), background/fork/revive children (whose {@link BackgroundChildEvent}
     * family this seat complements, not replaces — BCE stays the fleet-row lane with registry `a*`
     * handles and ticks; this seat is the leg-identity lane), and workflow-spawned agents. Frames are
     * minted at the child leg's own runner chokepoint and carry the #281 件A identity envelope plus a
     * terminal status summary; the `spawn` phase is the projection of the child's `task_started` frame
     * (once per cycle), the `terminal` phase is per leg — see {@link DelegationLifecycleEvent} for the
     * emission points and the recorded honest absences. Wire it once at Runner construction; a
     * deployment needs NO RunInternals access to observe delegation any more. Observation only — a
     * throwing or rejecting observer is contained ({@link deliverDelegationLifecycle}) and never
     * faults the child run; a PRESENT non-function value here is announced once per process and the
     * frames are simply not delivered (#123 — a bad seat must be loud, and this stream's loud exit is
     * the seat defect, not a per-frame console flood).
     */
    onDelegationLifecycle?: (event: DelegationLifecycleEvent) => void;
    /**
     * design/98 (S8) — the HARD sandbox seam for LLM-AUTHORED workflow scripts (`TaskSpec.selfOrchestration`).
     * A deployment supplies an isolated-vm / separate-process runner whose `safeForUntrustedScripts === true`;
     * core does NOT ship one (the Node `vm` dev runner is NOT a security boundary). With this unset (or
     * `safeForUntrustedScripts:false`), `selfOrchestration` is FAIL-CLOSED: the `run_workflow` tool is not
     * mounted and the orchestration prompt is not injected. See {@link WorkflowGovernanceBaseline}.
     */
    workflowScriptRunner?: import("../orchestration/workflow-script-runner.js").WorkflowScriptRunner;
    /**
     * design/98 §2.5 (S8b) — the deployment-trusted governance every workflow-spawned sub-agent inherits
     * (tighten-only). REQUIRED (alongside a hard `workflowScriptRunner`) for `selfOrchestration` to activate:
     * unset ⇒ FAIL-CLOSED (the `run_workflow` tool is not mounted — a script-spawned agent must never run
     * without a governed baseline). See {@link WorkflowGovernanceBaseline}.
     */
    workflowGovernanceBaseline?: WorkflowGovernanceBaseline;
    /** design/98 §D.6 (S8c) — deployment hard CEILINGS for an LLM-authored workflow (script length / agent
     *  count / timeouts / per-child cost+token caps / budget / result+log size). Defaults bound a runaway. */
    workflowLimits?: import("../orchestration/run-workflow-tool.js").WorkflowLimits;
    /** design/98 §D.5 (S8c) — re-invoke seam: called once when an LLM-authored workflow completes, with a
     *  redacted, bounded summary. Unset ⇒ the run still completes + is queryable; the originator just isn't
     *  pushed a notification. */
    workflowCompletionNotifier?: import("../orchestration/run-workflow-tool.js").WorkflowCompletionNotifier;
    /** design/97 S1b / design/98 §D.5 — persist LLM-authored workflow runs here for cross-replica `/workflows`
     *  history (opt-in; in-process runs are still subscribable without it). */
    workflowRunStore?: import("./workflow-run-store.js").WorkflowRunStore;
    /** design/151 S1a — the durable background-agent execution ledger (the `workflowRunStore` twin for
     *  `a*` rows): registrations write restart-surviving rows, and the auto-mounted TaskOutput/TaskStop
     *  fall back to it on a live-registry miss (terminal snapshots / honest cross-instance reads, behind
     *  the fail-closed §3.1c predicate). Opt-in; absent ⇒ the in-memory-only pre-151 lifecycle. A
     *  deployment that composes its own Agent tool MUST pass the SAME instance as
     *  `SubagentToolOptions.background.agentStore` (spawn-side writer half) — the engine cannot
     *  verify the pairing (the Agent tool is deployment-composed), and a half-wired deployment is a
     *  SILENT degrade: reader-only ⇒ no rows ever exist; writer-only ⇒ the auto-mounted fallbacks
     *  here never see them (RB-37). Third consumer: delegated
     *  CHILDREN's auto-mounted TaskOutput/TaskStop/AgentTranscript read THIS deps too (prepare-task
     *  wires them from the child's runner) — a sub-runner with different deps splits the read face. */
    backgroundAgentStore?: import("./background-agent-store.js").BackgroundAgentStore;
    /** design/151 §7 S3c — the durable SendMessage mailbox (delivery-ladder third rung): a message to a
     *  SETTLED named teammate is parked here ONLY inside the tier-3 claim-winner window and delivered
     *  when the revival spawns (claim/lease + ack — never a destructive drain; a crashed revival's
     *  lease expires and the messages re-serve with their original seq). Tier-3 activates only when
     *  BOTH this and `backgroundAgentStore` are wired AND a delegation tool (createSubagentTool) is
     *  mounted on the task — anything less keeps SendMessage's honest refusal texts, byte for byte.
     *  Same single-instance pairing discipline as `backgroundAgentStore` (RB-37): a deployment that
     *  composes its own tools must thread the SAME instance everywhere. */
    mailboxStore?: import("./mailbox-store.js").MailboxStore;
    /**
     * Subagent transcript persistence — the delegation ENTRY caps (CC parity values: 20 concurrent /
     * 200 cumulative per session tree; defaults exported as `DELEGATION_MAX_CONCURRENT_DEFAULT` /
     * `DELEGATION_MAX_PER_SESSION_DEFAULT`). Key = `(scope, rootSessionId)`, full depth (grandchildren
     * share the tree's pool). `maxConcurrent` bounds the tree's RUNNING children of every local lane:
     * running/pending a* handles in this process's registry PLUS the synchronous and forked children in
     * flight in this process (parked does not burn a slot — a suspension is not concurrency; a revival
     * claim counts like a spawn); `maxCumulativePerSession` bounds the RETAINED-WINDOW cumulative count
     * (registry-retained + store-retained rows, plus those same in-flight local children — a reaped row
     * returns its quota, and a synchronous child retains nothing so it leaves the window when it
     * settles; deliberately NOT CC's lifetime-monotonic session counter, which would require a
     * persistent counting surface this economic bound does not justify — registered divergence). One
     * consequence, stated rather than left to be discovered: a tree that only ever spawns synchronously
     * is bounded by the concurrency cap alone. Refusals are coded (`delegation.concurrency_cap` /
     * `delegation.session_cap`) with the current value and this knob's name in the text.
     *
     * BAD VALUES REFUSE LOUDLY at prepare (`config.delegation_entry_caps`, the #123 posture): a
     * non-positive/non-integer/NaN member, or a resolved pair where `maxConcurrent` exceeds
     * `maxCumulativePerSession` (you cannot run more at once than you may ever create) — never a
     * silent fold to the defaults. Per-replica bound (multi-replica deployments are each honestly
     * bounded; row-level CAS owns correctness, this cap owns economics).
     */
    delegationEntryCaps?: {
        maxConcurrent?: number;
        maxCumulativePerSession?: number;
    };
    /** The peer lane's ONE knob table (`PeerLaneConfig`): the always-on admission gate (rate/dedup/hop-chain/
     *  queue bounds), the wake and per-pass delivery budgets, and the `ListAgents` freshness window. Per-field
     *  range-validated with out-of-range values falling back to that field's default, announced once; there
     *  is NO off switch — absent means the default table. Threaded into the auto-mounted SendMessage as
     *  `SendMessageToolOptions.lane` and into the session-box drain; read per call / per pass. */
    peerLane?: Partial<import("../agents/peer-admission.js").PeerLaneConfig>;
    /**
     * design/385 §2.1 / §5.1 — the peer-session DIRECTORY: the discovery truth of the cross-session
     * lane (other sessions of this engine for the same user). A HOST implements the contract (cli: the
     * pid-keyed registration files; server: its session table) and writes the rows; the engine only
     * reads them. Seat semantics = the mailbox seat's: NO seat, NO lane — every face stays byte-identical
     * to a pre-385 build. With the seat wired the engine (a) mounts `ListAgents` (alias `ListPeers`),
     * (b) opens SendMessage's `session.<id>` address arm and its last `name [ref]` rung, (c) drains this
     * session's own `session.<sessionId>` box at every turn boundary (the mailbox contract's third
     * consumption chain), and (d) splices the cross-session rule into the auto-mode classifier slot.
     * HARD PRECONDITION (design/385 §1.2⑥): `mailboxStore` must be wired AND declare
     * `crossProcessSafe: true`; otherwise the lane is REFUSED at prepare with a named
     * `config.peer_lane_unmounted` notice (never mounted on luck), and the seat is inert for that run.
     */
    peerDirectory?: import("../agents/peer-directory.js").PeerDirectory;
    /**
     * design/385 §4.4 — this deployment's `crossSessionInbound` setting LAYERS for the drain-point
     * judgment (managed / user / repo, resolved by `resolveCrossSessionInboundSetting` — explicit value
     * wins, unset ⇒ mode parity, an unrecognized value forces `hold` loudly). Read at every drain, so a
     * host that re-reads its settings files hands the current layers through a getter. Absent = unset
     * everywhere (mode parity, the CC default). Inert without {@link peerDirectory}.
     */
    crossSessionInbound?: import("../agents/cross-session-judge.js").CrossSessionInboundSettingLayers | (() => import("../agents/cross-session-judge.js").CrossSessionInboundSettingLayers);
    /**
     * WHO consumes this deployment's principal inbox (`principal.inbox`, where the
     * organization's delivery gate appends cross-principal letters; the mailbox contract's fourth chain).
     * Absent = no consumer, no lane (byte for byte as before); a deployment that mounts a gate DECLARES
     * `{ assistant: "<agent handle>" }` (the scope's assistant is the only consumer; no terminal leg touches the
     * box). `"terminals"` (each terminal drains the box as a second lane) is refused until held decisions are
     * shared across terminals (`detail.reason: "terminals_unsupported"`). Letters are judged on the principal's
     * `crossSessionInbound` layers (managed, user), held when unset. ANY other value fails the prepare of a leg
     * that drains a session box with `config.principal_inbox_consumer_invalid` (`detail.reason: "malformed"`).
     */
    principalInboxConsumer?: "terminals" | {
        assistant: string;
    };
    /**
     * design/385 §4.4 (slice 4) — this deployment's `dialogExpiry` (CC settings key, verbatim vocabulary
     * `"60s" | "5m" | "10m" | "never"`, default `"5m"`): how long a HELD cross-session message whose cause
     * a human review could resolve (mode-mismatch / no-mode-asserted / invalid attestation) waits in this
     * session's held queue before it resolves to its safe no-action default — EXPIRED, dropped WITH a
     * receipt to the sender, never silently. `"never"` disables the deadline. Read at every drain round
     * (getter form for a host that re-reads its settings). A value outside the vocabulary is announced
     * ONCE per leg through `onError` (`classification: "peer-dialog-expiry"`) and the default applies —
     * a garbage setting never silently reads as a policy. Absent = the default. Inert without
     * {@link peerDirectory}.
     */
    crossSessionDialogExpiry?: import("../agents/peer-notices.js").CrossSessionDialogExpiry | (() => import("../agents/peer-notices.js").CrossSessionDialogExpiry | undefined);
    /**
     * The deployment's CAPABILITY FLOOR for durable engine frames: `true` declares that EVERY process
     * that drains this deployment's session boxes reads `engine_frame` records (this engine's codec or
     * newer). With it, an engine-minted task-notification frame addressed to an idle session parks in
     * the session's durable box (`MailboxPeerMeta.kind: "engine_frame"`) and survives a process restart;
     * without it (absent / `false`) such a frame parks in process exactly as before. A deployment flips
     * it only after every drainer is upgraded — a pre-codec drainer reads the record as a peer's words.
     * Inert without a cross-process-safe {@link mailboxStore}.
     */
    sessionBoxEngineFrames?: boolean;
    /**
     * design/164 件五 — DEPLOYMENT-level usage governance: allowances that span TASKS, evaluated per
     * principal (or once for the whole deployment when a task declares none). A different axis from
     * `TaskSpec.limits`, which is the allowance ONE task asked for — an operator granting "N tokens per 5
     * hours" cannot express it as a task limit, because nothing stops the next task from asking again.
     *
     * A window carries a TOKEN ceiling (`UsageWindow.maxTokens`), a MONEY ceiling (`UsageWindow.maxCostUsd`,
     * absolute USD — the `TaskLimits.maxCostUsd` quantity one governance level up), or both: each is
     * optional on its own and at least one is required, so a deployment governed by spend alone declares
     * only `maxCostUsd` (a window with neither ceiling is refused, `config.usage_window_invalid`). The two
     * are independent and every declared one binds. A $ ceiling requires a PRICED run: a task whose model
     * has neither a {@link pricing}
     * entry nor a `Model.cost` declaration is refused at the door (`config.usage_window_unpriced`) rather
     * than charged the fabricated 0 an unpriced run would otherwise file into an operator's ceiling.
     *
     * Unset (the default) ⇒ NO governance: no ledger is read or written and no task can be refused for
     * usage. When set, every window is evaluated at two moments:
     *  - **entry** (before the first model call of a fresh task): an exhausted window REFUSES the task with
     *    `usage.window_exhausted`, since there is no checkpoint seat yet to suspend into;
     *  - **each clean turn boundary**: an exhausted window suspends the run durably
     *    (`ResourceLimitReason:"usage_window"`, with a `resumeAfterMs` hint on the gate) when the
     *    infrastructure supports it, and otherwise stops it loudly with the same code.
     *
     * Resume is CALLER-DRIVEN, as everywhere else in this engine: core mints the checkpoint and the hint,
     * and a host schedules the resume (its own scheduler, delayed by `resumeAfterMs`).
     *
     * Requires {@link usageWindowStore} — windows without a ledger are reported through `onError` and NOT
     * enforced (a ledger-less window cannot be evaluated, and failing every task closed on a config typo
     * would take the deployment down instead of telling the operator).
     */
    usageWindows?: readonly import("./usage-window-store.js").UsageWindow[];
    /** design/164 件五 — the cross-task usage ledger {@link usageWindows} is evaluated against. Core bundles
     *  `InMemoryUsageWindowStore` (process-local) and `FileUsageWindowStore` (restart-surviving); a fleet
     *  deployment plugs a database behind the same two-method seam. Ignored when `usageWindows` is unset. */
    usageWindowStore?: import("./usage-window-store.js").UsageWindowStore;
    /** design/97 CORE-9 (Part A) — the LOAD-BEARING resume journal for LLM-authored (`run_workflow` tool) workflows
     *  (the twin of `workflowRunStore`; service ships a durable Postgres/TiDB one). Wired into the auto-mounted
     *  run_workflow tool so a tool-launched workflow can durably RESUME across a replica crash. Opt-in. */
    workflowJournalStore?: import("./workflow-journal-store.js").WorkflowJournalStore;
    /** B5/F2 + design/140 §6 1a — the script-persistence / named-registry seam, threaded into the auto-mounted
     *  run_workflow tool: every invocation's resolved script is persisted (iterate via `{scriptPath}`), `{name}`
     *  resolves a saved workflow, and a registration's `defaultArgs` merge under the call-time args. Opt-in
     *  (absent ⇒ inline `script` only — the engine ships no built-in named workflows). */
    workflowScriptStore?: import("../orchestration/workflow-script-store.js").WorkflowScriptStore;
    /**
     * design/97 CORE-9 (Part B) — a TRUSTED deployment sink for the steerable handle of every agent an LLM-authored
     * (`run_workflow` tool) workflow spawns. When set, the tool's `agent()` primitive runs each agent STEERABLE and
     * emits its {@link import("../orchestration/workflow.js").WorkflowAgentHandle} here (the script never receives
     * the handle — no host-context leak into the runner); the deployment registers it by `runId`+`label` to route a
     * human/cross-replica steer to `handle.steer(...)`. Unset ⇒ agents run non-steerable (no overhead). Opt-in.
     *
     * **Interaction with {@link workflowJournalStore}**: a steerable agent runs via `agentStream`, which reads the
     * resume journal exactly as the non-steerable lane does — recorded under the same call key, replayed by the same
     * unchanged-prefix rule. A completed agent a `resumeFromRunId` replays is emitted here as an already-settled
     * handle: `result()` resolves at once to the journaled result, `steer()` is refused `steering.not_running`.
     * A result the operator steered replays as what it was — the run's record of the work, like any model answer
     * that would come out differently if asked again.
     */
    onWorkflowAgentSpawn?: (handle: import("../orchestration/workflow.js").WorkflowAgentHandle) => void;
    /** design/72 §2.2 (B): deployment default for the suspend-loop cap (see `TaskSpec.maxSuspends`).
     *  Default 5 (loose — human-approval flows re-suspend rarely). A task's own `maxSuspends` overrides. */
    maxSuspends?: number;
    /** Deployment default for the outer per-brain-call guardrail — see
     *  `TaskSpec.limits.brainCallGuardrailMs` for the semantics (default one hour; `false`/`0` = off;
     *  an invalid value is a fail-loud config error). A task's own `limits.brainCallGuardrailMs`
     *  overrides it. Set it here to cover EVERY run of this Runner, delegated children included — a
     *  sub-agent's spec carries only its own `maxTurns`/`maxWalltimeMs`, so the deployment-level knob is
     *  the one that reaches a background child. */
    brainCallGuardrailMs?: import("../brain/timeout.js").BrainCallGuardrailKnob;
    /**
     * design/91: injectable wall-clock source for **human-review timing** (and `Checkpoint.suspendedAt`).
     * Defaults to `Date.now`. The Runner reads EVERY human-time timestamp through this — the synchronous
     * `resolveAsk` wait (`t1 − t0`), the durable suspend's `suspendedAt`, and the resume's derived human
     * latency (`now() − suspendedAt`) — so a test can inject a controllable clock (`let t = 0; now = () => t`)
     * and assert `stats.humanReview` deterministically (design/87 §4.3 flaky-guard: no bare `Date.now()` in a
     * hard-gate test). **Scope = human-time only**: this is NOT a general clock for token/cost/turn accounting
     * (those keep `Date.now`); `humanReview` is a budget-EXCLUDED side observable, never folded into cost. */
    now?: () => number;
    /**
     * design/138 S1 — the NEW-ARCHITECTURE memory seam (MemoryEngine over a {@link import("./memory-engine/types.js").MemoryBackend}).
     * When set AND `spec.memory.enabled`, the task runs the INJECTION-FIRST file-based memory lifecycle
     * (materialize → session file ops → harvest with the full gate set), the CC-verbatim `# Memory`
     * instruction + fenced derived index replace the legacy `<user_memory>` block, and NO remember/recall
     * tools are mounted (memory = the model's ordinary file skills). **S4: unset ⇒
     * the task runs MEMORY-LESS** — the legacy `memoryStore` runtime path was retired (one deprecation
     * warning via `onError` phase `"config"`); this field is the ONLY live memory seam.
     */
    memoryBackend?: import("./memory-engine/types.js").MemoryBackend;
    /**
     * design/177 — the SHARED memory-store supply seam: connected team/deployment memory libraries the
     * model may browse and read through the `memory_list` / `memory_read` tool pair. Presence of this dep
     * IS the deployment's intent, so it is the single mount switch — there is no TaskSpec companion knob.
     * Per-task removal goes through the existing `TaskSpec.excludeTools` valve (a true unmount).
     *
     * ORTHOGONAL to {@link memoryBackend}, deliberately and completely. That one is the LOCAL memory chain
     * (fenced index injection, file projection, harvest, entry transactions with a write path); this one is
     * a document-only READ face reached exclusively through tool calls. Shared content never enters the
     * injected memory block or index, never lands on a model-writable directory, and is never harvested;
     * the two seams are not bridged in either direction. Write governance for a shared store stays entirely
     * with the host — core's model face has no write channel at all and only relays each store's
     * `writable` bit.
     *
     * Unset ⇒ neither tool is mounted (not even a deferred placeholder).
     */
    sharedMemoryStores?: import("./shared-memory/types.js").SharedMemoryStoreProvider;
    /**
     * design/138 §2.7 — the memory-engine directory CONFIG ROOT used when {@link memoryBackend} does not
     * pin its own directory (a {@link import("./memory-engine/file-backend.js").FileMemoryEngineBackend}
     * exposes `directoryRoot` and wins). The per-repo memory dir derives as `<root>/<repoKey>/memory`
     * (repoKey = CC-style slug of the task root). Default: `AGENT_DATA_DIR` env or `~/.ai-agent`.
     * Location is PURE CONFIG (§2.7 r2.1): a deployment may equally construct the FileBackend over an
     * IN-REPO git-tracked directory for a project scope.
     */
    memoryEngineDir?: string;
    /**
     * design/138 S2-B (O-F7/C-F3) — the Runner-owned harvest-report sink. Both harvest boundaries
     * (the checkpoint mint inside commitSuspendSaga, `phase: "checkpoint"`, and the task terminal in
     * runtask, `phase: "terminal"`) deliver their {@link import("./memory-engine/types.js").HarvestReport}
     * here instead of dropping it — trace/accounting/alerting is the deployment's call. Swallow-guarded:
     * a throwing consumer never fails the boundary. (The model-facing announcement lane is separate:
     * the engine enqueues gate events at its harvest tail; the NEXT session's first injection drains
     * them — 时机①.) Optional; unset ⇒ no behavior change beyond the report no longer being discarded.
     */
    onMemoryHarvestReport?: (report: import("./memory-engine/types.js").HarvestReport, info: {
        sessionId: string;
        phase: "checkpoint" | "terminal";
    }) => void;
    /**
     * design/113 C4 — proactive project startup. The Runner calls this once during `prepareTask` (every prepare,
     * incl. durable resume — design/113 §4 H8) and injects the returned text as a READ-ONLY, FENCED, virtual
     * `"project"` memory layer at the TAIL (so the agent "sees" what project it is in from turn 1, like CC's
     * CLAUDE.md/git injection — the cure for "say hi → only greets"). The deployment/profile owns the CONTENT
     * (read CLAUDE.md / `.claude/` + fold in `git log -5`/status + a byte budget); core owns the seam + fence +
     * cache placement and NEVER hardcodes a filename (same trust posture as {@link executionEnvFactory}/
     * {@link mods}/{@link modelKnowledgeCutoffs}). A RunnerDeps FUNCTION, not a `TaskSpec` field: it is
     * unserializable (durable resume) and reading files for an UNTRUSTED caller would be a confused-deputy hole
     * (design/44 §7 Q4). DECOUPLED from `memoryStore` — injects even with NO long-term memory configured (H5).
     *
     * `undefined` (default) ⇒ the whole path is skipped = byte-identical to today (full parity, H3). Returning
     * `null`/blank ⇒ no injection (also byte-identical). The content is fenced/sanitized like every other memory
     * layer (repo-controlled CLAUDE.md is UNTRUSTED data, not an instruction — H2). The ctx lets a profile tailor
     * or suppress: `handsEnabled:false` = a hands-less chat with no real project (return null); `isSubagent` =
     * a delegated child (a profile may trim CLAUDE.md for a cheap "grep this" subagent — H7).
     */
    loadProjectMemory?: (ctx: {
        /** The task's working directory (= the execution env's cwd; a worktree task = its worktree root). */
        cwd: string;
        /** False ⇒ a hands-less chat (no real filesystem/project) — a profile typically returns null. */
        handsEnabled: boolean;
        /** True ⇒ a delegated subagent run (a profile may trim/suppress project content to control cost). */
        isSubagent: boolean;
        /** The subagent's type name, when `isSubagent` — lets a profile tailor content per agent type. */
        agentName?: string;
        /**
         * design-A C-1 ([A2]) — the acquired session's id (uuid domain; equals the run's `sessionId`).
         * A deployment keys its SESSION-LEVEL snapshot cache on this (design-A §4.1 freeze unit: read
         * once per session, reuse across the session's prepares) — core itself caches nothing across
         * prepares. Always filled by the Runner; optional so hand-built ctx objects stay valid.
         */
        sessionId?: string;
        /** The run this prepare belongs to — the id the `task.start` trace frame, `noteTaskRun` and `TaskResult.runId` carry. Always filled by the Runner. */
        runId?: string;
        /**
         * design-A C-1/C-2 ([A2]) — where this prepare sits in the snapshot lifecycle; the deployment's
         * cache-invalidation signal (core only derives and forwards it, and ignores the answer):
         * - `"fresh"`       — no prior context: a brand-new session (no `spec.sessionId`, or an empty one).
         * - `"resume"`      — an existing session's context continues (a durable-resume leg, a same-session
         *                     follow-up run, or a `resumeAt` rewind): a cached snapshot is still the one
         *                     the model has been seeing.
         * - `"post-compact"`— a compaction landed since the model's last full turn on this branch (branch
         *                     tail shows a `compaction` entry with no *user* message after it): the context
         *                     was rebuilt — the design-A §4.1 re-read boundary; a deployment should drop
         *                     its snapshot cache and read fresh. HONEST APPROXIMATION (recorded, see
         *                     docs/REVIEW-BACKLOG.md): a compaction followed by a mid-run user-role
         *                     injection (steer) before the next prepare reads as `"resume"` — the freshness
         *                     gap is covered by the C-4 `instructions_change` lane, not by this signal.
         * - `"post-clear"`  — RESERVED: core has no session-clear primitive today (a CC `/clear` maps to a
         *                     NEW session ⇒ `"fresh"`; rewinding past the root is rejected), so core never
         *                     emits this value — it exists so the seam's enum is stable when a clear verb lands.
         */
        phase?: "fresh" | "resume" | "post-compact" | "post-clear";
    }) => Promise<string | null | ProjectMemoryLoad> | string | null | ProjectMemoryLoad;
    /**
     * design-A C-4 ([A2]) — the mid-session instruction-file change PROBE (the server half of the
     * `instructions_change` attachment lane). At eligible turn boundaries (continuation-gated like
     * `date_change`) the run loop calls this with the snapshot's declared
     * {@link ProjectMemoryLoad.instructionSources} and compares the answer against the lane's
     * per-path last-announced hash; a moved hash appends a fixed ≤512-byte tail notice (the model is
     * told the file changed and can Read the truth — the snapshot itself stays frozen, §4.1).
     * Contract:
     * - return `null` ⇒ this boundary is SKIPPED silently (nothing announced, nothing committed; the
     *   next boundary retries) — the fail-soft path for a remote cwd / EACCES / transient IO.
     * - a per-entry `contentHash: null` ⇒ the file is GONE from disk (announced once as removed).
     * - paths not in `sources` are ignored; the probe should never throw (core guards anyway and
     *   treats a throw exactly like `null`).
     * - core never reads files here and never interprets the hash (opaque equality only — the same
     *   posture as `instructionSources`); the lstat/read/hash economics live in the deployment.
     * Unset ⇒ the lane does not exist: zero calls, zero state, byte-identical output.
     */
    probeInstructionSources?: (sources: ReadonlyArray<{
        path: string;
        contentHash: string | null;
    }>) => Promise<ReadonlyArray<{
        path: string;
        contentHash: string | null;
    }> | null>;
    /**
     * design/99 §E23 — the LIVE-ONLY inbound-elicitation seam (an MCP server asking the END USER for input
     * mid-tool-call). Wired ONLY for servers that opted in via {@link McpServerSpec.elicitation}; absent ⇒ core
     * advertises no elicitation capability (fail-closed). The deployment answers synchronously (e.g. prompts the
     * terminal user); core replies to the server on the open connection.
     */
    onElicit?: OnElicit;
    /**
     * design/116 CONFIRM-1 (加 seam 保体验): resize an over-limit MCP image so the model still SEES a
     * degraded image instead of a spill-to-disk note. Absent ⇒ core auto-detects `sharp` (dynamic import;
     * turnkey deployments with sharp installed get the CC experience with zero config) and otherwise spills.
     */
    mcpImageResizer?: import("./mcp.js").McpImageResizer;
    /** Default tool-call gate for all tasks (a task's own `toolPolicy` overrides this). */
    toolPolicy?: import("./tool-policy.js").ToolPolicy;
    /**
     * design/277 — the deployment seat of the tool-registration MODEL GATE (default ON; the gate
     * itself only ever touches entries tagged via {@link ToolSpec.modelGate}, so an untagged roster
     * is byte-identical under any value here):
     *  · `false` — deployment kill switch: the gate never trims anything.
     *  · `{ classes }` — per-class rule rows merged into the built-in table
     *    ({@link import("./tool-model-gate.js").TOOL_MODEL_GATE_CLASSES}) PER AXIS: a present
     *    `floors`/`modelIds` axis REPLACES that axis, an absent axis INHERITS the built-in one — so
     *    a row adding only `modelIds` (the BYOM channel for gating a deployment's own strong model)
     *    keeps the built-in claude floors armed. `floors: []` / `modelIds: []` are the EXPLICIT
     *    per-axis clears; a class whose merged rule has both axes empty gates nothing (legal
     *    per-class off). New class names extend the open vocabulary for deployment-authored tags.
     * The legality set is CLOSED (#123): any other shape — array/null/true seat, unknown keys at
     * either level, malformed floors/modelIds rows, families that can never match the canonical id
     * grammar, duplicate families/ids, `/`-prefixed ids — refuses the prepare loudly
     * (`config.tool_model_gate_invalid`), never folds to a guess. Process-level counterpart: env
     * `SEMA_TOOL_MODEL_GATE=off`. Removals are announced per (model, class) through `onNotice`
     * (`config.tool_model_gate_removed`) — a silent default-face trim is forbidden (#237).
     */
    toolModelGate?: false | {
        classes?: Record<string, import("./tool-model-gate.js").ToolModelGateRule>;
    };
    /** Deployment default for {@link TaskSpec.basePolicyForResumeEdit} (#93 / F-012 L3): the resume-edit
     *  re-adjudication override. Resolution: `spec.basePolicyForResumeEdit ?? THIS ?? (spec.toolPolicy ??
     *  deps.toolPolicy)` — absence falls back to the caller policy, never to a silent skip. */
    basePolicyForResumeEdit?: import("./tool-policy.js").ToolPolicy;
    /**
     * How `ask` decisions resolve when a policy/hook requests human confirmation (design/37). Default
     * (omitted) = `"deny"`: **headless auto-deny** — no approver, so `ask` resolves deterministically to
     * deny with a model-readable reason (the safe default for stateless automation). `"allow"` to
     * auto-approve, or a fail-closed approver function. A task's own `onAsk` overrides this.
     */
    onAsk?: import("./tool-policy.js").OnAsk;
    /** Content-ask seam (design/64 §5): when set, every task mounts an AskUserQuestion tool that routes a
     *  model-issued question to this callback (a real human/UI). Distinct from `onAsk` (permission). A task's
     *  own `onQuestion` overrides this. Unset ⇒ the tool mounts only if another delivery face exists
     *  (durable park consumer / explicit `TaskSpec.interactiveTools` — see that knob's criterion table). */
    onQuestion?: import("./ask-question.js").OnQuestion;
    /**
     * design/173 §3 — the deployment-level default interaction posture (see
     * {@link TaskSpec.interactionPosture} for the full contract; the spec seat overrides per run, so
     * one Runner can serve an interactive session AND cron/batch legs without the deps-level
     * declaration refusing the batch legs). Absent = no posture check anywhere.
     */
    interactionPosture?: "interactive" | "headless";
    /** LSP code-intelligence seam (design/64 §13.1): when set, every task WITH HANDS mounts an `lsp` tool routed to this
     *  manager (a deployment-provided language-server bridge, e.g. an E2B LSP sidecar). The manager returns
     *  `undefined` for a file whose language has no server, and the tool degrades to "fall back to grep". A
     *  task's own `lspManager` overrides this. Neither seat set, or a leg without an execution env ⇒ the engine mounts no `lsp` tool. */
    lspManager?: import("./lsp.js").LspServerManager;
    /**
     * The Artifacts hosting seat — a storage/service-tier seam of the same family as
     * {@link checkpointStore} and {@link rosterStore}. Three states, judged once at prepare: ABSENT ⇒ no
     * `Artifact` tool and no announcement (the default: a library deployment has no hosting); a host
     * whose `capabilities()` satisfies the declaration invariants ⇒ ONE `Artifact` tool assembled from
     * that declaration (`plane: "hosted"` mounts the hosted face over the declared action subset,
     * `plane: "local-stub"` — `localArtifactStub({ dir })` — mounts the publish-only local face with
     * `eval-stub://` links); a host whose declaration fails an invariant ⇒ no tool and a loud
     * `config.artifact_host_invalid` notice. Filling the seat IS the authorization to publish: there is
     * no second switch. `TaskSpec.excludeTools` unmounts the tool as it does any other.
     */
    artifactHost?: import("./artifact-host.js").ArtifactHost;
    /**
     * The deployment seat of the mods bus: extension registrations every task of this Runner carries (every leg — root,
     * delegated child, resume — admits them at its own prepare). Each declares its tier (`prepend`, `user` or `append`;
     * absent or another word refuses the prepare, `config.mods_invalid`). A task's own `TaskSpec.mods` run beside these,
     * never instead of them. A `classic.PreToolUse` handler's `allow` never bypasses `toolPolicy` — the policy is always
     * the final say.
     */
    mods?: readonly import("./mods/kernel/index.js").ModRegistration<import("./mods/dollar.js").EngineDollar>[];
    /**
     * Policy for remote image URLs (`TaskSpec.images[].url`). Return false to reject. When set it
     * **replaces** the built-in SSRF guard (https-only + private/reserved-host block) — e.g. supply a
     * CDN allowlist, or a stricter resolve-and-pin check. Omit to use the safe default.
     */
    allowImageUrl?: (url: string) => boolean;
    /**
     * Optional sink for best-effort background errors/warnings that are otherwise swallowed:
     * - `"compaction"` — a compaction failure (F8); a persistently failing one (flaky gateway, empty
     *   summaries) is invisible without this and lets a session grow until it tops the window.
     * - `"prompt-cache"` — a prefix-cache hazard on the wire: a detected cache break (design/31), or a
     *   mis-declared/mismatched `promptCacheFamily`. The prefix cache will miss. Log/alert on both.
     * - `"prompt-constitution"` — a prompt overlay did not take effect as written: a later source (or a later declaration in the same source) superseded
     *   it on the same target, its target was not in this run's prompt, or a mods handler's `prompt.compose` answer was skipped (it rewrote another's
     *   overlay, or wrote what the band table does not grant its seat); each overlay is named once per run. The center prompt too: a pinned session's
     *   stored artifact refused or unreadable (it fails `prompt.snapshot_unavailable`), a fresh session's candidate that does not verify, and a
     *   `CenterPromptSource` boot restore with no usable copy (`sessionId: ""`, `classification: "center-source-restore"`) — that last line only when the
     *   host passes this same sink to `CenterPromptSource` as its own `onError` (the source restores at boot, apart from any Runner; given none, it is silent).
     * - `"degraded"` — the task kept running with a capability quietly reduced: a question auto-answered with no human
     *   present, a skipped env git snapshot, a lost checkpoint-put confirmation, an unroutable descendant notification, and
     *   similar best-effort arms. The run proceeds; the reduced arm is what is being disclosed (`classification` names it).
     * - `"config"` — a deployment-wiring problem detected while preparing or tearing down a task: tool mount conflicts,
     *   refused/ignored knob values, gate-off notices, store/env teardown legs. The broadest class by call-site count — most
     *   misconfigurations announce here rather than failing the task.
     * - `"memory"` — a post-task memory-consolidation pass failed or skipped a malformed reconcile
     *   decision (design/41). Best-effort: the notes the model saved are kept; the task still succeeds.
     * - `"mcp"` — an MCP server failed to connect / list its tools and was skipped (fail-open, design/29);
     *   the task runs with the remaining servers' tools. One call per failed server.
     * - `"a2a"` — the A2A sibling of `"mcp"`: a declared peer's agent card could not be fetched/read, or it
     *   advertises no transport this client speaks, so the peer was skipped. One call per skipped peer.
     * - `"interrupt-reconcile"` — repairing an interrupted session's transcript on re-entry (synthesizing
     *   tool results for orphaned calls, flushing held session writes) failed; the run proceeds on the
     *   unrepaired transcript.
     * - `"suggestions"` — the best-effort follow-up-suggestions pass failed or timed out; the result
     *   simply carries no suggestions.
     * - `"rewind"` — the file-history machinery backing rewind reported a best-effort failure: a
     *   turn-start boundary capture failed or timed out (that turn cannot be rewound to; the turn
     *   itself proceeds), or a first-touch failure was tolerated under
     *   `onTrackFailure:"proceed-unprotected"`. (design/381: the whole-tree size gate and its
     *   `classification:"too_large"` payload are retired — per-edited-file history has no size axis.)
     * - `"hook"` — a deployment hook misbehaved: a callback threw, or returned a verdict it was not
     *   allowed to (e.g. a declared-observational hook). The engine applies the hook's own documented
     *   fallback (swallow, or fail-closed deny, per its contract) and reports the fact here.
     */
    onError?: (err: unknown, context: {
        phase: "compaction" | "prompt-cache" | "prompt-constitution" | "degraded" | "config" | "memory" | "mcp" | "a2a" | "interrupt-reconcile" | "suggestions" | "rewind" | "hook";
        sessionId: string;
        /**
         * Machine-readable sub-classification when the site has one (a kebab-case word naming the arm) —
         * e.g. the design/31 cache-break detector's root cause (`"model-switch" | "tool-schema" |
         * "tool-set" | "system-prefix" | "server-or-ttl"`), or the file-history liveness arms
         * (`"file-history-no-store"` / `"file-history-no-env"`). Lets the host pick its own log level:
         * `"server-or-ttl"` is usually benign in agentic tasks (slow tools ⇒ 5min+ request gaps expire
         * provider caches — service[64] flag②) and warrants info/warn, while the prefix-bug causes
         * warrant attention.
         */
        classification?: string;
    }) => void;
    /**
     * Structured sink for operator-facing NOTICES ({@link EngineNotice}) — announcements that are not
     * errors and do not affect the run, which the engine otherwise prints via `console.warn` (e.g. a
     * configured timeout/env value discarded in favor of another). When wired, a notice goes HERE
     * INSTEAD of `console.warn` (structured replaces the console line — a host forwarding notices to
     * its own surface must not show every fact twice); when absent, the historic `console.warn` line
     * is printed verbatim, so an unwired build keeps its exact loudness. Swallow-guarded at every
     * emission point (`onError`/`tracer` posture): neither a throwing sink nor an async sink's
     * rejected promise ever affects the run. A PRESENT NON-function value (an untyped host wiring
     * null/junk) is a bad deployment value, not a wired sink: every notice then falls back to the
     * `console.warn` line and the seat defect itself is announced once per process (#170 — a bad seat
     * must not silence both channels). Per-process announcement de-duplication is unchanged and
     * sits BEFORE the sink branch — a deduplicated repeat reaches neither channel.
     */
    onNotice?: (notice: EngineNotice) => void;
    /** Best-effort fire-and-forget trace sink (task/turn/brain/tool); `TaskSpec.tracer` overrides per task. */
    tracer?: import("./trace.js").TracerHook;
    /**
     * design/73 §1 v1 — fire-and-forget sink for {@link import("./task-outcome.js").TaskOutcome} facts
     * (the feedback ledger's core seam; tracer/onError posture — swallow-guarded, a throwing sink never
     * affects the run). 🔴 Red lines: core emits ONLY the `mechanical` tier (objective-oracle results —
     * llm_assisted/self-report never enter v1); every outcome carries `oracleHadRedRun`; ONE emission
     * point — `runGoal`'s terminal state (goal `doneCheck` = the natural mechanical oracle) plus explicit
     * `emitTaskOutcome` from harnesses that own a real oracle. Plain `runTask` never auto-emits (no
     * objective oracle → no fact). core emits facts only; aggregation/policy live in the service ledger.
     */
    onTaskOutcome?: (outcome: import("./task-outcome.js").TaskOutcome) => void;
    /** Per-model pricing (per-1M absolute USD) keyed by model id; falls back to each `Model.cost`. */
    pricing?: Record<string, import("./pricing.js").ModelPricing>;
    /**
     * The execution environment the agent's "hand" acts on (design/44) — file system + shell. Default:
     * `StubExecutionEnv` (no real I/O, current behavior). Inject `NodeExecutionEnv` for local fs/shell, or
     * a sandboxed/remote impl for multi-tenant isolation. **Injecting a non-stub env is the opt-in** that
     * activates the file tool band. ⚠️ A bare `NodeExecutionEnv` is unsandboxed — a multi-tenant deployment
     * MUST inject a chroot/container/per-principal-scoped impl; do not expose raw `NodeExecutionEnv`.
     */
    executionEnv?: import("../internal/harness.js").ExecutionEnv;
    /**
     * design/48 remote seam — a **trusted control-plane** factory that mints a FRESH execution env per
     * task (e.g. one isolated container per task in the remote model). Called once at task start; the env
     * it returns is owned by that task and released on task end. **Wins over** the shared static
     * `executionEnv`. Injecting it also activates the hand tool band (design/44), exactly like
     * `executionEnv`. This is the ONLY per-task env injection point — `TaskSpec` deliberately has no
     * `executionEnv`, so an untrusted caller can't swap the sandbox (design/44 §7 Q4); the trusted
     * deployment wires the factory.
     */
    executionEnvFactory?: import("./remote-env.js").ExecutionEnvFactory;
    /**
     * Containment root for the file tools (design/44 §4 invariant 7): every path must resolve within it,
     * else the tool rejects (`PathNotInRoot`). Default `process.cwd()`. A defense-in-depth guard rail at the
     * tool layer — NOT a sandbox (it does not constrain `bash`; that needs OS-level containment) and not a
     * substitute for an isolated `executionEnv`.
     */
    rootPath?: string;
    /**
     * Backing store for offloaded large tool results (design/30). Default: a task-scoped
     * `InMemoryToolResultStore` (no cross-wake durability). Inject a durable store (keyed by ref) so an
     * offloaded result survives wake/resume.
     */
    toolResultStore?: import("./tool-result-store.js").ToolResultStore;
    /**
     * Offload a tool result to the store + leave a preview when its text exceeds this many chars. Default
     * ~20000; set `0` or `Infinity` to disable offloading. Per-tool override via `ToolSpec.offloadThresholdChars`.
     */
    toolResultThresholdChars?: number;
    /**
     * design/374 — the microCompact knobs. Both default ON (`machine: "cc"`, `clearOnRejection: true`).
     *
     *  - `machine`: `"cc"` (default: the CC 2.1.223 rejection-leg form — keep 5, ≥20k minimum-savings
     *    gate, one deep clear beyond the keep window, CC marker bytes) or `"off"` (no proactive
     *    frontier clearing at all — the D-2 off switch; the machine then gets its ONE shot at the
     *    blocking point instead, the slice-3 arm A, trace trigger `"blocking"`). The vocabulary is
     *    {@link import("./context-edit.js").MICRO_COMPACT_MACHINES}; the guard chain behind either
     *    value is the same (blocking-point run → in-turn forced compaction behind the adopt seam →
     *    trim demoted to the disaster-only last resort), so an under-20k refusal never falls straight
     *    into a message-dropping trim.
     *  - `clearOnRejection` (MC-R, slice 2): on a provider input-too-long rejection, run ONE cheap
     *    deterministic clear over the rejected projection (the same machine, savings ≥20k or nothing)
     *    and retry inside the turn BEFORE the forced-compaction recovery. Default true. Independent of
     *    `machine` — an enabled MC-R clears even with the frontier pass `"off"`. BUDGET ACCOUNTING (design/374
     *    §3.2.1, stated here because it is otherwise invisible to a deployment): a successful MC-R
     *    clear-and-retry SPENDS one attempt of the shared prompt-too-long recovery budget (default 2
     *    attempts per chain), so a chain that clears and is rejected AGAIN has one forced-compaction
     *    attempt left where the knob-off chain nominally had two — the trade costs no effective
     *    compaction pass, because the second forced-compaction call of the off chain is structurally
     *    a no-op whenever the first one landed (the branch leaf is already a compaction entry).
     *
     *  A declaration outside the closed vocabulary (a `machine` string not in the union, a
     *  non-boolean `clearOnRejection` — JSON/env-derived config the type cannot guard) refuses the
     *  whole prepare loudly (`code: "config.microcompact_invalid"`, no silent re-default): folding it
     *  would silently run the DEFAULT machine while the deployment believes its declaration took
     *  effect.
     */
    microCompact?: {
        machine?: import("./context-edit.js").MicroCompactMachine;
        clearOnRejection?: boolean;
    };
    /**
     * Two-phase prefix-cache-break detection (design/31): per turn, fingerprint the prefix and, on a
     * confirmed `cacheRead` drop, emit a root-cause finding via `onError(phase:"prompt-cache")`. Cheap
     * (hashes only) and only fires on a detected break. Default **on**; set `false` to disable.
     */
    cacheBreakDetection?: boolean;
    /**
     * Aggregate inline-MEDIA (image) byte budget for the request (batch-C/§17.2 sibling of the text budget;
     * CC 2.1.196 `tengu_media_byte_cap` parity). Image reads (`Read` → ImageContent) are per-image-capped but
     * accumulate across turns; this bounds the SUM in the request by stripping the OLDEST images (replaced with a
     * marker) — request-only, non-destructive (the durable session keeps the image; the model can re-Read it).
     * The same budget bounds the media of one `tool_end.output` (spent there as base64 on the wire, x 4/3); both readers
     * read it through `mediaBudgetBytes`. Default {@link AGGREGATE_MEDIA_BUDGET_BYTES}; set `Infinity`/`0` to disable. A
     * non-number, `NaN` or a negative is refused at `new Runner` (`config.invalid_argument`), never read as "no budget".
     */
    mediaByteCapBytes?: number;
    /** Optional telemetry fired when {@link mediaByteCapBytes} strips media in a request (CC's
     *  `tengu_media_byte_cap_stripped` fields). Pure observability — no effect on the request. */
    onMediaStripped?: (info: {
        totalBytes: number;
        limitBytes: number;
        removedCount: number;
        removedBytes: number;
    }) => void;
    /**
     * Deferred-tool disclosure policy (design/36). Two rules govern deferral. ① The caller's list is
     * literal: {@link TaskSpec.deferTools} names defer on every request, `ToolSpec.alwaysLoad` /
     * {@link TaskSpec.alwaysLoadTools} pins inline on every request (judged first) — neither reads this
     * knob. ② The engine's own candidates — `ToolSpec.defer === true`, every REMOTE-PROTOCOL tool name
     * (MCP **and** A2A peer tools), and built-in tools that declare their own `defer` — sit behind ONE
     * threshold gate, all or nothing: deferred iff their inlined size (name + description + schema; the
     * candidate set only — the caller's list, pins and non-candidates are not measured) reaches the
     * {@link toolSearch} threshold (10% of the model's context window when that seat is absent); below that
     * they ship inline in full and no ToolSearch is mounted.
     *
     * `"auto"` is a CANDIDATE-WIDENING knob, not a second gate: it adds every caller spec — undeclared
     * caller tools included — to the candidate set, measured and decided by the same threshold. Engine
     * built-ins the deployment did not name are candidates under no setting. An explicit opt-in (council
     * minor #6) because it makes `tools[]` depend on the caller roster's total weight, which a deployment
     * with many thin tools may not want. Any other value refuses at prepare (`config.defer_mode_invalid`).
     * `classifyDeferred` / `deferralGate` in `src/core/runner/tool-disclosure.ts` are the single authority
     * if this text drifts.
     */
    deferMode?: "auto";
    /**
       * Rule ②'s GATE POLICY — when the engine's candidate set ships as placeholders — in three states:
       * `true` (always), `false` (never; with no caller list no ToolSearch mounts at all), `{mode:"auto",
       * thresholdPercent}` (the threshold gate at that percent of the model's context window). Absent ⇒
       * `{mode:"auto", thresholdPercent:10}`. ORTHOGONAL to {@link deferMode}, which decides WHICH tools are
       * candidates; neither touches rule ① ({@link TaskSpec.deferTools} still defers,
       * {@link TaskSpec.alwaysLoadTools} still pins first). A value outside the domain refuses at prepare
       * (`config.tool_search_invalid`), a `thresholdPercent` outside a finite `[0, 100]` included.
     */
    toolSearch?: import("./tool-search-setting.js").ToolSearchSetting;
    /**
     * design/84 Seam C — compact-boundary COST optimization (data-gated, default OFF). A FUNCTION seam (a
     * trusted control-plane injection), so it lives on `RunnerDeps` — NOT `TaskSpec`: `TaskSpec` is the
     * serializable, untrusted-caller, durable-resume-round-trippable per-task config (a function can be
     * neither serialized nor durably checkpointed, and the sandbox/env-class injections deliberately sit on
     * `RunnerDeps` for the same trust reason). Same placement as `mods` / `executionEnvFactory`.
     *
     * When `summaryProvider` is set, BOTH compaction call sites (the within-task turn boundary AND `finish()`
     * at task end) consult it: a NON-EMPTY string is reused verbatim (the real `generateSummary` LLM call is
     * skipped); `null`/`undefined`/blank falls back to the existing LLM path (byte-identical to today).
     * `onCompaction` is a pure post-compaction observer. `maxConsecutiveProviderReuse` (default 3, CC parity)
     * forces a real summary after N consecutive reuses to bound drift; the Runner OWNS the consecutive-reuse
     * counter across both call sites for the task (reset on a real summary, incremented on a reused one), so
     * the caller does not have to thread it back. **Absent (default) ⇒ no behavior change whatsoever.**
     *
     * SCOPE (design/141 examples 批实测): the seam covers the whole-turn compaction sites only. A
     * SPLIT-TURN cut point (mid-turn prefix summarization — the common shape inside a long single-user-
     * message tool loop) bypasses the provider by design (A2 algorithm sovereignty): the turn-prefix
     * summary is engine-owned. Deployments needing a clean provider-visible boundary should shape work
     * as separate tasks sharing `sessionId`.
     */
    summaryProvider?: import("./auto-compaction.js").MaybeCompactOptions["summaryProvider"];
    /** design/84 Seam C — pure post-compaction observer (fires only on a real compaction, never a no-op).
     *  Consumed ONLY when `summaryProvider` is also set (the Seam C options early-return otherwise) — a
     *  pure-observer deployment passes a provider that always returns `null` (keeps the LLM path intact). */
    onCompaction?: import("./auto-compaction.js").MaybeCompactOptions["onCompaction"];
    /** design/84 Seam C — force a real `generateSummary` after this many consecutive provider reuses (default
     *  3, CC parity). The Runner tracks the consecutive-reuse counter per task across both compaction sites. */
    maxConsecutiveProviderReuse?: number;
}