import { AgentHarness, type ThinkingLevel } from "../../internal/harness.js"; import type { Model } from "../../internal/llm.js"; import { type MaterializedMcp } from "../mcp.js"; import { MemoryEngine } from "../memory-engine/engine.js"; import type { HarvestReport, MemorySessionHandle } from "../memory-engine/types.js"; import { Session } from "../session.js"; import type { SessionStore } from "../session.js"; import { SubagentRetainLedger } from "../../agents/subagent.js"; import type { ToolPolicy } from "../tool-policy.js"; import { type ActiveSkillFrame } from "./active-skill-scope.js"; import { CacheBreakDetector, type ToolFingerprintInput } from "../cache-break-detector.js"; import { type OutputRef, type BlockedRef } from "./synthetic-tools.js"; import { type ConsolidationNote } from "./memory-consolidation.js"; import type { TaskNotificationPayload } from "../task-notification.js"; import { type CwdRef } from "../../tools/fs/index.js"; import type { Runner } from "./runtask.js"; import { type CheckpointGate, type CheckpointState, type CheckpointToken, type ResourceLedger, type ResourceLimitReason } from "../checkpoint-store.js"; import type { AgentTool, ExecutionEnv } from "../../internal/harness.js"; import type { ModelRole, RunnerDeps, TaskEvent, TaskSpec, ToolActivity, ToolEffect } from "../types.js"; import type { RepairBundle } from "../../agents/repair-loop.js"; /** Resolved memory-consolidation state for a task (design/41), present only when consolidation is active. */ export interface PreparedConsolidation { /** Memory scope to reconcile — the resolved `writeScope` (design/84 Seam A: the single layer ALL writes * route to), never untrusted input. Consolidation only runs when `writeScope !== null` (council F7/Q2). */ scope: string; /** This task's saved notes, collected by the `remember` tool during the run. */ notes: ConsolidationNote[]; /** Resolved settings (defaults applied) the Runner uses for the task-end pass. */ settings: { role: ModelRole; band: { lo: number; hi: number; }; searchLimit: number; maxNotes: number; timeoutSec: number; }; } /** design/77 §4.4: the multi-tenant scope used when a durable suspend fires for an IRREVERSIBLE tool in an * unattended deployment that did NOT opt into `durableApproval` (so there is no caller-supplied scope). The * checkpoint carries this scope; resume reads it back from the checkpoint (`cp.scope`), so it is * self-consistent without needing the original `TaskSpec`. Kept distinct from any tenant key to make an * unattended irreversible suspend auditable as such. */ export declare const DEFAULT_IRREVERSIBLE_SCOPE = "irreversible"; /** Everything the run loop needs, built once by {@link prepareTask} (task setup, isolated from the loop). */ export interface Prepared { harness: AgentHarness; session: Session; sessionId: string; /** 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; 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; }; /** design/134 复审: 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; /** Summed usage of nested sub-runs (sub-agents) spawned by this task's tools. */ nestedStats: { tokens: number; turns: number; tasks: number; costUsd: number; costMicroUsd: number; }; /** design/99 §E13 — 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; /** design/99 §E6 — the DENY-NARROWING layers (session rules + skill scope, deny-only). Re-checked on RESUME * before an approved pending tool executes, so a rule tightened during the suspend still applies. */ denyNarrowingPolicy?: ToolPolicy; /** Removes the `spec.signal` abort listener on task end (else a long-lived signal leaks listeners). */ releaseSignal: () => void; /** Per-task prefix-cache-break detector (design/31). The fingerprint is mutated when deferred tools * materialize (design/36) — 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 design/31 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), `"legacy"` (free-form `PromptProvider.system`). Deployments/tests assert * on this to make "which blocks were in the prompt" a runtime fact instead of archaeology. */ promptManifest: { constitution: "core" | "replaced" | "provider-assembled" | "legacy"; blocks: Array<{ id: string; chars: number; hash: string; }>; }; /** Deferred-tool disclosure (design/36): the monotonic set of activated deferred-tool names. Empty * (and no `tool_search` injected) when nothing is deferred. Mutated by `tool_search` across the run. */ activeTools: Set; /** @deprecated design/138 S4 — legacy memory consolidation (design/41) retired with the memoryStore * path: prepareTask never produces this anymore (always `undefined`; runtask's reconcile pass is * dead-by-input). Field kept one major so the runtask consumer compiles; removal is the next major. */ consolidation?: PreparedConsolidation; /** * design/138 S1 — the MemoryEngine session (present iff `deps.memoryBackend` + `spec.memory.enabled`). * `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 (design/142 S2b: 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; }; /** A per-task env minted by `RunnerDeps.executionEnvFactory` (design/48 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; /** design/45: a mutable holder the durable-suspend gate writes when a policy `ask` was checkpointed * (capture + abort). The run loop reads it to assemble `status:"suspended"`. Empty unless a suspension * fired this run. */ suspendRef: { token?: CheckpointToken; gate?: CheckpointGate; scope?: string; }; /** design/76 §2.5 (dry-run / shadow) + design/80 D-B (plan-gate): the DUAL of {@link suspendRef} for the * REVIEW-PAUSE family — a `{kind:"needs_review"}` pause (a profile's dry-run interception committed a * checkpoint whose predicted state-diff a human/judge must REVIEW) OR a `{kind:"plan_review"}` pause (a * profile's plan-gate committed a checkpoint whose proposed PLAN a human must approve/edit/reject). The * commit-side discriminant (`publishCommittedSuspend`) writes HERE for a `needs_review` OR `plan_review` * gate and into {@link suspendRef} for every other gate kind — **never both** (else assemble-result's slot * 8.6 `needs_review` branch is dead code, v4 MAJOR-A). The run loop reads it to assemble * `status:"needs_review"`. Empty unless a review pause fired this run. */ reviewRef: { token?: CheckpointToken; gate?: CheckpointGate; scope?: string; }; /** design/72 §2.2 (B): 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; }; /** design/74 Slice 3c: 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 (sets `suspendRef`); 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; }) => Promise; /** design/130 P1+P2 — present (≠ undefined) ONLY when `timeoutSec` is set and `callCapByDeadline` * isn't disabled. prepare-task closes the harness's per-call cap provider over it; the run loop * ARMS it (sets `deadlineMs`) once the task clock starts — and only for a NON-resource-suspend * task (a slice boundary is not a delivery deadline, codex ⑤) — and feeds call/tool samples. */ callCapRef?: import("./call-cap.js").CallCapRef; /** design/80 D-B — 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; }; }; /** design/80 D-B: 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"`, routes to `reviewRef`) + 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; /** design/74 Slice 4: 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; /** design/80 D-E-core (A3): 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; }; }; /** design/91: 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 (design/91 §1). */ humanReviewRef: { count: number; totalWaitMs: number; gates: Array<{ kind: string; waitMs: number; decision?: string; toolName?: string; toolArg?: string; }>; }; /** design/91: 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; /** design/45 resume: 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. */ tools: AgentTool[]; /** Name→effect map for every tool this task can call (design/44 §3). Used by the abort-path orphan * reconcile (design/64 §9) to make interrupted tool_results effect-aware (read/idempotent = safe to * repeat; write/unknown = outcome unknown). Unknown names default to `write` (conservative). */ toolEffects: Map; /** 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 — design/64 §26.7). */ promptOverheadTokens: number; /** 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. */ readTaskFile?: (path: string) => Promise; /** Blackboard 2026-07-03 (CC post-compact restore parity): 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). */ recentlyReadFiles?: () => string[]; /** design/121: 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; }; /** design/133 件④: the live plan-mode flag (`enter_plan_mode` flips it, run-local one-way). Exposed * so the run loop's plan-mode attachment producer reads the SAME flag the write-deny enforces — * never a second source of truth. Always present (`active:false` when plan mode is unused). */ planModeRef: { active: boolean; }; /** design/133 F5 (§R3 决议): 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 通告层 [482] — deferred tools MATERIALIZED (design/36 rematerialize) 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 only when the task has deferred tools at all. */ toolsDeltaRef?: { pending: 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. `seedAnnounced` = a durable-resume leg: the run loop MAY seed the * producer's announced map with these entries — 1.256 复审 MED-3②: only after CONFIRMING a listing * reminder actually rides the prior legs' transcript (a first leg that suspended before its first * clean boundary never announced; unconfirmed ⇒ the resume leg re-announces the initial listing). * Cross-leg roster drift is deliberately not delta-announced. 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; }; /** G1 通告层 [482] — 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; }>; /** design/122 D1 — 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; /** design/84 Seam C: 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; }; /** design/123 D4 — 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; }; /** @deprecated design/138 S4 — the dynamic re-recall seam (design/86 §3) served ONLY the legacy * memoryStore injection path, which is retired: prepareTask never wires this anymore (always * `undefined`; runtask's re-recall turn hook is dead-by-input). Field kept one major so the * runtask consumer compiles; removal is the next major. */ dynamicRecall?: { selector: import("../memory-recall.js").MemorySelector; scopes: string[]; recentTools: string[]; surfacedIds: Set; surfacedKeys: Set; }; } /** * design/45 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; /** The §4.bis correctness-state snapshot to re-seed (activeTools / outputRef / nestedStats / * consolidationNotes / readFileState). */ seed: CheckpointState; /** design/72 §2.2 (B): 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; /** design/74 Slice 4: 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; /** design/91: 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: Array<{ kind: string; waitMs: number; decision?: string; toolName?: string; toolArg?: string; }>; }; /** design/49 v1.5: 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 (remote-env.ts:234, code-ready council round-2). */ workspaceHandle?: import("../remote-env.js").WorkspaceHandle; } /** * design/78 Slice-1 (MAJOR-3 wiring): 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, design/44 §7 Q4). 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). * * Today it carries 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 (design/76 §2.2#1 r4 MAJOR-A). Mirrors how `nestedStats`/`resume.seed` thread trusted * run-scoped internals through the Runner without touching `TaskSpec`. */ 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; /** * 🔴 design/97 §H.1 / design/98 §0.1 (BLOCKER3) — 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`/`inheritedManifestScope` 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; /** * design/110 — 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 `inheritedManifestScope`. */ insideFork?: boolean; /** * G1+G2 合车复审修② (1.259.0) — 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; /** * design/96 §C (S2) — 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 (codex r2 — injection-ownership split). */ goalMode?: boolean; /** * 🔴 design/77 §3 / §7 (ON-前必关) — skill→subagent manifest-scope PROPAGATION. The parent task's * ACTIVE skill-manifest frames, snapshotted at the moment a subagent was spawned WHILE a manifest scope * was live on the parent. The child's {@link prepareTask} seeds its own {@link ActiveSkillScope} from * these so the child inherits the parent skill's deny-narrowing — fail-closed and MONOTONIC: a child of * a manifested skill is AT MOST as capable as the manifest (its own manifests can only narrow further, * never re-grant a tool/path the parent removed). * * This is a TRUSTED, run-scoped channel filled ONLY by `createSubagentTool`'s `execute` (a core caller), * NEVER a {@link TaskSpec} field (TaskSpec is the untrusted-caller surface — design/44 §7 Q4). It mirrors * how `repairBundle`/`resume.seed` thread live per-task state the Runner cannot see from `spec`. * * Fail-closed: when the parent HAD an active manifest at spawn but the precise frames cannot be * snapshotted, the subagent tool threads a single DENY-ALL `unresolved` frame here rather than letting the * child run unmanifested — the safe path is the default. An empty/absent value = no inheritance (a * subagent spawned with no active parent manifest behaves exactly as before, backward-compatible). */ inheritedManifestScope?: readonly ActiveSkillFrame[]; /** * design/99 §E2 (service [241]/[242]) — 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 `inheritedManifestScope`/`workflowDepth`. Absent for a top-level (non-delegated) task. */ parentToolCallId?: string; /** * design/99 MF-10 / BC-2 (Service AI [§I 1.5.1]) — 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 — dual-review Q2). Untrusted (`taskName` is model-chosen) → * the consumer sanitizes via `inlineUntrusted` at emit. */ agentName?: string; /** * design/99 (nested-subagent live tree) — 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; /** * design/99 (nested-subagent live tree) — 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 forwards ONLY `task_progress` through it; the child stream is NEVER merged into the * parent's MODEL context (this is purely a render channel). Absent unless the deployment opted in. */ onForwardEvent?: (event: TaskEvent) => void; /** * design/115 P2 core slice — trusted run-local system-injection sink. `Runner.runLocked` wires this to the * live TaskStream queue plus the current harness follow-up lane; it is not a public TaskSpec field. */ /** design/116 detach(飞轮 [C]): 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; onTaskNotification?: (notification: TaskNotificationPayload, /** CC injection priority (design/116 §7): "next" = boundary interrupt (finished shell), "later" * (default) = deliver when the agent would otherwise stop (agent/workflow completions). */ opts?: { priority?: import("../task-notification.js").SystemInjectionPriority; }) => void; /** * design/97 CORE-6 — 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, design/44 §7 Q4). 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 * (clay) — the orchestrator script reads each worktree's result and decides verify/merge in userland. */ isolation?: "worktree"; /** * Blackboard 2026-07-03 (sub-agent cwd inheritance, CC parity): 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; /** * Blackboard 2026-07-03 (subagent steer verb, clay dogfood "中途插话"): 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 (fable impl-review F3, recorded): 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; /** * design/97 CORE-8 (③) — 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). * * **v1 scope (audit MINOR)**: reaches activity on a FRESH run only — the durable-resume entry (`resumeStream`) * does not thread `internals`, so a resumed leg emits no activity. The workflow display is unaffected (its * agents fail-on-suspend rather than durably resume). */ onActivity?: (activity: ToolActivity) => void; } export declare function prepareTask(spec: TaskSpec, deps: RunnerDeps, sessions: SessionStore, resume?: PrepareResume, /** @deprecated design/138 S4 — served only the legacy selective/dynamic recall paths (retired); * ignored. Positional slot kept one major so the runtask call site compiles unchanged. */ _memorySelector?: import("../memory-recall.js").MemorySelector, internals?: RunInternals, /** design/98 §3.1 (S8c): a TRUSTED self-reference to the Runner, passed by the Runner itself (never a * TaskSpec field) so the `run_workflow` tool can execute child tasks via `runner.runTask`. Undefined when * prepareTask is exercised standalone (then run_workflow is simply not mounted). */ runnerSelf?: Runner): Promise; //# sourceMappingURL=prepare-task.d.ts.map