UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

2,918 lines • 204 kB
/**
 * The prepare CONTRACTS — the types that cross between `prepareTask` and the phase modules it drives.
 *
 * WHY THEY LIVE HERE. Every one of these was declared inside `prepare-task.ts`, the file that PRODUCES
 * them, so any module needing to name one had to import its own driver. Thirteen such edges had
 * accumulated by the time the dependency-direction gate was written: six phases reaching up for a
 * contract type, the vocabulary file reaching up for two more through inline `import()` type
 * positions, a phase reaching sideways for a pure path helper, and the orchestrator and its own
 * consumer naming each other. A contract belongs BELOW both the producer and the consumer; that is
 * all this file is.
 *
 * It declares TYPES ONLY — nothing is emitted, so it sits on the vocabulary floor beside `types.ts`
 * without adding a runtime edge anywhere. The blocks below are verbatim moves, in their original
 * order, JSDoc included, so the move stays auditable as a move.
 */
import type { HumanReviewGateRow } from "../task-result.js";
import type { RepairBundle } from "../../agents/repair-loop.js";
import type { SubagentRetainLedger } from "../../agents/retain-ledger.js";
import type { BrainCallGuardrailRef } from "../../brain/timeout.js";
import type { ActiveWorktreeSession, AgentHarness, AgentMessage, AgentTool, AnnouncedListingSnapshotInput, ExecutionEnv, ThinkingLevel } from "../../internal/harness.js";
import type { Model } from "../../internal/llm.js";
import type { WorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
import type { ToolManifestRow } from "../../prompt-assembly/tool-catalog.js";
import type { CwdRef, ContainmentFace } from "../../tools/fs/index.js";
/** A leg's WRITE face: the face in force (read per call, following the mode's face) and whether a seat or a clamp DECLARED the fence (a checkpoint row records it; a resume keeps it). */
export interface WriteFaceSeat {
    readonly inForce: () => ContainmentFace;
    readonly fenced: boolean;
}
import type { MaterializedA2a } from "../a2a.js";
import type { CompactionForkContext, CompactionPhaseDurations, MaybeCompactOptions, RapidRefillState } from "../auto-compaction.js";
import type { AutoModeArmingRecipe } from "../auto-mode-arming.js";
import type { AutoModeDecider, AutoModeDenialTracker, DenialLimitFallbackFace } from "../auto-mode.js";
import type { SettlementKind } from "../gate-outcome.js";
import type { CacheBreakDetector, ToolFingerprintInput } from "../cache-break-detector.js";
import type { ActorAssertion, Checkpoint, CheckpointState, CheckpointStore, CheckpointToken, PendingSteerEntry, PlatformLimitReason, ReopenReason, ResourceLedger, ResourceLimitReason, ResumeOutcome, SerializedCheckpointState } from "../checkpoint-store.js";
import type { ExecutionOutcomeRecordWord } from "../checkpoint-execution-record.js";
import type { ClearedProjectionLedger, MicroCompactMachine, OccurrenceIndex } from "../context-edit.js";
import type { HookInvocationIdentity, Hooks, OrgGateVerdict, PermissionDeniedPayload, PostToolBatchCall, PostToolBatchResult, ToolGateInput } from "../hooks.js";
import type { MaterializedMcp } from "../mcp.js";
import type { MemoryEngine } from "../memory-engine/engine.js";
import type { ResolvedSkillListingBudget } from "../skill-listing-budget.js";
import type { HarvestReport, MemorySessionHandle } from "../memory-engine/types.js";
import type { SessionPermissionRules } from "../session-policy-store.js";
import type { SafeNotifier } from "../safe-notify.js";
import type { RecoveredOrphan } from "../session-reconcile.js";
import type { SessionStore, StoredSession } from "../session.js";
import type { TaskOutcome } from "../task-outcome.js";
import type { ModelPricing } from "../pricing.js";
import type { PushQueue } from "../push-queue.js";
import type { SystemInjectionPriority, TaskNotificationPayload } from "../task-notification.js";
import type { OnAsk, PermissionResult, ResolvedAsk, ToolCallRequest, ToolPolicy } from "../tool-policy.js";
import type { RemoteExecutionEnv, SnapshotId, WorkspaceHandle } from "../remote-env.js";
import type { ToolDisclosureManifest } from "../trace.js";
import type { AgentDefinition, Brain, CompactOutcome, ModelRef, ModelRoles, NestedUsageAccum, PermissionMode, RunnerDeps, ShellGateDoctrine, TaskEvent, TaskEventIdentity, TaskResult, TaskSpec, TaskStream, ToolActivity, ToolEffect } from "../types.js";
import type { PermissionModeController } from "../mode-policy.js";
import type { WiringManifest } from "../wiring-manifest.js";
import type { SkillGateSeats, SkillGrantLane, SkillGrantRecord, SkillGrantSource } from "../skill-grant.js";
import type { BudgetAxis } from "./assemble-result.js";
import type { GitStatusLaneRef } from "./git-status-frame.js";
import type { SessionReadFileStates } from "./session-read-file-states.js";
import type { LegModeCeiling, SessionModeSeats } from "./session-mode-seats.js";
import type { BlockedRef, OutputRef, SkillListingEntry } from "./synthetic-tools.js";
import type { createAttachmentState, WriteFamily } from "./turn-attachments.js";
import type { cacheFamilyOf } from "./usage-accounting.js";
/**
 * The frozen task-start snapshot of the caller's tool-face control arrays: produced by the
 * config-doors phase, read by the hands/read-face phase. Named here rather than reached for as
 * `PrepareConfigDoorsResult["toolFaceSnapshot"]` — an indexed access into a sibling phase's Result
 * is a sideways dependency wearing a type's clothes.
 */
export interface ToolFaceSnapshot {
    exclude: readonly string[] | undefined;
    defer: readonly string[] | undefined;
    alwaysLoad: readonly string[] | undefined;
    /** The model-gate restore selector ({@link TaskSpec.restoreGatedTools}), fourth
     *  seat of the same frozen task-start snapshot: the gate decision and the delegation carrier
     *  read THIS, never the live spec. */
    restoreGated: readonly string[] | true | undefined;
}
/**
 * The once-per-session announcement ledger's OPERATOR SINK as the phases see it: the
 * `RunnerDeps.onError` signature, deduplicated per (code, text) across the session. The ledger itself
 * (load, digests, settle) lives in announce-once-ledger.ts; a phase that announces once invokes only
 * this member, so the member's shape is declared here — below every phase — rather than reached for
 * sideways as the ledger module's type.
 */
export interface AnnounceOnceSink {
    onError: NonNullable<RunnerDeps["onError"]>;
}
/** Static per-tool irreversibility tier, resolved at prepare time by the safety scan. */
export type IrreversibilityTier = Map<string, "never" | "maybe" | "always">;
/** Declared reversibility probes by tool name, collected by the safety scan on the same pass. */
export type ReversibilityProbes = Map<string, NonNullable<TaskSpec["tools"]>[number]["reversibilityProbe"]>;
/**
 * The prepare-FAILURE cleanup stack. A phase that acquires a resource (an execution env, an MCP
 * client, an A2A client) pushes its release the moment the handle exists, so a throw further down
 * cannot leak it — before this the driver could only see a half-acquired handle by hoisting a `let`
 * to the top of the function and hoping every acquisition site remembered to assign it.
 *
 * IT IS ARMED ONLY ON THE FAILURE PATH. The driver calls {@link RollbackStack.commit} before it
 * builds `Prepared`: past that point the stack is empty and every resource's cleanup belongs to the
 * Runner's finish/teardown, which is where a SUCCESSFUL run's resources have always been released.
 * The catch path calls {@link RollbackStack.unwindAll}, which runs the cleanups in reverse order,
 * each exactly once, none of them able to stop another — a bounded settle, the same posture the
 * teardown leg already uses. NEVER call unwind on the success path.
 *
 * The detach guard for an owned execution env and the worktree-isolation check stay with the DRIVER: a phase pushes, it does not destroy, because the driver's reference is
 * the authoritative one and a phase-local rebind could never reach it.
 */
export interface RollbackStack {
    /** Register one cleanup for the failure path. Called immediately after the handle is acquired. */
    push(cleanup: () => void | Promise<void>): void;
    /** Disarm: prepare succeeded, so ownership passes to `Prepared` and the stack is emptied. */
    commit(): void;
    /** Failure path only — run every registered cleanup in reverse order, each once, none blocking another. */
    unwindAll(): Promise<void>;
}
/**
 * The run's view of deployment usage governance, built once at prepare when
 * `RunnerDeps.usageWindows` + `usageWindowStore` are both wired (absent otherwise, so every consumer is
 * an explicit `!== undefined` test and an ungoverned deployment executes not one extra line).
 *
 * Both methods are called SEQUENTIALLY by the run loop (entry check, then one commit per turn boundary,
 * then one final commit) — the delta bookkeeping in `commit` assumes that and is not a concurrency
 * primitive.
 */
export interface UsageGovernance {
    /** The ledger key this run is governed under — the principal, or the shared global key. */
    readonly key: string;
    /** Does any governed window carry a MONEY ceiling? Read by the run loop's pricing seats: only a
     *  cost-governing deployment has to treat an unevaluable price table as unpriced spend. */
    readonly governsCost: boolean;
    /** Ms the caller must wait before ANY window would admit work again, or `undefined` when none is
     *  exhausted as of `now`. Reads the ledger; propagates a store failure (an unreadable ceiling must not
     *  read as an open one). */
    check(now: number): Promise<number | undefined>;
    /**
     * Charge whatever of the run's cumulative spend has not been charged yet. Takes the run's CUMULATIVE
     * totals rather than deltas so no caller can double-charge by calling twice, and so a caller that skips
     * a boundary loses nothing.
     *
     * `cumulativeCostMicroUsd` is the MONEY half (integer micro-USD, `stats.costMicroUsd`). Pass `undefined`
     * when the run's spend has no cost figure at all — a deployment governing a
     * `maxCostUsd` window then REFUSES here rather than charging the fabricated 0 that would let the ceiling
     * silently stop applying. A token-only deployment ignores the argument entirely.
     */
    commit(cumulativeTokens: number, cumulativeCostMicroUsd: number | undefined, now: number): Promise<void>;
}
/**
 * The run's turn-start file-history BOUNDARY seat. `begin(entryId)` starts the capture
 * for the turn's own initial entry (first-wins: exactly one boundary per turn incarnation, DV-4);
 * `settle()` awaits the in-flight capture's settle and is the caller's LEASE CLOSE (awaited before
 * the first tool executes) as well as the finish-tail durability await. The capture is bounded by a
 * 30s timeout whose abort FENCES the attempt (the store's `boundaryPublishVerdict` guarantees a
 * timed-out capture never publishes); failure is disclosed via onError(phase:"rewind") and NEVER
 * blocks the turn — a turn whose capture failed simply has no boundary (the reference behaves the
 * same), and there is no mid-turn re-capture (the store's spent lease refuses it with zero reads).
 */
export interface FileHistoryBoundarySeat {
    begin(entryId: string): void;
    settle(): Promise<void>;
}
/**
 * The commit-side facts of the run's ONE durable pause: the terminal cause the result assembles
 * ({@link import("../types.js").PausedCause} — gate, token, checkpoint id, restore mode), the COMMITTED scope
 * (read verbatim by the reap/cancel path — never re-derived from the spec, which the cross-scope re-scope
 * may no longer match), and the id of the tool call the park is holding (only a `tool_approval` pending
 * action parks ON a call; absent, never guessed, for the kinds that hold none).
 */
/** See {@link Prepared.undeliveredContinuationRef} — `parkedSteers` is in ISSUE order, and the wake message rides it with no `seq` (no store minted one). */
export interface UndeliveredContinuation {
    parkedSteers: Array<Omit<PendingSteerEntry, "seq"> & {
        seq?: number;
    }>;
    orphanTasks: Array<{
        id: string;
        description?: string;
    }>;
}
export interface CommittedPause {
    cause: import("../types.js").PausedCause;
    scope: string;
    gatedCallId?: string;
}
/** The session's date anchor as prepare read it off the branch. The system-prefix date is frozen per
 *  SEGMENT — from session start, or from the latest landed compaction, to the next one. */
export interface DateAnchorRead {
    /** The anchor written for the current segment; undefined ⇒ the leg renders today and anchors it. */
    current: {
        prefixDate: string;
        announcedDate: string;
    } | undefined;
    /** The current segment: the nearest compaction entry's id, null before the first compaction. */
    segment: string | null;
}
/**
 * One engine frame whose commit waits for the receipt of the message carrying it.
 *
 * @contract frames.commit_on_receipt — an engine-minted frame that rides a user message (the git-status
 * frame, an `agent_listing`, `skills_listing` or `date_change` frame) is recorded as announced ONLY when the run loop's
 * `message_end` walk sees a persisted user entry whose text equals the carrying text it was registered
 * with; every receipt with that text settles on that one entry. Until then nothing is recorded — a
 * message that never reaches the transcript (a pre-append failure, a suppressed or undrained steer, a
 * leg that ends first) leaves the frame unannounced, and the next boundary or leg renders it again.
 * The error asymmetry is one-directional by design: a lost receipt costs a duplicate frame, never a
 * fact the model was recorded as told and was not.
 */
export interface FrameReceipt {
    /** The carrying message's exact text (its first text block) — the receipt's match key. */
    text: string;
    /** Which frame this receipt settles. A boundary collector must not render a listing whose receipt is
     *  still pending (the frame is queued, not yet in the transcript). */
    source: "git_status" | "agent_listing" | "skills_listing" | "date_change" | "output_style";
    /** Runs inside the `message_end` walk (synchronous, never awaited): advances the run-local record
     *  and parks any session write for the next serialization point. */
    commit: (entryId: string) => void;
}
/** The leg's frame-receipt ledger — see {@link FrameReceipt}. `listingSnapshotOwed` is the parked
 *  announced-listing snapshot write (a session append from inside the walk would race the loop's own
 *  next transcript append); the boundary's end and the prompt's settle flush it. */
export interface FrameReceiptLedger {
    pending: FrameReceipt[];
    listingSnapshotOwed: boolean;
}
/**
 * The one closure that stops a run leg for a classifier denial-limit fallback NOBODY JUDGED — the
 * synchronous gate's headless arms and the durable resume station both call it, and both reach it
 * through {@link Prepared.stopForDenialLimit}. Declared here, beside the seat it fills
 * ({@link Prepared.gateStopRef}), so the two stations name one type rather than two.
 *
 * `settlement` is the kind that ENDED a parked fallback's wait, and is present only on the DURABLE
 * station: the synchronous one has no settlement to report (no approver was reached at all), and that
 * absence is what selects its clause of the shared sentence.
 */
export type StopForDenialLimit = (info: {
    toolName: string;
    toolCallId: string;
    fallback: DenialLimitFallbackFace;
    settlement?: SettlementKind;
}) => void;
/** The gate's seat inputs for one call (the static seat or the `Skill` tool's per-call reader, the run's skill and allow-list rows). */
export type GateSeatInputsFor = (calledName: string, wireName: string) => import("../mode-policy.js").PermissionSeatInputs & SkillGateSeats & import("../allow-entry.js").AllowEntryGateSeats;
export interface Prepared {
    harness: AgentHarness;
    /** The CONCRETE built-in session (engine-internal: prepare constructs/acquires `StoredSession` itself,
     *  and the epoch pin path needs its `appendPromptEpoch`/`getPromptEpoch`). Public consumer faces take
     *  the `Session` contract instead. */
    session: StoredSession;
    sessionId: string;
    /**
     * THIS INVOCATION's own identity, minted once at the top of {@link prepareTask} and never
     * rewritten. The third id of the trio, and the only one the engine owns outright:
     *  - `spec.taskId` — the HOST's task identity (absent whenever the host names nothing);
     *  - `sessionId`   — the CONVERSATION's identity (shared by every run that continues it);
     *  - `runId`       — THIS call of the engine.
     *
     * The gap it closes: the repo-wide `spec.taskId ?? sessionId` spelling answers the same value for
     * two runs of one session whenever the host supplies no task id, so every per-run account keyed on
     * it FOLDED two runs into one row (two runs each losing one steer read as one run losing one).
     * A per-run account must key on this; a per-TASK fact (trace `taskId`, the host's own correlation
     * handle) must keep the task spelling — the two are different questions and the station sweep
     * judged each one.
     *
     * Minted here (never at the Runner's stream entry) so that a standalone `prepareTask` exercise gets
     * a real id too, and so the mint has exactly one site. A leg whose prepare THROWS never returns a
     * `Prepared` at all — but the id still reaches its caller through the `runIdSink` carrier, because
     * prepare emits run-scoped disclosures of its own and each must have a terminal that names it.
     */
    runId: string;
    /** The (owner, scope) key this run's registry-facing tools register rows under — (host task id, mount domain), minted once at prepare; the teardown sweeps read it and never re-derive it. */
    registryAccess: {
        readonly owner: string;
        readonly scope: string;
    };
    /** The session's reminder provenance mark this run mints under (adopted
     *  through the prepare adoption ladder: checkpoint seed → trusted fork channel → session entry →
     *  fresh mint). Every engine-authored `<system-reminder>` open tag in the run carries it, and the
     *  system prompt's Harness declaration names it. Always present on a completed prepare. */
    reminderMark: string;
    /** The leg's reminder-disclosure trigger counters (mutated by the
     *  disclosure outlets at tool-execute time; read once at result assembly into
     *  `stats.mechanisms.reminderDisclosures` when any key is non-zero). Always present. */
    reminderDisclosureCounts: import("../reminder-disclosure.js").ReminderDisclosureCounts;
    /** Whether the full shell is on this leg's roster (the inherited-gate phase's bit, asserted against the mount). */
    fullShellReachable: boolean;
    /** The run's approval-posture facts as prepared — the effective permission mode and the ask seam's effective
     *  answer. The posture composition renders them into the engine's own marked reminders; no gate decision keys on them. */
    approvalPosture: import("../auto-mode-directive.js").ApprovalPostureFacts;
    /** The output-style frame body, rendered once at prepare from the screened `TaskSpec.outputStyle` (absent = no seat). */
    outputStyleFrame?: string;
    /** The leg's live permission-mode seat: the effective mode every mode consumer reads, the steer verb writes. */
    permissionMode: PermissionModeController;
    /** The ISOLATION-AWARE working-tree root for this task (a worktree's cwd when `isolation: "worktree"`, else
     *  `deps.rootPath ?? executionEnv.cwd`) — the same value the hands/LSP/policy/restore use. The Runner's
     *  rewind/snapshot path MUST key off THIS, not `deps.rootPath`, or a worktree-isolated turn snapshots the base
     *  repo (CORE-1~9 audit MAJOR). */
    taskRootPath: string;
    model: Model;
    /** Effective thinking level (explicit `spec.thinking` or the resolved role's default). */
    thinking?: ThinkingLevel;
    compModel?: Model;
    mcp: MaterializedMcp;
    /** The run handle's `reconnectMcpServer` implementation — {@link MaterializedMcp.reconnect} plus the
     *  live-roster application and the operator disclosure, which are runner concerns and not the MCP
     *  handle's. Present only when this leg DECLARED MCP servers; the stream verb's seat stays unbound
     *  otherwise and it refuses typed. */
    mcpRedial?: (server: string) => Promise<import("../mcp.js").McpReconnectResult>;
    /** Present only when `spec.a2a` declared peers — the run loop owes it the same end-of-task dispose
     *  it owes {@link mcp} (no sockets are held, but a disposed mount is what makes a post-task tool
     *  call refuse loudly instead of dialing a peer on a dead task's behalf). */
    a2a?: MaterializedA2a;
    blockedRef: BlockedRef;
    /** Holds the structured output once `submit_output` is called (when `spec.outputSchema` is set). */
    outputRef: OutputRef;
    /** Fires when the task aborts (timeout / max turns / end). Passed to `ToolPolicy.check` so a
     * pending human-approval gate is released instead of hanging past the deadline (F4). */
    abortController: AbortController;
    /** Flipped if any session write lost the optimistic lock during the run → `errorCode = "conflict"`. */
    conflictRef: {
        hit: boolean;
    };
    /** Tool-call ids the gate blocked (policy/hook/plan-mode deny) or suspended —
     *  populated only while a consumer is wired (per-tool post hooks or postToolBatch). The runner's
     *  batch collector DELETES on match (its tool_execution_end is the only end-event a blocked call
     *  emits; the tool_result-side delete in prepare-task never fires for immediate results). */
    blockedToolCalls: Set<string>;
    /**
     * WHAT ended the approval a gated call was waiting on and WHOSE settlement it
     * was, keyed by tool-call id: written ONLY by the tool gate, at the one exit where an ask resolved,
     * and read once when that call's `tool_end` frame is minted (the reader deletes on read; a call the
     * gate never settled has no entry, and an entry never names neither fact).
     *
     * ONE record rather than two parallel maps because they are one observation: an attribution without
     * the settlement kind beside it is unreadable ("alice" — approved? her window elapsed?), and two maps
     * keyed alike are two chances to drain one and leak the other.
     *
     * It is a sideband and not a field on the tool RESULT because a result is not a trustworthy carrier
     * for this: `details` is arbitrary tool-authored data that post-tool hooks may also replace, so a
     * failing tool could stamp a settlement on itself and tell a consumer's audit view that a person
     * approved something nobody was ever shown. Provenance has to travel on a channel only the
     * adjudicating layer can write. Same reason the entries are keyed by CALL id: the gate adjudicated
     * that exact call, and the frame that reads it is that call's own.
     */
    gateOutcomes: Map<string, import("../gate-outcome.js").GateOutcome>;
    /**
     * The parent-thread human-rejection halt fact (see `maybeHumanRejectionHalt`): present from the
     * moment a bare human rejection halts the turn's batch until the run ends or the NEXT provider
     * request begins (user input continuing the run clears it). Consumers: the runner's stop gate
     * (suppress natural-end pushback / final-verify injection — engine continuations must not restart
     * a run a person just stopped), the turn-boundary engine steers (same reason), and the result
     * stamp (`TaskResult.haltedOnUserRejection` — a human-halted run must not read as an ordinary
     * completion). Engine-owned sideband, same trust reasoning as `approvalSettlement` above.
     */
    batchHaltRef: {
        current?: {
            rejectedToolCallId: string;
        };
    };
    /** Summed usage of nested sub-runs (sub-agents) spawned by this task's tools. */
    nestedStats: NestedUsageAccum;
    /** Prepare-time rewind disclosures (conversation-only branch / no snapshot backend / no file
     *  env), echoed verbatim onto `TaskResult.rewindNotes`. Present only when there is something to say. */
    rewindNotes?: NonNullable<TaskResult["rewindNotes"]>;
    /** The run's edited-file ledger read face — what its OWN hands landed, as `TaskResult.editedFiles`
     *  (undefined when nothing landed: the key is absent, never an empty array). A LIVE reader rather
     *  than a snapshot, so the throw-path backstop terminal reports the same ledger the ordinary
     *  assembly would. Always present on Prepared; independent of whether a fileHistoryStore is wired. */
    editedFilesSnapshot: () => TaskResult["editedFiles"];
    /** The run's turn-start boundary seat (present iff the run records file history into a wired store and
     *  the run mounts a real fs env). runtask calls begin() at the first committed user entry and
     *  awaits settle() at the lease close + the finish tail. */
    fileHistoryBoundary?: FileHistoryBoundarySeat;
    /** The run's RESOLVED read face for the result observation seat
     *  (`TaskResult.effectiveReadFace`): `carrierReadFace()`'s value at prepare completion — the hands
     *  block's single resolution, or the hands-less legs' resolver run (live spec-time facts, with the
     *  checkpoint seed folded stricter-wins where one exists). Every leg that completes prepare has a
     *  read posture now — this is what its delegation subtree is clamped by even where no faces mount. */
    effectiveReadFace?: ContainmentFace;
    /** owned — the same observation on the WRITE seat (`TaskResult.effectiveWriteFace`). */
    effectiveWriteFace?: ContainmentFace;
    /** The normalized deny ADDITIONS in force (deployment ∪ task ∪ checkpoint seed; built-ins
     *  excluded), echoed on `TaskResult.effectiveReadDenyPatterns`. Present iff non-empty; a defensive
     *  copy (the wide-scope working array stays the engine's own). */
    effectiveReadDenyPatterns?: readonly import("../../tools/fs/read-deny.js").NormalizedReadDenyEntry[];
    /** The memory-visibility observation prepareMemory minted (echoed on
     *  `TaskResult.effectiveMemoryScopes`). Always present on a completed prepare (the memory-less
     *  states are their own values); the seat is optional only so a Prepared shape without the phase
     *  cannot fabricate one. */
    effectiveMemoryScopes?: import("../types.js").EffectiveMemoryScopes;
    /** The per-task logical cwd ref when a real shell is mounted (else undefined). The Runner
     *  reads `cwdRef.current` after each tool to detect a `cd` move and emit `workspace_changed`. */
    cwdRef?: CwdRef;
    /** The run's TOOL-PROGRESS lane seat, late-bound for the same reason `harnessRef` is (the tool band is
     *  built during prepare; the lane needs `pushContent` and `ident`, which exist only after it returns).
     *  The driver binds `current` right after `pushContent` exists. Always present on a completed prepare —
     *  the CELL, not the lane: unbound means "no run body has claimed this band", and emissions no-op. */
    toolProgressRef: {
        current?: ToolProgressLane;
    };
    /** The run's advertised-writable-directory seat (present with hands): `roots` is what the prompt advertised as writable and the fence admitted. Read live wherever a policy is adjudicated off a resumed leg, so the write gate's exemption domain is that same list. */
    advertisedWritableDirs?: Pick<import("./advertised-writable-dirs.js").AdvertisedWritableDirs, "roots">;
    /** The ACTIVE EnterWorktree session ref (mounted with the worktree tools; undefined without
     *  real write hands). runtask's settle write reads `current` for the workspace-state entry. */
    worktreeSessionRef?: {
        current?: ActiveWorktreeSession;
    };
    /** The settle-write base — the canonical root + what the cwd/worktree were RESTORED to at
     *  prepare. runtask diffs the live refs against this at settle and appends a `workspace_state` entry
     *  when the state changed (skipped on a durable suspend — the checkpoint lane owns that state). */
    workspaceStateSettle?: {
        rootCanonical: string;
        restoredHandsCwd?: string;
        restoredWorktreeDir?: string;
        baselineUnknown?: boolean;
    };
    /** The settle-time session-seat SEAL (see prepare-hands-readface): re-anchors
     *  the per-session read-state seat to the entry id THIS run last committed (its own
     *  message_committed tail — provenance, never a shared-leaf re-read). The driver calls it once at
     *  the run's settle, every terminal (suspend included), under the session lock; undefined argument
     *  (no commits) is a no-op. Synchronous, never throws. Absent ⇔ hands-less / no seat channel. */
    sealReadStateSeat?: (ownCommittedTailEntryId: string | undefined) => void;
    /** The DENY-NARROWING layers (session rules, inherited ancestor rules, the frozen constraint projections; deny-only).
     *  Re-checked on RESUME before an approved pending tool executes, so a rule tightened during the suspend still applies. */
    denyNarrowingPolicy?: ToolPolicy;
    /** The run's skill grant lane: its deny rows (a skill's `disallowed-tools`) are re-read on RESUME at the same station as the
     *  person's deny rows, on the bytes that will run. */
    skillGrants: SkillGrantLane;
    /** The skills this leg loads (normalized, past the load gate; empty when none) — the one list its `Skill` tool activates from, its park binds and the resumed execution boundary judges a parked Skill approval against. */
    skillSpecs: readonly SkillGrantSource[];
    /** The person's own persisted-rule lane (deny/ask rows), re-adjudicated on RESUME at the same
     *  station as the deny-narrowing layers: a deny row added while the call was parked vetoes the execution
     *  (`deniedBy: "persisted_rule"`), and so does a call the lane could not read against the rows (re-issue and
     *  re-approve); an ask row does not (the approval is its answer), and a store that could not be read
     *  contributes no row. Absent when no rule store is wired. */
    persistedRuleLane?: {
        adjudicate: (req: ToolCallRequest) => Promise<import("../hooks.js").PersistedRuleAnswer>;
    };
    /** The deployment's caller policy, re-checked on a durable resume when the bytes to execute are not the bytes the gate saw. */
    basePolicyForResumeEdit?: ToolPolicy;
    approvedCallScreen?: (call: {
        toolCallId: string;
        toolName: string;
        args: unknown;
        decidedBy: "person" | "sla_timeout" | "policy";
    }) => Promise<{
        readonly refused: string;
    } | {
        readonly args: unknown;
        readonly admit: () => void;
    }>;
    /** The leg's shell READ STATION, re-run on a durable RESUME before an approved shell call executes: the parked leg's
     *  admission died with that leg, so the resumed bytes are judged (and admitted for their pre-spawn re-establishment)
     *  on THIS leg — a deny row found now vetoes, an operator's edit is judged on the `rejudge` seat. */
    shellReadCheck?: import("../../tools/fs/shell-read-check.js").ShellReadCheck;
    /** The ORG adjudication face, re-resolved on a durable RESUME before an approved
     *  pending call executes. The resume path bypasses the harness gate by design (a human already
     *  adjudicated the checkpointed call), which is exactly where org policy skew is most likely: the
     *  suspend may have outlived the snapshot revision that was current when it was minted. Present only
     *  on a governed deployment. */
    permissionRuleOrg?: {
        adjudicate: (req: ToolCallRequest) => Promise<OrgGateVerdict>;
    };
    /** Removes the `spec.signal` abort listener on task end (else a long-lived signal leaks listeners). */
    releaseSignal: () => void;
    /** The leg has stopped: its children's mode ceiling moves to the session's current leg (called once its loop settled). */
    endModeCeiling: () => void;
    /**
     * End-of-leg sweep for questions this leg's gate routed to a person. Called once by the
     * run loop at task end, on every exit path. A binding that is still present means a person ANSWERED and
     * the call never executed to collect it (aborted, batch torn down, loop threw): that answer is disclosed
     * through the deployment's error sink rather than dropped, because "a human answered and it vanished" is
     * precisely the failure this whole path exists to remove — it must not reappear inside the mechanism
     * that removes it. Idempotent, never throws.
     */
    settleContentAskBindings: () => ReadonlyArray<{
        deliveryId: string;
        toolCallId: string;
    }>;
    /** Per-task prefix-cache-break detector. The fingerprint is mutated when deferred tools
   *  materialize — see `cacheFingerprint`. */
    cacheBreakDetector?: CacheBreakDetector;
    /** The fingerprinted prefix. `systemPrompt` is stable; `tools` is REFRESHED in place when a deferred
     *  tool is materialized (placeholder→full schema), so the cache-break detector sees the real tool set. */
    cacheFingerprint?: {
        systemPrompt: string;
        tools: ToolFingerprintInput[];
    };
    /**
     * 提示词主权批 — the labelled composition of the assembled system prompt, emitted as the
     * `prompt.assembled` trace event at task start. `constitution` says who owned the safety layer:
     * `"core"` (structural, the default), `"replaced"` (provider set `replaceAll` — deliberate opt-out),
     * `"provider-assembled"` (migration guard: a historic provider returned a full prompt, passed through
     * un-doubled + onError hint). Deployments/tests assert on this to make "which blocks were in the
     * prompt" a runtime fact instead of archaeology.
     */
    /**
     * This leg's EFFECTIVE wiring manifest (built once in prepare from resolved
     * facts through the same derivation as the static half). runtask emits it as the
     * `wiring_manifest` TaskEvent after the prepare consume point and before the leg's first
     * model/tool interaction. Host/operator plane — never enters model context.
     */
    wiringManifest: WiringManifest;
    /**
     * This leg's frozen identity envelope, minted ONCE in prepare beside the wiring
     * manifest (same leg derivation, one mint — {@link mintHookInvocationIdentity}). Every hook
     * station runtask drives (stop/stopFailure/userPromptSubmit/postToolBatch, the compaction
     * wrapper) and the 件B delegation-lifecycle observer read THIS object; prepare's own stations
     * (the tool gate, the post-tool contexts) close over the same const. Always present — a prepared
     * leg always knows its identity.
     */
    hookIdentity: HookInvocationIdentity;
    /**
     * This session's DURABLE box, present iff this leg mounted the session-box drain (a top-level leg
     * over a cross-process-safe mailbox with a grammatical session id). The notification lane binds it
     * to the park seat ({@link import("../task-notification.js").PendingSessionNotifications.bindSessionBox})
     * so a child's uplink frame parked for this session while it is idle lands in the box the next run
     * drains — one mount judgment, the drain and the park read it alike. Absent ⇒ in-process parking.
     */
    sessionBox?: import("../task-notification.js").SessionBoxBinding;
    /**
     * #1036 — the leg's TWO SETTLEMENT LEDGERS, one cell shared by the three beats that fill and
     * read them: the harness's transcript-append sink writes receipts, the compaction commit beat
     * writes effects, and the session-box drain's settlement half-pass reads both and clears what it
     * consumed. Always present (the cell is minted with the leg); empty on a run with no session box,
     * because nothing registers an interest and the receipt sink only records awaited keys.
     */
    frameSettlement: import("../frame-settlement.js").FrameSettlement;
    /**
     * #1036 — the session-box drain's BOUNDARY PASS, called by the turn-boundary lane as its tail
     * (in a `finally`, so every early exit of the boundary still settles). Present iff this leg bound
     * the drain. `continues` = this leg will issue another request after this boundary, which is what
     * decides whether the pass's NEW-delivery half runs; the settlement half runs either way.
     *
     * It is a tail call and not a `turn_boundary` hook of the drain's own because the ORDER matters and
     * a hook's position is registration-order luck: settlement must run AFTER this boundary's
     * compaction, or a record whose frame the compaction just summarized away is already acked (deleted
     * from the box) by the time anyone could notice.
     */
    peerBoundaryRound?: (o: {
        continues: boolean;
    }) => Promise<void>;
    /**
     * The per-invocation TIME BOUND every hook seat of this leg runs under (`Hooks.timeoutMs`, already
     * validated — a garbage value was refused to the default and disclosed ONCE, here, rather than on
     * every tool call). Published for the same reason `hookIdentity` is: the seats live in two files, and
     * a number each station re-derived would be two answers to one wiring question — including two
     * chances to re-refuse the same bad value.
     */
    hookTimeoutMs: number;
    promptManifest: {
        constitution: "core" | "replaced" | "provider-assembled";
        blocks: Array<{
            id: string;
            chars: number;
            hash: string;
        }>;
        /** Manifest v2 (stage S1, additive): the compiled-IR section view (slot/carrier/cadence/
         *  cacheClass per section). `contentHash` is the
         *  digest-tiering face: present ONLY on operator-declared sections (the typed
         *  `stableBlocks` lane, owner "deployment") as an UNSALTED full sha256 of the section text —
         *  the center-publish ↔ manifest end-to-end reconciliation anchor. Core-native sections (env
         *  facts, memory tail, user role text) stay salted-only: a plain hash of a short guessable
         *  block is a dictionary-preimage surface for trace readers ([E]). */
        sections?: Array<{
            id: string;
            slot: string;
            carrier: string;
            cadence: string;
            cacheClass: string;
            chars: number;
            hash: string;
            contentHash?: string;
        }>;
        /** S2 (additive): the mounted tools plane — contract identity, durable-replay aliases,
         *  presentation-invariant shape digest and full wire digest per tool (initial mount face). */
        tools?: ToolManifestRow[];
        /** The nine-element cache identity — digests only, no prompt
         *  text; backfilled once the final wire tool list exists. Live post-refresh truth rides
         *  `prepared.turnSnapshot`. */
        snapshot?: {
            cacheIdentity: string;
            elements: import("../../prompt-assembly/turn-snapshot.js").CacheIdentityElements;
        };
        /** The run's lowering record (version/wire form/known
         *  intentional divergences — run-static v1, one brain serves every call). */
        lowering?: import("../../prompt-assembly/turn-snapshot.js").LoweringRecord;
        /** The RESOLVED deferred-tool disclosure strategy for this leg and which
         *  seat chose it. Absent when nothing is deferred (there is no disclosure to describe). */
        toolDisclosure?: ToolDisclosureManifest;
    };
    /** This run's provider-declared prompt sections (epoch declaration axis),
     *  threaded to every compaction-boundary epoch selection so boundary re-pins hash the same
     *  artifact identity as the prepare-time pin. Empty on declaration-free runs. */
    epochDeclaredSections: import("../../prompt-assembly/epoch.js").EpochDeclaredSections;
    /** The LIVE nine-element identity face (refreshed by deferred materialization and
     *  the deferred-materialization adoption swap); the manifest holds the prepare-time initial copy. */
    turnSnapshot?: import("../../prompt-assembly/turn-snapshot.js").TurnPromptSnapshot;
    /** The compaction-boundary center-adoption seam (undefined = no source
     *  wired or session not center-pinned). Returns the maybeCompact `centerAdoption` fragment or
     *  undefined; `apply` runs post-CAS only (auto-compaction owns the ordering). Both
     *  `epochDeclaredSections` and `promptOverheadTokens` on THIS object are mutated by a successful
     *  adoption (read them at call time, never capture early). */
    centerCompactionCandidate?: () => {
        centerArtifactDigest?: string;
        sourceRevision?: string;
        clear?: true;
        declaredSections: import("../../prompt-assembly/epoch.js").EpochDeclaredSections;
        overheadTokensAfter: number;
        apply: (committedArtifactDigest?: string) => void;
    } | undefined;
    /** Deferred-tool disclosure: the monotonic set of activated deferred-tool names. Empty
     *  (and no `tool_search` injected) when nothing is deferred. Mutated by BOTH disclosure lanes across
     *  the run — the injected `tool_search` and the direct-call placeholder lane (default ON) —
     *  under ONE shared activation critical section, plus resume seeding at prepare. A leg on which the
     *  model never calls `tool_search` is therefore NOT quiescent. */
    activeTools: Set<string>;
    /** The DECLARED deferred set (populated by `classifyDeferred`, ghost names already pruned).
     *  Present iff the task has deferred tools at all; `deferredToolNames − activeTools` is the
     *  "undiscovered" census the tool_search_usage_reminder lane enumerates. Frozen at prepare time: a
     *  name never leaves the deferred set, it only moves into `activeTools`. */
    deferredToolNames?: ReadonlySet<string>;
    /** The deferral census seat, present iff {@link deferredToolNames} is. */ toolDisclosure?: import("./deferral-census.js").ToolDisclosureSeat;
    /** The RESOLVED materialization strategy this run runs under (`true` = "static":
     *  activation never swaps the placeholder; the real schema's only in-context carrier is the
     *  ToolSearch result text). Consumed by the tools_delta boundary renderer, whose "full schemas are
     *  loaded" wording is a statement about the tools block and is only true under swap. `false` when
     *  nothing is deferred (the knob is inert then). */
    toolMaterializeStatic: boolean;
    /** Is the direct-call lane mounted on this run's placeholders (`TaskSpec.deferSelfResolve`
     *  not disabled; default ON)? The SAME fact `createToolSearchTool` receives as `directCallEnabled`,
     *  surfaced here so every model-facing face that describes what happens to a call on a
     *  still-deferred tool reads ONE value — under the direct lane a schema-valid call executes rather
     *  than failing, so an absolute failure claim is only true for the opt-out posture. `false` when
     *  nothing is deferred (no placeholders, so the fact is inert). */
    deferDirectCall: boolean;
    /** Is THIS deferred name on the static face? False under `"swap"`, and false under
     *  `"static"` for a tool whose declaration the result-text carrier cannot hold (the per-tool
     *  exemption: it materializes into the tools block instead). Read at call time — the roster is
     *  mutated in place by MCP refresh, so renderability is a live fact. Absent when nothing is
     *  deferred; a caller with no accessor has no deferred family to describe. */
    staticFaceFor?: (name: string) => boolean;
    /**
     * The MemoryEngine session. Present when `deps.memoryBackend` + `spec.memory.enabled`
     * hold AND the engine mount succeeded: a materialize failure without a `config.memory_*` code is
     * fail-open (reported via `deps.onError`, the task runs memory-less), leaving this absent even though
     * both flags hold.
     * `harvest` is the swallow-guarded boundary hook (task terminal in runtask + the checkpoint mint
     * point in commitSuspendSaga): it runs the FULL gate set (containment/secret/caps/deletion fuse),
     * commits entry patches to the backend, self-heals the derived index, and re-baselines (a second
     * harvest of an unchanged session yields zero patches). It NEVER throws.
     * S2-B (O-F7/C-F3): the report is NOT discarded — `harvest` hands it to the Runner-owned
     * `deps.onMemoryHarvestReport` callback (with the boundary phase), and the engine itself enqueues
     * the announcement-queue entry at its harvest tail (drained into the NEXT session's first inject —
     * 时机①; the r1 in-run attachments lane was cut, O-F2/C-F4). `phase` defaults to "terminal" (the
     * runtask call site passes nothing); the checkpoint-mint call site passes "checkpoint".
     */
    memoryEngineSession?: {
        /** The WRITE-plane pair (dual roots collapse to one session face; these are the
         *  plane that owns the write gate — the read-only plane is internal to `inject`/`harvest`). */
        engine: MemoryEngine;
        handle: MemorySessionHandle;
        /** Merged injection across all planes (single-root sessions: identical to engine.inject(handle)). */
        inject: () => import("../memory-engine/engine.js").MemoryInjection;
        harvest: (phase?: "checkpoint" | "terminal") => Promise<HarvestReport | undefined>;
        /** End this mount's session leg WITHOUT a harvest: the run that mounted it will never reach its
         *  terminal harvest (a prepare that failed after the mount). Never throws. */
        release: () => void;
        /**
         * The session's capture opt-out face (the `TaskStream.optOutMemoryCapture`
         * verb's target; host-API only — no tool face reads it, §2.6). `flip` re-adjudicates the
         * entitlement at call time (fresh caps resolve), mints the one-way record, runs the §2.3
         * boundary sweep, and delivers the effectiveness notice; its typed refusals are the
         * `memory.capture_optout_*` family. `optedOut` is the live state read (declared / standing /
         * flipped) — a GENUINE-record read: a store fault answers false here and TRUE on
         * `indeterminate` instead: the fault must reach the delegation floor
         * as its own third state, never as either boolean; an irreversible record is never minted off
         * an unreadable state, and an outage must not spawn un-floored children. Present whenever
         * the memory session mounted.
         */
        captureOptOut?: {
            /** Dual form: a sync capture store answers synchronously; a Promise-form store answers a Promise. `await` is correct on either arm. */
            optedOut: () => boolean | Promise<boolean>;
            indeterminate: () => boolean | Promise<boolean>;
            flip: (reason?: string) => Promise<{
                outcome: "created" | "existed";
            }>;
        };
        /**
         * The session's ONE-WAY pollution face. `markPolluted` fires when an
         * external-content-class tool is invoked (the prepare-time tool wrap below is the caller);
         * durable + in-process, no unmark exists at any layer. `polluted` is what the harvest legs read
         * (they consult it at harvest time themselves — this accessor serves observers/tests).
         */
        pollution: {
            polluted: () => {
                at: number;
                reason: string;
                cause?: import("../memory-engine/types.js").MemoryOriginCause;
            } | undefined;
            /** `cause` is the structured mechanical fact the mark records (and the
             *  origin marker minted off it carries): `"observed"` for a witnessed external event,
             *  `"static"` for the capability over-approximation, `"derived"` for recall-taint. Absent
             *  folds to `"observed"` at mint time (the honest floor). */
            markPolluted: (reason: string, cause?: import("../memory-engine/types.js").MemoryOriginCause) => void;
        };
        /** The task's content-safety config (normalized memory spec): the allowlist for
         *  UNDECLARED tools and the strict execution-class upgrade. Consumed by the tool wrap only. */
        contentSafety: {
            trustedTools: ReadonlySet<string>;
            execIsExternalContent: boolean;
        };
        /**
         * The delegation-settlement handle: the control-plane coordinates a
         * settlement writer needs, as PURE DATA. Consumers (the subagent background leg's write-ahead
         * + terminal observation, the tool wrap's sync unattestable row) can outlive this prepared
         * leg, so they rebuild their write handle from these fields alone — never from the live
         * engine/session objects above. Present only under `memoryProvenance: "carry"` (the default):
         * an `"off"` deployment keeps the pre-336 accepted-cost posture byte-level.
         */
        settlement?: {
            /** The WRITE plane's control-plane dir (the pollution-marker/lineage sidecar home). */
            controlDir: string;
            sessionId: string;
            provenance: "carry";
        };
        /**
         * The Read-tool recall-taint judgment, present only under
         * `memoryProvenance: "carry"`. True ⇔ the delivered ABSOLUTE path sits inside a mounted memory
         * plane and its head bytes carry a committed external-origin marker; the tool wrap then marks
         * the session derived (same seat and cause as the memory_get propagation). Never throws;
         * relative paths and unreadable files answer false (named residuals beside the Bash channel).
         */
        recallTaint?: {
            judgeDeliveredPath: (absPath: string) => boolean;
        };
    };
    /** A per-task env minted by `RunnerDeps.executionEnvFactory` (the remote seam) that THIS task owns
     *  and the Runner must tear down on task end. Undefined when the env came from a (caller-owned) static
     *  `deps.executionEnv` or the stub — those outlive the task and must NOT be destroyed here. */
    ownedEnv?: ExecutionEnv;
    /** The run's ONE committed durable pause ({@link CommittedPause}): a mutable holder the commit-side
     *  publisher (`publishCommittedSuspend`) writes exactly once per run when a park commits — an approval
     *  or content ask, a resource slice, a platform pause, a plan or dry-run review — and the run loop reads
     *  to assemble the result's `terminal: { kind: "paused", … }`. WHICH pause it is is the cause's own
     *  `gate.kind` (a registry row), not the holder it landed in: the two same-shaped holders that used to
     *  split the approval family from the review family, and the commit-side routing between them, are
     *  gone. `current` is absent until a park commits. */
    pausedRef: {
        current?: CommittedPause;
    };
    /** A resumed leg's continuation content that has NOT reached the transcript yet: EVERY input frame the
     *  prompt carries (parked steers, the caller's wake message) and the orphaned background tasks whose obituary
     *  rides it. Set before the prompt, emptied at delivery; a re-park carries it forward (the saga folds the
     *  orphans in, the adoption re-parks the frames), and what no re-park carries the undrained account announces. */
    undeliveredContinuationRef: {
        current: UndeliveredContinuation;
    };
    /** Ruled 2026-08-05 (matrix ruling arm A): set true by the resume engine when this leg EXECUTES the
     *  approved pending call — the restart-loop cap then counts from a fresh base (consecutive
     *  no-progress suspends only). See `suspendChainBase`. */
    suspendProgressRef: {
        executedApproved: boolean;
    };
    /** The remote-workspace lifecycle failures this run hit, appended in call order and echoed
     *  verbatim on `TaskResult.remoteEnvFailures`. A shared array (not a per-call return) because a suspend
     *  refusal is reported through the deployment's `onError` side channel and the run then continues or
     *  stops by its own rules — without this collector the caller's result kept no trace that a durable
     *  suspend was even attempted, let alone which of the eleven codes refused it. Empty unless something
     *  failed. Resume-leg failures do NOT land here (that leg throws out of prepare before a `Prepared`
     *  exists) — they ride the thrown error's `remoteEnvFailure` carrier instead. */
    remoteEnvFailures: NonNullable<TaskResult["remoteEnvFailures"]>;
    /** Set when a suspend was REFUSED because the task already suspended `maxSuspends`
     *  times (a resume/restart loop) — the run is aborted and assembles as `failed`/`suspend.loop` instead
     *  of minting yet another checkpoint. */
    suspendLoopRef: {
        hit: boolean;
    };
    /** Opt-in resource-slice suspend. Present (≠ undefined) ONLY when the task opted in
     *  (`spec.resourceSuspend`) AND it is eligible to suspend durably (a checkpoint store, durable tool
     *  results, and a remote — or static caller-owned, never per-task-stub — env). The run loop calls it at a
     *  CLEAN turn boundary when a resource limit (turns/budget/walltime) was hit: it mints a `resource_limit`
     *  checkpoint + pauses the workspace + stops the loop cleanly (NOT abort). Returns true iff it committed a
     *  resumable checkpoint (publishes the committed pause); false ⇒ caller falls through to normal limit handling.
     *  `sliceSpend` (Slice 4) is THIS slice's cost/tokens/turns, debited onto the cross-slice ledger. */
    suspendForResource?: (reason: ResourceLimitReason, sliceSpend: {
        costMicroUsd: number;
        tokens: number;
        turns: number;
        walltimeMs: number;
    }) => Promise<boolean>;
    /** The PLATFORM-cause suspend — the same commit saga as {@link suspendForResource},
     *  exposed on the INFRASTRUCTURE alone (checkpoint store + durable tool results + remote/no owned env),
     *  WITHOUT the `spec.resourceSuspend` opt-in. The run loop calls it at a clean turn boundary when the
     *  execution environment is about to be reclaimed (`env_lifetime`) or a deployment usage window is
     *  exhausted (`usage_window`) — causes the task did not choose and cannot ask differently for, so a
     *  deployment that CAN keep the work has no reason to be asked whether it wants to. `hint.resumeAfterMs`
     *  (usage windows only) rides the gate and extends the checkpoint deadline. Returns true iff it committed
     *  a resumable checkpoint; false ⇒ the caller stops the run LOUDLY with the cause's terminal code. */
    suspendForPlatformLimit?: (reason: PlatformLimitReason, sliceSpend: {
        costMicroUsd: number;
        tokens: number;
        turns: number;
        walltimeMs: number;
    }, hint?: {
        resumeAfterMs: number;
    }) => Promise<boolean>;
    /** Epoch ms at which this run must stop and checkpoint because the EXECUTION
     *  ENVIRONMENT's declared lifetime is about to expire (`ExecutionEnv.lifetimeMs` minus
     *  {@link ENV_LIFETIME_SUSPEND_MARGIN_MS}). Undefined ⇒ the env declared no lifetime (every local env,
     *  and any adapter on an unbounded host) or declared one the engine could not anchor — in both cases the
     *  boundary check is dead code. Epoch, not monotonic: the anchor
     *  comes from the PLATFORM (an env can be older than this process), which is a wall-clock fact and the
     *  one domain both sides can name. */
    envLifetimeSuspendAt?: number;
    /** Deployment usage governance for this run, or undefined when the deployment wired
     *  none (or wired windows without a ledger, which is reported and not enforced). */
    usageGovernance?: UsageGovernance;
    /** 1.296 件2b — epoch ms at which the loop ISSUED the current provider call (stamped by the
     *  per-call stall-watchdog closure, consumed+cleared by runtask's brain.call trace row as
     *  `callStartedAt`). Always present. */
    callIssuedAtRef: {
        current?: number;
    };
    /** Was the run's WORK cut by a stop? Two observation points, one record, read by the answer-settled
     *  verdict (`answerSettled`) beside the harness's own undrained-input account:
     *  - `continuation` — the loop decided to CONTINUE past its last assistant turn and the abort caught it
     *    before that continuation dispatched. Written by the loop-trace sink from the loop's own steps: a
     *    `continue` step (a drained steer / follow-up, a truncated-output or thinking-only or degenerate-output
     *    nudge, a next tool turn) sets it; the terminal step clears it unless the loop exited at its
     *    pre-dispatch abort guard (`aborted_before_stream`) — the one exit where `final` is still the PREVIOUS
     *    turn's message. A clean "stop" the loop itself had decided was not the end is not a settled answer.
     *  - `abortedBeforeTurnEnd` — the run's abort was already in force when a turn ENDED (written by the
     *    `turn_end` handler before the turns cap fires its own abort). The ruling's line is the last
     *    `turn_end`: a stop that landed before it landed on work — even when the brain ignored the signal
     *    and delivered a full "stop" answer through it (the loop then ends at its own decision, so the
     *    continuation bit never sets); a stop that landed after it landed on the tail.
     *  Always present; both false on a run nothing stopped. */
    workCutRef: {
        continuation: boolean;
        abortedBeforeTurnEnd: boolean;
    };
    /** Records the FIRST brain call this run's outer guardrail gave up on (see
     *  {@link import("../../brain/timeout.js").withBrainCallGuardrail}). Always present; `timedOut`
     *  stays absent unless the guardrail fired. The run loop reads it AFTER the loop settles and gives
     *  the task the typed terminal — the harness turns a loop throw into an error assistant message, so
     *  without this the cause would reach the caller only as the generic `provider.error`. */
    brainCallGuardrailRef: BrainCallGuardrailRef;
    /**
     * The tool gate's own TYPED STOP: set (once) when the classifier denial limit fell back to a person
     * and nobody judged it — no approver to fall back to (headless), or a DURABLE park whose decide
     * settled with a word the nobody-judged table reads as such — together with the run abort. The run
     * loop adopts it as the terminal `threw` (`TaskResult.errorCode` = the error's `code`,
     * `errorMessage` = its sentence) the same way it adopts the brain-call guardrail's — a loop that
     * ended because THIS lane aborted it must report the cause, not the consequence. The abort-result
     * details seam reads it too, so the aborted call's own `tool_end` carries the code. `undefined` ⇒ no
     * gate stop happened.
     */
    gateStopRef: {
        terminal?: Error & {
            code: string;
        };
    };
    /** The ONE closure that fills {@link Prepared.gateStopRef} (minted by `createDenialLimitStop`),
     *  carried on `Prepared` so the DURABLE resume station reaches the same seat, the same sentence
     *  composer and the same notice as the synchronous gate's arms — the second station of one rule, not
     *  a second rule. */
    stopForDenialLimit: StopForDenialLimit;
    /** Set by a tool calling `ctx.requestReview()` (the first-party `present_plan` tool, CC
     *  ExitPlanMode parity): the run loop honors it at the next CLEAN turn boundary by minting a `plan_review`
     *  checkpoint. `{ pending }` is set (with an optional reason) the moment a tool requests review; the boundary
     *  reads + clears it. First request in a batch wins (idempotent). */
    reviewRequestRef: {
        pending?: {
            reason?: string;
        };
    };
    /** Present (≠ undefined) ONLY when a `checkpointStore` is wired (the deployment can pause). The
     *  run loop calls it at a CLEAN turn boundary when `reviewRequestRef.pending` is set: it mints a `plan_review`
     *  checkpoint (`status:"needs_review"` by its registry row) + pauses the workspace + aborts the loop, reusing
     *  the SAME commit saga as the human/resource suspends. Returns true iff it committed a resumable checkpoint;
     *  false ⇒ the request could not be honored (caller drops it and continues). */
    suspendForReview?: (reason?: string) => Promise<boolean>;
    /** The prior cross-slice {@link ResourceLedger} (from the resumed checkpoint), so the run
     *  loop can size this slice's effective budget = `min(maxCostUsd, remaining)`. Undefined on the first slice
     *  (or a non-resource task). */
    resourceLedger?: ResourceLedger;
    /** A mutable holder the run loop populates (right after `stats` exists) so the
     *  human/irreversible_ask suspend can debit THIS leg's live cumulative spend onto the durable approval
     *  ledger it attaches (the resource-slice path passes `sliceSpend` explicitly; this event-driven gate has
     *  no such arg, so it reads the live spend here). Read at suspend time; absent ⇒ this leg's spend is not
     *  debited (the prior ledger still rides for the cross-leg READ). */
    liveSpendRef: {
        get?: () => {
            costMicroUsd: number;
            tokens: number;
            turns: number;
            walltimeMs: number;
        };
    };
    /** The per-task human-review accumulator (synchronous `resolveAsk` waits this leg + the carried
     *  prior-leg burden seeded from the resumed checkpoint). The run loop ADDS the durable-resume latency
     *  (`now() − cp.suspendedAt`) on a resume, then surfaces it as `stats.humanReview` at assembly (omitted when
   *  empty). **Budget-EXCLUDED** — never folded into cost/the budget gate. */
    /** borrowed-mutable — the hands' read-state map (the SAME `Map` object the toolkit and the context
     *  lane hold; absent without hands). Read by the provider-rejection recovery arm so its stale-result
     *  clears retire the entries those transmissions fed, through the same landing the frontier pass uses. */
    readFileState?: import("../../tools/fs/safety.js").ReadFileState;
    humanReviewRef: {
        count: number;
        totalWaitMs: number;
        gates: HumanReviewGateRow[];
    };
    /** The injectable wall-clock the run loop uses for the durable-resume human-review latency
     *  (`humanLatencyMs = now() − cp.suspendedAt`), so it reads the SAME clock as the suspend-side `suspendedAt`. */
    now: () => number;
    /** The FULL resolved tool list (real tools, never deferred placeholders) so the
     *  resume engine can execute a previously-suspended pending tool call directly (it bypasses the gate —
     *  the human already adjudicated it). Same wrapping (offload + ctx) the harness runs with.
     *
     *  ONE array, mutated in place; identity is the contract (every closure that resolves a name at run time
     *  reads this exact array). The array is PRIVATE to the `RosterBuilder` (tool-roster.ts) —
     *  every station below writes through its verbs (`mount` / `mountAll` / `unmountWhere` / `replaceAt` /
     *  `replaceDomain`) and this field is the builder's read face. WRITER TABLE — the only
     *  stations that write it, in prepare order:
     *   · caps-and-workflow — MINTS it (`spec.tools` through the ctx wrap + the large-result wrapper), then pushes
     *     ReportBlocked (enableBlockedReport ≠ false), ReportFindings (no caller tool of that name/alias),
     *     ExitPlanMode + EnterPlanMode (enablePlanMode ∧ the plan-review face), Workflow (self-orchestration active
     *     ∧ runner self ∧ hard sandbox ∧ governance baseline);
     *   · the driver — pushes StructuredOutput (outputSchema);
     *   · protocol-tools — pushes the MCP tools (through the remote wrapper), RefreshMcpTools (servers declared),
     *     the A2A tools; at RUN time its refresh closure splices a server's name-prefix domain out and back in;
     *   · hands mount — pushes the hands band (env wired);
     *   · delegation-surface — pushes TaskOutput/TaskStop (background ∨ workflow door), SendMessage (door ∨
     *     parentNotify ∨ peer lane, ∧ runner self ∧ no shadow), AgentTranscript (door ∧ runner self ∧ no shadow),
     *     ListAgents (peer lane ∧ built-in SendMessage ∧ mountable), Monitor (background), EnterWorktree +
     *     ExitWorktree (tracked cwd), ReadToolResult (offload store);
     *   · question-face — pushes AskUserQuestion (the mount decision);
     *   · the driver — pushes CronCreate/CronDelete/CronList (write hands; inert without a daemon);
     *   · lsp — pushes the LSP tool (manager wired);
     *   · project-context — pushes Skill (manifest), the shared-memory pair and the memory-engine tools;
     *   · defer-classify — SPLICES OUT the excluded names and the retracted pair (the only removals at prepare);
     *   · memory-engine-session — REPLACES entries in place with the content-origin wrappers when it arms (a mounted
     *     memory session or a recorder channel); the refresh seam re-runs the same pass through `contentOriginWrapRef`
     *     after a re-splice (identity-tracked, so a wrapper is never wrapped again).
     *  tool-disclosure and listings only READ it (`harnessTools` is a derived list; placeholders never enter here). */
    tools: readonly AgentTool[];
    /**
     * This leg's minted TOOL ROSTER (the same object `wiringManifest.tools` carries):
     * one row per mounted instance in wire order. Minted ONCE at the prompt-assembly station (the same
     * pass that projects the prompt manifest's tool rows, so the two agree by construction); the park
     * station reads the gated call's identity off it, the exemption probe hands it to the host.
     */
    toolRoster: import("../tool-roster.js").ToolRoster;
    /**
     * The run-time delta seat: every roster change after prepare (the RefreshMcpTools
     * seam) re-mints the roster here and publishes a `ToolRosterDelta`. The run loop subscribes at the
     * point it emits `wiring_manifest` and forwards each delta as a `tool_roster_delta` event; until it
     * does, deltas are buffered in order.
     */
    toolRosterDeltas: import("../tool-roster.js").ToolRosterDeltaSeat;
    /**
     * The leg's `tool_end.structured` projector: the engine card set ∪ the caller rows' declared
     * `renderHints.resultCards`; an undeclared `details.type` is dropped and announced ONCE per leg
     * (`config.tool_card_undeclared`). The ONE card port: every tool_end body projection (the live loop, the resumed
     * batch, the reconcile-recovered orphan) takes it as a parameter; no module-level card set exists to fall back to.
     */
    structuredProjector: (result: unknown) => unknown;
    /** Name→effect map for every tool this task can call. Used by the abort-path orphan
   *  reconcile to make interrupted tool_results effect-aware (read/idempotent = safe to
     *  repeat; write/unknown = outcome unknown). Unknown names default to `write` (conservative). */
    toolEffects: Map<string, ToolEffect>;
    /** scan-1/A5 — the orphans the WAKE/CRASH reconcile closed while preparing this run (empty on a fresh or
     *  clean session). The run loop replays them onto the stream at run open as synthetic `tool_end` +
     *  `message_committed` frames, the same pair the live-abort leg mints at run close: a previous PROCESS
     *  died holding those calls, so this run's stream is the only place a consumer can ever learn they ended.
     *  Not the live-abort set — that one is reconciled inside the run and never passes through here. */
    wakeRecovered: RecoveredOrphan[];
    /** Fixed per-request prompt overhead (system prompt + tool schemas, ≈chars/4 tokens). Fed to
     *  `maybeCompact.overheadTokens` so the compaction trigger stays accurate in the anchor-less
     *  regime (custom Brains that don't report usage). */
    promptOverheadTokens: number;
    /** Accessor for the MAIN lane's latest real brain request (systemPrompt/messages/
     *  tools snapshot, recorded at the harness's provider seam just before each main-loop call). Fed
     *  to `maybeCompact.forkContext` on all three compaction lanes so the summary request can FORK the
     *  already-paid main prefix (CC form). `undefined` until the run's first main-loop call. Summary
     *  calls themselves never pass through the recorded seam (they ride the separate compaction-brain
     *  wrapper), so the snapshot is never polluted by a summary request. */
    lastBrainContext: () => CompactionForkContext | undefined;
    /** Narrow workspace reader for compaction working-file attachments (LONGRUN-2): reads a task file
     *  via the SAME env the hands ran against (so remote/k8s/E2B tasks read the container's tree, not
     *  the control plane's). Present only when the hands are enabled — without an env there is no
     *  workspace to re-read. null = unreadable (deleted/binary/transport error); callers skip it.
     *  `{ withheld }` = the target matches the sensitive-path read deny list — the
     *  attachment is deliberately withheld and the consumer must SAY so (an annotation, never a silent
     *  skip: a silently missing attachment reads as "file gone", which is a different fact). */
    readTaskFile?: (path: string) => Promise<string | null | {
        withheld: {
            pattern: string;
        };
    }>;
    /** CC post-compact restore parity (2026-07-03): the task's READ files, most recent
     *  first (from the hands' readFileState `lastReadAt` stamps). The compaction working-file
     *  attachment prefers this over the modified set — CC restores what the model RECENTLY READ,
     *  including untouched reference files. Present only with hands (same gate as readTaskFile).
     *  Seeded entries are excluded — their content lives in the system-prompt lane, which
     *  compaction never touches, so re-attaching them into the summary would be a duplicate. */
    recentlyReadFiles?: () => string[];
    /** Canonicalizes a raw (often relative, model-typed) path the
     *  SAME way the hands toolkit does (`resolveKey` against the same containment root `readTaskFile`
     *  uses), so `maybeCompact`'s kept-tail/instruction-source exclusions compare paths in the SAME
     *  coordinate `recentlyReadFiles()` already uses — without this a plain string match silently never
     *  fires for the common relative-vs-canonical case. Present only with hands (same gate as
     *  readTaskFile); absent there is no containment root to resolve against, and every path source is
     *  already in the same raw coordinate anyway. */
    normalizeAttachmentPath?: (raw: string) => Promise<string>;
    /** Recognizes the hands Read tool's own dedup-stub markers
     *  ({@link isReadDedupStubResult}), so `maybeCompact`'s kept-tail scan can tell a SUBSTANTIVE Read
     *  result from a stub hit whose original full transmission may already be summarized away. Present
     *  only with hands (same gate as readTaskFile). */
    isDedupStubResult?: (resultText: string) => boolean;
    /** Wired to `MaybeCompactOptions.onApplied` by every compaction lane (CC 220 clears readFileState at its
     *  compaction landing site, @388663). Drops the non-seeded read-state entries
     *  (the summary just replaced the Read results the dedup stubs point at) and re-registers the files
     *  that were re-attached WHOLE. Present only with hands (same gate as readTaskFile).
     *  `preserveReadState` carries the kept-tail-visible files that were
     *  deliberately skipped for re-attachment — their existing entry must survive the clear too (same
     *  reasoning as a seeded entry: the model's view of the file did not change).
     *
     *  SCOPE — read "every compaction lane" as exactly that and no wider. The request-layer
     *  stale-result pass takes a Read body off the wire with no compaction at all, and retires its own
     *  entries at its own landing site (`prepare-context-lane.ts`'s `landStaleResultClears`) through the
     *  SAME function this seat calls, on its `tool-results-cleared` arm: ONE retirement mechanism, two
     *  ways for content to leave. Taken as a claim about every SHRINK mechanism this sentence was true
     *  of the lanes it named and silently false of the one it did not. */
    onCompactionApplied?: (attachedComplete: ReadonlyArray<{
        path: string;
        content: string;
    }>, preserveReadState?: ReadonlyArray<string>) => void;
    /** The live diagnostics lane (present only when the gate passed — manager w/ registry +
     *  write hands + not opted out). `registry` is drained by runtask at turn boundaries; `nudge` is
     *  called (fire-and-forget) after each successful edit/write so the language server re-analyzes. */
    lspDiagnostics?: {
        registry: import("../lsp-diagnostics.js").LspDiagnosticsRegistry;
        nudge: (rawPath: string) => void;
        /** This run's key into the registry's delivered set (the registry is DEPLOYMENT-scoped — one per
         *  `NodeLspManager` — so "already delivered" has to be qualified by run). runtask passes it to every
         *  `drain` and calls `releaseRun` with it at the run's terminal. */
        runIdent: string;
    };
    /** The ctx `requestStopAfterTurn` primitive's run-local mirror (one-way, set when a tool asked for a
     *  clean stop after the current turn). The harness latch itself is private to the loop; the turn
     *  boundary reads THIS to know that no next request follows — a boundary bundle collected there
     *  would be steered into a queue the loop never drains, and every one-shot announcement in it
     *  (listing frames, the once-per-session MCP arms) would be committed as delivered while it was not. */
    stopRequestedRef: {
        current: boolean;
    };
    /** Whether prepare's ONE read of the branch's announced snapshot succeeded (see
     *  `AnnounceOnceLedger.recovered`). False ⇒ the run loop must not flush the once-per-session arms
     *  as a whole-snapshot append on this leg (it would replace the branch's record with a partial
     *  one); the leg's announcements then simply repeat next run. */
    announcedSnapshotRecovered: boolean;
    /** The date-flip detector seam: `legDate` = the date frozen
     *  into this leg's system prefix; `today()` = the boundary-time LOCAL date (user-zone-bound closure).
     *  The run loop feeds both to the `date_change` attachment producer; the prefix itself never
     *  re-renders mid-leg (cache preservation, CC parity). Present only when the prompt carries a date.
     *  `anchor` = the session's date anchor as prepare read it (undefined ⇒ the branch was unreadable
     *  and the leg writes none); the settle lane persists it when it is new or the announced date moved. */
    dateChange?: {
        legDate: string;
        today: () => string;
        anchor: DateAnchorRead | undefined;
    };
    /** [A2] C-4 (design-A §4) — the loadProjectMemory snapshot's declared instruction sources
     *  (`ProjectMemoryLoad.instructionSources`): the probe input AND the lane's per-path baseline
     *  hashes for the run loop's `instructions_change` attachment. Present only when the deployment's
     *  loader declared a non-empty list; the lane additionally requires
     *  `RunnerDeps.probeInstructionSources` — either absent ⇒ zero probe calls, byte-identical. */
    instructionSources?: ReadonlyArray<{
        path: string;
        contentHash: string | null;
    }>;
    /** The RAW instruction-file text this leg's `loadProjectMemory` returned
     *  (`ProjectMemoryLoad.content`), for the compaction lanes' `contextInstructionFiles` seat.
     *
     *  Which bytes, exactly: the string prepare-task hands `composeMemoryBlock(projectMem, "project")`
     *  — pre-compose, unfenced, straight from the host seam. The two neighbours are deliberately NOT
     *  candidates: the assembled `memoryBlock` carries the memory ENGINE's layers (bytes the model
     *  itself authored in-band this session through the memory tools) and the seat's contract is
     *  host/systemPrompt tier; the COMPOSED project block would arrive at a mint that neutralizes the
     *  whole authority family, `user_memory`/`scope` included, so the engine would rewrite the tags of
     *  its own composition. Raw text in, one fence at the mint.
     *
     *  Present only when the deployment wired the loader AND it answered non-blank — the same
     *  predicate that composes the project layer, so the summarizer's copy and the main prompt's copy
     *  can never disagree about whether this deployment HAS instruction files. Absent ⇒ every
     *  compaction lane omits the key entirely. */
    projectInstructionContent?: string;
    /** The `workflow_size_guideline_change` lane's seam, the {@link dateChange} shape one lane over:
     *  `legGuideline` = the RESOLVED guideline this leg's Workflow tool card was built with (the card is
     *  a per-mount snapshot, so it never re-renders mid-leg — the model learns a retune from the tail
     *  frame instead); `current()` = the boundary-time resolved guideline, read live off
     *  `RunnerDeps.workflowLimits` so a deployment retuning it mid-run is observable without a re-prepare.
     *  Present only when the Workflow tool is actually MOUNTED — a run with no workflow card has no
     *  guideline to change. The direct-construction face (`RunWorkflowToolDeps.sizeGuideline`) is out of
     *  scope by construction: this mount never passes it, so the deps channel is the lane's only source. */
    workflowSizeGuideline?: {
        legGuideline: WorkflowSizeGuideline;
        current: () => WorkflowSizeGuideline;
    };
    /** Boundary-time external-change scan over the ≤`maxFiles` most-recently-READ
     *  files. Stats each via `env.fileInfo` and reports paths whose `mtimeMs` moved past the recorded
     *  `lastReadAt` + 2s epsilon (CC getChangedFiles shape: readFileState needs NO new field; the agent's
     *  own write-backs refresh `lastReadAt`, so self-edits are immune). ENOENT evicts the readFileState
     *  entry (CC evict-only-on-ENOENT — transient stat failures skip, never evict) and is echoed in
     *  `evicted` so the caller drops its per-path dedup state in lockstep (LOW-9). Present only with
     *  hands; the run loop calls it ONLY when `spec.attachments.changedFiles` opted in (OFF ⇒ zero stat). */
    detectExternalChanges?: (maxFiles: number) => Promise<{
        changed: Array<{
            path: string;
            mtimeMs: number;
        }>;
        evicted: string[];
    }>;
    /** G1 通告层 — deferred tools MATERIALIZED (rematerialized) but not yet announced at a
     *  turn boundary. Appended by the rematerialize diff (newly-activated names only — the announced set
     *  is seeded with prepare-time actives INCLUDING resume-reseeded ones, so a resume never replays);
     *  DRAINED by the run loop only when the `tools_delta` attachment actually survived the byte cap.
   *  Present when the task has deferred tools at all — OR when a declared MCP server failed
     *  to connect, so the failure is announceable even on a task whose every MCP tool went missing with it.
     *
     *  The same frame's MCP arms, filled by the SAME rematerialize seam / materialize-time
     *  projection and drained by the same intact-survival predicate:
     *  - `pendingRemoved`: previously-ANNOUNCED tool names that left the live roster (a RefreshMcpTools
     *    re-splice whose server withdrew them). CC `removedNames`.
     *  - `pendingReadded`: names in `pendingRemoved`'s history that came back on a later refresh. CC
     *    `readdedNames`.
     *  - `pendingFailed`: declared servers whose connect failed at materialize (`statuses`), name/error
     *    neutralized + bounded at intake, MINUS the pairs this session's record already announced (CC
     *    `failedMcpServers` compared against the previous attachment — a serving layer runs prepare once
     *    per user message, so the per-run set alone re-announced every message). */
    toolsDeltaRef?: {
        pending: string[];
        pendingRemoved: string[];
        pendingReadded: string[];
        pendingFailed: Array<{
            name: string;
            error?: string;
        }>;
    };
    /** G1 通告层续批 (CC `agent_listing_delta` parity) — the mounted delegation tool's agent-type roster
     *  (read off `ToolSpec.agentListing`, filled by createSubagentTool), plus the tool's mounted name for
     *  the CC-verbatim headers. [c209-C]: this seam is now the roster's ONLY model-facing carrier —
     *  the run loop delivers the initial full frame ON THE FIRST USER TURN (prompt-adjacent) and
     *  boundary drift deltas after. `models` (Q4, read off `ToolSpec.agentModels`) rides the initial
     *  frame's tail line. `seedAnnounced` = a durable-resume leg: the run loop seeds the producer's
     *  announced set from the checkpoint's `announcedListings.agents` name-set when present (drift
     *  since suspend IS delta-announced), else the MED-3② transcript probe (delivered ⇒ seeded as
     *  the current entries; unconfirmed ⇒ the resume leg re-announces the initial listing). Present
     *  only when such a tool is mounted AND its roster is non-empty. */
    agentListing?: {
        entries: ReadonlyArray<{
            name: string;
            description: string;
        }>;
        toolName: string;
        seedAnnounced: boolean;
        models?: readonly string[];
        budget: ResolvedSkillListingBudget;
    };
    /** [c209-C] skills_listing counterpart of {@link agentListing} — the normalized skills METADATA
     *  projection (names/descriptions/attachment paths, never bodies; the exact list `createSkillTool`
     *  serves). Initial full `<skills>` frame on the first user turn, drift deltas at boundaries,
     *  same seeding contract. Present only when `spec.skills` is non-empty (post 1MB-gate). */
    skillsListing?: {
        entries: ReadonlyArray<SkillListingEntry>;
        seedAnnounced: boolean;
        budget: ResolvedSkillListingBudget;
    };
    /** [c209-C] Q5 — the run loop's live mirror of the listing frames' announced NAME-SETS (updated at
     *  every commit point: first-frame delivery, intact boundary survival, resume seed). Read by the
     *  suspend-time checkpoint serializer (`CheckpointState.announcedListings`) so a resume leg can
     *  diff the then-current roster/skills against what the model actually saw. Always present (empty
     *  object when nothing announced / no listing faces mounted). [c209-C] R2 C7: `models` mirrors the
     *  ANNOUNCED model catalog the same way (advanced only when a committed frame carried one), so a
     *  catalog change across a suspend is re-announced as a "Models available…" drift line.
     *  `mcpFailed` / `advisories` are the once-per-SESSION arms of the same snapshot (see
     *  announce-once-ledger.ts): seeded at prepare from the branch's record, advanced by the run loop
     *  on intact delivery of the tools_delta `failedServers` arm / the mcp_dropped_tools frame. */
    announcedListingsRef: AnnouncedListingSnapshotInput;
    /** The frame-receipt ledger — the ONE commit point of every engine frame that rides a user message
     *  (the git-status frame, the agent and skills listing frames). Always present; per leg. */
    frameReceipts: FrameReceiptLedger;
    /** env-tail migration — the git-status frame lane's run-local state: this leg's
     *  resolved frame (probe outcome rendered + hashed at prepare), the announced `(kind, hash)`
     *  mirror the checkpoint serializer reads, the trim-protection slot the request-build context
     *  handler matches on, and the re-assert closure the compaction landing + boundary retry call.
     *  Always present (empty object on a hands-less leg — the lane is then out of scope). */
    gitStatusRef: GitStatusLaneRef;
    /** The posture composition's run-local half (the newest carrier's protection slot; the row last composed for). */
    postureFrameRef: import("../auto-mode-directive.js").PostureFrameRef;
    /** G1 通告层 — narrow post-compact getter over the process task registry: THIS run's visible
     *  pending/running background tasks (same owner/scope/session identity the TaskOutput/TaskStop tools
     *  use), as a bounded display projection (id/description/status — never handles/env/abort). Called by
     *  the run loop ONLY when `spec.attachments.backgroundTasks` opted in AND a compaction just landed. */
    listBackgroundTasks: () => Array<{
        id: string;
        description?: string;
        status: string;
    }>;
    /** The parent-run subagent-retain ledger (present ONLY when `spec.retainSubagentSessions`
     *  is enabled). The Runner disposes it (abort in-flight resumes + unpin + release every retained child
     *  session) in the task's terminal `finally` — same UNCONDITIONAL posture as the background-agent reap:
     *  retain is NOT durable (a suspend leg's in-memory ledger cannot survive a re-prepare), so releasing on
     *  every exit path is hygiene, never a loss. */
    subagentRetain?: SubagentRetainLedger;
    /** Run-scoped consecutive-`summaryProvider`-reuse counter, OWNED by the Runner and
     *  SHARED across both compaction call sites (within-task turn boundary + `finish()`), so the
     *  `maxConsecutiveProviderReuse` drift guard is enforced over the whole task — incremented when a
     *  compaction reused the provider's summary, reset to 0 on a real (LLM) summary. */
    compactionReuseRef: {
        consecutive: number;
    };
    /** Trim→compaction pressure propagation (16k live sawtooth root cause): set by the
     *  context hook when `trimToBudget` actually DROPPED messages from a request view (request-only trim
     *  + usage-anchor mismatch deflates the next boundary's estimate → the trigger and floor are both
     *  deceived → full-size request spikes alternate with trimmed troughs). The next turn boundary's
     *  `maybeCompact` consumes it as `force: true` (bypasses the auto threshold AND the §25.2 anti-thrash
     *  floor — "the request layer was forced to drop history" is direct evidence compaction is overdue).
     *  One-shot: cleared on consumption; a failed compaction does NOT re-arm it (existing breaker path).
     *  Content-only clears (`clearStaleToolResults`) never set it — only real message drops do. */
    trimPressureRef: {
        droppedMessages: boolean;
    };
    /** The microCompact machine state this run: the selected clearing
     *  machine, the cleared-projection ledger (request-view application, durable decisions — see
     *  `context-edit.ts`'s ledger note; per-run in-memory, so durable resume / `resumeAt` rebuilds
     *  start EMPTY by construction), the last request's projection seat (what the provider actually
     *  saw — the MC-R rejection arm computes its candidates and savings on THIS view, never on the
     *  raw session rebuild), the MC-R knob, and the slice-3 arm-B seat. */
    microCompact: PreparedMicroCompact;
}
/** See {@link Prepared.microCompact}. */
export interface PreparedMicroCompact {
    /** The frontier-machine selection — `"off"` = no proactive frontier clearing (the machine
     *  instead gets its one blocking-point shot, slice-3 arm A). */
    machine: MicroCompactMachine;
    /** MC-R: one-shot clear-and-retry on a provider input-too-long rejection.
     *  Default true since the slice-3 flip. */
    clearOnRejection: boolean;
    /** The in-turn forced-compaction seat: runtask wires a closure
     *  that runs the SAME forced-compaction pass the prompt-too-long recovery uses (gates included)
     *  and answers whether a compaction landed in the session. The context hook calls it when the
     *  pre-send estimate breaks the guard budget and then returns `adoptSessionRebuild` so the
     *  harness adopts the reduced transcript. `signal` is the TURN-scoped abort of the request
     *  build (r3): a turn interrupt must be able to cut the summary call short instead of waiting
     *  it out. `anchoredEstimate` is the chain's own trigger coordinate — the seat consults the
     *  §25.2 anti-thrash floor against it (an ineffective landing must not be repeated per request
     *  build; the chain's arm C owns the bounded fallback). Unwired (pure-prepare callers) ⇒ arm B
     *  declines and the chain falls to the trim last resort — same posture as a compaction-disabled
     *  run. */
    inTurnCompactionRef: {
        current?: (signal?: AbortSignal, anchoredEstimate?: number) => Promise<boolean>;
    };
    ledger: ClearedProjectionLedger;
    projectionRef: {
        current?: {
            /** The FINAL projected view of the last provider request (post trim/sweep). */
            messages: AgentMessage[];
            /** Occurrence coordinates of that view (object-identity first, unambiguous-group fallback). */
            keyOf: OccurrenceIndex["keyOf"];
        };
    };
    /** The same offload persist seat the frontier machine uses (write-once, idempotent), so MC-R
     *  clears compose identical markers. Absent when no offload store is configured. */
    offloadPersist?: (toolCallId: string, fullText: string) => string;
}
/**
 * Resume inputs threaded into {@link prepareTask} to continue a suspended task. The Runner
 * builds it from the persisted {@link Checkpoint}: rewind the branch to the suspension leaf, skip the
 * suspended batch during wake-reconcile, and re-seed the §4.bis per-task correctness state so the
 * resumed run is in the **same state space** it suspended in.
 */
export interface PrepareResume {
    /** The session leaf to rewind to (the suspension point) BEFORE reconcile — discards the abort's
     *  off-branch "Operation aborted" writes so the resume engine resolves the pending batch cleanly. */
    leafId: string;
    /** Batch tool-call ids of the suspended turn — wake-reconcile SKIPS these (they are resumed, not
     *  crash-interrupted; closing them with `[INTERRUPTED]` would DESTROY the suspended batch, §15.2 #7). */
    suspendedBatch: ReadonlySet<string>;
    /** The §4.bis correctness-state snapshot to re-seed (activeTools / outputRef / nestedStats /
     *  readFileState). */
    seed: CheckpointState;
    /** The decision this resume carries (the gate word and, for a review, approve / edit / reject) — read by the mode seed
     *  (`modeRequestAfterResume`: an approved or edited `plan_review` exits a recorded plan to its pre-plan word). Absent on a prepare that resumes nothing. */
    outcome?: ResumeRun["outcome"];
    /** How many times this task already suspended (the resumed checkpoint's
     *  `suspendCount`). The next suspend mints `priorSuspendCount + 1`; past `maxSuspends` it fails
     *  (`suspend.loop`) instead of re-suspending. Absent/0 ⇒ no prior suspends. */
    priorSuspendCount?: number;
    /** The cross-slice {@link ResourceLedger} carried by the resumed `resource_limit`
     *  checkpoint (cumulative spend + the frozen human totals). The next slice's effective budget is
     *  `min(maxCostUsd, totalBudget − spent)`, and its own suspend debits onto this. Absent ⇒ the first slice. */
    priorLedger?: ResourceLedger;
    /** The accumulated human-review burden carried by the resumed checkpoint
     *  ({@link import("../checkpoint-store.js").Checkpoint.humanReview}) — the gates resolved up to and including
     *  the suspend BEFORE this one. Seeds the per-task accumulator so the resumed leg ADDS this suspend's own
     *  latency (`now() − cp.suspendedAt`) on top, reporting the WHOLE chain's burden. Absent ⇒ no prior human time. */
    priorHumanReview?: {
        count: number;
        totalWaitMs: number;
        gates: HumanReviewGateRow[];
    };
    /** When the suspend ran with a remote workspace, the {@link CheckpointState.workspaceHandle}
     *  to restore — prepare rebuilds the per-task env via `deps.executionEnvFactory` then `resumeVM(snapshotId)`
     *  + `postResumeInit()` (instead of running on a fresh, empty env). Threaded HERE (not via `ResumeTaskConfig`)
     *  so the factory stays a deployment-level `RunnerDeps` capability and never pollutes `TaskSpec` — preserving
     *  the "untrusted caller can't inject an env" red line ({@link import("../remote-env.js").ExecutionEnvFactory}'s
   *  own contract states it: "Lives on `RunnerDeps` (deployment-level) — NOT on `TaskSpec`"). */
    workspaceHandle?: import("../remote-env.js").WorkspaceHandle;
    /**
     * The call id of the CONTENT ask whose answer an operator's approval was spent on, when
     * that is what this leg redeems. Scoped as an id, not a leg-wide flag: an `unavailable` outcome on
     * THAT call must become a coded failure (the approval bought a question nobody answered), while a NEW
     * question raised later on the same leg had no approval spent on it and keeps the ordinary
     * continuation. Absent when the resumed pending action is not a question.
     */
    redeemedContentAskCallId?: string;
    /** Digest of that question's batch — the id alone can repeat, so the pairing is what keeps a LATER
     *  question from inheriting the claim that an operator approved it. */
    redeemedContentAskQuestionsHash?: string;
    /** true iff this resume will EXECUTE an approved pending tool (`tool_approval`
     *  checkpoint × an `allow` winner). The divergent-restore fail-closed guard keys on THIS, not on the
     *  batch being non-empty: a deny winner executes nothing, so a workspace-root divergence must not
     *  wedge the refusal (it proceeds under the path-state rebase and records the deny). */
    executesApprovedAction?: boolean;
}
/**
 * Parent effective-policy inheritance (tighten-only, {@link RunInternals.inheritedGate}): the spawning
 * chain's already-evaluated FINAL gate, split into two halves —
 *
 * - **Data half** (`ancestorRules`, `shellGate`, `permissionMode`): serializable snapshots. Each ancestor's per-session
 *   permission rules ride as (sessionId, principal, rev, rules) so the child's prepare can re-read them
 *   LIVE (rev-monotonic — a lagging replica never loosens the snapshot) and re-compile them against the
 *   CHILD's own env/root/effects as extra deny-narrowing layers. `shellGate` is the operator floor
 *   (`"always"`): a child inherits it and cannot lift it. `permissionMode` is the ceiling a child's own
 *   declaration is clamped under.
 * - **Opaque half** (`parentConstraints`): each ancestor's live caller `ToolPolicy` plus its FROZEN
 *   `onAsk`, ancestors first — and, for a hook-wired ancestor, a SECOND entry: its PreToolUse screening
 *   face folded into a `ToolPolicy` (`createPreToolUseConstraintPolicy`), carrying its own frozen
 *   approver/mandate/env axes (the `preToolUse`/`hookEnv` fields below). Folded AFTER the child's own caller policy and BEFORE the trailing
 *   deny-narrowing re-check, each wrapper resolving its own `ask` via the ancestor's frozen approver —
 *   so a parent `ask` never widens into a child auto-allow, and a parent `allow{updatedInput}` rewrite
 *   is still re-checked by the child's narrowing layers. NOT serializable: a durable resume must
 *   re-supply it via `resumeStream(..., internals)` (see {@link CheckpointState.inheritedGate}).
 *
 * Chain assembly is single-sited in prepareTask's ctx injection (`inheritedGateForChildren`): upstream
 * chain + THIS task's own contribution (its session-rule snapshot, its RESOLVED caller policy —
 * `spec.toolPolicy ?? deps.toolPolicy`, the same slot its own gate enforces — + frozen onAsk, its
 * effective shellGate, and — when this task resolved a PreToolUse hook — a screening entry for that
 * hook, deduped by full installation so a deps-level hook is consulted once per call at any depth).
 * Depth-N delegation is therefore a linear chain, each layer evaluated at most once per tool call.
 */
export interface InheritedGate {
    /** Serializable ancestor session-rule snapshots (data half), ancestors first. */
    ancestorRules?: ReadonlyArray<{
        sessionId: string;
        principal?: string;
        rev: number;
        gen?: number;
        rules: SessionPermissionRules;
    }>;
    /** The chain's shell floor (`"always"`) — a child inherits it; its own spec cannot lift it. */
    shellGate?: ShellGateDoctrine;
    /**
     * The chain's EFFECTIVE permission mode at spawn (data half, session-wide like every mode): the CEILING a
     * child's own declaration is clamped under (`TaskSpec.permissionMode` absent ⇒ inherit; declared ⇒ adopted only
     * where the rank table admits it, else folded and disclosed). Persists on the checkpoint's data half
     * (`CheckpointState.inheritedGate.permissionMode`) so a redemption in another process resumes under the same
     * declared mode — and re-judges the deployment's deny bits for its own leg.
     */
    permissionMode?: PermissionMode;
    /**
     * The parent's live mode ceiling (opaque half; never serialized): the parent leg's mode while that leg runs, its
     * SESSION's current leg after it — the child's effective mode is DERIVED from it on every read, so a parent's steer
     * reaches a running child on the child's next arbitration without a forwarding frame, and the session's next leg
     * reaches a child that outlived the leg that spawned it (a session-scoped background child). `sessionId` is the
     * parent's, the address of the user notice a folded child declaration is announced on. `hold` (the engine's own
     * ceiling): the child is re-judged at each move of the ceiling, and a fold that produces is announced as at prepare.
     */
    permissionModeCeiling?: {
        readonly current: PermissionMode;
        readonly sessionId: string;
        readonly hold?: LegModeCeiling["hold"];
    };
    /**
     * Org-memory admission freeze (ruled 2026-08-05): the spawning chain's FROZEN admitted org-scope
     * set — every org memory scope the parent actually mounted (deployment-origin + admitted request).
     * A delegated child's REQUEST-origin org scopes must be a subset (intersection ≠ request ⇒ the
     * child's prepare refuses, `memory.admission_denied`); deployment-origin scopes circumvent the freeze
     * (operator authority is deployment-wide). ALWAYS emitted by chain assembly (empty array = parent
     * mounted no org plane), so an ABSENT field discriminates an older-shape chain — which the child
     * side reads fail-closed as the empty set. Serializable (plain strings): persists on the
     * checkpoint's data half and folds seed ∩ live on resume (tighten-only).
     */
    admittedOrgScopes?: readonly string[];
    /**
     * The MONOTONIC org-governance provenance bit: `true` when any leg of this
     * tree ran with an org-admission surface configured (resolver / deployment scope list) or
     * inherited the bit. It never clears once set (a governed tree stays governed), and it counts as
     * governance evidence at the admission door EVEN when every admitted set en route is empty — a
     * governed parent's org-less child, resumed on a surface-less worker, must still refuse
     * org-shaped non-v2 scopes instead of mounting them as opaque keys. Absent on trees that never
     * had a governance surface (their opaque posture is untouched). Serializable; persists on checkpoints.
     */
    orgAdmissionGoverned?: true;
    /** Live ancestor caller-policy constraints (opaque half), ancestors first. `policy` is the ancestor's
     *  RESOLVED caller policy (`spec.toolPolicy ?? deps.toolPolicy` — the same slot the ancestor itself
     *  enforces, so a deps-level baseline is inherited too).
     *
     *  ⚖️ A-005.13 (ruled with the F-012 family, 2026-08-09): the ancestor's per-run BUDGET LEDGER does
     *  NOT travel this chain — by design, not by omission. An inherited policy's `check` receives the
     *  CHILD run's own `ToolCallRequest.budget` snapshot (the child's durable resource ledger), so a
     *  SELF-LIMITING ancestor policy ("allow N escalations, then ask") sees a fresh ledger in each
     *  delegated child rather than a continuation of the ancestor's counts. Freezing/snapshotting the
     *  ancestor ledger was considered and rejected: a frozen ledger goes stale the moment the ancestor
     *  keeps running (a stale ledger is a NEW wrongness surface, not a fix), and the durable resource
     *  ledger's identity is per-run by contract. A deployment wanting cross-delegation budget coherence
     *  carries it in its own policy state (the closure travels the chain intact — a closure-counter
     *  policy DOES aggregate across the tree, since every layer evaluates the same instance).
     *
     *  `onAsk` is the ancestor's frozen
     *  `spec.onAsk ?? deps.onAsk` — an ancestor `ask` resolves at that ancestor's own approver
     *  (headless ⇒ deny), never at the child's. `durableMandate` is frozen at chain-assembly time when the
     *  ancestor ran under a durable-approval regime that would actually PARK its own plain asks — a
     *  `forceDurableGate` entitlement, or `durableApproval` with NO live onAsk frozen (with a live approver
     *  the ancestor's own plain asks resolve synchronously there, so a descendant's ask resolves at that
     *  same frozen `onAsk` — no mandate). It is a CONSERVATIVE descendant-side mandate: whether or not a given ask would actually
     *  have parked at the ancestor (the regime may be scoped), the durable-park semantics cannot be
     *  reconstructed in a delegated child's context — so a wrapper seeing `ask` under this flag denies
     *  fail-closed (tighten-only holds) instead of resolving synchronously. */
    parentConstraints?: ReadonlyArray<{
        policy: ToolPolicy;
        onAsk?: OnAsk;
        durableMandate?: boolean;
        /** The CONTENT-ask twin of `durableMandate`, frozen from the QUESTION seat (`spec.onQuestion ??
         *  deps.onQuestion`) on the same rule: a `forceDurableGate` entitlement, or `durableApproval` with no
         *  LIVE question face frozen (absent, or the reserved resume placeholder — neither can answer). A
         *  wrapper seeing an `ask` on the question tool consults THIS bit, never the permission one: the two
         *  asks are answered by two different faces, and a run with a live `onQuestion` and no permission
         *  approver can answer a delegated question perfectly well. When it IS set the delegated question is
         *  marked unresolvable and takes the child gate's park / honest-refusal leg, exactly as an inherited
         *  permission ask does under `durableMandate`. */
        contentMandate?: boolean;
        /**
     * F-012 — the ancestor's FROZEN auto-mode classifier: the same decider instance the
         * ancestor's own gate consults for a surviving ask (`ToolGateInput.autoMode`). Without it the
         * ancestor's ask DECISION CHAIN did not travel — the wrappers resolved an inherited ask straight
         * at the frozen approver, so an ask the ancestor's classifier would have BLOCKED executed in the
         * child on the frozen approver's allow. The wrapper runs it BEFORE the frozen approver, in the
         * ancestor's own gate order (classifier → approver): `block` ⇒ deny (`decisionReason:"classifier"`),
         * `allow` ⇒ the classifier's auto-allow (the frozen approver is not consulted), anything else
         * falls through to the approver chain. Never consulted for the reserved question tool nor under a
         * durable mandate (a marked call must reach the park with no synchronous decision-maker between).
         */
        autoMode?: {
            decider: AutoModeDecider;
            /**
             * The ancestor's per-run DENIAL-LIMIT tracker, frozen beside its decider (same owner). The
             * wrapper arms count the frozen classifier's blocks on it and, at a bound, resolve the fallback
             * ask at the frozen approver instead of denying (`requiresRealApproval` set, sandbox admission
             * excluded). Live-only, like the decider: a cross-process redemption starts a fresh count.
             */
            denialTracking?: AutoModeDenialTracker;
            /**
             * The SERIALIZABLE criteria half of this classifier (assembly inputs + knobs + the
             * deployment's settings epoch), present when the arming deployment opted in
             * (`RunnerDeps.autoMode.persistArming`). A durable park records it on the chain entry so a
             * CROSS-PROCESS redemption can rebuild an equivalent decider over its own model leg instead of
             * answering `unavailable` forever; the constraint-chain digest binds it, so a resume re-supplies
             * exactly what the row carries. It never affects THIS process's decisions — the live `decider` is
             * the one every gate consults, here and on every descendant.
             */
            arming?: AutoModeArmingRecipe;
            /**
             * The ancestor's live mode ceiling (opaque, never serialized; the chain's `permissionModeCeiling`): the classifier
             * answers the ancestor's asks only while this reads `auto` — else they go to the frozen approver. Absent (a
             * re-supplied entry) ⇒ the classifier answers as recorded.
             */
            liveMode?: {
                readonly current: PermissionMode;
            };
        };
        /**
         * Set ONLY on an entry whose `policy` is the folded form of an ancestor's PreToolUse screening face
         * (issue #33 — the face used to stop at the task that installed it, so a call the ancestor had
         * refused executed one level down). It carries the raw callback for ONE purpose: identity. A
         * deployment-level face (`RunnerDeps.hooks`) is resolved by every task in the tree on its own, so
         * each descendant's gate already runs it in phase 1 — folding it AGAIN as an inherited constraint
         * would consult the same callback twice per call at depth 1 and once more per level below. Two
         * identity tests keep that at exactly one consultation per call: chain assembly does not re-append a
         * face already on the chain, and a descendant substitutes a pass-through for the entry naming the
         * very callback its own gate will run. A per-task face (`TaskSpec.hooks`) is a different function in
         * the descendant (or absent), so it folds and travels.
         *
         * Identity is the only sound test here, and it is deliberately the CONSERVATIVE one: an assembly that
         * hands each task a freshly bound wrapper over one underlying callback (the `runSpec` mux does this)
         * produces two distinct function objects, so the descendant both folds the ancestor's entry and runs
         * its own — the callback is consulted twice rather than skipped. Erring toward a second screening,
         * never toward a missed one, is what makes an identity miss a cost rather than a hole.
         */
        preToolUse?: Hooks["preToolUse"];
        /**
         * The environment this screening face was installed against — the source of the `HookEnvCapabilities`
         * handed to it, compared BY REFERENCE, `undefined` when the installation had no env face. Part of the
         * installation's identity for the same reason the frozen approver is (HIGH): a face that
         * resolves paths through `ctx.env` answers a different question in a different environment, so an
         * ancestor running against the deployment's tree and a descendant running in an isolated worktree are
         * two screenings, not one. A deployment with a per-task `executionEnvFactory` therefore has its face
         * consulted once per environment on the chain rather than once overall — which is the point: the
         * ancestor's environment is the only place the ancestor's verdict can be reproduced.
         *
         * ⚠️ RECORDED BOUNDARY (delegation provenance): the approver half of the key compares
         * through {@link askApproverIdentity}, which by its own contract answers "the same decision-maker",
         * collapsing a `withDelegationProvenance` wrapper onto the approver it forwards to. Two installations
         * whose approvers differ ONLY in the provenance they attach therefore count as one. Raw reference
         * equality is not an available alternative: a delegated child's approver is ALWAYS a fresh wrapper
         * over its parent's, so raw equality would collapse nothing and a deployment-level face would be
         * consulted once per ancestor level on every leaf call. The exposure is bounded on the side that
         * matters — the pass-through arm hands the frame to the DESCENDANT's own gate, whose approver carries
         * the descendant's own provenance (strictly more context, never less). What a chain-assembly collapse
         * can drop is one intermediate installation's provenance on an ask: an approver that DECIDES on
         * `req.delegation` would see the surviving installation's rather than both.
         */
        hookEnv?: unknown;
    }>;
}
/** The runtime feature flags the stable prompt's harness-context block gates its sections on
 *  (claim only what the task has). Spread into the assembly's StablePromptContext
 *  by the prompt-assembly phase, which re-derives the two orchestration flags against the post-exclusion roster. */
export interface PromptFeatureFlags {
    policyEnabled: boolean;
    hooksEnabled: boolean;
    isolationEnabled: boolean;
    reminderMark: string;
    readFaceOpen: boolean;
    writeFaceOpen: boolean;
    orchestrationEnabled: boolean;
    orchestrationDeferred: boolean;
    promptProfile: "simple" | "classic";
    fableMitigations: boolean;
    goalEnabled: boolean;
    awarenessEnabled: boolean;
    worktreeIsolated: boolean;
    withinTaskCompactionEnabled: boolean;
    isSubagent: boolean;
}
/**
 * A TRUSTED, run-scoped internal channel into {@link prepareTask}, set
 * ONLY by a trusted CORE caller (`runRepairLoop` via the Runner's internal `runTaskStream` arg) — NEVER from
 * a {@link TaskSpec} field (the untrusted-caller surface). It is the live-state counterpart
 * of {@link PrepareResume.seed}: where `resume.seed` re-seeds correctness state RESTORED from a checkpoint,
 * this carries the LIVE per-task state the Runner cannot otherwise see (it lives in the caller's closure).
 *
 * Originally it carried only the repair loop's live {@link RepairBundle}: `runRepairLoop` is a thin composition
 * OVER `runner.runTask`, so when an orthogonal durable suspend (resource/HITL) interleaves a repair attempt,
 * the bundle (failureTrace/diagnostics/attemptCount/oracleTier) sits in the loop's closure and was being lost
 * — the minted checkpoint serialized `repairBundle: undefined`. Threading it here lets
 * {@link prepareTask}'s `serializeCheckpointState` source the LIVE bundle so a resume re-seeds `attemptCount`
 * MONOTONICALLY. Mirrors how `nestedStats`/`resume.seed` thread trusted
 * run-scoped internals through the Runner without touching `TaskSpec`.
 *
 * @contract prepare.deps-read-stable — the `RunnerDeps`, `RunInternals` and `ExecutionEnv` handles a host
 * passes into {@link prepareTask} are READ-STABLE for the duration of the call: prepare reads them at the
 * points its phases need them, and a host that mutates them between microtasks while prepare is in flight
 * is outside the contract (the Runner is constructed over its deps; a mid-prepare rewrite is a race the host
 * made). Phase boundaries are scheduling points — an `await` on a phase that carries its own awaits adds a
 * microtask between that phase's last continuation and the driver's next read — and such a host can observe
 * them; the engine does not defend against it. The untrusted surface is {@link TaskSpec}, whose pre-first-
 * await latch is a separate, pinned guarantee (prepare-task-phase-pins T1).
 */
export interface RunInternals {
    /** The live repair bundle from a `runRepairLoop` attempt in flight (attemptCount>0). Serialized onto a
     *  checkpoint minted MID-attempt so a resume re-seeds it; undefined for any non-repair run. */
    repairBundle?: RepairBundle;
    /**
     * The ENGINE deliberately stripped the spawn turn's per-request `onQuestion`
     * face from this leg's spec (a long-lived background/retained/revived child must not hold a
     * callback torn down with the turn that spawned it). Minted ONLY by the subagent lanes'
     * `stripSpawnTurnQuestionFace` helper, PAIR-PRODUCED with the strip itself — never inferred from
     * spec shape. Consumed by the wiring manifest (`question.wired: "stripped_bg_lane"`) and by the
     * interaction-posture door (the strip is core's correct design, not a configuration lie, so an
     * `"interactive"` tree's engine-stripped legs are exempt). TRUST POSTURE (ruled 2026-08-05):
     * RunInternals is a public trusted-caller parameter, so this flag is an honesty channel, not a
     * security gate — a caller forging it is a deployment lying to itself; resource-face safety
     * stays with its own fail-closed mechanisms. INHERITS DOWN THE TREE: a stripped leg's descendants are faceless by
     * the same engine act (the trusted ctx carries it as `ToolExecuteContext.questionFaceStripped`, the Workflow mount as
     * `parentQuestionFaceStripped`), so the posture-door exemption travels with the facelessness it explains; a leg
     * that strips on its own mints the same flag.
     */
    questionFaceStripped?: true;
    /**
     * The SPAWNING run's resolved interaction posture, carried
     * into engine-built children over the trusted internals channel (never copied onto the child
     * SPEC — the §8.3 rule). The child's door resolves `spec ?? THIS ?? deps`, so a root's per-run
     * posture override governs its whole engine-built tree instead of every child falling back to
     * the deps-level default. Filled by the delegation lanes' `childInternals` and the workflow
     * spawn attribution; rides `internalsSnapshot`, so retained/revived legs keep it.
     */
    parentInteractionPosture?: "interactive" | "headless";
    /**
     * The trusted post-consume hook for the PARKED-RESUME drive:
     * called by `resumeStream` after its resolve CAS WON (the token is consumed — the master
     * arbitration is decided) and BEFORE the resumed leg starts. The parked-resume caller uses it to
     * flip the reserved row `parked→running` (guarded CAS) + adopt the live handle/lane — the ONLY
     * legal site for that flip (a pre-consume flip would let TaskStop hit the plain running arm and
     * circumvent the checkpoint arbitration entirely). A THROW here aborts the resume (propagates out of
     * `resumeStream`): the checkpoint is already consumed, so the caller's compensation
     * (`rollbackParkedClaim`) reads `resolved` and settles the honest failed/outcome-unknown terminal
     * — never a silent run without an adopted row. Deployment/trusted-caller channel only, mirroring
     * every other RunInternals field.
     */
    afterCheckpointResolve?: () => Promise<void>;
    /**
     * Parent effective-policy inheritance (tighten-only): the spawning parent's already-evaluated FINAL
     * gate, threaded into a child task so the child inherits it as ADDITIONAL constraint layers — a child
     * can only ever be narrowed by this, never widened (its own session rules / skill rows / caller policy
     * still apply in full). Filled ONLY by core delegation callers (`createSubagentTool`'s execute via
     * `ToolExecuteContext.inheritedGateForChildren`, the workflow spawn legs) — NEVER a {@link TaskSpec}
     * field (the untrusted-caller surface), mirroring `inheritedSkillGrants`'s ctx-injection posture.
     * Absent ⇒ no inheritance (a top-level task; fully backward-compatible).
     */
    inheritedGate?: InheritedGate;
    /**
     * The delegation RUNTIME-PROVENANCE channel. Minted by the spawning delegation
     * tool when the parent chain is armed (the parent mounts a memory session, or is itself recording
     * for ITS parent): `ref` is the child's monotonic aggregate — the child's recorder writes into it
     * as the run progresses (live faces read the current bits; the terminal attestation reduces from
     * it); `contentSafety` is the chain's FROZEN classification snapshot — the child may narrow it
     * with its own config, never widen. Trusted internals chain only, same posture as
     * {@link inheritedGate}. Absent ⇒ the child records nothing (its deliveries then read `unknown`,
     * and every judgment falls back to the static floor — fail-closed by construction; whether the
     * floor's verdict MARKS the judging session follows that run's deployment evidence standard,
     * {@link RunnerDeps.memoryDelegationEvidence}).
     */
    delegationProvenance?: {
        ref: {
            current: import("../memory-engine/delegation-provenance.js").DelegationProvenanceAggregate;
        };
        contentSafety: import("../memory-engine/delegation-provenance.js").DelegationContentSafety;
    };
    /**
     * The SESSION-scoped org-admission freeze for a SAME-SESSION continuation
     * that is NOT a checkpoint resume: a retained background child revived through the in-process resume
     * leg. The checkpoint plane already freezes the session's own verdict
     * ({@link InheritedGate.ownAdmittedOrgScopes}); this leg has no checkpoint, so without a carrier it
     * re-adjudicated from scratch and a resolver whose answer WIDENED between the two legs remounted a
     * tenant layer the session had already lost.
     *
     * A REF, not a value: the revival replays a spread COPY of the spawn-time internals, so a plain field
     * could only ever carry the value that existed at spawn (always `undefined`). The prepare reads
     * `current` as its `priorOwnVerdict` — intersected with any checkpoint seed, so the fold can only ever
     * narrow — and writes this leg's own verdict back, which is ⊆ the prior one by construction.
     *
     * TRUSTED internal, filled by `createSubagentTool`'s `childInternals` (one ref per spawned child, never
     * shared across generations — a child's own children build their own). Absent ⇒ no session freeze from
     * this channel, byte-identical to a deployment with no governance surface.
     */
    ownOrgAdmissionRef?: {
        current: import("../memory-admission.js").OwnOrgAdmissionVerdict | undefined;
    };
    /**
     * This run's LATE-BOUND peer-identity carrier (a REF, same family and reason as
     * {@link ownOrgAdmissionRef}: revival replays a spread copy of spawn-time internals, and the axes
     * a run wears are born at different points — a* handle at registration, session/task at prepare's
     * session acquisition). Created at internals assembly (delegation lanes; runtask normalizes one
     * in for a top-level run); axes recorded through the single monotonic `addAxis`; the canonical
     * key freezes at the first recorded axis (h→s→t by call-site ordering). Read by the SendMessage
     * mount (sender key + hop token) and paired into children as {@link parentPeerRef}.
     */
    peerSelfRef?: import("../../agents/peer-admission.js").PeerSelfRef;
    /**
     * This run's INBOUND peer-chain ref: overwritten at the harness consumption boundary
     * with the chain of the peer message the model just consumed (enqueue-time writes would leak a
     * not-yet-seen message's chain into outbound sends); seeded by the L3/L4 wake legs; empty
     * otherwise. Not checkpointed in v1 (recorded honest boundary: a cross-process resume forwards
     * from an empty chain — fail-open on loop suppression only).
     */
    peerInboundChainRef?: import("../../agents/peer-admission.js").PeerInboundChainRef;
    /**
     * The PARENT run's peer-identity ref, PAIR-MINTED with {@link parentNotify} at every
     * trust point that binds the uplink callback (spawn assembly, the resume face's replacement): the
     * callback is opaque and the parent AXES deliberately keep the original spawner on the revive arm
     * while the callback points at the WAKER — so the uplink's admission identity can only travel
     * with the binding itself. Consumed by the SendMessage mount as `uplinkRecipient`.
     */
    parentPeerRef?: import("../../agents/peer-admission.js").PeerSelfRef;
    /**
     * 🔴 The workflow **nesting depth** for this run, a TRUSTED
     * cross-process channel (worker/script can NEVER set it — it is not a {@link TaskSpec} field nor a
     * `run_workflow` tool param). When a deployment initiates a workflow on behalf of a parent run that is
     * itself inside a workflow (e.g. service's `/v1/workflows`), it threads `workflowDepth = parentDepth + 1`
     * into `startWorkflow`/`runWorkflow` so the one-level nesting guard fires across the process boundary.
     * In-process nesting needs nothing here — the engine's `AsyncLocalStorage` propagates depth automatically.
     * Mirrors how `repairBundle`/`inheritedSkillGrants` thread trusted run-scoped internals the Runner cannot
     * see from `spec`. Consumed by the `run_workflow` tool wiring (S8c), not by `prepareTask` itself.
     */
    workflowDepth?: number;
    /**
     * The host's DECISIONS for parked workflow-agent rows (`wa*` rows with `status:"parked"`), a TRUSTED
     * run channel like `workflowDepth` (never a `TaskSpec` field, never a tool argument — a model cannot decide
     * an approval). A host that decided a workflow child's parked checkpoint launches the run that re-invokes
     * `Workflow({resumeFromRunId})` with the decision here; the Workflow tool applies the entries naming the
     * resumed run (`RunWorkflowOptions.parkedResume`) and the resume drives the parked child on instead of
     * re-parking. `inheritedGate` is the cross-process re-supply of the checkpoint's opaque parent constraints
     * (the same seat the background revival's `reviveClaim.parkedResume` takes). Consumed by the `run_workflow`
     * tool wiring, not by `prepareTask` itself.
     */
    workflowParkedResume?: ReadonlyArray<{
        runId: string;
        token: CheckpointToken;
        outcome?: ResumeOutcome;
        inheritedGate?: InheritedGate;
    }>;
    /**
     * Set ONLY by the Agent tool's fork route (`Agent(subagent_type:"fork")`, a core caller) on the
     * child it spawns: this run IS a forked child. `prepareTask` threads it to tool ctx as `insideFork` so the
     * child's own Agent tool refuses a nested fork (nesting guard — mirrors CC's "fork is not available inside a
     * forked worker"; a fork can still delegate via `Agent`, just not fork again). TRUSTED internal (NOT a
     * `TaskSpec` field — the untrusted-caller surface), mirrors `inheritedSkillGrants`.
     */
    insideFork?: boolean;
    /**
     * The PARENT's reminder provenance mark, set ONLY by the Agent tool's
     * fork route on the child it spawns ("one declaration, one mark": a fork runs under the parent's
     * byte-identical system-prompt declaration, so its own engine mints must carry the PARENT's mark;
     * a spawn/clone context mints its own). Verified through the mint home's verify port at adoption
     * (`isValidReminderMark` — an unrecognized value re-mints, fail-safe); the forked SESSION's own
     * `reminder_mark` entry is the primary carrier (a store fork copies committed history), this
     * channel is the in-process belt over it. TRUSTED run-scoped channel (NOT a {@link TaskSpec}
     * field), mirroring `insideFork`.
     */
    reminderMark?: string;
    /**
     * Set UNCONDITIONALLY by every core spawn path (`createSubagentTool`'s
     * `childInternals` — shared by all four spawn legs sync/steer/background/fork, its persisted
     * `internalsSnapshot` for revive, and the workflow orchestrator's `spawnAttribution`), regardless
     * of whether a NAMEABLE `parentTaskId`/`parentToolCallId` exists. A directly-started workflow (no
     * launching tool call, e.g. cron-triggered) has neither of those — "no id is ever fabricated" for
     * attribution — but its spawned agents are still delegated children for consent-notice purposes.
     * Drives `isSubagent` below: do NOT use `parentTaskId` presence alone as the child-ness signal,
     * that under-covers exactly this case. TRUSTED run-scoped channel (NOT a {@link TaskSpec} field),
     * mirroring `insideFork`.
     */
    isDelegatedChild?: boolean;
    /**
     * Subagent transcript persistence — the child transcript session's PLACEMENT declaration, minted
     * by the background delegation lane (the trusted spawner: it knows the a* handle, scope and root)
     * and forwarded verbatim by this prepare's session acquire, so the session store CREATES the
     * child's transcript into its declared subagent partition (see {@link SessionStore.placements}).
     * Deliberately a TRUSTED internals seat and never a {@link TaskSpec} key: a public key would let
     * any caller push arbitrary sessions into the partition and poison store-side retention/
     * enumeration. Absent (sync children, forks, observers, every non-delegated run) ⇒ the acquire
     * carries no placement — byte-identical to before.
     */
    sessionPlacement?: import("../session.js").SessionPlacement;
    /**
     * The DEFAULT role-base persona for a DELEGATED child, threaded by
     * `createSubagentTool`'s execute (a core caller) when neither an agent-definition `systemPrompt` nor the
     * delegation tool's `opts.systemPrompt` names one. It sits at the BOTTOM of the role-base chain —
     * `spec.systemPrompt ?? resolvedRole.systemPrompt ?? internals.defaultSystemPrompt` — so a deployment's
     * `roles.subagent.systemPrompt` / `roles.default.systemPrompt` preset still wins (pre-G1 semantics: an
     * unset child systemPrompt let the role preset apply; G1's first cut put SUBAGENT_PROMPT at spec level
     * and silently shadowed the preset). Only when NO preset resolves does the lean SUBAGENT_PROMPT (CC 198
     * general-purpose persona, pretty.js:419977) replace the full DEFAULT_SYSTEM_PROMPT constitution base.
     * TRUSTED run-scoped channel (NOT a {@link TaskSpec} field), mirroring `insideFork`.
     */
    defaultSystemPrompt?: string;
    /**
     * The catalog WORD that chose the model this engine-spawned child was handed on its spec.
     *
     * The two halves of one fact travel on two channels on purpose: the spec carries the RESOLVED `Model`
     * object, so nothing re-resolves between the spawn's judgement and this child's prepare (the model the
     * spawn frames name is the model that runs); this carries the word that object came from, so the child
     * is not PINNED by having been handed an object — its own children inherit the word in turn, and the
     * deployment's per-model window overlay (keyed by word) still reaches it. Absent ⇒ no word exists for
     * this child's model (a caller-pinned object, a `select` criteria), and such a leg is deliberately never
     * re-resolved and never overlaid. Trusted channel, never a `TaskSpec` field: it is the engine's own
     * statement about a model the engine itself resolved, and a caller cannot author it.
     */
    inheritedModelRef?: ModelRef;
    /**
     * GOAL MODE flag, a TRUSTED internal channel set ONLY by `runGoal` (a core caller),
     * NEVER a {@link TaskSpec} field. It drives `featureFlags.goalEnabled` → injects `GOAL_COMPLETION_GUIDANCE`.
     * Why internal (not a public `TaskSpec.goalMode`): the guidance promises "declaring done STOPS iteration and
     * surfaces" — a promise only `runGoal`'s loop makes real. A public field would let a caller inject that
     * prompt with no loop behind it (§6.3 honesty violation). `runGoal` injects the `declare_done` tool itself;
     * this flag only governs the PROMPT (injection-ownership split).
     */
    goalMode?: boolean;
    /**
     * The skill grant rows the PARENT task held at the moment it spawned this child (its `allowed-tools` / `disallowed-tools`
     * rows of the run, whatever wrote them), captured by `createSubagentTool`'s `execute` from the parent's
     * `ctx.activeSkillGrants()` and seeded into this child's table as `inherited` rows: they hold until THIS child ends
     * (the parent's run ending ends the parent's rows, not this copy), beside the child's own activations.
     *
     * A TRUSTED, run-scoped channel (never a {@link TaskSpec} field — TaskSpec is the untrusted-caller surface), in the
     * posture `repairBundle` / `resume.seed` keep. A capture the delegation could not read refuses the spawn rather than
     * seeding nothing (it carries refusals too). Absent or empty ⇒ nothing inherited.
     */
    inheritedSkillGrants?: readonly SkillGrantRecord[];
    /** The parent's request allow entries at the spawn (`ctx.activeRequestAllowedTools()`), seeded as this child's own request rows until it ends — the channel of {@link inheritedSkillGrants}. */
    inheritedAllowedTools?: readonly string[];
    /**
     * When this task runs as a SUB-AGENT spawned under a parent task's
     * tool call, the spawning tool's `ToolExecuteContext.toolCallId`. The Runner stamps it onto this task's
     * stream content events as {@link TaskEvent.parentToolCallId} so a consumer can attribute the child's live
     * content to the delegation subtree WITHOUT core merging the child stream into the parent (lightweight
     * message-identity, not stream-merge). A TRUSTED, run-scoped channel filled by a core caller
     * (`createSubagentTool`'s `execute`) — NEVER a {@link TaskSpec} field (the untrusted-caller surface),
     * mirroring `inheritedSkillGrants`/`workflowDepth`. Absent for a top-level (non-delegated) task.
     */
    parentToolCallId?: string;
    /**
     * A SUBAGENT's human display NAME, threaded at spawn so the
     * child's `task_progress` ticks carry a readable label (a Fleet child row otherwise shows the raw `taskId`).
     * Filled by `createSubagentTool`'s `execute` = the explicit `taskName`, else the selected agent-type
     * (`AgentDefinition.name`). TRUSTED run-scoped channel (NOT a {@link TaskSpec} field), mirroring
     * `parentToolCallId`. Absent for a top-level run / a bare delegation with neither label — the child's
     * `task_progress` then carries NO `name` (it keeps its taskId; it deliberately does NOT fall back to the raw
     * objective, which could leak a delegated secret). Untrusted (`taskName` is model-chosen) →
     * the consumer sanitizes via `inlineUntrusted` at emit.
     */
    agentName?: string;
    /**
     * The EXPLICIT `Agent({name})` spawn identity — set
     * ONLY when the spawn carried a `name` parameter, unlike {@link agentName} (a DISPLAY label:
     * description or agent-type, present on nearly every spawn). Teammate semantics (hierarchy clamp,
     * teammate addendum, uplink attribution) key on THIS field; keying on the display label made
     * every described child a "teammate" (clamp over-wide, addendum over-composed).
     */
    explicitAgentName?: string;
    /**
     * The RESOLVED subagent type this child was spawned as (`"general-purpose"` / `"fork"` / a roster
     * definition's name — the same word the fleet row's `agentType` column carries; never the
     * description). Read by the hook identity envelope (`identity.agentType`) so a process-level hook
     * seat can tell WHAT kind of child it is serving without parsing display text. Trusted run-scoped
     * channel (NOT a {@link TaskSpec} field); absent on a top-level run and on a workflow-lane agent.
     */
    spawnAgentType?: string;
    /**
     * The SPAWNING run's taskId, threaded at spawn (from the parent's
     * `ToolExecuteContext.taskId`) so this child's `task_progress` ticks carry `parentTaskId`. Lets a UI build the
     * live nested-agent tree directly (child.parentTaskId === parent.taskId) at any depth. TRUSTED run-scoped
     * channel (NOT a {@link TaskSpec} field), mirroring `parentToolCallId`. Absent for a top-level run.
     */
    parentTaskId?: string;
    /** The spawning run's sessionId (paired with parentTaskId; see
     *  ToolExecuteContext.parentSessionId). */
    parentSessionId?: string;
    /**
     * The memory-capture opt-out FLOOR: TRUE ⇔ the spawning session was under an
     * opt-out when this child spawned. The child then runs opted-out itself (its prepare mints its
     * own one-way record, reason naming the floor) — NOT re-adjudicated against the child's
     * entitlement (the floor is the parent's already-granted opt-out tightening the tree; a
     * re-adjudication that could answer "no" would be a loosening door). Trusted internals channel
     * on purpose (the parentInteractionPosture law): never a child-spec field, so no chosen
     * AgentDefinition and no spec surgery can shed it; rides `internalsSnapshot`, so retained
     * revivals keep it. Grandchildren inherit through each generation's own live seat (an opted-out
     * child forwards TRUE to its children in turn — monotone by construction).
     */
    memoryCaptureFloor?: true;
    /**
     * The floor's THIRD state: the spawning session's
     * capture state was INDETERMINATE at spawn (its record store faulted at the getter read).
     * Neither boolean is honest there — no floor runs a possibly-recorded lineage captured, a coined
     * floor mints an IRREVERSIBLE record off an unreadable state — so the lane carries the fault
     * itself and the child's prepare resolves it against the live record query (found ⇒ floor;
     * still faulting ⇒ the child runs indeterminate — mechanical restrictions, no mint; readable
     * and clean ⇒ clean). Same trusted-channel law as {@link memoryCaptureFloor}; mutually
     * exclusive with it at the spawn site (the floor wins when the state IS known).
     */
    memoryCaptureFloorIndeterminate?: true;
    /** The spawning session's write-plane control dir (the coordinate its capture
     *  opt-out record is keyed under), so a child on a DIFFERENT memory plane can still run the
     *  record-query leg against the parent's own carrier. Trusted chain, rides internalsSnapshot. */
    memoryCaptureQueryDir?: string;
    /** The ancestor chain's capture coordinates (root first, one
     *  row per generation, appended by each spawner's ctx seat). The child's harvest closures walk
     *  every row, so a mid-run flip ANYWHERE up the tree suppresses every in-flight descendant.
     *  Trusted chain; rides internalsSnapshot. */
    memoryCaptureAncestors?: ReadonlyArray<{
        sessionId: string;
        controlDir?: string;
    }>;
    /**
     * The FLEET-task kind of this run, declared by the lane that spawned it and stamped onto every
     * `task_progress` tick the run mints (`TaskEvent`'s `taskType`). A consumer merging progress ticks
     * with task notifications into one ledger could otherwise only key on "it showed up on the fleet
     * stream, so it must be an agent" — an inference that has already produced a row for a task that
     * was never an agent. Same vocabulary as `TaskNotificationPayload.task_type`, so the merged ledger
     * has ONE type axis rather than two spellings of one.
     *
     * Set by the BACKGROUND delegation lanes (plain + fork), whose runs own a registry `a*` row, and by
     * both of the workflow orchestrator's spawn legs. A SYNCHRONOUS delegated child declares nothing —
     * it has no fleet row of any kind, so absence is a fact about the run rather than a gap in the
     * stamping. TRUSTED run-scoped channel (never a {@link TaskSpec} field).
     */
    delegationTaskType?: import("../types.js").DelegationTaskType;
    /**
     * The registry row's stop-cycle generation this run executes as (fresh spawn = 1, a
     * revival's bumped counter), threaded by the BACKGROUND delegation lanes from the registry's own
     * `cycleSeq` so every `task_progress` tick the run mints carries it as `seq` (same axis as
     * `TaskNotificationPayload.seq` / `BackgroundChildEvent.seq`). Absent for runs with no `a*` row
     * (sync children, workflow agents, top-level) — same absence-is-a-fact posture as
     * {@link delegationTaskType} above. TRUSTED run-scoped channel (never a TaskSpec field).
     */
    cycleSeq?: number;
    /** The ROOT host session of the whole delegation tree (fixed point: the
     *  spawner passes its own `ctx.rootSessionId ?? ctx.sessionId`, so depth 1 gets the host session
     *  and every deeper level inherits it verbatim). `parentSessionId` is the IMMEDIATE spawner —
     *  after a restart those intermediate sessions are dead ends, and a recovery face enumerating
     *  "everything under this host session" needs the root anchor, not an alias walk. */
    rootSessionId?: string;
    /**
     * The run tree's PLACEMENT root: the fixed point a target-bound env factory keys
     * its placement lookup on ({@link import("../remote-env.js").ExecutionEnvFactoryContext.placementRootSessionId}).
     * A SEPARATE axis from {@link rootSessionId} deliberately: that field means "member of this host
     * session's DELEGATION tree" and is consumed by the registry access/recovery faces — cascade rungs
     * and verification legs are intentionally NOT members of that tree (independent cold re-runs in
     * their own sessions), so widening `rootSessionId` to cover them would corrupt the recovery faces'
     * reading. This member says only "place me where this session was placed". Producers: the
     * orchestration entries (`runCascade` / `runWithVerification` family) after their first leg's
     * sessionId receipt, and — C12 — the delegation/workflow spawn chains, which re-thread a parent's
     * EXPLICIT value verbatim into child internals (ToolExecuteContext.placementRoot → childInternals;
     * workflow deps → shared internals base), so every descendant of a placed leg keeps the fixed
     * point; prepare's mint reads it first (`placementRoot ?? rootSessionId ?? sessionId`). Absent
     * everywhere else — the delegation lanes' `rootSessionId` fixed point then becomes the placement
     * root through the middle segment, unchanged. DURABLE: the suspend mint stamps the resolved fixed point as
     * `CheckpointState.placementRootSessionId`, and a bare durable resume restores it into this
     * member (live internals win when re-supplied; a CONTRADICTING re-supply refuses pre-CAS,
     * `resume.placement_mismatch`). TRUSTED run-scoped channel (never a {@link TaskSpec} field).
     */
    placementRoot?: string;
    /**
     * The REGISTRY SCOPE this run's own background row lives in: the domain its registry-facing
     * tools (TaskOutput / TaskStop / SendMessage / AgentTranscript / Monitor, and the announce listing)
     * must mount in to see it. Filled by the delegation tool at spawn, which is the party that CHOSE the
     * domain when it registered the row — `ctx.principal` when the spawning run has one, else the
     * delegation mount's declared `background.scope`, and on a revival the claimed row's own scope.
     *
     * Why this is not just `principal`: a deployment may declare its domain at MOUNT time and run its
     * tasks without `TaskSpec.principal`. The row then lands in the declared domain while the child runs
     * with no principal at all, and `principal ?? "default"` sends every one of its registry-facing tools
     * to a domain the row is not in — scope is fail-closed on both access predicates, so such a child
     * cannot see its own row, its siblings, or the children it spawns. Carrying the domain as its own
     * trusted axis keeps `principal` the IDENTITY it is: the identity also keys the durable
     * approval / checkpoint namespace (`checkpointScopeOf`), the runtime-capability lookup and the MCP
     * principal header, and a mount's registry scope has no business moving any of those (an unattended
     * safety park must not migrate into the caller's opt-in approval bucket).
     *
     * TRUSTED channel (never a {@link TaskSpec} field, never a model argument), like the parentage axes
     * above. When a deployment sets both, they agree by construction: with a principal present the
     * delegation tool resolves this axis TO that principal.
     */
    registryScope?: string;
    /** The spawning run's ADOPTED center artifact, threaded down the
     *  trusted internals chain so every child in the tree composes the SAME closure (a child must
     *  never spontaneously adopt the live candidate while its parent runs an older pin — that is the
     *  exact mixed-version state the epoch exists to prevent). Resolved by-digest through the
     *  verified store at child prepare; a miss is the same fail-loud `prompt.snapshot_unavailable`. */
    parentCenterArtifactDigest?: string;
    /** Publish provenance companion of {@link parentCenterArtifactDigest} (audit only). */
    parentCenterSourceRevision?: string;
    /** R2 双形轴 — parent's resolved prompt profile, inherited unless the child spec overrides. */
    promptProfile?: "simple" | "classic";
    /**
     * An OPT-IN, DISPLAY-ONLY event sink a deployment sets on the TOP run to
     * receive a subagent's live `task_progress` ticks (which otherwise stay in the child's ISOLATED stream). Threaded
     * recursively down the delegation tree (via `ctx.forwardEvent`), so every nested subagent's ticks bubble to the
     * SAME sink. The Runner's ctx wrapper forwards `task_progress` always; when the run's spec sets
   * `forwardSubagentEvents: true` it ALSO forwards the child's content events (`text_delta` / `text_end` /
     * `reasoning_delta` / `tool_start` / `tool_end` — the subagent viewing pane, carrying the same UNTRUSTED-RAW/consumer-must-redact
     * contract as the main stream's tool events). Either way the child stream is NEVER merged into the parent's
     * MODEL context (this is purely a render channel). Absent unless the deployment opted in. CONTAINED at
     * one door: every engine site feeding this seat goes through `forwardSafely` (`forward-sink.ts`), so a
     * sink that throws — or, since an `async` function satisfies this `=> void` type, one that returns a
     * REJECTING promise — drops its frame and nothing else, never awaited
     * (`@contract runtask.forward_sink_never_faults_the_run`).
     */
    onForwardEvent?: (event: TaskEvent) => void;
    /**
     * The run's OWN top-level `status` TaskEvent stream (brain liveness: rate-limit/retry/
     * reconnect/circuit-open), offered to the internals holder beside the queue. The queue alone was
     * enough for a direct `runTask` caller (the TaskStream carries these frames), but a COMPOSITION
     * entry (verify/cascade) drains its inner legs' queues itself — without this seat, an inner leg's
     * retry disclosure died inside the gate and the wire showed a silent stall. Fed the SAME frame
     * object the queue receives, at the same moment; contained by the run's safe notifier (a throwing
     * sink is swallowed, first failure per site disclosed, never faults the leg). Subagent
     * frames still ride {@link onForwardEvent} — this seat is ONLY the run's own status type.
     */
    onStatusEvent?: (event: Extract<TaskEvent, {
        type: "status";
    }>) => void;
    /** The run-local per-tool-call detach hub. runtask creates it and exposes
     *  `TaskStream.detach(toolCallId)`; the hands Bash tool threads `signalFor(toolCallId)` into env.exec. */
    detachHub?: import("../tool-detach.js").ToolDetachHub;
    /** The run's forwarding-failure bookkeeping for {@link onForwardEvent} — ENGINE-owned and opaque,
     *  minted once per run by `Runner.runTaskStream` and carried by every later COPY of this object (the
     *  prepare re-spread, a placement-root rebind, a child's snapshot), which is what makes "announced once
     *  per run" true across the lanes that forward. A caller value is overridden like the refs beside it;
     *  absent ⇒ the door mints a per-object one, so containment never depends on the threading. */
    forwardScope?: import("./forward-sink.js").ForwardSinkScope;
    /**
     * The Runner's per-session read-file-state seats (CC parity: readFileState is
     * session-scoped). Always set by the Runner's own prepare call (overriding any caller value, like
     * the peer refs beside it); absent on a standalone prepareTask, where the transcript-replay rung
     * covers alone. A CACHE, never an authority — see {@link SessionReadFileStates}. Trusted internals
     * channel, same posture as every other field here.
     */
    sessionReadStates?: SessionReadFileStates;
    /**
     * The Runner's per-session permission-mode seats ({@link SessionModeSeats}): the chain a leg hands its children
     * carries its SESSION's seat as the children's ceiling, and each leg binds itself to that seat once its prepare
     * commits — so a session-scoped background child follows the session's current leg. Always set by the Runner's own
     * prepare call (overriding any caller value); absent on a standalone prepareTask, which mints a seat of its own.
     */
    sessionModeSeats?: SessionModeSeats;
    /** Trusted system-injection sink (`Runner.runLocked` wires it to the live TaskStream queue plus the
     *  current harness lanes; never a public TaskSpec field), ADDRESSED TO THE SESSION rather than to this
     *  run: a producer wired here (a background shell, a watch, a delegation settle) keeps the closure long
     *  after the run that mounted it ended, and its frame is then routed to whatever run the SESSION has
     *  live — at that run's next turn boundary, the seat a steer lands on; only a session with NO live run parks it. */
    onTaskNotification?: (notification: TaskNotificationPayload, 
    /** Injection tier (the ladder is LIVE): "next" = the running turn's next boundary
     *  (arrival order, consecutive frames batch); "later" = the run's would-otherwise-stop seat
     *  (never folded into work in progress); "now" = class-head + earliest natural boundary on this
     *  lane (interrupt authority belongs to the steer face, never to notifications). Internal
     *  producers declare their tier explicitly (§3.7 census — completion-class lanes are "next");
     *  the parameterless default "later" serves the external verb's omitting callers only. */
    opts?: {
        priority?: import("../task-notification.js").SystemInjectionPriority;
    }) => void;
    /**
     * The PARENT run's notification injector (its runtask-wrapped
     * `injectTaskNotification`), threaded into a CHILD's internals at spawn time so the child's
     * SendMessage("main") uplink lands in the parent's queue at a turn boundary (the CC
     * "delivered automatically" parent half). DELIBERATELY a separate field from
     * {@link RunInternals.onTaskNotification}: that one is "inject into the run's own SESSION" (runtask
     * wraps it as upstream-observer + session route), and reusing it for the child would tee every
     * internal child notification (grandchild completions, monitor events) into the parent — double delivery.
     */
    parentNotify?: (notification: TaskNotificationPayload, opts?: {
        priority?: import("../task-notification.js").SystemInjectionPriority;
    }) => unknown;
    /**
     * The ONE address `SendMessage("main")` names for this child — the session that hosts this child's
     * lifecycle — paired with {@link parentNotify} at the same mint, three arms:
     *  · a fresh spawn: the SPAWNER's session, whose live injector is `parentNotify`;
     *  · a revival by that tree's ROOT (or any top-level session): the REVIVER's session, its injector;
     *  · a revival by a SIBLING child, or by a host verb with no session: the row's ROOT session, and
     *    NO `parentNotify` — a sibling is a peer the child addresses by name, not its main, and there is
     *    no live callback into the root from either reviver. Such a child's "main" send reads the
     *    honest not-deliverable text; its terminal still reaches the root through the root-anchor arm.
     * The uplink receipt names this id so the model knows WHICH conversation "main" is. Absent on a
     * top-level run. Trusted run-scoped channel (never a {@link TaskSpec} field).
     */
    uplinkSessionId?: string;
    /**
     * The PARENT run's subagent-retain ledger, threaded to a CHILD so its
     * SendMessage can continue a RETAINED SIBLING (the sibling's retain entry lives on the parent's
     * ledger — without this, sibling resolution succeeds but delivery always reads not-retained).
     * TRUSTED chain; read-only use (the sibling leg resumes through the same fenced resume face).
     */
    parentRetainLedger?: import("../../agents/retain-ledger.js").SubagentRetainLedger;
    /**
     * Hands THIS run's notification injector back to the SPAWNER once the lane is
     * live (runtask calls it with its wrapped `injectTaskNotification`). The spawner stores it on the
     * child's registry handle so a parent/sibling SendMessage can deliver TO the RUNNING child at its
     * next turn boundary (CC's in-memory `pendingMessages` pedestal, anchors/2.1.212
     * messaging-runtime.md §2.5). The injector outlives its run safely: once this lane is over the frame
     * is routed to whatever run the SESSION has live, and only a session with no live run parks it
     * (PendingSessionNotifications) — exactly CC's durable-mailbox posture for an idle teammate (§2.4).
     * TRUSTED chain (core spawner only); never reachable from TaskSpec.
     */
    onNotifyInjectorReady?: (inject: (notification: TaskNotificationPayload, opts?: {
        priority?: import("../task-notification.js").SystemInjectionPriority;
    }) => Promise<import("../task-notification.js").NotificationInjectionOutcome>) => void;
    /**
     * Per-task ISOLATION hint, a TRUSTED run-scoped channel filled ONLY by a core caller
     * (the workflow's `ctx.agent` when the SCRIPT passed `{ isolation: "worktree" }` as an OPTION) — NEVER a
     * {@link TaskSpec} field (the untrusted-caller surface). Forwarded to
     * {@link ExecutionEnvFactory} via {@link ExecutionEnvFactoryContext.isolation} so the trusted control-plane
     * factory mints a git-worktree-rooted env for this agent; and it makes root resolution use the worktree
     * env's own cwd (the worktree dir), bypassing `deps.rootPath`. Isolate-ONLY: the runtime never merges
     * — the orchestrator script reads each worktree's result and decides verify/merge in userland.
     * FAIL-CLOSED: a request the deployment cannot honor (no factory, or an observably non-isolated env)
     * throws at prepare time — the child never starts; there is no silent fallback to the shared tree.
     */
    isolation?: "worktree";
    /**
     * Sub-agent cwd inheritance (CC parity, 2026-07-03): the PARENT task's effective working root,
     * filled ONLY by core delegation callers (the workflow's `ctx.agent` / `createSubagentTool`'s execute —
     * NEVER a {@link TaskSpec} field). Forwarded to {@link ExecutionEnvFactory} via
     * {@link ExecutionEnvFactoryContext.parentCwd} so a single-user/TOC factory can root the child env at the
     * parent's cwd instead of an empty per-task sandbox. `isolation: "worktree"` wins over this when both set.
     */
    parentCwd?: string;
    /**
     * The spawning run's file-history LINEAGE — the scope it records first-touch edits into and the
     * TREE those records' keys are minted against (the canonical root spelling + the filesystem
     * identity of {@link fileHistoryFilesystemIdentity}) — threaded VERBATIM by core delegation
     * callers from {@link import("../types.js").ToolExecuteContext.fileHistoryLineage} (NEVER a
     * {@link TaskSpec} field). {@link resolveFileHistoryScope} is the ONE reading: this run records
     * into the lineage's scope iff its own tree coordinates BOTH equal the lineage's, and then
     * re-exposes the SAME triple on its own ctx, so every same-tree descendant of a root session — at
     * any depth — lands in the root session's scope (the fixed point), while a descendant on another
     * tree (worktree isolation, explicit `cwd`, a fresh per-task sandbox) becomes the root of its own
     * subtree's history. Absent on a top-level run, on a run that records no file history, and on a
     * tier-3 revival (the reviver's lineage says nothing about the revived row's tree).
     */
    fileHistoryLineage?: {
        scope: string;
        root: string;
        fs: string;
    };
    /**
     * [c209-D] — the EXPLICIT Agent.cwd request, distinct from the best-effort `parentCwd`
     * inheritance hint above: inheritance may be silently ignored by a factory (or absent without one),
     * but an explicit cwd the model asked for MUST either take effect or fail loud. prepareTask enforces
     * the contract: no `executionEnvFactory` ⇒ throw `config.cwd_unsupported`; after env creation the
     * env's actual cwd must canonically equal this path or the task fails `config.cwd_not_honored`; the
     * task root follows the env's cwd (never `deps.rootPath`) when set. Filled ONLY by core delegation
     * callers (ctx-injection posture, same as `parentCwd`).
     */
    requestedCwd?: string;
    /**
     * Subagent steer verb (dogfood finding 2026-07-03, "中途插话"): the host run's opt-in
     * SUBAGENT-STEER-HANDLE sink. When set, `createSubagentTool` runs each child via `runTaskStream`
     * and emits a steer handle here (the model never sees the handle — same host-context-isolation
     * posture as `onWorkflowAgentSpawn`). A deployment registers it by `taskId` to route a human steer
     * into the running child (fenced-marker semantics matching the workflow agent handle). Threaded to
     * the tool ctx as {@link ToolExecuteContext.onSubagentSpawn} and recursively down the delegation
     * tree. Absent ⇒ children run non-steerable (prior behavior, zero overhead).
     * SCOPE: only SYNC delegations emit a handle — a
     * `run_in_background` child does not (poll/stop it via TaskOutput/TaskStop); wiring the background
     * lane is a recorded follow-up, not an oversight a deployment should discover at runtime.
     */
    onSubagentSpawn?: (handle: import("../../agents/subagent.js").SubagentSteerHandle) => void;
    /**
     * A TRUSTED run-scoped tool-ACTIVITY sink, filled ONLY by a core caller (the workflow's
     * `ctx.agent`, to render a per-agent "last N tool calls" drill-down). Called synchronously at each tool start +
     * end with structural data (name/phase/ids) — NEVER args/output (those carry untrusted/host data). NEVER a
     * {@link TaskSpec} field. Absent ⇒ no activity capture (default).
     *
     * Reaches activity on FRESH and RESUMED runs alike — the durable-resume entry (`resumeStream`) threads
     * `internals` too (see its parent-constraint re-supply snapshot), so a resumed leg's SUBSEQUENT tool calls
   * hit this sink. One real boundary remains: the resume's already-approved pending call itself is
     * executed by `applyResumeDecision`'s own callback, outside the frame-minting harness, so THAT one call
     * emits no activity.
     */
    onActivity?: (activity: ToolActivity) => void;
    /**
     * A TRUSTED run-scoped WORKSPACE-observation sink, filled ONLY by a core caller
     * (the workflow's `ctx.agent` / `ctx.agentStream`, to record an isolated agent's worktree directory on the
     * persisted run record). NEVER a {@link TaskSpec} field — same posture as {@link onActivity}.
     *
     * WHY the engine must hand this out: for `isolation: "worktree"` the worktree path is minted INSIDE
     * {@link RunnerDeps.executionEnvFactory} and lands only on the per-task env's `cwd`; no runner-outward face
     * (TaskEvent union / TaskStream / TaskResult) carries it back. An orchestrator therefore could not record
     * WHERE its isolated agent worked — the recovery path after an interrupted run had to enumerate the
     * worktrees directory and guess, which is exactly the contradiction recorded against the
     * "isolate-only, userland decides verify/merge" intent.
     *
     * Called at most ONCE per prepared run with the task's FINAL working root (see {@link ResolvedWorkspace}),
     * after the durable-resume restore may have re-rooted it. Observe-only: a throwing sink is swallowed (an
     * observation must never fault a prepare that already minted a workspace).
     *
     * Like {@link onActivity}, this seat rides `internals` on FRESH and RESUMED runs alike (`resumeStream`
     * threads it too) — a resumed leg's restore re-fires the observation with the settled root.
     */
    onWorkspaceResolved?: (workspace: ResolvedWorkspace) => void;
    /**
     * A TRUSTED run-scoped WIRING-MANIFEST sink, filled ONLY by a core caller and NEVER a {@link
     * TaskSpec} field — the posture of {@link onActivity} and {@link onWorkspaceResolved} beside it.
     * Handed the very object this leg's `wiring_manifest` frame carries, at the moment that frame is
     * pushed: ONCE per prepared leg, after prepare and before the leg's first model or tool
     * interaction. Rides `internals` on FRESH and RESUMED legs alike, because the mint sits in the
     * run body both entries share.
     *
     * WHY a seat, when the frame already rides the stream: a COMPOSITION entry (the verify gate, the
     * cascade) drains its inner legs' queues ITSELF, so the host holds no TaskStream for any of them
     * and an inner leg's manifest dies inside the gate — the hole {@link onStatusEvent} was opened
     * for, one frame family over. A direct `runTask` caller needs neither seat: its own stream
     * carries both frames.
     *
     * `leg` is the LEG IDENTITY, and the engine mints NONE of it. It is present only where a
     * composition labelled the leg it dispatched: the verify entries hand each leg a per-dispatch
     * copy of this object whose sink closes over that leg's identity, so a consumer can tell the
     * implementation leg from pass 2's verifier without inferring it from arrival order (the
     * ordering is all the sibling sinks have — see the attribution paragraph on `verifyCompleted`).
     * ABSENT therefore means "nobody labelled this leg", never "implementation": a plain `runTask`,
     * a workflow agent call and a cascade rung all reach this sink with no identity, and reading one
     * in would be the engine fabricating a fact it does not hold. NOT the same axis as the manifest's
     * OWN `leg` field ({@link WiringManifest.leg}, `root` / `child` / `resume`), which is a fact about
     * how the ENGINE reached this run; this one is a fact about the composition that dispatched it, and
     * a resumed fix leg carries both at once — `manifest.leg.kind === "resume"` and `leg.kind === "fix"`
     * are both true of it. `pass` is 1-based over the gate's verification rounds: a `verifier` leg
     * carries the one it verifies, a `fix` leg the one whose findings it answers, and the entry
     * implementation leg carries 0 — before the first. The axis is relative to the gate INVOCATION,
     * not to the session's history: a resume entry's work leg is THAT invocation's implementation leg
     * (`impl`, pass 0) even when the checkpoint it resumes was minted inside a previous invocation's
     * fix pass, because the invocation's own pass counter (`VerificationResult.rounds`) restarts with
     * it — one axis for both fields, rather than a leg that reads "fix pass 1" inside a result whose
     * rounds begin at 1 again.
     *
     * Observe-only and failure-isolated through the SAME per-run notifier as {@link onActivity} (one
     * isolation scope for every sink on this object, so a broken sink is counted and disclosed once,
     * not once per site family): a throw — or an `async` sink returning a rejecting promise — is
     * contained and never faults the leg whose manifest it was handed.
     */
    onWiringManifest?: (manifest: WiringManifest, leg?: {
        readonly kind: "impl" | "verifier" | "fix";
        readonly round: number;
    }) => void;
}
/**
 * The payload of {@link RunInternals.onWorkspaceResolved}: the working root this task actually
 * runs on, plus whether that root is the task's OWN isolated workspace.
 *
 * `isolated: true` means "`cwd` is this agent's own git worktree": it is reported only when
 * {@link RunInternals.isolation} was requested AND the fail-closed worktree enforcement accepted the env, so a
 * consumer may treat the path as private to this agent (safe to diff / merge / remove in userland). A
 * non-isolated run reports its plain root with `isolated: false` — the observation face stays complete, while
 * the honest non-claim keeps a consumer from mistaking a SHARED tree for its own worktree.
 */
export interface ResolvedWorkspace {
    /** The task's effective working root — the same value every fence / LSP / prompt consumer uses. */
    cwd: string;
    /** True only for an accepted `isolation: "worktree"` request (⇒ `cwd` is this agent's own worktree). */
    isolated: boolean;
    /** True when the task's execution env is REMOTE: `cwd` then names a path INSIDE the remote
     *  target (a per-task sandbox on the shape-(d) exemption leg, e.g. `/workspace`), which does not exist —
     *  or names an unrelated directory — on the host. `isolated && !remote` is the only combination under
     *  which `cwd` is a host path a consumer may diff / merge / remove. */
    remote: boolean;
}
/**
 * The resolver's answer, with the WITHHELD cause beside the value.
 *
 * The old signature returned a bare `unknown`, which collapsed three different worlds into one
 * `undefined`: "this tool declares no preview at all", "the projection threw / could not be
 * serialized", and "the projection declined to speak for these args". The middle one is the case an
 * approval surface must be told about — a person is being asked to approve something whose display
 * projection could NOT be produced — and it was indistinguishable from the other two.
 *
 * `withheld` names the cause; it is ORTHOGONAL to `preview` presence, deliberately: the oversize arm
 * still ships its truncation stub (a surface that renders it tells the person the size, which is more
 * useful than nothing), and the stub alone never carried the fact that the real projection is missing.
 */
export interface ResolvedApprovalPreview {
    /** The display projection, when one could be produced (or the oversize truncation stub). */
    preview?: unknown;
    /** Present ⇔ the tool DECLARED a preview and the gate could not present a usable one:
     *  `"oversize"` — produced but past the 16KiB display bound (the stub rides `preview`);
     *  `"unavailable"` — the projection threw, or its value has no JSON serialization.
     *  ABSENT covers both "no preview declared" and "the projection returned nothing to show" — neither
     *  is a withholding, and claiming one would make every preview-less tool look suppressed. */
    withheld?: "oversize" | "unavailable";
}
/** The in-stream ask lane: the six closures the park closure and the gate station consume. Built as ONE value so
 *  "the lane exists" is one fact, not six independently-undefined seats. */
export interface AskLane {
    /** The shared run+call signal composition, bound to this task's run signal once. */
    composedCallSignal: (callSignal: AbortSignal | undefined) => AbortSignal;
    /** The abort-bound policy check, or undefined when no policy is wired (→ the gate reads allow). */
    adjudicate: ((req: ToolCallRequest, callSignal?: AbortSignal) => Promise<PermissionResult>) | undefined;
    /** The approval display projection over the live roster (alias-aware; oversize/unavailable named). */
    approvalPreviewOf: (toolName: string, args: unknown) => ResolvedApprovalPreview;
    /** THE synchronous ask mint: resolves a surviving `ask` through the live seat, books the ledger. */
    resolveAskBound: (decision: PermissionResult, req: ToolCallRequest, callSignal?: AbortSignal) => Promise<ResolvedAsk>;
    /** The pre-wrapped deny observer, or undefined when the deployment wired no `permissionDenied` hook. */
    notifyPermissionDenied: ((payload: PermissionDeniedPayload) => Promise<void>) | undefined;
    /** The deployment-facing half of a crashing PreToolUse hook (swallow-guarded). */
    notifyHookError: (err: unknown) => void;
}
/** How the put-commit + put-failure saga ended (see `commitSuspendSaga` inside the phase for each arm's meaning). */
export type SuspendSagaOutcome = {
    tag: "committed";
} | {
    tag: "cut";
} | {
    tag: "absent";
    reason?: string;
} | {
    tag: "unknown";
    reason: string;
} | {
    tag: "compensation_failed";
    reason: string;
};
/** The durable settlement machinery the boundary parks and the park closure consume. Built as ONE value so "the saga
 *  exists" is one fact, not eight independently-undefined seats. */
export interface SuspendSaga {
    /** THIS leg's spend so far in micro-USD (own + nested delta), read at mint time. */
    inFlightSpendMicroUsd: () => number;
    /** The §4.bis correctness-state whitelist, serialized from the live values at suspend. */
    serializeCheckpointState: (workspaceHandle: WorkspaceHandle | undefined, parkedSpendMicroUsd?: number) => SerializedCheckpointState;
    /** The shared paused-VM compensation, bound to this task's failure ledger and error face. */
    compensatePausedVM: (remoteEnv: RemoteExecutionEnv, snapshotId: SnapshotId) => Promise<{
        ok: true;
    } | {
        ok: false;
        reason: string;
    }>;
    /** The put-commit + put-failure split — the point of no return, tagged. */
    commitSuspendSaga: (token: CheckpointToken, cp: Checkpoint, remoteEnv: RemoteExecutionEnv | undefined, remoteHandle: WorkspaceHandle | undefined, cutSignal?: AbortSignal) => Promise<SuspendSagaOutcome>;
    /** The shared suspend-loop-cap guard (safety state first, diagnostic second). */
    suspendLoopCapHit: (count: number, cap: number | undefined, detail: string) => boolean;
    /** The `suspendVM` target by CAPABILITY, or undefined. */
    suspendableEnv: RemoteExecutionEnv | undefined;
    /** The park-only remote target (non-suspendable, externally durable), or undefined. */
    parkOnlyRemoteEnv: RemoteExecutionEnv | undefined;
    /** The park-only identity capture (snapshotId stripped, `restoreMode: "park_only"`). */
    parkOnlyHandle: (env: RemoteExecutionEnv) => WorkspaceHandle;
}
/** The park closure and the content-ask routing closure the gate station hands to `runToolGate`. Built as ONE value so
 *  "the park lane's closures exist" is one fact; `suspendAsk` stays optional INSIDE it because the park predicate (a store,
 *  and something to park for) is a narrower condition than the gate machinery's activation. */
export interface ParkAsk {
    /** The gate's content-ask routing closure (total by construction; answers `unavailable` with no live face). */
    resolveContentAsk: NonNullable<ToolGateInput["resolveContentAsk"]>;
    /** The durable park closure (twelve positional seats then the structural carry), or undefined when the park
     *  lane is not armed (the gate then keeps the synchronous onAsk path). */
    suspendAsk: ToolGateInput["suspendAsk"];
}
/**
 * `runLocked`'s per-run mutable state, EXPLICIT. These fields started as loose
 * `let`/`const` bindings in the R2 declaration hub; the extraction kept every assignment at its
 * original site and order (binding-form-only rewrite — the event-snapshot pin held the run
 * byte-identical), and later work has since added fields directly here (see the per-field notes).
 * Grouped by lifecycle semantics per the B15 pre-recon partition. Function members start as inert
 * placeholders and are assigned at their ORIGINAL declaration sites (the original code could not
 * call them earlier either — TDZ proved it). Not on the npm face; this extraction is what enabled
 * createTurnBoundary / createHarnessHandlers (both are run lanes of their own).
 *
 * WRITERS, by group (the driver mints the zero value, initial-run-state.ts, and hands it to every
 * lane as a borrowed-mutable seat): `telemetry` / `degrade` / `limits` / `budget` initial values and function
 * members — the telemetry-and-budget-seats lane; `counters` / `attach` initial values — the attachment-seats lane
 * (the clock lane zeroes `counters.walltimeSyncBackstopFired`; the tool-mount facts only read); the per-turn writes — the harness-handlers lane
 * (`turn`, `counters`, `limits`, `budget`), the turn-boundary lane (`counters`, `attach`, `budget`) and the
 * recovery lane (`counters.compactionFloor`, `attach.attachState`); the settle-time reads stay the driver's.
 */
export interface RunState {
    telemetry: {
        cacheFamily: ReturnType<typeof cacheFamilyOf>;
        pricing: ModelPricing;
        /** Whether the CURRENT serving model has a configured price table (a
            *  `RunnerDeps.pricing` entry or a `Model.cost` declaration). When neither exists,
            *  `modelCostToPricing(undefined)` yields an all-zero table and every computed cost is a
            *  fabricated 0 — indistinguishable from "declared free". This flag keeps the two apart so
            *  the DISCLOSURE faces (trace frames, public stats) can go absent instead of lying; the
            *  internal budget coordinate (`stats.costMicroUsd`) stays numeric either way. */
        pricingConfigured: boolean;
        /** Sticky — some spend was ACCOUNTED while `pricingConfigured` was false (a mid-task
         *  model switch can flip pricedness, so per-call state isn't enough for the task total). Once
         *  true, the task-level cost total is an undercount and its faces are omitted. */
        unpricedSpend: boolean;
        tracer: TaskSpec["tracer"];
        taskId: string;
        /** The run's engine-minted identity, so the task.start/task.end pair carries it from ONE
         *  seat (the same place taskId is published). Empty only before the run body assigns it. */
        runId: string;
        taskStart: number;
        taskStartMonotonic: number;
        cacheBreakReported: boolean;
    };
    degrade: {
        degradeToModel: Model | undefined;
        degraded: TaskResult["degraded"];
        outputErrorStreak: number;
        outputInvalid: boolean;
        recordDegraded: (info: NonNullable<TaskResult["degraded"]>, toModel?: Model) => void;
    };
    limits: {
        turnsExceeded: boolean;
        budgetHit: "exceeded" | "precall" | undefined;
        /** WHICH budget axis tripped, set at the same instant as {@link budgetHit} so the
         *  terminal can name it (`limits.max_tokens_exceeded` vs `limits.max_cost_exceeded`). Tokens
         *  outrank cost when both are over at the same boundary (the documented attribution order). */
        budgetAxis: BudgetAxis | undefined;
        /** The TYPED terminal an EXTERNAL cause (a dying execution environment, an
         *  exhausted governance window) recorded when it could not suspend the run durably. Set together with
         *  the abort it triggers, and adopted as the run's `threw` once the loop settles, so the caller gets
         *  `env.lifetime_expired` / `usage.window_exhausted` instead of the anonymous error an abort produces.
         *  Same carrier shape as `brainCallGuardrailRef.timedOut`, and for the same reason: the harness turns a
         *  loop throw into an error assistant message that strips the error object. */
        platformTerminal: (Error & {
            code?: string;
            retryAfterMs?: number;
        }) | undefined;
        outputRetryCap: number;
        effectiveMaxTurns: number | undefined;
    };
    budget: {
        remainingMicroUsd: number | undefined;
        maxCostMicroUsd: number | undefined;
        /** Tokens still available on the cross-slice allocation (`resourceSuspend.totalTokens`
         *  minus everything earlier slices spent), or `undefined` when no total is set. */
        remainingTokens: number | undefined;
        /** This slice's EFFECTIVE token ceiling = `min(limits.maxTokens, remainingTokens)`. The exact mirror
         *  of `maxCostMicroUsd`. `undefined` = no token ceiling; `0` is a real, exhausted ceiling. */
        maxTokensWindow: number | undefined;
        /** The axis that is over its ceiling right now, or `undefined` when neither is — a predicate that
         *  ALSO carries the attribution, so no caller can set `budgetHit` without an axis. */
        overBudget: () => BudgetAxis | undefined;
        streamCancel: boolean;
        callOutputChars: number;
        lastStreamBudgetCheck: number;
        projectedOverBudget: () => BudgetAxis | undefined;
    };
    turn: {
        callStartAt: number | undefined;
        firstTokenAt: number | undefined;
        turnUsage: {
            inputTokens: number;
            totalInputTokens: number;
            outputTokens: number;
            cacheReadTokens: number;
            cacheWriteTokens: number;
            costMicroUsd: number;
        } | undefined;
        turnUsageMissing: boolean;
        turnStopReason: string | undefined;
        /** The PROVIDER-REPORTED model name of this turn's LAST assistant message, when it differed from the
         *  requested id — surfaced on `turn_end.responseModel`, reset with the rest of the per-turn
         *  bookkeeping. Same shape as `turnStopReason`: last-message-wins, absent by default. */
        turnResponseModel: string | undefined;
        lastTurnHadToolCalls: boolean;
        /** B15 三期使能:postToolBatch 的本回合聚集批(每回合边界消费并重置 — 回合语义,故归 turn)。 */
        toolBatch: PostToolBatchCall[];
    };
    counters: {
        /** How many LIMIT-APPROACH frames have been injected this run (0..2). Also the
         *  one-shot latch itself: each frame fires when the count is still below its index. */
        approachNoticesSent: number;
        /** B15 三期使能:R9 引用的 R3/压缩区 latch(walltime 收尾 latch — preemptIgnoredReported 同族)。 */
        walltimeSyncBackstopFired: boolean;
        /** B15 三期终件补遗:压缩地板/背压 latch — rs.counters.compactionFloor 跨 drainManualCompact(写)/R9(读写)/收尾(读)三方共享,rs.counters.trimForceBackoff 为 R9 跨调用 latch(同款只改绑定不改时机)。 */
        compactionFloor: number;
        trimForceBackoff: boolean;
        /** Landed compactions (boundary + prompt-too-long lanes) and the last one's kind — the `TaskResult.compaction` source (the breaker state is read off the breaker itself). */
        compactionPasses: number;
        compactionKind?: import("../task-notification.js").CompactionKind;
        repetitionCuts: number;
        repetitionSpared: number;
        repetitionEvents: Array<{
            turn: number;
            action: "cut" | "spared";
            rule: "char-run" | "unit-loop";
            period: number;
            reps: number;
            segment: string;
        }>;
        REPETITION_EVENTS_CAP: number;
        preemptIgnoredReported: boolean;
        wroteThisRun: boolean;
        finalVerifyInjections: number;
        groundingSignalPreR9: boolean;
        groundingSignalPostR9: boolean;
        cadenceTurns: number;
    };
    attach: {
        attachmentsCfg: TaskSpec["attachments"];
        agentListingOn: boolean;
        skillsListingOn: boolean;
        attachState: ReturnType<typeof createAttachmentState> | undefined;
        dateState: {
            announcedDate: string;
        } | undefined;
        instrProbe: RunnerDeps["probeInstructionSources"];
        instrState: {
            lastAnnouncedHash: Map<string, string | null>;
        } | undefined;
        /** The Workflow tool card's advisory size guideline as last ANNOUNCED to the model,
         *  plus the live boundary-time read (Prepared.workflowSizeGuideline). Same deliberate posture as
         *  `dateState`/`instrState`: NOT an AttachmentState member, one field, zero reducers. */
        sizeGuidelineState: {
            announcedGuideline: WorkflowSizeGuideline;
            current: () => WorkflowSizeGuideline;
        } | undefined;
        /** B15 三期使能:boundary 注入计数(→ stats.mechanisms.attachmentsInjected;attachState 注入族语义,故归 attach)。 */
        attachmentsInjected: number;
    };
}
/** One accepted live steer's REPLAY identity, the twin of the parked
 *  queue's `samePendingSteerPayload`. `payload` is the exact string handed to the harness, so the text,
 *  the speaker envelope and the trust framing all fold into it; `trusted` is kept SEPARATELY from those
 *  framed bytes so a text that spells its own `<system-reminder>` wrapper can never read as the trusted
 *  arm's byte-equal twin. `actor` carries the attribution the ledger frame reports. */
export interface AcceptedSteerInput {
    payload: string;
    trusted: boolean;
    /** The NORMALIZED priority the call delivered under (absent input ⇒ "next"),
     *  the replay identity's sixth axis: same id + same text at a DIFFERENT tier is not a replay —
     *  a `next` input re-sent as `now` answering "idempotent success" without the interrupt would be
     *  a disposition lie, so it refuses `duplicate_input_id` like any other identity mismatch. */
    priority: SystemInjectionPriority;
    actor?: ActorAssertion;
}
/**
 * Config re-supplied to {@link Runner.resume}. A suspended task's tools / model / policy /
 * hooks cannot be reconstructed from a checkpoint token (the session stores neither tool implementations
 * nor the hand band), so the caller's trusted control plane re-supplies the same {@link TaskSpec} it ran
 * with — minus the conversation bits: `sessionId` comes from the checkpoint and `objective` is replaced by
 * an internally-generated continuation, so both are omitted.
 */
export type ResumeTaskConfig = Omit<TaskSpec, "objective" | "sessionId">;
/** Resume plan threaded from {@link Runner.resume} into the shared run loop. */
export interface ResumeRun {
    cp: Checkpoint;
    /**
     * Did the APPROVED ACTION actually begin executing?
     *
     * The reopen compensation used to key on an error-code whitelist (`resume.env_failed` /
     * `resume.session_not_found` / `resume.tool_unavailable`). That is a proxy for the thing that actually
     * matters, and every time a NEW way to exit without running the action appeared, the whitelist did not
     * know about it: a caller's abort landing anywhere between the CAS and `tool.execute` — across
     * `SessionStore.acquire` (pluggable, cross-network on a durable backend), the MCP reconnect, the env
     * rebuild, `resumeVM`, tool materialization — produced a `failed` result with NO errorCode, no reopen,
   * and a human approval consumed for work that never happened. Two rounds of fixes each
     * moved that window rather than closing it, because both extended the whitelist.
     *
     * This binds the compensation to the FACT instead: set the instant before `tool.execute` for the gated
     * call (the same `onExecuteStart` signal the orphan-reconcile split already trusts). Every terminal —
     * throw OR returned result — asks one question: was the checkpoint consumed while this stayed false?
     */
    pendingActionStarted?: boolean;
    /** Validated against `cp.gate.kind` at the resume entry: human/irreversible_ask→`policy_ask`,
   *  resource_limit→`resource_limit`, needs_review→`dry_run_review`,
     *  plan_review→`plan_review`. The gate-match guard in `resumeStream` enforces the
     *  correlation. `wake` is the NON-GATE arm — only a checkpoint awaiting NO gate
     *  decision passes the resume entry with it (gate purity, `wake.gate_pending`); the run loop skips
     *  `applyResumeDecision` for it (no pending action to resolve) and re-enters via the continuation +
     *  pendingSteer tail alone. */
    outcome: Extract<ResumeOutcome, {
        gate: "policy_ask" | "resource_limit" | "dry_run_review" | "plan_review" | "wake";
    }>;
    /** The wake's own operator message, validated (`validatePendingSteer`) at the
     *  resume entry and carried SEPARATELY from the checkpoint's parked `pendingSteer`: a message-bearing
     *  wake of a checkpoint that ALSO holds a parked steer must deliver BOTH (park order: parked first,
     *  wake message second), each under its own trusted framing — the old merge-into-the-slot shape
     *  silently DISPLACED the parked (undelivered) supervisor steer. Wake outcomes only. */
    wakeMessage?: Omit<PendingSteerEntry, "seq">;
    /** The userPromptSubmit screen's `additionalContext` for {@link wakeMessage},
     *  captured at the resume ENTRY (the message is screened once, pre-CAS, on the resuming process's
     *  hook) and delivered by the drain as the engine's own reminder AHEAD of the wake frame — carrying
     *  it forward is what keeps the hook single-run (re-screening at the drain would be the double-run
     *  §4.3-3 reserves for the cross-process parked leg). Present only when a wake message passed a
     *  screen that supplied context. */
    wakeMessageHookContext?: string;
    /** Compensation hook: called iff the resumed run fails with `resume.env_failed` (post-CAS
     *  workspace `resumeVM` failed) OR `resume.tool_unavailable` (P-7: the approved tool vanished) — in both
     *  the CAS already consumed the checkpoint but the pending action never ran. `resumeStream` supplies a
     *  closure that reopens the checkpoint (`resolved → pending`) so a retry re-resumes the SAME suspended work
     *  instead of losing it to a forced "re-initiate" (reopen-by-reason): the `reason` is
     *  recorded on the reopened row so the next re-resume validates per reason — an `env_failed` reopen must
     *  replay the persisted winner (a system retry of the approved action), while a `tool_unavailable` reopen
     *  lets a human re-decide with the tool present (a fresh decision is allowed — preserves P-7). */
    onEnvRestoreFailed?: (reason: ReopenReason) => Promise<void>;
    /** The row's execution-record verb, bound to THIS resume's token and scope by `resumeStream` (the
     *  same closure form as {@link onEnvRestoreFailed}, but REQUIRED: the store contract requires the
     *  verb, so there is no store this seat could be absent for). The pending-call resolver calls it
     *  exactly once per resolution, right after it emits the resolved call's `tool_end` frame, with the
     *  {@link import("../gate-outcome.js").GateOutcome} that frame carried — every disposition, the
     *  executed allow included (see `CheckpointRow.executionOutcome`: absence means unknown, never
     *  allowed). The word it answers is read through `EXECUTION_RECORD_LEAVES_ROW_UNRECORDED`; a
     *  conflict throw propagates (an engine defect, not a store condition). */
    recordExecutionOutcome: (gate: import("../gate-outcome.js").GateOutcome) => Promise<ExecutionOutcomeRecordWord>;
    /** Set in the run body right after `applyResumeDecision` completes: the negative-
     *  decision twin of `pendingActionStarted`. A reject/deny consumes its gate BY BEING DELIVERED, and
     *  this bit is the delivery fact — every throw-arm cause (prepare failure, `session_not_found`, a
     *  pre-delivery abort) and the walltime-exhausted settle fire BEFORE it is set, so an undelivered
     *  negative decision still reopens as `env_failed` (the retry replays the persisted decision; it
     *  never re-asks), closing the loss class the first cut reopened. */
    decisionDelivered?: boolean;
}
/**
 * What {@link TaskStream.destroy} needs to reap a SUSPENDED task. Captured (stashed) onto
 * the stream handle the moment a run assembles `status:"suspended"` — `finish()` deliberately left the
 * paused env + committed checkpoint alive for resume, so without this stash they'd be unreachable (the
 * `Prepared`, and its `ownedEnv`, are GC'd once the run promise settles). `env` is `undefined` for a
 * process-local suspend (no remote env to reap — only the checkpoint is expired).
 */
export interface SuspendReap {
    env: Prepared["ownedEnv"];
    token: CheckpointToken;
    scope: string;
    store: CheckpointStore;
    /** The suspended task's session — for onError correlation only (the `token` is a capability and must
     *  NEVER be logged, checkpoint-store.ts §6). */
    sessionId: string;
}
/** B15 三期终件 — R9(回合边界全域:压缩/attachments 注入/提醒节律/预算判决)工厂。
 *  正文自 runLocked 逐字搬迁(this 面经 runnerHooks 闭包打包,B13 #coreView 同款);模块级符号
 *  同文件直见不经 deps;可变运行态全在 rs(B15 二期),deps 全部为 const 稳定引用。 */
export interface TurnBoundaryDeps {
    spec: TaskSpec;
    queue: PushQueue<TaskEvent>;
    /** The run loop's own latch — read for `userHalted` (the public `TaskStream.halt()` verb, which sets
     *  the harness stop latch directly, not through the ctx primitive `Prepared.stopRequestedRef`
     *  mirrors): a halted boundary has no next request either, so the bundle is not collected there. */
    loopLatch: {
        readonly userHalted: boolean;
    };
    manualCompactRef: {
        requested: boolean;
        waiters: Array<{
            resolve: (outcome: CompactOutcome) => void;
            signal?: AbortSignal;
            instructions?: string;
        }>;
    };
    todoToolMounted: boolean;
    taskToolsMounted: boolean;
    walltimeMonotonicDeadline: number | undefined;
    timeout: {
        fired: boolean;
        latenessMs?: number;
        clear: () => void;
    };
    ident: () => {
        eventId: string;
        parentToolCallId?: string;
        sourceTaskId?: string;
    };
    postToolBatchHook: ((batch: PostToolBatchCall[], meta?: {
        injectedThisTurn: "final_verification" | "finalize";
    }, ctx?: import("../hooks.js").PostToolBatchContext) => PostToolBatchResult | undefined | Promise<PostToolBatchResult | undefined>) | undefined;
    batchArgs: Map<string, unknown> | undefined;
    compactionBrain: Brain;
    withinTaskCompaction: () => boolean;
    compactionBreaker: {
        failures: number;
    };
    windowSafetyOptions: (mainModel: Model) => Pick<MaybeCompactOptions, "fallbackBudget" | "onWindowSafety">;
    rapidRefill: RapidRefillState;
    drainManualCompact: (outcome: CompactOutcome) => void;
    runnerHooks: {
        onError: RunnerDeps["onError"];
    };
    /** The Runner's deployment deps, read LIVE ({@link RunnerDepsSeat}): the compaction seams (compaction-seams.ts) the
     *  boundary's compaction pass reaches down for read the summary provider, the hooks slot and the error sink through it
   *  on every pass — the three Runner methods that used to sit on `runnerHooks` as delegates. */
    runner: RunnerDepsSeat;
}
/** R5(harness 事件处理器族)的依赖包。全部为 runLocked 内声明顺序早于
 *  工厂调用点的 const 稳定引用 + 三个 runLocked 形参;this 面(deps.onError)经 runnerHooks 打包
 *  (B13 #coreView / R9 TurnBoundaryDeps 同款)。*/
/** What a PRODUCER of a `tool_progress` frame knows: the FACTS it observed. The frame minus everything only
 *  the run supplies — its discriminator, its {@link TaskEventIdentity} stamp, and the elapsed time, which the
 *  lane computes from its own per-call origin. Derived from the union member, so seat and frame cannot drift. */
export type ToolProgressBody = Omit<Extract<TaskEvent, {
    type: "tool_progress";
}>, "type" | "elapsedTimeSeconds" | keyof TaskEventIdentity>;
/** The run's TOOL-PROGRESS lane: the ONE mint of `tool_progress` plus the heartbeat clock over the calls
 *  in flight. Declared on the FLOOR because both registry-side readers sit above its implementation (the
 *  driver builds it, the harness-handler lane drives it) and the Prepared seat that carries it to the tool
 *  band is here too. `createToolProgressLane` (tool-progress-lane.ts, layer 1) is its one implementation. */
export interface ToolProgressLane {
    /** Offer ONE frame — from a tool that observes its own progress (the shell's per-second tick) or from
     *  the lane's own heartbeat. The lane stamps the elapsed time, DROPS the offer when it would land inside
     *  the cadence floor, and resets that call's beat clock when it does not. NEVER THROWS: a tool calls this
     *  from inside an environment output callback, where a throw kills the command it is describing. */
    emit(body: ToolProgressBody): void;
    /** A tool call entered execution: it joins the heartbeat's in-flight set and its clock starts, stamped
     *  by the LANE (one clock owns one number — see its module header on why that clock is monotonic). */
    noteStart(toolCallId: string, toolName: string): void;
    /** A tool call settled: it leaves the in-flight set. */
    noteEnd(toolCallId: string): void;
    /** Drop every in-flight call and disarm the timer (turn end / teardown): no beating into the next turn. */
    clear(): void;
}
export interface HarnessHandlersDeps {
    spec: TaskSpec;
    queue: PushQueue<TaskEvent>;
    internals: RunInternals | undefined;
    ident: () => {
        eventId: string;
        parentToolCallId?: string;
        sourceTaskId?: string;
    };
    parentToolCallId: string | undefined;
    subagentName: string | undefined;
    pushContent: (e: TaskEvent) => void;
    emitCommitted: (entryId: string, role: "user" | "assistant" | "toolResult", toolCallId?: string) => void;
    startedToolCallIds: Set<string>;
    toolStartAt: Map<string, number>;
    /** The run's tool-progress lane: the handlers tell it when a call enters and leaves execution, and
     *  clear it with the rest of the per-call bookkeeping at turn end. */
    toolProgress: ToolProgressLane;
    writeFamilyOf: (name: string) => WriteFamily | undefined;
    toolLabels: Map<string, string>;
    postToolBatchHook: ((batch: PostToolBatchCall[], meta?: {
        injectedThisTurn: "final_verification" | "finalize";
    }, ctx?: import("../hooks.js").PostToolBatchContext) => PostToolBatchResult | undefined | Promise<PostToolBatchResult | undefined>) | undefined;
    batchArgs: Map<string, unknown> | undefined;
    runnerHooks: {
        onError: RunnerDeps["onError"];
        onNotice: RunnerDeps["onNotice"];
    };
    /** The run's ONE isolation scope for the {@link RunInternals} observation sinks, minted by the
     *  identity-wiring lane (which calls the first of them) and shared with these handlers, so the
     *  "first failure per site" disclosure is one account for the whole run rather than one per lane. */
    internalsNotifier: SafeNotifier;
}
/**
 * The Runner as the modules it drives and the compositions over it see it — the delegation tools
 * (Agent / SendMessage / AgentTranscript), the repair, verify, cascade, teacher and team loops, the
 * workflow and goal orchestrations, the scenario registry, the HTTP server, `warmResume`, and the
 * orchestrator's own trusted `runnerSelf` seat. Every one of them used to name the `Runner` CLASS
 * through `import type`, which is a type edge from below the runner back up into it — a cycle the
 * direction gate could only carry as an exemption, and one that reached the orchestrator through the
 * delegation tools (prepare-task → send-message-tool → runtask).
 *
 * THE MEMBER SET IS MEASURED, NOT DESIGNED: it is exactly the union of what those consumers read off a
 * Runner today (a TS-Program census over `src/`, pinned in test/runner-self-seat.test.ts so a member
 * nobody reads reds as dead weight and a consumer reaching for a member outside it reds as a widening).
 * `swapDeps`, `sideQuery` and the constructor are NOT here — no consumer reaches them; a host that
 * needs them holds the class. The class implements this interface, so the two cannot drift apart.
 *
 * Signatures are spelled out (not `Pick<Runner, …>`), because naming the class from here would be the
 * very edge this seat exists to remove.
 */
/**
 * The Runner's deployment deps as a LIVE read for the lanes that read them after an await (the resume ladder's
 * rungs). `Runner.deps` is not a constant: `swapDeps` replaces the object, so a lane handed the object at its
 * call would read a stale catalog — and a stale anything — where the one-function ladder read `this.deps.x` at the
 * instant of the read. The seat is the Runner's own view (a getter over the private field), so `runner.deps.x`
 * in a lane is exactly `this.deps.x` in the method it came from.
 */
export interface RunnerDepsSeat {
    /** The deps object the Runner holds RIGHT NOW — never cache it across an await. */
    readonly deps: RunnerDeps;
}
export interface RunnerSelfSeat {
    /** The session store this Runner was constructed over — the delegation tools acquire / release / fork through it. */
    readonly sessions: SessionStore;
    /** The deployment agent + model catalog, READ-ONLY (a fresh copy per read; the definitions are the shared objects); `roles` = `RunnerDeps.roles` (the delegation seat's `childModelSeat` reads `roles.subagent`). */
    readonly agentCatalog: {
        agents?: AgentDefinition[];
        builtinAgents?: boolean;
        models?: Record<string, Model>;
        roles?: ModelRoles;
    };
    /** The context-window overlay a leg on `ref` reads RIGHT NOW (`RunnerDeps.contextSettings`, tier aliases followed
     *  against the catalog in force): prepare hands its leg a call-time reader over it, because this is the one seat a
     *  running leg re-reads (geometry, not route — see `context-settings.ts`). */
    contextOverlayOf?(ref: import("../model-seat.js").ModelRef | undefined): import("../context-settings.js").ContextWindowOverlay | undefined;
    /** The deployment-level gating baseline (deps toolPolicy / hooks) as delegate wrappers — for assemblers that compose with it. */
    readonly gateBaseline: {
        toolPolicy?: RunnerDeps["toolPolicy"];
        hooks?: RunnerDeps["hooks"];
    };
    /** Fire `RunnerDeps.onTaskOutcome` through the swallow-guarded chokepoint (thin compositions emit at their terminal state). */
    emitTaskOutcome(outcome: TaskOutcome): void;
    /** Run a task to completion. `internals` is the TRUSTED run-scoped channel (never a TaskSpec field). */
    runTask(spec: TaskSpec, internals?: RunInternals): Promise<TaskResult>;
    /** Streaming form of {@link RunnerSelfSeat.runTask}; `resume` is the run loop's resume plan, `internals` the trusted channel. */
    runTaskStream(spec: TaskSpec, resume?: ResumeRun, internals?: RunInternals): TaskStream;
    /** Resume a suspended task from its checkpoint token with the caller's decision, to completion. */
    resume(token: CheckpointToken, outcome: ResumeOutcome, taskConfig: ResumeTaskConfig, internals?: RunInternals): Promise<TaskResult>;
    /** Streaming form of {@link RunnerSelfSeat.resume}: the pre-CAS guards and the CAS run first, then the live stream is returned. */
    resumeStream(token: CheckpointToken, outcome: ResumeOutcome, taskConfig: ResumeTaskConfig, internals?: RunInternals): Promise<TaskStream>;
}
/**
 * The orchestrator's entry (`prepareTask`, prepare-task.ts) as ONE contract: the notification lane's
 * `prepareTask` seat names it, and the orchestrator pins its own declaration against it (`PrepareTaskIsTheContract`), so a
 * signature change reds at the declaration — not at the driver's hand-in, and not as a seat spelled a second time. It lives
 * on the floor because a lane may not name the orchestrator (docs/LAYERING.json: layer 4 sits above the lanes). Positional,
 * exactly as the entry is declared — the seat carries the orchestrator's function itself, not a lane Input.
 */
export type PrepareTaskFn = (spec: TaskSpec, deps: RunnerDeps, sessions: SessionStore, resume?: PrepareResume, internals?: RunInternals, runnerSelf?: RunnerSelfSeat, runIdSink?: {
    runId?: string;
}, holdSessionLock?: (sessionId: string) => Promise<void>) => Promise<Prepared>;
/**
 * The Result of an INSTALLING phase: a lane whose every product is installed on a seat it borrowed
 * (the harness's stop gate and recovery chain, the run state's counter groups, the stream's result setter, the claimed
 * row's registries) hands nothing back, and says so by extending this marker. The marker is machine-read by gate:phase-api
 * a Result with no members MUST extend it, a Result with members MAY NOT, and no Result
 * inherits anything else — so an empty Result is a stated fact about the phase, never an interface someone forgot to
 * fill, and a phase that starts handing a product back must drop the declaration in the same edit. Six lanes carry it:
 * the attachment seats, the stop gate, the recovery lanes, the settle and teardown (run-), the preflight and the claim
 * (resume-).
 */
export interface InstallingPhaseResult {
}
/** The live-task handle `runLocked` publishes once the harness exists: the harness + abort
 *  controller, the loop-liveness latch (`ended` flips when the single `harness.prompt` settles; `userInterrupted`
 *  / `userHalted` are the interrupt and halt verbs' attribution seats), the run's reminder mark, its session and
 *  engine-minted run id, and the hook bound + identity envelope the steer entrance screen runs under. */
export interface LiveHandle {
    harness: AgentHarness;
    abortController: AbortController;
    loop: {
        ended: boolean;
        userInterrupted: boolean;
        userHalted: boolean;
    };
    reminderMark: string;
    sessionId: string;
    /** The run body's engine-minted run id, so the stream-layer verbs can name the run their disclosures are about. */
    runId: string;
    hookTimeoutMs: number;
    hookIdentity: HookInvocationIdentity;
    /** The leg's live permission-mode seat (`TaskStream.setPermissionMode` steers it). */
    permissionMode: PermissionModeController;
}
/** The run body's backstop CARRIER: the effective ids and the post-prepare
 *  observations the run body publishes as they are minted, so the stream layer's failure backstop names the
 *  same run, session and observations the frames the run body already emitted did. */
export interface TaskIdRef {
    current?: string;
    sessionId?: string;
    runId?: string;
    effectiveMemoryScopes?: TaskResult["effectiveMemoryScopes"];
    effectiveReasoning?: TaskResult["effectiveReasoning"];
    delegationTerminalOwed?: HookInvocationIdentity;
    editedFiles?: () => TaskResult["editedFiles"];
}
/** The manual `/compact` request seat: the `requested` flag, the parked waiters (each with its
 *  caller's cancel signal and per-call instructions), the run body's mooted-frame channel and the registration
 *  gate the run-end backstop closes before its final drain. */
export interface ManualCompactRef {
    requested: boolean;
    waiters: Array<{
        resolve: (outcome: CompactOutcome) => void;
        signal?: AbortSignal;
        instructions?: string;
    }>;
    emitMooted?: (reason: string) => void;
    closed?: boolean;
}
/**
 * The end-of-task compaction pass's outcome (`Runner.finish`'s return): what the terminal-adoption lane
 * hands the settle lane, which reads it for the `compacted` frame, the phase-timings frame and the detector reset. Named
 * once here so the two lanes and the method spell one type.
 */
export type EndOfTaskCompaction = {
    compacted: boolean;
    tokensBefore?: number;
    freedTokens?: number;
    triggerTokens?: number;
    postTriggerTokens?: number;
    durationMs?: number;
    phaseDurations?: CompactionPhaseDurations;
    firstKeptEntryId?: string;
    attachedFiles?: Array<{
        path: string;
        chars: number;
        truncated: boolean;
    }>;
    modelFallback?: true;
    fallbackReason?: "window";
    clampedRatio?: number;
    clampReason?: "budget" | "tolerance";
} | undefined;
/**
 * The notification lane's BINDINGS: the run-notification-lane's own four `let`s as ONE seat (getters
 * and setters over the lane's variables, never a copy). The identity-wiring lane binds `harness` / `sessionId` /
 * `ident` the moment `prepared` exists (the lane's closures read them by variable — the routing listener, the park
 * destination, the injection entry); the leg lane flips `live` in its finally, from which point every notification
 * parks per session for the next run. One seat, four slots; nothing copies a binding out of it.
 */
export interface NotificationLaneBindings {
    harness: AgentHarness | undefined;
    sessionId: string | undefined;
    ident: () => {
        eventId?: string;
        parentToolCallId?: string;
        sourceTaskId?: string;
    };
    live: boolean;
}
/** The `notify()` bridge: `runLocked` binds `inject` the moment the task-notification lane exists. */
export interface NotifyRef {
    inject?: (n: TaskNotificationPayload, opts?: {
        priority?: SystemInjectionPriority;
    }) => void;
}
/** The capture opt-out flip verb's binding; bound only when the run mounted a memory session. */
export interface CaptureOptOutRef {
    flip?: (reason?: string) => Promise<{
        outcome: "created" | "existed";
    }>;
}
/** The MCP re-dial verb's binding; bound only when the run declared MCP servers — a leg with
 *  none has nothing to re-dial, and the stream verb then refuses TYPED rather than answering about a
 *  materialization that was never made. Same shape and same law as {@link CaptureOptOutRef}. */
export interface McpRedialRef {
    redial?: (server: string) => Promise<import("../mcp.js").McpReconnectResult>;
}
/**
 * The stream's LIVE state as the verb lanes read it — a view over the driver's own locals (getters, and one
 * setter), never a copy: `resultValue` is written by the run body's `setResult` callback and by the failure
 * backstop, `handle` by the run body's `onReady`, `reapHandle` by its `onSuspend`; every lane reads each of them
 * at the instant of its own read, exactly as the one-function façade read the closure variables.
 */
export interface TaskStreamLiveSeat {
    /** The assembled result once the run settled (the run body's `setResult`, or the failure backstop's mint). */
    resultValue: TaskResult | undefined;
    /** The live-task handle once `runLocked` published it; `undefined` before, and forever when prepare threw. */
    readonly handle: LiveHandle | undefined;
    /** What `destroy()` reaps when the run SUSPENDED; unset for every other terminal state. */
    readonly reapHandle: SuspendReap | undefined;
}