@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,1174 @@
1
+ /**
2
+ * The TOOL contract: the side-effect word (`ToolEffect`), the content-origin word
3
+ * (`ToolContentOrigin`), the reversibility verdict, the spec itself (`ToolSpec`), what a call returns
4
+ * (`ToolReturn`), the nested-usage rollup a delegating tool reports (`NestedUsage` /
5
+ * `NestedUsageAccum`) and the execute context every call receives. `ToolExecuteContext` lives here
6
+ * rather than in a module of its own because it is the parameter of `ToolSpec.execute` — splitting the
7
+ * pair would put one contract on two sides of a module boundary. Layer 0 vocabulary; `types.ts`
8
+ * re-exports every name below, so no consumer's import changes.
9
+ */
10
+ import type { TSchema } from "typebox";
11
+ import type { ThinkingLevel, ToolInputValidationContext, ToolInputVerdict } from "../internal/harness.js";
12
+ import type { DocumentContent, ImageContent, Model, TextContent } from "../internal/llm.js";
13
+ import type { AgentDefinition } from "./agent-definition.js";
14
+ import type { TaskEvent } from "./task-event.js";
15
+ import type { TaskSpec } from "./task-spec.js";
16
+ import type { BackgroundChildEvent } from "./delegation-frames.js";
17
+ /**
18
+ * Side-effect class of a tool. Used by wake/resume reconciliation: when a call was interrupted
19
+ * before its result was recorded, the outcome is unknown, so core must decide whether re-doing it
20
+ * is safe. See {@link reconcileInterruptedSession}.
21
+ * - `read` — no side effects (a query/lookup). Safe to call again.
22
+ * - `idempotent` — has a side effect, but repeating it with the same args is equivalent to doing it
23
+ * once (e.g. `PUT` a known key, set-to-value). Safe to call again.
24
+ * - `write` — a non-idempotent side effect (delete, append, charge, send). **Unsafe to repeat
25
+ * blindly.** This is the conservative default when `effect` is omitted.
26
+ */
27
+ export type ToolEffect = "read" | "write" | "idempotent";
28
+ /** design/178 §3 — the content-origin classes (see {@link ToolSpec.contentOrigin}). */
29
+ export type ToolContentOrigin = "external" | "execution" | "local";
30
+ /** A native tool the model can call. `parameters` is a typebox schema; `execute` does the work. */
31
+ /**
32
+ * design/77 §4 — what a {@link ToolSpec.reversibilityProbe} returns for ONE call.
33
+ *
34
+ * `reversible` alone decides: the gate tightens a surviving `allow` to `ask` unless it is exactly `true`,
35
+ * and no optional member can widen that. They exist so the resulting approval card can say WHY, a
36
+ * thing the probe knows at the moment it decides and nothing downstream can re-derive.
37
+ *
38
+ * Supply `cause` when the reason has parts a consumer should render itself; `reason` when it is prose
39
+ * this engine cannot interpret. All three are optional and independent, so a probe written against the
40
+ * original bare `{ reversible }` shape remains valid.
41
+ */
42
+ export interface ReversibilityVerdict {
43
+ reversible: boolean;
44
+ /** Free-text account, neutralized and length-capped by the gate before it reaches any card. */
45
+ reason?: string;
46
+ /** Structured account — see {@link import("./checkpoint-store.js").ProbeCause}. Validated by the gate;
47
+ * a malformed value costs the DISCLOSURE only, never the ask. */
48
+ cause?: import("./checkpoint-store.js").ProbeCause;
49
+ /**
50
+ * #502 (additive) — this demotion is STRUCTURAL, not the probe hesitating: the call crosses a
51
+ * boundary the deployment declared, so a persisted allow rule must not retire the resulting ask.
52
+ * The gate folds it into the #144 mandate family ("allow rules silence the CLASSIFIER's questions,
53
+ * never a MANDATED one") beside an operator `shellGate:"always"` and the tool's own
54
+ * egress/irreversibility marks: a matching rule is DISCLOSED as shadowed instead of clearing the
55
+ * ask, and the card's "stop asking me this" offer is withheld rather than offered and then refused.
56
+ *
57
+ * Read on the demotion arm only — a `reversible: true` verdict ends the gate with no ask to mandate,
58
+ * and this member cannot make one. It is the one member a DEPLOYMENT-supplied probe may set that the
59
+ * engine acts on, and it is safe for the same reason the probe itself is: the effect is strictly
60
+ * TIGHTENING (one more question survives one more configuration), so an over-claiming probe costs
61
+ * its own deployment a re-confirmation and can never turn an ask into an allow.
62
+ *
63
+ * The built-in shell probe sets it for exactly one family: a listed reader naming a path outside the
64
+ * session's allowed directories. The engine's own out-of-root demotion has no clearing channel in
65
+ * this version other than confirming the call each time — a directory-scoped grant that WOULD clear
66
+ * it is a separate, later mechanism, and until it exists a surface must not word the refusal as
67
+ * though one already answers it.
68
+ */
69
+ mandated?: boolean;
70
+ }
71
+ export interface ToolSpec<TParams extends TSchema = TSchema> {
72
+ /**
73
+ * Model-facing tool name. MUST NOT contain `__` — that separator is reserved by the PROTOCOL TABLE
74
+ * (`protocol-table.ts`), the umbrella over every namespaced family the engine speaks: `mcp__<server>__<tool>`,
75
+ * `a2a__<peer>__<skill>`, and whatever a third protocol appends. A caller tool name carrying it is rejected
76
+ * at prepare-time (`config.tool_name_invalid`).
77
+ *
78
+ * The live reason (not the retired durable-resume name shim, RB-476-A): every table-driven site — the
79
+ * namespace `parse`, the grouped display key, the permission-rule lanes, the tool-manifest origin
80
+ * projection — decides "which protocol owns this name, and which peer inside it" from the prefix and the
81
+ * FIRST separator. A caller tool named through that shape would be attributed to a remote peer that does
82
+ * not exist, on surfaces where attribution is what a policy or an operator acts on.
83
+ */
84
+ name: string;
85
+ /**
86
+ * Backward-compatible model-facing names that should dispatch to this tool. New transcripts and tool rosters
87
+ * should use {@link name}; aliases exist only so old model calls / SDK payloads / resumed sessions don't strand
88
+ * across a canonical rename.
89
+ */
90
+ aliases?: string[];
91
+ description: string;
92
+ /**
93
+ * R2 双形轴(2026-07-18 裁):the CLASSIC-profile variant of {@link description}. CC 2.1.212
94
+ * keys its tool descriptions on `LT(model_id)` (simple short form vs classic long form); sema's
95
+ * counterpart is {@link TaskSpec.promptProfile} — default "simple" (what CC serves every BYOM
96
+ * model id). When the run resolves to "classic" and this field is present, it REPLACES
97
+ * `description` at mount time. Absent = the tool has one form (description serves both profiles).
98
+ */
99
+ descriptionClassic?: string;
100
+ /**
101
+ * Human label for UIs. Defaults to `name`.
102
+ *
103
+ * ⚠️ Currently INERT (design/71 liveness sweep, 2026-06-12): mapped onto the tool object but no
104
+ * runtime path or emitted event reads it — the model and TaskStream consumers only ever see
105
+ * `name`. Kept for API stability; wire it into tool events or drop it before building on it.
106
+ */
107
+ label?: string;
108
+ /**
109
+ * G1 通告层 (CC `agent_listing_delta` parity): the delegation tool's agent-type roster (name + one-line
110
+ * description), filled by `createSubagentTool` so the Runner's announcer can surface the
111
+ * "Available agent types" reminder without reaching into the tool's closure. Deployment-authored tools
112
+ * normally leave it unset. [c209-C]: this is now the roster's ONLY model-facing carrier (the tool
113
+ * description/schema carry a static pointer sentence instead — cache-prefix immunity), delivered on
114
+ * the FIRST user turn plus boundary drift deltas; `TaskSpec.attachments.agentListing` is default-ON
115
+ * (explicit `false` opts out, with a config warning when a roster is mounted).
116
+ */
117
+ agentListing?: ReadonlyArray<{
118
+ name: string;
119
+ description: string;
120
+ }>;
121
+ /**
122
+ * design/178 §3 (ruled 2026-08-08) — the MACHINE-readable twin of {@link agentListing}'s display
123
+ * `(Tools: …)` note: per offered agent type, the allow/deny narrowing that decides which of the
124
+ * offered pool a child of that type actually mounts. Filled by `createSubagentTool` beside
125
+ * `agentListing`; a type with neither list gets the whole offered pool.
126
+ *
127
+ * Read by ONE consumer: the memory content-origin face, which classifies a delegation RESULT by the
128
+ * child's tool face. A child that can reach a network or protocol tool returns content from outside
129
+ * the trust boundary just as surely as calling that tool here would, and the parent's transcript is
130
+ * where it lands. Static by construction — the child's face is known from its definition, so no
131
+ * runtime tracking of what the child actually did is involved (that is the v2 ticket).
132
+ */
133
+ agentToolFaces?: ReadonlyArray<{
134
+ name: string;
135
+ allowTools?: readonly string[];
136
+ denyTools?: readonly string[];
137
+ canRedelegate?: boolean;
138
+ }>;
139
+ /**
140
+ * design/178 §3 (ruled 2026-08-08) — the POOL a delegated child draws from, as the delegation tool
141
+ * captured it. Read together with {@link agentToolFaces} by the memory content-origin face: the face
142
+ * narrows, this is what it narrows FROM. It is the CHILD'S pool, deliberately: a delegation tool
143
+ * carries its own tool set, and a parent that mounts nothing but the delegation tool can still hand
144
+ * its children a network tool, so the parent's roster is the wrong thing to look at.
145
+ *
146
+ * ABSENT ⇔ the pool is not statically knowable — a spawn-time tool factory can inject tools whose
147
+ * identity does not exist until the call happens — and the reader fails closed on that.
148
+ */
149
+ agentToolPool?: ReadonlyArray<{
150
+ name: string;
151
+ aliases?: readonly string[];
152
+ contentOrigin?: ToolContentOrigin;
153
+ }>;
154
+ /** approval display projection (see AgentTool.approvalPreview): pure args→display value
155
+ * for human approval surfaces; clamped + throw-swallowed by the gate; never adjudication input. */
156
+ approvalPreview?: (args: unknown) => unknown;
157
+ /**
158
+ * OPTIONAL input pre-validation — runs BEFORE the permission ask (and before every other tool-call
159
+ * gate station: PreToolUse hooks, policy, the auto-mode classifier), AFTER schema validation. A
160
+ * refusal (`{ ok: false, message }`) is returned to the model as a typed error result and NO
161
+ * approval card is shown for the call; `{ ok: true }` / `undefined` lets the call proceed to the
162
+ * gate unchanged. Upstream form (CC 2.1.250 `validateInput` → `{result:false, message, errorCode}`,
163
+ * consulted ahead of the permission prompt): a call the tool would refuse on its own precondition —
164
+ * an Edit/Write/NotebookEdit whose target file was never Read this session — used to reach the
165
+ * operator as a card that could only ever fail; the model then retried the same failing call under
166
+ * auto mode until the turn cap. A validator MUST be pure (read tracking state, path grammar; no
167
+ * side effects, no writes, nothing the run observes) and MUST refuse with the SAME text its
168
+ * execution would have produced for that input, so the model sees one refusal either way. A
169
+ * validator that THROWS is read as "no verdict": the call proceeds to the gate and the fault is
170
+ * disclosed via `RunnerDeps.onError` (phase `"hook"`) — a broken validator must not refuse tools.
171
+ * The execution-time check stays in place (validate and execute are two reads; a direct
172
+ * `AgentTool.execute` caller — durable replay — never passes this seat).
173
+ */
174
+ validateInput?: (args: unknown, ctx: ToolInputValidationContext) => Promise<ToolInputVerdict | undefined> | ToolInputVerdict | undefined;
175
+ /**
176
+ * [c209-C] Q4 — the model catalog names (`SubagentToolOptions.models` keys), filled by
177
+ * `createSubagentTool` alongside {@link agentListing}. Rendered as the agent_listing INITIAL frame's
178
+ * trailing "Models available for the 'model' parameter: …" line — the announce-face replacement for
179
+ * the `(available: …)` / `(one of: …)` interpolations the tool description/schema used to carry
180
+ * (no CC anchor: CC's model face is a static tier enum; sema's catalog is deployment-dynamic).
181
+ */
182
+ agentModels?: readonly string[];
183
+ /**
184
+ * core half — PER-TASK agent-roster rebuild seam, filled ONLY by `createSubagentTool`
185
+ * (deployment-authored tools leave it unset). The delegation tool's roster is a mount-time SNAPSHOT
186
+ * baked into its parameter enum/description (see the `SubagentToolOptions` snapshot note), so per-task
187
+ * additions ({@link TaskSpec.agents}) cannot mutate the mounted instance — `prepareTask` instead calls
188
+ * this to obtain a REBUILT ToolSpec whose roster is the UNION of the mount-time roster and the given
189
+ * per-task definitions (a same-name per-task definition WINS over the mount-time one; built-in
190
+ * shadowing/fork semantics re-derive over the merged set). Everything else about the tool
191
+ * (pool/limits/depth/background lanes) rebuilds from the same options snapshot. The rebuild re-runs the
192
+ * full assembly-time validation (duplicate/reserved names, string `model` refs against the catalog) —
193
+ * fail-loud at task start, never deep inside a run. POLICY-NEUTRAL: this is a roster LIST injection
194
+ * only — the child's effective policy chain (tighten-only parent inheritance via
195
+ * `RunInternals.inheritedGate`) applies at spawn time, entirely downstream of the roster.
196
+ */
197
+ withAgents?: (agents: ReadonlyArray<AgentDefinition>) => ToolSpec;
198
+ /**
199
+ * design/136 §2.1.c — OPTIONAL pre-validation argument adapter (mirrors the engine
200
+ * `AgentTool.prepareArguments` seam it is forwarded onto): map an alternative argument shape onto the
201
+ * current schema BEFORE schema validation. MUST be idempotent and identity for already-canonical args
202
+ * (the engine path applies it before validation AND the defineTool wrapper applies it again before its
203
+ * own check). RB-479 note: its original LEGACY-replay purpose (pre-rename durable calls resolved via
204
+ * the retired canonicalizer) is dead with RB-476-A; what remains is a generic third-party arg-migration
205
+ * capability — the first-party legacy adapters riding it are booked for removal (RB-479-A③).
206
+ */
207
+ prepareArguments?: (args: unknown) => unknown;
208
+ /**
209
+ * Side-effect class for wake/resume safety. Defaults to `write` (conservative) when omitted:
210
+ * an interrupted call of unknown effect is treated as possibly-applied and never auto-repeated.
211
+ */
212
+ effect?: ToolEffect;
213
+ /**
214
+ * design/70: marks a tool whose execution is an EXTERNAL write (egress) — an outward-visible,
215
+ * effectively-irreversible action (open a PR, push a branch, send) as opposed to an internal side
216
+ * effect. **Orthogonal to {@link effect}**: `effect` is the repeat-safety axis (wake/resume
217
+ * reconciliation), `egress` is the blast-radius axis (the design/37 gate); reconcile/wake ignore it.
218
+ * The gate NEVER auto-allows an egress tool: a surviving `allow` (default or policy) is tightened to
219
+ * `ask`, so execution always passes an explicit ask-resolution — synchronous `onAsk`, a durable
220
+ * suspend (design/45), or a deployment-authored approver. Headless stays deny (an external write
221
+ * never happens without explicit confirmation). An egress tool must be repeat-unsafe: declaring
222
+ * `effect: "read"`/`"idempotent"` with `egress: true` is a configuration error (fail-fast at
223
+ * prepare) — omitting `effect` is fine (the default IS `write`).
224
+ */
225
+ egress?: true;
226
+ /**
227
+ * design/77 §4 (Gate 4 — irreversibility axis): how reversible this tool's effect is. A THIRD axis,
228
+ * orthogonal to {@link effect} (repeat-safety) and {@link egress} (blast-radius). For office/finance
229
+ * primitives (send money, file a return, send an email) where a wrong action cannot be undone, this
230
+ * forces a durable human-approval suspend BEFORE the tool runs:
231
+ * - `"never"` — fully reversible; the gate leaves a surviving `allow` untouched (no ask).
232
+ * - `"maybe"` — reversibility depends on the args; the gate calls {@link reversibilityProbe} (when one is
233
+ * declared) on a surviving `allow` and tightens to `ask` UNLESS the probe reports reversible. A tool
234
+ * that declares a `reversibilityProbe` but omits `irreversibility` defaults to `"maybe"` (so it always
235
+ * enters the activation set and is gated). Since #502 the probe ALSO runs over a surviving `ask`
236
+ * — see {@link reversibilityProbe} for what that arm reads (only {@link ReversibilityVerdict.mandated})
237
+ * and what it costs; it tightens nothing there, because an ask cannot be tightened into an ask.
238
+ * - `"always"` — irreversible; the gate ALWAYS tightens a surviving `allow` to `ask` (like {@link egress}).
239
+ *
240
+ * The tighten mirrors the egress tighten (it only ever tightens allow→ask; a `deny`/`ask` is untouched),
241
+ * runs immediately AFTER it, and routes through the SAME ask-resolution: synchronous `onAsk`, a durable
242
+ * suspend (a new `{kind:"irreversible_ask"}` checkpoint → `status:"suspended"`), or a deployment approver.
243
+ * Headless with no approver SUSPENDS durably (never a model-facing deny — that would be a reward-hack
244
+ * retry surface), so an irreversible action never runs unattended without explicit confirmation.
245
+ *
246
+ * **Note (deliberate redundancy):** `egress` already means "effectively-irreversible external write"; an
247
+ * `egress:true` + `irreversibility:"always"` tool is DOUBLE-tightened. Harmless (allow→ask is idempotent
248
+ * under the deny>ask fold) — the two axes overlap on purpose, they are NOT strictly disjoint.
249
+ */
250
+ irreversibility?: "never" | "maybe" | "always";
251
+ /**
252
+ * design/77 §4: optional deployment-injected probe for an `irreversibility:"maybe"` tool. Given the call's
253
+ * (post-hook) args, it reports whether THIS specific call is reversible. It is time-bounded (the
254
+ * approval timeout when configured, a 30s default when absent — the probe wait is always finite) and
255
+ * **fail-closed**: a non-`reversible` verdict, a timeout, or a throw all tighten to
256
+ * `ask`. Declaring this probe defaults `irreversibility` to `"maybe"`. It is read from the spec at
257
+ * prepare-time and captured in a closure — NOT a tool argument — so the model cannot monkey-patch it.
258
+ *
259
+ * **WHEN THE GATE CALLS IT — two arms, and the second is newer than this paragraph's first draft**
260
+ * (#502; `irreversibility` must resolve to `"maybe"` for either):
261
+ * 1. the surviving decision is `allow` — the TIGHTEN arm: the verdict decides allow→ask, and a
262
+ * tightening verdict's `reason`/`cause` ride the minted ask;
263
+ * 2. the surviving decision is already `ask` — the MANDATE arm: the probe is the only source of
264
+ * {@link ReversibilityVerdict.mandated}, and an ask another layer raised needed it too. (Before
265
+ * #502 a policy that asked FIRST skipped this block entirely, so an out-of-root read never
266
+ * acquired its mandate and a covering allow rule retired the question — measured, not theorised.)
267
+ * Here the `reversible` verdict is DISCARDED (a probe may never un-ask another layer's question),
268
+ * `reason`/`cause` are not merged onto somebody else's ask, and only `mandated` is read.
269
+ * The TIGHTEN is still `allow`→`ask` only — a `deny`/`ask` is never re-minted — but the INVOCATION
270
+ * set is wider than the tighten's, and that difference is the deployment's to budget for: on an
271
+ * ask-first policy the probe is called (and awaited, to the bound above) on every `"maybe"`-tier
272
+ * call, and a probe that throws or times out reports through `onError` with `phase: "hook"` on
273
+ * those calls too. A probe should therefore be a cheap, side-effect-free inspection of the args.
274
+ *
275
+ * **`reason` (optional, additive both ways)** — WHY this call was not proven reversible, in the probe's
276
+ * own words, minted in the same pass that reached the verdict (so nothing downstream re-derives it).
277
+ * Read ONLY on a tightening verdict: a `reversible: true` return ends the gate, and a timeout/throw
278
+ * produces no verdict at all, so the cause travels exactly when there is an approval card to put it on.
279
+ * The gate neutralizes and length-caps it, then carries it to both approval routes — the synchronous
280
+ * `AskRequest.probeReason` and the durable park's `RiskDescriptor.probeReason`. Compatible in BOTH
281
+ * directions by construction: a probe written against the older bare `{ reversible }` shape stays
282
+ * type-correct and simply supplies no cause, and an engine that does not know the member ignores it.
283
+ *
284
+ * **`cause` (optional)** — the STRUCTURED alternative, for a probe whose verdict is machine-describable:
285
+ * a `code` plus operand families as arrays with honest totals ({@link ProbeCause}), carried to
286
+ * `AskRequest.probeCause` / `RiskDescriptor.probeCause`. Prefer it wherever the cause has parts. Prose
287
+ * has to be written before the edge cases are known — a sentence about what a command DOES is false
288
+ * wherever the check over-fires, and a multi-part disclosure flattened into one capped string loses
289
+ * whichever part sorts last — whereas a code cannot be false and an array cannot be truncated into a
290
+ * lie. `reason` remains for deployments whose cause is genuinely just prose this engine cannot read.
291
+ * Both may be supplied; each is validated and carried independently.
292
+ *
293
+ * TRUST: the text is DEPLOYMENT-authored (and, for the built-in shell probe, derived from the model's
294
+ * own command), so it is DISPLAY/TRIAGE metadata only — never adjudication input, and never a channel
295
+ * that can widen a verdict. A probe cannot use it to auto-allow: `reversible` alone decides.
296
+ *
297
+ * That property is STRUCTURAL, and keeping it so constrains where the cause may be written. It is
298
+ * carried as a member and deliberately NOT folded into the ask's `message`, because the message is
299
+ * what an auto-mode classifier is handed as its `askMessage` — and that classifier may answer an ask
300
+ * with `allow`. Text on the prose channel is therefore steering input to a decider that can clear the
301
+ * very ask the probe raised, which is exactly the authority a probe must not have. Anything added
302
+ * later that renders this cause must keep it on display surfaces only.
303
+ */
304
+ reversibilityProbe?: (args: unknown) => ReversibilityVerdict | Promise<ReversibilityVerdict>;
305
+ /**
306
+ * design/178 §3 — CONTENT-ORIGIN class of what this tool can bring into the session, a fourth axis
307
+ * orthogonal to {@link effect} (repeat-safety), {@link egress} (blast-radius) and
308
+ * {@link irreversibility}. It feeds long-term-memory write governance only — no gate/policy/roster
309
+ * behavior reads it, and on a task without an engine-memory session it is fully inert:
310
+ * - `"external"` — the tool takes content from outside the deployment's trust boundary (network
311
+ * fetch/search families, external protocol channels). Invoking it marks the session's memory
312
+ * EXTERNALLY EXPOSED (one-way, durable). What the mark does at harvest is the deployment's
313
+ * {@link RunnerDeps.memoryProvenance} mode's question (design/336): under `"carry"` (default)
314
+ * ordinary entries commit WITH an engine-minted `origin` marker and instruction-form files are
315
+ * withheld; under `"off"` the harvest quarantines instead of committing — either way,
316
+ * third-party text never rides the automatic path into long-term memory UNMARKED.
317
+ * - `"execution"` — general execution (shell/exec families). NOT polluting by default — an
318
+ * execution tool CAN reach external content indirectly, but excluding every shell session from
319
+ * memory would disable memory for the main coding scenario; the write-side scans/fences remain
320
+ * as compensating controls, and `TaskSpec.memory.execIsExternalContent` upgrades this class to
321
+ * `"external"` for high-assurance deployments.
322
+ * - `"local"` — purely local reads/computation.
323
+ * Classification is single-sourced with the tool definition: core built-ins declare theirs here;
324
+ * a HOST tool that omits it is classified fail-closed as `"external"` (unknown = external) unless
325
+ * the deployment exempts the name via `TaskSpec.memory.trustedTools`. A declaration BEATS that
326
+ * allowlist (the allowlist is the channel for UNDECLARED tools, never a lever to re-grade a tool
327
+ * that classified itself), and only a member of the closed vocabulary counts as one — an unreadable
328
+ * value classifies `"external"` rather than exempting anything.
329
+ *
330
+ * Tools core mints on the deployment's behalf get the same seat one level up: an MCP server's whole
331
+ * tool set is declared at its entry ({@link McpServerSpec.contentOrigin}, design/378), because the
332
+ * host never touches those tool objects.
333
+ */
334
+ contentOrigin?: ToolContentOrigin;
335
+ /**
336
+ * Batch execution mode (design/64 §1 P1, CC reads-parallel/writes-sequential partition). When omitted,
337
+ * `defineTool` derives it FAIL-CLOSED from `effect` (CC 2.1.206 `TOOL_DEFAULTS.isConcurrencySafe → false`):
338
+ * `read` → `"parallel"`; `write`/`idempotent`/UNDECLARED → `"sequential"` — an unannotated tool never
339
+ * silently rides a parallel batch, and two writes in one batch never race. Set explicitly to override.
340
+ */
341
+ executionMode?: "sequential" | "parallel";
342
+ /**
343
+ * design/120 P1.5 (CC `isConcurrencySafe(parsedInput)` parity): input-level concurrency
344
+ * refinement for a non-`"parallel"` tool — return `true` to let THIS CALL run in a parallel batch
345
+ * (and, under `streamingToolExecution`, start in-stream). An explicit `executionMode:"parallel"`
346
+ * needs no refinement (always safe); a throw = false (fail-closed). In the batch/stream paths the
347
+ * hook receives schema-VALIDATED (coerced) args — CC 206 `parsed.data` parity; a schema-invalid
348
+ * call is judged unsafe before the hook runs. Still treat defensively (direct callers may pass raw).
349
+ * The one real consumer is Bash's read-only classifier.
350
+ */
351
+ isConcurrencySafe?: (args: unknown) => boolean;
352
+ /** typebox schema for the tool arguments. */
353
+ parameters: TParams;
354
+ /**
355
+ * Large-result offload (design/30): set `false` to NEVER offload this tool's output (e.g. a file-read
356
+ * tool whose output IS the deliverable — offloading then immediately re-reading would just churn).
357
+ * Default: offload when the result exceeds the resolved threshold.
358
+ */
359
+ offload?: boolean;
360
+ /** Per-tool offload threshold (chars), overriding the global {@link RunnerDeps.toolResultThresholdChars}.
361
+ * Same contract as that global knob (CLS-A-2): `0`, `Infinity` or any non-finite value DISABLES offloading
362
+ * for this tool — it is not a "threshold so low that everything offloads". */
363
+ offloadThresholdChars?: number;
364
+ /**
365
+ * Deferred disclosure (design/36): when `true`, this tool is NOT inlined with its full JSON Schema in
366
+ * every request. It appears as a lightweight placeholder (`{name, one-line hint, empty params}`) so
367
+ * the model knows it exists. Activation is a DISCLOSURE fact (has the model seen the schema?), not an
368
+ * execution precondition: the model activates the tool via the injected ToolSearch, or by making a call
369
+ * whose arguments already match the real parameters (see {@link TaskSpec.deferSelfResolve}, on by
370
+ * default). Where the schema then lands depends on {@link TaskSpec.toolMaterializeStrategy} — the next
371
+ * request's `tools[]` under `"swap"` (the default, cache prefix undisturbed), or the ToolSearch result
372
+ * text under `"static"`, where the placeholder is never swapped. Use for large/MCP tool sets where
373
+ * inlining hundreds of schemas blows up turn-1 tokens and risks prefix-cache breakage. Default: not
374
+ * deferred (full schema inlined). See {@link RunnerDeps.deferMode}.
375
+ *
376
+ * SCOPE: this flag reaches the classifier through CALLER specs (`TaskSpec.tools`) only — a
377
+ * built-in/injected tool's spec never enters that list, so setting `defer` on a spec that shadows a
378
+ * built-in name does nothing. To defer an already-mounted tool (built-ins included), list its wire
379
+ * name in {@link TaskSpec.deferTools}. Also inert when the tool ends up excluded (exclusion wins)
380
+ * or pinned ({@link alwaysLoad} / {@link TaskSpec.alwaysLoadTools}).
381
+ */
382
+ defer?: boolean;
383
+ /**
384
+ * RB-400-a — inline-keep override, judged FIRST in the deferral chain (CC 220 counterpart:
385
+ * `alwaysLoad`, declared per MCP tool via `_meta["anthropic/alwaysLoad"]`, whose `isDeferredTool`
386
+ * checks it before every deferral arm): `true` pins this tool's full schema inline in every
387
+ * request — it is never deferred, regardless of its own {@link defer}, {@link TaskSpec.deferTools},
388
+ * or `deferMode:"auto"`. The deferral sources only ever ADD; this is the explicit subtract valve.
389
+ * Exclusion still wins ({@link TaskSpec.excludeTools} unmounts — nothing left to keep inline).
390
+ */
391
+ alwaysLoad?: boolean;
392
+ /**
393
+ * design/277 — model-gate CLASS tag (open vocabulary; the built-in table is
394
+ * {@link import("./tool-model-gate.js").TOOL_MODEL_GATE_CLASSES}, v1 vocabulary =
395
+ * `"task-scaffold"`). A tagged entry declares "I am a default-mounted scaffold of this class":
396
+ * at prepare, when the task's RESOLVED model id matches the class's rule, THIS entry is dropped
397
+ * from the roster (true unmount, entry-level — a same-name untagged entry is untouched; the
398
+ * removed entry stops occupying its name on every downstream surface). Untagged = never gated.
399
+ * The default-bundle assemblers stamp their default arms only; an EXPLICITLY composed tool is
400
+ * not tagged (user asked ⇒ user gets) — so don't stamp hand-mounted factories. FAIL-OPEN: an id
401
+ * the merged table knows nothing about is never gated (BYOM open set — the table encodes
402
+ * positive knowledge only), and a tag naming an unknown class is inert + announced
403
+ * (`config.tool_model_gate_unknown_class`). Restore channels: explicit composition (no tag),
404
+ * {@link TaskSpec.restoreGatedTools}, env `SEMA_TOOL_MODEL_GATE=off`,
405
+ * `RunnerDeps.toolModelGate: false`; {@link TaskSpec.excludeTools} always wins regardless.
406
+ * The materialized `AgentTool` face does NOT carry this field (`defineTool` is a whitelist
407
+ * constructor) — the decision completes on the ToolSpec face inside `prepareConfigDoors`,
408
+ * before any conversion.
409
+ */
410
+ modelGate?: string;
411
+ /**
412
+ * Tool contract identity (stage S2, prompt-assembly protocol §7): declares the EXECUTION
413
+ * CONTRACT this tool implements, independent of its presentation text. `defineTool` attaches it
414
+ * to the materialized tool via the catalog side-table (identity follows the OBJECT, never the
415
+ * wire name — a caller tool shadowing a built-in name never inherits the core contract). Omitted
416
+ * ⇒ the tool reports a deterministic `legacy:<shapeDigest>` contract in the assembly manifest.
417
+ */
418
+ contract?: {
419
+ /** Stable versioned id, e.g. `core.read@2` (built-ins) or `acme.deploy@1` (caller tools). */
420
+ contractId: string;
421
+ /** Declared behavior revision — bump when execute-visible behavior changes. */
422
+ implementationRevision: string;
423
+ };
424
+ /**
425
+ * Execute the tool. Return a string (becomes text content) or a structured result.
426
+ * Throw to signal failure (the loop encodes it as an error tool result).
427
+ */
428
+ execute: (args: unknown, ctx: ToolExecuteContext) => Promise<ToolReturn> | ToolReturn;
429
+ }
430
+ /** What a tool's execute may return. */
431
+ export type ToolReturn = string | {
432
+ content: Array<TextContent | ImageContent | DocumentContent> | string;
433
+ /** Arbitrary structured details for logs/UI (not shown to the model unless put in content).
434
+ * Producer side of the tool-result 双面契约: `content` is the MODEL face (may carry model-directed
435
+ * framing/trailers), `details` is the HOST face (per-tool structured Output). Hosts render from the
436
+ * structured face and never re-parse the model face — see the `tool_end.structured` JSDoc
437
+ * (CC 2.1.207 @765495, CC207-CORE-SWEEP P2-3) for the full contract. */
438
+ details?: unknown;
439
+ /** Hint the agent to stop after this tool batch. */
440
+ terminate?: boolean;
441
+ /**
442
+ * RB-211 (root-cause widening): a `ToolSpec.execute` can RETURN a failure
443
+ * receipt instead of throwing (teaching errors that carry structured `details`, e.g. an
444
+ * unrecoverable-but-informative rejection). Before this field existed, `ToolReturn` had NO
445
+ * isError channel at all — `defineTool`'s wrapper builds a fresh `{content,details,terminate}`
446
+ * object from whatever `execute` returns, so ANY extra property (including a caller trying to
447
+ * set `isError` via an unchecked/cast return) was silently discarded before it ever reached the
448
+ * wire. Confirmed empirically: a `ToolSpec.execute` explicitly returning `isError:true` still
449
+ * produced `isError:false` on the model-facing `toolResult`, in BOTH the live per-turn path and
450
+ * the durable-resume path — a returned rejection was structurally unable to look like anything
451
+ * other than success. Omitted/false = success (matches `AgentToolResult.isError`'s own
452
+ * convention exactly — this is that SAME field, finally reachable from a `ToolSpec`).
453
+ */
454
+ isError?: boolean;
455
+ };
456
+ /**
457
+ * REF-D15 — DELEGATED (nested) sub-run usage: the ONE object that flows tool → runner → result. A
458
+ * delegation tool reports it via {@link ToolExecuteContext.reportUsage}, the Runner folds it into its
459
+ * accumulator, and it surfaces as {@link TaskResult.stats}`.nested`. Named because all four sites carry
460
+ * the same quantity and this is money (`costMicroUsd` is micro-USD, the billing figure).
461
+ *
462
+ * `costMicroUsd` is OPTIONAL here by the RB-368 absence contract: a run whose model had no price entry
463
+ * reports NO cost rather than a fabricated `0`, so "unpriced" stays distinguishable from "declared free".
464
+ * The RUNNING accumulator uses {@link NestedUsageAccum} instead, where it is required.
465
+ *
466
+ * NOT a general `UsageStats` base type, deliberately: the own-usage field sets on `Stats` (runner-internal,
467
+ * mostly REQUIRED so a new accumulation site that forgets a leg fails the type check) and on
468
+ * `TaskResult.stats` (public, all OPTIONAL because a gateway may report no usage at all) differ ON PURPOSE
469
+ * — folding them into one shape would delete that type check.
470
+ */
471
+ export interface NestedUsage {
472
+ tokens: number;
473
+ turns: number;
474
+ tasks: number;
475
+ /** ABSENT when the delegated spend was unpriced (RB-368) — never a fabricated 0. */
476
+ costMicroUsd?: number;
477
+ }
478
+ /**
479
+ * REF-D15 — the RUNNING accumulator form of {@link NestedUsage}: `costMicroUsd` is REQUIRED because an
480
+ * accumulator is seeded at 0 and summed into (`+= u.costMicroUsd ?? 0`), so it always holds a number.
481
+ * Used by the live `Prepared.nestedStats` and by the checkpoint's `nestedStats` snapshot, which must
482
+ * round-trip a concrete total across a suspend/resume (an absent field there would lose pre-suspend
483
+ * child cost — design/38 §4.bis/Q7).
484
+ */
485
+ export interface NestedUsageAccum {
486
+ tokens: number;
487
+ turns: number;
488
+ tasks: number;
489
+ costMicroUsd: number;
490
+ /** batch-4 F3 (RB-368 reaches the nested face): true once ANY delegated leg reported no cost —
491
+ * the summed `costMicroUsd` is then a priced-subtotal, not a total, and the publish site must go
492
+ * ABSENT instead of impersonating one. REQUIRED so every seeding/snapshot site is tsc-forced to
493
+ * carry knownness alongside the number it qualifies. */
494
+ anyUnpriced: boolean;
495
+ }
496
+ export interface ToolExecuteContext {
497
+ toolCallId: string;
498
+ signal?: AbortSignal;
499
+ /**
500
+ * design/147 S1 — the SPAWNING run's taskId when THIS run is a delegated child (Runner-filled from
501
+ * the trusted internals chain, absent on a top-level run). Read-only context: the Agent tool's
502
+ * hierarchy clamp and SendMessage's "main" uplink judge child-ness on it. NOT a TaskSpec field —
503
+ * the untrusted caller surface cannot fake parentage into extra capability (forging it only
504
+ * TIGHTENS: the clamp fires and the uplink routes to a parent that ignores unknown children).
505
+ */
506
+ parentTaskId?: string;
507
+ /** design/148 S1 — this run's adopted center prompt artifact (Runner-filled, trusted; same seat
508
+ * as {@link parentTaskId}): delegation tools thread it into child RunInternals so the whole
509
+ * agent tree composes one closure. Absent on bundled-only runs. */
510
+ centerArtifactDigest?: string;
511
+ /** Publish provenance companion of {@link centerArtifactDigest}. */
512
+ centerSourceRevision?: string;
513
+ /**
514
+ * design/147 S2a — the SPAWNING run's sessionId (trusted internals chain, same seat as
515
+ * {@link parentTaskId}). Sibling resolution/delivery needs the parent's SESSION axis too: a
516
+ * session-scoped sibling registers with owner = the parent's sessionId, which the parent's taskId
517
+ * alone cannot satisfy when the two differ.
518
+ */
519
+ parentSessionId?: string;
520
+ /** A-3 — the ROOT host session of the delegation tree (see RunInternals.rootSessionId:
521
+ * fixed point `ctx.rootSessionId ?? ctx.sessionId` at every spawn). Absent for a top-level run
522
+ * (its OWN sessionId is the root). */
523
+ rootSessionId?: string;
524
+ /** design/380 O1② (C12) — the spawning run's EXPLICIT placement fixed point
525
+ * (`RunInternals.placementRoot`), present only when its internals carried one (cascade rungs /
526
+ * verification legs after the first, or a caller-declared root). A delegation tool threads it
527
+ * VERBATIM into child RunInternals so every descendant keeps the ladder/gate placement; absent ⇒
528
+ * the `rootSessionId` chain above IS the placement root through prepare's mint middle segment
529
+ * (`placementRoot ?? rootSessionId ?? sessionId`) and nothing extra is threaded. */
530
+ placementRoot?: string;
531
+ /**
532
+ * design/151 §7 S3c — TRUSTED tier-3 revival claim (same trusted-internals seat as
533
+ * {@link parentTaskId}; never a model argument). Filled ONLY by the auto-mounted SendMessage's
534
+ * revival leg after it WON the durable row's claim-CAS: the Agent tool's execute then re-launches
535
+ * the claimed row — register-with-id on its ORIGINAL handle, `requireExisting` on its ORIGINAL
536
+ * transcript session, identity fields inherited VERBATIM from `row` (never re-derived from the
537
+ * reviving caller's ctx) — instead of minting a fresh agent. `rev` is the row's CURRENT revision
538
+ * (post-claim); the registration seeds its durable write lane from it so the first settle CASes
539
+ * against the claim, not a stale pre-claim rev.
540
+ */
541
+ reviveClaim?: {
542
+ row: import("./background-agent-store.js").BackgroundAgentRecord;
543
+ rev: number;
544
+ /**
545
+ * design/153 §7.2d (件3c) — the PARKED-RESUME drive: present iff this re-launch redeems a
546
+ * parked approval (the caller — server /decide — already won a {@link import("./task-registry.js").ParkedClaimTicket}
547
+ * reservation). The Agent tool's bg lane then: registers the handle PARKED-born (checkpoint
548
+ * arbitration stays live pre-consume), preflights the transcript session (requireExisting),
549
+ * drives `runner.resumeStream(ticket.token, outcome, …)` with the consume-flip hook, and on ANY
550
+ * failure converges through `rollbackParkedClaim` (checkpoint-truth dispositions). `rev` above
551
+ * must equal `ticket.reservedRev` (the lane seeds from the reserved row).
552
+ */
553
+ parkedResume?: {
554
+ ticket: import("./task-registry.js").ParkedClaimTicket;
555
+ outcome: import("./checkpoint-store.js").ResumeOutcome;
556
+ /**
557
+ * the CROSS-PROCESS re-supply seat for the checkpoint's opaque parent constraints.
558
+ * A checkpoint minted under inherited parent-policy constraints records only
559
+ * `requiresParentConstraint` (live ToolPolicy/onAsk closures cannot persist); the same-process
560
+ * drive is re-supplied automatically from the Runner's in-memory `parentConstraintRegistry`,
561
+ * but a FRESH Runner (server restart / rolling update — the production shape where an operator
562
+ * decides hours after the park) has an empty registry, so the resume is rejected pre-CAS
563
+ * (`resume.parent_constraint_missing`) and the row becomes permanently unredeemable. A
564
+ * deployment that can REBUILD the same-semantics constraint chain (its policies are
565
+ * config-driven) passes it here; the drive threads it into `resumeStream(..., internals)`,
566
+ * where the recorded-count shape check still applies verbatim (a partial/mismatched re-supply
567
+ * is rejected — this seat never widens, it only reopens the documented re-supply channel to
568
+ * the parked-decide lane). Absent ⇒ exactly the previous behavior.
569
+ *
570
+ * One entry kind is NOT a policy the deployment authored: a hook-wired parent contributes a
571
+ * PreToolUse SCREENING entry (issue #33), whose policy the engine mints. Rebuild it with the
572
+ * exported `createPreToolUseConstraintPolicy(hook, env)` — same callback, same installed env —
573
+ * or the chain handed back here will be short and rejected on the count check. A face that only
574
+ * observes should carry `Hooks.preToolUseObservational` instead, and then never enters the chain
575
+ * at all; a park under a screening entry announces the requirement on `onError`
576
+ * (classification `screening-constraint-in-durable-chain`) at park time.
577
+ */
578
+ inheritedGate?: import("./runner/contracts.js").InheritedGate;
579
+ };
580
+ /**
581
+ * design/176 — the peer-chain SEED for a message-driven (tier-3) revival: the hop chain of the
582
+ * LAST message in the leased mailbox batch (upstream's "most recent peer-origin message" rule).
583
+ * The spawn chain plants it as the revived cycle's initial inbound chain, so a woken agent's
584
+ * first forward continues the chain that woke it (delivery itself never appends — a revival is
585
+ * not a hop). Absent ⇒ the revived cycle starts with an empty chain.
586
+ */
587
+ peerSeed?: {
588
+ hopChain: string[];
589
+ };
590
+ };
591
+ /**
592
+ * design/147 S1 — the explicit name THIS run was spawned under (`Agent({name})`), when any
593
+ * (trusted internals chain, same seat as {@link parentTaskId}). A named child is sema's teammate:
594
+ * the CC hierarchy clamp ("teammates cannot spawn other teammates") keys on exactly this.
595
+ */
596
+ spawnedAgentName?: string;
597
+ /**
598
+ * design/176 — THIS run's late-bound peer-identity carrier (Runner-filled trusted seat, same
599
+ * chain as {@link parentTaskId}; never a model argument). Delegation tools pair it with the
600
+ * child's uplink binding (`RunInternals.parentPeerRef`) so the child's "main" sends are judged
601
+ * against the identity the callback actually reaches; SendMessage reads it for its own sender
602
+ * key and hop token.
603
+ */
604
+ peerSelfRef?: import("../agents/peer-admission.js").PeerSelfRef;
605
+ /**
606
+ * design/176 — THIS run's inbound peer-chain ref (Runner-filled trusted seat, written at the
607
+ * harness consumption boundary): the hop chain of the most recently CONSUMED peer message, which
608
+ * outbound SendMessage calls inherit (upstream `Zbr` semantics on sema's carrier).
609
+ */
610
+ peerInboundChainRef?: import("../agents/peer-admission.js").PeerInboundChainRef;
611
+ /**
612
+ * design/147 S1c — the deployment's durable roster ({@link RunnerDeps.rosterStore}), Runner-filled
613
+ * read-only. The Agent tool records named background spawns here (advisory try/catch — a store
614
+ * fault never fails the spawn); never a model argument.
615
+ */
616
+ roster?: import("../agents/roster-store.js").RosterStore;
617
+ /**
618
+ * fork source-store fix — the HOST Runner's session-fork face, Runner-filled read-only.
619
+ * The fork arm's source session lives in the store of the Runner RUNNING this task; `opts.runner`
620
+ * is the CHILD-EXECUTION runner, which a deployment may deliberately split (server's subRunner) —
621
+ * forking through it queried an empty store and every fork missed ("source session not found").
622
+ * The fork arm prefers this face and falls back to `opts.runner.sessions.fork` (single-runner
623
+ * deployments unchanged).
624
+ *
625
+ * ZERO-ARG by design: the source session id AND principal are bound in the Runner's
626
+ * closure at injection — a mounted third-party tool holding this context can only branch THIS task's
627
+ * own session under THIS task's principal, never copy a foreign session by supplying its id. The
628
+ * returned handle's `release` is scoped to the just-created branch through the same closure, so
629
+ * failure cleanup releases through the store that OWNS the branch (`opts.runner.sessions.release`
630
+ * may be a different store entirely).
631
+ *
632
+ * Store contract: the branch is created in the HOST store, but the fork child still
633
+ * executes on `opts.runner` with `requireExistingSession: true` — if the two runners do not share
634
+ * session backing, the child run fails LOUD ("refusing a silent fresh run") instead of silently
635
+ * running an empty session under the forked id. Split-runner deployments must share session backing
636
+ * (same store instance or same durable backend) for fork to complete.
637
+ */
638
+ hostSessionFork?: () => Promise<{
639
+ sessionId: string;
640
+ release: () => Promise<void>;
641
+ } | null>;
642
+ /**
643
+ * The RUNNING task's effective working root (worktree-aware), Runner-filled — read-only context a
644
+ * delegation tool threads into its child's trusted internals so the child's env can inherit the
645
+ * parent's cwd (CC parity: a Task sub-agent works in the main session's directory). Undefined when
646
+ * the tool runs outside a Runner task.
647
+ */
648
+ parentCwd?: string;
649
+ /**
650
+ * The RUNNING task's file-history LINEAGE (Runner-filled, read-only, never a model/tool argument;
651
+ * present iff a `RunnerDeps.fileHistoryStore` is live for the run): the history scope this run
652
+ * records its first-touch edits into and the TREE those records' keys are minted against — the
653
+ * canonical root spelling plus the env's filesystem identity (`fs`: a remote workspace's handle,
654
+ * `"host"` for the control-plane host's filesystem, else a per-env-instance token).
655
+ *
656
+ * WHICH children are threaded it, exactly: the Agent tool's FRESH spawn legs (sync / steer /
657
+ * background / fork) pass it VERBATIM into the child's trusted internals
658
+ * (`RunInternals.fileHistoryLineage`), which is how a same-process delegation tree keeps ONE
659
+ * history: a child on the SAME tree (both coordinates equal) records into the lineage's scope (the
660
+ * root session's), so the root session's `rewindFilesTo` reaches the child's edits; a child on a
661
+ * different tree (worktree isolation, an explicit `cwd`, a fresh per-task sandbox) starts its own
662
+ * lineage, because root-relative keys re-joined against a different tree would name files that run
663
+ * never edited. Three delegated legs deliberately carry NO lineage and keep their own scope: the
664
+ * Agent tool's REVIVE arm (a revival's lineage would be the REVIVER's tree, which says nothing
665
+ * about the revived row's), and the workflow / team orchestration spawn legs (`run_workflow`,
666
+ * `runTeamDiscussion` — they build their children's internals without this seat). Edits made by
667
+ * those children are therefore NOT reachable from the root session's rewind, and a restore request
668
+ * on one of them is not refused either — it converges that child's own scope.
669
+ */
670
+ fileHistoryLineage?: {
671
+ scope: string;
672
+ root: string;
673
+ fs: string;
674
+ };
675
+ /**
676
+ * design/319 (A ticket) — the RUNNING task's reminder provenance mark, Runner-filled, read-only,
677
+ * NEVER a model/tool argument. The Agent tool's FORK route threads it into the forked child's
678
+ * trusted internals (`RunInternals.reminderMark` — the fork runs under the parent's byte-identical
679
+ * system-prompt declaration, so its engine mints must carry the PARENT's mark); spawn routes
680
+ * deliberately do NOT copy it (a spawned context mints its own — "one declaration, one mark").
681
+ * Undefined when the tool runs outside a Runner task.
682
+ */
683
+ reminderMark?: string;
684
+ /**
685
+ * design/319 (B ticket) — the RUNNING task's reminder-disclosure trigger counters
686
+ * ({@link import("./reminder-disclosure.js").ReminderDisclosureCounts}), Runner-filled on the same
687
+ * trusted seat as {@link reminderMark}. Caller-mounted verbatim outlets that run the
688
+ * detect-and-disclose pipeline (the web tools' exact-mark defuse arm) bump their `<outlet>.<form>`
689
+ * keys here; the Runner folds the non-zero result into
690
+ * `TaskResult.stats.mechanisms.reminderDisclosures`. Mutable by design (a counter seat), but only
691
+ * ever additive — never a decision input. Undefined outside a Runner task.
692
+ */
693
+ reminderDisclosureCounts?: import("./reminder-disclosure.js").ReminderDisclosureCounts;
694
+ /**
695
+ * Subagent transcript persistence — the RESOLVED delegation entry caps for this run
696
+ * ({@link RunnerDeps.delegationEntryCaps} after prepare's loud validation; both members always
697
+ * present). Runner-filled trusted seat, never a model argument — the Agent tool's spawn gate reads
698
+ * it on EVERY local lane (background, background-fork, sync fork, sync spawn), each at its own
699
+ * launch point. Undefined outside a Runner task (the lane then applies the
700
+ * exported defaults itself, so a directly-driven tool is bounded too).
701
+ */
702
+ delegationEntryCaps?: {
703
+ maxConcurrent: number;
704
+ maxCumulativePerSession: number;
705
+ };
706
+ /**
707
+ * Report usage spent in a nested run this tool spawned (e.g. a sub-agent). The Runner accumulates
708
+ * it into the parent task's `TaskResult.stats.nested`, so delegated cost — the multi-agent "~15×"
709
+ * — is visible at the top level. `createSubagentTool` calls this automatically.
710
+ */
711
+ reportUsage?: (usage: NestedUsage) => void;
712
+ /**
713
+ * The model the PARENT task is currently running on, **snapshotted at the moment this tool is called**
714
+ * (design/38 1B). A delegation tool reads it to let a sub-agent `inherit` the caller's model. If the
715
+ * parent later degrades, an already-started child is unaffected (it kept the snapshot). Undefined when
716
+ * the tool runs outside a Runner task.
717
+ */
718
+ model?: Model;
719
+ /**
720
+ * The PARENT task's current thinking level, snapshotted at call time — the {@link model} companion.
721
+ * A delegation tool threads it into the child so a sub-agent inherits the caller's thinking depth by
722
+ * default (an explicit agent-definition/script `thinking` wins). Undefined outside a Runner task.
723
+ */
724
+ thinkingLevel?: ThinkingLevel;
725
+ /**
726
+ * The running task's authenticated end-user {@link TaskSpec.principal} (design/62), **read-only** — the
727
+ * Runner fills it; a tool cannot change it. A delegation tool (`createSubagentTool`) propagates it
728
+ * verbatim to its child task so the end-user identity is inherited down the delegation tree (the worker
729
+ * cannot substitute a different principal — it is not a tool argument). Undefined when the task has no
730
+ * principal or the tool runs outside a Runner task.
731
+ */
732
+ principal?: string;
733
+ /**
734
+ * ruled 2026-08-04 — the RUNNING task's EFFECTIVE synchronous approver (`spec.onAsk ?? deps.onAsk`,
735
+ * the exact value this task's own gate resolves an `ask` at), Runner-filled read-only on the same
736
+ * trusted seat as {@link principal}: never a model/tool argument, so a worker can neither substitute
737
+ * nor suppress it. Absent when the run has no approver at all (headless) — the seat is never caller-declared.
738
+ *
739
+ * A delegation tool (`createSubagentTool`) forwards it into every child it spawns (sync / background /
740
+ * fork), wrapped so the approver learns which delegation raised the ask ({@link
741
+ * import("./tool-policy.js").AskDelegationProvenance}); the workflow lane forwards the SAME frozen
742
+ * value into every workflow-spawned agent (backlog #342: `RunWorkflowToolDeps.parentOnAsk`, folded
743
+ * onto the governed baseline's base slot with the same provenance wrapper — the auto-mounted tool's
744
+ * execute ctx is minimal, so the value rides a mount dep there; a deployment-pinned `base.onAsk`
745
+ * wins). Without this seat, a deployment that wires its
746
+ * approver per TASK (one closure per attached connection — the common shape) had it reach the host run
747
+ * only: the child's own asks resolved at `deps.onAsk`, i.e. the headless auto-deny, while a live
748
+ * operator sat attached to the parent. The ancestor-constraint chain
749
+ * ({@link inheritedGateForChildren}) already routed asks raised by an ANCESTOR's policy to that
750
+ * ancestor's frozen approver; this seat covers the asks the chain never sees — the child's own
751
+ * egress/irreversibility safety tightens, its session-rule asks, its hooks.
752
+ *
753
+ * Not a widening of what may run: an ask still has to be answered, and every ancestor constraint is
754
+ * folded first. It replaces "silently denied without anyone being asked" with "asked".
755
+ */
756
+ onAsk?: import("./tool-policy.js").OnAsk;
757
+ /**
758
+ * The RUNNING task's EFFECTIVE content-ask seam (`spec.onQuestion ?? deps.onQuestion` — the exact
759
+ * value this task's own `AskUserQuestion` resolves at), Runner-filled read-only on the same trusted
760
+ * seat as {@link onAsk}: never a model/tool argument, so a worker can neither substitute nor suppress
761
+ * it. Absent when the run has no live question face at all — the seat is never caller-declared.
762
+ *
763
+ * A delegation tool (`createSubagentTool`) forwards it into every child it spawns (sync / background /
764
+ * fork), because the tool's own MOUNT predicate is keyed on it: without the seat, a deployment that
765
+ * wires its question face per TASK (`spec.onQuestion`, one closure per attached connection — the same
766
+ * shape {@link onAsk} covers for permissions) gave the child neither the face NOR the tool. The child
767
+ * did not fall back to the headless "no human available" text; `AskUserQuestion` was simply absent
768
+ * from its roster, so a question it needed to ask silently became a guess.
769
+ *
770
+ * Forwarded VERBATIM, unlike `onAsk`'s provenance-stamped wrapper: a content ask carries its issuing
771
+ * identity in the request itself (`AskQuestionRequest.principal` + `sourceTaskId`, which the child's
772
+ * own prepare fills from the child's inherited principal and its Runner-minted session id), and
773
+ * nothing in the question path does approver-identity collapse (the reason the permission wrapper
774
+ * exists). Wrapping here would add a field no consumer reads and a second function identity for one
775
+ * decision-maker.
776
+ *
777
+ * Not a widening: a question still has to be answered by the human the deployment wired, and the
778
+ * child's questions were never anyone else's to answer.
779
+ */
780
+ onQuestion?: import("./ask-question.js").OnQuestion;
781
+ /**
782
+ * The running task's WRITE-HANDS CLAMP ({@link TaskSpec.handsReadOnly} — the read-only band the
783
+ * Runner mounts instead of the write hands), Runner-filled read-only on the same trusted seat as
784
+ * {@link principal}; present ONLY when the clamp is on, so an ordinary run's ctx gains no key.
785
+ *
786
+ * A delegation tool (`createSubagentTool`) copies it onto every child spec it builds: the clamp is a
787
+ * tighten-only safety axis (`tightenTaskSpec`: `true → false` is a loosening that throws), but the
788
+ * delegation lane never carried it, so a clamped parent could derive a FULL-WRITE child and the
789
+ * read-only boundary ended at one level. Strictly a capability REMOVAL — it can never widen a child.
790
+ */
791
+ handsReadOnly?: true;
792
+ /**
793
+ * design/199 — the READ-face clamp carrier ({@link TaskSpec.readFace}): present (as "roots") ONLY
794
+ * when this run RESOLVED to the roots face, so a delegation tool pins every child spec to roots —
795
+ * without it a roots-narrowed parent's children would resolve the shared deployment seat (possibly
796
+ * "open") and read wider than the parent could. Strictly a tightening carrier: an open parent adds
797
+ * no key, and nothing in the delegation lane can spell "open". F10 ruling (v1): this seat exists
798
+ * ONLY as the delegation carrier — no tool adapts its model-facing behavior on it.
799
+ */
800
+ readFace?: "roots";
801
+ /**
802
+ * design/199 件B — the parent task's OWN deny-set additions ({@link TaskSpec.readDenyPatterns}),
803
+ * traveling the delegation tree add-only (a child judges at least every entry its parent judged).
804
+ * Runner-filled trusted seat, never a model argument.
805
+ */
806
+ readDenyPatterns?: readonly import("../tools/fs/read-deny.js").ReadDenyEntry[];
807
+ /**
808
+ * The HARD-HEADLESS clamp ({@link TaskSpec.interactiveTools} set to `false` — "never mount a
809
+ * human-facing tool on this run, whatever faces exist"), Runner-filled read-only on the same trusted
810
+ * seat as {@link principal}; present ONLY when the clamp is on, so every other run's ctx gains no key.
811
+ *
812
+ * A delegation tool copies it onto every child spec, for the same reason as {@link handsReadOnly}: a
813
+ * clamp that stops at one level is not a clamp. Without it a child of a hard-headless parent both
814
+ * inherits the live {@link onQuestion} face and has no clamp of its own, so prepare's automatic
815
+ * criterion mounts `AskUserQuestion` in the child — re-opening in the subtree exactly the interaction
816
+ * channel the task disabled. Only the `false` value travels (a forced-mount `true` is a widening and
817
+ * is deliberately NOT inherited).
818
+ */
819
+ interactiveTools?: false;
820
+ /**
821
+ * RB-220 — read-only mirror of {@link "./types.js".TaskSpec.oneShot} (same trust tier/seat as
822
+ * `principal`): this RUN has no later turn for an async background notification to land in (a
823
+ * headless `-p` process exits once the turn ends). A delegation tool (`createSubagentTool`) reads
824
+ * it to branch its background-launch RECEIPT toward an active block-wait instruction instead of
825
+ * "end your turn and wait" — mirrors {@link import("../orchestration/run-workflow-tool.js").RunWorkflowToolDeps.oneShot}'s
826
+ * guidance-text branch for `run_workflow`. Undefined when the tool runs outside a Runner task.
827
+ */
828
+ oneShot?: boolean;
829
+ /**
830
+ * design/112 C1/C5 — read-only SNAPSHOT of the parent task's client-supplied USER facts (timeZone/userEmail),
831
+ * inherited verbatim down the delegation tree like `principal`, so a subagent/fork child's
832
+ * `# Environment` block localizes "today" to the SAME user zone (not the container's UTC) and names the same
833
+ * user. Runner-filled (NOT a tool argument). Undefined when the task carries no clientContext.
834
+ */
835
+ clientContext?: TaskSpec["clientContext"];
836
+ /** — the parent's tool-face controls ({@link TaskSpec.excludeTools} /
837
+ * {@link TaskSpec.deferTools}), inherited down the delegation tree like `principal` (Runner-filled,
838
+ * read-only): a scenario-wide roster must not be escapable by delegating to a child that remounts
839
+ * the excluded tool. Children merge these with their own spec values (union — tighten-only). */
840
+ excludeTools?: readonly string[];
841
+ deferTools?: readonly string[];
842
+ /** DD-3 — the INLINE-PIN half of the same tool-face control ({@link TaskSpec.alwaysLoadTools}),
843
+ * inherited on the same seat and by the same rule as {@link deferTools} (Runner-filled, read-only).
844
+ * The two are one decision expressed in two lists: "defer this batch, but keep X inlined". Passing
845
+ * only the deferral half down the delegation tree silently rewrites the scenario for every child —
846
+ * X arrives deferred, the exemption having been dropped in transit — which is the one outcome the
847
+ * parent explicitly ruled out. Children merge this with their own spec value. */
848
+ alwaysLoadTools?: readonly string[];
849
+ /** design/277 — the model-gate restore selector ({@link TaskSpec.restoreGatedTools}), inherited
850
+ * down the delegation tree on the same trusted seat as the three tool-face controls above
851
+ * (Runner-filled, read-only, frozen snapshot). Restoration is a TASK-TREE intent ("this work
852
+ * wants the scaffold back") and each child re-judges the gate under its OWN resolved model —
853
+ * dropping the selector in transit would trim a same-model child's roster in a way nobody
854
+ * chose (the gate is a default, not a policy; there is no tighten-only axis to protect). */
855
+ restoreGatedTools?: readonly string[] | true;
856
+ /** R2 双形轴 — the parent's resolved prompt profile, inherited down the delegation tree like the
857
+ * tool-face controls (Runner-filled): a classic-profile parent's children speak classic too
858
+ * unless the child spec says otherwise (child spec wins — profile is presentation, not policy). */
859
+ promptProfile?: "simple" | "classic";
860
+ /** the parent's declared extra file-tool roots ({@link TaskSpec.additionalDirectories}),
861
+ * inherited down the delegation tree (Runner-filled, copy-at-spawn): a deployment that widened the
862
+ * fs fence (e.g. the scratchpad host lane) widened it for the TASK, and a delegated child doing the
863
+ * same work hit `path_not_in_root` on the very directory the parent could write. Same trust tier as
864
+ * the spec field (deployment declaration, never a model argument). */
865
+ additionalDirectories?: readonly string[];
866
+ /** The read-only half of the same seat ({@link TaskSpec.additionalReadDirectories}), inherited by
867
+ * the same rule (Runner-filled, copy-at-spawn): a child doing the parent's work needs the same
868
+ * read whitelist, and MUST NOT have it silently widened into a write grant in transit. */
869
+ additionalReadDirectories?: readonly string[];
870
+ /** the parent's environment facts ({@link TaskSpec.envFacts}), inherited down the
871
+ * delegation tree (Runner-filled, copy-at-spawn): the sandbox binding (profile/egress/scratchpad)
872
+ * is a DEPLOYMENT property, not a per-task one — a child in the same sandbox needs the same facts
873
+ * (its env block renders the scratchpad section; its root fence admits the scratchpad dir). */
874
+ envFacts?: TaskSpec["envFacts"];
875
+ /**
876
+ * The parent task's DECLARED {@link TaskSpec.memoryPersistenceCapable} — present ONLY when the spec
877
+ * set it (an inferred run's ctx gains no key), Runner-filled on the same trusted seat as
878
+ * {@link principal}: never a model/tool argument. A delegation tool (`createSubagentTool`) forwards
879
+ * it into every child spec it builds so the deployment's persistence statement survives delegation:
880
+ * `false` is a floor a chosen agent definition cannot loosen (the read-only-memory disclosure must
881
+ * hold tree-wide when the deployment says nothing durable is reachable), `true` is a default a
882
+ * definition may narrow back to `false`. Absent ⇒ nothing is forwarded and each child's own roster
883
+ * inference decides, exactly as before the seat existed.
884
+ */
885
+ memoryPersistenceCapable?: boolean;
886
+ /**
887
+ * design/383 §2.5 — TRUE ⇔ the spawning session is under a memory-capture OPT-OUT at the moment
888
+ * a delegation tool reads this seat (declared at its prepare, a standing record, or a mid-run
889
+ * flip — the seat is live, not a prepare-time snapshot). The delegation lanes forward it into
890
+ * every child's trusted internals as the capture FLOOR (`memoryCaptureFloor`): a child of an
891
+ * opted-out session captures nothing, whatever its chosen AgentDefinition says — the opt-out is
892
+ * a floor no selection loosens (the `memoryPersistenceCapable:false` floor's exact law, on the
893
+ * privacy axis). Trusted Runner-filled seat, never a model/tool argument.
894
+ *
895
+ * DUAL FORM (#511 件2): on a deployment whose capture record store is synchronous (the default
896
+ * file trio) this is a plain boolean, byte-identical to before; a Promise-form
897
+ * {@link RunnerDeps.memoryCaptureRecordStore} makes the live read answer a `Promise<boolean>`.
898
+ * Consumers must `await` (identity on the boolean arm) — a bare `=== true` on the Promise arm
899
+ * would coin `false`, the exact un-floored escape this seat closes.
900
+ */
901
+ memoryCaptureOptedOut?: boolean | Promise<boolean>;
902
+ /**
903
+ * design/383 §2.5 (rescan post-6.0.0-RC) — the floor seat's THIRD state: TRUE ⇔ the spawning
904
+ * session's capture opt-out state is INDETERMINATE at the moment a delegation tool reads this
905
+ * seat (its capture record store is faulting, and no genuine record is known in-process). The
906
+ * delegation lanes forward it into the child's trusted internals as the floor's indeterminate
907
+ * twin (`memoryCaptureFloorIndeterminate`): the child's prepare re-resolves it against the live
908
+ * record query — an ancestor record found takes the ordinary floor arm; a still-faulting store
909
+ * runs the child under the same fail-closed suppression an own-store outage imposes (no
910
+ * irreversible record is ever minted off an unreadable state); a readable-and-clean answer
911
+ * proceeds clean. Never TRUE beside {@link memoryCaptureOptedOut} — a known opt-out is
912
+ * determinate. Trusted Runner-filled seat, never a model/tool argument.
913
+ * Dual form like its twin (#511 件2): `boolean` over a sync store, `Promise<boolean>` over a
914
+ * Promise-form store — consumers `await`.
915
+ */
916
+ memoryCaptureIndeterminate?: boolean | Promise<boolean>;
917
+ /** design/383 §2.5 — the spawning session's write-plane control dir (the coordinate its capture
918
+ * record is keyed under), forwarded beside the floor bit so a cross-plane child's record-query
919
+ * leg reads the PARENT's carrier, not its own plane's. Trusted Runner-filled seat. */
920
+ memoryCaptureControlDir?: string;
921
+ /** design/383 §2.5 (codex round 3) — the WHOLE ancestor chain's capture-record coordinates
922
+ * ({sessionId, controlDir?} per generation, root first), appended one row per spawn. The
923
+ * record-query leg walks ALL of it: a root that flips after spawning an already-running child
924
+ * AND grandchild is invisible to the grandchild's one-hop parent query (the clean middle
925
+ * session has no record of its own to find), while the chain reaches the root directly.
926
+ * Trusted Runner-filled seat, never a model argument. */
927
+ memoryCaptureAncestors?: ReadonlyArray<{
928
+ sessionId: string;
929
+ controlDir?: string;
930
+ }>;
931
+ /**
932
+ * the parent task's per-model auth hook ({@link TaskSpec.getApiKeyAndHeaders}), inherited
933
+ * verbatim down the delegation tree like `principal`/`clientContext` (Runner-filled, read-only, NEVER
934
+ * a model/tool argument). Without this seat a per-task agent pinned to a cross-provider model (its own
935
+ * apiKeyEnv) spawned a child whose run had NO auth hook — the brain silently fell back to the global
936
+ * key and the child 401'd (or worse, ran on the wrong account). Undefined when the parent spec carries
937
+ * no hook or the tool runs outside a Runner task.
938
+ */
939
+ getApiKeyAndHeaders?: TaskSpec["getApiKeyAndHeaders"];
940
+ /**
941
+ * 🔴 design/77 §3 / §7 — read-only SNAPSHOT of the parent task's ACTIVE skill-manifest frames (the
942
+ * Gate-3 deny-narrowing scope, see {@link SkillManifest}), filled by the Runner. A delegation tool
943
+ * (`createSubagentTool`) reads it AT SPAWN TIME and propagates it into the child task so the child
944
+ * inherits the parent skill's tool/path restrictions (a child of a manifested skill is at most as
945
+ * capable as the manifest). Returns an opaque, frozen array of frames; an empty array = no manifest is
946
+ * active (the common case). The model/worker can neither read nor set it (it is not a tool argument).
947
+ * Undefined when the tool runs outside a Runner task. The returned frame objects are intentionally
948
+ * opaque to callers other than the Runner internals that re-seed them.
949
+ */
950
+ activeSkillScope?: () => readonly unknown[];
951
+ /**
952
+ * Parent effective-policy inheritance (tighten-only) — the Runner-filled chain accessor a delegation tool
953
+ * (`createSubagentTool` / the `run_workflow` spawn legs) calls AT SPAWN TIME and threads verbatim into the
954
+ * child's trusted `RunInternals.inheritedGate`. Returns the upstream inherited chain PLUS this (parent)
955
+ * task's own contribution: its session-rule snapshot (data half), its RESOLVED caller policy
956
+ * (`spec.toolPolicy ?? deps.toolPolicy` — the exact slot this task's own gate enforces) with the frozen
957
+ * task onAsk (opaque half), and its effective shellGate. The child's prepare folds these as
958
+ * ADDITIONAL constraint layers — inheritance can only narrow a child, never widen it. Runner-filled,
959
+ * read-only, NEVER a model/tool argument (same posture as {@link activeSkillScope}). Undefined when the
960
+ * tool runs outside a Runner task.
961
+ */
962
+ inheritedGateForChildren?: () => import("./runner/contracts.js").InheritedGate;
963
+ /**
964
+ * design/180 half A — the delegation runtime-provenance ARMING face. Runner-filled; a delegation
965
+ * tool calls it at spawn time: a non-undefined return means this (parent) run is armed (it mounts
966
+ * a memory session, or is itself recording for its own parent) and carries the chain's FROZEN
967
+ * content-safety snapshot — the tool then mints the child's recorder ref and threads both into
968
+ * the child's trusted `RunInternals.delegationProvenance`. Undefined return / absent field ⇒ the
969
+ * child spawns without a recorder (its deliveries read `unknown` and every judgment stays on the
970
+ * static floor — whose MARK action follows the judging run's deployment evidence standard,
971
+ * {@link RunnerDeps.memoryDelegationEvidence}; under the `"static-face"` default this is v1
972
+ * behavior byte-identical). Same trust posture as
973
+ * {@link inheritedGateForChildren}: never a model/tool argument, never a TaskSpec field.
974
+ */
975
+ delegationProvenanceForChildren?: () => import("./memory-engine/delegation-provenance.js").DelegationContentSafety | undefined;
976
+ /**
977
+ * design/336 §3.3 — the delegation-settlement account's coordinates (the WRITE plane's memory
978
+ * control dir + this session's id), Runner-filled when the run mounts a memory session under
979
+ * `memoryProvenance: "carry"`. A delegation tool's BACKGROUND lane writes its launch write-ahead
980
+ * (pending row BEFORE registration and invoke) and its terminal settle through these — as pure
981
+ * data, because the terminal observation can run after this task leg returned and must rebuild
982
+ * its write handle from the coordinates alone. Undefined ⇒ no settlement account (memory-less
983
+ * run, or `"off"`): the background lane launches without rows, the pre-336 accepted-cost shape.
984
+ * Same trust posture as {@link delegationProvenanceForChildren}: never a model/tool argument.
985
+ */
986
+ delegationSettlement?: () => {
987
+ controlDir: string;
988
+ sessionId: string;
989
+ } | undefined;
990
+ /**
991
+ * RB-201 FO-3 (form-one audit, CC 220 `Ipd`/`ein` parity) — the auto-mode classifier decider ARMED
992
+ * for THIS task (auto-mode intent ∧ `RunnerDeps.autoMode` present ∧ `RuntimeCaps.autoMode !== false`
993
+ * — see {@link TaskSpec.autoModeRequested}; the same
994
+ * instance `runToolGate`'s per-call ask review already consults, carrying its own live breaker
995
+ * state — not a fresh one built from raw config). Runner-filled, trusted, undefined when auto-mode
996
+ * is not armed or the tool runs outside a Runner task.
997
+ *
998
+ * CC's classifier covers two more scenes beyond a single tool-call ask that sema had NOT ported: a
999
+ * delegation tool reviews the CHILD's prompt+toolset BEFORE spawning it (CC `Ipd` — a main agent
1000
+ * blocked from a dangerous action directly could otherwise write it into a sub-agent's prompt and
1001
+ * have the child execute it, a delegation-based classifier circumvention), and reviews the child's COMPLETED
1002
+ * work when it hands control back (CC `ein` — flags the main agent with a security warning before it
1003
+ * acts on unreviewed sub-agent output). `createSubagentTool`/the workflow spawn legs read this at
1004
+ * BOTH points and thread it into the child (never a model/tool argument), same posture as
1005
+ * {@link inheritedGateForChildren}.
1006
+ */
1007
+ autoModeReview?: {
1008
+ decider: import("./auto-mode.js").AutoModeDecider;
1009
+ };
1010
+ /**
1011
+ * design/153 §7.4 (件4) — the parent task's `durableApproval` opt-in (value copy), Runner-filled so a
1012
+ * delegation tool can FORWARD it into an eligible background child's spec (named non-fork child + durable
1013
+ * row + checkpoint store + `ensureChildSessionDurable` attested — the §7.3 park-eligibility family). With
1014
+ * it forwarded, a child's plain policy `ask` suspends durably and the settle watcher PARKS the row instead
1015
+ * of the pre-153 `unexpected.suspended` failure. Tighten-only: forwarding grants the child a PARK
1016
+ * capability, never a permission widening — the park's resume decision stays with the operator, and the
1017
+ * ancestor mandate chain (frozen at chain assembly) is unchanged by the child's own spec. Runner-filled,
1018
+ * read-only, NEVER a model/tool argument. Undefined when the parent did not opt in (children keep the
1019
+ * pre-153 lifecycle byte-for-byte). Side effect worth noting (件4 复审): forwarding also flips the
1020
+ * child's `durableQuestionFace` true (a wired checkpoint store + this opt-in), so an eligible child
1021
+ * additionally mounts `AskUserQuestion` — a deliberate consequence (a question the child can't resolve
1022
+ * synchronously should park too), not a separate grant.
1023
+ */
1024
+ durableApprovalForChildren?: {
1025
+ scope: string;
1026
+ ttlMs?: number;
1027
+ };
1028
+ /**
1029
+ * Ruled 2026-08-04 — the host run set {@link TaskSpec.checkpointStore} to `"disabled"` (the per-run
1030
+ * off switch for the durable machine). Runner-filled, read-only, NEVER a model/tool argument; absent
1031
+ * on every other run, so a deployment that does not use the off switch sees no change at all.
1032
+ *
1033
+ * A delegation tool MUST copy `checkpointStore: "disabled"` onto every child spec it builds (sync,
1034
+ * background and fork alike). Without that copy the off switch is a ONE-LEVEL guarantee: the child
1035
+ * runs on the same Runner, so it resolves `RunnerDeps.checkpointStore` on its own and re-arms every
1036
+ * suspend leg the parent just disarmed — a machine-started run would then park in its subtree, which
1037
+ * is exactly the permanently-pending checkpoint the null exists to make impossible. Same discipline
1038
+ * as `principal` (inherit verbatim down the delegation tree off the trusted seat), and strictly a
1039
+ * capability REMOVAL, so it can never widen a child.
1040
+ */
1041
+ checkpointStoreDisabledForChildren?: true;
1042
+ /**
1043
+ * design/99 (nested-subagent live observability): the host run's stable taskId (`spec.taskId ?? sessionId`),
1044
+ * filled by the Runner. A delegation tool (`createSubagentTool`) reads it AT SPAWN TIME and threads it into the
1045
+ * child as the child's `parentTaskId`, so a UI can build the live nested-agent tree (child.parentTaskId === this
1046
+ * taskId). Undefined when the tool runs outside a Runner task.
1047
+ */
1048
+ taskId?: string;
1049
+ /** design/129: the host run's SESSION id (Runner-filled, read-only) — a session-scoped background child
1050
+ * registers under it so later turns (fresh taskId) still reach the entry. */
1051
+ sessionId?: string;
1052
+ /** design/129: the host spec's `backgroundScope`, threaded read-only so delegation tools apply the
1053
+ * deployment's background-lifetime policy (never a model argument). */
1054
+ backgroundScope?: "task" | "session";
1055
+ /**
1056
+ * design/173 §8.3 (review fold r2-F1) — the host run's RESOLVED interaction posture
1057
+ * (`spec ?? parent ?? deps`), threaded read-only so delegation/workflow lanes carry it into their
1058
+ * children's TRUSTED internals (`RunInternals.parentInteractionPosture`). Without this carry, a
1059
+ * root that overrode a deps-level `"interactive"` with `"headless"` spawned children that fell
1060
+ * back to the deps default and were refused at their own door. Engine children still never get
1061
+ * the posture COPIED onto their spec (the §8.3 rule) — it rides internals, the engine channel.
1062
+ */
1063
+ interactionPosture?: "interactive" | "headless";
1064
+ /** design/129-B: the process-level background-child observer ({@link RunnerDeps.onBackgroundChildEvent}),
1065
+ * threaded read-only so the Agent tool's background lane reports spawn/tick/terminal for the child's
1066
+ * whole lifetime (a per-leg sink goes stale when the leg settles; this one never does). */
1067
+ onBackgroundChildEvent?: (event: BackgroundChildEvent) => void;
1068
+ /**
1069
+ * design/115 P3 — the run-local task-notification sink (the SAME lane runtask wires for workflow/bash
1070
+ * completions). Injected by prepare-task's spec.tools wrapper so a caller-mounted delegation tool can
1071
+ * fire background-agent completion notifications into the SOURCE task's next turn. TRUSTED, run-scoped.
1072
+ */
1073
+ onTaskNotification?: (n: import("./task-notification.js").TaskNotificationPayload, opts?: {
1074
+ priority?: import("./task-notification.js").SystemInjectionPriority;
1075
+ }) => void;
1076
+ /**
1077
+ * design/99 (nested-subagent live observability): the host run's OPT-IN display sink — a deployment sets it (via
1078
+ * `RunInternals.onForwardEvent`) to receive a SUBAGENT's live `task_progress` ticks that otherwise stay in the
1079
+ * child's ISOLATED stream. A delegation tool threads it to the child so nested progress bubbles to one sink. This
1080
+ * is a DISPLAY channel ONLY — the child stream is NEVER merged into the parent's MODEL context, and nothing
1081
+ * security-relevant consumes a forwarded event. Present ONLY when the deployment opted in. The tool-ctx wrapper
1082
+ * passes `task_progress` unconditionally and — when the deployment sets `forwardSubagentEvents: true` — the
1083
+ * transcript classes too (text_delta/text_end/reasoning_delta/tool_start/tool_end); other event types never cross it.
1084
+ * The delegation lane's OWN tap is trusted and forwards the child's FULL event stream (bg frames tagged
1085
+ * with bgAgentId). ⚠️ Forwarded ticks are UNTRUSTED display hints — any
1086
+ * tool holding this ctx could self-declare one, so a consumer validates `parentTaskId` against its known runs.
1087
+ */
1088
+ forwardEvent?: (event: TaskEvent) => void;
1089
+ /**
1090
+ * Subagent steer verb (2026-07-03): the host run's opt-in SUBAGENT-STEER-HANDLE sink
1091
+ * (threaded from `RunInternals.onSubagentSpawn`). When present, `createSubagentTool` runs each sync
1092
+ * child via `runTaskStream` and emits a steer handle here; the deployment routes a human steer into
1093
+ * the running child. The handle never reaches the model. Absent ⇒ children run non-steerable.
1094
+ */
1095
+ onSubagentSpawn?: (handle: import("../agents/subagent.js").SubagentSteerHandle) => void;
1096
+ /**
1097
+ * design/122 D1 — the parent run's subagent-retain LEDGER, present ONLY when the parent's
1098
+ * {@link TaskSpec.retainSubagentSessions} is enabled. TRUSTED run-scoped state the Runner fills (never a
1099
+ * tool argument): `createSubagentTool` registers each retained child here (pre-minted sessionId + a
1100
+ * FROZEN plain spec snapshot — never the live ctx/spec-builder closures, r1-m5) and the Runner disposes
1101
+ * the whole ledger (abort in-flight resumes + unpin + release every retained session) when the parent
1102
+ * run ends (D4).
1103
+ */
1104
+ subagentRetain?: import("../agents/retain-ledger.js").SubagentRetainLedger;
1105
+ /**
1106
+ * design/135 §0 — the TRUSTED per-call worktree-isolation lane for the Agent tool (CC
1107
+ * `isolation: "worktree"`). The Runner fills it (prepare-task builds it over the run's ExecutionEnv +
1108
+ * task root via `createSubagentWorktreeHelper`) for write-capable runs; a tool cannot self-declare it and the
1109
+ * model only REQUESTS isolation via the Agent tool's `isolation` parameter — the capability itself is
1110
+ * never a model argument. Absent ⇒ the deployment cannot mint worktrees (read-only run / outside a
1111
+ * Runner) and the Agent tool reports isolation as honestly unavailable.
1112
+ */
1113
+ worktreeIsolation?: import("../agents/subagent.js").SubagentWorktreeIsolation;
1114
+ /**
1115
+ * design/135 §0 — TRUE when THIS run is itself a forked child ({@link RunInternals.insideFork}),
1116
+ * threaded read-only so the Agent tool's built-in `subagent_type:"fork"` route can refuse nested forks
1117
+ * (CC: "fork is not available inside a forked worker") even for a deployment-mounted Agent tool that
1118
+ * flowed into the forked child via its tool list. Runner-filled; never a model argument.
1119
+ */
1120
+ insideFork?: boolean;
1121
+ /**
1122
+ * OBS-2 (CC 2.1.206 observer parity) — present iff this run's principal resolved
1123
+ * {@link RuntimeCaps.allowObservers}` === true` (EXPLICIT opt-in, default OFF — see the polarity
1124
+ * note there). The delegation tool honors {@link AgentDefinition.observer} declarations only under
1125
+ * this flag; absent ⇒ declarations resolve to unobserved with NO warn (the gate, not a mistake).
1126
+ * Runner-filled at call time (same closure posture as `forkAccess`); never a model argument.
1127
+ */
1128
+ observersAllowed?: true;
1129
+ /**
1130
+ * design/136 §6 盲点① — the Runner-computed fork-GOVERNANCE verdict for THIS run (from
1131
+ * `forkGovernanceDenial(spec.enableFork, runtimeCaps.allowFork)`), threaded read-only so the Agent
1132
+ * tool's built-in `subagent_type:"fork"` route enforces the SAME per-task/per-principal authorization
1133
+ * as the standalone Fork tool's mount gate (pre-1.256, `allowFork:false` only prevented mounting Fork
1134
+ * while Agent-fork ran ungoverned). Present ONLY when governance DENIES (`denied:"task"` =
1135
+ * `enableFork:false`; `denied:"principal"` = `runtimeCaps.allowFork:false`) — absent means governance
1136
+ * does not object (or the tool runs outside a Runner ⇒ capability checks only). Runner-filled; never a
1137
+ * model argument.
1138
+ */
1139
+ forkAccess?: {
1140
+ denied: "task" | "principal";
1141
+ };
1142
+ /**
1143
+ * design/80 D-B — the general "a tool yields to a HUMAN review gate" primitive. A tool calls this to request
1144
+ * that, at the next SAFE turn boundary, the engine pause with a durable `plan_review` checkpoint
1145
+ * (`status:"needs_review"`) so a human approves/edits/rejects before the run continues. The tool still returns
1146
+ * its `content` normally first (the plan/diff lives there → reaches the reviewer via the transcript). HONORED
1147
+ * only when the deployment can actually pause (a `checkpointStore` is wired); otherwise the request is a silent
1148
+ * no-op (read-only/headless degrade). The first-party `present_plan` tool (CC `ExitPlanMode` parity) is a thin
1149
+ * caller of this; any tool may use it to gate a high-blast step on human review. Idempotent within a batch (the
1150
+ * first request wins; the suspend fires once at the boundary). The Runner fills it; not a tool arg.
1151
+ */
1152
+ requestReview?: (opts?: {
1153
+ reason?: string;
1154
+ }) => void;
1155
+ /**
1156
+ * design/105 — request a CLEAN stop of THIS run after the current turn (NOT an abort: the loop finishes the
1157
+ * turn, then exits gracefully — no orphan-[INTERRUPTED] reconcile). A thin binding to the harness's existing
1158
+ * one-way `requestStopAfterTurn` flag (the same the human-suspend saga uses). Typical use: a tool that
1159
+ * persisted a delayed self-wake intent (e.g. CronCreate with kind:"delay") ends the run cleanly so the
1160
+ * daemon owns the re-wake ("end now, re-wake" — NOT "suspend-continue", see design/105 §3.4). Idempotent
1161
+ * one-way flag; a no-op for tools that don't call it. The Runner fills it; not a tool arg. Absent outside a Runner.
1162
+ */
1163
+ requestStopAfterTurn?: () => void;
1164
+ /**
1165
+ * design/108 — enter mid-run READ-ONLY plan mode: the rest of THIS run rejects write-effect tool calls
1166
+ * (`effect !== "read"`), while read/research tools and `present_plan` still run. A run-local one-way flag
1167
+ * (NOT durable — a later approve-resume re-prepares with it clear, so the approved plan can write). The
1168
+ * first-party `enter_plan_mode` tool (CC `EnterPlanMode` parity) is the only caller; the engine enforces the
1169
+ * read-only restriction in a dedicated plan-mode tool-call check (separate from the safety gate — plan mode
1170
+ * is a FIDELITY mode, not a security boundary; the real safety gates apply independently). Idempotent; a
1171
+ * no-op for tools that don't call it. The Runner fills it; not a tool arg. Absent outside a Runner.
1172
+ */
1173
+ enterPlanMode?: () => void;
1174
+ }