@sema-agent/core 7.5.2 → 7.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/dist/agents/agent-transcript-tool.d.ts +2 -2
  3. package/dist/agents/cascade.d.ts +4 -5
  4. package/dist/agents/cascade.js +12 -10
  5. package/dist/agents/repair-loop.d.ts +7 -5
  6. package/dist/agents/repair-loop.js +13 -15
  7. package/dist/agents/retain-ledger.d.ts +2 -3
  8. package/dist/agents/send-message-tool.d.ts +2 -2
  9. package/dist/agents/session-util.d.ts +2 -2
  10. package/dist/agents/subagent.d.ts +27 -46
  11. package/dist/agents/subagent.js +119 -105
  12. package/dist/agents/suspend-guard.d.ts +31 -19
  13. package/dist/agents/suspend-guard.js +14 -8
  14. package/dist/agents/teacher.d.ts +2 -2
  15. package/dist/agents/teacher.js +9 -9
  16. package/dist/agents/team.d.ts +6 -5
  17. package/dist/agents/team.js +10 -8
  18. package/dist/agents/verify.d.ts +8 -9
  19. package/dist/agents/verify.js +17 -17
  20. package/dist/core/a2a.js +2 -1
  21. package/dist/core/agent-definition.d.ts +172 -0
  22. package/dist/core/agent-definition.js +1 -0
  23. package/dist/core/ask-origin.d.ts +60 -7
  24. package/dist/core/ask-origin.js +26 -1
  25. package/dist/core/checkpoint-store.d.ts +78 -76
  26. package/dist/core/checkpoint-store.js +17 -1
  27. package/dist/core/delegation-frames.d.ts +298 -0
  28. package/dist/core/delegation-frames.js +21 -0
  29. package/dist/core/engine-notice.d.ts +555 -0
  30. package/dist/core/engine-notice.js +55 -0
  31. package/dist/core/gate-fold.d.ts +12 -0
  32. package/dist/core/gate-fold.js +158 -0
  33. package/dist/core/gate-lanes.d.ts +93 -0
  34. package/dist/core/gate-lanes.js +626 -0
  35. package/dist/core/gate-outcome.d.ts +189 -0
  36. package/dist/core/gate-outcome.js +70 -0
  37. package/dist/core/hands-band.d.ts +134 -0
  38. package/dist/core/hands-band.js +1 -0
  39. package/dist/core/hooks.d.ts +22 -177
  40. package/dist/core/hooks.js +53 -851
  41. package/dist/core/mcp-failure.d.ts +142 -0
  42. package/dist/core/mcp-failure.js +145 -0
  43. package/dist/core/mcp-server-spec.d.ts +217 -0
  44. package/dist/core/mcp-server-spec.js +1 -0
  45. package/dist/core/mcp.d.ts +21 -77
  46. package/dist/core/mcp.js +76 -150
  47. package/dist/core/model-seat.d.ts +99 -0
  48. package/dist/core/model-seat.js +1 -0
  49. package/dist/core/pause-registry.d.ts +131 -0
  50. package/dist/core/pause-registry.js +27 -0
  51. package/dist/core/reminder-mint.d.ts +10 -0
  52. package/dist/core/reminder-mint.js +3 -0
  53. package/dist/core/runner/assemble-result.d.ts +32 -41
  54. package/dist/core/runner/assemble-result.js +55 -74
  55. package/dist/core/runner/contracts.d.ts +427 -69
  56. package/dist/core/runner/denial-limit-arms.d.ts +1 -1
  57. package/dist/core/runner/denial-limit-arms.js +3 -3
  58. package/dist/core/runner/gate-exit.d.ts +242 -0
  59. package/dist/core/runner/gate-exit.js +124 -0
  60. package/dist/core/runner/park-commit.d.ts +17 -23
  61. package/dist/core/runner/park-commit.js +14 -15
  62. package/dist/core/runner/prepare-ask-lane.d.ts +0 -3
  63. package/dist/core/runner/prepare-ask-lane.js +3 -5
  64. package/dist/core/runner/prepare-boundary-parks.d.ts +3 -6
  65. package/dist/core/runner/prepare-boundary-parks.js +3 -3
  66. package/dist/core/runner/prepare-caps-and-workflow.d.ts +2 -7
  67. package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
  68. package/dist/core/runner/prepare-delegation-surface.d.ts +2 -7
  69. package/dist/core/runner/prepare-gate-stations.d.ts +4 -7
  70. package/dist/core/runner/prepare-gate-stations.js +29 -54
  71. package/dist/core/runner/prepare-inherited-gate.js +1 -1
  72. package/dist/core/runner/prepare-memory.d.ts +44 -26
  73. package/dist/core/runner/prepare-park-ask.d.ts +2 -4
  74. package/dist/core/runner/prepare-park-ask.js +5 -5
  75. package/dist/core/runner/prepare-task.d.ts +2 -2
  76. package/dist/core/runner/prepare-task.js +8 -9
  77. package/dist/core/runner/prepare-wiring-manifest.d.ts +7 -15
  78. package/dist/core/runner/prepare-wiring-manifest.js +9 -10
  79. package/dist/core/runner/runtask.d.ts +20 -102
  80. package/dist/core/runner/runtask.js +119 -121
  81. package/dist/core/runner/terminal-projection.d.ts +22 -0
  82. package/dist/core/runner/terminal-projection.js +28 -0
  83. package/dist/core/runner-deps.d.ts +1416 -0
  84. package/dist/core/runner-deps.js +1 -0
  85. package/dist/core/runtime-caps.d.ts +164 -0
  86. package/dist/core/runtime-caps.js +1 -0
  87. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -1
  88. package/dist/core/store-contracts/checkpoint-store-contract.js +8 -2
  89. package/dist/core/task-event.d.ts +910 -0
  90. package/dist/core/task-event.js +1 -0
  91. package/dist/core/task-limits.d.ts +110 -0
  92. package/dist/core/task-limits.js +1 -0
  93. package/dist/core/task-result.d.ts +809 -0
  94. package/dist/core/task-result.js +1 -0
  95. package/dist/core/task-spec.d.ts +1370 -0
  96. package/dist/core/task-spec.js +1 -0
  97. package/dist/core/task-stream.d.ts +382 -0
  98. package/dist/core/task-stream.js +1 -0
  99. package/dist/core/terminal-cause.d.ts +137 -0
  100. package/dist/core/terminal-cause.js +9 -0
  101. package/dist/core/tool-policy.d.ts +43 -139
  102. package/dist/core/tool-policy.js +79 -112
  103. package/dist/core/tool-spec.d.ts +1174 -0
  104. package/dist/core/tool-spec.js +1 -0
  105. package/dist/core/types.d.ts +27 -7789
  106. package/dist/core/types.js +2 -76
  107. package/dist/core/warm-resume.d.ts +2 -2
  108. package/dist/core/wiring-manifest.d.ts +6 -3
  109. package/dist/core/workflow-journal-store.js +3 -4
  110. package/dist/engine/harness/agent-harness.d.ts +1 -1
  111. package/dist/index.d.ts +12 -8
  112. package/dist/index.js +9 -6
  113. package/dist/orchestration/builtin-workflows.d.ts +2 -2
  114. package/dist/orchestration/builtin-workflows.js +1 -1
  115. package/dist/orchestration/goal.d.ts +2 -2
  116. package/dist/orchestration/goal.js +8 -7
  117. package/dist/orchestration/run-spec.d.ts +2 -2
  118. package/dist/orchestration/run-spec.js +5 -3
  119. package/dist/orchestration/run-workflow-tool.d.ts +4 -4
  120. package/dist/orchestration/run-workflow-tool.js +4 -4
  121. package/dist/orchestration/workflow-governance.d.ts +4 -4
  122. package/dist/orchestration/workflow-governance.js +4 -2
  123. package/dist/orchestration/workflow-primitives.d.ts +1 -1
  124. package/dist/orchestration/workflow-primitives.js +1 -1
  125. package/dist/orchestration/workflow.d.ts +15 -4
  126. package/dist/orchestration/workflow.js +64 -39
  127. package/dist/prompts/supervisor.d.ts +1 -1
  128. package/dist/prompts/supervisor.js +3 -3
  129. package/dist/scenarios/scenario-registry.d.ts +3 -3
  130. package/dist/scenarios/scenario-registry.js +1 -1
  131. package/dist/scenarios/teacher-quickstart.d.ts +2 -2
  132. package/dist/server/http.d.ts +2 -2
  133. package/dist/stores/file/fs-atomic.d.ts +88 -12
  134. package/dist/stores/file/fs-atomic.js +184 -55
  135. package/dist/stores/file/index.d.ts +1 -0
  136. package/dist/stores/file/index.js +1 -0
  137. package/package.json +3 -1
  138. package/test/export-surface.snapshot.json +82 -22
