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