@sema-agent/core 7.6.0 → 7.6.2

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 (78) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/agents/agent-transcript-tool.d.ts +2 -2
  3. package/dist/agents/cascade.d.ts +2 -3
  4. package/dist/agents/repair-loop.d.ts +2 -2
  5. package/dist/agents/retain-ledger.d.ts +2 -3
  6. package/dist/agents/send-message-tool.d.ts +2 -2
  7. package/dist/agents/session-util.d.ts +2 -2
  8. package/dist/agents/subagent.d.ts +3 -4
  9. package/dist/agents/teacher.d.ts +2 -2
  10. package/dist/agents/team.d.ts +2 -2
  11. package/dist/agents/verify.d.ts +5 -6
  12. package/dist/core/agent-definition.d.ts +172 -0
  13. package/dist/core/agent-definition.js +1 -0
  14. package/dist/core/checkpoint-store.d.ts +8 -4
  15. package/dist/core/delegation-frames.d.ts +298 -0
  16. package/dist/core/delegation-frames.js +21 -0
  17. package/dist/core/engine-notice.d.ts +555 -0
  18. package/dist/core/engine-notice.js +55 -0
  19. package/dist/core/gate-fold.d.ts +12 -0
  20. package/dist/core/gate-fold.js +158 -0
  21. package/dist/core/gate-lanes.d.ts +93 -0
  22. package/dist/core/gate-lanes.js +626 -0
  23. package/dist/core/hands-band.d.ts +134 -0
  24. package/dist/core/hands-band.js +1 -0
  25. package/dist/core/hooks.d.ts +20 -101
  26. package/dist/core/hooks.js +53 -854
  27. package/dist/core/mcp-failure.d.ts +43 -5
  28. package/dist/core/mcp-failure.js +31 -14
  29. package/dist/core/mcp-server-spec.d.ts +217 -0
  30. package/dist/core/mcp-server-spec.js +1 -0
  31. package/dist/core/model-seat.d.ts +99 -0
  32. package/dist/core/model-seat.js +1 -0
  33. package/dist/core/reminder-mint.d.ts +10 -0
  34. package/dist/core/reminder-mint.js +3 -0
  35. package/dist/core/runner/contracts.d.ts +382 -6
  36. package/dist/core/runner/gate-exit.d.ts +177 -9
  37. package/dist/core/runner/gate-exit.js +70 -1
  38. package/dist/core/runner/prepare-caps-and-workflow.d.ts +2 -7
  39. package/dist/core/runner/prepare-delegation-surface.d.ts +2 -7
  40. package/dist/core/runner/prepare-run-refs.d.ts +12 -0
  41. package/dist/core/runner/prepare-run-refs.js +5 -0
  42. package/dist/core/runner/prepare-task.d.ts +2 -2
  43. package/dist/core/runner/runtask.d.ts +4 -71
  44. package/dist/core/runner/runtask.js +18 -6
  45. package/dist/core/runner-deps.d.ts +1416 -0
  46. package/dist/core/runner-deps.js +1 -0
  47. package/dist/core/runtime-caps.d.ts +164 -0
  48. package/dist/core/runtime-caps.js +1 -0
  49. package/dist/core/task-event.d.ts +910 -0
  50. package/dist/core/task-event.js +1 -0
  51. package/dist/core/task-limits.d.ts +110 -0
  52. package/dist/core/task-limits.js +1 -0
  53. package/dist/core/task-result.d.ts +809 -0
  54. package/dist/core/task-result.js +1 -0
  55. package/dist/core/task-spec.d.ts +1370 -0
  56. package/dist/core/task-spec.js +1 -0
  57. package/dist/core/task-stream.d.ts +382 -0
  58. package/dist/core/task-stream.js +1 -0
  59. package/dist/core/tool-spec.d.ts +1174 -0
  60. package/dist/core/tool-spec.js +1 -0
  61. package/dist/core/types.d.ts +26 -7691
  62. package/dist/core/types.js +2 -76
  63. package/dist/core/warm-resume.d.ts +2 -2
  64. package/dist/index.d.ts +2 -1
  65. package/dist/index.js +1 -1
  66. package/dist/orchestration/goal.d.ts +2 -2
  67. package/dist/orchestration/run-spec.d.ts +2 -2
  68. package/dist/orchestration/run-workflow-tool.d.ts +3 -3
  69. package/dist/orchestration/workflow.d.ts +4 -4
  70. package/dist/scenarios/scenario-registry.d.ts +3 -3
  71. package/dist/scenarios/teacher-quickstart.d.ts +2 -2
  72. package/dist/server/http.d.ts +2 -2
  73. package/dist/stores/file/fs-atomic.d.ts +88 -12
  74. package/dist/stores/file/fs-atomic.js +184 -55
  75. package/dist/stores/file/index.d.ts +1 -0
  76. package/dist/stores/file/index.js +1 -0
  77. package/package.json +1 -1
  78. package/test/export-surface.snapshot.json +9 -1