@@ -0,0 +1,1370 @@
1
+ /**
2
+ * The task INPUT surface: `TaskSpec` — everything a caller hands `runTask` — plus the two shapes that
3
+ * exist only as one of its fields (`ImageInput`, `StaleToolResultOffloadOptions`). The limits live
4
+ * beside it in `task-limits.ts`; the terminal record it produces in `task-result.ts`. Layer 0
5
+ * vocabulary; `types.ts` re-exports the three, so no consumer's import changes.
6
+ */
7
+ import type { TSchema } from "typebox";
8
+ import type { ThinkingLevel } from "../internal/harness.js";
9
+ import type { ActorAssertion, Model, ResilienceOptions } from "../internal/llm.js";
10
+ import type { AgentDefinition, SkillSpec } from "./agent-definition.js";
11
+ import type { A2aServerSpec, McpServerSpec } from "./mcp-server-spec.js";
12
+ import type { ModelRef, ModelRole, ModelRoles } from "./model-seat.js";
13
+ import type { TaskLimits } from "./task-limits.js";
14
+ import type { ToolSpec } from "./tool-spec.js";
15
+ /** design/169-F — knobs for the opt-in stale tool-result offload (see
16
+ * `TaskSpec.compaction.staleToolResultOffload`, where the semantics are documented). */
17
+ export interface StaleToolResultOffloadOptions {
18
+ /** How many most-recent results PER TOOL NAME stay verbatim in the request. Default 3. */
19
+ keepRecentPerTool?: number;
20
+ /** Minimum chars a replacement must save (raw text − pointer text) to happen. Default 2000. */
21
+ minSavingsChars?: number;
22
+ }
23
+ /** An image attached to the task's user message: either inline base64 or a URL to fetch. */
24
+ export type ImageInput = {
25
+ /** Base64-encoded image bytes (no data: prefix). */
26
+ data: string;
27
+ /** MIME type, e.g. "image/png" / "image/jpeg". */
28
+ mimeType: string;
29
+ } | {
30
+ /** URL the runner will fetch and inline as base64 before sending to the model. */
31
+ url: string;
32
+ };
33
+ /** A single self-contained task with its full configuration. */
34
+ export interface TaskSpec {
35
+ taskId?: string;
36
+ /** The instruction / latest user message for this turn. */
37
+ objective: string;
38
+ /**
39
+ * Authenticated **end-user principal** this task runs on behalf of (design/62) — an opaque identity string
40
+ * (user id, email, tenant-scoped subject) the **trusted caller** sets at task creation. The Runner holds it
41
+ * for the whole run and injects it into MCP requests for any server with a `principalHeader`
42
+ * ({@link McpServerSpec}), enabling per-user RBAC in the MCP server. Delegated child tasks (sub-agent / team /
43
+ * fan-out) inherit it.
44
+ *
45
+ * 🔴 **Trust contract (design/53 — authority stays in the control plane):** this is a control-plane value,
46
+ * **NOT model/worker input.** Set it only from the authenticated task-submission context; it is not exposed
47
+ * as a tool argument and the model cannot read or change it (a worker filling its own principal = a privilege
48
+ * circumvention). Treat it as write-once for the run. The MCP server MUST trust this header only on the Runner's
49
+ * authenticated/TLS channel and default to deny/public when it is absent — never default-admin.
50
+ *
51
+ * 🔴🔴 **This is the GOVERNANCE key.** Per-principal entitlements are resolved off this value:
52
+ * {@link RunnerDeps.runtimeCapsResolver}`(spec.principal)` decides `allowWorkflows` (and thus whether
53
+ * self-orchestration / `RunWorkflow` is permitted). So a caller MUST set `spec.principal` to a
54
+ * **cryptographically VERIFIED** identity — **never a raw, spoofable request header**. A deployment that holds
55
+ * both an unverified header value AND a verified one (e.g. a "direct-door" path) MUST pass the VERIFIED one
56
+ * here: keying a gate on a spoofable principal is a multi-tenant governance BYPASS (a caller-declared header would
57
+ * inherit another tenant's entitlements). Core cannot verify identity itself (no crypto context) — it trusts
58
+ * this value by contract; getting it wrong is a privilege-escalation hole, not a core bug.
59
+ */
60
+ principal?: string;
61
+ /**
62
+ * design/171 §6.4 — WHO opened this turn (the thread's first-post author in a shared session):
63
+ * the CANONICAL attribution seat for the objective. Rendered as the speaker envelope on the first
64
+ * user message (single projection point) and persisted as `UserMessage.actor`; absent = anonymous
65
+ * (every single-user host — the model-facing bytes are then unchanged).
66
+ *
67
+ * Attribution ONLY, never authority: permission/approval/scope decisions key on
68
+ * {@link TaskSpec.principal}, never on this (the normative clause on {@link ActorAssertion}).
69
+ * Relationship to `clientContext.userEmail`: that field keeps its environment-facts role (an
70
+ * UNVERIFIED display string in the env block) and carries no attribution duty — this seat is the
71
+ * honest one, because `hostAsserted` states whether the identity was ingress-derived.
72
+ */
73
+ actor?: ActorAssertion;
74
+ /**
75
+ * design/173 §3/§8.3 — the caller's DECLARED interaction posture for this run. `"interactive"`
76
+ * asserts a live human is reachable and makes prepare REFUSE the leg (fail-loud
77
+ * `config.interaction_posture`) when the assembly cannot deliver on it: it requires the resolved
78
+ * permission-ask seat to be a live approver callback (`ask.effective === "human_reachable"` — a
79
+ * blanket allow/deny or an absent seat fails the door) AND a content-question channel (a resolved
80
+ * `onQuestion` face, OR the engine's own `stripped_bg_lane` legs — core's deliberate strip of a
81
+ * long-lived child's per-request face is correct design, not a configuration lie, so those legs
82
+ * are exempt). The park lane is deliberately NOT required (interactive-without-park is a
83
+ * legitimate light deployment — a synchronous CLI/local UI). `"headless"` declares the opposite
84
+ * and checks nothing (a batch deployment carrying an onAsk for audit is legal). Absent = no
85
+ * check, bytes unchanged.
86
+ *
87
+ * Seat resolution is THREE-leg: `spec.interactionPosture ?? <the spawning run's resolved posture>
88
+ * ?? deps.interactionPosture` — a Runner serving both an interactive session and cron/batch legs
89
+ * declares at deps level and overrides per spec, while a delegated child inherits the posture its
90
+ * SPAWNER resolved (carried on trusted engine internals, never copied onto the child's spec). The
91
+ * middle leg is load-bearing: without it a root that overrode a deps-level "interactive" with
92
+ * "headless" spawned children that fell back to the deps default and were refused at their own door.
93
+ * TRUST BOUNDARY (§8.3): only the HOST-authored spec carries this (spec is the deployment-face
94
+ * input in this library's trust model); the engine never copies it onto the child specs it
95
+ * constructs (a child's lane is the engine's decision — the strip flag is its expression), and a
96
+ * RESUME leg re-resolves posture from the CURRENT deps/taskConfig — never from the checkpoint.
97
+ * This door is an HONESTY gate, not a security gate.
98
+ *
99
+ * Runtime side (design/173 §8.2): under `"interactive"` BOTH no-answer arms of a WIRED question
100
+ * channel harden into coded tool failures instead of the silent synthetic self-answer continuation —
101
+ * a channel that THROWS returns `question.human_channel_failed`, and one that honestly reports nobody
102
+ * reachable returns `question.human_unavailable`. See {@link interactiveQuestionFallback} for the
103
+ * explicit opt-out, which covers both arms.
104
+ */
105
+ interactionPosture?: "interactive" | "headless";
106
+ /**
107
+ * design/173 §8.2 — the EXPLICIT knob (default off) letting a posture-`"interactive"` run
108
+ * CONTINUE with the synthetic self-answer instruction after all on BOTH hardened question arms
109
+ * (the disclosure card + onError leg still fire). Without it those arms return the coded
110
+ * `question.human_channel_failed` (channel threw) / `question.human_unavailable` (channel reported
111
+ * nobody reachable) tool errors. Meaningless (ignored) outside posture `"interactive"` — headless
112
+ * runs already continue.
113
+ */
114
+ interactiveQuestionFallback?: boolean;
115
+ /** Optional images attached to this turn's user message (model must support vision). */
116
+ images?: ImageInput[];
117
+ /** Reuse an existing in-memory session for chat continuity; omit to start fresh. */
118
+ sessionId?: string;
119
+ /**
120
+ * design/114 Phase3 — with `sessionId` set, REQUIRE that session to already exist: if it is gone (e.g. a
121
+ * non-durable store lost it across a restart, or a typo'd id), the run FAILS LOUD (`resume.session_not_found`)
122
+ * instead of silently creating a fresh empty session and running with NO warm context. Use this for a
123
+ * reuse-style warm-resume (`runTask({sessionId, requireExistingSession: true})`) so "looks warm, actually
124
+ * fresh" can never happen. (The `fork`-first path via `warmResume` is already fail-loud without this flag.)
125
+ * Ignored when `sessionId` is absent. Default `false` = today's create-on-miss behavior.
126
+ */
127
+ requireExistingSession?: boolean;
128
+ /**
129
+ * Per-request semantics — this SUBMISSION is one-shot:
130
+ * no later turn exists in which an async background notification could land (the archetypal case is a
131
+ * headless `sema -p` invocation, whose process exits once this turn ends). Per-request rather than
132
+ * per-connection/per-process on purpose: "does this particular submission expect to be steered/continued"
133
+ * is a property of the submission, not of the channel it arrived on — a persistent connection can still
134
+ * mix interactive and one-shot submissions, which a connection-level flag could not express.
135
+ *
136
+ * Currently consumed by the `RunWorkflow` tool's guidance text only (RB-215 candidate ①):
137
+ * a workflow started from a one-shot submission is told to actively block-wait via
138
+ * `TaskOutput({ block: true })` before ending its turn, instead of the default "end your turn, you will
139
+ * be notified" guidance — the latter is actively wrong here (BGB drilldown case 2, `../sema-test/bench-bg/
140
+ * intel/drilldown-C101-B401-20260727.md`: a model that followed that exact guidance lost background
141
+ * results because the one-shot process had nothing left alive to receive the notification). Default
142
+ * `false`/absent = today's behavior (unchanged for interactive sessions). A deployment MAY thread it
143
+ * into other one-shot-sensitive decisions of its own.
144
+ *
145
+ * INHERITED down the delegation tree (only the `true` value, off the trusted
146
+ * {@link ToolExecuteContext.oneShot} seat): the property being described — "the process this
147
+ * submission runs in exits when the turn ends" — is a fact about the whole run tree, not about one
148
+ * level of it. A delegated child of a one-shot submission that launched its own background workflow
149
+ * was told to end its turn and wait for a notification that could not arrive, which is the exact
150
+ * lost-results shape this flag exists to prevent.
151
+ */
152
+ oneShot?: boolean;
153
+ /**
154
+ * design/112 C1/C5 — CLIENT-supplied USER facts (distinct from execution-env/container facts). In TOB the
155
+ * worker container runs UTC and has no idea who the user is or where they are; the client/REPL/gateway knows.
156
+ * It threads these so the `# Environment` block localizes "today" to the user's zone (else it annotates UTC)
157
+ * and names who the agent acts for. All optional; absent ⇒ current behavior (UTC date, no user line). This is
158
+ * the USER half of the env block — the WORKSPACE half (cwd/git/platform/shell) is probed THROUGH the
159
+ * ExecutionEnv (the container), never from here.
160
+ */
161
+ clientContext?: {
162
+ /** IANA time zone of the user (e.g. `Asia/Tokyo`) — localizes the env block's date. Invalid zones fall back
163
+ * to UTC (date AND annotation) rather than mislabeling — see `isValidTimeZone`. */
164
+ timeZone?: string;
165
+ /** The user's identity (e.g. email) — surfaced in the env block so the agent knows who it acts for. */
166
+ userEmail?: string;
167
+ };
168
+ /**
169
+ * design/99 §E18 (resume-at) — BRANCH the session at this prior entry BEFORE the turn: replay root→here,
170
+ * then run the new `objective` as the next turn (the shell's "rewind to this message, ask differently").
171
+ * Requires `sessionId`. **The id is a core `SessionTreeEntry.id`** — a persisted session-entry id — **NOT a
172
+ * live {@link TaskEventIdentity.eventId}** (eventIds are never persisted; a deployment that holds eventIds
173
+ * owns the eventId→entryId map). Must reference a `message`/`custom_message` entry, and NOT an assistant
174
+ * message that ends mid-tool-call (a SETTLED turn boundary — else the replay would orphan-close a done call).
175
+ * Non-destructive: recorded as a branch `leaf` entry, so prior leaves stay in the tree (re-resume any time).
176
+ * Mutually exclusive with a durable `runner.resume(...)` (the Runner rejects both at once).
177
+ */
178
+ resumeAt?: string;
179
+ /**
180
+ * design/99 §E18 — how {@link resumeAt} positions the new branch relative to the resolved entry.
181
+ *
182
+ * - `"at"` (default, and the behavior when this field is omitted): branch AT the entry — **inclusive**.
183
+ * The target message stays in the replayed context as the leaf; the new `objective` is the next turn
184
+ * after it. This is the "keep that message, continue differently from it" shape.
185
+ * - `"before"`: branch BEFORE the entry — **exclusive**. The leaf becomes the target's PARENT
186
+ * (`entry.parentId`). The target message and its whole subtree are OFF the new branch (still in the
187
+ * tree — non-destructive, re-resume any time).
188
+ *
189
+ * 🔴 Consumer guidance: a UI that truncates its transcript EXCLUSIVELY — CC-style rewind, where the
190
+ * selected user message is REMOVED from the transcript and refilled into the input box — MUST send
191
+ * `"before"`. With `"at"` the removed message silently remains the model's leaf, so the model keeps
192
+ * answering a message the user no longer sees (transcript and model context split).
193
+ *
194
+ * `"before"`-specific target rules (STRICTER than the mode-independent settled-boundary validation,
195
+ * which still runs first):
196
+ * - The target must be a plain USER message (`resume_at.before_target_not_user` otherwise). That is the
197
+ * exclusive-rewind product shape — the thing the UI removed is a user message — and it is the only
198
+ * target whose parent is guaranteed to be a settled replay point (an assistant or custom-message
199
+ * target's parent can be a mid-turn entry that wake-reconcile would mishandle).
200
+ * - The target must not be the session's FIRST message (`resume_at.before_root_unsupported`): branching
201
+ * before the root would append the new objective as a SECOND root entry and break the session log's
202
+ * exactly-one-root invariant (export/import validation rejects such a log). Product semantics:
203
+ * rewinding to before the very first message = start a NEW session. (A first-class branch-reset entry
204
+ * that could support this without a second root is a recorded design candidate, not built.)
205
+ *
206
+ * File-history interaction (design/381): with {@link restoreFiles}, the `"before"` file anchor is the
207
+ * target's OWN entry id first — history boundaries are keyed by a turn's initial entry and mean "file
208
+ * state when that turn began", so B(target) IS the before-state. When the target carries no boundary
209
+ * (a mixed-epoch session, or a non-turn-initial target), the anchor falls back to the nearest
210
+ * boundary-carrying ANCESTOR, so files and context land on the same side of the cut. If no such
211
+ * boundary exists, prepare fails loud (`rewind_snapshot.unresolvable`) instead of silently leaving
212
+ * the files at their pre-rewind state while the conversation moved back.
213
+ * Fail-loud: setting this without {@link resumeAt}, or an unknown mode value, rejects at prepare.
214
+ */
215
+ resumeAtMode?: "at" | "before";
216
+ /**
217
+ * Which model to run on. If omitted, resolved from the `modelRole` (default `"default"`) against
218
+ * `roles` / `RunnerDeps.roles`. Either `model` or a resolvable role must be available.
219
+ */
220
+ model?: ModelRef;
221
+ /** Which role to resolve when `model` is omitted. Default `"default"`. */
222
+ modelRole?: ModelRole;
223
+ /** Per-task/scenario role→model overrides (merged over `RunnerDeps.roles`, task wins). */
224
+ roles?: ModelRoles;
225
+ /** Optional cheaper model for compaction/summarization. Falls back to the `summarize` role if set.
226
+ * design/145 §1 semantics: used only while WINDOW-SAFE and budget/walltime allow — when its
227
+ * window would clamp-truncate the conversation past `compaction.clampTolerance`, the summary
228
+ * call falls back to the MAIN model (CC's unconditional posture; the independent compaction
229
+ * model is sema's superset) and the `compacted` event says so (`modelFallback`). Subagent
230
+ * declaration (design/145 §5): delegated children never inherit this field — a child always
231
+ * self-compacts on its own main model, so the window-safety judgment is structurally never
232
+ * triggered in a subagent (guard-tested). */
233
+ compactionModel?: ModelRef;
234
+ /** Thinking level. If omitted, a default from the resolved role's `RoleSpec.thinking` applies. */
235
+ thinking?: ThinkingLevel;
236
+ /** Preset system prompt for this task. */
237
+ systemPrompt?: string;
238
+ /**
239
+ * APPEND a caller block to the assembled system prompt, keeping the engine's base + harness/safety blocks
240
+ * (unlike `systemPrompt`, which REPLACES the base, and `promptProvider`, which rewrites the whole assembly).
241
+ * The lightweight user/scenario-extension seam (≈ CC `--append-system-prompt`): add task-specific rules,
242
+ * domain context, or a persona tweak without losing the neutral base, cyber/url safety, harness context, or
243
+ * memory discipline. CALLER-TRUSTED (same trust level as `systemPrompt`/`skills` — NOT user input). Composed
244
+ * as a STABLE block (prepare-time) at the END of the stable prefix, before the volatile `<user_memory>` tail,
245
+ * so it never breaks prefix-cache friendliness. Composes WITH `systemPrompt` (appended after it).
246
+ */
247
+ appendSystemPrompt?: string;
248
+ /**
249
+ * Per-task ROLE-LAYER prompt provider (overrides `RunnerDeps.promptProvider`). Lets one Runner serve
250
+ * different scenarios with different role prompts. 提示词主权批: `stableSystem` supplies the role base
251
+ * only — core appends the constitution (harness/safety/mode/memory blocks) structurally, so a provider
252
+ * cannot drop it by accident; `replaceAll: true` is the explicit full-replacement escape hatch.
253
+ * TRUST BOUNDARY: a provider is function-valued and therefore CODE — it can only be set by in-process
254
+ * deployment code, never by a wire-level (JSON) task submission; untrusted self-orchestration specs
255
+ * additionally have this field stripped by workflow governance. The `prompt.assembled` trace records
256
+ * which mode ran, so a `replaceAll` is always observable.
257
+ */
258
+ promptProvider?: import("../prompts/default.js").PromptProvider;
259
+ /**
260
+ * Enable long-term memory for this task. **design/138 S4 (BREAKING): requires `RunnerDeps.memoryBackend`**
261
+ * (the injection-first memory engine — materialize → file ops → harvest). With a backend configured the
262
+ * task gets the CC-verbatim `# Memory` instruction + fenced derived index and works memory through its
263
+ * ordinary file tools. WITHOUT a backend this field is INERT: the legacy `RunnerDeps.memoryStore` fallback
264
+ * (remember/recall tools + legacy `<user_memory>` injection) was retired — the task runs memory-less and
265
+ * one deprecation warning is emitted via `onError` (phase `"config"`).
266
+ *
267
+ * **design/84 Seam A** — single opaque scope → an **ordered, opaque scope list + a single writeScope**.
268
+ * The engine only does read-side **layered injection** (one `<user_memory>` block, a `<scope …>` subsection
269
+ * per scope, ordered by priority — the LAST scope is highest-priority and lands at the prompt TAIL) and
270
+ * write-side **single-point routing** (remember/consolidation only ever write `writeScope`; the model never
271
+ * picks a layer). Scope strings stay OPAQUE — the client/profile derives them (TOC: managed/user/project/
272
+ * agent/local; TOB: tenant hierarchy). The fields are normalized to one canonical shape
273
+ * ({@link import("./memory.js").normalizeMemorySpec}) — `writeScope` defaults to the LAST scope — so the
274
+ * consumer side reads one array with zero narrowing.
275
+ *
276
+ * Cache discipline (§5, implemented in prepare-task): the scopes are injected in list order, so the
277
+ * caller places STABLE layers (managed/user/org — rarely change) FIRST (cacheable prefix) and VOLATILE
278
+ * layers (project/agent/local) LAST (only the tail re-computes). The stable prefix is byte-stable.
279
+ *
280
+ * ONE form (RB-479-B②): `scopes` is the only spelling of the read layering. The singular `scope`
281
+ * field was removed, and a spec still carrying it is REFUSED at normalization
282
+ * (`config.memory_scope_spelling`) rather than read as "no scopes" — a silent memory-off task is the
283
+ * one outcome this field must never produce by accident.
284
+ *
285
+ * The accepted shape is {@link import("./memory.js").MemorySpecInput} (the SAME type
286
+ * {@link import("./memory.js").normalizeMemorySpec} consumes) — DESIGN-2 (council): typed by import rather
287
+ * than re-declared inline so the input contract and its normalizer can never silently diverge. Per-field
288
+ * semantics: `scopes` = ordered opaque layering (STABLE first / VOLATILE last; last = highest priority);
289
+ * `writeScope` = the single layer all writes land in (default = last scope; explicit `null` = read-only —
290
+ * no `remember` tool, consolidation off); `enabled` = master switch (default true).
291
+ */
292
+ memory?: import("./memory.js").MemorySpecInput;
293
+ /**
294
+ * design/100 §E12 — after the task COMPLETES, run ONE extra opt-in LLM pass to propose "next prompts" the
295
+ * user might send (the shell's follow-up suggestions). OFF/omitted = zero extra LLM calls, behavior unchanged.
296
+ * Fire-and-forget (never blocks the result); the suggestions are surfaced via {@link TaskStream.suggestions}
297
+ * and the (budget-excluded) cost via {@link TaskStats.suggestions}. `count` default 3, `role` default
298
+ * `"summarize"` (a cheap tier → falls back to the task model). The output is UNTRUSTED model text for the
299
+ * shell UI ONLY — never re-feed it to a model.
300
+ */
301
+ suggestNextPrompts?: boolean | {
302
+ count?: number;
303
+ role?: ModelRole;
304
+ };
305
+ /**
306
+ * design/381 (DV-9) — the RESTORE half of file rewind: when this task uses {@link resumeAt} to branch at
307
+ * a prior entry, ALSO converge the working tree's **agent-edited files** (the tracked set — files this
308
+ * session touched through Edit/Write/NotebookEdit; nothing else is ever visible to the history, so a
309
+ * user's parallel work is structurally untouchable) to the history boundary of that entry (pairs with
310
+ * E18: branch the session + restore the tracked files). SCOPE = THIS session: a delegated
311
+ * subagent runs under its own session and its edits land in its own history scope, so a parent
312
+ * rewind does not converge files only a subagent touched (the retired whole-tree restore did —
313
+ * a design/381 narrowing, disclosed here). Requires `RunnerDeps.fileHistoryStore`.
314
+ * Fail-loud: setting this without {@link resumeAt} rejects at prepare (`rewind.invalid_spec`) — a
315
+ * restore with no anchor is a contradiction, not a default.
316
+ *
317
+ * There is NO capture flag anymore: first-touch tracking is ALWAYS ON whenever a
318
+ * `RunnerDeps.fileHistoryStore` is wired (per-edited-file backups are near-zero cost — no tree walk,
319
+ * no size gate), which is what retired the old `rewindFiles` capture request. The retired
320
+ * `rewindFiles` spelling is handled at prepare: its capture sense (true, no `resumeAt`) is a
321
+ * tolerated no-op with a deprecation disclosure (the intent is structurally satisfied); its restore
322
+ * sense (true, with `resumeAt`) is a typed retirement rejection naming THIS field — the restore
323
+ * semantics genuinely changed (whole-tree ⇒ tracked set), so that migration must be explicit.
324
+ */
325
+ restoreFiles?: boolean;
326
+ /**
327
+ * design/381 (DV-15) — opt-in tolerance for a PARTIAL file restore: when the {@link restoreFiles} /
328
+ * {@link rewindFilesTo} convergence ends with ≥1 per-file refusal or failure (the others applied),
329
+ * the default is a loud terminal failure carrying the per-file ledger (`rewind.restore_failed`) —
330
+ * a silent continue would run the agent on a mixed-epoch tree without anyone knowing. Setting this
331
+ * `true` keeps the run going instead and discloses the same ledger as a `restore_partial` note on
332
+ * {@link TaskResult.rewindNotes} (the reference UI's "skipped files" seat in library form). The
333
+ * documented recovery either way: re-run the same restore — it is per-file idempotent and converges.
334
+ */
335
+ acceptPartialRestore?: boolean;
336
+ /**
337
+ * design/101 §E19 R8 + design/381 (CC Rewind `code`-only mode) — converge the tracked set (see
338
+ * {@link restoreFiles} for what that is and is not) to the history boundary of this prior entryId
339
+ * WITHOUT forking the conversation (no {@link resumeAt} leaf-branch). This is CC's "Restore code"
340
+ * mode (vs "Restore code and conversation" = {@link resumeAt}+{@link restoreFiles}, and "Restore
341
+ * conversation" = {@link resumeAt} alone). Legal ONLY when {@link resumeAt} is ABSENT — handing
342
+ * BOTH anchors is refused loud (`rewind.conflicting_targets`): two file targets is a
343
+ * contradiction, and silently preferring one is the silent-swallow family this seam refuses; a
344
+ * target with no history boundary fails loud (`rewind_snapshot.unresolvable`). Requires
345
+ * `RunnerDeps.fileHistoryStore`. The session leaf is untouched — the next turn continues the
346
+ * CURRENT conversation, only the files moved.
347
+ *
348
+ * A same-process child THE AGENT TOOL SPAWNED FRESH (the legs that are handed a lineage — see
349
+ * `ToolExecuteContext.fileHistoryLineage` for the exact set, and for the legs that are not) which
350
+ * shares its root session's tree records its edits into the ROOT session's history, so a rewind
351
+ * that should cover those edits is the root session's action; a restore request on such a child run
352
+ * is refused loud (`rewind.child_scope_unsupported`) rather than converging a scope that holds none
353
+ * of its edits. A delegated child that was handed NO lineage — a revival, a workflow/team member —
354
+ * keeps its own scope: its edits are outside this target's reach, and its own restore request is an
355
+ * ordinary one.
356
+ */
357
+ rewindFilesTo?: string;
358
+ /**
359
+ * Native tools available to this task.
360
+ *
361
+ * NAME OCCUPANCY IS A DECLARATION: a caller tool that takes a built-in's reserved wire name
362
+ * shadows that built-in (the hands band name-shadows caller tools the other way around only for
363
+ * its own mount), and the engine reads the occupancy itself as the caller declaring it will serve
364
+ * that CHANNEL — most concretely, a tool named `Write` on this roster counts as the memory write
365
+ * channel, so the `# Memory` write instruction (and its index read-seed) is supplied whenever a
366
+ * `Write` is mounted and not excluded, whatever the tool's `execute` actually does (the engine
367
+ * cannot read execute semantics; the NAME is the only judgeable fact). A shadowing mount is also
368
+ * surfaced to the operator as a config-phase warning. Protocol tools (MCP/A2A) can never collide
369
+ * here: their wire names are namespaced (`mcp__<server>__…`) and a caller name containing `__`
370
+ * is rejected at prepare.
371
+ *
372
+ * Mount point: the roster (with `defer`/`excludeTools`/`deferTools`) lives on this PER-TASK spec —
373
+ * the Runner constructor's deps carry no tool roster. Plain-JS callers beware: an unrecognized key
374
+ * passed to the constructor is dropped by ordinary object semantics (TypeScript callers get an
375
+ * excess-property error), so a roster placed there never mounts and its `defer` flags never apply.
376
+ */
377
+ tools?: ToolSpec[];
378
+ /**
379
+ * per-scenario tool ROSTER (true unmount, NOT a permission gate): wire names listed here
380
+ * are removed from the mounted tool set AFTER assembly — their schemas never reach `tools[]`
381
+ * (unlike a policy deny, which gates calls but still ships the schema bytes every request —
382
+ * measured at zero token savings). Exact wire-name match (aliases resolve to their
383
+ * canonical wire name at mount, so list the canonical name). Unknown names are ignored (a
384
+ * center-distributed roster may cover tools this deployment doesn't mount). The injected
385
+ * ToolSearch cannot be excluded (it mounts after this filter, only when deferred tools exist).
386
+ * ⚠️ Excluding structural tools (submit_output / ReportBlocked) can strand the task — the caller
387
+ * owns that risk, same trust level as assembling `spec.tools` itself.
388
+ */
389
+ excludeTools?: string[];
390
+ /**
391
+ * Per-request DEFERRED disclosure for ALREADY-MOUNTED tools (built-ins
392
+ * included — `ToolSpec.defer` only covers caller specs): wire names listed here ship as
393
+ * lightweight placeholders (name + one-line hint, schema bytes OFF the cache prefix) and are
394
+ * activated on demand via the injected ToolSearch (design/36 materialization — prefix untouched).
395
+ * "Default-on but not exposed" = the deployment lists the tool here on requests where no
396
+ * activation source fired (e.g. Workflow unless the user said "workflow"/"ultracode"); omitting
397
+ * the name on a later request mounts it fully (activation = not deferring). Unknown names are
398
+ * ignored. Names also in `excludeTools` are excluded (exclusion wins — nothing to defer).
399
+ */
400
+ deferTools?: string[];
401
+ /**
402
+ * design/168 (P3) — how a deferred tool's ACTIVATION reaches the provider-facing tools block.
403
+ * `"swap"` (default, prior behavior): ToolSearch activation swaps the placeholder for the full
404
+ * schema via setTools — the tools block changes mid-run, which invalidates the provider prompt
405
+ * cache for everything after it (tools serialize FIRST in the cache hierarchy). `"static"`: the
406
+ * tools block stays byte-identical for the whole run — placeholders never swap; the full schema
407
+ * reaches the model only through the ToolSearch result text (the existing delta-as-tool-result
408
+ * carrier), and argument validation against the REAL schema is unchanged (engine-side). Genuine
409
+ * roster changes (an MCP refresh adding/removing names) still propagate on either setting.
410
+ *
411
+ * DEFAULT `"swap"` (rolled back 2026-08-06 after a brief default-`"static"` window). `"static"` is
412
+ * a STRICT EXPLICIT OPT-IN, and the condition to opt in is a property of the whole serving route,
413
+ * not of one model: every model the run can reach — the prepare-time model, every cross-provider
414
+ * fallback target of the degrading brain, and any near-budget model hand-off — must be able to
415
+ * emit arguments it read from RESULT TEXT while the advertised wire schema stays empty. A model
416
+ * served with CONSTRAINED DECODING cannot: its sampler is bound by the advertised
417
+ * `{"type":"object","properties":{}}`. On the mainstream implementations — the ones that compile a
418
+ * grammar from the declared `properties` — that admits exactly one argument object, `{}`, so the
419
+ * engine's corrective rejection can never be acted on and the run burns turns instead of
420
+ * converging. (A decoder that instead reads the schema by strict draft semantics would treat the
421
+ * absent `additionalProperties` as permissive and let other keys through; the model still has no
422
+ * schema to form them FROM, so the best case there is guessing, not converging.) A protocol dialect
423
+ * is no guarantee either (an Anthropic-shaped endpoint may serve a constrained-decoding model),
424
+ * which is why the opt-in is the deployment's declaration that it verified the route, fallback
425
+ * targets included, rather than anything core can infer.
426
+ *
427
+ * Interlock with {@link TaskSpec.deferSelfResolve}: `"static"` requires the direct-call lane — a
428
+ * never-swapped placeholder with the lane disabled would make every deferred tool permanently
429
+ * uncallable. Explicitly pairing `"static"` with `deferSelfResolve: false` is refused at prepare
430
+ * (`config.tool_materialize_unreachable`); a `"static"` arriving from the env channel degrades to
431
+ * `"swap"` on such a task instead. A value outside the closed set refuses loudly
432
+ * (`config.tool_materialize_invalid`) — a typo must not silently select the opposite arm.
433
+ */
434
+ toolMaterializeStrategy?: "swap" | "static";
435
+ /**
436
+ * RB-400-a — the inline-pin counterpart of {@link deferTools} (same wire-name addressing, opposite
437
+ * direction; CC 220 `alwaysLoad` 对位): names listed here are NEVER deferred — not by
438
+ * `ToolSpec.defer`, not by the MCP constant-defer arm (besides the server's own
439
+ * `_meta["anthropic/alwaysLoad"]` declaration this list is the only channel that keeps a chosen
440
+ * MCP tool's full schema inlined), not by `deferTools`, not by `deferMode:"auto"`. Unknown names
441
+ * are ignored (the valve only subtracts). Names also in `excludeTools` stay excluded (exclusion
442
+ * wins — an unmounted tool has no schema to keep inline).
443
+ */
444
+ alwaysLoadTools?: string[];
445
+ /**
446
+ * design/277 — per-task restore valve for the tool-registration MODEL GATE. The gate trims
447
+ * default-mounted scaffold entries (tagged via {@link ToolSpec.modelGate}) from the roster when
448
+ * the task's resolved model id matches the gate table — this valve opts the task (and its whole
449
+ * delegation tree — the selector inherits down like `excludeTools`) back in:
450
+ * · an ARRAY of wire names — naming ANY tagged tool restores that tool's WHOLE class for this
451
+ * task (CC parity: opting into any member of the family restores the family). Judged against
452
+ * the full stamp set, so a name that `excludeTools` also lists still works as a class
453
+ * SELECTOR — while the exclusion itself still wins for that name (exclusion is the final
454
+ * valve; no restore channel resurrects an excluded name). Unknown names are inert (a
455
+ * center-distributed list may be a superset), same posture as `excludeTools`.
456
+ * · literal `true` — every gate class restored for this task (the shortest "this task wants
457
+ * all its scaffolding" spelling for center-distributed specs).
458
+ * A malformed value refuses at prepare (`config.tool_model_gate_invalid`, #123) — garbage must
459
+ * not silently read as "restore nothing" in the trimming direction. Absent = the gate's verdict
460
+ * stands. Explicit composition needs no valve: a hand-mounted (untagged) tool is never gated.
461
+ */
462
+ restoreGatedTools?: string[] | true;
463
+ /**
464
+ * RB-403 — whether a schema-VALID call on a still-deferred tool runs the real tool directly
465
+ * (activating it as a side effect). Default `true`, matching the upstream posture where the
466
+ * defer gate is shape validation, not activation state (a deferred tool called with correct
467
+ * arguments succeeds; activation only controls whether the model has SEEN the schema). Set
468
+ * `false` to restore the strict posture: every call on an un-activated placeholder is rejected
469
+ * with the ToolSearch teaching message regardless of argument shape. Governance is unaffected
470
+ * either way — policy/effect/irreversibility gates key on the tool NAME maps and run upstream
471
+ * of any execute.
472
+ */
473
+ deferSelfResolve?: boolean;
474
+ /**
475
+ * R2 双形轴(2026-07-18 裁): which prompt-profile face this task speaks — "simple" (default;
476
+ * CC 2.1.212's short-form tool descriptions + lean system sections, what CC serves every non-claude
477
+ * model id) or "classic" (the long-form face, ≈ CC 4.x-era wording — switchable per task/model,
478
+ * e.g. serving classic to a model that scores better on it; center distribution rides this field).
479
+ * Affects PRESENTATION only (descriptions/section wording), never tool behavior or policy.
480
+ * Inherited by delegated children unless their spec overrides.
481
+ */
482
+ promptProfile?: "simple" | "classic";
483
+ /**
484
+ * core half — PER-TASK custom sub-agents. Same shape as the boot injection point
485
+ * ({@link RunnerDeps.agents} / `SubagentToolOptions.agents`): full {@link AgentDefinition} objects
486
+ * (the shell resolves agent files / frontmatter into this shape; `model` is the already-resolved
487
+ * real model ref passed through — core carries no model-word vocabulary). When set (non-empty),
488
+ * `prepareTask` REBUILDS the mounted delegation tool (via {@link ToolSpec.withAgents}) so its roster
489
+ * is the UNION of the boot roster and these definitions — a same-name per-task definition WINS over
490
+ * the boot one. Validation contract (revision of the posture, dual-leg verified against
491
+ * upstream): an `allowTools` entry naming a tool that does not exist is ITEM-FILTERED at spawn (the
492
+ * agent stays listed and delegable; the only host disclosure is a prepare-time HEURISTIC advisory
493
+ * via `RunnerDeps.onError` phase `"config"` for entries unknown to the parent's assembled roster —
494
+ * zero model-facing warning, the verified upstream shape); a `denyTools` entry naming an
495
+ * unknown tool is REJECTED at task start (`config.agent.unknown_tool` — a deny-typo silently WIDENS,
496
+ * the opposite failure direction, sema extension with no upstream deny-list to defer to); and
497
+ * providing this field with NO delegation tool mounted is rejected too
498
+ * (`config.agents.no_delegation_tool`) — never a silent no-op. POLICY-NEUTRAL: a
499
+ * roster listing only; the 1.294 tighten-only parent-policy inheritance is applied at spawn time,
500
+ * downstream of this list, so a per-task agent can never widen what deployment/parent policy allows.
501
+ * Omitted/empty ⇒ byte-identical prior behavior.
502
+ */
503
+ agents?: AgentDefinition[];
504
+ /** Gate tool calls before they run (allow/deny/approval). Overrides `RunnerDeps.toolPolicy`. */
505
+ toolPolicy?: import("./tool-policy.js").ToolPolicy;
506
+ /**
507
+ * #93 (F-012 L3) — the OVERRIDE seat for the durable resume-edit re-adjudication policy: the policy
508
+ * an approver's `updatedInput` EDIT is re-checked against before a resumed pending call executes
509
+ * (and, at park-mint time, the policy a store-codec-moved projection is re-adjudicated by). ABSENT
510
+ * falls back to the caller policy (`toolPolicy ?? RunnerDeps.toolPolicy`) — never to a silent skip:
511
+ * the pre-#93 shape skipped the whole recheck for deployments with no caller policy, which let a
512
+ * fidelity-projection-moved value land unadjudicated. Supply this only to make the resume-edit
513
+ * boundary STRICTER/different from the live caller policy; it never widens (the frozen ancestor
514
+ * projections and the deny-narrowing layers still apply regardless).
515
+ */
516
+ basePolicyForResumeEdit?: import("./tool-policy.js").ToolPolicy;
517
+ /** How `ask` decisions resolve for this task (headless auto-deny by default). Overrides `RunnerDeps.onAsk`. */
518
+ onAsk?: import("./tool-policy.js").OnAsk;
519
+ /** Content-ask seam (design/64 §5): routes an AskUserQuestion tool call to a real human/UI. When set, the
520
+ * AskUserQuestion tool is mounted (one of the delivery faces in the criterion — see
521
+ * {@link interactiveTools}). Overrides `RunnerDeps.onQuestion`. Distinct from `onAsk` (permission). */
522
+ onQuestion?: import("./ask-question.js").OnQuestion;
523
+ /** LSP code-intelligence seam (design/64 §13.1): when set, the `lsp` tool is mounted and routed to this
524
+ * manager. Overrides `RunnerDeps.lspManager`. Unset ⇒ no `lsp` tool (the model uses grep/read_file). */
525
+ lspManager?: import("./lsp.js").LspServerManager;
526
+ /**
527
+ * design/121 (CC 198 parity): after the agent edits a file, NEW language-server diagnostics are
528
+ * injected into the model (`<new-diagnostics>` context message, 10/file · 30 total · 4000-char cap)
529
+ * and emitted as a structured `diagnostics` event for the shell. **Default ON** whenever the
530
+ * preconditions hold — an LSP manager with a diagnostics registry is wired AND the roster has
531
+ * write-capable fs tools (CC's same gate); zero cost/behavior change otherwise. `false` opts out.
532
+ */
533
+ lspDiagnostics?: boolean;
534
+ /** In-process hooks for this task — the full `Hooks` lifecycle seam (tool pre/post/failure/batch
535
+ * interception, prompt gating, stop pushback, compaction taps, permission-denied observation; each
536
+ * member's contract is on the interface). Overrides `RunnerDeps.hooks`. */
537
+ hooks?: import("./hooks.js").Hooks;
538
+ /**
539
+ * design/45 **durable suspend-on-approval** (F4). When set AND a `CheckpointStore` is wired
540
+ * (`checkpointStore` here or on `RunnerDeps`), a tool-policy `ask` **can suspend durably**: when no LIVE
541
+ * approver is present, or a control-plane `forceDurableGate` mandates it, the ask is **persisted as a
542
+ * checkpoint** and the task ends `status:"suspended"` + `checkpointToken` — resume later with
543
+ * `runner.resume(token, outcome)`. With a live approver, the ask resolves synchronously in-stream FIRST
544
+ * (same turn, no checkpoint) — this option is the durable FALLBACK for when no live approver can answer,
545
+ * not an "always park" mandate. It is the cross-process / long / headless version of the `onAsk` gate.
546
+ * **opt-in, default-off**: unset → the synchronous `onAsk`-only path (a headless ask then auto-denies).
547
+ * Only safe at the top-level `runTask` (orchestrators fail-fast, §11 Q6).
548
+ *
549
+ * **Precedence vs a live `onAsk`:** "live approver" means a FUNCTION-valued `spec.onAsk ?? deps.onAsk`.
550
+ * With one wired, BOTH plain and SAFETY asks (egress/irreversibility tightens) resolve through it
551
+ * in-stream — same turn, no checkpoint, the gate recorded in `humanReview.gates` — and this option acts
552
+ * as the no-approver fallback. The durable park keeps priority only when: (a) there is no function seat
553
+ * (absent, or a blanket `"allow"`/`"deny"` string mode — a policy setting is not a reachable human);
554
+ * (b) a control-plane `forceDurableGate` entitlement is in force; (c) the live face already answered
555
+ * `"unavailable"` for this ask; or (d) the call is marked inherited-unavailable by an ancestor.
556
+ */
557
+ durableApproval?: {
558
+ /** Multi-tenant isolation key, forced into the checkpoint + `resolve`/`reap` WHERE. Required. */
559
+ scope: string;
560
+ /** Optional awaiting-human TTL (ms). Past it a reaper CAS-expires the checkpoint (≈ deny). */
561
+ ttlMs?: number;
562
+ };
563
+ /**
564
+ * design/74: opt into resource-slice suspend. When a resource limit (maxTurns / budget / walltime) is hit
565
+ * AND the task is eligible (a `checkpointStore` + a remote, suspendable `executionEnvFactory` env), suspend
566
+ * as a resumable `resource_limit` checkpoint instead of failing (the "失败≠没做完" third state) — resume
567
+ * continues the run with the next slice's allowance. `scope` is the multi-tenant isolation key (like
568
+ * `durableApproval.scope`). Absent ⇒ a limit hit still FAILS (backward compatible). Independent of
569
+ * `durableApproval` (a run can have one, both, or neither).
570
+ */
571
+ resourceSuspend?: {
572
+ /** Multi-tenant isolation key, forced into the checkpoint + `resolve`/`reap` WHERE. Required. */
573
+ scope: string;
574
+ /**
575
+ * design/74 Slice 4: the task's TOTAL $ budget (USD) across the whole resume→re-suspend chain — the
576
+ * human's allocation, distinct from `limits.maxCostUsd` (the per-SLICE window). Each slice may spend
577
+ * `min(limits.maxCostUsd, total − alreadySpent)`; the ledger (carried on the checkpoint) accumulates
578
+ * spend so a resume can't refresh the budget. `undefined` ⇒ no $ ceiling on the whole task (each slice
579
+ * is bounded only by its own window). Set ONLY on the FIRST slice; later slices read the frozen total
580
+ * from the ledger.
581
+ */
582
+ totalBudgetUsd?: number;
583
+ /**
584
+ * design/164 (the TOKEN sibling of `totalBudgetUsd`, replacing the retired walltime total): the task's
585
+ * TOTAL token allocation across the whole resume→re-suspend chain — distinct from `limits.maxTokens`
586
+ * (the per-SLICE window). Each slice may spend `min(limits.maxTokens, total − alreadySpent)`; spend
587
+ * accumulates on the ledger's `spentTokens`, so a resume can't refresh the allocation. An EXHAUSTED
588
+ * resume (0 remaining) fails fast without running a model turn. `undefined` ⇒ no token ceiling on the
589
+ * whole task. Set ONLY on the FIRST slice; later slices read the frozen total from the ledger.
590
+ */
591
+ totalTokens?: number;
592
+ /**
593
+ * design/74 (HIGH-1): max resource SLICES (resume legs) before the run fails with
594
+ * `"suspend.loop"`. The resource-slice analogue of `maxSuspends`, but counted on the ledger's
595
+ * `sliceCount` and INDEPENDENT of the restart-loop `maxSuspends`: a legitimate long resource run must
596
+ * not be capped by the (low) restart-loop safety, and restart-loop suspends must not consume the slice
597
+ * allowance (the bug: both shared one `suspendCount` + `maxSuspends`). The cross-slice allocations
598
+ * (`totalTokens` / `totalBudgetUsd`) are the PRIMARY bound; this is a backstop against a
599
+ * zero-progress slice loop. **Unset ⇒ no slice ceiling** (design/164 default retirement): a driver
600
+ * that resumes a task which makes no progress will loop until an allocation binds, so a host driving
601
+ * resumes unattended should set this.
602
+ */
603
+ maxSlices?: number;
604
+ /**
605
+ * design/74 (HIGH-2): retention TTL (ms) stamped onto the resource checkpoint's `deadline`
606
+ * so the durable reaper can GC an orphaned (never-resumed) resource suspend. WITHOUT it the checkpoint
607
+ * has no `deadline`, both reapers filter on `deadline`, and the `pending` row (+ in-memory pin) leaks
608
+ * forever. Default `DEFAULT_RESOURCE_TTL_MS` (30 days, the spec `RESOURCE_SUSPEND_TTL_SEC`). A legitimate
609
+ * resume happens long before this; the deadline only reaps abandonment.
610
+ */
611
+ ttlMs?: number;
612
+ };
613
+ /**
614
+ * design/80 Seam #2 (external preempt/yield): the SCHEDULER raises this `AbortSignal` to PREEMPT this task —
615
+ * a graceful, RESUMABLE durable yield (free resources for a higher-priority task), NOT an abort (abort kills
616
+ * the run and loses its state). When raised, the task durably suspends at the next CLEAN turn boundary as a
617
+ * `resource_limit` checkpoint with reason `"preempt"` (the SAME mechanism as a budget/turns/walltime resource
618
+ * suspend — only the trigger differs), and is resumed later via `runner.resume(token, {gate:"resource_limit",
619
+ * decision:"continue"}, …)` exactly like any resource-limit resume (continue the work, no decision).
620
+ *
621
+ * **Eligibility (v1 reuses the resource-suspend opt-in):** preempt only fires when the task is eligible for
622
+ * durable suspend — i.e. it opted into {@link resourceSuspend} AND a durable `checkpointStore` is wired AND
623
+ * tool-results are durable AND the per-task env is a remote (suspendable) `RemoteExecutionEnv`. This is
624
+ * correct: a non-durable task cannot be preempted-AND-resumed anyway. If `preemptSignal` is raised on a task
625
+ * that is NOT resource-suspend-eligible, preempt is a SILENT NO-OP (the run continues normally to its natural
626
+ * end) — decoupling preempt's own opt-in from `resourceSuspend` is a future enhancement, not built here.
627
+ *
628
+ * **Re-supply on resume:** like `resourceSuspend`/`durableApproval`, this is part of the resume task config —
629
+ * a long task that is resumed may need re-preemption, so supply a FRESH `preemptSignal` (a new
630
+ * `AbortController.signal`) in the resume `taskConfig` to make the resumed leg preempt-able again.
631
+ */
632
+ preemptSignal?: AbortSignal;
633
+ /**
634
+ * design/131 — per-task resilience overrides, INTENT-ONLY (no decorator
635
+ * topology on the spec; the deployment's brain stack stays the single owner). For bench/eval or
636
+ * critical tasks that need the provider's TRUE failure shape instead of a silent fallback:
637
+ * - `allowDegrade:false` — reactive model degrade is off for this task (failure passes through).
638
+ * - `allowFailover:false` — cross-gateway failover is off (only the primary brain is tried).
639
+ * - `bypassBreaker:true` — an open circuit breaker's fast-fail is waived for THIS task; outcomes
640
+ * are still recorded (shared observation undiminished). Operator-facing — deployments SHOULD
641
+ * NOT expose this to wire users directly (it defeats provider protection).
642
+ * All absent ⇒ exactly today's behavior. Threaded per-call as `StreamOptions.resilience`; the
643
+ * first-party degrading/failover/breaker decorators honor it, custom brains may ignore it.
644
+ */
645
+ resilience?: ResilienceOptions;
646
+ /**
647
+ * design/132 — final-verification gate (OPT-IN: `true` enables). When a task is about to end
648
+ * NATURALLY (inside budget) and this run made at least one non-`read` tool call, the engine
649
+ * injects a reminder to re-verify the final deliverable through its REAL entry point
650
+ * (execute it directly; don't trust earlier self-tests or shell redirections) before finishing.
651
+ * Oracle-grounding upgrade (docs/DESIGN-ORACLE-GROUNDING-GATE-2026-07-10.md §3.A): if the run's
652
+ * tool-call record carried a deterministic verifiable-structure signal (raw-byte read /
653
+ * structural parse / digest-reconciliation word-faces in shell commands — never a semantic scan
654
+ * of closing text) and the model ends AGAIN without headroom concerns, ONE grounding re-entry
655
+ * reminder is injected; total injections are hard-capped at 2, then the run always ends. A
656
+ * submitted structured output and the maxTurns-1 boundary always pass through un-gated.
657
+ * Evidence: TB full-run autopsy — the biggest shell-gap bucket (7-8 tasks) finished convinced by
658
+ * their own fake self-check; prompt discipline alone (1.242) had a low landing rate. Opt-in
659
+ * because the evidence is from UNATTENDED long-task legs; consumers default it OFF and expose an
660
+ * explicit opt-in (ruled 2026-08-04). Interactive deployments choose per scenario. Read-only /
661
+ * Q&A runs never trigger it. Telemetry: `stats.mechanisms.finalVerifyInjected`.
662
+ */
663
+ finalVerification?: boolean;
664
+ /**
665
+ * design/72 §2.2 (B): max times this task may durably suspend before the run fails with
666
+ * `"suspend.loop"` instead of minting another checkpoint. Caps a resume/restart loop (a model that
667
+ * re-issues the gated/egress call every resume, re-suspending forever). Counted across resume chains.
668
+ * Default 5 (loose — a real human-approval flow re-suspends rarely, so 5 separates a normal resume from a
669
+ * pathological loop; a deployment that genuinely needs more sets this). Overrides `RunnerDeps.maxSuspends`.
670
+ */
671
+ maxSuspends?: number;
672
+ /**
673
+ * Per-task {@link import("./checkpoint-store.js").CheckpointStore} for durable suspend/resume
674
+ * (design/45). Overrides `RunnerDeps.checkpointStore`. Needed (with `durableApproval`) to suspend.
675
+ *
676
+ * THREE-VALUED (ruled 2026-08-04):
677
+ * · `undefined` — inherit `RunnerDeps.checkpointStore` (the historic shape, unchanged);
678
+ * · a store — this run's store, overriding the deployment's;
679
+ * · `null` — this run explicitly has NO store, even on a deployment that wired one.
680
+ *
681
+ * The `null` arm is the per-run OFF SWITCH for the whole durable machine, meant for a run a MACHINE
682
+ * started rather than a person (a notification redelivery, a maintenance sweep). Such a run must
683
+ * never reach `status:"suspended"`, because the checkpoint it would mint has no operator who will
684
+ * ever come back and resolve it — a permanently pending row plus a permanently pinned session.
685
+ *
686
+ * It works BY CONSTRUCTION rather than by a new rule: all four legs into `suspended` — the approval
687
+ * park, the design/74 resource-slice pause, the design/164 platform pause (`env_lifetime` /
688
+ * `usage_window`), and the design/80 `plan_review` pause — are already gated on a store being
689
+ * present, so removing the store removes all four at once and the run falls into the ALREADY-TESTED
690
+ * no-store deployment semantics: a policy `ask` resolves at a live `onAsk` when one is wired and
691
+ * otherwise fail-closed denies; a resource/platform limit is a loud terminal (`limits.*_exceeded` /
692
+ * `env.lifetime_expired` / `usage.window_exhausted`, still carrying `retryAfterMs`); the
693
+ * interactive plan/question tools are not mounted (no delivery face exists). Nothing about it is a
694
+ * second set of rules to keep in sync with the first.
695
+ *
696
+ * Consequence worth stating, because this field used to answer only "WHICH store": on a store-null
697
+ * run {@link Runner.preempt} is a documented no-op — there is no suspend to preempt into, so the
698
+ * request is dropped with the existing `preempt.ignored` trace instead of pausing the run.
699
+ *
700
+ * Delegated children inherit it: the sync / background / fork spawn lanes copy the `"disabled"` word
701
+ * into every child spec (same discipline as `principal`), so a store-less run's whole tree stays
702
+ * store-less.
703
+ *
704
+ * The off switch is the WORD `"disabled"`, never `null`: a string is not nullish, so the ordinary
705
+ * `spec ?? deps` coalesce is the whole resolution rule and no reader has to remember a special
706
+ * null test (the retired `null` spelling is refused loudly at the first read, with the word to write).
707
+ */
708
+ checkpointStore?: import("./checkpoint-store.js").CheckpointStore | "disabled";
709
+ /**
710
+ * Restrict the injected "hand" band (design/44) to its effect:read tools for this task — read_file /
711
+ * grep / glob (+ `bash_readonly` when a shell is available); edit_file / write_file / `bash` are not
712
+ * mounted. This is the verifier read-only boundary (§6): an falsification-style verifier inspects but must not
713
+ * modify the project. No effect unless an `executionEnv` is injected. Default false.
714
+ */
715
+ handsReadOnly?: boolean;
716
+ /**
717
+ * Whether this run can persist a user's "remember X" somewhere durable — the deployment's own
718
+ * statement, overriding the runner's inference. The runner mounts a read-only-memory disclosure
719
+ * (the model must decline to "remember" instead of receipting a save that never happens) when the
720
+ * session provably cannot persist; provability is inferred from KNOWN store paths only (a mounted
721
+ * file-write tool or a write-capable shell). A caller tool that persists through its own closure
722
+ * (a custom memory writer) is invisible to that inference and would be contradicted by the
723
+ * disclosure — set `true` to declare the channel and suppress it. Set `false` to force the
724
+ * disclosure even when write-capable tools mount (e.g. they cannot reach any durable store);
725
+ * a forced `false` also replaces the `# Memory` write instruction itself and drops the
726
+ * preference-writing discipline and index seed that serve it.
727
+ * Default: inferred. The inference also treats a REMOTE execution env's hand band as unable to
728
+ * reach the host-side memory root (sandbox filesystem); a deployment whose remote env shares a
729
+ * mount with the memory root declares `true` — on such a remote, `true` also restores the
730
+ * `# Memory` write instruction when a Write tool mounts (a declared-capable session must be told
731
+ * the path, not left silent). If your persistence channel is NOT the file face, do not mount a
732
+ * tool named `Write` alongside the declaration, or keep the default and teach your own channel.
733
+ * Delegation: a DECLARED value crosses the delegation boundary (sync / background / fork spawns and
734
+ * the retained-resume snapshot alike). An explicit `false` is a FLOOR — a chosen
735
+ * {@link AgentDefinition.memoryPersistenceCapable} `true` cannot loosen it (the disclosure is
736
+ * about the deployment's storage, which no agent selection changes); an explicit `true` is a
737
+ * DEFAULT the chosen definition may narrow back to `false`; absent = absent downstream too — each
738
+ * child runs its own inference over its own roster. A retained child woken by a DIFFERENT run
739
+ * additionally folds the waker's declared `false` on top of the spawn snapshot (tighten-only,
740
+ * like the other resume clamps).
741
+ * KNOWN GAPS (registered, whole-clamp-family shapes — `handsReadOnly`/`interactiveTools` share
742
+ * them): a TIER-3 durable revival rebuilds the child from the REVIVER's context (the durable row
743
+ * records lookup keys, never a serialized spec), so the spawn-time declaration does not survive
744
+ * that lane — the reviver's own declaration governs; and the workflow HOST lane does not forward
745
+ * the host TaskSpec's declaration into `agent()` children (an {@link AgentDefinition} on the
746
+ * workflow agent type does carry).
747
+ */
748
+ memoryPersistenceCapable?: boolean;
749
+ /**
750
+ * design/119 (CC --add-dir parity): extra directories the FILE tools may access in addition to the
751
+ * containment root — each is canonicalized into the containment allowlist and listed in the
752
+ * `# Environment` block so the model knows they're available. Same defense-in-depth caveat as the
753
+ * root: this bounds the file tools, not `bash` (OS-level containment is the env's job).
754
+ */
755
+ additionalDirectories?: string[];
756
+ /**
757
+ * Interactive read-boundary whitelist (ruled 2026-08-05, both-arms ruling, arm A): extra directories
758
+ * the READ faces may access — and nothing else. Joins the containment allowlist of the read-only
759
+ * tools (read_file / grep / glob / repo_map / `bash_readonly`) and the `shellGate:"classify"`
760
+ * read-boundary (so a provably read-only command like `head <dir>/settings.json` auto-allows instead
761
+ * of asking on every call), but is NEVER admitted by the write faces (edit_file / write_file /
762
+ * notebook_edit refuse these paths exactly as before). Use `additionalDirectories` when the task
763
+ * should also write there. Same canonicalization and fail-closed skip rules as
764
+ * `additionalDirectories`. Typical use: a deployment's own user-config directory in an interactive
765
+ * assembly, where a read-classifying shell gate would otherwise consult the approver for every
766
+ * config read.
767
+ */
768
+ additionalReadDirectories?: string[];
769
+ /**
770
+ * design/199 件B — TASK-layer ADDITIONS to the built-in sensitive-path READ deny set
771
+ * ({@link import("../tools/fs/read-deny.js").READ_FACE_DEFAULT_DENY_ENTRIES}). Judged by the
772
+ * structured read faces (Read/Grep/Glob/RepoMap, their traversals, the classify shell gate's
773
+ * auto-allow probe and the compaction attachment reader) in BOTH containment modes. Add-only at
774
+ * the task layer: entries here UNION with the active built-ins and the deployment's
775
+ * {@link RunnerDeps.readDenyPatterns}; `[]` ≡ absent (union identity); NOTHING at the task layer
776
+ * can remove a built-in — the one removal channel is the DEPLOYMENT's built-in configuration
777
+ * ({@link RunnerDeps.readDenyBuiltinTiers} / {@link RunnerDeps.readDenyBuiltinExclude}, #245
778
+ * revision of D-4), which no TaskSpec key reaches. Bad entry shapes refuse loudly at prepare
779
+ * (#123). The write faces are untouched.
780
+ */
781
+ readDenyPatterns?: readonly import("../tools/fs/read-deny.js").ReadDenyEntry[];
782
+ /**
783
+ * design/199 件A — the TASK layer's read-face containment declaration
784
+ * ({@link import("../tools/fs/read-face.js").ReadFace}). "roots" pins this task (and, via the
785
+ * tighten-only clamp, its whole delegation subtree) to the historical containment; "open" lifts
786
+ * the roots judgment for the structured read faces — refused loudly under org governance (the
787
+ * task layer only tightens there; the deployment opens via {@link RunnerDeps.readFace}) and
788
+ * beside `handsReadOnly: true` (the verifier containment is load-bearing). Absent ⇒ the
789
+ * deployment seat, then the engine default ("roots" — D-1b: an upgrade never opens implicitly).
790
+ * Bad values refuse loudly (#123). Never affects the write faces.
791
+ */
792
+ readFace?: import("../tools/fs/read-face.js").ReadFace;
793
+ /**
794
+ * design/80 D-B — opt in to PLAN MODE: mount the first-party `present_plan` tool (CC `ExitPlanMode` parity).
795
+ * The model calls it to present a plan; the engine then pauses with a durable `plan_review` checkpoint
796
+ * (`status:"needs_review"`) so a human approves/edits/rejects before any action. Typically paired with
797
+ * `handsReadOnly:true` (research read-only until the plan is approved, then the resume drops it). Default
798
+ * false — when unset the tool is NOT mounted, NOT disclosed, and a `ToolReturn.requestReview` is ignored, so
799
+ * a non-plan-mode task is byte-identical. Needs a `checkpointStore` wired to actually pause (else a
800
+ * `requestReview` is a no-op). The tool is a thin composition over the general `requestReview` primitive.
801
+ */
802
+ enablePlanMode?: boolean;
803
+ /**
804
+ * Interactive-tool mounting override (CC 2.1.207/209 parity: AskUserQuestion / ExitPlanMode /
805
+ * EnterPlanMode share one `isEnabled` gate — in non-interactive mode without an SDK permission-prompt
806
+ * tool the tool is NOT offered at all, so the model never asks into the void). sema's automatic
807
+ * criterion (this knob unset): a human-facing tool mounts only when the deployment has SOME face that
808
+ * can deliver it to a person —
809
+ * · `AskUserQuestion`: a live `onQuestion` (spec ?? deps), OR a durable park consumer = a wired
810
+ * `checkpointStore` AND (`durableApproval` opted in OR a `forceDurableGate` entitlement) — the
811
+ * approval-center posture (≅ CC's SDK permission-prompt-tool exception).
812
+ * · `present_plan` (within `enablePlanMode`): a wired `checkpointStore` — the `plan_review` park's
813
+ * only consumer (without one the pause is a silent no-op the model mistakes for a delivered plan).
814
+ * Neither face ⇒ not mounted, not disclosed (a bare headless run must not park on a question nobody
815
+ * will answer and then be projected as an empty success). Override: `true` ⇒ always mount (the
816
+ * deployment consumes asks some other way; `enter_plan_mode` still requires a `checkpointStore` —
817
+ * without one its read-only latch would be inescapable); `false` ⇒ never mount (hard headless);
818
+ * unset ⇒ the automatic criterion. `enablePlanMode` stays the plan family's opt-in — this knob never
819
+ * mounts plan tools on its own.
820
+ */
821
+ interactiveTools?: boolean;
822
+ /**
823
+ * design/110 / design/136 §2.1.a (⚠️ BREAKING semantics change) — the per-task FORK governance gate for
824
+ * `Agent(subagent_type:"fork")` (the standalone `Fork` tool was retired; the capability lives in the Agent
825
+ * tool's `subagent_type` value domain — CC-exact shape). The fork spawns a child that INHERITS this
826
+ * conversation's full context (vs the clean-context delegation) and shares the prompt-cache prefix.
827
+ *
828
+ * OPT-OUT default (flipped from the pre-136 opt-IN): `undefined`/`true` = fork is AVAILABLE, bounded by the
829
+ * session capability (`hasSessionFork` durable store + a session id + not inside a fork — honest refusal
830
+ * otherwise); an EXPLICIT `false` = fork is DENIED for this task with the honest `fork.disabled` result
831
+ * (never a silent downgrade). Multi-tenant deployments that previously relied on `undefined` being OFF must
832
+ * now send `enableFork: false` explicitly (or deny per-principal via `RuntimeCaps.allowFork`).
833
+ */
834
+ enableFork?: boolean;
835
+ /**
836
+ * design/80 D-2 (part-1): gate the injected `bash` hand tool, since a full shell can run egress (`curl`,
837
+ * `git push`) and irreversible (`rm`) commands that the per-tool egress/irreversibility marks otherwise miss.
838
+ * The §4-OQ4 doctrine: shell-in-presence ⇒ always-gate UNLESS a real parsed classifier is wired.
839
+ * - `"off"` (default): `bash` is unmarked — the current behavior (subject only to the deployment's policy).
840
+ * - `"always"`: every `bash` command tightens to an `irreversible_ask` durable suspend (fail-closed; the
841
+ * correct default for an unattended/supervisor deployment with no classifier).
842
+ * - `"classify"`: mark `bash` `irreversibility:"maybe"` with {@link import("../tools/fs/index.js").bashReversibilityProbe}
843
+ * — a provably-benign single allowlisted read-only command (no operators) auto-allows; everything else
844
+ * (writes, egress, pipes, unknown commands) tightens to ask. An egress command (e.g. `curl`) suspends under
845
+ * the IRREVERSIBLE axis (the shell mark is irreversibility, not egress) — the kind is non-budgetable either
846
+ * way, so the budget resolver still never auto-approves it; the axis label is informational.
847
+ * NOT unconditional: a POSITIVE per-tool mark on the shell tool keeps its seat — an explicit
848
+ * `irreversibility:"always"` is never downgraded (the tier combine is tighten-only) and an explicit
849
+ * `"maybe"` keeps its own probe (a probe-less explicit `"maybe"` stays fail-closed ask; the doctrine's
850
+ * generic probe installs only on doctrine-owned seats). An explicit `"never"` is NOT a mark — the
851
+ * doctrine governs that seat as if unmarked.
852
+ * No effect under `handsReadOnly` (that mounts `bash_readonly`, already allowlisted) or with no execution env.
853
+ * **Re-supply on resume:** like `toolPolicy`/`tools`/`durableApproval`, `shellGate` is part of the resume
854
+ * task config — a resume that omits it leaves the resumed run's SUBSEQUENT `bash` calls ungated (the approved
855
+ * pending call itself runs once, having been adjudicated). Re-pass the same value the original task used.
856
+ */
857
+ shellGate?: "off" | "always" | "classify";
858
+ /**
859
+ * The caller's AUTO-MODE INTENT for this task — the "user turned auto on" half of the classifier
860
+ * arming (CC 2.1.250 polarity: auto mode is something the USER enables at the permission-mode
861
+ * seat, and something an ORGANIZATION may only DENY; it is never something an organization grants
862
+ * on a user's behalf). The engine arms its per-run classifier only when ALL THREE hold:
863
+ * - this seat is `true` (intent),
864
+ * - {@link RunnerDeps.autoMode} is present (the deployment is classifier-capable — the trust gate;
865
+ * classifier RULES still enter ONLY through that face, never through this seat), and
866
+ * - {@link RuntimeCaps.autoMode} is not `false` (the per-principal DENY bit; ABSENT is NOT a denial).
867
+ *
868
+ * **Ownership** — same split as {@link shellGate}: the service layer TRANSLATES the effective
869
+ * permission mode into this seat (at the same point it translates the mode into `shellGate`);
870
+ * core only CONSUMES it. Absent ⇒ not an auto-mode task (byte-identical to the pre-seat shape:
871
+ * asks flow the original chain). Only the literal `true` is a value here: any other present value
872
+ * (`false`, `"true"`, `1`) is refused at the door (`config.auto_mode_requested_invalid`) rather than
873
+ * read as either polarity — a marshalled string must never silently arm, or silently disarm, a
874
+ * classifier.
875
+ *
876
+ * **Inheritance**: the intent is session-wide, like a permission mode — an engine-spawned child of
877
+ * an auto-mode task inherits it through the trusted constraint chain
878
+ * (`InheritedGate.autoModeRequested`), never through a model-authored argument; the child's own
879
+ * deny bit and deployment face are still evaluated for the child. **On resume:** the intent the
880
+ * suspend leg resolved (this seat, or the bit its chain carried) is recorded on the checkpoint's
881
+ * data half (`CheckpointState.inheritedGate.autoModeRequested`) and read back as one more intent
882
+ * source, so a redemption in another process arms as the suspend leg did without re-passing the
883
+ * seat; re-passing it is still honoured (the sources fold by OR). Intent only — the resuming
884
+ * deployment's face and the resuming principal's deny bit are judged afresh on every leg.
885
+ */
886
+ autoModeRequested?: true;
887
+ /** Task-scoped MCP servers, materialized into tools then disposed. */
888
+ mcp?: McpServerSpec[];
889
+ /** Task-scoped A2A peers (remote agents), whose advertised skills mount as `a2a__<peer>__<skill>`
890
+ * tools for this task. Every mounted skill is an external write by default — see {@link A2aServerSpec}. */
891
+ a2a?: A2aServerSpec[];
892
+ /**
893
+ * Task-scoped skills (object form). Two first-party halves (design/71 黑盒#5 fix; [c209-C] R2 C8
894
+ * delivery reality): a `<skills>` disclosure block listing name+description (deterministic order,
895
+ * capped) delivered as a `<system-reminder>` on the FIRST user turn — plus boundary drift deltas —
896
+ * NOT part of the stable system prompt (design/C moved it off the cached prefix), and an
897
+ * injected `skill` tool (effect:read) that returns the skill's FULL content as a tool result —
898
+ * no invoke-time truncation (CC parity; the only doors are a 1MB per-skill load gate at spec
899
+ * assembly, which REJECTS an oversized skill whole with a config-phase error, and the
900
+ * compaction-time invoked-skill retention caps). Duplicate names: first wins. The name
901
+ * `skill` becomes reserved when this field is non-empty (`config.reserved_tool_name`).
902
+ *
903
+ * Skill content is CALLER-trusted — same trust level as `systemPrompt`. Callers forwarding
904
+ * end-user-authored skills are responsible for vetting them: a skill IS instructions by design.
905
+ */
906
+ skills?: SkillSpec[];
907
+ /**
908
+ * design/129 — lifetime scope for BACKGROUND children (Agent/Fork `run_in_background`). Default `"task"`:
909
+ * the parent runTask's terminal reaps them (design/115 — no orphans burning tokens; turn-end killed
910
+ * frame). `"session"`: children OUTLIVE the turn (CC Backgrounded semantics) — registered under the
911
+ * SESSION owner (cross-turn TaskOutput poll works), skipped by the parent-teardown reap, force-capped by
912
+ * a default child timeout when none is given, and finally reapable via
913
+ * `TaskRegistry.reapSessionBackground(sessionId, scope)` (call it from your session release/sweep).
914
+ * CALLER-trusted (systemPrompt tier): never a model-facing parameter, never on the governed workflow
915
+ * whitelist — a child's lifetime crossing the turn boundary is an isolation-contract surface.
916
+ */
917
+ backgroundScope?: "task" | "session";
918
+ /**
919
+ * A1 — structured SANDBOX facts for the `# Environment` block, so the agent knows what
920
+ * toolchains the environment already carries (no blind reinstall), which package source installs go
921
+ * through, and whether the network is reachable at all (egress=none ⇒ don't burn budget on downloads).
922
+ * DEPLOYMENT-trusted (systemPrompt tier — the deployment knows its own sandbox binding): rendered
923
+ * sanitized+bounded by prepare-task; never a model-facing parameter, never on the governed workflow
924
+ * whitelist. All fields optional; absent ⇒ block unchanged.
925
+ */
926
+ envFacts?: {
927
+ /** The bound sandbox image profile (e.g. `dev-mobile`). */
928
+ profile?: string;
929
+ /** Toolchains/capabilities the image carries (e.g. `android-sdk`, `gradle`). Rendered bounded. */
930
+ capabilities?: readonly string[];
931
+ /** Which package-source lane installs go through (e.g. `cn` | `global` | `custom`). */
932
+ pkgSource?: string;
933
+ /** Outbound-network posture. `none` renders an explicit "downloads will fail" caveat. */
934
+ egress?: "none" | "allowlist" | "full";
935
+ /** Session-specific scratch directory for temporary files (CC Scratchpad parity, 1.257.3): when set,
936
+ * the environment block instructs the model to use this dir instead of `/tmp` — prevents parallel
937
+ * tasks trampling each other's temp files and project-dir pollution. Unset ⇒ no section rendered. */
938
+ scratchpadDir?: string;
939
+ /**
940
+ * B-half — PER-LANE resume-continuity facts, rendered ONLY on a durable-resume leg so
941
+ * the model reasons honestly about what survived the suspension. DEPLOYMENT-supplied per its own lane
942
+ * capability (a VM snapshot lane preserves processes; an ssh park-only workspace usually keeps /tmp;
943
+ * a rebuilt container loses both) — core hardcodes NO lane table and renders NOTHING when a field is
944
+ * absent (an unstated fact must not become a confident sentence; a blanket "lost" wording would
945
+ * misinform the model on lanes where the state genuinely persists). All fields optional.
946
+ */
947
+ resumeFacts?: {
948
+ /** Whether processes started before the suspension are still running after resume. */
949
+ processes?: "preserved" | "lost";
950
+ /** Whether out-of-workspace temp state (e.g. `/tmp`) survived the suspension. */
951
+ scratch?: "preserved" | "lost";
952
+ /** Free-form lane-specific caveat (sanitized + bounded at render). */
953
+ note?: string;
954
+ };
955
+ };
956
+ /**
957
+ * design/164 — the SINGLE task-limit surface. Every budget axis lives here (the former top-level
958
+ * `maxTokens` / `maxCostUsd` / `budgetStreamCancel` / `degrade` were folded in), and **every axis is
959
+ * DEFAULT-EMPTY**: a task with no `limits` runs with no engine-imposed turn / token / cost / wall-clock
960
+ * ceiling at all.
961
+ *
962
+ * Consequence, stated plainly (ruled 2026-08-04): a zero-config runaway has NO task-level backstop.
963
+ * What still binds is the ENGINEERING GUARDRAIL family — {@link TaskLimits.brainCallGuardrailMs} (a
964
+ * brain call that never settles), the stall watchdogs, and the deployment's own usage governance. Those
965
+ * answer "did the system hang", never "how much may this task spend"; the second question is the
966
+ * caller's to answer, by setting a limit here.
967
+ */
968
+ limits?: TaskLimits;
969
+ /**
970
+ * Config-catalog override declarations (ruled 2026-07-19, anti-chaos contract): a HOST that
971
+ * overrides a recommended knob at its own layer (env cap, adapter flag, harness default) declares
972
+ * it here — key + value + REASON — so the override rides the `config.assembled` trace with
973
+ * provenance `host-declared` instead of being invisible to forensics. Advisory observability
974
+ * metadata only: declarations never change an effective value (set the real field for that).
975
+ * See `describeConfigCatalog()` for the recommended scale.
976
+ */
977
+ configOverrides?: ReadonlyArray<import("../config/catalog.js").ConfigOverrideDeclaration>;
978
+ /**
979
+ * Subagent viewing pane (dogfood finding 2026-07-03): widen the opt-in display sink
980
+ * (`RunInternals.onForwardEvent`) from `task_progress`-only to a SUBAGENT's live CONTENT events
981
+ * (`text_delta` / `text_end` (#447) / `reasoning_delta` / `tool_start` / `tool_end`), so a UI can render a delegated
982
+ * child's transcript live. Default OFF (progress-only, prior behavior). The child stream is still
983
+ * NEVER merged into the parent's model context — this is purely a render channel; forwarded events
984
+ * carry `parentToolCallId` (attribution) and the same UNTRUSTED-RAW contract as the main stream's
985
+ * `tool_start.args`/`tool_end.output` (consumer MUST redact — design/99 §E1). Single-level: each
986
+ * delegation level opts in for ITS direct children.
987
+ */
988
+ forwardSubagentEvents?: boolean;
989
+ /**
990
+ * design/122 D1 — RETAIN sync sub-agent sessions after they settle so an operator can RESUME (revive) a
991
+ * finished child with a new prompt via {@link import("../agents/subagent.js").SubagentSteerHandle}'s
992
+ * `resume` verb (CC `dfe` resumeAgentBackground parity). Default OFF = prior behavior byte-for-byte:
993
+ * child sessions are throwaway (released eagerly after the delegation returns).
994
+ *
995
+ * ON (`true` or a config object): each SYNC delegation spawned while the deployment's
996
+ * `RunInternals.onSubagentSpawn` sink is wired (the handle-emitting path — without a handle there is no
997
+ * resume capability, so retaining would be a pure leak) pre-mints the child's sessionId, PINS it against
998
+ * the store's idle sweep, and SKIPS the eager release. The session is retained in a parent-run-scoped
999
+ * ledger until `ttlMs` after settle (default 30 min), eviction past `max` retained children (default 16),
1000
+ * or the PARENT run reaching a terminal state — whichever comes first; then it is unpinned + explicitly
1001
+ * released (throwaway semantics restored — a retained session never dangles to the store's 7-day TTL).
1002
+ * `run_in_background` children are OUT OF SCOPE (no handle, no retain — r1-M3; the bg lane's resume needs
1003
+ * durable support and is a separate item). Resume is reachable only while the parent run lives (the
1004
+ * ledger is run-scoped, not a durable registry).
1005
+ */
1006
+ retainSubagentSessions?: boolean | {
1007
+ ttlMs?: number;
1008
+ max?: number;
1009
+ };
1010
+ /**
1011
+ * Keep BACKGROUND PROCESSES this task started (background bash shells / monitored processes) ALIVE
1012
+ * after the task reaches a terminal state, instead of the default run-teardown reap. Default `false`
1013
+ * = current behavior byte-for-byte: the teardown settles this run's shell-lane children and disposes
1014
+ * the env's background shells.
1015
+ *
1016
+ * `true` = the "deliver a living service" contract (the task's deliverable is a running daemon that
1017
+ * must survive for a later probe/consumer): the run teardown neither kills nor disposes this task's
1018
+ * background processes. Semantics are BEST-EFFORT "do not actively kill":
1019
+ * - **OR-composed with the env-level opt-in** (e.g. `NodeExecutionEnv({ retainBackgroundProcesses })`):
1020
+ * either being `true` retains. The env-level lane is the stronger form — it also spools child stdio
1021
+ * to files at SPAWN time, so retained processes survive the host process exiting; a spec-level
1022
+ * retain keeps processes alive past the task, but their stdio may still be tied to host pipes.
1023
+ * - A per-task ISOLATED owned env (container/VM) still destroys its processes with the env teardown —
1024
+ * retention there would be illusory, so the teardown keeps its honest killed receipts instead.
1025
+ * - win32: best-effort "no active kill" only; without detached process groups, process-tree survival
1026
+ * past the host is a platform boundary (some children may still exit with it).
1027
+ * - Explicit stops are untouched: the TaskStop tool, per-shell background timeouts, and the
1028
+ * deployment's session-release reap still kill — this flag only exempts the automatic run-teardown
1029
+ * sweep (settle + dispose).
1030
+ * - NOT inherited by delegated child tasks: each child's own spec decides for the child's own
1031
+ * background processes.
1032
+ */
1033
+ retainBackgroundProcesses?: boolean;
1034
+ /**
1035
+ * design/120 P2: opt in to in-stream tool execution — safe tool calls start while the model is
1036
+ * still streaming its response instead of waiting for the full reply. Default OFF. Forwarded to
1037
+ * the harness, which additionally withholds it whenever a `tool_call` gate handler is registered
1038
+ * (policy / approval / egress runs — a gate could durably suspend, which is impossible
1039
+ * mid-stream), so setting this on a gated task is safe and simply has no effect. A run whose only
1040
+ * gate concern is resource-suspend does NOT register that handler (it stops at clean turn
1041
+ * boundaries), so in-stream execution stays available there.
1042
+ *
1043
+ * Known limitation (documented): with this ON, a tool's side effect can complete BEFORE the
1044
+ * assistant message persists; a hard process crash in that window leaves no transcript trace of
1045
+ * the call (wake reconcile cannot see an orphan that was never written). Prefer OFF for
1046
+ * deployments where crash-exactly-once accounting of tool effects matters more than latency.
1047
+ */
1048
+ streamingToolExecution?: boolean;
1049
+ /**
1050
+ * **Structured output** (1.41): constrain the task's FINAL result to this JSON schema (a typebox
1051
+ * object schema). Injects a built-in `submit_output` tool whose parameters ARE this schema; the model
1052
+ * calls it to finish, the validated object is surfaced as `TaskResult.structuredOutput`, and the task
1053
+ * ends. Provider-agnostic (it's just a tool). If the model can't produce a valid object within a few
1054
+ * tries the task fails with `errorCode="output.invalid"`. A caller tool named `submit_output` is
1055
+ * rejected (reserved). The model may still answer in prose (then `structuredOutput` is undefined) or
1056
+ * `report_blocked`. Cast the result with `Static<typeof yourSchema>`.
1057
+ */
1058
+ outputSchema?: TSchema;
1059
+ /**
1060
+ * How many times the model may **retry** a rejected `submit_output` before the task fails with
1061
+ * `errorCode="output.invalid"` (only meaningful when {@link outputSchema} is set). `0` = fail on the
1062
+ * first invalid submit (no in-loop retry); the caller decides whether to re-run. Default `2`
1063
+ * (3 total attempts). Clamped to a non-negative integer. This bounds only the *in-task* retry of a
1064
+ * malformed object — it is not an auto-fixer; pair it with a caller-side retry on `output.invalid`.
1065
+ */
1066
+ outputRetries?: number;
1067
+ /** Best-effort fire-and-forget trace sink (task/turn/brain/tool). Overrides `RunnerDeps.tracer`. */
1068
+ tracer?: import("./trace.js").TracerHook;
1069
+ /** Per-task override of the large-tool-result offload threshold (chars); see `RunnerDeps.toolResultThresholdChars`. */
1070
+ toolResultThresholdChars?: number;
1071
+ /**
1072
+ * Override auto-compaction thresholds for this task's session. Compaction (LLM summary, persisted
1073
+ * to the session) runs in two places sharing these settings: at every within-task turn boundary
1074
+ * (design/64 §25 (A) — the long-single-task case) and once at task end (the between-task case).
1075
+ * Defaults: enabled, reserveTokens 16384, keepRecentTokens 0 (design/169-C; 20000 was the
1076
+ * pre-169 default). A zero budget is NOT "no verbatim tail": the cut-point search has a
1077
+ * structural floor under the budget, so the default already leaves the last exchange verbatim
1078
+ * after the summary (a trailing tool-result batch keeps its emitting assistant with it) — the
1079
+ * same one-group posture CC's reactive compaction ships. Set `keepRecentTokens` explicitly only
1080
+ * to keep a WIDER tail than that. Repaired
1081
+ * automatically when
1082
+ * pathological for the model's window: threshold ≤ 0, keepRecent ≥ threshold, or a threshold above
1083
+ * the clearStale point (0.7×window — the prefix-cache "death band", design/64 §26.4) is clamped
1084
+ * down to it. The clamp applies to explicit values too (the clearStale point is not configurable,
1085
+ * so a threshold above it cannot express consistent intent); explicit values below all three
1086
+ * bounds are never overridden (see `sanitizeCompactionSettings`).
1087
+ * `instructions` is appended to the summarization prompt — use it to guarantee critical
1088
+ * facts survive (e.g. "preserve verbatim any codes, IDs, names, and explicit user instructions").
1089
+ * design/145 §4 hygiene (every instructions channel, incl. a `TaskStream.compact({instructions})`
1090
+ * per-call override and a preCompact hook's additions): break-out tags (`</system-reminder>`) and
1091
+ * fence sentinels are defused, and the text is capped at 2048 code points; clean text under the
1092
+ * cap passes through byte-identical.
1093
+ * Scope caveat (design/141 examples 批实测): `instructions` rides the WHOLE-TURN summarization
1094
+ * request only; a split-turn cut point (mid-turn prefix summarization) uses the engine-owned
1095
+ * turn-prefix prompt and does not carry it (same A2 boundary as `RunnerDeps.summaryProvider`).
1096
+ * `withinTask: false` keeps end-of-task compaction but disables the ROUTINE within-task
1097
+ * (turn-boundary) trigger — the escape hatch for the §25 (A) control-flow change; the system
1098
+ * prompt then stops claiming ROUTINE mid-task summarization (§6.3 honesty). It does NOT silence
1099
+ * the recovery-class forced lanes (design/374 slice 3): a prompt-too-long recovery or the guard
1100
+ * chain's arm B may still legitimately compact WITHIN the task — error/pressure recovery is not
1101
+ * a routine boundary pass, so a within-task compaction under this flag is contract-conforming.
1102
+ * `attachWorkingFiles` (LONGRUN-2; **default ON since 2026-07-03** — CC 198 hard-codes its
1103
+ * post-compact file restore, and LONGRUN-2 measured ≈2.3 extra read round-trips per compaction
1104
+ * without it): after each compaction, re-read the task's most recently READ files (CC
1105
+ * readFileState semantics; falls back to modified-by-recency when read tracking is absent) and
1106
+ * append their current contents to the summary (bounded: `maxFiles` default 3,
1107
+ * `maxCharsPerFile` default 16k chars ≈ 4k tokens, plus a total cap that self-scales to ~15% of
1108
+ * the model window). The `compacted` wire event carries the attached list as `attachedFiles`
1109
+ * (shell renders CC's post-compact Read cards). `false` opts out; an object customizes caps;
1110
+ * no-op when the task has no execution env (no hands, nothing to re-read).
1111
+ */
1112
+ compaction?: {
1113
+ enabled?: boolean;
1114
+ withinTask?: boolean;
1115
+ reserveTokens?: number;
1116
+ keepRecentTokens?: number;
1117
+ instructions?: string;
1118
+ attachWorkingFiles?: boolean | {
1119
+ maxFiles?: number;
1120
+ maxCharsPerFile?: number;
1121
+ };
1122
+ /**
1123
+ * design/169-F (CC 220 :386605-386659/:597965 lightweight-clear posture) — OPT-IN stale
1124
+ * tool-result offload, a projection-layer clear below full compaction: in the REQUEST sent to
1125
+ * the model, same-tool results older than the most recent `keepRecentPerTool` are replaced by a
1126
+ * short pointer ("offloaded … saved to persisted output ref …", readable back via the
1127
+ * `read_tool_result` tool) once the replacement saves ≥ `minSavingsChars` chars. The on-disk /
1128
+ * session transcript is NEVER rewritten (append-only + projection is this repo's constitution);
1129
+ * only the outgoing request projection changes. Replacement decisions are BYTE-STABLE across
1130
+ * turns by construction (a result is replaced exactly when ≥ keepRecentPerTool newer same-tool
1131
+ * results exist — monotone, so a decision never reverses — and the pointer text is a pure
1132
+ * function of the result), so provider prompt-cache prefixes are never churned by re-decisions.
1133
+ * `undefined` (default) = completely off, requests byte-identical to pre-169 behavior. Requires
1134
+ * the tool-result offload store (on by default; a deployment that disabled offloading via
1135
+ * `toolResultThresholdChars: 0` gets one onError config warning and the knob stays inert).
1136
+ * Field defaults when the object is present: keepRecentPerTool 3, minSavingsChars 2000.
1137
+ * Values must be non-negative integers — anything else (negative, fractional, non-finite) fails
1138
+ * loudly at prepare (`code: "config.stale_tool_result_offload_invalid"`, no silent re-default).
1139
+ */
1140
+ staleToolResultOffload?: StaleToolResultOffloadOptions;
1141
+ /** design/145 §1 — window-safety tolerance for an independent `compactionModel`: when the
1142
+ * summary input would be clamp-truncated by MORE than this ratio to fit the compaction
1143
+ * model's window, the call falls back to the main model (subject to the budget/walltime
1144
+ * gates). 0 = any truncation triggers the fallback candidacy; 1 = never fall back (the
1145
+ * pre-145 clamp-only escape hatch). Default 0.10 — the honest read: head+tail clamping keeps
1146
+ * ≤10% mid-section loss BOUNDED AND DISCLOSED (`compaction.clamp_disclosure`), not lossless. */
1147
+ clampTolerance?: number;
1148
+ };
1149
+ /**
1150
+ * design/133 §R3 — turn-boundary attachment producers (CC `getContextAttachments` parity), OPT-IN
1151
+ * with one carve-out: [c209-C] the LISTING FAMILY (`agentListing`/`skillsListing`) is DEFAULT-ON —
1152
+ * the agent roster and skills listing were evicted from the tool descriptions / system prompt
1153
+ * (cache-prefix immunity, design/C), so the reminder frame is their ONLY model-facing carrier and
1154
+ * an opt-in default would leave the static pointer sentences ("Available agent types are listed in
1155
+ * <system-reminder> messages…") pointing at reminders that never arrive. Explicit `false` still
1156
+ * opts out (config warning when the corresponding face is mounted). Every OTHER member keeps the
1157
+ * design/132 default-face judgment (B2/H2): the evidence for those reminders comes from UNATTENDED
1158
+ * long-task legs (weak-model drift safety net — CC's own trigger counts proved unreproducible: the
1159
+ * observed CC runs never mounted TodoWrite), so long-run deployments turn them on and
1160
+ * interactive deployments stay byte-identical until a behavior leg proves them neutral. At most ONE
1161
+ * coalesced `<system-reminder>` steer per clean turn boundary, lowest lane: finalize/deadline-nudge/
1162
+ * diagnostics at the same boundary — and the design/130 P2 / design/132 end-game latches — all
1163
+ * suppress it. The listing family ALSO delivers its initial full frames on the FIRST user turn
1164
+ * (prompt-adjacent, CC 209 classic wire shape) — that first-frame delivery is part of the first
1165
+ * message, not a boundary steer, so none of the boundary gates apply to it.
1166
+ *
1167
+ * - `todoReminder`: CC 10/10 cadence — when TodoWrite (or the task tools; both mounted ⇒ the
1168
+ * task family wins, CC isTodoV2) hasn't been used for 10 assistant turns, re-surface the list
1169
+ * (thin projection; an all-completed list is cleared, never regurgitated). No tools mounted ⇒
1170
+ * permanently silent.
1171
+ * - `changedFiles`: boundary-time stat of ≤20 (or `maxFiles`) most-recently-READ files; a file
1172
+ * whose mtime moved past the recorded read time (+2s epsilon) is listed (path-only v1, "do not
1173
+ * blindly revert — re-read"). OFF ⇒ zero stat calls. Self-edits are immune (write-back
1174
+ * refreshes the read stamp); mtime skew on exotic fs remains a documented false-positive source.
1175
+ * - `planModeReminder`: while `enter_plan_mode` is active, re-surface the read-only constraint
1176
+ * every 5 assistant turns (full copy every 5th, sparse between) so a long plan phase doesn't
1177
+ * drift into write-deny thrash. Exiting plan mode stops it.
1178
+ * - `backgroundTasks` (G1 通告层 — CC /compact parity): after a compaction LANDS, the
1179
+ * next boundary announces this run's still pending/running background tasks (task registry
1180
+ * snapshot: id/description/status, CC three-state copy). ONE-SHOT per compaction — never
1181
+ * per-boundary nagging; no compaction ⇒ permanently silent.
1182
+ * - `toolsDelta` (G1 — CC deferred-tools boundary notice): when design/36 `tool_search`
1183
+ * materializes deferred tools mid-task (setTools + fingerprint refresh), the next boundary
1184
+ * announces the NEWLY available names once. Already-active and resume-reseeded tools are never
1185
+ * (re)announced; no deferred tools ⇒ permanently silent.
1186
+ * - `agentListing` (G1 续批 — CC `agent_listing_delta` parity; [c209-C] DEFAULT-ON, see above):
1187
+ * when a delegation tool built by `createSubagentTool` is mounted, the FIRST user turn carries
1188
+ * the full agent-type roster ("Available agent types for the Agent tool:", CC-verbatim; + the
1189
+ * concurrency note when more than one type; + the Q4 "Models available for the 'model'
1190
+ * parameter:" tail when a catalog is mounted). Boundaries announce only roster drift
1191
+ * (adds/removals — static in practice; the delta lane serves dynamic-registration deployments
1192
+ * and cross-leg drift). A durable resume leg is seeded as already-announced from the
1193
+ * checkpoint's `announcedListings` name-set (drift since suspend IS delta-announced), falling
1194
+ * back to a transcript probe for older checkpoints — never replayed when already delivered.
1195
+ * - `skillsListing` ([c209-C], DEFAULT-ON, mechanism-family sibling of `agentListing` — no CC
1196
+ * delta anchor, registered in cc-divergences): the FIRST user turn carries the full `<skills>`
1197
+ * disclosure block (the exact render that used to sit in the stable system prompt — capped
1198
+ * byte-exact at SKILLS_BLOCK_MAX_BYTES 8000 ≤ the 8192 bundle cap since [c209-C] R2 C4, with
1199
+ * name-only overflow); boundaries announce only skill-set drift. Resume seeding mirrors
1200
+ * `agentListing`.
1201
+ * - `mcpInstructions` (G1 续批 — CC `mcp_instructions_delta` parity): the INITIAL server
1202
+ * instructions stay in the stable system prompt (`# MCP Server Instructions`, design/64 §17.3 —
1203
+ * deliberate CC deviation: CC injects them as reminders, sema's stable-prompt carrier is
1204
+ * prefix-cache-correct). This member announces only DELTAS: a mid-task disconnect of an
1205
+ * instruction-bearing server ("The following MCP servers have disconnected…", CC-verbatim), and
1206
+ * the add lane for a future dynamic-connect face. Added instruction text is UNTRUSTED server
1207
+ * text → fenced with `delimitUntrusted` (deliberate CC deviation: CC injects it raw).
1208
+ *
1209
+ * Telemetry: `steering_injected` (source `todo_reminder`/`task_reminder`/`changed_files`/
1210
+ * `plan_mode`/`background_tasks`/`tools_delta`/`agent_listing`/`skills_listing`/
1211
+ * `mcp_instructions`) + `stats.mechanisms.attachmentsInjected` (first-frame listing deliveries
1212
+ * emit the same echo frames and count too — source-accurate observability for the shell).
1213
+ */
1214
+ attachments?: {
1215
+ todoReminder?: true;
1216
+ /**
1217
+ * task #51 口径③ — CC `r2o()` kill-switch parity (CC 198 pretty.js:479167-479171; both reminder
1218
+ * producers consume it as `if (r2o() === "off") return []`, :480232/:480268). `"off"` silences the
1219
+ * todo/task reminder lane even when `todoReminder` is wired on (the other attachment producers are
1220
+ * untouched — CC's nudge gate is lane-scoped); any other value (or absence) = CC `"baseline"`.
1221
+ * CC resolves this from env `CLAUDE_CODE_TODO_REMINDER_MODE` / the `tengu_soft_slate_nudge` gate;
1222
+ * sema keeps env resolution at the deployment shell — core reads no env.
1223
+ */
1224
+ todoReminderMode?: "baseline" | "off";
1225
+ /**
1226
+ * RB-313 — `tool_search_usage_reminder` (CC 2.1.220 producer `Nop` @608915, renderer @626269): the
1227
+ * REVERSE nudge for delayed tool disclosure. When tools are deferred (design/36), a model that has
1228
+ * gone N turns without calling `ToolSearch` and still has unloaded schemas is told they exist and
1229
+ * how to load them, so "this capability does not exist, I will build a workaround" stops being the
1230
+ * silent failure mode of the mechanism.
1231
+ *
1232
+ * Activation (all four, CC-exact): opted in here; the task actually HAS deferred tools; at least one
1233
+ * is still undiscovered; and BOTH windows are open — ≥15 cadence units since the last `ToolSearch`
1234
+ * call AND since the last such reminder (`TOOL_SEARCH_REMINDER_CONFIG`, CC's shipped 15/10 defaults).
1235
+ * MUTUALLY EXCLUSIVE with the todo/task reminder in the same boundary (CC `task_reminder_same_turn`):
1236
+ * at most one work-hygiene nudge per boundary. Opt-in, default OFF like every other 133 member —
1237
+ * CC's own mount is likewise config-gated (`cXi()` null ⇒ the lane is never registered).
1238
+ */
1239
+ toolSearchReminder?: true;
1240
+ changedFiles?: true | {
1241
+ maxFiles?: number;
1242
+ };
1243
+ planModeReminder?: true;
1244
+ /**
1245
+ * SR-3 (CC 198 `budget_usd` lane, producer QFm pretty.js:480424-480428 + renderer :600225): the
1246
+ * progressive $-budget readout `USD budget: $used/$total; $remaining remaining` (CC-verbatim
1247
+ * template). CC-exact activation: live ONLY while the task carries an effective cost ceiling
1248
+ * (`maxCostUsd`, or a resource-slice ledger remaining — CC `options.maxBudgetUsd`); no ceiling ⇒
1249
+ * permanently silent even when opted in. CC-exact cadence: NO threshold ladder and NO throttle —
1250
+ * CC mounts it on every attachment cycle whose task carries a ceiling (RB-311: CC 220 @607474 puts
1251
+ * it in the SHARED producer group, so a SUBAGENT with its own ceiling sees it too — the `!agentId`
1252
+ * flag gates the main-agent-exclusive group next to it, not this lane), so
1253
+ * sema emits one line at every collected clean turn boundary; the "progression" is the numbers
1254
+ * advancing with spend. Recorded deviation (continuation gate): CC attaches at request assembly
1255
+ * and can never EXTEND a run, while a sema boundary steer revives an idle harness — so this lane
1256
+ * only rides boundaries whose turn resolved ≥1 tool call (a next request is already guaranteed);
1257
+ * a text-only final turn is never dragged past its natural end. Numbers ride the enforcement
1258
+ * coordinate (`stats.costMicroUsd` vs the effective ceiling, ÷1e6 → USD) so the readout never
1259
+ * disagrees with `limits.max_cost_exceeded`.
1260
+ * NOTE: this deliberately relaxes the design/74 "no budget language reaches the model" default —
1261
+ * that judgment shaped the DEFAULT-OFF posture; an opted-in deployment chooses CC parity.
1262
+ */
1263
+ budgetUsd?: true;
1264
+ /**
1265
+ * RB-318 (ruled 2026-08-26) — `total_tokens_reminder` (CC 2.1.245 producer `MPs`, renderer `Oje`):
1266
+ * the TOKEN twin of {@link budgetUsd}. One line per collected boundary,
1267
+ * `<total_tokens>N tokens left</total_tokens>`, CC-verbatim bytes.
1268
+ *
1269
+ * Activation, CC-exact in shape and BYOM-honest in substance: opted in here AND — for the two
1270
+ * COUNTDOWN arms — the task carries a token ceiling (`limits.maxTokens`, or a resource-slice
1271
+ * allocation's remainder). No ceiling ⇒ permanently silent, because core does not know and will not
1272
+ * guess your model's context window: the number it publishes is the one it already ENFORCES
1273
+ * (`stats.tokens` against that ceiling), so the readout can never disagree with an eventual
1274
+ * `limits.max_tokens_exceeded`. The two CONSTANT arms (`infinite` / `fixed`) read no measurement at
1275
+ * all and need no ceiling.
1276
+ *
1277
+ * Cadence, CC-exact: no threshold ladder and no throttle — every collected boundary carries it, and
1278
+ * the "progression" is the numbers advancing with spend. Recorded deviation (shared with
1279
+ * {@link budgetUsd}, and for the same reason): the lane only rides boundaries whose turn resolved
1280
+ * ≥1 tool call, so a boundary steer never EXTENDS a run that reached its natural end.
1281
+ *
1282
+ * DEFAULT ON since design/374 slice 4 (ruled 2026-08-28) — boolean, not `true`: explicit
1283
+ * `false` is the opt-out, the {@link backgroundTasks} / listing-family contract rather than the
1284
+ * `true`-only 133 form. BREAKING for a deployment that declares a token ceiling: pre-flip it saw
1285
+ * no `<total_tokens>` frame without opting in, and a probe pinning that absence reds.
1286
+ *
1287
+ * WHY THIS LANE AND NOT {@link budgetUsd}. CC 2.1.245+ ships the readout on by default (its own
1288
+ * settings schema reads "Defaults to padded-countdown"), which is the fact that reopened the
1289
+ * 2026-07-30 registration; the design/74 "no budget language reaches the model" default is
1290
+ * relaxed here for the same reason CC relaxed it. The BYOM honesty objection does not survive
1291
+ * either: core estimates nothing, publishes only the ceiling it already ENFORCES, and a task
1292
+ * that declares no token ceiling never fires the countdown arms at all — so the flip is
1293
+ * observable exactly in the deployments that asked for a ceiling. That property is EXACT, not
1294
+ * approximate: CC's two CONSTANT arms (`fixed`/`infinite`) publish a number with no ceiling
1295
+ * behind it, so the DEFAULT deliberately does not reach them — they stay behind an explicit
1296
+ * `true` here (see the runner's lane gate). The $ twin keeps its opt-in
1297
+ * default: CC has no equivalent default-on posture for it.
1298
+ */
1299
+ totalTokensReminder?: boolean;
1300
+ /**
1301
+ * RB-318 — which arm of the readout, CC 2.1.245's closed set (`off` / `infinite` / `fixed` /
1302
+ * `countdown` / `padded-countdown`); absent ⇒ CC's own default `"padded-countdown"`.
1303
+ *
1304
+ * - `countdown` — remaining = ceiling − spend, raw.
1305
+ * - `padded-countdown` — the same, through a monotone floor, so the number NEVER JUMPS BACK
1306
+ * within one engine leg (CC's per-agent smoothing floor; ONE `runTask` invocation is the
1307
+ * epoch). A durable-resume leg is a NEW epoch by design: the slice window, the spend
1308
+ * coordinate and the floor all restart, so the resumed leg's readout RE-ANCHORS to its own
1309
+ * real remaining — which may sit above the prior leg's last readout (the fresh slice window
1310
+ * is a genuine new allowance; CC's own re-anchor arm counts a new task epoch down from the
1311
+ * full budget again). Carrying the floor across legs would publish "0 tokens left" against a
1312
+ * window the run genuinely still holds — a frozen falsehood, deliberately not done.
1313
+ * - `off` — silences the lane while leaving it wired, for a deployment that resolves the mode from
1314
+ * its own env/settings the way CC does (core reads no env).
1315
+ * - `infinite` / `fixed` — CC's two constant arms: the literal `Infinite`, and the constant
1316
+ * 5000000. They publish no measurement (that is what they are for) and need no ceiling.
1317
+ *
1318
+ * A value outside the set is REFUSED at prepare (`config.attachment_invalid`), never folded to the
1319
+ * default — a near-miss spelling must not silently publish a different readout than the one asked for.
1320
+ */
1321
+ totalTokensReminderMode?: "off" | "infinite" | "fixed" | "countdown" | "padded-countdown";
1322
+ /** Post-compact background-task restatement — DEFAULT ON since 5.12.0 (boolean, not `true`:
1323
+ * explicit `false` is the opt-out; same contract as the listing family below). CC hard-codes
1324
+ * this behavior, and the opt-in default left every non-shell host (server-driven runs) with a
1325
+ * window where the model had no context evidence of an in-flight task after a compaction —
1326
+ * duplicate spawns / premature turn ends (board case, ruled 2026-08-04). BREAKING: pre-5.12.0
1327
+ * the default was off — a probe pinning "no background_tasks frame without opt-in" reds. */
1328
+ backgroundTasks?: boolean;
1329
+ toolsDelta?: true;
1330
+ /** [c209-C] listing family — DEFAULT ON (boolean, not `true`: explicit `false` is the opt-out;
1331
+ * see the family carve-out note above). BREAKING (B-3): pre-[c209-C] the default was off. */
1332
+ agentListing?: boolean;
1333
+ /** [c209-C] listing family — DEFAULT ON (same contract as `agentListing`). */
1334
+ skillsListing?: boolean;
1335
+ mcpInstructions?: true;
1336
+ };
1337
+ /**
1338
+ * Inject a built-in `report_blocked` tool the agent can call when it cannot finish
1339
+ * (missing info, permission, ambiguity). When called, the task ends with status "blocked".
1340
+ * Default: true.
1341
+ */
1342
+ enableBlockedReport?: boolean;
1343
+ /**
1344
+ * **LLM self-orchestration** (design/98, S8). Allow THIS task's model to AUTHOR + RUN its own workflow
1345
+ * (a deterministic JS orchestration script) via the `run_workflow` tool. Default `false`.
1346
+ *
1347
+ * 🔴 REQUIRES a deployment-provided HARD sandbox AND a governance baseline:
1348
+ * `RunnerDeps.workflowScriptRunner.safeForUntrustedScripts === true` (an isolated-vm / separate-process
1349
+ * runner — the Node `vm` dev runner is NOT a security boundary) AND `RunnerDeps.workflowGovernanceBaseline`
1350
+ * (the deployment-trusted governance every spawned sub-agent inherits, tighten-only). With EITHER missing
1351
+ * the tool is NOT mounted and the orchestration prompt is NOT injected (FAIL-CLOSED) — core never runs an
1352
+ * LLM-authored script in the dev `vm`, and never lets a script-spawned agent run with no governed baseline.
1353
+ *
1354
+ * Recommended ONLY for strong models; weak models default-off (force-enableable, but the hard-sandbox +
1355
+ * baseline bar still applies — the gate is the deployment's sandbox/governance, NOT the model id). NOT
1356
+ * enforced by model id.
1357
+ */
1358
+ selfOrchestration?: boolean;
1359
+ /** Per-model auth. Returns the api key (and optional headers) for a given provider/model. */
1360
+ getApiKeyAndHeaders?: (model: Model) => Promise<{
1361
+ apiKey: string;
1362
+ headers?: Record<string, string>;
1363
+ } | undefined>;
1364
+ /**
1365
+ * External cancellation. When it aborts, the task aborts (the brain request is cancelled and any
1366
+ * pending tool-policy gate releases). Pass a parent tool's `ToolExecuteContext.signal` here so a
1367
+ * nested `runTask` stops the instant the parent does instead of running to its own `limits.maxWalltimeMs`.
1368
+ */
1369
+ signal?: AbortSignal;
1370
+ }