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