@@ -0,0 +1,1416 @@
1
+ /**
2
+ * The HOST wiring seat: `RunnerDeps` — every store, sink, hook and policy provider a deployment hands
3
+ * the engine — plus the three structured shapes only its own callbacks produce (`ProjectMemoryLoad`,
4
+ * `ResumePreflightInfo`, `ResumePreflightVerdict`). Layer 0 vocabulary; `types.ts` re-exports all four,
5
+ * so no consumer's import changes.
6
+ */
7
+ import type { Model } from "../internal/llm.js";
8
+ import type { AgentDefinition } from "./agent-definition.js";
9
+ import type { BackgroundChildEvent, DelegationLifecycleEvent } from "./delegation-frames.js";
10
+ import type { EngineNotice } from "./engine-notice.js";
11
+ import type { HandsBandOptions } from "./hands-band.js";
12
+ import type { OnElicit } from "./mcp-server-spec.js";
13
+ import type { Brain, ModelRef, ModelRoles } from "./model-seat.js";
14
+ import type { RuntimeCaps, WorkflowGovernanceBaseline } from "./runtime-caps.js";
15
+ /**
16
+ * parity-204 — structured `RunnerDeps.loadProjectMemory` return (backward-compatible: a bare
17
+ * `string | null` keeps its exact historical meaning). Adds the CC 2.1.204 `seededFromContext`
18
+ * seam (cc204-bundle @17917159: CC preloads readFileState for CLAUDE.md/nested-memory files it
19
+ * placed in context, and a DEFAULT whole-file Read of an unchanged seeded file answers with the
20
+ * already-in-context reminder instead of re-transmitting the body — token economy, GA in 204).
21
+ */
22
+ export interface ProjectMemoryLoad {
23
+ /** The project-context text to inject — same semantics as returning a bare string (null/blank ⇒ no injection). */
24
+ content: string | null;
25
+ /**
26
+ * Files whose FULL, disk-verbatim text the deployment has placed in the model's context (e.g. the
27
+ * CLAUDE.md that `content` embeds). The Runner pre-seeds the hands' read state (through the same
28
+ * resolveKey containment as a real Read — an out-of-root path is silently skipped), so a default
29
+ * whole-file Read of an UNCHANGED seeded file returns the CC-verbatim reminder
30
+ * (`This file is already in your context (see "Contents of <path>" above) and has not changed on
31
+ * disk…`) instead of the body. `content` here MUST be the file's exact disk text at load time —
32
+ * the unchanged check hashes it; never declare a truncated/annotated variant (CC's `isPartialView`
33
+ * exemption). A file that later changes on disk always reads normally (hash mismatch bypasses the
34
+ * dedup and disarms it). Requires hands (an executionEnv); ignored on a hands-less task.
35
+ */
36
+ seededFiles?: ReadonlyArray<{
37
+ path: string;
38
+ content: string;
39
+ }>;
40
+ /**
41
+ * design-A C-3 (AGENTS.md×ingest, [A1]) — the deployment DECLARES which instruction files this
42
+ * snapshot was built from and their fingerprints (e.g. `AGENTS.md` / `CLAUDE.md`, hash over the
43
+ * raw pre-cap file bytes). Core does NOT interpret the hash algorithm or re-derive it — the pair
44
+ * is an OPAQUE comparison baseline (`path` + verbatim `contentHash` equality only), kept so the
45
+ * mid-session change probe can answer "did the file behind my snapshot move?" without core
46
+ * ever learning a file-name policy (types.ts seam rule: core never hardcodes instruction file
47
+ * names). [A2]: this list now FEEDS the C-4 `instructions_change` lane — when the deployment also
48
+ * wires {@link RunnerDeps.probeInstructionSources}, the run loop probes these paths at eligible
49
+ * turn boundaries and a moved hash is announced via a fixed tail attachment. Optional and inert
50
+ * when absent (today's loaders keep their exact behavior; no sources ⇒ the lane never runs).
51
+ */
52
+ instructionSources?: ReadonlyArray<{
53
+ path: string;
54
+ /** (additive widening): `null` = the source is DECLARED but currently
55
+ * ABSENT from disk — the change probe then watches for its APPEARANCE (renders "absent → hash").
56
+ * Runtime already handled null (collectInstructionsChange's from===null arm); this widening
57
+ * only aligns the declared type with the shipped behavior. */
58
+ contentHash: string | null;
59
+ }>;
60
+ }
61
+ /**
62
+ * design/380 O2 — the facts handed to a deployment's {@link RunnerDeps.resumePreflight}: the row's
63
+ * own recorded identity + placement record, read off the persisted checkpoint (never off the resume
64
+ * caller's bag), so the hook judges the SAME row the CAS is about to consume.
65
+ */
66
+ export interface ResumePreflightInfo {
67
+ token: import("./checkpoint-store.js").CheckpointToken;
68
+ sessionId: string;
69
+ /** design/380 O1③ — the row's recorded placement root, when stamped. */
70
+ placementRootSessionId?: string;
71
+ /** The row's recorded identity ({@link import("./checkpoint-store.js").Checkpoint.principal}), when stamped —
72
+ * the resume entry's identity-continuity rung already refused a contradicting supplied principal
73
+ * before this hook runs, so a present value IS the resumed leg's identity. */
74
+ principal?: string;
75
+ /** provider / deviceId / mountPath — the placement facts. */
76
+ workspaceHandle?: import("./remote-env.js").WorkspaceHandle;
77
+ gateKind: import("./checkpoint-store.js").CheckpointGate["kind"];
78
+ }
79
+ /** design/380 O2 — a {@link RunnerDeps.resumePreflight} answer. */
80
+ export type ResumePreflightVerdict = {
81
+ ok: true;
82
+ }
83
+ /** Transient obstacle (device offline, dependency briefly down): the checkpoint stays `pending`,
84
+ * the SAME token is redeemable later; retryAfterMs threads the wait hint (the #449 G1 carrier). */
85
+ | {
86
+ ok: false;
87
+ disposition?: "retry_later";
88
+ message: string;
89
+ retryAfterMs?: number;
90
+ }
91
+ /** PERMANENT obstacle (binding revoked, placement identity gone): the row is settled TERMINALLY —
92
+ * core CASes it out of `pending` before answering, so a dead binding cannot
93
+ * be redialed forever (prose-terminal + mechanically-retriable = unbounded redeem). The `expire`
94
+ * CAS is a pure STATUS flip — the row records no reason (the store seam has no seat for one);
95
+ * the REASON travels on the typed `resume.preflight_rejected` refusal (the hook's `message`
96
+ * inlined), the `onError` disclosure sink, and the deployment hook's own audit plane — it
97
+ * authored the verdict and owns the durable record of why. The exact
98
+ * terminal CAS form is the single-shot `expire` CAS (reaper parity — exactly one settler wins; a
99
+ * LOST race is re-read and reported as concurrent movement, never claimed as this refusal's
100
+ * settle); the CONTRACT is: terminal verdict ⇒ atomic single-shot settle, never a silent
101
+ * pending-forever. PHYSICAL reclamation of a suspended workspace stays with the DEPLOYMENT that
102
+ * ruled the binding dead — core holds no env pre-restore and a factory cannot reconnect to a
103
+ * revoked target; the session pin follows the store contract's documented reap posture. Trust
104
+ * grant is acceptable: the hook lives on RunnerDeps, the same deployment plane that owns the
105
+ * checkpoint store itself. */
106
+ | {
107
+ ok: false;
108
+ disposition: "terminal";
109
+ message: string;
110
+ };
111
+ /** Runtime dependencies shared across tasks. */
112
+ export interface RunnerDeps {
113
+ brain: Brain;
114
+ /** Catalog used to resolve string ModelRefs to Model objects. */
115
+ models?: Record<string, Model>;
116
+ /**
117
+ * design/338 (mid-turn MCP revocation) — the HOST's revocation ledger, probed synchronously at
118
+ * every MCP dispatch (tool call + the three resource tools) BEFORE the transport. The engine
119
+ * never caches the answer: the ledger's one authority lives on the host (a cached copy would be
120
+ * a split-state second authority). A revoked server's calls settle as the coded refusal
121
+ * `mcp.server_revoked` with known-not-executed wording; in-flight calls a revocation raced are
122
+ * deliberately not chased (the threat shape is "new calls after removal"). Absent seat = the
123
+ * pre-338 semantics. A THROWING probe fails open (revocation is a tightening face — a broken
124
+ * probe must not brick every MCP call) with a once-per-MATERIALIZATION `mcp.revocation_probe_failed`
125
+ * notice (a resume re-materializes and may announce again — the standing condition is re-news at
126
+ * each fresh mount, never per-call). The notice is a deployment wiring fact: `detail: { message }`
127
+ * only, no session attribution, `"operator"` audience by its explicit {@link NOTICE_AUDIENCE}
128
+ * row (#433 made the registry total over the catalog — no engine-minted code is
129
+ * audience-defaulted any more) — a
130
+ * wire projector forwards it operator-tier and needs no per-session de-duplication of its own.
131
+ */
132
+ mcpRevocations?: {
133
+ isRevoked(serverName: string): boolean;
134
+ };
135
+ /**
136
+ * design/147 S1c (ruled 2026-07-18) — the DURABLE name→agent roster behind explicit-name
137
+ * addressing, a storage-tier seam like the checkpoint store: core bundles `MemoryRosterStore`
138
+ * (default when omitted... none — omitted means NO durable roster; the in-memory task registry
139
+ * still resolves live/recent names exactly as before) and `FileRosterStore`; a server deployment
140
+ * plugs pg/tidb behind the same interface. Named background-agent spawns are recorded here
141
+ * (advisory — a throwing store never faults a spawn), and SendMessage consults it AFTER the live
142
+ * registry misses, re-applying the caller's access axes (owner/scope) before disclosing anything.
143
+ */
144
+ rosterStore?: import("../agents/roster-store.js").RosterStore;
145
+ /**
146
+ * design/148 S1 — the center prompt-artifact source (protocol §9.2). `current()` supplies the
147
+ * validated candidate NEW sessions adopt at session_start; `get(digest)` resolves a PINNED
148
+ * center artifact on resume (a miss is fail-loud `prompt.snapshot_unavailable` — core never
149
+ * recomposes a hybrid from same-named newer content). Core bundles `CenterPromptSource`
150
+ * (+ memory/file artifact & source-state stores); a server deployment feeds it verified
151
+ * effective-snapshot envelopes via `applySnapshot`. Omitted = bundled-only (today's shape).
152
+ */
153
+ promptSource?: import("../prompt-assembly/artifact-store.js").PromptArtifactSource;
154
+ /**
155
+ * 模型档位绑定(2026-07-11):sema 档位词(`max`/`pro`/`flash`/`lite`,`ultra` 预留)→ 具体模型
156
+ * (catalog 名或 Model)。Runner 构造时经 `expandTiers` 增广进 catalog,使档位词与 CC alias
157
+ * (`fable`/`mythos`/`opus`/`sonnet`/`haiku`,内置固定映射)在全部模型寻址面直接可用(TaskSpec.model /
158
+ * roles / Agent 工具 model / workflow agent() / `@model` mention)。无绑定档沿 DEFAULT_TIER_ORDER 降档;
159
+ * 与部署 catalog 同名键时 catalog WINS(SHADOW)。center 经配置域下发/覆写此表。
160
+ */
161
+ tiers?: Record<string, ModelRef>;
162
+ /**
163
+ * F4 agentType 注册表(2026-07-11):部署声明的 {@link AgentDefinition} 集,workflow `agent(…,{agentType})`
164
+ * 经此解析(内置 Explore/Plan 之上 SHADOW)。design/141 件A:Agent 工具 lane(`createSubagentTool`)未显式
165
+ * 传 `agents` 时经 `Runner.agentCatalog` 自动取此表——同一 Runner 两 lane 单源;显式传参仍 WINS。
166
+ */
167
+ agents?: AgentDefinition[];
168
+ /** `false` 从 workflow agentType 注册表移除内置 Explore/Plan(镜像 createSubagentTool 同名开关)。 */
169
+ builtinAgents?: boolean;
170
+ /**
171
+ * design/141 件2 — hands 工具带(Read/Write/Edit/Bash…)的部署方窄配置面。刻意不导出
172
+ * `createHandsToolkit` 整厂(部署自装配塞 `spec.tools` 会被 Runner 的自动装配同名覆盖=反模式;
173
+ * CC 亦无 options-bag 开放面,走单例+hook)。只开放安全子集,Runner 装配时合成:
174
+ * - `beforeWrite` 与引擎的 MemoryEngine 写扫描门 **COMPOSE 而非替换**(部署 hook 先判,拒即拒;
175
+ * 放行后引擎门仍全量生效——写保护不可能被部署配置规避)。
176
+ * Runner 私有编排字段(taskRegistry/detachHub/execClamp/cwdRef/…)不在此面,永不开放。
177
+ */
178
+ hands?: HandsBandOptions;
179
+ /**
180
+ * design/199 件B — DEPLOYMENT-layer ADDITIONS to the built-in sensitive-path READ deny set
181
+ * ({@link import("../tools/fs/read-deny.js").READ_FACE_DEFAULT_DENY_ENTRIES}); see
182
+ * {@link TaskSpec.readDenyPatterns} for the judged surfaces. Add-only on THIS seat: unions with
183
+ * the active built-ins and any task-layer additions, and `[]` ≡ absent — narrowing the built-in
184
+ * set is the sibling knobs' job ({@link readDenyBuiltinTiers} / {@link readDenyBuiltinExclude},
185
+ * #245 revision of D-4), not a replacement escape hatch here. Bad entry shapes refuse loudly at
186
+ * prepare (#123).
187
+ */
188
+ readDenyPatterns?: readonly import("../tools/fs/read-deny.js").ReadDenyEntry[];
189
+ /**
190
+ * #245 (revises the D-4 zero-shrink ruling) — the deployment's built-in deny-table TIER selection:
191
+ * EXACTLY the listed tiers of
192
+ * {@link import("../tools/fs/read-deny.js").READ_FACE_BUILTIN_DENY_TABLE} are active (`[]` = none —
193
+ * explicit and legal); absent = the default selection
194
+ * ({@link import("../tools/fs/read-deny.js").READ_DENY_DEFAULT_TIERS}: every tier except
195
+ * `shell-history`, which is unrestricted by default). Composes with
196
+ * {@link readDenyBuiltinExclude} (tiers first, then row removal); never touches the additions
197
+ * seats or the write faces. Unknown tier names refuse loudly at prepare (#123). DEPLOYMENT seat
198
+ * ONLY: deliberately not a TaskSpec key and not in the governed workflow whitelist — a task or
199
+ * governed script can only ADD deny entries, never widen the built-in face below its deployment.
200
+ * Not frozen into checkpoints: the checkpoint face section persists ADDITIONS only, so a resumed
201
+ * row's built-in face follows the CURRENT deployment configuration (the operator's live
202
+ * authority), while frozen additions stay stricter-wins.
203
+ */
204
+ readDenyBuiltinTiers?: readonly string[];
205
+ /**
206
+ * #245 — per-row removal from the built-in deny table by STABLE NAME (= the row's canonical
207
+ * pattern text, e.g. `".ssh"`, `".config/gcloud"`; the admin delete channel), applied after
208
+ * {@link readDenyBuiltinTiers} selection. Naming a row of an inactive tier is a satisfied intent
209
+ * (no-op); a name matching NO table row refuses loudly at prepare (#123 — never silently
210
+ * ignored). Same deployment-only seat, clamp argument and checkpoint posture as the tiers key.
211
+ */
212
+ readDenyBuiltinExclude?: readonly string[];
213
+ /**
214
+ * backlog #286 (#279 — CC 2.1.233 `DANGEROUS_FILES`/`DANGEROUS_DIRECTORIES`/
215
+ * `DANGEROUS_DIRECTORY_PATHS` parity): the WRITE-protection table. DEFAULT-ON: a path-confinable
216
+ * write (Write/Edit/NotebookEdit) whose target lands on a table row has a surviving `allow`
217
+ * demoted to `ask` at the tool gate (`decisionReason: "safety"`; a deny/ask verdict is untouched;
218
+ * approval flows the ordinary ask-resolution chain — classifier, blanket `onAsk`, durable park —
219
+ * with no `requiresRealApproval` mandate). Absent =
220
+ * {@link import("./write-protect.js").WRITE_PROTECTED_DEFAULT_TABLE} (the CC triple verbatim +
221
+ * the two argued sema rows). This key IS the whole-table escape hatch, deployment seat ONLY (no
222
+ * TaskSpec twin, no governed-workflow channel): `[]` = no table (explicit and legal); a non-empty
223
+ * list REPLACES the built-in table whole (compose additions as
224
+ * `[...WRITE_PROTECTED_DEFAULT_TABLE, …]`; drop rows by filtering the exported table — the
225
+ * visible/deletable admin face). Bad values refuse loudly at prepare (#123): garbage shapes,
226
+ * glob metacharacters (the table speaks LITERAL names — glob semantics live in
227
+ * `createSensitivePathPolicy`), unknown kinds, impossible kind/name combinations. Matching is
228
+ * lexical over the spelled target with one case fold (ı/ſ included) — a symlink alias evades it
229
+ * by construction; the canonicalizing opt-in deny policy remains the hard layer. Not frozen into
230
+ * checkpoints: a resumed task follows the CURRENT deployment table (an approved parked call
231
+ * bypasses the gate as always — the human already adjudicated it).
232
+ */
233
+ writeProtectedPaths?: readonly import("./write-protect.js").WriteProtectedEntry[];
234
+ /**
235
+ * design/324 (#324 ruling ① containment) — the EVIDENCE STANDARD the delegation arm of the
236
+ * content-origin wrap applies when deciding whether a delegation call marks THIS session's memory
237
+ * polluted (design/178 §3 / design/180 half A):
238
+ * - `"static-face"` (absent ≡ this; the default) — today's behavior, byte-identical: a delegation
239
+ * whose delivered attestation is missing/unknown and whose static tool face can reach external
240
+ * content marks the session (the capability over-approximation: possibility counts as exposure).
241
+ * - `"attested-only"` — exactly that ONE static-face mark is waived, and each prepared leg
242
+ * announces the first waiver (`"memory.delegation_static_mark_waived"`). Everything else is
243
+ * unchanged: a delivered `"external"` attestation still marks, the chain's `incomplete`
244
+ * recording still happens, a non-delegation polluting-class tool still marks, and a delegation
245
+ * tool that is ITSELF classified polluting still marks pre-call.
246
+ * ACCEPTED COST (the deployment's to own, stated as mechanism, not as absence of risk): under
247
+ * `"attested-only"` a BACKGROUND child's real external contact does not mark this session — its
248
+ * content re-enters through the TaskOutput result, the task-notification injection, or the
249
+ * AgentTranscript step summaries, none of which carries an attestation — and a foreground child
250
+ * that ended abnormally (crash/salvage) is likewise not marked on its face alone (the chain still
251
+ * records `incomplete`). Already-marked sessions are never retroactively cleaned; the key only
252
+ * governs NEW marks.
253
+ * DEPLOYMENT seat ONLY (same posture as {@link readDenyBuiltinTiers}): deliberately no TaskSpec
254
+ * twin and not in the governed workflow whitelist — a task author or governed script gets no
255
+ * channel to loosen the evidence standard below its deployment. Not frozen into checkpoints: a
256
+ * resumed leg follows the CURRENT deployment configuration. Each run reads the deps of the Runner
257
+ * that PREPARES it — a multi-runner assembly should configure every runner with the same value
258
+ * (drift is the deployment's own configuration hazard; recorder/attestation semantics are
259
+ * value-independent, so the chain's evidence quality never varies with this key). Any other value
260
+ * refuses loudly at prepare (`config.memory_delegation_evidence`, #123 — exact spellings only,
261
+ * never truthiness).
262
+ */
263
+ memoryDelegationEvidence?: "static-face" | "attested-only";
264
+ /**
265
+ * design/336 §13-3 — the memory-provenance master switch (F-020 深修):
266
+ * - `"carry"` (absent ≡ this; the default) — an externally exposed session's ORDINARY memory
267
+ * writes are committed WITH an engine-minted `origin` marker that travels with the entry
268
+ * (backend/sync/bundle); instruction-form files (type: feedback / pinned/triggers/applies-when
269
+ * hints) from exposed sessions are withheld and quarantined; the derived index's session prose
270
+ * still rolls back; the content scan gates run unchanged (a marker is not an exemption).
271
+ * - `"off"` — the pre-336 behavior: no origin marker is minted and an exposed session's harvest
272
+ * admits nothing (full containment, quarantined for host review). Standing committed markers
273
+ * still carry forward on edits — "off" stops minting, it never strips facts already recorded.
274
+ * ORTHOGONAL to {@link memoryDelegationEvidence} on purpose: that key decides WHEN a delegation
275
+ * marks the session exposed (the evidence standard); this key decides WHAT the mark does to the
276
+ * session's writes (tag-admit vs containment). All four combinations are legal and pinned.
277
+ * DEPLOYMENT seat ONLY (same posture as {@link memoryDelegationEvidence}): no TaskSpec twin and
278
+ * not in the governed workflow whitelist — a task author or governed script gets no channel to
279
+ * change the provenance posture below its deployment; an org administers it through this seat.
280
+ * Not frozen into checkpoints: a resumed leg follows the CURRENT deployment configuration. Any
281
+ * other value refuses loudly at prepare (`config.memory_provenance`, #123 — exact spellings,
282
+ * never truthiness).
283
+ */
284
+ memoryProvenance?: "off" | "carry";
285
+ /**
286
+ * design/383 §3.1 — the deployment's memory-capture POSTURE (the frame the per-principal
287
+ * {@link RuntimeCaps.allowMemoryOptOut} verdict is read inside). Closed spellings, screened at
288
+ * the config doors (`config.memory_capture_policy` — a garbage value refuses the prepare, never
289
+ * folds to any posture):
290
+ * - absent / `"open"` — an opt-out declaration takes effect at face value; only a resolver's
291
+ * EXPLICIT `false` denies it (`memory.capture_optout_denied`, terminal). Resolver fault
292
+ * (throw / non-boolean / absent-on-fault) ⇒ the opt-out STILL TAKES EFFECT, with a named
293
+ * `onError({phase:"config"})` disclosure — the polarity-reversal arm: on this posture the
294
+ * irreversible failure is capturing a session whose user said no, so the fail-safe faces the
295
+ * other way from the caps family's deny-on-outage degrade. Under the ABSENT posture the first
296
+ * opt-out actually honored additionally announces one operator line (once per process): the
297
+ * switch is in use and no policy was ever configured.
298
+ * - `"governed"` — the per-principal verdict is MANDATORY (the mixed-fleet compliance
299
+ * deployment: some principals are retention-required). A verdict of `true` allows, `false`
300
+ * denies; resolver fault / absent verdict ⇒ the task is REFUSED (fail-closed): this posture
301
+ * is the deployment explicitly declaring the compliance side's irreversibility (a
302
+ * retention-required user acquiring an opt-out during an outage window) the heavier one.
303
+ * - `"capture-required"` — every opt-out declaration is refused (deployment-wide forced
304
+ * retention, one line, resolver-independent).
305
+ * The two fault polarities are BOTH deliberate and BOTH pinned; the cost table lives beside the
306
+ * adjudicator. Authority vs record separation (§3.3): this posture governs whether a NEW
307
+ * declaration (or a resumed run) may proceed — it never retroactively re-enables capture for a
308
+ * session whose one-way opt-out record already stands (record wins for capture; under
309
+ * `"governed"`/`"capture-required"` a standing record whose entitlement has been revoked refuses
310
+ * to CONTINUE instead — zero capture and zero out-of-policy running, availability paid).
311
+ */
312
+ memoryCapturePolicy?: "open" | "governed" | "capture-required";
313
+ /**
314
+ * design/383 §2.1b — an optional host-supplied carrier for the session capture opt-out record
315
+ * (per memory plane, keyed by that plane's control dir). Absent = the control-plane file trio
316
+ * (single-process/single-host deployments: byte-identical behavior). A deployment whose control
317
+ * plane does not survive into resume replicas (the stateless multi-replica server form) supplies
318
+ * a durable store here; see {@link import("./memory-engine/layout.js").SessionCaptureRecordStore}
319
+ * for the per-leg fail-closed contract. Also the §2.1b capability signal: on a REMOTE-execution
320
+ * deployment with no store supplied here, a `capture:"off"` declaration is refused
321
+ * (`config.memory_capture_unsupported`) rather than accepted onto a carrier that the next resume
322
+ * cannot see — a false privacy promise is worse than a loud one.
323
+ */
324
+ memoryCaptureRecordStore?: (plane: {
325
+ controlDir: string;
326
+ }) => import("./memory-engine/layout.js").SessionCaptureRecordStore;
327
+ /**
328
+ * design/339 §6.1 — the v3 memory-consolidation switch, threaded verbatim to the engine seat
329
+ * ({@link import("../core/memory-engine/engine.js").MemoryEngineOptions.consolidation} — same
330
+ * transport as {@link memoryProvenance}). ABSENT = OFF (the shipped default): no gate state is
331
+ * ever written, no recommendation is ever minted, and the consolidation verbs on an engine the
332
+ * host constructs over the same store refuse coded. PRESENT = enabled: the engine counts
333
+ * distinct terminal-harvest sessions per scope and surfaces the advisory
334
+ * `memory.consolidation_recommended` notice when the configured thresholds cross — EXECUTION
335
+ * stays host-owned (the four verbs are engine host API; nothing in a task can trigger a run).
336
+ * `provenance: "off"` beside this refuses loudly at prepare
337
+ * (`config.memory_consolidation_provenance_off` — the fold law must be able to mint);
338
+ * `multiNode: true` without a lease refuses (`config.memory_consolidation_lease_required`);
339
+ * every other bad value refuses under `config.memory_consolidation` (#123). DEPLOYMENT seat
340
+ * ONLY (no TaskSpec twin, not in the governed workflow whitelist), same law as
341
+ * {@link memoryProvenance}.
342
+ */
343
+ memoryConsolidation?: import("../core/memory-engine/consolidation.js").MemoryConsolidationOptions;
344
+ /**
345
+ * design/376 片② — the memory-consolidation DRIVER seat (the LLM half), ORTHOGONAL to
346
+ * {@link memoryConsolidation} on purpose: that key is the write PROTOCOL's parameters (fuse,
347
+ * caps, cadence — engine-side), this key is who thinks (model seat + mint knobs — driver-side).
348
+ * The ENGINE itself keeps zero model seats (design/339 §1.1: engine adjudicates, driver
349
+ * proposes; an engine-held model would put distillation authority on the wrong side of the
350
+ * trust boundary), so the seat lives here and is consumed by the host driver verb
351
+ * (`runMemoryConsolidationDriver` + `resolveMemoryConsolidationDriver(deps)`).
352
+ *
353
+ * `chat` — the explicit model seat: an endpoint triple (screened loudly: explicit model id, no
354
+ * alias spellings, key present) or any JSON-only completion function. ABSENT ⇒ the driver
355
+ * resolves the `consolidate` role (`consolidate → summarize → coded refusal`; deliberately
356
+ * never `default` — see the {@link ModelRole} doc). Every other knob mirrors the driver verb's
357
+ * options and is screened there (#123: bad values refuse coded, never fold to defaults).
358
+ * DEPLOYMENT seat ONLY, same law as {@link memoryConsolidation}: no TaskSpec twin — a task
359
+ * author gets no channel to point the library's consolidation at a model of its choosing.
360
+ * Absent alongside absent {@link memoryConsolidation} ⇒ nothing runs and nothing changes
361
+ * (the v3 default posture).
362
+ *
363
+ * ⚠️ EGRESS — read this before configuring the seat (design/383 §1.5). Turning consolidation on
364
+ * SENDS MEMORY CONTENT TO A MODEL PROVIDER. Specifically:
365
+ * - WHAT: that cycle's ELIGIBLE CANDIDATES — not the whole library (entries already superseded,
366
+ * of an ineligible type, or held out by a governance gate are not in the set) — each rendered
367
+ * as its `name`, `description` and `body`. The bodies are the user's own notes, verbatim.
368
+ * - WHERE: the provider this seat resolves to. When `chat` is absent that is whatever the
369
+ * `consolidate → summarize` role chain lands on, which MAY BE THE SAME PROVIDER AS THE MAIN
370
+ * MODEL and may equally be a different vendor — the resolution is the deployment's, and the
371
+ * driver announces the model id and endpoint it actually used at the moment of the first call.
372
+ * - MARKED CONTENT: entries carrying an external-origin (or repo-ingest `trust`) marker are
373
+ * withheld from the CLEAN products' prompts and minted through a separate marked-only call.
374
+ * "Withheld from the clean arm" is NOT "not sent" — such rows still go to the provider in
375
+ * their own call, except a lone marked row, which is sent nowhere. The run receipt reports the
376
+ * two counts separately (`withheldFromCleanArm` / `withheldNeverSent`) and so should any UI.
377
+ * - IRREVERSIBILITY: bytes that have already crossed to a provider cannot be recalled by any
378
+ * later configuration change. Disabling the seat stops FUTURE egress and nothing else.
379
+ */
380
+ memoryConsolidationDriver?: {
381
+ chat?: {
382
+ baseUrl: string;
383
+ apiKey: string;
384
+ model: string;
385
+ } | import("../core/memory-engine/distiller.js").DistillerChatFn;
386
+ /** The audit model id — REQUIRED when `chat` is a bare function (a function carries no id and
387
+ * the run/archive must not record a guess); derived from the endpoint triple or the resolved
388
+ * role otherwise. */
389
+ model?: string;
390
+ contract?: import("../core/memory-engine/distiller.js").LlmDistillerContract;
391
+ maxCycles?: number;
392
+ mintRepairBudget?: number;
393
+ };
394
+ /**
395
+ * design/199 件A — the DEPLOYMENT's read-face declaration
396
+ * ({@link import("../tools/fs/read-face.js").ReadFace}; see {@link TaskSpec.readFace} for the
397
+ * task layer and the resolution order). An interactive single-user product declares
398
+ * `readFace: "open"` here in one line; the engine default stays "roots" (D-1b). Under org
399
+ * governance this seat may still open (it is the deployment's own declaration); the task layer
400
+ * may not. Beside a read-only (verifier) mount this seat's "open" silently CLAMPS to roots (a
401
+ * deployment default cannot override a load-bearing containment wall) — TaskSpec.readFace's own
402
+ * "open" there is the genuine per-task contradiction, and that one still refuses loudly (#123).
403
+ */
404
+ readFace?: import("../tools/fs/read-face.js").ReadFace;
405
+ /**
406
+ * design/96 §20 (P1a) — DEPLOYMENT-DECLARED training knowledge cutoff per model id (e.g.
407
+ * `{ "qwen-3.5-35b": "2025-01", "deepseek-pro": "2024-07" }`). When the resolved model id is present, the
408
+ * `# Environment` block shows `Knowledge cutoff: <value>` so the agent reasons about what it may not know.
409
+ * Deployment-owned ON PURPOSE: core hardcodes no model→date map (a stale cutoff = a §25 honesty breach, and
410
+ * the model catalog is a deployment/profile concern). Omitted ⇒ no cutoff line (honest absence). Optional.
411
+ */
412
+ modelKnowledgeCutoffs?: Record<string, string>;
413
+ /**
414
+ * Default model roles: declare your models once and let every subsystem (main task, compaction,
415
+ * subagent, team) resolve its role instead of repeating a model name. `TaskSpec.roles` overrides
416
+ * per task/scenario. e.g. `{ default: "strong", summarize: "cheap", subagent: "cheap" }`.
417
+ */
418
+ roles?: ModelRoles;
419
+ /**
420
+ * Session lifecycle/persistence backend. Defaults to `TtlSessionStore` (in-memory, 7-day idle TTL
421
+ * — lost on restart). For **durable** sessions (a "session center" that survives restarts and is
422
+ * shared across stateless replicas), supply a `TtlSessionStore` over a durable `SessionRepo`
423
+ * (e.g. TiDB-backed), or a fully custom `SessionStore`. See `design/10-会话持久化与会话中心.md`.
424
+ */
425
+ sessionStore?: import("./session.js").SessionStore;
426
+ /**
427
+ * Durable-checkpoint backend for suspend/resume (design/45). A task with `durableApproval` set
428
+ * persists its awaiting-human checkpoints here and ends `status:"suspended"`; `runner.resume()` reads
429
+ * them back — cross-replica when this is durable (service's approval/run-store). A task's own
430
+ * `checkpointStore` overrides this. Omitted → no durable suspension (policy `ask` stays synchronous).
431
+ */
432
+ checkpointStore?: import("./checkpoint-store.js").CheckpointStore;
433
+ /**
434
+ * design/380 O2 — deployment-supplied resume preflight, called INSIDE the pre-CAS ladder (after the
435
+ * row-integrity rungs, immediately before the CAS) with a bounded deadline
436
+ * ({@link resumePreflightTimeoutMs}). Refusal / throw / timeout ⇒ typed `resume.preflight_rejected`
437
+ * (CheckpointError closed-set addition); the DEFAULT/absent disposition is `retry_later` (the safe
438
+ * arm — fail-closed hooks that just throw can never accidentally terminalize a row) — the
439
+ * entrance-screen posture of the wake-message hook (design/373 D2), generalized from "screen the
440
+ * wake message" to "screen the resume". A deployment binding runs to targets (a device lane) checks
441
+ * its placement/binding tables here: a transient obstacle answers `retry_later` (+`retryAfterMs`,
442
+ * the #449 G1 wait-hint carrier — the row stays `pending`, the SAME token redeems later); a
443
+ * PERMANENT one answers `terminal` (the row is settled by the single-shot `expire` CAS — never a
444
+ * silent pending-forever; bounded waiting for a target to come back also belongs HERE, inside the
445
+ * deadline, never in the env factory). Absent ⇒ no preflight, byte-identical resume behavior.
446
+ */
447
+ resumePreflight?: (info: ResumePreflightInfo, signal: AbortSignal) => Promise<ResumePreflightVerdict>;
448
+ /**
449
+ * design/380 O2 — the {@link resumePreflight} deadline in ms. Default 10s (its own bound,
450
+ * deliberately NOT the hooks-record default: a resume preflight sits on every redeem attempt of a
451
+ * parked row and must answer promptly or get out of the way). Bad values (non-finite, ≤ 0,
452
+ * non-number) are DISCLOSED loudly through `onError` and fall back to the default (the bad-value
453
+ * loudness default: announce, never silently absorb).
454
+ */
455
+ resumePreflightTimeoutMs?: number;
456
+ /**
457
+ * design/381 — the per-edited-file rewind history backend (CC FileHistory topology; replaces the
458
+ * E19 whole-tree `fileSnapshotStore`). WIRING IS THE OPT-IN (DV-9, the reference's own SDK
459
+ * posture): with a store present, every Edit/Write/NotebookEdit first-touch is backed up before
460
+ * the byte changes, every turn mints a history boundary keyed by its own initial session entry
461
+ * ("file state when this turn began"), and a {@link TaskSpec.resumeAt}+{@link TaskSpec.restoreFiles}
462
+ * (or {@link TaskSpec.rewindFilesTo}) run converges the tracked set back to a boundary. Omitted ⇒
463
+ * rewind is OFF: zero track calls, zero boundary work, zero disclosure noise; a restore request
464
+ * then fails loud (`rewind.store_unconfigured`). The reference impl is
465
+ * {@link InMemoryFileHistoryStore}; a deployment supplies a durable backend behind the same seam
466
+ * (backends MUST reuse the exported env-half helpers — see the contract JSDoc). Works against ANY
467
+ * ExecutionEnv (local or remote): bytes are read/written through the env's FileSystem face and
468
+ * stored host-side, so an env rebuild does not lose history.
469
+ */
470
+ fileHistoryStore?: import("./file-history-store.js").FileHistoryStore;
471
+ /**
472
+ * design/381 DV-14 (终审落锤) — what an Edit/Write/NotebookEdit does when its FIRST-TOUCH history
473
+ * record cannot be persisted (`FileHistoryStore.trackEdit` answered `{ok:false}` = NO durable
474
+ * first-touch state exists). Default **"refuse"**: the edit is refused with a typed tool error
475
+ * naming the history-store failure — proceeding would leave the path indistinguishable from
476
+ * never-touched, and the store's post-recovery retry would then mint already-modified bytes as
477
+ * pristine v1 (the fake-pristine hole this default welds shut).
478
+ *
479
+ * `"proceed-unprotected"` is the availability concession arm: the edit proceeds and the failure is
480
+ * disclosed via `onError` (phase "rewind"). ITS RESIDUAL, stated so the choice is informed: during
481
+ * a store outage NO durable trace of the touch can be written, so "the pristine promise is void"
482
+ * lives only in this process's memory — after a process restart the risk is exactly the default
483
+ * arm's nightmare again (the store recovers, the next edit mints already-modified bytes as v1).
484
+ * A deployment choosing this arm owns that window and is expected to record the disclosed events
485
+ * in its own audit surface. Values outside the two-member set refuse prepare loudly.
486
+ */
487
+ onTrackFailure?: "refuse" | "proceed-unprotected";
488
+ /**
489
+ * design/381 §5.3① / §10 S-1 — the ADDITIVE discriminant for the rewind refusal that this engine
490
+ * cannot decide on its own. When a restore finds no boundary for the requested entry, there are
491
+ * two very different worlds: the boundary NEVER existed (those turns ran with no history store, or
492
+ * it was reaped), or it exists in the RETIRED whole-tree snapshot epoch, which the per-edited-file
493
+ * seat deliberately does not restore. The engine holds no whole-tree state, so absent this probe
494
+ * the refusal honestly names BOTH possibilities rather than picking one — the default, and the
495
+ * only honest wording for a deployment that never had a whole-tree epoch at all.
496
+ *
497
+ * A deployment that DOES hold the retired epoch (its assembly layer can ask the old table whether
498
+ * that entry has a legacy row) supplies the bit here, and the refusal splits into the two precise
499
+ * messages. `true` = a whole-tree-era boundary exists for that entry; `false` = it definitively
500
+ * does not; `undefined` = the deployment cannot tell, which reads exactly like an absent probe.
501
+ *
502
+ * Consulted ONLY on the refusal path (never on a successful restore), so a slow probe costs
503
+ * nothing in the ordinary case. It is a DIAGNOSTIC input, never a control input: a probe that
504
+ * throws, hangs past the caller's own patience, or answers a non-boolean cannot change the outcome
505
+ * — the refusal still happens, the wording falls back to the honest both-possibilities form, and
506
+ * the bad value is disclosed through `onError` (phase "rewind") rather than silently absorbed.
507
+ */
508
+ legacyRewindBoundaryProbe?: (query: {
509
+ sessionId: string;
510
+ entryId: string;
511
+ }) => boolean | undefined | Promise<boolean | undefined>;
512
+ /**
513
+ * design/99 §E6 — DURABLE per-session permission rules (a deny-narrowing folded into the task-time tool
514
+ * policy). Omitted ⇒ the feature is OFF (no rules read, zero behavior change). A deployment supplies a
515
+ * cross-replica backend whose CAS-rev + tighten-only semantics match the core
516
+ * {@link import("./session-policy-store.js").InMemorySessionPolicyStore}. Rules are read at prepare time for a
517
+ * task carrying its own `sessionId` (a delegated subagent inherits none) and composed BEFORE the caller
518
+ * policy, so they can only SUBTRACT capability. core does NOT verify owner/operator identity — the store
519
+ * honors the `operator` flag the (service) caller freezes from a verified principal.
520
+ */
521
+ sessionPolicyStore?: import("./session-policy-store.js").SessionPolicyStore;
522
+ /**
523
+ * design/389 — the ONE permission-rule seam: the unified store, partitioned by source
524
+ * (`org | user | project | session`), built by `createPermissionRuleStoreProvider({ durable?, org?, session? })`.
525
+ *
526
+ * It is the one seam in this file that LOOSENS, and it is shaped so it can only do so within limits the
527
+ * engine holds. The provider hands out a store whose ONE read query answers every partition with each
528
+ * row's source and effective status; the write face is an exported BACKEND CONTRACT for the durable
529
+ * partition (an out-of-repo store twin builds against the same definitions instead of mirroring them),
530
+ * but the ENGINE reaches it only on the consent lanes — redemption of a confirmed human decision, the
531
+ * tighten-delete, and the sync join — a wiring invariant pinned by a registered-caller scan
532
+ * (test/permission-rule-writer-callers); no exported convenience mints a rule around consent. Personal
533
+ * rules are consumed post-fold, in the gate's ask branch, and never resolve an ask carrying
534
+ * `requiresRealApproval` or one a PreToolUse hook raised.
535
+ *
536
+ * The ORG partition (present iff the provider was composed with an `org` config — the governance
537
+ * declaration, design/182 §7.4) is the outermost TIGHTENING authority: every tool call is adjudicated
538
+ * against the org's published deny/ask snapshot BEFORE the ask-resolution chain (org deny > org ask >
539
+ * personal allow rule > bare ask), and while the org partition cannot adjudicate the WHOLE decision
540
+ * boundary fails closed — every terminal allow becomes a real-approval ask and both ask→allow seams are
541
+ * disarmed. The SESSION partition (present iff composed with one) is where `{kind:"session"}` grants
542
+ * land and are read from, spliced in front of the durable rows on every adjudication of that session.
543
+ *
544
+ * Omitted ⇒ no rule lane, no org layer, no session source: no rules are read, no field is added to any
545
+ * ask, and the decision path is byte-identical to a build without it. An unauthenticated task (no
546
+ * `principal`) resolves to zero personal rules rather than to a shared bucket (the org partition still
547
+ * adjudicates), and a store whose durable partition cannot be read yields zero rules plus a disclosure
548
+ * — a loosening face fails toward asking.
549
+ */
550
+ permissionRuleStore?: import("./permission-rule-provider.js").PermissionRuleStoreProvider;
551
+ /**
552
+ * design/182 §4.5 (F-011) — declare that this deployment keeps its permission rules in the
553
+ * IDENTITY-LESS local bucket: a task with no `principal` resolves rules through the provider's
554
+ * `forLocalOwner()` face instead of resolving to zero rules.
555
+ *
556
+ * A DECLARATION, never an inference: the local bucket is "this machine's owner", a fact only the
557
+ * deployment knows. Omitted/`false` ⇒ an unauthenticated task reads no rules. Declared `true` while the
558
+ * wired provider has no local-owner face (or while no provider is wired at all) is a configuration
559
+ * contradiction and is refused loudly at prepare, rather than silently degrading to "the rules this
560
+ * person approved stopped applying".
561
+ *
562
+ * A local-owner bucket cannot cloud-sync (syncing is an authenticated act); it is adopted into a
563
+ * principal bucket by `adoptFilePermissionRuleStore`, after which the local-owner face resolves the
564
+ * adopted principal's bucket forever.
565
+ */
566
+ localOwnerRules?: boolean;
567
+ /**
568
+ * design/182 §9 — DECLARE that this deployment drives cloud sync (`syncPermissionRules`) for the
569
+ * wired rule store. Purely a disclosure input: it is reported as `permissionRules.syncWired` on the
570
+ * wiring manifest and changes no decision. It exists because the sync loop is HOST-driven (core
571
+ * bundles no fetch and owns no timer), so there is nothing for the engine to infer — and "are this
572
+ * machine's standing approvals shared with other devices and a server?" is exactly the trust-domain
573
+ * fact an operator must be able to read off the manifest instead of guessing.
574
+ */
575
+ permissionRuleSyncWired?: boolean;
576
+ /**
577
+ * design/99 §K — resolve the **per-principal runtime ENTITLEMENTS** the engine
578
+ * ENFORCES server-side, keyed by `spec.principal`. A deployment (the SERVICE) implements it over center's
579
+ * `GET /api/config/effective?principal=` `runtimeCaps`. Resolved once at prepare time.
580
+ *
581
+ * 🔐 This is a TRUSTED deployment seam, NOT a caller-facing `TaskSpec` field: entitlements are purely
582
+ * server-governed — an untrusted caller must never be able to self-grant a capability. `undefined` (no
583
+ * resolver, or an unset cap) = NO per-principal restriction (TIGHTEN-ONLY: a cap can only ever DENY or
584
+ * FORCE; absence falls back to the deployment-level default). Symmetric with {@link sessionPolicyStore}.
585
+ *
586
+ * core enforces the six caps it CAN enforce in the engine (polarity per member on {@link RuntimeCaps}):
587
+ * `allowWorkflows` (the third stage of the workflows gate — task opt-in ∧ deployment capability ∧ this),
588
+ * `allowFork` (the Agent-fork route), `allowObservers` (explicit opt-in for observer auto-spawn),
589
+ * `autoMode` (the per-principal deny bit of the user-enabled auto mode), `allowMemoryOptOut` (may this
590
+ * principal declare a capture opt-out), and `forceDurableGate` (forces a run onto the durable-approval
591
+ * path so a policy `ask` suspends to the wire — where a checkpoint store is wired; a store-less leg
592
+ * announces `config.durable_gate_unavailable`). `allowUltracode` is a shell/UX concern (reasoning-tier,
593
+ * not a core primitive); `allowBypassPermissions` is enforced at the service settings-resolution layer
594
+ * (the engine sees only a resolved policy, never a "this allow came from bypass" signal).
595
+ */
596
+ runtimeCapsResolver?: (principal: string | undefined) => RuntimeCaps | undefined | Promise<RuntimeCaps | undefined>;
597
+ /**
598
+ * Administrator lock layer (ruled 2026-08-05) — the deployment's config-time declaration of which
599
+ * lockable keys are locked (closed registry: `mcp` / `toolPolicy` / `compliancePosture` /
600
+ * `retentionPolicy`; see {@link import("./locked-config.js").LOCKED_KEY_REGISTRY}). A task spec
601
+ * carrying a value for a locked key is refused wholesale at the prepare door
602
+ * (`config.locked_key`); an unknown key in this declaration is itself refused
603
+ * (`config.locked_registry` — a security control that doesn't parse must not half-arm).
604
+ *
605
+ * 🔐 TRUSTED deployment seam, never a `TaskSpec` field. Locks are minted only by the deployment's
606
+ * own configuration plane (env / central governance domain) under that plane's existing
607
+ * authentication — "who can unlock" ≡ "who can change the deployment config"; there is no
608
+ * request-time unlock.
609
+ */
610
+ lockedConfig?: import("./locked-config.js").LockedConfig;
611
+ /**
612
+ * Compliance capability veto (ruled 2026-08-05) — resolve the running principal's compliance
613
+ * POSTURE (closed profile + closed additional denies; effective denies =
614
+ * `BUILTIN_COMPLIANCE_DENIES[profile] ∪ additionalDenies`, supply can only tighten). Mirrors
615
+ * {@link runtimeCapsResolver}: a 🔐 TRUSTED deployment seam keyed by `spec.principal`, resolved
616
+ * ONCE per prepare; the verdict is frozen for the run and follows the delegation tree through the
617
+ * inherited principal. Fail-closed: a resolver that throws (or returns an unparseable posture)
618
+ * denies EVERY managed capability for the run and surfaces via `onError` (phase `"config"`); a
619
+ * denied capability that the spec explicitly requests refuses the prepare loudly
620
+ * (`config.compliance_denied`, or `config.compliance_required` when the refusal came from the
621
+ * resolver fault rather than an adjudicated posture). `undefined` (no resolver / no posture for
622
+ * the principal) = no compliance restriction — existing deployments are untouched.
623
+ */
624
+ compliancePostureResolver?: (principal: string | undefined) => import("./compliance.js").CompliancePosture | undefined | Promise<import("./compliance.js").CompliancePosture | undefined>;
625
+ /**
626
+ * Org-memory tenant admission (ruled 2026-08-05) — the fail-closed adjudicator for REQUEST-origin
627
+ * `org:` memory scopes (`TaskSpec.memory` / a subagent's `AgentDefinition.memory`, under the v2
628
+ * scope contract). Runs at prepare, after `normalizeMemorySpec` and before any memory-backend I/O.
629
+ * Verdicts are two-state ({@link import("./memory-admission.js").MemoryAdmissionVerdict}): any
630
+ * request-origin org scope not admitted refuses the WHOLE prepare (`memory.admission_denied`) —
631
+ * silent narrowing would turn mount visibility into an org-membership probe. Absent resolver +
632
+ * request-origin org scope ⇒ `memory.admission_required` (fail-closed, transient — the control
633
+ * plane may be mid-rollout); resolver throw ⇒ same code. Deployment-origin scopes
634
+ * ({@link deploymentMemoryScopes}) pass WITHOUT this resolver; when present it may still narrow
635
+ * them (disclosed via `onError`, phase `"memory"`).
636
+ *
637
+ * 🔐 TRUSTED deployment seam, never a `TaskSpec` field (an untrusted caller must never adjudicate
638
+ * its own tenant admission). The admitted org set is frozen into the run's child chain — a
639
+ * delegated child's request plane can only narrow it, never exceed it.
640
+ */
641
+ memoryScopeAdmission?: import("./memory-admission.js").MemoryScopeAdmission;
642
+ /**
643
+ * The deployment's SELF-CERTIFIED org memory scopes (ruled 2026-08-05): org keys the operator
644
+ * pinned in the deployment's own configuration (env / registry defaultScopes). A scope listed here
645
+ * is `origin: "deployment"` at the admission door — the operator writing the config IS the
646
+ * authorization: no principal required, no {@link memoryScopeAdmission} resolver required (the
647
+ * single-user zero-migration path). Everything not listed here that requests an org plane is
648
+ * `origin: "request"` and must pass fail-closed admission.
649
+ */
650
+ deploymentMemoryScopes?: readonly string[];
651
+ /**
652
+ * Managed retention policy (ruled 2026-08-05, retention.ts). Core consumes it ONLY for the
653
+ * startup/prepare capability validation: when the locked-config plane LOCKS the `retentionPolicy`
654
+ * key AND this policy is configured, every wired store (session/checkpoint/tool-result) must
655
+ * declare `retention: "managed"` — otherwise the deployment refuses to start
656
+ * (`config.retention_capability`; the prepare preflight is the per-leg backstop). The engine
657
+ * itself never deletes data on the task path — retention EXECUTION (scheduling, retries, audit)
658
+ * is the deployment scheduler's job against the `ManagedRetentionCapability` contract.
659
+ */
660
+ retentionPolicy?: import("./retention.js").RetentionPolicy;
661
+ /**
662
+ * design/143 批2b — the auto-mode classifier's DEPLOYMENT assembly face. This is the
663
+ * CAPABILITY arm of the three-arm arming: a run arms auto mode only when the task carries the
664
+ * auto-mode INTENT ({@link TaskSpec.autoModeRequested}, or the same bit inherited on the chain by an
665
+ * engine-spawned child) AND this face is present AND the per-principal DENY bit is not set
666
+ * ({@link RuntimeCaps.autoMode}` !== false` — absent is not a denial).
667
+ *
668
+ * 🔐 Trust gate (the CC 2.1.207 three-source invariant): classifier RULES enter EXCLUSIVELY
669
+ * here — a deployment-constructed object, never a `TaskSpec` field, never a repo-file plane. The
670
+ * prompt is assembled from the SHA-locked CC 2.1.207 assets (`buildAutoModePrompt`) with these
671
+ * rules merged CC-exactly (user rules replace a section; the `"$defaults"` sentinel splices the
672
+ * stock rules back in). The classify leg runs on the `classifier` role's model (fallback
673
+ * `summarize → default`; tier default `flash`) via the brain's simple-completion pipe — same
674
+ * lane as compaction/consolidation, no tools, no session writes.
675
+ */
676
+ autoMode?: {
677
+ /** Rule overrides for the four permissions sections (omitted section = CC defaults verbatim). */
678
+ rules?: import("./auto-mode-prompt.js").AutoModeRules;
679
+ /** Settings-plane deny rules rendered into the `<settings_deny_rules>` slot (circumvention catch). */
680
+ settingsDenyRules?: readonly string[];
681
+ /** Extra `## Session Context` facts (e.g. the user-identity line). */
682
+ sessionContext?: readonly string[];
683
+ /** Transcript-window bounds for the classify prompt (defaults: 40 entries / 2000 chars each). */
684
+ window?: import("./auto-mode-prompt.js").AutoModeWindowOptions;
685
+ /** Classify round-trip cap, ms (default 15_000). */
686
+ timeoutMs?: number;
687
+ /** Consecutive-failure threshold opening the one-way session breaker (default 3). */
688
+ failureThreshold?: number;
689
+ /** Fired ONCE when the session breaker opens (the "退回非 auto" alarm — surface it to the operator). */
690
+ onBreakerOpen?: (info: {
691
+ consecutiveFailures: number;
692
+ lastCause: string;
693
+ }) => void;
694
+ /**
695
+ * The classifier DENIAL LIMIT (CC 2.1.250 `FO`/`AKe`): a run whose classifier keeps blocking falls
696
+ * back to a PERSON instead of being denied without end. Per run: a `block` first increments the
697
+ * consecutive and total counts and then judges `consecutive >= maxConsecutive || total >= maxTotal`
698
+ * — the block that reaches a bound is itself the one that becomes an `ask` (the 3rd consecutive
699
+ * block asks). That ask carries `requiresRealApproval: true` (no automatic lane may clear it — not
700
+ * a sandbox admission, not an inherited resolver, not a blanket `onAsk:"allow"`) plus the additive
701
+ * `denialLimitFallback` member with the counts and its own auto-deny window; an ask handed to a LIVE
702
+ * function approver and left unanswered auto-denies after `autoDenyAfterMs` (default 120s; `0` = no
703
+ * window) — the window is armed at that hand-out only, so a durably PARKED fallback carries `0`
704
+ * (nothing counts down on that lane). A classifier allow, or a
705
+ * person's allow of the fallback ask, zeroes the consecutive count; reaching the total bound
706
+ * zeroes everything. With no approver wired at all (headless), the fallback has nowhere to go and
707
+ * the run STOPS with `TaskResult.errorCode = "classifier.denial_limit"` (a notice of the same
708
+ * code is minted).
709
+ *
710
+ * Every member optional (defaults 3 / 20 / 120_000). A present member with a bad value is refused
711
+ * loudly at prepare — never clamped, never silently read as the default.
712
+ * See {@link import("./auto-mode.js").AutoModeDenialLimitOptions}.
713
+ */
714
+ denialLimit?: import("./auto-mode.js").AutoModeDenialLimitOptions;
715
+ /**
716
+ * #503 — OPT IN to recording this arming's serializable criteria (an
717
+ * {@link import("./auto-mode-arming.js").AutoModeArmingRecipe}) on the constraint-chain entries a
718
+ * durable park freezes, so a CROSS-PROCESS redemption can rebuild an equivalent classifier over its
719
+ * own model leg ({@link import("./auto-mode-rebuild.js").rebuildAutoModeDecider}) instead of
720
+ * answering `unavailable` forever and parking the same approval for a human on every redemption.
721
+ *
722
+ * DEFAULT OFF, and the default is the safety story: a row minted without it is byte-identical to
723
+ * the pre-#503 shape, so a mixed-version fleet (a worker that predates this field resuming a row a
724
+ * newer worker minted) keeps the digest it can compute. Turn it on fleet-wide, not per-worker.
725
+ *
726
+ * What gets written down: the rule overrides, the settings-deny rules, the session-context facts,
727
+ * the window bounds and the two knobs — i.e. deployment-authored configuration, on a durable row.
728
+ * `sessionContext` is the one field that routinely carries per-session facts (a user-identity
729
+ * line); a deployment that treats those as sensitive at rest should keep them out of the arming
730
+ * face rather than out of this flag.
731
+ *
732
+ * Recording is CONDITIONAL on the session breaker still being closed at park time: a session that
733
+ * already fell back to non-auto hands nothing forward (the redemption must never run wider than the
734
+ * ancestor it inherits). A `true` here with a non-serializable face announces once and records
735
+ * nothing (`phase:"config"`).
736
+ */
737
+ persistArming?: boolean;
738
+ /**
739
+ * #503 — the deployment's auto-mode SETTINGS GENERATION (a config revision, a settings hash, a
740
+ * publish id — the string is opaque to the engine). Recorded in the arming recipe and compared at
741
+ * rebuild time against the REDEEMING deployment's own: it is a declaration axis that can only make
742
+ * the rebuild stricter (see {@link import("./auto-mode-arming.js").foldAutoModeArming}). Absent =
743
+ * undeclared, which the fold treats as "cannot assume unchanged".
744
+ */
745
+ settingsEpoch?: string;
746
+ };
747
+ /**
748
+ * design/129-B — PROCESS-level observer for background delegation children (Agent/Fork
749
+ * `run_in_background`): `spawn` (at registry registration) / `tick` (every forwarded `task_progress`)
750
+ * / `terminal` (after the registry settle), for the child's WHOLE lifetime. Motivation: every per-leg
751
+ * sink (`RunInternals.onForwardEvent` etc.) carries the SPAWNING turn's lifecycle — correct for sync
752
+ * children, structurally wrong for a session-scoped background child that outlives the turn (the
753
+ * deployment rightly freezes per-leg publishers at leg settle, which silences the child). This seam is
754
+ * keyed to nothing but the process: wire it once at Runner construction and publish fleet rows /
755
+ * completion pushes from it. Observability only — a throwing observer is swallowed, never faults a run.
756
+ */
757
+ onBackgroundChildEvent?: (event: BackgroundChildEvent) => void;
758
+ /**
759
+ * #281 件B — PROCESS-level observer for EVERY delegated child leg's lifecycle (`spawn`/`terminal`),
760
+ * all delegation lanes at once: synchronous and steer-handle delegations (whose spawn/settle
761
+ * previously reached only the trusted `RunInternals.onSubagentSpawn` third parameter — and whose
762
+ * `settled` is a void promise), background/fork/revive children (whose {@link BackgroundChildEvent}
763
+ * family this seat complements, not replaces — BCE stays the fleet-row lane with registry `a*`
764
+ * handles and ticks; this seat is the leg-identity lane), and workflow-spawned agents. Frames are
765
+ * minted at the child leg's own runner chokepoint and carry the #281 件A identity envelope plus a
766
+ * terminal status summary — see {@link DelegationLifecycleEvent} for the emission points, the
767
+ * per-leg semantics and the recorded honest absences. Wire it once at Runner construction; a
768
+ * deployment needs NO RunInternals access to observe delegation any more. Observation only — a
769
+ * throwing or rejecting observer is contained ({@link deliverDelegationLifecycle}) and never
770
+ * faults the child run; a PRESENT non-function value here is announced once per process and the
771
+ * frames are simply not delivered (#123 — a bad seat must be loud, and this stream's loud exit is
772
+ * the seat defect, not a per-frame console flood).
773
+ */
774
+ onDelegationLifecycle?: (event: DelegationLifecycleEvent) => void;
775
+ /**
776
+ * design/98 (S8) — the HARD sandbox seam for LLM-AUTHORED workflow scripts (`TaskSpec.selfOrchestration`).
777
+ * A deployment supplies an isolated-vm / separate-process runner whose `safeForUntrustedScripts === true`;
778
+ * core does NOT ship one (the Node `vm` dev runner is NOT a security boundary). With this unset (or
779
+ * `safeForUntrustedScripts:false`), `selfOrchestration` is FAIL-CLOSED: the `run_workflow` tool is not
780
+ * mounted and the orchestration prompt is not injected. See {@link WorkflowGovernanceBaseline}.
781
+ */
782
+ workflowScriptRunner?: import("../orchestration/workflow-script-runner.js").WorkflowScriptRunner;
783
+ /**
784
+ * design/98 §2.5 (S8b) — the deployment-trusted governance every workflow-spawned sub-agent inherits
785
+ * (tighten-only). REQUIRED (alongside a hard `workflowScriptRunner`) for `selfOrchestration` to activate:
786
+ * unset ⇒ FAIL-CLOSED (the `run_workflow` tool is not mounted — a script-spawned agent must never run
787
+ * without a governed baseline). See {@link WorkflowGovernanceBaseline}.
788
+ */
789
+ workflowGovernanceBaseline?: WorkflowGovernanceBaseline;
790
+ /** design/98 §D.6 (S8c) — deployment hard CEILINGS for an LLM-authored workflow (script length / agent
791
+ * count / timeouts / per-child cost+token caps / budget / result+log size). Defaults bound a runaway. */
792
+ workflowLimits?: import("../orchestration/run-workflow-tool.js").WorkflowLimits;
793
+ /** design/98 §D.5 (S8c) — re-invoke seam: called once when an LLM-authored workflow completes, with a
794
+ * redacted, bounded summary. Unset ⇒ the run still completes + is queryable; the originator just isn't
795
+ * pushed a notification. */
796
+ workflowCompletionNotifier?: import("../orchestration/run-workflow-tool.js").WorkflowCompletionNotifier;
797
+ /** design/97 S1b / design/98 §D.5 — persist LLM-authored workflow runs here for cross-replica `/workflows`
798
+ * history (opt-in; in-process runs are still subscribable without it). */
799
+ workflowRunStore?: import("./workflow-run-store.js").WorkflowRunStore;
800
+ /** design/151 S1a — the durable background-agent execution ledger (the `workflowRunStore` twin for
801
+ * `a*` rows): registrations write restart-surviving rows, and the auto-mounted TaskOutput/TaskStop
802
+ * fall back to it on a live-registry miss (terminal snapshots / honest cross-instance reads, behind
803
+ * the fail-closed §3.1c predicate). Opt-in; absent ⇒ the in-memory-only pre-151 lifecycle. A
804
+ * deployment that composes its own Agent tool MUST pass the SAME instance as
805
+ * `SubagentToolOptions.background.agentStore` (spawn-side writer half) — the engine cannot
806
+ * verify the pairing (the Agent tool is deployment-composed), and a half-wired deployment is a
807
+ * SILENT degrade: reader-only ⇒ no rows ever exist; writer-only ⇒ the auto-mounted fallbacks
808
+ * here never see them (RB-37). Third consumer: delegated
809
+ * CHILDREN's auto-mounted TaskOutput/TaskStop/AgentTranscript read THIS deps too (prepare-task
810
+ * wires them from the child's runner) — a sub-runner with different deps splits the read face. */
811
+ backgroundAgentStore?: import("./background-agent-store.js").BackgroundAgentStore;
812
+ /** design/151 §7 S3c — the durable SendMessage mailbox (delivery-ladder third rung): a message to a
813
+ * SETTLED named teammate is parked here ONLY inside the tier-3 claim-winner window and delivered
814
+ * when the revival spawns (claim/lease + ack — never a destructive drain; a crashed revival's
815
+ * lease expires and the messages re-serve with their original seq). Tier-3 activates only when
816
+ * BOTH this and `backgroundAgentStore` are wired AND a delegation tool (createSubagentTool) is
817
+ * mounted on the task — anything less keeps SendMessage's honest refusal texts, byte for byte.
818
+ * Same single-instance pairing discipline as `backgroundAgentStore` (RB-37): a deployment that
819
+ * composes its own tools must thread the SAME instance everywhere. */
820
+ mailboxStore?: import("./mailbox-store.js").MailboxStore;
821
+ /**
822
+ * Subagent transcript persistence — the delegation ENTRY caps (CC parity values: 20 concurrent /
823
+ * 200 cumulative per session tree; defaults exported as `DELEGATION_MAX_CONCURRENT_DEFAULT` /
824
+ * `DELEGATION_MAX_PER_SESSION_DEFAULT`). Key = `(scope, rootSessionId)`, full depth (grandchildren
825
+ * share the tree's pool). `maxConcurrent` bounds the tree's RUNNING children of every local lane:
826
+ * running/pending a* handles in this process's registry PLUS the synchronous and forked children in
827
+ * flight in this process (parked does not burn a slot — a suspension is not concurrency; a revival
828
+ * claim counts like a spawn); `maxCumulativePerSession` bounds the RETAINED-WINDOW cumulative count
829
+ * (registry-retained + store-retained rows, plus those same in-flight local children — a reaped row
830
+ * returns its quota, and a synchronous child retains nothing so it leaves the window when it
831
+ * settles; deliberately NOT CC's lifetime-monotonic session counter, which would require a
832
+ * persistent counting surface this economic bound does not justify — registered divergence). One
833
+ * consequence, stated rather than left to be discovered: a tree that only ever spawns synchronously
834
+ * is bounded by the concurrency cap alone. Refusals are coded (`delegation.concurrency_cap` /
835
+ * `delegation.session_cap`) with the current value and this knob's name in the text.
836
+ *
837
+ * BAD VALUES REFUSE LOUDLY at prepare (`config.delegation_entry_caps`, the #123 posture): a
838
+ * non-positive/non-integer/NaN member, or a resolved pair where `maxConcurrent` exceeds
839
+ * `maxCumulativePerSession` (you cannot run more at once than you may ever create) — never a
840
+ * silent fold to the defaults. Per-replica bound (multi-replica deployments are each honestly
841
+ * bounded; row-level CAS owns correctness, this cap owns economics).
842
+ */
843
+ delegationEntryCaps?: {
844
+ maxConcurrent?: number;
845
+ maxCumulativePerSession?: number;
846
+ };
847
+ /** design/176 — deployment tuning for the always-on peer-message admission gate (SendMessage entry
848
+ * judgment: rate/dedup/hop-chain/queue bounds). Per-field range-validated against the upstream
849
+ * table with out-of-range values falling back to that field's default; there is NO off switch —
850
+ * absent means the default table, not "guard off". Threaded into the auto-mounted SendMessage as
851
+ * `SendMessageToolOptions.admission`; read per call (a value change governs the next message). */
852
+ peerAdmission?: Partial<import("../agents/peer-admission.js").PeerAdmissionConfig>;
853
+ /**
854
+ * design/385 §2.1 / §5.1 — the peer-session DIRECTORY: the discovery truth of the cross-session
855
+ * lane (other sessions of this engine for the same user). A HOST implements the contract (cli: the
856
+ * pid-keyed registration files; server: its session table) and writes the rows; the engine only
857
+ * reads them. Seat semantics = the mailbox seat's: NO seat, NO lane — every face stays byte-identical
858
+ * to a pre-385 build. With the seat wired the engine (a) mounts `ListAgents` (alias `ListPeers`),
859
+ * (b) opens SendMessage's `session.<id>` address arm and its last `name [ref]` rung, (c) drains this
860
+ * session's own `session.<sessionId>` box at every turn boundary (the mailbox contract's third
861
+ * consumption chain), and (d) splices the cross-session rule into the auto-mode classifier slot.
862
+ * HARD PRECONDITION (design/385 §1.2⑥): `mailboxStore` must be wired AND declare
863
+ * `crossProcessSafe: true`; otherwise the lane is REFUSED at prepare with a named
864
+ * `config.peer_lane_unmounted` notice (never mounted on luck), and the seat is inert for that run.
865
+ */
866
+ peerDirectory?: import("../agents/peer-directory.js").PeerDirectory;
867
+ /**
868
+ * design/385 §4.4 — this deployment's `crossSessionInbound` setting LAYERS for the drain-point
869
+ * judgment (managed / user / repo, resolved by `resolveCrossSessionInboundSetting` — explicit value
870
+ * wins, unset ⇒ mode parity, an unrecognized value forces `hold` loudly). Read at every drain, so a
871
+ * host that re-reads its settings files hands the current layers through a getter. Absent = unset
872
+ * everywhere (mode parity, the CC default). Inert without {@link peerDirectory}.
873
+ */
874
+ crossSessionInbound?: import("../agents/cross-session-judge.js").CrossSessionInboundSettingLayers | (() => import("../agents/cross-session-judge.js").CrossSessionInboundSettingLayers);
875
+ /**
876
+ * design/385 §4.4 (slice 4) — this deployment's `dialogExpiry` (CC settings key, verbatim vocabulary
877
+ * `"60s" | "5m" | "10m" | "never"`, default `"5m"`): how long a HELD cross-session message whose cause
878
+ * a human review could resolve (mode-mismatch / no-mode-asserted / invalid attestation) waits in this
879
+ * session's held queue before it resolves to its safe no-action default — EXPIRED, dropped WITH a
880
+ * receipt to the sender, never silently. `"never"` disables the deadline. Read at every drain round
881
+ * (getter form for a host that re-reads its settings). A value outside the vocabulary is announced
882
+ * ONCE per leg through `onError` (`classification: "peer-dialog-expiry"`) and the default applies —
883
+ * a garbage setting never silently reads as a policy. Absent = the default. Inert without
884
+ * {@link peerDirectory}.
885
+ */
886
+ crossSessionDialogExpiry?: import("../agents/peer-notices.js").CrossSessionDialogExpiry | (() => import("../agents/peer-notices.js").CrossSessionDialogExpiry | undefined);
887
+ /**
888
+ * design/164 件五 — DEPLOYMENT-level usage governance: allowances that span TASKS, evaluated per
889
+ * principal (or once for the whole deployment when a task declares none). A different axis from
890
+ * `TaskSpec.limits`, which is the allowance ONE task asked for — an operator granting "N tokens per 5
891
+ * hours" cannot express it as a task limit, because nothing stops the next task from asking again.
892
+ *
893
+ * A window carries a TOKEN ceiling (`UsageWindow.maxTokens`), a MONEY ceiling (`UsageWindow.maxCostUsd`,
894
+ * absolute USD — the `TaskLimits.maxCostUsd` quantity one governance level up), or both: each is
895
+ * optional on its own and at least one is required, so a deployment governed by spend alone declares
896
+ * only `maxCostUsd` (a window with neither ceiling is refused, `config.usage_window_invalid`). The two
897
+ * are independent and every declared one binds. A $ ceiling requires a PRICED run: a task whose model
898
+ * has neither a {@link pricing}
899
+ * entry nor a `Model.cost` declaration is refused at the door (`config.usage_window_unpriced`) rather
900
+ * than charged the fabricated 0 an unpriced run would otherwise file into an operator's ceiling.
901
+ *
902
+ * Unset (the default) ⇒ NO governance: no ledger is read or written and no task can be refused for
903
+ * usage. When set, every window is evaluated at two moments:
904
+ * - **entry** (before the first model call of a fresh task): an exhausted window REFUSES the task with
905
+ * `usage.window_exhausted`, since there is no checkpoint seat yet to suspend into;
906
+ * - **each clean turn boundary**: an exhausted window suspends the run durably
907
+ * (`ResourceLimitReason:"usage_window"`, with a `resumeAfterMs` hint on the gate) when the
908
+ * infrastructure supports it, and otherwise stops it loudly with the same code.
909
+ *
910
+ * Resume is CALLER-DRIVEN, as everywhere else in this engine: core mints the checkpoint and the hint,
911
+ * and a host schedules the resume (its own scheduler, delayed by `resumeAfterMs`).
912
+ *
913
+ * Requires {@link usageWindowStore} — windows without a ledger are reported through `onError` and NOT
914
+ * enforced (a ledger-less window cannot be evaluated, and failing every task closed on a config typo
915
+ * would take the deployment down instead of telling the operator).
916
+ */
917
+ usageWindows?: readonly import("./usage-window-store.js").UsageWindow[];
918
+ /** design/164 件五 — the cross-task usage ledger {@link usageWindows} is evaluated against. Core bundles
919
+ * `InMemoryUsageWindowStore` (process-local) and `FileUsageWindowStore` (restart-surviving); a fleet
920
+ * deployment plugs a database behind the same two-method seam. Ignored when `usageWindows` is unset. */
921
+ usageWindowStore?: import("./usage-window-store.js").UsageWindowStore;
922
+ /** design/97 CORE-9 (Part A) — the LOAD-BEARING resume journal for LLM-authored (`run_workflow` tool) workflows
923
+ * (the twin of `workflowRunStore`; service ships a durable Postgres/TiDB one). Wired into the auto-mounted
924
+ * run_workflow tool so a tool-launched workflow can durably RESUME across a replica crash. Opt-in. */
925
+ workflowJournalStore?: import("./workflow-journal-store.js").WorkflowJournalStore;
926
+ /** B5/F2 + design/140 §6 1a — the script-persistence / named-registry seam, threaded into the auto-mounted
927
+ * run_workflow tool: every invocation's resolved script is persisted (iterate via `{scriptPath}`), `{name}`
928
+ * resolves a saved workflow, and a registration's `defaultArgs` merge under the call-time args. Opt-in
929
+ * (absent ⇒ inline `script` + built-in names only). */
930
+ workflowScriptStore?: import("../orchestration/workflow-script-store.js").WorkflowScriptStore;
931
+ /** design/140 §6 1c — `false` removes the BUILT-IN named workflows (`discussion`, …) from the
932
+ * auto-mounted run_workflow tool (the `builtinAgents:false` analog; default ON). A deployment
933
+ * `workflowScriptStore` registration of the same name shadows a built-in regardless. */
934
+ builtinWorkflows?: boolean;
935
+ /**
936
+ * design/97 CORE-9 (Part B) — a TRUSTED deployment sink for the steerable handle of every agent an LLM-authored
937
+ * (`run_workflow` tool) workflow spawns. When set, the tool's `agent()` primitive runs each agent STEERABLE and
938
+ * emits its {@link import("../orchestration/workflow.js").WorkflowAgentHandle} here (the script never receives
939
+ * the handle — no host-context leak into the runner); the deployment registers it by `runId`+`label` to route a
940
+ * human/cross-replica steer to `handle.steer(...)`. Unset ⇒ agents run non-steerable (no overhead). Opt-in.
941
+ *
942
+ * **Interaction with {@link workflowJournalStore}**: a steerable agent runs via
943
+ * `agentStream`, whose results ARE RECORDED in the journal since 1.355 (complete read-face: the journal
944
+ * API / diagnostics coordinate show every agent) but are NEVER REPLAYED — `resumeFromRunId` on a
945
+ * steerable workflow still re-runs agents LIVE (steer input is not deterministic; a cache would replay
946
+ * a result the operator steered). Pre-1.355 the stream lane recorded NOTHING — with both deps set the
947
+ * journal read empty on every workflow (the zero-write case), silently.
948
+ */
949
+ onWorkflowAgentSpawn?: (handle: import("../orchestration/workflow.js").WorkflowAgentHandle) => void;
950
+ /** design/72 §2.2 (B): deployment default for the suspend-loop cap (see `TaskSpec.maxSuspends`).
951
+ * Default 5 (loose — human-approval flows re-suspend rarely). A task's own `maxSuspends` overrides. */
952
+ maxSuspends?: number;
953
+ /** RB-458 (ruled 2026-08-03): deployment default for the outer per-brain-call guardrail — see
954
+ * `TaskSpec.limits.brainCallGuardrailMs` for the semantics (default one hour; `false`/`0` = off;
955
+ * an invalid value is a fail-loud config error). A task's own `limits.brainCallGuardrailMs`
956
+ * overrides it. Set it here to cover EVERY run of this Runner, delegated children included — a
957
+ * sub-agent's spec carries only its own `maxTurns`/`maxWalltimeMs`, so the deployment-level knob is
958
+ * the one that reaches a background child. */
959
+ brainCallGuardrailMs?: import("../brain/timeout.js").BrainCallGuardrailKnob;
960
+ /**
961
+ * design/91: injectable wall-clock source for **human-review timing** (and `Checkpoint.suspendedAt`).
962
+ * Defaults to `Date.now`. The Runner reads EVERY human-time timestamp through this — the synchronous
963
+ * `resolveAsk` wait (`t1 − t0`), the durable suspend's `suspendedAt`, and the resume's derived human
964
+ * latency (`now() − suspendedAt`) — so a test can inject a controllable clock (`let t = 0; now = () => t`)
965
+ * and assert `stats.humanReview` deterministically (design/87 §4.3 flaky-guard: no bare `Date.now()` in a
966
+ * hard-gate test). **Scope = human-time only**: this is NOT a general clock for token/cost/turn accounting
967
+ * (those keep `Date.now`); `humanReview` is a budget-EXCLUDED side observable, never folded into cost. */
968
+ now?: () => number;
969
+ /**
970
+ * design/138 S1 — the NEW-ARCHITECTURE memory seam (MemoryEngine over a {@link import("./memory-engine/types.js").MemoryBackend}).
971
+ * When set AND `spec.memory.enabled`, the task runs the INJECTION-FIRST file-based memory lifecycle
972
+ * (materialize → session file ops → harvest with the full gate set), the CC-verbatim `# Memory`
973
+ * instruction + fenced derived index replace the legacy `<user_memory>` block, and NO remember/recall
974
+ * tools are mounted (memory = the model's ordinary file skills). **S4: unset ⇒
975
+ * the task runs MEMORY-LESS** — the legacy `memoryStore` runtime path was retired (one deprecation
976
+ * warning via `onError` phase `"config"`); this field is the ONLY live memory seam.
977
+ */
978
+ memoryBackend?: import("./memory-engine/types.js").MemoryBackend;
979
+ /**
980
+ * design/177 — the SHARED memory-store supply seam: connected team/deployment memory libraries the
981
+ * model may browse and read through the `memory_list` / `memory_read` tool pair. Presence of this dep
982
+ * IS the deployment's intent, so it is the single mount switch — there is no TaskSpec companion knob.
983
+ * Per-task removal goes through the existing `TaskSpec.excludeTools` valve (a true unmount).
984
+ *
985
+ * ORTHOGONAL to {@link memoryBackend}, deliberately and completely. That one is the LOCAL memory chain
986
+ * (fenced index injection, file projection, harvest, entry transactions with a write path); this one is
987
+ * a document-only READ face reached exclusively through tool calls. Shared content never enters the
988
+ * injected memory block or index, never lands on a model-writable directory, and is never harvested;
989
+ * the two seams are not bridged in either direction. Write governance for a shared store stays entirely
990
+ * with the host — core's model face has no write channel at all and only relays each store's
991
+ * `writable` bit.
992
+ *
993
+ * Unset ⇒ neither tool is mounted (not even a deferred placeholder).
994
+ */
995
+ sharedMemoryStores?: import("./shared-memory/types.js").SharedMemoryStoreProvider;
996
+ /**
997
+ * design/138 §2.7 — the memory-engine directory CONFIG ROOT used when {@link memoryBackend} does not
998
+ * pin its own directory (a {@link import("./memory-engine/file-backend.js").FileMemoryEngineBackend}
999
+ * exposes `directoryRoot` and wins). The per-repo memory dir derives as `<root>/<repoKey>/memory`
1000
+ * (repoKey = CC-style slug of the task root). Default: `AGENT_DATA_DIR` env or `~/.ai-agent`.
1001
+ * Location is PURE CONFIG (§2.7 r2.1): a deployment may equally construct the FileBackend over an
1002
+ * IN-REPO git-tracked directory for a project scope.
1003
+ */
1004
+ memoryEngineDir?: string;
1005
+ /**
1006
+ * design/138 S2-B (O-F7/C-F3) — the Runner-owned harvest-report sink. Both harvest boundaries
1007
+ * (the checkpoint mint inside commitSuspendSaga, `phase: "checkpoint"`, and the task terminal in
1008
+ * runtask, `phase: "terminal"`) deliver their {@link import("./memory-engine/types.js").HarvestReport}
1009
+ * here instead of dropping it — trace/accounting/alerting is the deployment's call. Swallow-guarded:
1010
+ * a throwing consumer never fails the boundary. (The model-facing announcement lane is separate:
1011
+ * the engine enqueues gate events at its harvest tail; the NEXT session's first injection drains
1012
+ * them — 时机①.) Optional; unset ⇒ no behavior change beyond the report no longer being discarded.
1013
+ */
1014
+ onMemoryHarvestReport?: (report: import("./memory-engine/types.js").HarvestReport, info: {
1015
+ sessionId: string;
1016
+ phase: "checkpoint" | "terminal";
1017
+ }) => void;
1018
+ /**
1019
+ * design/113 C4 — proactive project startup. The Runner calls this once during `prepareTask` (every prepare,
1020
+ * incl. durable resume — design/113 §4 H8) and injects the returned text as a READ-ONLY, FENCED, virtual
1021
+ * `"project"` memory layer at the TAIL (so the agent "sees" what project it is in from turn 1, like CC's
1022
+ * CLAUDE.md/git injection — the cure for "say hi → only greets"). The deployment/profile owns the CONTENT
1023
+ * (read CLAUDE.md / `.claude/` + fold in `git log -5`/status + a byte budget); core owns the seam + fence +
1024
+ * cache placement and NEVER hardcodes a filename (same trust posture as {@link executionEnvFactory}/
1025
+ * {@link promptProvider}/{@link modelKnowledgeCutoffs}). A RunnerDeps FUNCTION, not a `TaskSpec` field: it is
1026
+ * unserializable (durable resume) and reading files for an UNTRUSTED caller would be a confused-deputy hole
1027
+ * (design/44 §7 Q4). DECOUPLED from `memoryStore` — injects even with NO long-term memory configured (H5).
1028
+ *
1029
+ * `undefined` (default) ⇒ the whole path is skipped = byte-identical to today (full parity, H3). Returning
1030
+ * `null`/blank ⇒ no injection (also byte-identical). The content is fenced/sanitized like every other memory
1031
+ * layer (repo-controlled CLAUDE.md is UNTRUSTED data, not an instruction — H2). The ctx lets a profile tailor
1032
+ * or suppress: `handsEnabled:false` = a hands-less chat with no real project (return null); `isSubagent` =
1033
+ * a delegated child (a profile may trim CLAUDE.md for a cheap "grep this" subagent — H7).
1034
+ */
1035
+ loadProjectMemory?: (ctx: {
1036
+ /** The task's working directory (= the execution env's cwd; a worktree task = its worktree root). */
1037
+ cwd: string;
1038
+ /** False ⇒ a hands-less chat (no real filesystem/project) — a profile typically returns null. */
1039
+ handsEnabled: boolean;
1040
+ /** True ⇒ a delegated subagent run (a profile may trim/suppress project content to control cost). */
1041
+ isSubagent: boolean;
1042
+ /** The subagent's type name, when `isSubagent` — lets a profile tailor content per agent type. */
1043
+ agentName?: string;
1044
+ /**
1045
+ * design-A C-1 ([A2]) — the acquired session's id (uuid domain; equals the run's `sessionId`).
1046
+ * A deployment keys its SESSION-LEVEL snapshot cache on this (design-A §4.1 freeze unit: read
1047
+ * once per session, reuse across the session's prepares) — core itself caches nothing across
1048
+ * prepares. Always filled by the Runner; optional so hand-built ctx objects stay valid.
1049
+ */
1050
+ sessionId?: string;
1051
+ /**
1052
+ * design-A C-1/C-2 ([A2]) — where this prepare sits in the snapshot lifecycle; the deployment's
1053
+ * cache-invalidation signal (core only derives and forwards it, and ignores the answer):
1054
+ * - `"fresh"` — no prior context: a brand-new session (no `spec.sessionId`, or an empty one).
1055
+ * - `"resume"` — an existing session's context continues (a durable-resume leg, a same-session
1056
+ * follow-up run, or a `resumeAt` rewind): a cached snapshot is still the one
1057
+ * the model has been seeing.
1058
+ * - `"post-compact"`— a compaction landed since the model's last full turn on this branch (branch
1059
+ * tail shows a `compaction` entry with no *user* message after it): the context
1060
+ * was rebuilt — the design-A §4.1 re-read boundary; a deployment should drop
1061
+ * its snapshot cache and read fresh. HONEST APPROXIMATION (recorded, see
1062
+ * docs/REVIEW-BACKLOG.md): a compaction followed by a mid-run user-role
1063
+ * injection (steer) before the next prepare reads as `"resume"` — the freshness
1064
+ * gap is covered by the C-4 `instructions_change` lane, not by this signal.
1065
+ * - `"post-clear"` — RESERVED: core has no session-clear primitive today (a CC `/clear` maps to a
1066
+ * NEW session ⇒ `"fresh"`; rewinding past the root is rejected), so core never
1067
+ * emits this value — it exists so the seam's enum is stable when a clear verb lands.
1068
+ */
1069
+ phase?: "fresh" | "resume" | "post-compact" | "post-clear";
1070
+ }) => Promise<string | null | ProjectMemoryLoad> | string | null | ProjectMemoryLoad;
1071
+ /**
1072
+ * design-A C-4 ([A2]) — the mid-session instruction-file change PROBE (the server half of the
1073
+ * `instructions_change` attachment lane). At eligible turn boundaries (continuation-gated like
1074
+ * `date_change`) the run loop calls this with the snapshot's declared
1075
+ * {@link ProjectMemoryLoad.instructionSources} and compares the answer against the lane's
1076
+ * per-path last-announced hash; a moved hash appends a fixed ≤512-byte tail notice (the model is
1077
+ * told the file changed and can Read the truth — the snapshot itself stays frozen, §4.1).
1078
+ * Contract:
1079
+ * - return `null` ⇒ this boundary is SKIPPED silently (nothing announced, nothing committed; the
1080
+ * next boundary retries) — the fail-soft path for a remote cwd / EACCES / transient IO.
1081
+ * - a per-entry `contentHash: null` ⇒ the file is GONE from disk (announced once as removed).
1082
+ * - paths not in `sources` are ignored; the probe should never throw (core guards anyway and
1083
+ * treats a throw exactly like `null`).
1084
+ * - core never reads files here and never interprets the hash (opaque equality only — the same
1085
+ * posture as `instructionSources`); the lstat/read/hash economics live in the deployment.
1086
+ * Unset ⇒ the lane does not exist: zero calls, zero state, byte-identical output.
1087
+ */
1088
+ probeInstructionSources?: (sources: ReadonlyArray<{
1089
+ path: string;
1090
+ contentHash: string | null;
1091
+ }>) => Promise<ReadonlyArray<{
1092
+ path: string;
1093
+ contentHash: string | null;
1094
+ }> | null>;
1095
+ /** Override system-prompt assembly. Defaults to `defaultPromptProvider`. */
1096
+ promptProvider?: import("../prompts/default.js").PromptProvider;
1097
+ /**
1098
+ * design/99 §E23 — the LIVE-ONLY inbound-elicitation seam (an MCP server asking the END USER for input
1099
+ * mid-tool-call). Wired ONLY for servers that opted in via {@link McpServerSpec.elicitation}; absent ⇒ core
1100
+ * advertises no elicitation capability (fail-closed). The deployment answers synchronously (e.g. prompts the
1101
+ * terminal user); core replies to the server on the open connection.
1102
+ */
1103
+ onElicit?: OnElicit;
1104
+ /**
1105
+ * design/116 CONFIRM-1 (加 seam 保体验): resize an over-limit MCP image so the model still SEES a
1106
+ * degraded image instead of a spill-to-disk note. Absent ⇒ core auto-detects `sharp` (dynamic import;
1107
+ * turnkey deployments with sharp installed get the CC experience with zero config) and otherwise spills.
1108
+ */
1109
+ mcpImageResizer?: import("./mcp.js").McpImageResizer;
1110
+ /** Default tool-call gate for all tasks (a task's own `toolPolicy` overrides this). */
1111
+ toolPolicy?: import("./tool-policy.js").ToolPolicy;
1112
+ /**
1113
+ * design/277 — the deployment seat of the tool-registration MODEL GATE (default ON; the gate
1114
+ * itself only ever touches entries tagged via {@link ToolSpec.modelGate}, so an untagged roster
1115
+ * is byte-identical under any value here):
1116
+ * · `false` — deployment kill switch: the gate never trims anything.
1117
+ * · `{ classes }` — per-class rule rows merged into the built-in table
1118
+ * ({@link import("./tool-model-gate.js").TOOL_MODEL_GATE_CLASSES}) PER AXIS: a present
1119
+ * `floors`/`modelIds` axis REPLACES that axis, an absent axis INHERITS the built-in one — so
1120
+ * a row adding only `modelIds` (the BYOM channel for gating a deployment's own strong model)
1121
+ * keeps the built-in claude floors armed. `floors: []` / `modelIds: []` are the EXPLICIT
1122
+ * per-axis clears; a class whose merged rule has both axes empty gates nothing (legal
1123
+ * per-class off). New class names extend the open vocabulary for deployment-authored tags.
1124
+ * The legality set is CLOSED (#123): any other shape — array/null/true seat, unknown keys at
1125
+ * either level, malformed floors/modelIds rows, families that can never match the canonical id
1126
+ * grammar, duplicate families/ids, `/`-prefixed ids — refuses the prepare loudly
1127
+ * (`config.tool_model_gate_invalid`), never folds to a guess. Process-level counterpart: env
1128
+ * `SEMA_TOOL_MODEL_GATE=off`. Removals are announced per (model, class) through `onNotice`
1129
+ * (`config.tool_model_gate_removed`) — a silent default-face trim is forbidden (#237).
1130
+ */
1131
+ toolModelGate?: false | {
1132
+ classes?: Record<string, import("./tool-model-gate.js").ToolModelGateRule>;
1133
+ };
1134
+ /** Deployment default for {@link TaskSpec.basePolicyForResumeEdit} (#93 / F-012 L3): the resume-edit
1135
+ * re-adjudication override. Resolution: `spec.basePolicyForResumeEdit ?? THIS ?? (spec.toolPolicy ??
1136
+ * deps.toolPolicy)` — absence falls back to the caller policy, never to a silent skip. */
1137
+ basePolicyForResumeEdit?: import("./tool-policy.js").ToolPolicy;
1138
+ /**
1139
+ * How `ask` decisions resolve when a policy/hook requests human confirmation (design/37). Default
1140
+ * (omitted) = `"deny"`: **headless auto-deny** — no approver, so `ask` resolves deterministically to
1141
+ * deny with a model-readable reason (the safe default for stateless automation). `"allow"` to
1142
+ * auto-approve, or a fail-closed approver function. A task's own `onAsk` overrides this.
1143
+ */
1144
+ onAsk?: import("./tool-policy.js").OnAsk;
1145
+ /** Content-ask seam (design/64 §5): when set, every task mounts an AskUserQuestion tool that routes a
1146
+ * model-issued question to this callback (a real human/UI). Distinct from `onAsk` (permission). A task's
1147
+ * own `onQuestion` overrides this. Unset ⇒ the tool mounts only if another delivery face exists
1148
+ * (durable park consumer / explicit `TaskSpec.interactiveTools` — see that knob's criterion table). */
1149
+ onQuestion?: import("./ask-question.js").OnQuestion;
1150
+ /**
1151
+ * design/173 §3 — the deployment-level default interaction posture (see
1152
+ * {@link TaskSpec.interactionPosture} for the full contract; the spec seat overrides per run, so
1153
+ * one Runner can serve an interactive session AND cron/batch legs without the deps-level
1154
+ * declaration refusing the batch legs). Absent = no posture check anywhere.
1155
+ */
1156
+ interactionPosture?: "interactive" | "headless";
1157
+ /** LSP code-intelligence seam (design/64 §13.1): when set, every task mounts an `lsp` tool routed to this
1158
+ * manager (a deployment-provided language-server bridge, e.g. an E2B LSP sidecar). The manager returns
1159
+ * `undefined` for a file whose language has no server, and the tool degrades to "fall back to grep". A
1160
+ * task's own `lspManager` overrides this. Unset ⇒ no `lsp` tool. */
1161
+ lspManager?: import("./lsp.js").LspServerManager;
1162
+ /**
1163
+ * Default in-process hooks for all tasks (design/37) — the FULL lifecycle seam of the `Hooks`
1164
+ * interface, not just the tool-call trio: `preToolUse` (rewrite/restrict args + inject context),
1165
+ * `postToolUse` (rewrite output + inject context), `userPromptSubmit` (design/373 §4.3: screens
1166
+ * EVERY user-lane entrance — the objective, a live `TaskStream.steer`, a wake resume's message,
1167
+ * and a parked steer's redelivery, discriminated by `ctx.source`; a hook that blocks
1168
+ * unconditionally refuses steers/wakes too — see the ⚠️ WIDENED INVOCATION SET banner on
1169
+ * {@link import("./hooks.js").Hooks.userPromptSubmit}), plus `stop` (push back when the run would otherwise end and continue
1170
+ * it), `postToolUseFailure` / `postToolBatch` / `permissionDenied` (failure, batch-boundary and
1171
+ * deny observers), `preCompact` / `postCompact` (compaction gate + observer), `stopFailure`
1172
+ * (API-error terminal observer) and the `preToolUseObservational` declaration flag — each member's
1173
+ * contract is documented on the `Hooks` interface itself. A task's own `hooks` overrides this. A
1174
+ * PreToolUse hook's `allow` never bypasses `toolPolicy` — the policy is always the final say.
1175
+ */
1176
+ hooks?: import("./hooks.js").Hooks;
1177
+ /**
1178
+ * Policy for remote image URLs (`TaskSpec.images[].url`). Return false to reject. When set it
1179
+ * **replaces** the built-in SSRF guard (https-only + private/reserved-host block) — e.g. supply a
1180
+ * CDN allowlist, or a stricter resolve-and-pin check. Omit to use the safe default.
1181
+ */
1182
+ allowImageUrl?: (url: string) => boolean;
1183
+ /**
1184
+ * Optional sink for best-effort background errors/warnings that are otherwise swallowed:
1185
+ * - `"compaction"` — a compaction failure (F8); a persistently failing one (flaky gateway, empty
1186
+ * summaries) is invisible without this and lets a session grow until it tops the window.
1187
+ * - `"prompt-cache"` — a prefix-cache hazard on the wire: a detected cache break (design/31), or a
1188
+ * mis-declared/mismatched `promptCacheFamily`. The prefix cache will miss. Log/alert on both.
1189
+ * - `"prompt-constitution"` — a `stableSystem` provider returned an ALREADY-assembled prompt
1190
+ * (constitution anchor found); core passed it through un-doubled. Upgrade the provider to return
1191
+ * only the role base (or set `replaceAll: true` to own the whole base).
1192
+ * - `"degraded"` — the task kept running with a capability quietly reduced: a question auto-answered
1193
+ * with no human present, a skipped env git snapshot, a lost checkpoint-put confirmation, an
1194
+ * unroutable descendant notification, and similar best-effort arms. The run proceeds; the reduced
1195
+ * arm is what is being disclosed (`classification` names it).
1196
+ * - `"config"` — a deployment-wiring problem detected while preparing or tearing down a task: tool
1197
+ * mount conflicts, refused/ignored knob values, gate-off notices, store/env teardown legs. The
1198
+ * broadest class by call-site count — most misconfigurations announce here rather than failing
1199
+ * the task.
1200
+ * - `"memory"` — a post-task memory-consolidation pass failed or skipped a malformed reconcile
1201
+ * decision (design/41). Best-effort: the notes the model saved are kept; the task still succeeds.
1202
+ * - `"mcp"` — an MCP server failed to connect / list its tools and was skipped (fail-open, design/29);
1203
+ * the task runs with the remaining servers' tools. One call per failed server.
1204
+ * - `"a2a"` — the A2A sibling of `"mcp"`: a declared peer's agent card could not be fetched/read, or it
1205
+ * advertises no transport this client speaks, so the peer was skipped. One call per skipped peer.
1206
+ * - `"interrupt-reconcile"` — repairing an interrupted session's transcript on re-entry (synthesizing
1207
+ * tool results for orphaned calls, flushing held session writes) failed; the run proceeds on the
1208
+ * unrepaired transcript.
1209
+ * - `"suggestions"` — the best-effort follow-up-suggestions pass failed or timed out; the result
1210
+ * simply carries no suggestions.
1211
+ * - `"rewind"` — the file-history machinery backing rewind reported a best-effort failure: a
1212
+ * turn-start boundary capture failed or timed out (that turn cannot be rewound to; the turn
1213
+ * itself proceeds), or a first-touch failure was tolerated under
1214
+ * `onTrackFailure:"proceed-unprotected"`. (design/381: the whole-tree size gate and its
1215
+ * `classification:"too_large"` payload are retired — per-edited-file history has no size axis.)
1216
+ * - `"hook"` — a deployment hook misbehaved: a callback threw, or returned a verdict it was not
1217
+ * allowed to (e.g. a declared-observational hook). The engine applies the hook's own documented
1218
+ * fallback (swallow, or fail-closed deny, per its contract) and reports the fact here.
1219
+ */
1220
+ onError?: (err: unknown, context: {
1221
+ phase: "compaction" | "prompt-cache" | "prompt-constitution" | "degraded" | "config" | "memory" | "mcp" | "a2a" | "interrupt-reconcile" | "suggestions" | "rewind" | "hook";
1222
+ sessionId: string;
1223
+ /**
1224
+ * Machine-readable sub-classification when the phase has one — currently the design/31
1225
+ * cache-break detector's root cause (`"model-switch" | "tool-schema" | "tool-set" |
1226
+ * "system-prefix" | "server-or-ttl"`). Lets the host pick its own log level: `"server-or-ttl"`
1227
+ * is usually benign in agentic tasks (slow tools ⇒ 5min+ request gaps expire provider caches —
1228
+ * service[64] flag②) and warrants info/warn, while the prefix-bug causes warrant attention.
1229
+ */
1230
+ classification?: string;
1231
+ }) => void;
1232
+ /**
1233
+ * Structured sink for operator-facing NOTICES ({@link EngineNotice}) — announcements that are not
1234
+ * errors and do not affect the run, which the engine otherwise prints via `console.warn` (e.g. a
1235
+ * configured timeout/env value discarded in favor of another). When wired, a notice goes HERE
1236
+ * INSTEAD of `console.warn` (structured replaces the console line — a host forwarding notices to
1237
+ * its own surface must not show every fact twice); when absent, the historic `console.warn` line
1238
+ * is printed verbatim, so an unwired build keeps its exact loudness. Swallow-guarded at every
1239
+ * emission point (`onError`/`tracer` posture): neither a throwing sink nor an async sink's
1240
+ * rejected promise ever affects the run. A PRESENT NON-function value (an untyped host wiring
1241
+ * null/junk) is a bad deployment value, not a wired sink: every notice then falls back to the
1242
+ * `console.warn` line and the seat defect itself is announced once per process (#170 — a bad seat
1243
+ * must not silence both channels). Per-process announcement de-duplication is unchanged and
1244
+ * sits BEFORE the sink branch — a deduplicated repeat reaches neither channel.
1245
+ */
1246
+ onNotice?: (notice: EngineNotice) => void;
1247
+ /** Best-effort fire-and-forget trace sink (task/turn/brain/tool); `TaskSpec.tracer` overrides per task. */
1248
+ tracer?: import("./trace.js").TracerHook;
1249
+ /**
1250
+ * design/73 §1 v1 — fire-and-forget sink for {@link import("./task-outcome.js").TaskOutcome} facts
1251
+ * (the feedback ledger's core seam; tracer/onError posture — swallow-guarded, a throwing sink never
1252
+ * affects the run). 🔴 Red lines: core emits ONLY the `mechanical` tier (objective-oracle results —
1253
+ * llm_assisted/self-report never enter v1); every outcome carries `oracleHadRedRun`; ONE emission
1254
+ * point — `runGoal`'s terminal state (goal `doneCheck` = the natural mechanical oracle) plus explicit
1255
+ * `emitTaskOutcome` from harnesses that own a real oracle. Plain `runTask` never auto-emits (no
1256
+ * objective oracle → no fact). core emits facts only; aggregation/policy live in the service ledger.
1257
+ */
1258
+ onTaskOutcome?: (outcome: import("./task-outcome.js").TaskOutcome) => void;
1259
+ /** Per-model pricing (per-1M absolute USD) keyed by model id; falls back to each `Model.cost`. */
1260
+ pricing?: Record<string, import("./pricing.js").ModelPricing>;
1261
+ /**
1262
+ * The execution environment the agent's "hand" acts on (design/44) — file system + shell. Default:
1263
+ * `StubExecutionEnv` (no real I/O, current behavior). Inject `NodeExecutionEnv` for local fs/shell, or
1264
+ * a sandboxed/remote impl for multi-tenant isolation. **Injecting a non-stub env is the opt-in** that
1265
+ * activates the file tool band. ⚠️ A bare `NodeExecutionEnv` is unsandboxed — a multi-tenant deployment
1266
+ * MUST inject a chroot/container/per-principal-scoped impl; do not expose raw `NodeExecutionEnv`.
1267
+ */
1268
+ executionEnv?: import("../internal/harness.js").ExecutionEnv;
1269
+ /**
1270
+ * design/48 remote seam — a **trusted control-plane** factory that mints a FRESH execution env per
1271
+ * task (e.g. one isolated container per task in the remote model). Called once at task start; the env
1272
+ * it returns is owned by that task and released on task end. **Wins over** the shared static
1273
+ * `executionEnv`. Injecting it also activates the hand tool band (design/44), exactly like
1274
+ * `executionEnv`. This is the ONLY per-task env injection point — `TaskSpec` deliberately has no
1275
+ * `executionEnv`, so an untrusted caller can't swap the sandbox (design/44 §7 Q4); the trusted
1276
+ * deployment wires the factory.
1277
+ */
1278
+ executionEnvFactory?: import("./remote-env.js").ExecutionEnvFactory;
1279
+ /**
1280
+ * design/77 §1 Gate-1 (oracleIsolation) — a **DISTINCT** trusted control-plane factory that mints the
1281
+ * ISOLATED grader env an out-of-process oracle grades in (the 4th auto-promote gate). It is a SIBLING of
1282
+ * {@link executionEnvFactory}, never the same env: the worker runs in `executionEnvFactory`'s env, the
1283
+ * grader in `graderEnvFactory`'s — so a worker can't corrupt the env that judges it. It inherits the exact
1284
+ * same trust contract: `TaskSpec` deliberately has NO env field (design/44 §7 Q4), so an untrusted caller
1285
+ * can never inject/swap a grader env and grade itself; only the trusted deployment wires this factory. The
1286
+ * RESERVED SEAM, stated plainly (ruled 2026-08-04, field-liveness review): the engine has NO call
1287
+ * site for this factory today — nothing mints, wraps, or asserts through it yet. The intended wirer
1288
+ * is the hardened-grader path a deployment builds on its remote-env side (the k8s pod-spec-patch
1289
+ * seam is the documented server-side half); when that live runner lands, the env it mints is to be
1290
+ * wrapped in a {@link import("./oracle-isolation.js").GraderEnv} and asserted via
1291
+ * {@link import("./oracle-isolation.js").assertOracleIsolation} before any positive oracle verdict.
1292
+ * **Auto-promote stays mandate-OFF until the four gates are implemented + verified end-to-end** (design/77
1293
+ * §7); this seam only makes auto-accept implementable — it does not turn it on.
1294
+ */
1295
+ graderEnvFactory?: import("./remote-env.js").ExecutionEnvFactory;
1296
+ /**
1297
+ * Containment root for the file tools (design/44 §4 invariant 7): every path must resolve within it,
1298
+ * else the tool rejects (`PathNotInRoot`). Default `process.cwd()`. A defense-in-depth guard rail at the
1299
+ * tool layer — NOT a sandbox (it does not constrain `bash`; that needs OS-level containment) and not a
1300
+ * substitute for an isolated `executionEnv`.
1301
+ */
1302
+ rootPath?: string;
1303
+ /**
1304
+ * Backing store for offloaded large tool results (design/30). Default: a task-scoped
1305
+ * `InMemoryToolResultStore` (no cross-wake durability). Inject a durable store (keyed by ref) so an
1306
+ * offloaded result survives wake/resume.
1307
+ */
1308
+ toolResultStore?: import("./tool-result-store.js").ToolResultStore;
1309
+ /**
1310
+ * Offload a tool result to the store + leave a preview when its text exceeds this many chars. Default
1311
+ * ~20000; set `0` or `Infinity` to disable offloading. Per-tool override via `ToolSpec.offloadThresholdChars`.
1312
+ */
1313
+ toolResultThresholdChars?: number;
1314
+ /**
1315
+ * design/374 — microCompact machine-alignment knobs. Since the slice-3 default flip BOTH knobs
1316
+ * default ON (`machine: "cc"`, `clearOnRejection: true`); the pre-374 behavior is the explicit
1317
+ * opt-out `{ machine: "legacy", clearOnRejection: false }`.
1318
+ *
1319
+ * - `machine`: which stale-tool-result clearing machine the request pipeline runs —
1320
+ * `"cc"` (default: the CC 2.1.223 rejection-leg form — keep 5, ≥20k minimum-savings gate, one
1321
+ * deep clear beyond the keep window, CC marker bytes), `"legacy"` (the historical keep-3 /
1322
+ * clear-to-budget machine, kept as the compatibility opt-out; it also keeps the pre-374
1323
+ * backstop order — guard trim as the ordinary second line), or `"off"` (no proactive frontier
1324
+ * clearing at all — the D-2 off switch; the unified machine then gets its ONE shot at the
1325
+ * blocking point instead, the slice-3 arm A, trace trigger `"blocking"`). The pre-flip P-form
1326
+ * warning on {@link import("./context-edit.js").ContextEditMachine} is resolved: the slice-3
1327
+ * fallback re-ordering shipped with this default (blocking-point re-run → in-turn forced
1328
+ * compaction behind the adopt seam → trim demoted to the disaster-only last resort), so an
1329
+ * under-20k refusal no longer falls straight into a message-dropping trim.
1330
+ * - `clearOnRejection` (MC-R, slice 2): on a provider input-too-long rejection, run ONE cheap
1331
+ * deterministic clear over the rejected projection (same cc machine, savings ≥20k or nothing)
1332
+ * and retry inside the turn BEFORE the forced-compaction recovery. Default true (flipped with
1333
+ * the machine default in slice 3; X2's dark-landing clause is spent). Independent of
1334
+ * `machine` — an enabled MC-R always clears in the cc form (the rejection arm has no
1335
+ * budget coordinate for the legacy incremental form to stop at). BUDGET ACCOUNTING (design/374
1336
+ * §3.2.1, stated here because it is otherwise invisible to a deployment): a successful MC-R
1337
+ * clear-and-retry SPENDS one attempt of the shared prompt-too-long recovery budget (default 2
1338
+ * attempts per chain), so a chain that clears and is rejected AGAIN has one forced-compaction
1339
+ * attempt left where the knob-off chain nominally had two — the trade costs no effective
1340
+ * compaction pass, because the second forced-compaction call of the off chain is structurally
1341
+ * a no-op whenever the first one landed (the branch leaf is already a compaction entry).
1342
+ *
1343
+ * A declaration outside the closed vocabulary (a `machine` string not in the union, a
1344
+ * non-boolean `clearOnRejection` — JSON/env-derived config the type cannot guard) refuses the
1345
+ * whole prepare loudly (`code: "config.microcompact_invalid"`, no silent re-default): folding it
1346
+ * would silently run the DEFAULT machine while the deployment believes its declaration took
1347
+ * effect.
1348
+ */
1349
+ microCompact?: {
1350
+ machine?: "off" | "legacy" | "cc";
1351
+ clearOnRejection?: boolean;
1352
+ };
1353
+ /**
1354
+ * Two-phase prefix-cache-break detection (design/31): per turn, fingerprint the prefix and, on a
1355
+ * confirmed `cacheRead` drop, emit a root-cause finding via `onError(phase:"prompt-cache")`. Cheap
1356
+ * (hashes only) and only fires on a detected break. Default **on**; set `false` to disable.
1357
+ */
1358
+ cacheBreakDetection?: boolean;
1359
+ /**
1360
+ * Aggregate inline-MEDIA (image) byte budget for the request (batch-C/§17.2 sibling of the text budget;
1361
+ * CC 2.1.196 `tengu_media_byte_cap` parity). Image reads (`Read` → ImageContent) are per-image-capped but
1362
+ * accumulate across turns; this bounds the SUM in the request by stripping the OLDEST images (replaced with a
1363
+ * marker) — request-only, non-destructive (the durable session keeps the image; the model can re-Read it).
1364
+ * Default {@link AGGREGATE_MEDIA_BUDGET_BYTES}; set `Infinity`/`0` to disable.
1365
+ */
1366
+ mediaByteCapBytes?: number;
1367
+ /** Optional telemetry fired when {@link mediaByteCapBytes} strips media in a request (CC's
1368
+ * `tengu_media_byte_cap_stripped` fields). Pure observability — no effect on the request. */
1369
+ onMediaStripped?: (info: {
1370
+ totalBytes: number;
1371
+ limitBytes: number;
1372
+ removedCount: number;
1373
+ removedBytes: number;
1374
+ }) => void;
1375
+ /**
1376
+ * Deferred-tool disclosure policy (design/36). Default (omitted): deterministic per-tool, so the same
1377
+ * `TaskSpec` always yields the same `tools[]`. The default deferral sources are `ToolSpec.defer === true`,
1378
+ * every REMOTE-PROTOCOL tool name (MCP **and** A2A peer tools), {@link TaskSpec.deferTools}, and built-in
1379
+ * tools that declare their own `defer` (today the shared-memory pair) — minus anything pinned inline by
1380
+ * `ToolSpec.alwaysLoad` / {@link TaskSpec.alwaysLoadTools}. `classifyDeferred`'s own contract is the
1381
+ * single authority if this list ever drifts again. `"auto"`: additionally auto-defer tools when the total
1382
+ * inlined schema size would exceed a fraction of the model's context window. Auto is an explicit opt-in
1383
+ * (council minor #6) because an implicit threshold makes `tools[]` depend on the model/context, which
1384
+ * callers find hard to predict; explicit `defer` flags always win regardless of this setting.
1385
+ */
1386
+ deferMode?: "auto";
1387
+ /**
1388
+ * design/84 Seam C — compact-boundary COST optimization (data-gated, default OFF). A FUNCTION seam (a
1389
+ * trusted control-plane injection), so it lives on `RunnerDeps` — NOT `TaskSpec`: `TaskSpec` is the
1390
+ * serializable, untrusted-caller, durable-resume-round-trippable per-task config (a function can be
1391
+ * neither serialized nor durably checkpointed, and the sandbox/env-class injections deliberately sit on
1392
+ * `RunnerDeps` for the same trust reason). Same placement as `promptProvider` / `executionEnvFactory`.
1393
+ *
1394
+ * When `summaryProvider` is set, BOTH compaction call sites (the within-task turn boundary AND `finish()`
1395
+ * at task end) consult it: a NON-EMPTY string is reused verbatim (the real `generateSummary` LLM call is
1396
+ * skipped); `null`/`undefined`/blank falls back to the existing LLM path (byte-identical to today).
1397
+ * `onCompaction` is a pure post-compaction observer. `maxConsecutiveProviderReuse` (default 3, CC parity)
1398
+ * forces a real summary after N consecutive reuses to bound drift; the Runner OWNS the consecutive-reuse
1399
+ * counter across both call sites for the task (reset on a real summary, incremented on a reused one), so
1400
+ * the caller does not have to thread it back. **Absent (default) ⇒ no behavior change whatsoever.**
1401
+ *
1402
+ * SCOPE (design/141 examples 批实测): the seam covers the whole-turn compaction sites only. A
1403
+ * SPLIT-TURN cut point (mid-turn prefix summarization — the common shape inside a long single-user-
1404
+ * message tool loop) bypasses the provider by design (A2 algorithm sovereignty): the turn-prefix
1405
+ * summary is engine-owned. Deployments needing a clean provider-visible boundary should shape work
1406
+ * as separate tasks sharing `sessionId`.
1407
+ */
1408
+ summaryProvider?: import("./auto-compaction.js").MaybeCompactOptions["summaryProvider"];
1409
+ /** design/84 Seam C — pure post-compaction observer (fires only on a real compaction, never a no-op).
1410
+ * Consumed ONLY when `summaryProvider` is also set (the Seam C options early-return otherwise) — a
1411
+ * pure-observer deployment passes a provider that always returns `null` (keeps the LLM path intact). */
1412
+ onCompaction?: import("./auto-compaction.js").MaybeCompactOptions["onCompaction"];
1413
+ /** design/84 Seam C — force a real `generateSummary` after this many consecutive provider reuses (default
1414
+ * 3, CC parity). The Runner tracks the consecutive-reuse counter per task across both compaction sites. */
1415
+ maxConsecutiveProviderReuse?: number;
1416
+ }