@sema-agent/core 7.5.0 → 7.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/dist/core/auto-mode.d.ts +9 -0
- package/dist/core/auto-mode.js +11 -0
- package/dist/core/checkpoint-store.js +5 -1
- package/dist/core/runner/checkpoint-scope.d.ts +32 -0
- package/dist/core/runner/checkpoint-scope.js +4 -0
- package/dist/core/runner/contracts.d.ts +1878 -0
- package/dist/core/runner/contracts.js +1 -0
- package/dist/core/runner/denial-limit-arms.d.ts +1 -1
- package/dist/core/runner/derived-route-fallback.d.ts +34 -0
- package/dist/core/runner/derived-route-fallback.js +16 -0
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +1 -1
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +170 -0
- package/dist/core/runner/prepare-caps-and-workflow.js +255 -0
- package/dist/core/runner/prepare-config-doors.d.ts +2 -10
- package/dist/core/runner/prepare-defer-classify.d.ts +86 -0
- package/dist/core/runner/prepare-defer-classify.js +107 -0
- package/dist/core/runner/prepare-delegation-surface.d.ts +104 -0
- package/dist/core/runner/prepare-delegation-surface.js +144 -0
- package/dist/core/runner/prepare-execution-env.d.ts +54 -0
- package/dist/core/runner/prepare-execution-env.js +86 -0
- package/dist/core/runner/prepare-file-history.d.ts +95 -0
- package/dist/core/runner/prepare-file-history.js +383 -0
- package/dist/core/runner/prepare-hands-readface.d.ts +5 -7
- package/dist/core/runner/prepare-hands-readface.js +1 -1
- package/dist/core/runner/prepare-inherited-gate.d.ts +268 -0
- package/dist/core/runner/prepare-inherited-gate.js +266 -0
- package/dist/core/runner/prepare-listings.d.ts +77 -0
- package/dist/core/runner/prepare-listings.js +76 -0
- package/dist/core/runner/prepare-lsp.d.ts +55 -0
- package/dist/core/runner/prepare-lsp.js +27 -0
- package/dist/core/runner/prepare-memory.d.ts +1 -1
- package/dist/core/runner/prepare-offload-wrappers.d.ts +62 -0
- package/dist/core/runner/prepare-offload-wrappers.js +45 -0
- package/dist/core/runner/prepare-project-context.d.ts +131 -0
- package/dist/core/runner/prepare-project-context.js +150 -0
- package/dist/core/runner/prepare-prompt-inputs.d.ts +138 -0
- package/dist/core/runner/prepare-prompt-inputs.js +141 -0
- package/dist/core/runner/prepare-protocol-tools.d.ts +91 -0
- package/dist/core/runner/prepare-protocol-tools.js +182 -0
- package/dist/core/runner/prepare-question-face.d.ts +119 -0
- package/dist/core/runner/prepare-question-face.js +83 -0
- package/dist/core/runner/prepare-run-refs.d.ts +89 -0
- package/dist/core/runner/prepare-run-refs.js +39 -0
- package/dist/core/runner/prepare-safety-scan.d.ts +3 -2
- package/dist/core/runner/prepare-task.d.ts +11 -1846
- package/dist/core/runner/prepare-task.js +83 -2366
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +111 -0
- package/dist/core/runner/prepare-tool-disclosure-mount.js +219 -0
- package/dist/core/runner/prepare-wiring-manifest.d.ts +184 -0
- package/dist/core/runner/prepare-wiring-manifest.js +240 -0
- package/dist/core/runner/prepare-workspace-restore.d.ts +1 -27
- package/dist/core/runner/prepare-workspace-restore.js +1 -22
- package/dist/core/runner/rollback-stack.d.ts +32 -0
- package/dist/core/runner/rollback-stack.js +30 -0
- package/dist/core/runner/workspace-path.d.ts +33 -0
- package/dist/core/runner/workspace-path.js +22 -0
- package/dist/core/tool-policy.d.ts +16 -0
- package/dist/core/tool-policy.js +3 -0
- package/dist/core/types.d.ts +2 -2
- package/dist/core/write-protect.js +3 -2
- package/package.json +6 -2
|
@@ -0,0 +1,1878 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The prepare CONTRACTS — the types that cross between `prepareTask` and the phase modules it drives.
|
|
3
|
+
*
|
|
4
|
+
* WHY THEY LIVE HERE. Every one of these was declared inside `prepare-task.ts`, the file that PRODUCES
|
|
5
|
+
* them, so any module needing to name one had to import its own driver. Thirteen such edges had
|
|
6
|
+
* accumulated by the time the dependency-direction gate was written: six phases reaching up for a
|
|
7
|
+
* contract type, the vocabulary file reaching up for two more through inline `import()` type
|
|
8
|
+
* positions, a phase reaching sideways for a pure path helper, and the orchestrator and its own
|
|
9
|
+
* consumer naming each other. A contract belongs BELOW both the producer and the consumer; that is
|
|
10
|
+
* all this file is.
|
|
11
|
+
*
|
|
12
|
+
* It declares TYPES ONLY — nothing is emitted, so it sits on the vocabulary floor beside `types.ts`
|
|
13
|
+
* without adding a runtime edge anywhere. The blocks below are verbatim moves, in their original
|
|
14
|
+
* order, JSDoc included, so the move stays auditable as a move.
|
|
15
|
+
*/
|
|
16
|
+
import type { RepairBundle } from "../../agents/repair-loop.js";
|
|
17
|
+
import type { SubagentRetainLedger } from "../../agents/retain-ledger.js";
|
|
18
|
+
import type { BrainCallGuardrailRef } from "../../brain/timeout.js";
|
|
19
|
+
import type { ActiveWorktreeSession, AgentHarness, AgentMessage, AgentTool, AnnouncedListingSnapshotInput, ExecutionEnv, ThinkingLevel } from "../../internal/harness.js";
|
|
20
|
+
import type { Model } from "../../internal/llm.js";
|
|
21
|
+
import type { WorkflowSizeGuideline } from "../../orchestration/workflow-size-guideline.js";
|
|
22
|
+
import type { ToolManifestRow } from "../../prompt-assembly/tool-catalog.js";
|
|
23
|
+
import type { CwdRef, ReadFace } from "../../tools/fs/index.js";
|
|
24
|
+
import type { MaterializedA2a } from "../a2a.js";
|
|
25
|
+
import type { CompactionForkContext } from "../auto-compaction.js";
|
|
26
|
+
import type { AutoModeArmingRecipe } from "../auto-mode-arming.js";
|
|
27
|
+
import type { AutoModeDecider, AutoModeDenialTracker } from "../auto-mode.js";
|
|
28
|
+
import type { CacheBreakDetector, ToolFingerprintInput } from "../cache-break-detector.js";
|
|
29
|
+
import type { CheckpointGate, CheckpointState, CheckpointToken, PlatformLimitReason, ResourceLedger, ResourceLimitReason } from "../checkpoint-store.js";
|
|
30
|
+
import type { ClearedProjectionLedger, ContextEditMachine, OccurrenceIndex } from "../context-edit.js";
|
|
31
|
+
import type { HookInvocationIdentity, Hooks, OrgGateVerdict } from "../hooks.js";
|
|
32
|
+
import type { MaterializedMcp } from "../mcp.js";
|
|
33
|
+
import type { MemoryEngine } from "../memory-engine/engine.js";
|
|
34
|
+
import type { HarvestReport, MemorySessionHandle } from "../memory-engine/types.js";
|
|
35
|
+
import type { SessionPermissionRules } from "../session-policy-store.js";
|
|
36
|
+
import type { RecoveredOrphan } from "../session-reconcile.js";
|
|
37
|
+
import type { StoredSession } from "../session.js";
|
|
38
|
+
import type { TaskNotificationPayload } from "../task-notification.js";
|
|
39
|
+
import type { OnAsk, ToolCallRequest, ToolPolicy } from "../tool-policy.js";
|
|
40
|
+
import type { ToolDisclosureManifest } from "../trace.js";
|
|
41
|
+
import type { NestedUsageAccum, RunnerDeps, TaskEvent, TaskResult, TaskSpec, ToolActivity, ToolEffect } from "../types.js";
|
|
42
|
+
import type { WiringManifest } from "../wiring-manifest.js";
|
|
43
|
+
import type { ActiveSkillFrame } from "./active-skill-scope.js";
|
|
44
|
+
import type { GitStatusLaneRef } from "./git-status-frame.js";
|
|
45
|
+
import type { SessionReadFileStates } from "./prepare-hands-readface.js";
|
|
46
|
+
import type { BlockedRef, OutputRef, SkillListingEntry } from "./synthetic-tools.js";
|
|
47
|
+
/**
|
|
48
|
+
* The frozen task-start snapshot of the caller's tool-face control arrays: produced by the
|
|
49
|
+
* config-doors phase, read by the hands/read-face phase. Named here rather than reached for as
|
|
50
|
+
* `PrepareConfigDoorsResult["toolFaceSnapshot"]` — an indexed access into a sibling phase's Result
|
|
51
|
+
* is a sideways dependency wearing a type's clothes.
|
|
52
|
+
*/
|
|
53
|
+
export interface ToolFaceSnapshot {
|
|
54
|
+
exclude: readonly string[] | undefined;
|
|
55
|
+
defer: readonly string[] | undefined;
|
|
56
|
+
alwaysLoad: readonly string[] | undefined;
|
|
57
|
+
/** design/277 — the model-gate restore selector ({@link TaskSpec.restoreGatedTools}), fourth
|
|
58
|
+
* seat of the same frozen task-start snapshot: the gate decision and the delegation carrier
|
|
59
|
+
* read THIS, never the live spec. */
|
|
60
|
+
restoreGated: readonly string[] | true | undefined;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The once-per-session announcement ledger's OPERATOR SINK as the phases see it: the
|
|
64
|
+
* `RunnerDeps.onError` signature, deduplicated per (code, text) across the session. The ledger itself
|
|
65
|
+
* (load, digests, settle) lives in prepare-announce-once.ts; a phase that announces once invokes only
|
|
66
|
+
* this member, so the member's shape is declared here — below every phase — rather than reached for
|
|
67
|
+
* sideways as the ledger module's type.
|
|
68
|
+
*/
|
|
69
|
+
export interface AnnounceOnceSink {
|
|
70
|
+
onError: NonNullable<RunnerDeps["onError"]>;
|
|
71
|
+
}
|
|
72
|
+
/** Static per-tool irreversibility tier, resolved at prepare time by the safety scan. */
|
|
73
|
+
export type IrreversibilityTier = Map<string, "never" | "maybe" | "always">;
|
|
74
|
+
/** Declared reversibility probes by tool name, collected by the safety scan on the same pass. */
|
|
75
|
+
export type ReversibilityProbes = Map<string, NonNullable<TaskSpec["tools"]>[number]["reversibilityProbe"]>;
|
|
76
|
+
/**
|
|
77
|
+
* The prepare-FAILURE cleanup stack. A phase that acquires a resource (an execution env, an MCP
|
|
78
|
+
* client, an A2A client) pushes its release the moment the handle exists, so a throw further down
|
|
79
|
+
* cannot leak it — before this the driver could only see a half-acquired handle by hoisting a `let`
|
|
80
|
+
* to the top of the function and hoping every acquisition site remembered to assign it.
|
|
81
|
+
*
|
|
82
|
+
* IT IS ARMED ONLY ON THE FAILURE PATH. The driver calls {@link RollbackStack.commit} before it
|
|
83
|
+
* builds `Prepared`: past that point the stack is empty and every resource's cleanup belongs to the
|
|
84
|
+
* Runner's finish/teardown, which is where a SUCCESSFUL run's resources have always been released.
|
|
85
|
+
* The catch path calls {@link RollbackStack.unwindAll}, which runs the cleanups in reverse order,
|
|
86
|
+
* each exactly once, none of them able to stop another — a bounded settle, the same posture the
|
|
87
|
+
* teardown leg already uses. NEVER call unwind on the success path.
|
|
88
|
+
*
|
|
89
|
+
* The detach guard for an owned execution env and the worktree-isolation check stay with the DRIVER
|
|
90
|
+
* (design/238 D-8 case ①): a phase pushes, it does not destroy, because the driver's reference is
|
|
91
|
+
* the authoritative one and a phase-local rebind could never reach it.
|
|
92
|
+
*/
|
|
93
|
+
export interface RollbackStack {
|
|
94
|
+
/** Register one cleanup for the failure path. Called immediately after the handle is acquired. */
|
|
95
|
+
push(cleanup: () => void | Promise<void>): void;
|
|
96
|
+
/** Disarm: prepare succeeded, so ownership passes to `Prepared` and the stack is emptied. */
|
|
97
|
+
commit(): void;
|
|
98
|
+
/** Failure path only — run every registered cleanup in reverse order, each once, none blocking another. */
|
|
99
|
+
unwindAll(): Promise<void>;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* design/164 件五 — the run's view of deployment usage governance, built once at prepare when
|
|
103
|
+
* `RunnerDeps.usageWindows` + `usageWindowStore` are both wired (absent otherwise, so every consumer is
|
|
104
|
+
* an explicit `!== undefined` test and an ungoverned deployment executes not one extra line).
|
|
105
|
+
*
|
|
106
|
+
* Both methods are called SEQUENTIALLY by the run loop (entry check, then one commit per turn boundary,
|
|
107
|
+
* then one final commit) — the delta bookkeeping in `commit` assumes that and is not a concurrency
|
|
108
|
+
* primitive.
|
|
109
|
+
*/
|
|
110
|
+
export interface UsageGovernance {
|
|
111
|
+
/** The ledger key this run is governed under — the principal, or the shared global key. */
|
|
112
|
+
readonly key: string;
|
|
113
|
+
/** Does any governed window carry a MONEY ceiling? Read by the run loop's pricing seats: only a
|
|
114
|
+
* cost-governing deployment has to treat an unevaluable price table as unpriced spend. */
|
|
115
|
+
readonly governsCost: boolean;
|
|
116
|
+
/** Ms the caller must wait before ANY window would admit work again, or `undefined` when none is
|
|
117
|
+
* exhausted as of `now`. Reads the ledger; propagates a store failure (an unreadable ceiling must not
|
|
118
|
+
* read as an open one). */
|
|
119
|
+
check(now: number): Promise<number | undefined>;
|
|
120
|
+
/**
|
|
121
|
+
* Charge whatever of the run's cumulative spend has not been charged yet. Takes the run's CUMULATIVE
|
|
122
|
+
* totals rather than deltas so no caller can double-charge by calling twice, and so a caller that skips
|
|
123
|
+
* a boundary loses nothing.
|
|
124
|
+
*
|
|
125
|
+
* `cumulativeCostMicroUsd` is the MONEY half (integer micro-USD, `stats.costMicroUsd`). Pass `undefined`
|
|
126
|
+
* when the run's spend has no cost figure at all (RB-368's unpriced state) — a deployment governing a
|
|
127
|
+
* `maxCostUsd` window then REFUSES here rather than charging the fabricated 0 that would let the ceiling
|
|
128
|
+
* silently stop applying. A token-only deployment ignores the argument entirely.
|
|
129
|
+
*/
|
|
130
|
+
commit(cumulativeTokens: number, cumulativeCostMicroUsd: number | undefined, now: number): Promise<void>;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* design/381 — the run's turn-start file-history BOUNDARY seat. `begin(entryId)` starts the capture
|
|
134
|
+
* for the turn's own initial entry (first-wins: exactly one boundary per turn incarnation, DV-4);
|
|
135
|
+
* `settle()` awaits the in-flight capture's settle and is the caller's LEASE CLOSE (awaited before
|
|
136
|
+
* the first tool executes) as well as the finish-tail durability await. The capture is bounded by a
|
|
137
|
+
* 30s timeout whose abort FENCES the attempt (the store's `boundaryPublishVerdict` guarantees a
|
|
138
|
+
* timed-out capture never publishes); failure is disclosed via onError(phase:"rewind") and NEVER
|
|
139
|
+
* blocks the turn — a turn whose capture failed simply has no boundary (the reference behaves the
|
|
140
|
+
* same), and there is no mid-turn re-capture (the store's spent lease refuses it with zero reads).
|
|
141
|
+
*/
|
|
142
|
+
export interface FileHistoryBoundarySeat {
|
|
143
|
+
begin(entryId: string): void;
|
|
144
|
+
settle(): Promise<void>;
|
|
145
|
+
}
|
|
146
|
+
export interface Prepared {
|
|
147
|
+
harness: AgentHarness;
|
|
148
|
+
/** The CONCRETE built-in session (engine-internal: prepare constructs/acquires `StoredSession` itself,
|
|
149
|
+
* and the epoch pin path needs its `appendPromptEpoch`/`getPromptEpoch`). Public consumer faces take
|
|
150
|
+
* the `Session` contract instead. */
|
|
151
|
+
session: StoredSession;
|
|
152
|
+
sessionId: string;
|
|
153
|
+
/**
|
|
154
|
+
* #499 — THIS INVOCATION's own identity, minted once at the top of {@link prepareTask} and never
|
|
155
|
+
* rewritten. The third id of the trio, and the only one the engine owns outright:
|
|
156
|
+
* - `spec.taskId` — the HOST's task identity (absent whenever the host names nothing);
|
|
157
|
+
* - `sessionId` — the CONVERSATION's identity (shared by every run that continues it);
|
|
158
|
+
* - `runId` — THIS call of the engine.
|
|
159
|
+
*
|
|
160
|
+
* The gap it closes: the repo-wide `spec.taskId ?? sessionId` spelling answers the same value for
|
|
161
|
+
* two runs of one session whenever the host supplies no task id, so every per-run account keyed on
|
|
162
|
+
* it FOLDED two runs into one row (two runs each losing one steer read as one run losing one).
|
|
163
|
+
* A per-run account must key on this; a per-TASK fact (trace `taskId`, the host's own correlation
|
|
164
|
+
* handle) must keep the task spelling — the two are different questions and the station sweep
|
|
165
|
+
* judged each one.
|
|
166
|
+
*
|
|
167
|
+
* Minted here (never at the Runner's stream entry) so that a standalone `prepareTask` exercise gets
|
|
168
|
+
* a real id too, and so the mint has exactly one site. A leg whose prepare THROWS never returns a
|
|
169
|
+
* `Prepared` at all — but the id still reaches its caller through the `runIdSink` carrier, because
|
|
170
|
+
* prepare emits run-scoped disclosures of its own and each must have a terminal that names it.
|
|
171
|
+
*/
|
|
172
|
+
runId: string;
|
|
173
|
+
/** design/319 (A ticket) — the session's reminder provenance mark this run mints under (adopted
|
|
174
|
+
* through the prepare adoption ladder: checkpoint seed → trusted fork channel → session entry →
|
|
175
|
+
* fresh mint). Every engine-authored `<system-reminder>` open tag in the run carries it, and the
|
|
176
|
+
* system prompt's Harness declaration names it. Always present on a completed prepare. */
|
|
177
|
+
reminderMark: string;
|
|
178
|
+
/** design/319 (B ticket) — the leg's reminder-disclosure trigger counters (mutated by the
|
|
179
|
+
* disclosure outlets at tool-execute time; read once at result assembly into
|
|
180
|
+
* `stats.mechanisms.reminderDisclosures` when any key is non-zero). Always present. */
|
|
181
|
+
reminderDisclosureCounts: import("../reminder-disclosure.js").ReminderDisclosureCounts;
|
|
182
|
+
/** The ISOLATION-AWARE working-tree root for this task (a worktree's cwd when `isolation: "worktree"`, else
|
|
183
|
+
* `deps.rootPath ?? executionEnv.cwd`) — the same value the hands/LSP/policy/restore use. The Runner's
|
|
184
|
+
* rewind/snapshot path MUST key off THIS, not `deps.rootPath`, or a worktree-isolated turn snapshots the base
|
|
185
|
+
* repo (CORE-1~9 audit MAJOR). */
|
|
186
|
+
taskRootPath: string;
|
|
187
|
+
model: Model;
|
|
188
|
+
/** Effective thinking level (explicit `spec.thinking` or the resolved role's default). */
|
|
189
|
+
thinking?: ThinkingLevel;
|
|
190
|
+
compModel?: Model;
|
|
191
|
+
mcp: MaterializedMcp;
|
|
192
|
+
/** Present only when `spec.a2a` declared peers — the run loop owes it the same end-of-task dispose
|
|
193
|
+
* it owes {@link mcp} (no sockets are held, but a disposed mount is what makes a post-task tool
|
|
194
|
+
* call refuse loudly instead of dialing a peer on a dead task's behalf). */
|
|
195
|
+
a2a?: MaterializedA2a;
|
|
196
|
+
blockedRef: BlockedRef;
|
|
197
|
+
/** Holds the structured output once `submit_output` is called (when `spec.outputSchema` is set). */
|
|
198
|
+
outputRef: OutputRef;
|
|
199
|
+
/** Fires when the task aborts (timeout / max turns / end). Passed to `ToolPolicy.check` so a
|
|
200
|
+
* pending human-approval gate is released instead of hanging past the deadline (F4). */
|
|
201
|
+
abortController: AbortController;
|
|
202
|
+
/** Flipped if any session write lost the optimistic lock during the run → `errorCode = "conflict"`. */
|
|
203
|
+
conflictRef: {
|
|
204
|
+
hit: boolean;
|
|
205
|
+
};
|
|
206
|
+
/** design/134 复审: tool-call ids the gate blocked (policy/hook/plan-mode deny) or suspended —
|
|
207
|
+
* populated only while a consumer is wired (per-tool post hooks or postToolBatch). The runner's
|
|
208
|
+
* batch collector DELETES on match (its tool_execution_end is the only end-event a blocked call
|
|
209
|
+
* emits; the tool_result-side delete in prepare-task never fires for immediate results). */
|
|
210
|
+
blockedToolCalls: Set<string>;
|
|
211
|
+
/**
|
|
212
|
+
* WHAT ended the approval a gated call was waiting on and — design/252 G-7 — WHOSE settlement it
|
|
213
|
+
* was, keyed by tool-call id: written ONLY by the tool gate, at the one exit where an ask resolved,
|
|
214
|
+
* and read once when that call's `tool_end` frame is minted (the reader deletes on read; a call the
|
|
215
|
+
* gate never settled has no entry, and an entry never names neither fact).
|
|
216
|
+
*
|
|
217
|
+
* ONE record rather than two parallel maps because they are one observation: an attribution without
|
|
218
|
+
* the settlement kind beside it is unreadable ("alice" — approved? her window elapsed?), and two maps
|
|
219
|
+
* keyed alike are two chances to drain one and leak the other.
|
|
220
|
+
*
|
|
221
|
+
* It is a sideband and not a field on the tool RESULT because a result is not a trustworthy carrier
|
|
222
|
+
* for this: `details` is arbitrary tool-authored data that post-tool hooks may also replace, so a
|
|
223
|
+
* failing tool could stamp `settledBy:"human"` on itself and tell a consumer's audit view that a
|
|
224
|
+
* person approved something nobody was ever shown. Provenance has to travel on a channel only the
|
|
225
|
+
* adjudicating layer can write. Same reason the entries are keyed by CALL id: the gate adjudicated
|
|
226
|
+
* that exact call, and the frame that reads it is that call's own.
|
|
227
|
+
*/
|
|
228
|
+
approvalSettlement: Map<string, {
|
|
229
|
+
settledBy?: import("../tool-policy.js").ApprovalSettledBy;
|
|
230
|
+
approver?: string;
|
|
231
|
+
resolution?: import("../tool-policy.js").AskDenyResolution;
|
|
232
|
+
autoDenied?: true;
|
|
233
|
+
}>;
|
|
234
|
+
/**
|
|
235
|
+
* The parent-thread human-rejection halt fact (see `maybeHumanRejectionHalt`): present from the
|
|
236
|
+
* moment a bare human rejection halts the turn's batch until the run ends or the NEXT provider
|
|
237
|
+
* request begins (user input continuing the run clears it). Consumers: the runner's stop gate
|
|
238
|
+
* (suppress natural-end pushback / final-verify injection — engine continuations must not restart
|
|
239
|
+
* a run a person just stopped), the turn-boundary engine steers (same reason), and the result
|
|
240
|
+
* stamp (`TaskResult.haltedOnUserRejection` — a human-halted run must not read as an ordinary
|
|
241
|
+
* completion). Engine-owned sideband, same trust reasoning as `approvalSettlement` above.
|
|
242
|
+
*/
|
|
243
|
+
batchHaltRef: {
|
|
244
|
+
current?: {
|
|
245
|
+
rejectedToolCallId: string;
|
|
246
|
+
};
|
|
247
|
+
};
|
|
248
|
+
/** Summed usage of nested sub-runs (sub-agents) spawned by this task's tools. */
|
|
249
|
+
nestedStats: NestedUsageAccum;
|
|
250
|
+
/** RB-430-a: prepare-time rewind disclosures (conversation-only branch / no snapshot backend / no file
|
|
251
|
+
* env), echoed verbatim onto `TaskResult.rewindNotes`. Present only when there is something to say. */
|
|
252
|
+
rewindNotes?: NonNullable<TaskResult["rewindNotes"]>;
|
|
253
|
+
/** The run's edited-file ledger read face — what its OWN hands landed, as `TaskResult.editedFiles`
|
|
254
|
+
* (undefined when nothing landed: the key is absent, never an empty array). A LIVE reader rather
|
|
255
|
+
* than a snapshot, so the throw-path backstop terminal reports the same ledger the ordinary
|
|
256
|
+
* assembly would. Always present on Prepared; independent of whether a fileHistoryStore is wired. */
|
|
257
|
+
editedFilesSnapshot: () => TaskResult["editedFiles"];
|
|
258
|
+
/** design/381 — the run's turn-start boundary seat (present iff a fileHistoryStore is wired and
|
|
259
|
+
* the run mounts a real fs env). runtask calls begin() at the first committed user entry and
|
|
260
|
+
* awaits settle() at the lease close + the finish tail. */
|
|
261
|
+
fileHistoryBoundary?: FileHistoryBoundarySeat;
|
|
262
|
+
/** #240 (design/199 v1.1) + #242 — the run's RESOLVED read face for the result observation seat
|
|
263
|
+
* (`TaskResult.effectiveReadFace`): `carrierReadFace()`'s value at prepare completion — the hands
|
|
264
|
+
* block's single resolution, or the hands-less legs' resolver run (live spec-time facts, with the
|
|
265
|
+
* checkpoint seed folded stricter-wins where one exists). Every leg that completes prepare has a
|
|
266
|
+
* read posture now — this is what its delegation subtree is clamped by even where no faces mount. */
|
|
267
|
+
effectiveReadFace?: ReadFace;
|
|
268
|
+
/** #240 — the normalized deny ADDITIONS in force (deployment ∪ task ∪ checkpoint seed; built-ins
|
|
269
|
+
* excluded), echoed on `TaskResult.effectiveReadDenyPatterns`. Present iff non-empty; a defensive
|
|
270
|
+
* copy (the wide-scope working array stays the engine's own). */
|
|
271
|
+
effectiveReadDenyPatterns?: readonly import("../../tools/fs/read-deny.js").NormalizedReadDenyEntry[];
|
|
272
|
+
/** design/178 v2 §2.3 (件①) — the memory-visibility observation prepareMemory minted (echoed on
|
|
273
|
+
* `TaskResult.effectiveMemoryScopes`). Always present on a completed prepare (the memory-less
|
|
274
|
+
* states are their own values); the seat is optional only so a Prepared shape without the phase
|
|
275
|
+
* cannot fabricate one. */
|
|
276
|
+
effectiveMemoryScopes?: import("../types.js").EffectiveMemoryScopes;
|
|
277
|
+
/** design/99 §E13 — the per-task logical cwd ref when a real shell is mounted (else undefined). The Runner
|
|
278
|
+
* reads `cwdRef.current` after each tool to detect a `cd` move and emit `workspace_changed`. */
|
|
279
|
+
cwdRef?: CwdRef;
|
|
280
|
+
/** design/155: the ACTIVE EnterWorktree session ref (mounted with the worktree tools; undefined without
|
|
281
|
+
* real write hands). runtask's settle write reads `current` for the workspace-state entry. */
|
|
282
|
+
worktreeSessionRef?: {
|
|
283
|
+
current?: ActiveWorktreeSession;
|
|
284
|
+
};
|
|
285
|
+
/** design/155: the settle-write base — the canonical root + what the cwd/worktree were RESTORED to at
|
|
286
|
+
* prepare. runtask diffs the live refs against this at settle and appends a `workspace_state` entry
|
|
287
|
+
* when the state changed (skipped on a durable suspend — the checkpoint lane owns that state). */
|
|
288
|
+
workspaceStateSettle?: {
|
|
289
|
+
rootCanonical: string;
|
|
290
|
+
restoredHandsCwd?: string;
|
|
291
|
+
restoredWorktreeDir?: string;
|
|
292
|
+
baselineUnknown?: boolean;
|
|
293
|
+
};
|
|
294
|
+
/** #483 codex r2+r3 — the settle-time session-seat SEAL (see prepare-hands-readface): re-anchors
|
|
295
|
+
* the per-session read-state seat to the entry id THIS run last committed (its own
|
|
296
|
+
* message_committed tail — provenance, never a shared-leaf re-read). The driver calls it once at
|
|
297
|
+
* the run's settle, every terminal (suspend included), under the session lock; undefined argument
|
|
298
|
+
* (no commits) is a no-op. Synchronous, never throws. Absent ⇔ hands-less / no seat channel. */
|
|
299
|
+
sealReadStateSeat?: (ownCommittedTailEntryId: string | undefined) => void;
|
|
300
|
+
/** design/99 §E6 — the DENY-NARROWING layers (session rules + skill scope, deny-only). Re-checked on RESUME
|
|
301
|
+
* before an approved pending tool executes, so a rule tightened during the suspend still applies. */
|
|
302
|
+
denyNarrowingPolicy?: ToolPolicy;
|
|
303
|
+
/** RB-63: the deployment's own caller policy, re-checked on a durable resume ONLY when the approver
|
|
304
|
+
* rewrote the pending call's args (see the composition site for why the edit case is special). */
|
|
305
|
+
basePolicyForResumeEdit?: ToolPolicy;
|
|
306
|
+
/** design/182 §7 — the ORG adjudication face, re-resolved on a durable RESUME before an approved
|
|
307
|
+
* pending call executes. The resume path bypasses the harness gate by design (a human already
|
|
308
|
+
* adjudicated the checkpointed call), which is exactly where org policy skew is most likely: the
|
|
309
|
+
* suspend may have outlived the snapshot revision that was current when it was minted. Present only
|
|
310
|
+
* on a governed deployment. */
|
|
311
|
+
permissionRuleOrg?: {
|
|
312
|
+
adjudicate: (req: ToolCallRequest) => Promise<OrgGateVerdict>;
|
|
313
|
+
};
|
|
314
|
+
/** Removes the `spec.signal` abort listener on task end (else a long-lived signal leaks listeners). */
|
|
315
|
+
releaseSignal: () => void;
|
|
316
|
+
/**
|
|
317
|
+
* design/174 — end-of-leg sweep for questions this leg's gate routed to a person. Called once by the
|
|
318
|
+
* run loop at task end, on every exit path. A binding that is still present means a person ANSWERED and
|
|
319
|
+
* the call never executed to collect it (aborted, batch torn down, loop threw): that answer is disclosed
|
|
320
|
+
* through the deployment's error sink rather than dropped, because "a human answered and it vanished" is
|
|
321
|
+
* precisely the failure this whole path exists to remove — it must not reappear inside the mechanism
|
|
322
|
+
* that removes it. Idempotent, never throws.
|
|
323
|
+
*/
|
|
324
|
+
settleContentAskBindings: () => ReadonlyArray<{
|
|
325
|
+
deliveryId: string;
|
|
326
|
+
toolCallId: string;
|
|
327
|
+
}>;
|
|
328
|
+
/** Per-task prefix-cache-break detector (design/31). The fingerprint is mutated when deferred tools
|
|
329
|
+
* materialize (design/36) — see `cacheFingerprint`. */
|
|
330
|
+
cacheBreakDetector?: CacheBreakDetector;
|
|
331
|
+
/** The fingerprinted prefix. `systemPrompt` is stable; `tools` is REFRESHED in place when a deferred
|
|
332
|
+
* tool is materialized (placeholder→full schema), so the design/31 detector sees the real tool set. */
|
|
333
|
+
cacheFingerprint?: {
|
|
334
|
+
systemPrompt: string;
|
|
335
|
+
tools: ToolFingerprintInput[];
|
|
336
|
+
};
|
|
337
|
+
/**
|
|
338
|
+
* 提示词主权批 — the labelled composition of the assembled system prompt, emitted as the
|
|
339
|
+
* `prompt.assembled` trace event at task start. `constitution` says who owned the safety layer:
|
|
340
|
+
* `"core"` (structural, the default), `"replaced"` (provider set `replaceAll` — deliberate opt-out),
|
|
341
|
+
* `"provider-assembled"` (migration guard: a historic provider returned a full prompt, passed through
|
|
342
|
+
* un-doubled + onError hint). Deployments/tests assert on this to make "which blocks were in the
|
|
343
|
+
* prompt" a runtime fact instead of archaeology.
|
|
344
|
+
*/
|
|
345
|
+
/**
|
|
346
|
+
* design/173 §8.1 — this leg's EFFECTIVE wiring manifest (built once in prepare from resolved
|
|
347
|
+
* facts through the same derivation as the static half). runtask emits it as the
|
|
348
|
+
* `wiring_manifest` TaskEvent after the prepare consume point and before the leg's first
|
|
349
|
+
* model/tool interaction. Host/operator plane — never enters model context.
|
|
350
|
+
*/
|
|
351
|
+
wiringManifest: WiringManifest;
|
|
352
|
+
/**
|
|
353
|
+
* #281 件A — this leg's frozen identity envelope, minted ONCE in prepare beside the wiring
|
|
354
|
+
* manifest (same leg derivation, one mint — {@link mintHookInvocationIdentity}). Every hook
|
|
355
|
+
* station runtask drives (stop/stopFailure/userPromptSubmit/postToolBatch, the compaction
|
|
356
|
+
* wrapper) and the 件B delegation-lifecycle observer read THIS object; prepare's own stations
|
|
357
|
+
* (the tool gate, the post-tool contexts) close over the same const. Always present — a prepared
|
|
358
|
+
* leg always knows its identity.
|
|
359
|
+
*/
|
|
360
|
+
hookIdentity: HookInvocationIdentity;
|
|
361
|
+
/**
|
|
362
|
+
* The per-invocation TIME BOUND every hook seat of this leg runs under (`Hooks.timeoutMs`, already
|
|
363
|
+
* validated — a garbage value was refused to the default and disclosed ONCE, here, rather than on
|
|
364
|
+
* every tool call). Published for the same reason `hookIdentity` is: the seats live in two files, and
|
|
365
|
+
* a number each station re-derived would be two answers to one wiring question — including two
|
|
366
|
+
* chances to re-refuse the same bad value.
|
|
367
|
+
*/
|
|
368
|
+
hookTimeoutMs: number;
|
|
369
|
+
promptManifest: {
|
|
370
|
+
constitution: "core" | "replaced" | "provider-assembled";
|
|
371
|
+
blocks: Array<{
|
|
372
|
+
id: string;
|
|
373
|
+
chars: number;
|
|
374
|
+
hash: string;
|
|
375
|
+
}>;
|
|
376
|
+
/** Manifest v2 (stage S1, additive): the compiled-IR section view (slot/carrier/cadence/
|
|
377
|
+
* cacheClass per section). `contentHash` is the
|
|
378
|
+
* digest-tiering face: present ONLY on operator-declared sections (the typed
|
|
379
|
+
* `stableBlocks` lane, owner "deployment") as an UNSALTED full sha256 of the section text —
|
|
380
|
+
* the center-publish ↔ manifest end-to-end reconciliation anchor. Core-native sections (env
|
|
381
|
+
* facts, memory tail, user role text) stay salted-only: a plain hash of a short guessable
|
|
382
|
+
* block is a dictionary-preimage surface for trace readers ([E]). */
|
|
383
|
+
sections?: Array<{
|
|
384
|
+
id: string;
|
|
385
|
+
slot: string;
|
|
386
|
+
carrier: string;
|
|
387
|
+
cadence: string;
|
|
388
|
+
cacheClass: string;
|
|
389
|
+
chars: number;
|
|
390
|
+
hash: string;
|
|
391
|
+
contentHash?: string;
|
|
392
|
+
}>;
|
|
393
|
+
/** S2 (additive): the mounted tools plane — contract identity, durable-replay aliases,
|
|
394
|
+
* presentation-invariant shape digest and full wire digest per tool (initial mount face). */
|
|
395
|
+
tools?: ToolManifestRow[];
|
|
396
|
+
/** design/148 S3 (additive, §10.2): the nine-element cache identity — digests only, no prompt
|
|
397
|
+
* text; backfilled once the final wire tool list exists. Live post-refresh truth rides
|
|
398
|
+
* `prepared.turnSnapshot`. */
|
|
399
|
+
snapshot?: {
|
|
400
|
+
cacheIdentity: string;
|
|
401
|
+
elements: import("../../prompt-assembly/turn-snapshot.js").CacheIdentityElements;
|
|
402
|
+
};
|
|
403
|
+
/** design/148 S3 (additive, §10.1): the run's lowering record (version/wire form/known
|
|
404
|
+
* intentional divergences — run-static v1, one brain serves every call). */
|
|
405
|
+
lowering?: import("../../prompt-assembly/turn-snapshot.js").LoweringRecord;
|
|
406
|
+
/** design/168 (additive): the RESOLVED deferred-tool disclosure strategy for this leg and which
|
|
407
|
+
* seat chose it. Absent when nothing is deferred (there is no disclosure to describe). */
|
|
408
|
+
toolDisclosure?: ToolDisclosureManifest;
|
|
409
|
+
};
|
|
410
|
+
/** This run's provider-declared prompt sections (epoch declaration axis),
|
|
411
|
+
* threaded to every compaction-boundary epoch selection so boundary re-pins hash the same
|
|
412
|
+
* artifact identity as the prepare-time pin. Empty on declaration-free runs. */
|
|
413
|
+
epochDeclaredSections: import("../../prompt-assembly/epoch.js").EpochDeclaredSections;
|
|
414
|
+
/** design/148 S3 — the LIVE nine-element identity face (refreshed by deferred materialization and
|
|
415
|
+
* the RB-31 adoption swap); the manifest holds the prepare-time initial copy. */
|
|
416
|
+
turnSnapshot?: import("../../prompt-assembly/turn-snapshot.js").TurnPromptSnapshot;
|
|
417
|
+
/** RB-31 (design/148 S2) — the compaction-boundary center-adoption seam (undefined = no source
|
|
418
|
+
* wired or session not center-pinned). Returns the maybeCompact `centerAdoption` fragment or
|
|
419
|
+
* undefined; `apply` runs post-CAS only (auto-compaction owns the ordering). Both
|
|
420
|
+
* `epochDeclaredSections` and `promptOverheadTokens` on THIS object are mutated by a successful
|
|
421
|
+
* adoption (read them at call time, never capture early). */
|
|
422
|
+
centerCompactionCandidate?: () => {
|
|
423
|
+
centerArtifactDigest?: string;
|
|
424
|
+
sourceRevision?: string;
|
|
425
|
+
clear?: true;
|
|
426
|
+
declaredSections: import("../../prompt-assembly/epoch.js").EpochDeclaredSections;
|
|
427
|
+
overheadTokensAfter: number;
|
|
428
|
+
apply: (committedArtifactDigest?: string) => void;
|
|
429
|
+
} | undefined;
|
|
430
|
+
/** Deferred-tool disclosure (design/36): the monotonic set of activated deferred-tool names. Empty
|
|
431
|
+
* (and no `tool_search` injected) when nothing is deferred. Mutated by BOTH disclosure lanes across
|
|
432
|
+
* the run — the injected `tool_search` and the RB-403 direct-call placeholder lane (default ON) —
|
|
433
|
+
* under ONE shared activation critical section, plus resume seeding at prepare. A leg on which the
|
|
434
|
+
* model never calls `tool_search` is therefore NOT quiescent. */
|
|
435
|
+
activeTools: Set<string>;
|
|
436
|
+
/** RB-313 — the DECLARED deferred set (design/36 `classifyDeferred`, ghost names already pruned).
|
|
437
|
+
* Present iff the task has deferred tools at all; `deferredToolNames − activeTools` is the
|
|
438
|
+
* "undiscovered" census the tool_search_usage_reminder lane enumerates. Frozen at prepare time: a
|
|
439
|
+
* name never leaves the deferred set, it only moves into `activeTools`. */
|
|
440
|
+
deferredToolNames?: ReadonlySet<string>;
|
|
441
|
+
/** design/168 — the RESOLVED materialization strategy this run runs under (`true` = "static":
|
|
442
|
+
* activation never swaps the placeholder; the real schema's only in-context carrier is the
|
|
443
|
+
* ToolSearch result text). Consumed by the tools_delta boundary renderer, whose "full schemas are
|
|
444
|
+
* loaded" wording is a statement about the tools block and is only true under swap. `false` when
|
|
445
|
+
* nothing is deferred (the knob is inert then). */
|
|
446
|
+
toolMaterializeStatic: boolean;
|
|
447
|
+
/** RB-403 — is the direct-call lane mounted on this run's placeholders (`TaskSpec.deferSelfResolve`
|
|
448
|
+
* not disabled; default ON)? The SAME fact `createToolSearchTool` receives as `directCallEnabled`,
|
|
449
|
+
* surfaced here so every model-facing face that describes what happens to a call on a
|
|
450
|
+
* still-deferred tool reads ONE value — under the direct lane a schema-valid call executes rather
|
|
451
|
+
* than failing, so an absolute failure claim is only true for the opt-out posture. `false` when
|
|
452
|
+
* nothing is deferred (no placeholders, so the fact is inert). */
|
|
453
|
+
deferDirectCall: boolean;
|
|
454
|
+
/** design/168 — is THIS deferred name on the static face? False under `"swap"`, and false under
|
|
455
|
+
* `"static"` for a tool whose declaration the result-text carrier cannot hold (the per-tool
|
|
456
|
+
* exemption: it materializes into the tools block instead). Read at call time — the roster is
|
|
457
|
+
* mutated in place by MCP refresh, so renderability is a live fact. Absent when nothing is
|
|
458
|
+
* deferred; a caller with no accessor has no deferred family to describe. */
|
|
459
|
+
staticFaceFor?: (name: string) => boolean;
|
|
460
|
+
/**
|
|
461
|
+
* design/138 S1 — the MemoryEngine session. Present when `deps.memoryBackend` + `spec.memory.enabled`
|
|
462
|
+
* hold AND the engine mount succeeded: a materialize failure without a `config.memory_*` code is
|
|
463
|
+
* fail-open (reported via `deps.onError`, the task runs memory-less), leaving this absent even though
|
|
464
|
+
* both flags hold.
|
|
465
|
+
* `harvest` is the swallow-guarded boundary hook (task terminal in runtask + the checkpoint mint
|
|
466
|
+
* point in commitSuspendSaga): it runs the FULL gate set (containment/secret/caps/deletion fuse),
|
|
467
|
+
* commits entry patches to the backend, self-heals the derived index, and re-baselines (a second
|
|
468
|
+
* harvest of an unchanged session yields zero patches). It NEVER throws.
|
|
469
|
+
* S2-B (O-F7/C-F3): the report is NOT discarded — `harvest` hands it to the Runner-owned
|
|
470
|
+
* `deps.onMemoryHarvestReport` callback (with the boundary phase), and the engine itself enqueues
|
|
471
|
+
* the announcement-queue entry at its harvest tail (drained into the NEXT session's first inject —
|
|
472
|
+
* 时机①; the r1 in-run attachments lane was cut, O-F2/C-F4). `phase` defaults to "terminal" (the
|
|
473
|
+
* runtask call site passes nothing); the checkpoint-mint call site passes "checkpoint".
|
|
474
|
+
*/
|
|
475
|
+
memoryEngineSession?: {
|
|
476
|
+
/** The WRITE-plane pair (design/142 S2b: dual roots collapse to one session face; these are the
|
|
477
|
+
* plane that owns the write gate — the read-only plane is internal to `inject`/`harvest`). */
|
|
478
|
+
engine: MemoryEngine;
|
|
479
|
+
handle: MemorySessionHandle;
|
|
480
|
+
/** Merged injection across all planes (single-root sessions: identical to engine.inject(handle)). */
|
|
481
|
+
inject: () => import("../memory-engine/engine.js").MemoryInjection;
|
|
482
|
+
harvest: (phase?: "checkpoint" | "terminal") => Promise<HarvestReport | undefined>;
|
|
483
|
+
/**
|
|
484
|
+
* design/383 §2.1 — the session's capture opt-out face (the `TaskStream.optOutMemoryCapture`
|
|
485
|
+
* verb's target; host-API only — no tool face reads it, §2.6). `flip` re-adjudicates the
|
|
486
|
+
* entitlement at call time (fresh caps resolve), mints the one-way record, runs the §2.3
|
|
487
|
+
* boundary sweep, and delivers the effectiveness notice; its typed refusals are the
|
|
488
|
+
* `memory.capture_optout_*` family. `optedOut` is the live state read (declared / standing /
|
|
489
|
+
* flipped) — a GENUINE-record read: a store fault answers false here and TRUE on
|
|
490
|
+
* `indeterminate` instead (rescan post-6.0.0-RC — the fault must reach the delegation floor
|
|
491
|
+
* as its own third state, never as either boolean; an irreversible record is never minted off
|
|
492
|
+
* an unreadable state, and an outage must not spawn un-floored children). Present whenever
|
|
493
|
+
* the memory session mounted.
|
|
494
|
+
*/
|
|
495
|
+
captureOptOut?: {
|
|
496
|
+
/** Dual form: a sync capture store answers synchronously; a Promise-form store answers a Promise. `await` is correct on either arm. */
|
|
497
|
+
optedOut: () => boolean | Promise<boolean>;
|
|
498
|
+
indeterminate: () => boolean | Promise<boolean>;
|
|
499
|
+
flip: (reason?: string) => Promise<{
|
|
500
|
+
outcome: "created" | "existed";
|
|
501
|
+
}>;
|
|
502
|
+
};
|
|
503
|
+
/**
|
|
504
|
+
* design/178 §3 — the session's ONE-WAY pollution face. `markPolluted` fires when an
|
|
505
|
+
* external-content-class tool is invoked (the prepare-time tool wrap below is the caller);
|
|
506
|
+
* durable + in-process, no unmark exists at any layer. `polluted` is what the harvest legs read
|
|
507
|
+
* (they consult it at harvest time themselves — this accessor serves observers/tests).
|
|
508
|
+
*/
|
|
509
|
+
pollution: {
|
|
510
|
+
polluted: () => {
|
|
511
|
+
at: number;
|
|
512
|
+
reason: string;
|
|
513
|
+
cause?: import("../memory-engine/types.js").MemoryOriginCause;
|
|
514
|
+
} | undefined;
|
|
515
|
+
/** design/336 §2.2 — `cause` is the structured mechanical fact the mark records (and the
|
|
516
|
+
* origin marker minted off it carries): `"observed"` for a witnessed external event,
|
|
517
|
+
* `"static"` for the capability over-approximation, `"derived"` for recall-taint. Absent
|
|
518
|
+
* folds to `"observed"` at mint time (the honest floor). */
|
|
519
|
+
markPolluted: (reason: string, cause?: import("../memory-engine/types.js").MemoryOriginCause) => void;
|
|
520
|
+
};
|
|
521
|
+
/** design/178 §3 — the task's content-safety config (normalized memory spec): the allowlist for
|
|
522
|
+
* UNDECLARED tools and the strict execution-class upgrade. Consumed by the tool wrap only. */
|
|
523
|
+
contentSafety: {
|
|
524
|
+
trustedTools: ReadonlySet<string>;
|
|
525
|
+
execIsExternalContent: boolean;
|
|
526
|
+
};
|
|
527
|
+
/**
|
|
528
|
+
* design/336 §3.3 — the delegation-settlement handle: the control-plane coordinates a
|
|
529
|
+
* settlement writer needs, as PURE DATA. Consumers (the subagent background leg's write-ahead
|
|
530
|
+
* + terminal observation, the tool wrap's sync unattestable row) can outlive this prepared
|
|
531
|
+
* leg, so they rebuild their write handle from these fields alone — never from the live
|
|
532
|
+
* engine/session objects above. Present only under `memoryProvenance: "carry"` (the default):
|
|
533
|
+
* an `"off"` deployment keeps the pre-336 accepted-cost posture byte-level.
|
|
534
|
+
*/
|
|
535
|
+
settlement?: {
|
|
536
|
+
/** The WRITE plane's control-plane dir (the pollution-marker/lineage sidecar home). */
|
|
537
|
+
controlDir: string;
|
|
538
|
+
sessionId: string;
|
|
539
|
+
provenance: "carry";
|
|
540
|
+
};
|
|
541
|
+
/**
|
|
542
|
+
* design/336 §5.5 (file-face half) — the Read-tool recall-taint judgment, present only under
|
|
543
|
+
* `memoryProvenance: "carry"`. True ⇔ the delivered ABSOLUTE path sits inside a mounted memory
|
|
544
|
+
* plane and its head bytes carry a committed external-origin marker; the tool wrap then marks
|
|
545
|
+
* the session derived (same seat and cause as the memory_get propagation). Never throws;
|
|
546
|
+
* relative paths and unreadable files answer false (named residuals beside the Bash channel).
|
|
547
|
+
*/
|
|
548
|
+
recallTaint?: {
|
|
549
|
+
judgeDeliveredPath: (absPath: string) => boolean;
|
|
550
|
+
};
|
|
551
|
+
};
|
|
552
|
+
/** A per-task env minted by `RunnerDeps.executionEnvFactory` (design/48 remote seam) that THIS task owns
|
|
553
|
+
* and the Runner must tear down on task end. Undefined when the env came from a (caller-owned) static
|
|
554
|
+
* `deps.executionEnv` or the stub — those outlive the task and must NOT be destroyed here. */
|
|
555
|
+
ownedEnv?: ExecutionEnv;
|
|
556
|
+
/** design/45: a mutable holder the durable-suspend gate writes when a policy `ask` was checkpointed
|
|
557
|
+
* (capture + abort). The run loop reads it to assemble `status:"suspended"`. Empty unless a suspension
|
|
558
|
+
* fired this run.
|
|
559
|
+
*
|
|
560
|
+
* `gatedCallId` is the id of the tool call the committed gate is holding — read straight off the
|
|
561
|
+
* committed checkpoint's `tool_approval` pendingAction, so the id a contaminated sibling frame names
|
|
562
|
+
* and the id the checkpoint parks on are the SAME value by construction, not by convention. ABSENT
|
|
563
|
+
* (never guessed) for a park that binds no tool call at all — a `resource_limit` slice and a
|
|
564
|
+
* `plan_review` pause both have pendingActions with no tool call, so there is no causal id to name.
|
|
565
|
+
* Written unconditionally by the commit-side publisher precisely so a later park cannot inherit an
|
|
566
|
+
* earlier one's id. */
|
|
567
|
+
suspendRef: {
|
|
568
|
+
token?: CheckpointToken;
|
|
569
|
+
checkpointId?: string;
|
|
570
|
+
gate?: CheckpointGate;
|
|
571
|
+
scope?: string;
|
|
572
|
+
restoreMode?: "snapshot" | "park_only";
|
|
573
|
+
gatedCallId?: string;
|
|
574
|
+
};
|
|
575
|
+
/** Ruled 2026-08-05 (matrix ruling arm A): set true by the resume engine when this leg EXECUTES the
|
|
576
|
+
* approved pending call — the restart-loop cap then counts from a fresh base (consecutive
|
|
577
|
+
* no-progress suspends only). See `suspendChainBase`. */
|
|
578
|
+
suspendProgressRef: {
|
|
579
|
+
executedApproved: boolean;
|
|
580
|
+
};
|
|
581
|
+
/** design/76 §2.5 (dry-run / shadow) + design/80 D-B (plan-gate): the DUAL of {@link suspendRef} for the
|
|
582
|
+
* REVIEW-PAUSE family — a `{kind:"needs_review"}` pause (a profile's dry-run interception committed a
|
|
583
|
+
* checkpoint whose predicted state-diff a human/judge must REVIEW) OR a `{kind:"plan_review"}` pause (a
|
|
584
|
+
* profile's plan-gate committed a checkpoint whose proposed PLAN a human must approve/edit/reject). The
|
|
585
|
+
* commit-side discriminant (`publishCommittedSuspend`) writes HERE for a `needs_review` OR `plan_review`
|
|
586
|
+
* gate and into {@link suspendRef} for every other gate kind — **never both** (else assemble-result's slot
|
|
587
|
+
* 8.6 `needs_review` branch is dead code, v4 MAJOR-A). The run loop reads it to assemble
|
|
588
|
+
* `status:"needs_review"`. Empty unless a review pause fired this run. */
|
|
589
|
+
reviewRef: {
|
|
590
|
+
token?: CheckpointToken;
|
|
591
|
+
checkpointId?: string;
|
|
592
|
+
gate?: CheckpointGate;
|
|
593
|
+
scope?: string;
|
|
594
|
+
restoreMode?: "snapshot" | "park_only";
|
|
595
|
+
gatedCallId?: string;
|
|
596
|
+
};
|
|
597
|
+
/** RB-439-a: the remote-workspace lifecycle failures this run hit, appended in call order and echoed
|
|
598
|
+
* verbatim on `TaskResult.remoteEnvFailures`. A shared array (not a per-call return) because a suspend
|
|
599
|
+
* refusal is reported through the deployment's `onError` side channel and the run then continues or
|
|
600
|
+
* stops by its own rules — without this collector the caller's result kept no trace that a durable
|
|
601
|
+
* suspend was even attempted, let alone which of the eleven codes refused it. Empty unless something
|
|
602
|
+
* failed. Resume-leg failures do NOT land here (that leg throws out of prepare before a `Prepared`
|
|
603
|
+
* exists) — they ride the thrown error's `remoteEnvFailure` carrier instead. */
|
|
604
|
+
remoteEnvFailures: NonNullable<TaskResult["remoteEnvFailures"]>;
|
|
605
|
+
/** design/72 §2.2 (B): set when a suspend was REFUSED because the task already suspended `maxSuspends`
|
|
606
|
+
* times (a resume/restart loop) — the run is aborted and assembles as `failed`/`suspend.loop` instead
|
|
607
|
+
* of minting yet another checkpoint. */
|
|
608
|
+
suspendLoopRef: {
|
|
609
|
+
hit: boolean;
|
|
610
|
+
};
|
|
611
|
+
/** design/74 Slice 3c: opt-in resource-slice suspend. Present (≠ undefined) ONLY when the task opted in
|
|
612
|
+
* (`spec.resourceSuspend`) AND it is eligible to suspend durably (a checkpoint store, durable tool
|
|
613
|
+
* results, and a remote — or static caller-owned, never per-task-stub — env). The run loop calls it at a
|
|
614
|
+
* CLEAN turn boundary when a resource limit (turns/budget/walltime) was hit: it mints a `resource_limit`
|
|
615
|
+
* checkpoint + pauses the workspace + stops the loop cleanly (NOT abort). Returns true iff it committed a
|
|
616
|
+
* resumable checkpoint (sets `suspendRef`); false ⇒ caller falls through to normal limit handling.
|
|
617
|
+
* `sliceSpend` (Slice 4) is THIS slice's cost/tokens/turns, debited onto the cross-slice ledger. */
|
|
618
|
+
suspendForResource?: (reason: ResourceLimitReason, sliceSpend: {
|
|
619
|
+
costMicroUsd: number;
|
|
620
|
+
tokens: number;
|
|
621
|
+
turns: number;
|
|
622
|
+
walltimeMs: number;
|
|
623
|
+
}) => Promise<boolean>;
|
|
624
|
+
/** design/164 件四/件五: the PLATFORM-cause suspend — the same commit saga as {@link suspendForResource},
|
|
625
|
+
* exposed on the INFRASTRUCTURE alone (checkpoint store + durable tool results + remote/no owned env),
|
|
626
|
+
* WITHOUT the `spec.resourceSuspend` opt-in. The run loop calls it at a clean turn boundary when the
|
|
627
|
+
* execution environment is about to be reclaimed (`env_lifetime`) or a deployment usage window is
|
|
628
|
+
* exhausted (`usage_window`) — causes the task did not choose and cannot ask differently for, so a
|
|
629
|
+
* deployment that CAN keep the work has no reason to be asked whether it wants to. `hint.resumeAfterMs`
|
|
630
|
+
* (usage windows only) rides the gate and extends the checkpoint deadline. Returns true iff it committed
|
|
631
|
+
* a resumable checkpoint; false ⇒ the caller stops the run LOUDLY with the cause's terminal code. */
|
|
632
|
+
suspendForPlatformLimit?: (reason: PlatformLimitReason, sliceSpend: {
|
|
633
|
+
costMicroUsd: number;
|
|
634
|
+
tokens: number;
|
|
635
|
+
turns: number;
|
|
636
|
+
walltimeMs: number;
|
|
637
|
+
}, hint?: {
|
|
638
|
+
resumeAfterMs: number;
|
|
639
|
+
}) => Promise<boolean>;
|
|
640
|
+
/** design/164 件四 — epoch ms at which this run must stop and checkpoint because the EXECUTION
|
|
641
|
+
* ENVIRONMENT's declared lifetime is about to expire (`ExecutionEnv.lifetimeMs` minus
|
|
642
|
+
* {@link ENV_LIFETIME_SUSPEND_MARGIN_MS}). Undefined ⇒ the env declared no lifetime (every local env,
|
|
643
|
+
* and any adapter on an unbounded host) or declared one the engine could not anchor — in both cases the
|
|
644
|
+
* boundary check is dead code, exactly as it was before design/164. Epoch, not monotonic: the anchor
|
|
645
|
+
* comes from the PLATFORM (an env can be older than this process), which is a wall-clock fact and the
|
|
646
|
+
* one domain both sides can name. */
|
|
647
|
+
envLifetimeSuspendAt?: number;
|
|
648
|
+
/** design/164 件五 — deployment usage governance for this run, or undefined when the deployment wired
|
|
649
|
+
* none (or wired windows without a ledger, which is reported and not enforced). */
|
|
650
|
+
usageGovernance?: UsageGovernance;
|
|
651
|
+
/** 1.296 件2b — epoch ms at which the loop ISSUED the current provider call (stamped by the
|
|
652
|
+
* per-call stall-watchdog closure, consumed+cleared by runtask's brain.call trace row as
|
|
653
|
+
* `callStartedAt`). Always present. */
|
|
654
|
+
callIssuedAtRef: {
|
|
655
|
+
current?: number;
|
|
656
|
+
};
|
|
657
|
+
/** RB-458 — records the FIRST brain call this run's outer guardrail gave up on (see
|
|
658
|
+
* {@link import("../../brain/timeout.js").withBrainCallGuardrail}). Always present; `timedOut`
|
|
659
|
+
* stays absent unless the guardrail fired. The run loop reads it AFTER the loop settles and gives
|
|
660
|
+
* the task the typed terminal — the harness turns a loop throw into an error assistant message, so
|
|
661
|
+
* without this the cause would reach the caller only as the generic `provider.error`. */
|
|
662
|
+
brainCallGuardrailRef: BrainCallGuardrailRef;
|
|
663
|
+
/**
|
|
664
|
+
* #548 — the tool gate's own TYPED STOP: set (once) when the classifier denial limit was reached with
|
|
665
|
+
* no approver to fall back to (headless), together with the run abort. The run loop adopts it as the
|
|
666
|
+
* terminal `threw` (`TaskResult.errorCode` = the error's `code`, `errorMessage` = its sentence) the
|
|
667
|
+
* same way it adopts the brain-call guardrail's — a loop that ended because THIS lane aborted it must
|
|
668
|
+
* report the cause, not the consequence. The abort-result details seam reads it too, so the aborted
|
|
669
|
+
* call's own `tool_end` carries the code. `undefined` ⇒ no gate stop happened.
|
|
670
|
+
*/
|
|
671
|
+
gateStopRef: {
|
|
672
|
+
terminal?: Error & {
|
|
673
|
+
code: string;
|
|
674
|
+
};
|
|
675
|
+
};
|
|
676
|
+
/** design/80 D-B — set by a tool calling `ctx.requestReview()` (the first-party `present_plan` tool, CC
|
|
677
|
+
* ExitPlanMode parity): the run loop honors it at the next CLEAN turn boundary by minting a `plan_review`
|
|
678
|
+
* checkpoint. `{ pending }` is set (with an optional reason) the moment a tool requests review; the boundary
|
|
679
|
+
* reads + clears it. First request in a batch wins (idempotent). */
|
|
680
|
+
reviewRequestRef: {
|
|
681
|
+
pending?: {
|
|
682
|
+
reason?: string;
|
|
683
|
+
};
|
|
684
|
+
};
|
|
685
|
+
/** design/80 D-B: present (≠ undefined) ONLY when a `checkpointStore` is wired (the deployment can pause). The
|
|
686
|
+
* run loop calls it at a CLEAN turn boundary when `reviewRequestRef.pending` is set: it mints a `plan_review`
|
|
687
|
+
* checkpoint (`status:"needs_review"`, routes to `reviewRef`) + pauses the workspace + aborts the loop, reusing
|
|
688
|
+
* the SAME commit saga as the human/resource suspends. Returns true iff it committed a resumable checkpoint;
|
|
689
|
+
* false ⇒ the request could not be honored (caller drops it and continues). */
|
|
690
|
+
suspendForReview?: (reason?: string) => Promise<boolean>;
|
|
691
|
+
/** design/74 Slice 4: the prior cross-slice {@link ResourceLedger} (from the resumed checkpoint), so the run
|
|
692
|
+
* loop can size this slice's effective budget = `min(maxCostUsd, remaining)`. Undefined on the first slice
|
|
693
|
+
* (or a non-resource task). */
|
|
694
|
+
resourceLedger?: ResourceLedger;
|
|
695
|
+
/** design/80 D-E-core (A3): a mutable holder the run loop populates (right after `stats` exists) so the
|
|
696
|
+
* human/irreversible_ask suspend can debit THIS leg's live cumulative spend onto the durable approval
|
|
697
|
+
* ledger it attaches (the resource-slice path passes `sliceSpend` explicitly; this event-driven gate has
|
|
698
|
+
* no such arg, so it reads the live spend here). Read at suspend time; absent ⇒ this leg's spend is not
|
|
699
|
+
* debited (the prior ledger still rides for the cross-leg READ). */
|
|
700
|
+
liveSpendRef: {
|
|
701
|
+
get?: () => {
|
|
702
|
+
costMicroUsd: number;
|
|
703
|
+
tokens: number;
|
|
704
|
+
turns: number;
|
|
705
|
+
walltimeMs: number;
|
|
706
|
+
};
|
|
707
|
+
};
|
|
708
|
+
/** design/91: the per-task human-review accumulator (synchronous `resolveAsk` waits this leg + the carried
|
|
709
|
+
* prior-leg burden seeded from the resumed checkpoint). The run loop ADDS the durable-resume latency
|
|
710
|
+
* (`now() − cp.suspendedAt`) on a resume, then surfaces it as `stats.humanReview` at assembly (omitted when
|
|
711
|
+
* empty). **Budget-EXCLUDED** — never folded into cost/the budget gate (design/91 §1). */
|
|
712
|
+
humanReviewRef: {
|
|
713
|
+
count: number;
|
|
714
|
+
totalWaitMs: number;
|
|
715
|
+
gates: Array<{
|
|
716
|
+
kind: string;
|
|
717
|
+
waitMs: number;
|
|
718
|
+
decision?: string;
|
|
719
|
+
toolName?: string;
|
|
720
|
+
toolArg?: string;
|
|
721
|
+
}>;
|
|
722
|
+
};
|
|
723
|
+
/** design/91: the injectable wall-clock the run loop uses for the durable-resume human-review latency
|
|
724
|
+
* (`humanLatencyMs = now() − cp.suspendedAt`), so it reads the SAME clock as the suspend-side `suspendedAt`. */
|
|
725
|
+
now: () => number;
|
|
726
|
+
/** design/45 resume: the FULL resolved tool list (real tools, never deferred placeholders) so the
|
|
727
|
+
* resume engine can execute a previously-suspended pending tool call directly (it bypasses the gate —
|
|
728
|
+
* the human already adjudicated it). Same wrapping (offload + ctx) the harness runs with.
|
|
729
|
+
*
|
|
730
|
+
* ONE array, mutated in place; identity is the contract (every closure that resolves a name at run time
|
|
731
|
+
* reads this exact array). WRITER TABLE (design/238 R-3) — the only stations that write it, in prepare order:
|
|
732
|
+
* · caps-and-workflow — MINTS it (`spec.tools` through the ctx wrap + the large-result wrapper), then pushes
|
|
733
|
+
* ReportBlocked (enableBlockedReport ≠ false), ReportFindings (no caller tool of that name/alias),
|
|
734
|
+
* ExitPlanMode + EnterPlanMode (enablePlanMode ∧ the plan-review face), Workflow (self-orchestration active
|
|
735
|
+
* ∧ runner self ∧ hard sandbox ∧ governance baseline);
|
|
736
|
+
* · the driver — pushes StructuredOutput (outputSchema);
|
|
737
|
+
* · protocol-tools — pushes the MCP tools (through the remote wrapper), RefreshMcpTools (servers declared),
|
|
738
|
+
* the A2A tools; at RUN time its refresh closure splices a server's name-prefix domain out and back in;
|
|
739
|
+
* · hands mount — pushes the hands band (env wired);
|
|
740
|
+
* · delegation-surface — pushes TaskOutput/TaskStop (background ∨ workflow door), SendMessage (door ∨
|
|
741
|
+
* parentNotify ∨ peer lane, ∧ runner self ∧ no shadow), AgentTranscript (door ∧ runner self ∧ no shadow),
|
|
742
|
+
* ListAgents (peer lane ∧ built-in SendMessage ∧ mountable), Monitor (background), EnterWorktree +
|
|
743
|
+
* ExitWorktree (tracked cwd), ReadToolResult (offload store);
|
|
744
|
+
* · question-face — pushes AskUserQuestion (the mount decision);
|
|
745
|
+
* · the driver — pushes CronCreate/CronDelete/CronList (write hands; inert without a daemon);
|
|
746
|
+
* · lsp — pushes the LSP tool (manager wired);
|
|
747
|
+
* · project-context — pushes Skill (manifest), the shared-memory pair and the memory-engine tools;
|
|
748
|
+
* · defer-classify — SPLICES OUT the excluded names and the retracted pair (the only removals at prepare).
|
|
749
|
+
* tool-disclosure and listings only READ it (`harnessTools` is a derived list; placeholders never enter here). */
|
|
750
|
+
tools: AgentTool[];
|
|
751
|
+
/** Name→effect map for every tool this task can call (design/44 §3). Used by the abort-path orphan
|
|
752
|
+
* reconcile (design/64 §9) to make interrupted tool_results effect-aware (read/idempotent = safe to
|
|
753
|
+
* repeat; write/unknown = outcome unknown). Unknown names default to `write` (conservative). */
|
|
754
|
+
toolEffects: Map<string, ToolEffect>;
|
|
755
|
+
/** scan-1/A5 — the orphans the WAKE/CRASH reconcile closed while preparing this run (empty on a fresh or
|
|
756
|
+
* clean session). The run loop replays them onto the stream at run open as synthetic `tool_end` +
|
|
757
|
+
* `message_committed` frames, the same pair the live-abort leg mints at run close: a previous PROCESS
|
|
758
|
+
* died holding those calls, so this run's stream is the only place a consumer can ever learn they ended.
|
|
759
|
+
* Not the live-abort set — that one is reconciled inside the run and never passes through here. */
|
|
760
|
+
wakeRecovered: RecoveredOrphan[];
|
|
761
|
+
/** Fixed per-request prompt overhead (system prompt + tool schemas, ≈chars/4 tokens). Fed to
|
|
762
|
+
* `maybeCompact.overheadTokens` so the compaction trigger stays accurate in the anchor-less
|
|
763
|
+
* regime (custom Brains that don't report usage — design/64 §26.7). */
|
|
764
|
+
promptOverheadTokens: number;
|
|
765
|
+
/** design/169-A — accessor for the MAIN lane's latest real brain request (systemPrompt/messages/
|
|
766
|
+
* tools snapshot, recorded at the harness's provider seam just before each main-loop call). Fed
|
|
767
|
+
* to `maybeCompact.forkContext` on all three compaction lanes so the summary request can FORK the
|
|
768
|
+
* already-paid main prefix (CC form). `undefined` until the run's first main-loop call. Summary
|
|
769
|
+
* calls themselves never pass through the recorded seam (they ride the separate compaction-brain
|
|
770
|
+
* wrapper), so the snapshot is never polluted by a summary request. */
|
|
771
|
+
lastBrainContext: () => CompactionForkContext | undefined;
|
|
772
|
+
/** Narrow workspace reader for compaction working-file attachments (LONGRUN-2): reads a task file
|
|
773
|
+
* via the SAME env the hands ran against (so remote/k8s/E2B tasks read the container's tree, not
|
|
774
|
+
* the control plane's). Present only when the hands are enabled — without an env there is no
|
|
775
|
+
* workspace to re-read. null = unreadable (deleted/binary/transport error); callers skip it.
|
|
776
|
+
* design/199 件B: `{ withheld }` = the target matches the sensitive-path read deny list — the
|
|
777
|
+
* attachment is deliberately withheld and the consumer must SAY so (an annotation, never a silent
|
|
778
|
+
* skip: a silently missing attachment reads as "file gone", which is a different fact). */
|
|
779
|
+
readTaskFile?: (path: string) => Promise<string | null | {
|
|
780
|
+
withheld: {
|
|
781
|
+
pattern: string;
|
|
782
|
+
};
|
|
783
|
+
}>;
|
|
784
|
+
/** CC post-compact restore parity (2026-07-03): the task's READ files, most recent
|
|
785
|
+
* first (from the hands' readFileState `lastReadAt` stamps). The compaction working-file
|
|
786
|
+
* attachment prefers this over the modified set — CC restores what the model RECENTLY READ,
|
|
787
|
+
* including untouched reference files. Present only with hands (same gate as readTaskFile).
|
|
788
|
+
* RB-197: seeded entries are excluded — their content lives in the system-prompt lane, which
|
|
789
|
+
* compaction never touches, so re-attaching them into the summary would be a duplicate. */
|
|
790
|
+
recentlyReadFiles?: () => string[];
|
|
791
|
+
/** RB-197②(独立复审 + 命中,已修) — canonicalizes a raw (often relative, model-typed) path the
|
|
792
|
+
* SAME way the hands toolkit does (`resolveKey` against the same containment root `readTaskFile`
|
|
793
|
+
* uses), so `maybeCompact`'s kept-tail/instruction-source exclusions compare paths in the SAME
|
|
794
|
+
* coordinate `recentlyReadFiles()` already uses — without this a plain string match silently never
|
|
795
|
+
* fires for the common relative-vs-canonical case. Present only with hands (same gate as
|
|
796
|
+
* readTaskFile); absent there is no containment root to resolve against, and every path source is
|
|
797
|
+
* already in the same raw coordinate anyway. */
|
|
798
|
+
normalizeAttachmentPath?: (raw: string) => Promise<string>;
|
|
799
|
+
/** RB-197②(交叉复审命中,已修) — recognizes the hands Read tool's own dedup-stub markers
|
|
800
|
+
* ({@link isReadDedupStubResult}), so `maybeCompact`'s kept-tail scan can tell a SUBSTANTIVE Read
|
|
801
|
+
* result from a stub hit whose original full transmission may already be summarized away. Present
|
|
802
|
+
* only with hands (same gate as readTaskFile). */
|
|
803
|
+
isDedupStubResult?: (resultText: string) => boolean;
|
|
804
|
+
/** RB-197 (form-one; CC 220 clears readFileState at its compaction landing site, @388663): wired to
|
|
805
|
+
* `MaybeCompactOptions.onApplied` by every compaction lane. Drops the non-seeded read-state entries
|
|
806
|
+
* (the summary just replaced the Read results the dedup stubs point at) and re-registers the files
|
|
807
|
+
* that were re-attached WHOLE. Present only with hands (same gate as readTaskFile).
|
|
808
|
+
* RB-197②(命中,已修): `preserveReadState` carries the kept-tail-visible files that were
|
|
809
|
+
* deliberately skipped for re-attachment — their existing entry must survive the clear too (same
|
|
810
|
+
* reasoning as a seeded entry: the model's view of the file did not change). */
|
|
811
|
+
onCompactionApplied?: (attachedComplete: ReadonlyArray<{
|
|
812
|
+
path: string;
|
|
813
|
+
content: string;
|
|
814
|
+
}>, preserveReadState?: ReadonlyArray<string>) => void;
|
|
815
|
+
/** design/121: the live diagnostics lane (present only when the gate passed — manager w/ registry +
|
|
816
|
+
* write hands + not opted out). `registry` is drained by runtask at turn boundaries; `nudge` is
|
|
817
|
+
* called (fire-and-forget) after each successful edit/write so the language server re-analyzes. */
|
|
818
|
+
lspDiagnostics?: {
|
|
819
|
+
registry: import("../lsp-diagnostics.js").LspDiagnosticsRegistry;
|
|
820
|
+
nudge: (rawPath: string) => void;
|
|
821
|
+
/** This run's key into the registry's delivered set (the registry is DEPLOYMENT-scoped — one per
|
|
822
|
+
* `NodeLspManager` — so "already delivered" has to be qualified by run). runtask passes it to every
|
|
823
|
+
* `drain` and calls `releaseRun` with it at the run's terminal. */
|
|
824
|
+
runIdent: string;
|
|
825
|
+
};
|
|
826
|
+
/** design/133 件④: the live plan-mode flag (`enter_plan_mode` flips it, run-local one-way). Exposed
|
|
827
|
+
* so the run loop's plan-mode attachment producer reads the SAME flag the write-deny enforces —
|
|
828
|
+
* never a second source of truth. Always present (`active:false` when plan mode is unused). */
|
|
829
|
+
planModeRef: {
|
|
830
|
+
active: boolean;
|
|
831
|
+
};
|
|
832
|
+
/** The ctx `requestStopAfterTurn` primitive's run-local mirror (one-way, set when a tool asked for a
|
|
833
|
+
* clean stop after the current turn). The harness latch itself is private to the loop; the turn
|
|
834
|
+
* boundary reads THIS to know that no next request follows — a boundary bundle collected there
|
|
835
|
+
* would be steered into a queue the loop never drains, and every one-shot announcement in it
|
|
836
|
+
* (listing frames, the once-per-session MCP arms) would be committed as delivered while it was not. */
|
|
837
|
+
stopRequestedRef: {
|
|
838
|
+
current: boolean;
|
|
839
|
+
};
|
|
840
|
+
/** Whether prepare's ONE read of the branch's announced snapshot succeeded (see
|
|
841
|
+
* `AnnounceOnceLedger.recovered`). False ⇒ the run loop must not flush the once-per-session arms
|
|
842
|
+
* as a whole-snapshot append on this leg (it would replace the branch's record with a partial
|
|
843
|
+
* one); the leg's announcements then simply repeat next run. */
|
|
844
|
+
announcedSnapshotRecovered: boolean;
|
|
845
|
+
/** A1 (design/66 anchor revision) — the date-flip detector seam: `legDate` = the date frozen
|
|
846
|
+
* into this leg's system prefix; `today()` = the boundary-time LOCAL date (user-zone-bound closure).
|
|
847
|
+
* The run loop feeds both to the `date_change` attachment producer; the prefix itself never
|
|
848
|
+
* re-renders mid-leg (cache preservation, CC parity). Present only when the prompt carries a date. */
|
|
849
|
+
dateChange?: {
|
|
850
|
+
legDate: string;
|
|
851
|
+
today: () => string;
|
|
852
|
+
};
|
|
853
|
+
/** [A2] C-4 (design-A §4) — the loadProjectMemory snapshot's declared instruction sources
|
|
854
|
+
* (`ProjectMemoryLoad.instructionSources`): the probe input AND the lane's per-path baseline
|
|
855
|
+
* hashes for the run loop's `instructions_change` attachment. Present only when the deployment's
|
|
856
|
+
* loader declared a non-empty list; the lane additionally requires
|
|
857
|
+
* `RunnerDeps.probeInstructionSources` — either absent ⇒ zero probe calls, byte-identical. */
|
|
858
|
+
instructionSources?: ReadonlyArray<{
|
|
859
|
+
path: string;
|
|
860
|
+
contentHash: string | null;
|
|
861
|
+
}>;
|
|
862
|
+
/** #500 — the RAW instruction-file text this leg's `loadProjectMemory` returned
|
|
863
|
+
* (`ProjectMemoryLoad.content`), for the compaction lanes' `contextInstructionFiles` seat.
|
|
864
|
+
*
|
|
865
|
+
* Which bytes, exactly: the string prepare-task hands `composeMemoryBlock(projectMem, "project")`
|
|
866
|
+
* — pre-compose, unfenced, straight from the host seam. The two neighbours are deliberately NOT
|
|
867
|
+
* candidates: the assembled `memoryBlock` carries the memory ENGINE's layers (bytes the model
|
|
868
|
+
* itself authored in-band this session through the memory tools) and the seat's contract is
|
|
869
|
+
* host/systemPrompt tier; the COMPOSED project block would arrive at a mint that neutralizes the
|
|
870
|
+
* whole authority family, `user_memory`/`scope` included, so the engine would rewrite the tags of
|
|
871
|
+
* its own composition. Raw text in, one fence at the mint.
|
|
872
|
+
*
|
|
873
|
+
* Present only when the deployment wired the loader AND it answered non-blank — the same
|
|
874
|
+
* predicate that composes the project layer, so the summarizer's copy and the main prompt's copy
|
|
875
|
+
* can never disagree about whether this deployment HAS instruction files. Absent ⇒ every
|
|
876
|
+
* compaction lane omits the key entirely. */
|
|
877
|
+
projectInstructionContent?: string;
|
|
878
|
+
/** The `workflow_size_guideline_change` lane's seam, the {@link dateChange} shape one lane over:
|
|
879
|
+
* `legGuideline` = the RESOLVED guideline this leg's Workflow tool card was built with (the card is
|
|
880
|
+
* a per-mount snapshot, so it never re-renders mid-leg — the model learns a retune from the tail
|
|
881
|
+
* frame instead); `current()` = the boundary-time resolved guideline, read live off
|
|
882
|
+
* `RunnerDeps.workflowLimits` so a deployment retuning it mid-run is observable without a re-prepare.
|
|
883
|
+
* Present only when the Workflow tool is actually MOUNTED — a run with no workflow card has no
|
|
884
|
+
* guideline to change. The direct-construction face (`RunWorkflowToolDeps.sizeGuideline`) is out of
|
|
885
|
+
* scope by construction: this mount never passes it, so the deps channel is the lane's only source. */
|
|
886
|
+
workflowSizeGuideline?: {
|
|
887
|
+
legGuideline: WorkflowSizeGuideline;
|
|
888
|
+
current: () => WorkflowSizeGuideline;
|
|
889
|
+
};
|
|
890
|
+
/** design/133 F5 (§R3 决议): boundary-time external-change scan over the ≤`maxFiles` most-recently-READ
|
|
891
|
+
* files. Stats each via `env.fileInfo` and reports paths whose `mtimeMs` moved past the recorded
|
|
892
|
+
* `lastReadAt` + 2s epsilon (CC getChangedFiles shape: readFileState needs NO new field; the agent's
|
|
893
|
+
* own write-backs refresh `lastReadAt`, so self-edits are immune). ENOENT evicts the readFileState
|
|
894
|
+
* entry (CC evict-only-on-ENOENT — transient stat failures skip, never evict) and is echoed in
|
|
895
|
+
* `evicted` so the caller drops its per-path dedup state in lockstep (LOW-9). Present only with
|
|
896
|
+
* hands; the run loop calls it ONLY when `spec.attachments.changedFiles` opted in (OFF ⇒ zero stat). */
|
|
897
|
+
detectExternalChanges?: (maxFiles: number) => Promise<{
|
|
898
|
+
changed: Array<{
|
|
899
|
+
path: string;
|
|
900
|
+
mtimeMs: number;
|
|
901
|
+
}>;
|
|
902
|
+
evicted: string[];
|
|
903
|
+
}>;
|
|
904
|
+
/** G1 通告层 — deferred tools MATERIALIZED (design/36 rematerialize) but not yet announced at a
|
|
905
|
+
* turn boundary. Appended by the rematerialize diff (newly-activated names only — the announced set
|
|
906
|
+
* is seeded with prepare-time actives INCLUDING resume-reseeded ones, so a resume never replays);
|
|
907
|
+
* DRAINED by the run loop only when the `tools_delta` attachment actually survived the byte cap.
|
|
908
|
+
* Present when the task has deferred tools at all — OR (RB-309) when a declared MCP server failed
|
|
909
|
+
* to connect, so the failure is announceable even on a task whose every MCP tool went missing with it.
|
|
910
|
+
*
|
|
911
|
+
* RB-309 — the same frame's MCP arms, filled by the SAME rematerialize seam / materialize-time
|
|
912
|
+
* projection and drained by the same intact-survival predicate:
|
|
913
|
+
* - `pendingRemoved`: previously-ANNOUNCED tool names that left the live roster (a RefreshMcpTools
|
|
914
|
+
* re-splice whose server withdrew them). CC `removedNames`.
|
|
915
|
+
* - `pendingReadded`: names in `pendingRemoved`'s history that came back on a later refresh. CC
|
|
916
|
+
* `readdedNames`.
|
|
917
|
+
* - `pendingFailed`: declared servers whose connect failed at materialize (`statuses`), name/error
|
|
918
|
+
* neutralized + bounded at intake, MINUS the pairs this session's record already announced (CC
|
|
919
|
+
* `failedMcpServers` compared against the previous attachment — a serving layer runs prepare once
|
|
920
|
+
* per user message, so the per-run set alone re-announced every message). */
|
|
921
|
+
toolsDeltaRef?: {
|
|
922
|
+
pending: string[];
|
|
923
|
+
pendingRemoved: string[];
|
|
924
|
+
pendingReadded: string[];
|
|
925
|
+
pendingFailed: Array<{
|
|
926
|
+
name: string;
|
|
927
|
+
error?: string;
|
|
928
|
+
}>;
|
|
929
|
+
};
|
|
930
|
+
/** G1 通告层续批 (CC `agent_listing_delta` parity) — the mounted delegation tool's agent-type roster
|
|
931
|
+
* (read off `ToolSpec.agentListing`, filled by createSubagentTool), plus the tool's mounted name for
|
|
932
|
+
* the CC-verbatim headers. [c209-C]: this seam is now the roster's ONLY model-facing carrier —
|
|
933
|
+
* the run loop delivers the initial full frame ON THE FIRST USER TURN (prompt-adjacent) and
|
|
934
|
+
* boundary drift deltas after. `models` (Q4, read off `ToolSpec.agentModels`) rides the initial
|
|
935
|
+
* frame's tail line. `seedAnnounced` = a durable-resume leg: the run loop seeds the producer's
|
|
936
|
+
* announced set from the checkpoint's `announcedListings.agents` name-set when present (drift
|
|
937
|
+
* since suspend IS delta-announced), else the MED-3② transcript probe (delivered ⇒ seeded as
|
|
938
|
+
* the current entries; unconfirmed ⇒ the resume leg re-announces the initial listing). Present
|
|
939
|
+
* only when such a tool is mounted AND its roster is non-empty. */
|
|
940
|
+
agentListing?: {
|
|
941
|
+
entries: ReadonlyArray<{
|
|
942
|
+
name: string;
|
|
943
|
+
description: string;
|
|
944
|
+
}>;
|
|
945
|
+
toolName: string;
|
|
946
|
+
seedAnnounced: boolean;
|
|
947
|
+
models?: readonly string[];
|
|
948
|
+
};
|
|
949
|
+
/** [c209-C] skills_listing counterpart of {@link agentListing} — the normalized skills METADATA
|
|
950
|
+
* projection (names/descriptions/attachment paths, never bodies; the exact list `createSkillTool`
|
|
951
|
+
* serves). Initial full `<skills>` frame on the first user turn, drift deltas at boundaries,
|
|
952
|
+
* same seeding contract. Present only when `spec.skills` is non-empty (post 1MB-gate). */
|
|
953
|
+
skillsListing?: {
|
|
954
|
+
entries: ReadonlyArray<SkillListingEntry>;
|
|
955
|
+
seedAnnounced: boolean;
|
|
956
|
+
};
|
|
957
|
+
/** [c209-C] Q5 — the run loop's live mirror of the listing frames' announced NAME-SETS (updated at
|
|
958
|
+
* every commit point: first-frame delivery, intact boundary survival, resume seed). Read by the
|
|
959
|
+
* suspend-time checkpoint serializer (`CheckpointState.announcedListings`) so a resume leg can
|
|
960
|
+
* diff the then-current roster/skills against what the model actually saw. Always present (empty
|
|
961
|
+
* object when nothing announced / no listing faces mounted). [c209-C] R2 C7: `models` mirrors the
|
|
962
|
+
* ANNOUNCED model catalog the same way (advanced only when a committed frame carried one), so a
|
|
963
|
+
* catalog change across a suspend is re-announced as a "Models available…" drift line.
|
|
964
|
+
* `mcpFailed` / `advisories` are the once-per-SESSION arms of the same snapshot (see
|
|
965
|
+
* prepare-announce-once.ts): seeded at prepare from the branch's record, advanced by the run loop
|
|
966
|
+
* on intact delivery of the tools_delta `failedServers` arm / the mcp_dropped_tools frame. */
|
|
967
|
+
announcedListingsRef: AnnouncedListingSnapshotInput;
|
|
968
|
+
/** env-tail migration (#254 shape) — the git-status frame lane's run-local state: this leg's
|
|
969
|
+
* resolved frame (probe outcome rendered + hashed at prepare), the announced `(kind, hash)`
|
|
970
|
+
* mirror the checkpoint serializer reads, the trim-protection slot the request-build context
|
|
971
|
+
* handler matches on, and the re-assert closure the compaction landing + boundary retry call.
|
|
972
|
+
* Always present (empty object on a hands-less leg — the lane is then out of scope). */
|
|
973
|
+
gitStatusRef: GitStatusLaneRef;
|
|
974
|
+
/** G1 通告层 — narrow post-compact getter over the process task registry: THIS run's visible
|
|
975
|
+
* pending/running background tasks (same owner/scope/session identity the TaskOutput/TaskStop tools
|
|
976
|
+
* use), as a bounded display projection (id/description/status — never handles/env/abort). Called by
|
|
977
|
+
* the run loop ONLY when `spec.attachments.backgroundTasks` opted in AND a compaction just landed. */
|
|
978
|
+
listBackgroundTasks: () => Array<{
|
|
979
|
+
id: string;
|
|
980
|
+
description?: string;
|
|
981
|
+
status: string;
|
|
982
|
+
}>;
|
|
983
|
+
/** design/122 D1 — the parent-run subagent-retain ledger (present ONLY when `spec.retainSubagentSessions`
|
|
984
|
+
* is enabled). The Runner disposes it (abort in-flight resumes + unpin + release every retained child
|
|
985
|
+
* session) in the task's terminal `finally` — same UNCONDITIONAL posture as the background-agent reap:
|
|
986
|
+
* retain is NOT durable (a suspend leg's in-memory ledger cannot survive a re-prepare), so releasing on
|
|
987
|
+
* every exit path is hygiene, never a loss. */
|
|
988
|
+
subagentRetain?: SubagentRetainLedger;
|
|
989
|
+
/** design/84 Seam C: run-scoped consecutive-`summaryProvider`-reuse counter, OWNED by the Runner and
|
|
990
|
+
* SHARED across both compaction call sites (within-task turn boundary + `finish()`), so the
|
|
991
|
+
* `maxConsecutiveProviderReuse` drift guard is enforced over the whole task — incremented when a
|
|
992
|
+
* compaction reused the provider's summary, reset to 0 on a real (LLM) summary. */
|
|
993
|
+
compactionReuseRef: {
|
|
994
|
+
consecutive: number;
|
|
995
|
+
};
|
|
996
|
+
/** design/123 D4 — trim→compaction pressure propagation (16k live sawtooth root cause): set by the
|
|
997
|
+
* context hook when `trimToBudget` actually DROPPED messages from a request view (request-only trim
|
|
998
|
+
* + usage-anchor mismatch deflates the next boundary's estimate → the trigger and floor are both
|
|
999
|
+
* deceived → full-size request spikes alternate with trimmed troughs). The next turn boundary's
|
|
1000
|
+
* `maybeCompact` consumes it as `force: true` (bypasses the auto threshold AND the §25.2 anti-thrash
|
|
1001
|
+
* floor — "the request layer was forced to drop history" is direct evidence compaction is overdue).
|
|
1002
|
+
* One-shot: cleared on consumption; a failed compaction does NOT re-arm it (existing breaker path).
|
|
1003
|
+
* Content-only clears (`clearStaleToolResults`) never set it — only real message drops do. */
|
|
1004
|
+
trimPressureRef: {
|
|
1005
|
+
droppedMessages: boolean;
|
|
1006
|
+
};
|
|
1007
|
+
/** design/374 slices 1b/2/3 — the microCompact machine state this run: the selected clearing
|
|
1008
|
+
* machine, the cleared-projection ledger (request-view application, durable decisions — see
|
|
1009
|
+
* `context-edit.ts`'s ledger note; per-run in-memory, so durable resume / `resumeAt` rebuilds
|
|
1010
|
+
* start EMPTY by construction), the last request's projection seat (what the provider actually
|
|
1011
|
+
* saw — the MC-R rejection arm computes its candidates and savings on THIS view, never on the
|
|
1012
|
+
* raw session rebuild), the MC-R knob, and the slice-3 arm-B seat. The explicit opt-out
|
|
1013
|
+
* (`machine: "legacy"` + MC-R off) ⇒ the ledger never gains an entry and every replay is a
|
|
1014
|
+
* same-reference no-op (opt-out bytes unchanged). */
|
|
1015
|
+
microCompact: PreparedMicroCompact;
|
|
1016
|
+
}
|
|
1017
|
+
/** See {@link Prepared.microCompact}. */
|
|
1018
|
+
export interface PreparedMicroCompact {
|
|
1019
|
+
/** The frontier-machine selection — `"off"` = no proactive frontier clearing (the unified
|
|
1020
|
+
* machine instead gets its one blocking-point shot, slice-3 arm A). */
|
|
1021
|
+
machine: "off" | ContextEditMachine;
|
|
1022
|
+
/** MC-R (design/374 §3.2): one-shot clear-and-retry on a provider input-too-long rejection.
|
|
1023
|
+
* Default true since the slice-3 flip. */
|
|
1024
|
+
clearOnRejection: boolean;
|
|
1025
|
+
/** design/374 slice 3 (arm B) — the in-turn forced-compaction seat: runtask wires a closure
|
|
1026
|
+
* that runs the SAME forced-compaction pass the prompt-too-long recovery uses (gates included)
|
|
1027
|
+
* and answers whether a compaction landed in the session. The context hook calls it when the
|
|
1028
|
+
* pre-send estimate breaks the guard budget and then returns `adoptSessionRebuild` so the
|
|
1029
|
+
* harness adopts the reduced transcript. `signal` is the TURN-scoped abort of the request
|
|
1030
|
+
* build (r3): a turn interrupt must be able to cut the summary call short instead of waiting
|
|
1031
|
+
* it out. `anchoredEstimate` is the chain's own trigger coordinate — the seat consults the
|
|
1032
|
+
* §25.2 anti-thrash floor against it (an ineffective landing must not be repeated per request
|
|
1033
|
+
* build; the chain's arm C owns the bounded fallback). Unwired (pure-prepare callers) ⇒ arm B
|
|
1034
|
+
* declines and the chain falls to the trim last resort — same posture as a compaction-disabled
|
|
1035
|
+
* run. */
|
|
1036
|
+
inTurnCompactionRef: {
|
|
1037
|
+
current?: (signal?: AbortSignal, anchoredEstimate?: number) => Promise<boolean>;
|
|
1038
|
+
};
|
|
1039
|
+
ledger: ClearedProjectionLedger;
|
|
1040
|
+
projectionRef: {
|
|
1041
|
+
current?: {
|
|
1042
|
+
/** The FINAL projected view of the last provider request (post trim/sweep). */
|
|
1043
|
+
messages: AgentMessage[];
|
|
1044
|
+
/** Occurrence coordinates of that view (object-identity first, unambiguous-group fallback). */
|
|
1045
|
+
keyOf: OccurrenceIndex["keyOf"];
|
|
1046
|
+
};
|
|
1047
|
+
};
|
|
1048
|
+
/** The same offload persist seat the frontier machine uses (write-once, idempotent), so MC-R
|
|
1049
|
+
* clears compose identical markers. Absent when no offload store is configured. */
|
|
1050
|
+
offloadPersist?: (toolCallId: string, fullText: string) => string;
|
|
1051
|
+
}
|
|
1052
|
+
/**
|
|
1053
|
+
* design/45 resume inputs threaded into {@link prepareTask} to continue a suspended task. The Runner
|
|
1054
|
+
* builds it from the persisted {@link Checkpoint}: rewind the branch to the suspension leaf, skip the
|
|
1055
|
+
* suspended batch during wake-reconcile, and re-seed the §4.bis per-task correctness state so the
|
|
1056
|
+
* resumed run is in the **same state space** it suspended in.
|
|
1057
|
+
*/
|
|
1058
|
+
export interface PrepareResume {
|
|
1059
|
+
/** The session leaf to rewind to (the suspension point) BEFORE reconcile — discards the abort's
|
|
1060
|
+
* off-branch "Operation aborted" writes so the resume engine resolves the pending batch cleanly. */
|
|
1061
|
+
leafId: string;
|
|
1062
|
+
/** Batch tool-call ids of the suspended turn — wake-reconcile SKIPS these (they are resumed, not
|
|
1063
|
+
* crash-interrupted; closing them with `[INTERRUPTED]` would DESTROY the suspended batch, §15.2 #7). */
|
|
1064
|
+
suspendedBatch: ReadonlySet<string>;
|
|
1065
|
+
/** The §4.bis correctness-state snapshot to re-seed (activeTools / outputRef / nestedStats /
|
|
1066
|
+
* consolidationNotes / readFileState). */
|
|
1067
|
+
seed: CheckpointState;
|
|
1068
|
+
/** design/72 §2.2 (B): how many times this task already suspended (the resumed checkpoint's
|
|
1069
|
+
* `suspendCount`). The next suspend mints `priorSuspendCount + 1`; past `maxSuspends` it fails
|
|
1070
|
+
* (`suspend.loop`) instead of re-suspending. Absent/0 ⇒ no prior suspends. */
|
|
1071
|
+
priorSuspendCount?: number;
|
|
1072
|
+
/** design/74 Slice 4: the cross-slice {@link ResourceLedger} carried by the resumed `resource_limit`
|
|
1073
|
+
* checkpoint (cumulative spend + the frozen human totals). The next slice's effective budget is
|
|
1074
|
+
* `min(maxCostUsd, totalBudget − spent)`, and its own suspend debits onto this. Absent ⇒ the first slice. */
|
|
1075
|
+
priorLedger?: ResourceLedger;
|
|
1076
|
+
/** design/91: the accumulated human-review burden carried by the resumed checkpoint
|
|
1077
|
+
* ({@link import("../checkpoint-store.js").Checkpoint.humanReview}) — the gates resolved up to and including
|
|
1078
|
+
* the suspend BEFORE this one. Seeds the per-task accumulator so the resumed leg ADDS this suspend's own
|
|
1079
|
+
* latency (`now() − cp.suspendedAt`) on top, reporting the WHOLE chain's burden. Absent ⇒ no prior human time. */
|
|
1080
|
+
priorHumanReview?: {
|
|
1081
|
+
count: number;
|
|
1082
|
+
totalWaitMs: number;
|
|
1083
|
+
gates: Array<{
|
|
1084
|
+
kind: string;
|
|
1085
|
+
waitMs: number;
|
|
1086
|
+
decision?: string;
|
|
1087
|
+
toolName?: string;
|
|
1088
|
+
toolArg?: string;
|
|
1089
|
+
}>;
|
|
1090
|
+
};
|
|
1091
|
+
/** design/49 v1.5: when the suspend ran with a remote workspace, the {@link CheckpointState.workspaceHandle}
|
|
1092
|
+
* to restore — prepare rebuilds the per-task env via `deps.executionEnvFactory` then `resumeVM(snapshotId)`
|
|
1093
|
+
* + `postResumeInit()` (instead of running on a fresh, empty env). Threaded HERE (not via `ResumeTaskConfig`)
|
|
1094
|
+
* so the factory stays a deployment-level `RunnerDeps` capability and never pollutes `TaskSpec` — preserving
|
|
1095
|
+
* the "untrusted caller can't inject an env" red line ({@link import("../remote-env.js").ExecutionEnvFactory}'s
|
|
1096
|
+
* own contract states it: "Lives on `RunnerDeps` (deployment-level) — NOT on `TaskSpec`"; code-ready council round-2). */
|
|
1097
|
+
workspaceHandle?: import("../remote-env.js").WorkspaceHandle;
|
|
1098
|
+
/**
|
|
1099
|
+
* design/174 — the call id of the CONTENT ask whose answer an operator's approval was spent on, when
|
|
1100
|
+
* that is what this leg redeems. Scoped as an id, not a leg-wide flag: an `unavailable` outcome on
|
|
1101
|
+
* THAT call must become a coded failure (the approval bought a question nobody answered), while a NEW
|
|
1102
|
+
* question raised later on the same leg had no approval spent on it and keeps the ordinary
|
|
1103
|
+
* continuation. Absent when the resumed pending action is not a question.
|
|
1104
|
+
*/
|
|
1105
|
+
redeemedContentAskCallId?: string;
|
|
1106
|
+
/** Digest of that question's batch — the id alone can repeat, so the pairing is what keeps a LATER
|
|
1107
|
+
* question from inheriting the claim that an operator approved it. */
|
|
1108
|
+
redeemedContentAskQuestionsHash?: string;
|
|
1109
|
+
/** true iff this resume will EXECUTE an approved pending tool (`tool_approval`
|
|
1110
|
+
* checkpoint × an `allow` winner). The divergent-restore fail-closed guard keys on THIS, not on the
|
|
1111
|
+
* batch being non-empty: a deny winner executes nothing, so a workspace-root divergence must not
|
|
1112
|
+
* wedge the refusal (it proceeds under the path-state rebase and records the deny). */
|
|
1113
|
+
executesApprovedAction?: boolean;
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Parent effective-policy inheritance (tighten-only, {@link RunInternals.inheritedGate}): the spawning
|
|
1117
|
+
* chain's already-evaluated FINAL gate, split into two halves —
|
|
1118
|
+
*
|
|
1119
|
+
* - **Data half** (`ancestorRules`, `shellGate`): serializable snapshots. Each ancestor's per-session
|
|
1120
|
+
* permission rules ride as (sessionId, principal, rev, rules) so the child's prepare can re-read them
|
|
1121
|
+
* LIVE (rev-monotonic — a lagging replica never loosens the snapshot) and re-compile them against the
|
|
1122
|
+
* CHILD's own env/root/effects as extra deny-narrowing layers. `shellGate` folds by max-rank
|
|
1123
|
+
* (off < classify < always): a child can only tighten the parent's shell doctrine, never relax it.
|
|
1124
|
+
* - **Opaque half** (`parentConstraints`): each ancestor's live caller `ToolPolicy` plus its FROZEN
|
|
1125
|
+
* `onAsk`, ancestors first — and, for a hook-wired ancestor, a SECOND entry: its PreToolUse screening
|
|
1126
|
+
* face folded into a `ToolPolicy` (`createPreToolUseConstraintPolicy`), carrying its own frozen
|
|
1127
|
+
* approver/mandate/env axes (the `preToolUse`/`hookEnv` fields below). Folded AFTER the child's own caller policy and BEFORE the trailing
|
|
1128
|
+
* deny-narrowing re-check, each wrapper resolving its own `ask` via the ancestor's frozen approver —
|
|
1129
|
+
* so a parent `ask` never widens into a child auto-allow, and a parent `allow{updatedInput}` rewrite
|
|
1130
|
+
* is still re-checked by the child's narrowing layers. NOT serializable: a durable resume must
|
|
1131
|
+
* re-supply it via `resumeStream(..., internals)` (see {@link CheckpointState.inheritedGate}).
|
|
1132
|
+
*
|
|
1133
|
+
* Chain assembly is single-sited in prepareTask's ctx injection (`inheritedGateForChildren`): upstream
|
|
1134
|
+
* chain + THIS task's own contribution (its session-rule snapshot, its RESOLVED caller policy —
|
|
1135
|
+
* `spec.toolPolicy ?? deps.toolPolicy`, the same slot its own gate enforces — + frozen onAsk, its
|
|
1136
|
+
* effective shellGate, and — when this task resolved a PreToolUse hook — a screening entry for that
|
|
1137
|
+
* hook, deduped by full installation so a deps-level hook is consulted once per call at any depth).
|
|
1138
|
+
* Depth-N delegation is therefore a linear chain, each layer evaluated at most once per tool call.
|
|
1139
|
+
*/
|
|
1140
|
+
export interface InheritedGate {
|
|
1141
|
+
/** Serializable ancestor session-rule snapshots (data half), ancestors first. */
|
|
1142
|
+
ancestorRules?: ReadonlyArray<{
|
|
1143
|
+
sessionId: string;
|
|
1144
|
+
principal?: string;
|
|
1145
|
+
rev: number;
|
|
1146
|
+
rules: SessionPermissionRules;
|
|
1147
|
+
}>;
|
|
1148
|
+
/** The chain's effective shell-gate doctrine — the child folds it by max-rank with its own spec. */
|
|
1149
|
+
shellGate?: "off" | "always" | "classify";
|
|
1150
|
+
/**
|
|
1151
|
+
* The chain's AUTO-MODE INTENT (`TaskSpec.autoModeRequested`, session-wide like a permission mode):
|
|
1152
|
+
* emitted when the spawning task carried the intent itself or inherited it, so an engine-spawned
|
|
1153
|
+
* child of an auto-mode task arms its OWN per-run classifier exactly as its parent did — the
|
|
1154
|
+
* intent half only; the child's deny bit (`RuntimeCaps.autoMode === false`) and deployment face
|
|
1155
|
+
* (`RunnerDeps.autoMode`) are evaluated for the child. Trusted chain data (Runner-assembled, never a
|
|
1156
|
+
* model-authored argument); absent ⇒ the child is an auto-mode task only if its own spec says so.
|
|
1157
|
+
* Persists on the checkpoint's data half (`CheckpointState.inheritedGate.autoModeRequested`) so a
|
|
1158
|
+
* redemption in another process — no seat re-passed, no waking chain — reads the same intent the
|
|
1159
|
+
* suspend leg had; the intent folds by OR across seat, live chain and seed, while the deny bit and
|
|
1160
|
+
* the deployment face stay per-leg.
|
|
1161
|
+
*/
|
|
1162
|
+
autoModeRequested?: true;
|
|
1163
|
+
/**
|
|
1164
|
+
* Org-memory admission freeze (ruled 2026-08-05): the spawning chain's FROZEN admitted org-scope
|
|
1165
|
+
* set — every org memory scope the parent actually mounted (deployment-origin + admitted request).
|
|
1166
|
+
* A delegated child's REQUEST-origin org scopes must be a subset (intersection ≠ request ⇒ the
|
|
1167
|
+
* child's prepare refuses, `memory.admission_denied`); deployment-origin scopes circumvent the freeze
|
|
1168
|
+
* (operator authority is deployment-wide). ALWAYS emitted by chain assembly (empty array = parent
|
|
1169
|
+
* mounted no org plane), so an ABSENT field discriminates an older-shape chain — which the child
|
|
1170
|
+
* side reads fail-closed as the empty set. Serializable (plain strings): persists on the
|
|
1171
|
+
* checkpoint's data half and folds seed ∩ live on resume (tighten-only).
|
|
1172
|
+
*/
|
|
1173
|
+
admittedOrgScopes?: readonly string[];
|
|
1174
|
+
/**
|
|
1175
|
+
* The MONOTONIC org-governance provenance bit (falsification-style round 5): `true` when any leg of this
|
|
1176
|
+
* tree ran with an org-admission surface configured (resolver / deployment scope list) or
|
|
1177
|
+
* inherited the bit. It never clears once set (a governed tree stays governed), and it counts as
|
|
1178
|
+
* governance evidence at the admission door EVEN when every admitted set en route is empty — a
|
|
1179
|
+
* governed parent's org-less child, resumed on a surface-less worker, must still refuse
|
|
1180
|
+
* org-shaped non-v2 scopes instead of mounting them as opaque keys. Absent on trees that never
|
|
1181
|
+
* had a governance surface (their opaque posture is untouched). Serializable; persists on checkpoints.
|
|
1182
|
+
*/
|
|
1183
|
+
orgAdmissionGoverned?: true;
|
|
1184
|
+
/** Live ancestor caller-policy constraints (opaque half), ancestors first. `policy` is the ancestor's
|
|
1185
|
+
* RESOLVED caller policy (`spec.toolPolicy ?? deps.toolPolicy` — the same slot the ancestor itself
|
|
1186
|
+
* enforces, so a deps-level baseline is inherited too).
|
|
1187
|
+
*
|
|
1188
|
+
* ⚖️ A-005.13 (ruled with the F-012 family, 2026-08-09): the ancestor's per-run BUDGET LEDGER does
|
|
1189
|
+
* NOT travel this chain — by design, not by omission. An inherited policy's `check` receives the
|
|
1190
|
+
* CHILD run's own `ToolCallRequest.budget` snapshot (the child's durable resource ledger), so a
|
|
1191
|
+
* SELF-LIMITING ancestor policy ("allow N escalations, then ask") sees a fresh ledger in each
|
|
1192
|
+
* delegated child rather than a continuation of the ancestor's counts. Freezing/snapshotting the
|
|
1193
|
+
* ancestor ledger was considered and rejected: a frozen ledger goes stale the moment the ancestor
|
|
1194
|
+
* keeps running (a stale ledger is a NEW wrongness surface, not a fix), and the durable resource
|
|
1195
|
+
* ledger's identity is per-run by contract. A deployment wanting cross-delegation budget coherence
|
|
1196
|
+
* carries it in its own policy state (the closure travels the chain intact — a closure-counter
|
|
1197
|
+
* policy DOES aggregate across the tree, since every layer evaluates the same instance).
|
|
1198
|
+
*
|
|
1199
|
+
* `onAsk` is the ancestor's frozen
|
|
1200
|
+
* `spec.onAsk ?? deps.onAsk` — an ancestor `ask` resolves at that ancestor's own approver
|
|
1201
|
+
* (headless ⇒ deny), never at the child's. `durableMandate` is frozen at chain-assembly time when the
|
|
1202
|
+
* ancestor ran under a durable-approval regime that would actually PARK its own plain asks — a
|
|
1203
|
+
* `forceDurableGate` entitlement, or `durableApproval` with NO live onAsk frozen (with a live approver
|
|
1204
|
+
* the ancestor's own plain asks resolve synchronously there, so a descendant's ask resolves at that
|
|
1205
|
+
* same frozen `onAsk` — no mandate). It is a CONSERVATIVE descendant-side mandate: whether or not a given ask would actually
|
|
1206
|
+
* have parked at the ancestor (the regime may be scoped), the durable-park semantics cannot be
|
|
1207
|
+
* reconstructed in a delegated child's context — so a wrapper seeing `ask` under this flag denies
|
|
1208
|
+
* fail-closed (tighten-only holds) instead of resolving synchronously. */
|
|
1209
|
+
parentConstraints?: ReadonlyArray<{
|
|
1210
|
+
policy: ToolPolicy;
|
|
1211
|
+
onAsk?: OnAsk;
|
|
1212
|
+
durableMandate?: boolean;
|
|
1213
|
+
/** The CONTENT-ask twin of `durableMandate`, frozen from the QUESTION seat (`spec.onQuestion ??
|
|
1214
|
+
* deps.onQuestion`) on the same rule: a `forceDurableGate` entitlement, or `durableApproval` with no
|
|
1215
|
+
* LIVE question face frozen (absent, or the reserved resume placeholder — neither can answer). A
|
|
1216
|
+
* wrapper seeing an `ask` on the question tool consults THIS bit, never the permission one: the two
|
|
1217
|
+
* asks are answered by two different faces, and a run with a live `onQuestion` and no permission
|
|
1218
|
+
* approver can answer a delegated question perfectly well. When it IS set the delegated question is
|
|
1219
|
+
* marked unresolvable and takes the child gate's park / honest-refusal leg, exactly as an inherited
|
|
1220
|
+
* permission ask does under `durableMandate`. */
|
|
1221
|
+
contentMandate?: boolean;
|
|
1222
|
+
/**
|
|
1223
|
+
* F-012 (#106) — the ancestor's FROZEN auto-mode classifier: the same decider instance the
|
|
1224
|
+
* ancestor's own gate consults for a surviving ask (`ToolGateInput.autoMode`). Without it the
|
|
1225
|
+
* ancestor's ask DECISION CHAIN did not travel — the wrappers resolved an inherited ask straight
|
|
1226
|
+
* at the frozen approver, so an ask the ancestor's classifier would have BLOCKED executed in the
|
|
1227
|
+
* child on the frozen approver's allow. The wrapper runs it BEFORE the frozen approver, in the
|
|
1228
|
+
* ancestor's own gate order (classifier → approver): `block` ⇒ deny (`decisionReason:"classifier"`),
|
|
1229
|
+
* `allow` ⇒ the classifier's auto-allow (the frozen approver is not consulted), anything else
|
|
1230
|
+
* falls through to the approver chain. Never consulted for the reserved question tool nor under a
|
|
1231
|
+
* durable mandate (a marked call must reach the park with no synchronous decision-maker between).
|
|
1232
|
+
*/
|
|
1233
|
+
autoMode?: {
|
|
1234
|
+
decider: AutoModeDecider;
|
|
1235
|
+
/**
|
|
1236
|
+
* #548 — the ancestor's per-run DENIAL-LIMIT tracker, frozen beside its decider (same owner). The
|
|
1237
|
+
* wrapper arms count the frozen classifier's blocks on it and, at a bound, resolve the fallback
|
|
1238
|
+
* ask at the frozen approver instead of denying (`requiresRealApproval` set, sandbox admission
|
|
1239
|
+
* excluded). Live-only, like the decider: a cross-process redemption starts a fresh count.
|
|
1240
|
+
*/
|
|
1241
|
+
denialTracking?: AutoModeDenialTracker;
|
|
1242
|
+
/**
|
|
1243
|
+
* #503 — the SERIALIZABLE criteria half of this classifier (assembly inputs + knobs + the
|
|
1244
|
+
* deployment's settings epoch), present when the arming deployment opted in
|
|
1245
|
+
* (`RunnerDeps.autoMode.persistArming`). A durable park records it on the chain entry so a
|
|
1246
|
+
* CROSS-PROCESS redemption can rebuild an equivalent decider over its own model leg instead of
|
|
1247
|
+
* answering `unavailable` forever; the constraint-chain digest binds it, so a resume re-supplies
|
|
1248
|
+
* exactly what the row carries. It never affects THIS process's decisions — the live `decider` is
|
|
1249
|
+
* the one every gate consults, here and on every descendant.
|
|
1250
|
+
*/
|
|
1251
|
+
arming?: AutoModeArmingRecipe;
|
|
1252
|
+
};
|
|
1253
|
+
/**
|
|
1254
|
+
* Set ONLY on an entry whose `policy` is the folded form of an ancestor's PreToolUse screening face
|
|
1255
|
+
* (issue #33 — the face used to stop at the task that installed it, so a call the ancestor had
|
|
1256
|
+
* refused executed one level down). It carries the raw callback for ONE purpose: identity. A
|
|
1257
|
+
* deployment-level face (`RunnerDeps.hooks`) is resolved by every task in the tree on its own, so
|
|
1258
|
+
* each descendant's gate already runs it in phase 1 — folding it AGAIN as an inherited constraint
|
|
1259
|
+
* would consult the same callback twice per call at depth 1 and once more per level below. Two
|
|
1260
|
+
* identity tests keep that at exactly one consultation per call: chain assembly does not re-append a
|
|
1261
|
+
* face already on the chain, and a descendant substitutes a pass-through for the entry naming the
|
|
1262
|
+
* very callback its own gate will run. A per-task face (`TaskSpec.hooks`) is a different function in
|
|
1263
|
+
* the descendant (or absent), so it folds and travels.
|
|
1264
|
+
*
|
|
1265
|
+
* Identity is the only sound test here, and it is deliberately the CONSERVATIVE one: an assembly that
|
|
1266
|
+
* hands each task a freshly bound wrapper over one underlying callback (the `runSpec` mux does this)
|
|
1267
|
+
* produces two distinct function objects, so the descendant both folds the ancestor's entry and runs
|
|
1268
|
+
* its own — the callback is consulted twice rather than skipped. Erring toward a second screening,
|
|
1269
|
+
* never toward a missed one, is what makes an identity miss a cost rather than a hole.
|
|
1270
|
+
*/
|
|
1271
|
+
preToolUse?: Hooks["preToolUse"];
|
|
1272
|
+
/**
|
|
1273
|
+
* The environment this screening face was installed against — the source of the `HookEnvCapabilities`
|
|
1274
|
+
* handed to it, compared BY REFERENCE, `undefined` when the installation had no env face. Part of the
|
|
1275
|
+
* installation's identity for the same reason the frozen approver is (HIGH): a face that
|
|
1276
|
+
* resolves paths through `ctx.env` answers a different question in a different environment, so an
|
|
1277
|
+
* ancestor running against the deployment's tree and a descendant running in an isolated worktree are
|
|
1278
|
+
* two screenings, not one. A deployment with a per-task `executionEnvFactory` therefore has its face
|
|
1279
|
+
* consulted once per environment on the chain rather than once overall — which is the point: the
|
|
1280
|
+
* ancestor's environment is the only place the ancestor's verdict can be reproduced.
|
|
1281
|
+
*
|
|
1282
|
+
* ⚠️ RECORDED BOUNDARY (delegation provenance): the approver half of the key compares
|
|
1283
|
+
* through {@link askApproverIdentity}, which by its own contract answers "the same decision-maker",
|
|
1284
|
+
* collapsing a `withDelegationProvenance` wrapper onto the approver it forwards to. Two installations
|
|
1285
|
+
* whose approvers differ ONLY in the provenance they attach therefore count as one. Raw reference
|
|
1286
|
+
* equality is not an available alternative: a delegated child's approver is ALWAYS a fresh wrapper
|
|
1287
|
+
* over its parent's, so raw equality would collapse nothing and a deployment-level face would be
|
|
1288
|
+
* consulted once per ancestor level on every leaf call. The exposure is bounded on the side that
|
|
1289
|
+
* matters — the pass-through arm hands the frame to the DESCENDANT's own gate, whose approver carries
|
|
1290
|
+
* the descendant's own provenance (strictly more context, never less). What a chain-assembly collapse
|
|
1291
|
+
* can drop is one intermediate installation's provenance on an ask: an approver that DECIDES on
|
|
1292
|
+
* `req.delegation` would see the surviving installation's rather than both.
|
|
1293
|
+
*/
|
|
1294
|
+
hookEnv?: unknown;
|
|
1295
|
+
}>;
|
|
1296
|
+
}
|
|
1297
|
+
/**
|
|
1298
|
+
* design/78 Slice-1 (MAJOR-3 wiring): a TRUSTED, run-scoped internal channel into {@link prepareTask}, set
|
|
1299
|
+
* ONLY by a trusted CORE caller (`runRepairLoop` via the Runner's internal `runTaskStream` arg) — NEVER from
|
|
1300
|
+
* a {@link TaskSpec} field (the untrusted-caller surface, design/44 §7 Q4). It is the live-state counterpart
|
|
1301
|
+
* of {@link PrepareResume.seed}: where `resume.seed` re-seeds correctness state RESTORED from a checkpoint,
|
|
1302
|
+
* this carries the LIVE per-task state the Runner cannot otherwise see (it lives in the caller's closure).
|
|
1303
|
+
*
|
|
1304
|
+
* Originally it carried only the repair loop's live {@link RepairBundle}: `runRepairLoop` is a thin composition
|
|
1305
|
+
* OVER `runner.runTask`, so when an orthogonal durable suspend (resource/HITL) interleaves a repair attempt,
|
|
1306
|
+
* the bundle (failureTrace/diagnostics/attemptCount/oracleTier) sits in the loop's closure and was being lost
|
|
1307
|
+
* — the minted checkpoint serialized `repairBundle: undefined`. Threading it here lets
|
|
1308
|
+
* {@link prepareTask}'s `serializeCheckpointState` source the LIVE bundle so a resume re-seeds `attemptCount`
|
|
1309
|
+
* MONOTONICALLY (design/76 §2.2#1 r4 MAJOR-A). Mirrors how `nestedStats`/`resume.seed` thread trusted
|
|
1310
|
+
* run-scoped internals through the Runner without touching `TaskSpec`.
|
|
1311
|
+
*
|
|
1312
|
+
* @contract prepare.deps-read-stable — the `RunnerDeps`, `RunInternals` and `ExecutionEnv` handles a host
|
|
1313
|
+
* passes into {@link prepareTask} are READ-STABLE for the duration of the call: prepare reads them at the
|
|
1314
|
+
* points its phases need them, and a host that mutates them between microtasks while prepare is in flight
|
|
1315
|
+
* is outside the contract (the Runner is constructed over its deps; a mid-prepare rewrite is a race the host
|
|
1316
|
+
* made). Phase boundaries are scheduling points — an `await` on a phase that carries its own awaits adds a
|
|
1317
|
+
* microtask between that phase's last continuation and the driver's next read — and such a host can observe
|
|
1318
|
+
* them; the engine does not defend against it. The untrusted surface is {@link TaskSpec}, whose pre-first-
|
|
1319
|
+
* await latch is a separate, pinned guarantee (prepare-task-phase-pins T1).
|
|
1320
|
+
*/
|
|
1321
|
+
export interface RunInternals {
|
|
1322
|
+
/** The live repair bundle from a `runRepairLoop` attempt in flight (attemptCount>0). Serialized onto a
|
|
1323
|
+
* checkpoint minted MID-attempt so a resume re-seeds it; undefined for any non-repair run. */
|
|
1324
|
+
repairBundle?: RepairBundle;
|
|
1325
|
+
/**
|
|
1326
|
+
* design/173 §8.2 — the ENGINE deliberately stripped the spawn turn's per-request `onQuestion`
|
|
1327
|
+
* face from this leg's spec (a long-lived background/retained/revived child must not hold a
|
|
1328
|
+
* callback torn down with the turn that spawned it). Minted ONLY by the subagent lanes'
|
|
1329
|
+
* `stripSpawnTurnQuestionFace` helper, PAIR-PRODUCED with the strip itself — never inferred from
|
|
1330
|
+
* spec shape. Consumed by the wiring manifest (`question.wired: "stripped_bg_lane"`) and by the
|
|
1331
|
+
* interaction-posture door (the strip is core's correct design, not a configuration lie, so an
|
|
1332
|
+
* `"interactive"` tree's engine-stripped legs are exempt). TRUST POSTURE (ruled 2026-08-05):
|
|
1333
|
+
* RunInternals is a public trusted-caller parameter, so this flag is an honesty channel, not a
|
|
1334
|
+
* security gate — a caller forging it is a deployment lying to itself; resource-face safety
|
|
1335
|
+
* stays with its own fail-closed mechanisms.
|
|
1336
|
+
*/
|
|
1337
|
+
questionFaceStripped?: true;
|
|
1338
|
+
/**
|
|
1339
|
+
* design/173 §8.3 (review fold r2-F1) — the SPAWNING run's resolved interaction posture, carried
|
|
1340
|
+
* into engine-built children over the trusted internals channel (never copied onto the child
|
|
1341
|
+
* SPEC — the §8.3 rule). The child's door resolves `spec ?? THIS ?? deps`, so a root's per-run
|
|
1342
|
+
* posture override governs its whole engine-built tree instead of every child falling back to
|
|
1343
|
+
* the deps-level default. Filled by the delegation lanes' `childInternals` and the workflow
|
|
1344
|
+
* spawn attribution; rides `internalsSnapshot`, so retained/revived legs keep it.
|
|
1345
|
+
*/
|
|
1346
|
+
parentInteractionPosture?: "interactive" | "headless";
|
|
1347
|
+
/**
|
|
1348
|
+
* design/153 §7.2c (件3c, r6 H-1) — the trusted post-consume hook for the PARKED-RESUME drive:
|
|
1349
|
+
* called by `resumeStream` after its resolve CAS WON (the token is consumed — the master
|
|
1350
|
+
* arbitration is decided) and BEFORE the resumed leg starts. The parked-resume caller uses it to
|
|
1351
|
+
* flip the reserved row `parked→running` (guarded CAS) + adopt the live handle/lane — the ONLY
|
|
1352
|
+
* legal site for that flip (a pre-consume flip would let TaskStop hit the plain running arm and
|
|
1353
|
+
* circumvent the checkpoint arbitration entirely). A THROW here aborts the resume (propagates out of
|
|
1354
|
+
* `resumeStream`): the checkpoint is already consumed, so the caller's compensation
|
|
1355
|
+
* (`rollbackParkedClaim`) reads `resolved` and settles the honest failed/outcome-unknown terminal
|
|
1356
|
+
* — never a silent run without an adopted row. Deployment/trusted-caller channel only, mirroring
|
|
1357
|
+
* every other RunInternals field.
|
|
1358
|
+
*/
|
|
1359
|
+
afterCheckpointResolve?: () => Promise<void>;
|
|
1360
|
+
/**
|
|
1361
|
+
* Parent effective-policy inheritance (tighten-only): the spawning parent's already-evaluated FINAL
|
|
1362
|
+
* gate, threaded into a child task so the child inherits it as ADDITIONAL constraint layers — a child
|
|
1363
|
+
* can only ever be narrowed by this, never widened (its own session rules / skill scope / caller policy
|
|
1364
|
+
* still apply in full). Filled ONLY by core delegation callers (`createSubagentTool`'s execute via
|
|
1365
|
+
* `ToolExecuteContext.inheritedGateForChildren`, the workflow spawn legs) — NEVER a {@link TaskSpec}
|
|
1366
|
+
* field (the untrusted-caller surface), mirroring `inheritedManifestScope`'s ctx-injection posture.
|
|
1367
|
+
* Absent ⇒ no inheritance (a top-level task; fully backward-compatible).
|
|
1368
|
+
*/
|
|
1369
|
+
inheritedGate?: InheritedGate;
|
|
1370
|
+
/**
|
|
1371
|
+
* design/180 half A — the delegation RUNTIME-PROVENANCE channel. Minted by the spawning delegation
|
|
1372
|
+
* tool when the parent chain is armed (the parent mounts a memory session, or is itself recording
|
|
1373
|
+
* for ITS parent): `ref` is the child's monotonic aggregate — the child's recorder writes into it
|
|
1374
|
+
* as the run progresses (live faces read the current bits; the terminal attestation reduces from
|
|
1375
|
+
* it); `contentSafety` is the chain's FROZEN classification snapshot — the child may narrow it
|
|
1376
|
+
* with its own config, never widen (design/180 A-2). Trusted internals chain only, same posture as
|
|
1377
|
+
* {@link inheritedGate}. Absent ⇒ the child records nothing (its deliveries then read `unknown`,
|
|
1378
|
+
* and every judgment falls back to the static floor — fail-closed by construction; whether the
|
|
1379
|
+
* floor's verdict MARKS the judging session follows that run's deployment evidence standard,
|
|
1380
|
+
* {@link RunnerDeps.memoryDelegationEvidence}).
|
|
1381
|
+
*/
|
|
1382
|
+
delegationProvenance?: {
|
|
1383
|
+
ref: {
|
|
1384
|
+
current: import("../memory-engine/delegation-provenance.js").DelegationProvenanceAggregate;
|
|
1385
|
+
};
|
|
1386
|
+
contentSafety: import("../memory-engine/delegation-provenance.js").DelegationContentSafety;
|
|
1387
|
+
};
|
|
1388
|
+
/**
|
|
1389
|
+
* #22 (ruled 2026-08-05) — the SESSION-scoped org-admission freeze for a SAME-SESSION continuation
|
|
1390
|
+
* that is NOT a checkpoint resume: a retained background child revived through the in-process resume
|
|
1391
|
+
* leg. The checkpoint plane already freezes the session's own verdict
|
|
1392
|
+
* ({@link InheritedGate.ownAdmittedOrgScopes}); this leg has no checkpoint, so without a carrier it
|
|
1393
|
+
* re-adjudicated from scratch and a resolver whose answer WIDENED between the two legs remounted a
|
|
1394
|
+
* tenant layer the session had already lost.
|
|
1395
|
+
*
|
|
1396
|
+
* A REF, not a value: the revival replays a spread COPY of the spawn-time internals, so a plain field
|
|
1397
|
+
* could only ever carry the value that existed at spawn (always `undefined`). The prepare reads
|
|
1398
|
+
* `current` as its `priorOwnVerdict` — intersected with any checkpoint seed, so the fold can only ever
|
|
1399
|
+
* narrow — and writes this leg's own verdict back, which is ⊆ the prior one by construction.
|
|
1400
|
+
*
|
|
1401
|
+
* TRUSTED internal, filled by `createSubagentTool`'s `childInternals` (one ref per spawned child, never
|
|
1402
|
+
* shared across generations — a child's own children build their own). Absent ⇒ no session freeze from
|
|
1403
|
+
* this channel, byte-identical to a deployment with no governance surface.
|
|
1404
|
+
*/
|
|
1405
|
+
ownOrgAdmissionRef?: {
|
|
1406
|
+
current: import("../memory-admission.js").OwnOrgAdmissionVerdict | undefined;
|
|
1407
|
+
};
|
|
1408
|
+
/**
|
|
1409
|
+
* design/176 — this run's LATE-BOUND peer-identity carrier (a REF, same family and reason as
|
|
1410
|
+
* {@link ownOrgAdmissionRef}: revival replays a spread copy of spawn-time internals, and the axes
|
|
1411
|
+
* a run wears are born at different points — a* handle at registration, session/task at prepare's
|
|
1412
|
+
* session acquisition). Created at internals assembly (delegation lanes; runtask normalizes one
|
|
1413
|
+
* in for a top-level run); axes recorded through the single monotonic `addAxis`; the canonical
|
|
1414
|
+
* key freezes at the first recorded axis (h→s→t by call-site ordering). Read by the SendMessage
|
|
1415
|
+
* mount (sender key + hop token) and paired into children as {@link parentPeerRef}.
|
|
1416
|
+
*/
|
|
1417
|
+
peerSelfRef?: import("../../agents/peer-admission.js").PeerSelfRef;
|
|
1418
|
+
/**
|
|
1419
|
+
* design/176 — this run's INBOUND peer-chain ref: overwritten at the harness consumption boundary
|
|
1420
|
+
* with the chain of the peer message the model just consumed (enqueue-time writes would leak a
|
|
1421
|
+
* not-yet-seen message's chain into outbound sends); seeded by the L3/L4 wake legs; empty
|
|
1422
|
+
* otherwise. Not checkpointed in v1 (recorded honest boundary: a cross-process resume forwards
|
|
1423
|
+
* from an empty chain — fail-open on loop suppression only).
|
|
1424
|
+
*/
|
|
1425
|
+
peerInboundChainRef?: import("../../agents/peer-admission.js").PeerInboundChainRef;
|
|
1426
|
+
/**
|
|
1427
|
+
* design/176 — the PARENT run's peer-identity ref, PAIR-MINTED with {@link parentNotify} at every
|
|
1428
|
+
* trust point that binds the uplink callback (spawn assembly, the resume face's replacement): the
|
|
1429
|
+
* callback is opaque and the parent AXES deliberately keep the original spawner on the revive arm
|
|
1430
|
+
* while the callback points at the WAKER — so the uplink's admission identity can only travel
|
|
1431
|
+
* with the binding itself. Consumed by the SendMessage mount as `uplinkRecipient`.
|
|
1432
|
+
*/
|
|
1433
|
+
parentPeerRef?: import("../../agents/peer-admission.js").PeerSelfRef;
|
|
1434
|
+
/**
|
|
1435
|
+
* 🔴 design/97 §H.1 / design/98 §0.1 (BLOCKER3) — the workflow **nesting depth** for this run, a TRUSTED
|
|
1436
|
+
* cross-process channel (worker/script can NEVER set it — it is not a {@link TaskSpec} field nor a
|
|
1437
|
+
* `run_workflow` tool param). When a deployment initiates a workflow on behalf of a parent run that is
|
|
1438
|
+
* itself inside a workflow (e.g. service's `/v1/workflows`), it threads `workflowDepth = parentDepth + 1`
|
|
1439
|
+
* into `startWorkflow`/`runWorkflow` so the one-level nesting guard fires across the process boundary.
|
|
1440
|
+
* In-process nesting needs nothing here — the engine's `AsyncLocalStorage` propagates depth automatically.
|
|
1441
|
+
* Mirrors how `repairBundle`/`inheritedManifestScope` thread trusted run-scoped internals the Runner cannot
|
|
1442
|
+
* see from `spec`. Consumed by the `run_workflow` tool wiring (S8c), not by `prepareTask` itself.
|
|
1443
|
+
*/
|
|
1444
|
+
workflowDepth?: number;
|
|
1445
|
+
/**
|
|
1446
|
+
* design/110 — set ONLY by the Agent tool's fork route (`Agent(subagent_type:"fork")`, a core caller) on the
|
|
1447
|
+
* child it spawns: this run IS a forked child. `prepareTask` threads it to tool ctx as `insideFork` so the
|
|
1448
|
+
* child's own Agent tool refuses a nested fork (nesting guard — mirrors CC's "fork is not available inside a
|
|
1449
|
+
* forked worker"; a fork can still delegate via `Agent`, just not fork again). TRUSTED internal (NOT a
|
|
1450
|
+
* `TaskSpec` field — the untrusted-caller surface), mirrors `inheritedManifestScope`.
|
|
1451
|
+
*/
|
|
1452
|
+
insideFork?: boolean;
|
|
1453
|
+
/**
|
|
1454
|
+
* design/319 (A ticket) — the PARENT's reminder provenance mark, set ONLY by the Agent tool's
|
|
1455
|
+
* fork route on the child it spawns ("one declaration, one mark": a fork runs under the parent's
|
|
1456
|
+
* byte-identical system-prompt declaration, so its own engine mints must carry the PARENT's mark;
|
|
1457
|
+
* a spawn/clone context mints its own). Verified through the mint home's verify port at adoption
|
|
1458
|
+
* (`isValidReminderMark` — an unrecognized value re-mints, fail-safe); the forked SESSION's own
|
|
1459
|
+
* `reminder_mark` entry is the primary carrier (a store fork copies committed history), this
|
|
1460
|
+
* channel is the in-process belt over it. TRUSTED run-scoped channel (NOT a {@link TaskSpec}
|
|
1461
|
+
* field), mirroring `insideFork`.
|
|
1462
|
+
*/
|
|
1463
|
+
reminderMark?: string;
|
|
1464
|
+
/**
|
|
1465
|
+
* RB-204 P1 — set UNCONDITIONALLY by every core spawn path (`createSubagentTool`'s
|
|
1466
|
+
* `childInternals` — shared by all four spawn legs sync/steer/background/fork, its persisted
|
|
1467
|
+
* `internalsSnapshot` for revive, and the workflow orchestrator's `spawnAttribution`), regardless
|
|
1468
|
+
* of whether a NAMEABLE `parentTaskId`/`parentToolCallId` exists. A directly-started workflow (no
|
|
1469
|
+
* launching tool call, e.g. cron-triggered) has neither of those — "no id is ever fabricated" for
|
|
1470
|
+
* attribution — but its spawned agents are still delegated children for consent-notice purposes.
|
|
1471
|
+
* Drives `isSubagent` below: do NOT use `parentTaskId` presence alone as the child-ness signal,
|
|
1472
|
+
* that under-covers exactly this case. TRUSTED run-scoped channel (NOT a {@link TaskSpec} field),
|
|
1473
|
+
* mirroring `insideFork`.
|
|
1474
|
+
*/
|
|
1475
|
+
isDelegatedChild?: boolean;
|
|
1476
|
+
/**
|
|
1477
|
+
* Subagent transcript persistence — the child transcript session's PLACEMENT declaration, minted
|
|
1478
|
+
* by the background delegation lane (the trusted spawner: it knows the a* handle, scope and root)
|
|
1479
|
+
* and forwarded verbatim by this prepare's session acquire, so the session store CREATES the
|
|
1480
|
+
* child's transcript into its declared subagent partition (see {@link SessionStore.placements}).
|
|
1481
|
+
* Deliberately a TRUSTED internals seat and never a {@link TaskSpec} key: a public key would let
|
|
1482
|
+
* any caller push arbitrary sessions into the partition and poison store-side retention/
|
|
1483
|
+
* enumeration. Absent (sync children, forks, observers, every non-delegated run) ⇒ the acquire
|
|
1484
|
+
* carries no placement — byte-identical to before.
|
|
1485
|
+
*/
|
|
1486
|
+
sessionPlacement?: import("../session.js").SessionPlacement;
|
|
1487
|
+
/**
|
|
1488
|
+
* G1+G2 合车复审修② (1.259.0) — the DEFAULT role-base persona for a DELEGATED child, threaded by
|
|
1489
|
+
* `createSubagentTool`'s execute (a core caller) when neither an agent-definition `systemPrompt` nor the
|
|
1490
|
+
* delegation tool's `opts.systemPrompt` names one. It sits at the BOTTOM of the role-base chain —
|
|
1491
|
+
* `spec.systemPrompt ?? resolvedRole.systemPrompt ?? internals.defaultSystemPrompt` — so a deployment's
|
|
1492
|
+
* `roles.subagent.systemPrompt` / `roles.default.systemPrompt` preset still wins (pre-G1 semantics: an
|
|
1493
|
+
* unset child systemPrompt let the role preset apply; G1's first cut put SUBAGENT_PROMPT at spec level
|
|
1494
|
+
* and silently shadowed the preset). Only when NO preset resolves does the lean SUBAGENT_PROMPT (CC 198
|
|
1495
|
+
* general-purpose persona, pretty.js:419977) replace the full DEFAULT_SYSTEM_PROMPT constitution base.
|
|
1496
|
+
* TRUSTED run-scoped channel (NOT a {@link TaskSpec} field), mirroring `insideFork`.
|
|
1497
|
+
*/
|
|
1498
|
+
defaultSystemPrompt?: string;
|
|
1499
|
+
/**
|
|
1500
|
+
* design/96 §C (S2) — GOAL MODE flag, a TRUSTED internal channel set ONLY by `runGoal` (a core caller),
|
|
1501
|
+
* NEVER a {@link TaskSpec} field. It drives `featureFlags.goalEnabled` → injects `GOAL_COMPLETION_GUIDANCE`.
|
|
1502
|
+
* Why internal (not a public `TaskSpec.goalMode`): the guidance promises "declaring done STOPS iteration and
|
|
1503
|
+
* surfaces" — a promise only `runGoal`'s loop makes real. A public field would let a caller inject that
|
|
1504
|
+
* prompt with no loop behind it (§6.3 honesty violation). `runGoal` injects the `declare_done` tool itself;
|
|
1505
|
+
* this flag only governs the PROMPT (injection-ownership split).
|
|
1506
|
+
*/
|
|
1507
|
+
goalMode?: boolean;
|
|
1508
|
+
/**
|
|
1509
|
+
* 🔴 design/77 §3 / §7 (ON-前必关) — skill→subagent manifest-scope PROPAGATION. The parent task's
|
|
1510
|
+
* ACTIVE skill-manifest frames, snapshotted at the moment a subagent was spawned WHILE a manifest scope
|
|
1511
|
+
* was live on the parent. The child's {@link prepareTask} seeds its own {@link ActiveSkillScope} from
|
|
1512
|
+
* these so the child inherits the parent skill's deny-narrowing — fail-closed and MONOTONIC: a child of
|
|
1513
|
+
* a manifested skill is AT MOST as capable as the manifest (its own manifests can only narrow further,
|
|
1514
|
+
* never re-grant a tool/path the parent removed).
|
|
1515
|
+
*
|
|
1516
|
+
* This is a TRUSTED, run-scoped channel filled ONLY by `createSubagentTool`'s `execute` (a core caller),
|
|
1517
|
+
* NEVER a {@link TaskSpec} field (TaskSpec is the untrusted-caller surface — design/44 §7 Q4). It mirrors
|
|
1518
|
+
* how `repairBundle`/`resume.seed` thread live per-task state the Runner cannot see from `spec`.
|
|
1519
|
+
*
|
|
1520
|
+
* Fail-closed: when the parent HAD an active manifest at spawn but the precise frames cannot be
|
|
1521
|
+
* snapshotted, the subagent tool threads a single DENY-ALL `unresolved` frame here rather than letting the
|
|
1522
|
+
* child run unmanifested — the safe path is the default. An empty/absent value = no inheritance (a
|
|
1523
|
+
* subagent spawned with no active parent manifest behaves exactly as before, backward-compatible).
|
|
1524
|
+
*/
|
|
1525
|
+
inheritedManifestScope?: readonly ActiveSkillFrame[];
|
|
1526
|
+
/**
|
|
1527
|
+
* design/99 §E2 — when this task runs as a SUB-AGENT spawned under a parent task's
|
|
1528
|
+
* tool call, the spawning tool's `ToolExecuteContext.toolCallId`. The Runner stamps it onto this task's
|
|
1529
|
+
* stream content events as {@link TaskEvent.parentToolCallId} so a consumer can attribute the child's live
|
|
1530
|
+
* content to the delegation subtree WITHOUT core merging the child stream into the parent (lightweight
|
|
1531
|
+
* message-identity, not stream-merge). A TRUSTED, run-scoped channel filled by a core caller
|
|
1532
|
+
* (`createSubagentTool`'s `execute`) — NEVER a {@link TaskSpec} field (the untrusted-caller surface),
|
|
1533
|
+
* mirroring `inheritedManifestScope`/`workflowDepth`. Absent for a top-level (non-delegated) task.
|
|
1534
|
+
*/
|
|
1535
|
+
parentToolCallId?: string;
|
|
1536
|
+
/**
|
|
1537
|
+
* design/99 MF-10 / BC-2 (Service AI [§I 1.5.1]) — a SUBAGENT's human display NAME, threaded at spawn so the
|
|
1538
|
+
* child's `task_progress` ticks carry a readable label (a Fleet child row otherwise shows the raw `taskId`).
|
|
1539
|
+
* Filled by `createSubagentTool`'s `execute` = the explicit `taskName`, else the selected agent-type
|
|
1540
|
+
* (`AgentDefinition.name`). TRUSTED run-scoped channel (NOT a {@link TaskSpec} field), mirroring
|
|
1541
|
+
* `parentToolCallId`. Absent for a top-level run / a bare delegation with neither label — the child's
|
|
1542
|
+
* `task_progress` then carries NO `name` (it keeps its taskId; it deliberately does NOT fall back to the raw
|
|
1543
|
+
* objective, which could leak a delegated secret — dual-review Q2). Untrusted (`taskName` is model-chosen) →
|
|
1544
|
+
* the consumer sanitizes via `inlineUntrusted` at emit.
|
|
1545
|
+
*/
|
|
1546
|
+
agentName?: string;
|
|
1547
|
+
/**
|
|
1548
|
+
* design/147 S3 (label-vs-identity): the EXPLICIT `Agent({name})` spawn identity — set
|
|
1549
|
+
* ONLY when the spawn carried a `name` parameter, unlike {@link agentName} (a DISPLAY label:
|
|
1550
|
+
* description or agent-type, present on nearly every spawn). Teammate semantics (hierarchy clamp,
|
|
1551
|
+
* teammate addendum, uplink attribution) key on THIS field; keying on the display label made
|
|
1552
|
+
* every described child a "teammate" (clamp over-wide, addendum over-composed).
|
|
1553
|
+
*/
|
|
1554
|
+
explicitAgentName?: string;
|
|
1555
|
+
/**
|
|
1556
|
+
* design/99 (nested-subagent live tree) — the SPAWNING run's taskId, threaded at spawn (from the parent's
|
|
1557
|
+
* `ToolExecuteContext.taskId`) so this child's `task_progress` ticks carry `parentTaskId`. Lets a UI build the
|
|
1558
|
+
* live nested-agent tree directly (child.parentTaskId === parent.taskId) at any depth. TRUSTED run-scoped
|
|
1559
|
+
* channel (NOT a {@link TaskSpec} field), mirroring `parentToolCallId`. Absent for a top-level run.
|
|
1560
|
+
*/
|
|
1561
|
+
parentTaskId?: string;
|
|
1562
|
+
/** design/147 S2a — the spawning run's sessionId (paired with parentTaskId; see
|
|
1563
|
+
* ToolExecuteContext.parentSessionId). */
|
|
1564
|
+
parentSessionId?: string;
|
|
1565
|
+
/**
|
|
1566
|
+
* design/383 §2.5 — the memory-capture opt-out FLOOR: TRUE ⇔ the spawning session was under an
|
|
1567
|
+
* opt-out when this child spawned. The child then runs opted-out itself (its prepare mints its
|
|
1568
|
+
* own one-way record, reason naming the floor) — NOT re-adjudicated against the child's
|
|
1569
|
+
* entitlement (the floor is the parent's already-granted opt-out tightening the tree; a
|
|
1570
|
+
* re-adjudication that could answer "no" would be a loosening door). Trusted internals channel
|
|
1571
|
+
* on purpose (the parentInteractionPosture law): never a child-spec field, so no chosen
|
|
1572
|
+
* AgentDefinition and no spec surgery can shed it; rides `internalsSnapshot`, so retained
|
|
1573
|
+
* revivals keep it. Grandchildren inherit through each generation's own live seat (an opted-out
|
|
1574
|
+
* child forwards TRUE to its children in turn — monotone by construction).
|
|
1575
|
+
*/
|
|
1576
|
+
memoryCaptureFloor?: true;
|
|
1577
|
+
/**
|
|
1578
|
+
* design/383 §2.5 (rescan post-6.0.0-RC) — the floor's THIRD state: the spawning session's
|
|
1579
|
+
* capture state was INDETERMINATE at spawn (its record store faulted at the getter read).
|
|
1580
|
+
* Neither boolean is honest there — no floor runs a possibly-recorded lineage captured, a coined
|
|
1581
|
+
* floor mints an IRREVERSIBLE record off an unreadable state — so the lane carries the fault
|
|
1582
|
+
* itself and the child's prepare resolves it against the live record query (found ⇒ floor;
|
|
1583
|
+
* still faulting ⇒ the child runs indeterminate — mechanical restrictions, no mint; readable
|
|
1584
|
+
* and clean ⇒ clean). Same trusted-channel law as {@link memoryCaptureFloor}; mutually
|
|
1585
|
+
* exclusive with it at the spawn site (the floor wins when the state IS known).
|
|
1586
|
+
*/
|
|
1587
|
+
memoryCaptureFloorIndeterminate?: true;
|
|
1588
|
+
/** design/383 §2.5 — the spawning session's write-plane control dir (the coordinate its capture
|
|
1589
|
+
* opt-out record is keyed under), so a child on a DIFFERENT memory plane can still run the
|
|
1590
|
+
* record-query leg against the parent's own carrier. Trusted chain, rides internalsSnapshot. */
|
|
1591
|
+
memoryCaptureQueryDir?: string;
|
|
1592
|
+
/** design/383 §2.5 (codex round 3) — the ancestor chain's capture coordinates (root first, one
|
|
1593
|
+
* row per generation, appended by each spawner's ctx seat). The child's harvest closures walk
|
|
1594
|
+
* every row, so a mid-run flip ANYWHERE up the tree suppresses every in-flight descendant.
|
|
1595
|
+
* Trusted chain; rides internalsSnapshot. */
|
|
1596
|
+
memoryCaptureAncestors?: ReadonlyArray<{
|
|
1597
|
+
sessionId: string;
|
|
1598
|
+
controlDir?: string;
|
|
1599
|
+
}>;
|
|
1600
|
+
/**
|
|
1601
|
+
* The FLEET-task kind of this run, declared by the lane that spawned it and stamped onto every
|
|
1602
|
+
* `task_progress` tick the run mints (`TaskEvent`'s `taskType`). A consumer merging progress ticks
|
|
1603
|
+
* with task notifications into one ledger could otherwise only key on "it showed up on the fleet
|
|
1604
|
+
* stream, so it must be an agent" — an inference that has already produced a row for a task that
|
|
1605
|
+
* was never an agent. Same vocabulary as `TaskNotificationPayload.task_type`, so the merged ledger
|
|
1606
|
+
* has ONE type axis rather than two spellings of one.
|
|
1607
|
+
*
|
|
1608
|
+
* Set by the BACKGROUND delegation lanes (plain + fork), whose runs own a registry `a*` row, and by
|
|
1609
|
+
* both of the workflow orchestrator's spawn legs. A SYNCHRONOUS delegated child declares nothing —
|
|
1610
|
+
* it has no fleet row of any kind, so absence is a fact about the run rather than a gap in the
|
|
1611
|
+
* stamping. TRUSTED run-scoped channel (never a {@link TaskSpec} field).
|
|
1612
|
+
*/
|
|
1613
|
+
delegationTaskType?: import("../types.js").DelegationTaskType;
|
|
1614
|
+
/**
|
|
1615
|
+
* #258 — the registry row's stop-cycle generation this run executes as (fresh spawn = 1, a
|
|
1616
|
+
* revival's bumped counter), threaded by the BACKGROUND delegation lanes from the registry's own
|
|
1617
|
+
* `cycleSeq` so every `task_progress` tick the run mints carries it as `seq` (same axis as
|
|
1618
|
+
* `TaskNotificationPayload.seq` / `BackgroundChildEvent.seq`). Absent for runs with no `a*` row
|
|
1619
|
+
* (sync children, workflow agents, top-level) — same absence-is-a-fact posture as
|
|
1620
|
+
* {@link delegationTaskType} above. TRUSTED run-scoped channel (never a TaskSpec field).
|
|
1621
|
+
*/
|
|
1622
|
+
cycleSeq?: number;
|
|
1623
|
+
/** The ROOT host session of the whole delegation tree (fixed point: the
|
|
1624
|
+
* spawner passes its own `ctx.rootSessionId ?? ctx.sessionId`, so depth 1 gets the host session
|
|
1625
|
+
* and every deeper level inherits it verbatim). `parentSessionId` is the IMMEDIATE spawner —
|
|
1626
|
+
* after a restart those intermediate sessions are dead ends, and a recovery face enumerating
|
|
1627
|
+
* "everything under this host session" needs the root anchor, not an alias walk. */
|
|
1628
|
+
rootSessionId?: string;
|
|
1629
|
+
/**
|
|
1630
|
+
* design/380 O1② — the run tree's PLACEMENT root: the fixed point a target-bound env factory keys
|
|
1631
|
+
* its placement lookup on ({@link import("../remote-env.js").ExecutionEnvFactoryContext.placementRootSessionId}).
|
|
1632
|
+
* A SEPARATE axis from {@link rootSessionId} deliberately: that field means "member of this host
|
|
1633
|
+
* session's DELEGATION tree" and is consumed by the registry access/recovery faces — cascade rungs
|
|
1634
|
+
* and verification legs are intentionally NOT members of that tree (independent cold re-runs in
|
|
1635
|
+
* their own sessions), so widening `rootSessionId` to cover them would corrupt the recovery faces'
|
|
1636
|
+
* reading. This member says only "place me where this session was placed". Producers: the
|
|
1637
|
+
* orchestration entries (`runCascade` / `runWithVerification` family) after their first leg's
|
|
1638
|
+
* sessionId receipt, and — C12 — the delegation/workflow spawn chains, which re-thread a parent's
|
|
1639
|
+
* EXPLICIT value verbatim into child internals (ToolExecuteContext.placementRoot → childInternals;
|
|
1640
|
+
* workflow deps → shared internals base), so every descendant of a placed leg keeps the fixed
|
|
1641
|
+
* point; prepare's mint reads it first (`placementRoot ?? rootSessionId ?? sessionId`). Absent
|
|
1642
|
+
* everywhere else — the delegation lanes' `rootSessionId` fixed point then becomes the placement
|
|
1643
|
+
* root through the middle segment, unchanged. DURABLE since design/380 O1③ (the former C12
|
|
1644
|
+
* residual, fulfilled): the suspend mint stamps the resolved fixed point as
|
|
1645
|
+
* `CheckpointState.placementRootSessionId`, and a bare durable resume restores it into this
|
|
1646
|
+
* member (live internals win when re-supplied; a CONTRADICTING re-supply refuses pre-CAS,
|
|
1647
|
+
* `resume.placement_mismatch`). TRUSTED run-scoped channel (never a {@link TaskSpec} field).
|
|
1648
|
+
*/
|
|
1649
|
+
placementRoot?: string;
|
|
1650
|
+
/**
|
|
1651
|
+
* RB-429 — the REGISTRY SCOPE this run's own background row lives in: the domain its registry-facing
|
|
1652
|
+
* tools (TaskOutput / TaskStop / SendMessage / AgentTranscript / Monitor, and the announce listing)
|
|
1653
|
+
* must mount in to see it. Filled by the delegation tool at spawn, which is the party that CHOSE the
|
|
1654
|
+
* domain when it registered the row — `ctx.principal` when the spawning run has one, else the
|
|
1655
|
+
* delegation mount's declared `background.scope`, and on a revival the claimed row's own scope.
|
|
1656
|
+
*
|
|
1657
|
+
* Why this is not just `principal`: a deployment may declare its domain at MOUNT time and run its
|
|
1658
|
+
* tasks without `TaskSpec.principal`. The row then lands in the declared domain while the child runs
|
|
1659
|
+
* with no principal at all, and `principal ?? "default"` sends every one of its registry-facing tools
|
|
1660
|
+
* to a domain the row is not in — scope is fail-closed on both access predicates, so such a child
|
|
1661
|
+
* cannot see its own row, its siblings, or the children it spawns. Carrying the domain as its own
|
|
1662
|
+
* trusted axis keeps `principal` the design/62 IDENTITY it is: the identity also keys the durable
|
|
1663
|
+
* approval / checkpoint namespace (`checkpointScopeOf`), the runtime-capability lookup and the MCP
|
|
1664
|
+
* principal header, and a mount's registry scope has no business moving any of those (an unattended
|
|
1665
|
+
* safety park must not migrate into the caller's opt-in approval bucket — design/153 §7.4).
|
|
1666
|
+
*
|
|
1667
|
+
* TRUSTED channel (never a {@link TaskSpec} field, never a model argument), like the parentage axes
|
|
1668
|
+
* above. When a deployment sets both, they agree by construction: with a principal present the
|
|
1669
|
+
* delegation tool resolves this axis TO that principal.
|
|
1670
|
+
*/
|
|
1671
|
+
registryScope?: string;
|
|
1672
|
+
/** design/148 S1 — the spawning run's ADOPTED center artifact, threaded down the
|
|
1673
|
+
* trusted internals chain so every child in the tree composes the SAME closure (a child must
|
|
1674
|
+
* never spontaneously adopt the live candidate while its parent runs an older pin — that is the
|
|
1675
|
+
* exact mixed-version state the epoch exists to prevent). Resolved by-digest through the
|
|
1676
|
+
* verified store at child prepare; a miss is the same fail-loud `prompt.snapshot_unavailable`. */
|
|
1677
|
+
parentCenterArtifactDigest?: string;
|
|
1678
|
+
/** Publish provenance companion of {@link parentCenterArtifactDigest} (audit only). */
|
|
1679
|
+
parentCenterSourceRevision?: string;
|
|
1680
|
+
/** R2 双形轴 — parent's resolved prompt profile, inherited unless the child spec overrides. */
|
|
1681
|
+
promptProfile?: "simple" | "classic";
|
|
1682
|
+
/**
|
|
1683
|
+
* design/99 (nested-subagent live tree) — an OPT-IN, DISPLAY-ONLY event sink a deployment sets on the TOP run to
|
|
1684
|
+
* receive a subagent's live `task_progress` ticks (which otherwise stay in the child's ISOLATED stream). Threaded
|
|
1685
|
+
* recursively down the delegation tree (via `ctx.forwardEvent`), so every nested subagent's ticks bubble to the
|
|
1686
|
+
* SAME sink. The Runner's ctx wrapper forwards `task_progress` always; when the run's spec sets
|
|
1687
|
+
* `forwardSubagentEvents: true` it ALSO forwards the child's content events (`text_delta` / `text_end` (#447) /
|
|
1688
|
+
* `reasoning_delta` / `tool_start` / `tool_end` — the subagent viewing pane, carrying the same UNTRUSTED-RAW/consumer-must-redact
|
|
1689
|
+
* contract as the main stream's tool events). Either way the child stream is NEVER merged into the parent's
|
|
1690
|
+
* MODEL context (this is purely a render channel). Absent unless the deployment opted in.
|
|
1691
|
+
*/
|
|
1692
|
+
onForwardEvent?: (event: TaskEvent) => void;
|
|
1693
|
+
/**
|
|
1694
|
+
* #253 — the run's OWN top-level `status` TaskEvent stream (brain liveness: rate-limit/retry/
|
|
1695
|
+
* reconnect/circuit-open), offered to the internals holder beside the queue. The queue alone was
|
|
1696
|
+
* enough for a direct `runTask` caller (the TaskStream carries these frames), but a COMPOSITION
|
|
1697
|
+
* entry (verify/cascade) drains its inner legs' queues itself — without this seat, an inner leg's
|
|
1698
|
+
* retry disclosure died inside the gate and the wire showed a silent stall. Fed the SAME frame
|
|
1699
|
+
* object the queue receives, at the same moment; contained by the run's safe notifier (#248 form:
|
|
1700
|
+
* a throwing sink is swallowed, first failure per site disclosed, never faults the leg). Subagent
|
|
1701
|
+
* frames still ride {@link onForwardEvent} — this seat is ONLY the run's own status type.
|
|
1702
|
+
*/
|
|
1703
|
+
onStatusEvent?: (event: Extract<TaskEvent, {
|
|
1704
|
+
type: "status";
|
|
1705
|
+
}>) => void;
|
|
1706
|
+
/**
|
|
1707
|
+
* design/115 P2 core slice — trusted run-local system-injection sink. `Runner.runLocked` wires this to the
|
|
1708
|
+
* live TaskStream queue plus the current harness follow-up lane; it is not a public TaskSpec field.
|
|
1709
|
+
*/
|
|
1710
|
+
/** design/116 detach: the run-local per-tool-call detach hub. runtask creates it and exposes
|
|
1711
|
+
* `TaskStream.detach(toolCallId)`; the hands Bash tool threads `signalFor(toolCallId)` into env.exec. */
|
|
1712
|
+
detachHub?: import("../tool-detach.js").ToolDetachHub;
|
|
1713
|
+
/**
|
|
1714
|
+
* #483 rung-1 — the Runner's per-session read-file-state seats (CC parity: readFileState is
|
|
1715
|
+
* session-scoped). Always set by the Runner's own prepare call (overriding any caller value, like
|
|
1716
|
+
* the peer refs beside it); absent on a standalone prepareTask, where the transcript-replay rung
|
|
1717
|
+
* covers alone. A CACHE, never an authority — see {@link SessionReadFileStates}. Trusted internals
|
|
1718
|
+
* channel, same posture as every other field here.
|
|
1719
|
+
*/
|
|
1720
|
+
sessionReadStates?: SessionReadFileStates;
|
|
1721
|
+
onTaskNotification?: (notification: TaskNotificationPayload,
|
|
1722
|
+
/** Injection tier (design/373 — the ladder is LIVE): "next" = the running turn's next boundary
|
|
1723
|
+
* (arrival order, consecutive frames batch); "later" = the run's would-otherwise-stop seat
|
|
1724
|
+
* (never folded into work in progress); "now" = class-head + earliest natural boundary on this
|
|
1725
|
+
* lane (interrupt authority belongs to the steer face, never to notifications). Internal
|
|
1726
|
+
* producers declare their tier explicitly (§3.7 census — completion-class lanes are "next");
|
|
1727
|
+
* the parameterless default "later" serves the external verb's omitting callers only. */
|
|
1728
|
+
opts?: {
|
|
1729
|
+
priority?: import("../task-notification.js").SystemInjectionPriority;
|
|
1730
|
+
}) => void;
|
|
1731
|
+
/**
|
|
1732
|
+
* design/147 S1a — the PARENT run's notification injector (its runtask-wrapped
|
|
1733
|
+
* `injectTaskNotification`), threaded into a CHILD's internals at spawn time so the child's
|
|
1734
|
+
* SendMessage("main") uplink lands in the parent's queue at a turn boundary (the CC
|
|
1735
|
+
* "delivered automatically" parent half). DELIBERATELY a separate field from
|
|
1736
|
+
* {@link RunInternals.onTaskNotification}: that one is "inject into THIS run" (runtask wraps it
|
|
1737
|
+
* as upstream-observer + own-queue), and reusing it for the child would tee every internal child
|
|
1738
|
+
* notification (grandchild completions, monitor events) into the parent — double delivery.
|
|
1739
|
+
*/
|
|
1740
|
+
parentNotify?: (notification: TaskNotificationPayload, opts?: {
|
|
1741
|
+
priority?: import("../task-notification.js").SystemInjectionPriority;
|
|
1742
|
+
}) => void;
|
|
1743
|
+
/**
|
|
1744
|
+
* design/147 S3a — the PARENT run's subagent-retain ledger, threaded to a CHILD so its
|
|
1745
|
+
* SendMessage can continue a RETAINED SIBLING (the sibling's retain entry lives on the parent's
|
|
1746
|
+
* ledger — without this, sibling resolution succeeds but delivery always reads not-retained).
|
|
1747
|
+
* TRUSTED chain; read-only use (the sibling leg resumes through the same fenced resume face).
|
|
1748
|
+
*/
|
|
1749
|
+
parentRetainLedger?: import("../../agents/retain-ledger.js").SubagentRetainLedger;
|
|
1750
|
+
/**
|
|
1751
|
+
* design/147 S2a — hands THIS run's notification injector back to the SPAWNER once the lane is
|
|
1752
|
+
* live (runtask calls it with its wrapped `injectTaskNotification`). The spawner stores it on the
|
|
1753
|
+
* child's registry handle so a parent/sibling SendMessage can deliver TO the RUNNING child at its
|
|
1754
|
+
* next turn boundary (CC's in-memory `pendingMessages` pedestal, anchors/2.1.212
|
|
1755
|
+
* messaging-runtime.md §2.5). The injector is RUN-SCOPED but fail-safe after the run: the lane's
|
|
1756
|
+
* teardown branch parks late payloads per session (PendingSessionNotifications), which is exactly
|
|
1757
|
+
* CC's durable-mailbox posture for an idle teammate (§2.4) — no separate file mailbox needed.
|
|
1758
|
+
* TRUSTED chain (core spawner only); never reachable from TaskSpec.
|
|
1759
|
+
*/
|
|
1760
|
+
onNotifyInjectorReady?: (inject: (notification: TaskNotificationPayload, opts?: {
|
|
1761
|
+
priority?: import("../task-notification.js").SystemInjectionPriority;
|
|
1762
|
+
}) => Promise<"queued" | "parked" | "dropped_duplicate">) => void;
|
|
1763
|
+
/**
|
|
1764
|
+
* design/97 CORE-6 — per-task ISOLATION hint, a TRUSTED run-scoped channel filled ONLY by a core caller
|
|
1765
|
+
* (the workflow's `ctx.agent` when the SCRIPT passed `{ isolation: "worktree" }` as an OPTION) — NEVER a
|
|
1766
|
+
* {@link TaskSpec} field (the untrusted-caller surface, design/44 §7 Q4). Forwarded to
|
|
1767
|
+
* {@link ExecutionEnvFactory} via {@link ExecutionEnvFactoryContext.isolation} so the trusted control-plane
|
|
1768
|
+
* factory mints a git-worktree-rooted env for this agent; and it makes root resolution use the worktree
|
|
1769
|
+
* env's own cwd (the worktree dir), bypassing `deps.rootPath`. Isolate-ONLY: the runtime never merges
|
|
1770
|
+
* — the orchestrator script reads each worktree's result and decides verify/merge in userland.
|
|
1771
|
+
* FAIL-CLOSED: a request the deployment cannot honor (no factory, or an observably non-isolated env)
|
|
1772
|
+
* throws at prepare time — the child never starts; there is no silent fallback to the shared tree.
|
|
1773
|
+
*/
|
|
1774
|
+
isolation?: "worktree";
|
|
1775
|
+
/**
|
|
1776
|
+
* Sub-agent cwd inheritance (CC parity, 2026-07-03): the PARENT task's effective working root,
|
|
1777
|
+
* filled ONLY by core delegation callers (the workflow's `ctx.agent` / `createSubagentTool`'s execute —
|
|
1778
|
+
* NEVER a {@link TaskSpec} field). Forwarded to {@link ExecutionEnvFactory} via
|
|
1779
|
+
* {@link ExecutionEnvFactoryContext.parentCwd} so a single-user/TOC factory can root the child env at the
|
|
1780
|
+
* parent's cwd instead of an empty per-task sandbox. `isolation: "worktree"` wins over this when both set.
|
|
1781
|
+
*/
|
|
1782
|
+
parentCwd?: string;
|
|
1783
|
+
/**
|
|
1784
|
+
* The spawning run's file-history LINEAGE — the scope it records first-touch edits into and the
|
|
1785
|
+
* TREE those records' keys are minted against (the canonical root spelling + the filesystem
|
|
1786
|
+
* identity of {@link fileHistoryFilesystemIdentity}) — threaded VERBATIM by core delegation
|
|
1787
|
+
* callers from {@link import("../types.js").ToolExecuteContext.fileHistoryLineage} (NEVER a
|
|
1788
|
+
* {@link TaskSpec} field). {@link resolveFileHistoryScope} is the ONE reading: this run records
|
|
1789
|
+
* into the lineage's scope iff its own tree coordinates BOTH equal the lineage's, and then
|
|
1790
|
+
* re-exposes the SAME triple on its own ctx, so every same-tree descendant of a root session — at
|
|
1791
|
+
* any depth — lands in the root session's scope (the fixed point), while a descendant on another
|
|
1792
|
+
* tree (worktree isolation, explicit `cwd`, a fresh per-task sandbox) becomes the root of its own
|
|
1793
|
+
* subtree's history. Absent on a top-level run, on a run with no live history store, and on a
|
|
1794
|
+
* tier-3 revival (the reviver's lineage says nothing about the revived row's tree).
|
|
1795
|
+
*/
|
|
1796
|
+
fileHistoryLineage?: {
|
|
1797
|
+
scope: string;
|
|
1798
|
+
root: string;
|
|
1799
|
+
fs: string;
|
|
1800
|
+
};
|
|
1801
|
+
/**
|
|
1802
|
+
* [c209-D] — the EXPLICIT Agent.cwd request, distinct from the best-effort `parentCwd`
|
|
1803
|
+
* inheritance hint above: inheritance may be silently ignored by a factory (or absent without one),
|
|
1804
|
+
* but an explicit cwd the model asked for MUST either take effect or fail loud. prepareTask enforces
|
|
1805
|
+
* the contract: no `executionEnvFactory` ⇒ throw `config.cwd_unsupported`; after env creation the
|
|
1806
|
+
* env's actual cwd must canonically equal this path or the task fails `config.cwd_not_honored`; the
|
|
1807
|
+
* task root follows the env's cwd (never `deps.rootPath`) when set. Filled ONLY by core delegation
|
|
1808
|
+
* callers (ctx-injection posture, same as `parentCwd`).
|
|
1809
|
+
*/
|
|
1810
|
+
requestedCwd?: string;
|
|
1811
|
+
/**
|
|
1812
|
+
* Subagent steer verb (dogfood finding 2026-07-03, "中途插话"): the host run's opt-in
|
|
1813
|
+
* SUBAGENT-STEER-HANDLE sink. When set, `createSubagentTool` runs each child via `runTaskStream`
|
|
1814
|
+
* and emits a steer handle here (the model never sees the handle — same host-context-isolation
|
|
1815
|
+
* posture as `onWorkflowAgentSpawn`). A deployment registers it by `taskId` to route a human steer
|
|
1816
|
+
* into the running child (fenced-marker semantics matching the workflow agent handle). Threaded to
|
|
1817
|
+
* the tool ctx as {@link ToolExecuteContext.onSubagentSpawn} and recursively down the delegation
|
|
1818
|
+
* tree. Absent ⇒ children run non-steerable (prior behavior, zero overhead).
|
|
1819
|
+
* SCOPE (fable impl-review F3, recorded): only SYNC delegations emit a handle — a
|
|
1820
|
+
* `run_in_background` child does not (poll/stop it via TaskOutput/TaskStop); wiring the background
|
|
1821
|
+
* lane is a recorded follow-up, not an oversight a deployment should discover at runtime.
|
|
1822
|
+
*/
|
|
1823
|
+
onSubagentSpawn?: (handle: import("../../agents/subagent.js").SubagentSteerHandle) => void;
|
|
1824
|
+
/**
|
|
1825
|
+
* design/97 CORE-8 (③) — a TRUSTED run-scoped tool-ACTIVITY sink, filled ONLY by a core caller (the workflow's
|
|
1826
|
+
* `ctx.agent`, to render a per-agent "last N tool calls" drill-down). Called synchronously at each tool start +
|
|
1827
|
+
* end with structural data (name/phase/ids) — NEVER args/output (those carry untrusted/host data). NEVER a
|
|
1828
|
+
* {@link TaskSpec} field. Absent ⇒ no activity capture (default).
|
|
1829
|
+
*
|
|
1830
|
+
* Reaches activity on FRESH and RESUMED runs alike — the durable-resume entry (`resumeStream`) threads
|
|
1831
|
+
* `internals` too (see its parent-constraint re-supply snapshot), so a resumed leg's SUBSEQUENT tool calls
|
|
1832
|
+
* hit this sink. One real boundary remains (#249): the resume's already-approved pending call itself is
|
|
1833
|
+
* executed by `applyResumeDecision`'s own callback, outside the frame-minting harness, so THAT one call
|
|
1834
|
+
* emits no activity.
|
|
1835
|
+
*/
|
|
1836
|
+
onActivity?: (activity: ToolActivity) => void;
|
|
1837
|
+
/**
|
|
1838
|
+
* RB-393① — a TRUSTED run-scoped WORKSPACE-observation sink, filled ONLY by a core caller
|
|
1839
|
+
* (the workflow's `ctx.agent` / `ctx.agentStream`, to record an isolated agent's worktree directory on the
|
|
1840
|
+
* persisted run record). NEVER a {@link TaskSpec} field — same posture as {@link onActivity}.
|
|
1841
|
+
*
|
|
1842
|
+
* WHY the engine must hand this out: for `isolation: "worktree"` the worktree path is minted INSIDE
|
|
1843
|
+
* {@link RunnerDeps.executionEnvFactory} and lands only on the per-task env's `cwd`; no runner-outward face
|
|
1844
|
+
* (TaskEvent union / TaskStream / TaskResult) carries it back. An orchestrator therefore could not record
|
|
1845
|
+
* WHERE its isolated agent worked — the recovery path after an interrupted run had to enumerate the
|
|
1846
|
+
* worktrees directory and guess, which is exactly the contradiction recorded against the
|
|
1847
|
+
* "isolate-only, userland decides verify/merge" intent.
|
|
1848
|
+
*
|
|
1849
|
+
* Called at most ONCE per prepared run with the task's FINAL working root (see {@link ResolvedWorkspace}),
|
|
1850
|
+
* after the durable-resume restore may have re-rooted it. Observe-only: a throwing sink is swallowed (an
|
|
1851
|
+
* observation must never fault a prepare that already minted a workspace).
|
|
1852
|
+
*
|
|
1853
|
+
* Like {@link onActivity}, this seat rides `internals` on FRESH and RESUMED runs alike (`resumeStream`
|
|
1854
|
+
* threads it too) — a resumed leg's restore re-fires the observation with the settled root.
|
|
1855
|
+
*/
|
|
1856
|
+
onWorkspaceResolved?: (workspace: ResolvedWorkspace) => void;
|
|
1857
|
+
}
|
|
1858
|
+
/**
|
|
1859
|
+
* RB-393① — the payload of {@link RunInternals.onWorkspaceResolved}: the working root this task actually
|
|
1860
|
+
* runs on, plus whether that root is the task's OWN isolated workspace.
|
|
1861
|
+
*
|
|
1862
|
+
* `isolated: true` means "`cwd` is this agent's own git worktree": it is reported only when
|
|
1863
|
+
* {@link RunInternals.isolation} was requested AND the fail-closed worktree enforcement accepted the env, so a
|
|
1864
|
+
* consumer may treat the path as private to this agent (safe to diff / merge / remove in userland). A
|
|
1865
|
+
* non-isolated run reports its plain root with `isolated: false` — the observation face stays complete, while
|
|
1866
|
+
* the honest non-claim keeps a consumer from mistaking a SHARED tree for its own worktree.
|
|
1867
|
+
*/
|
|
1868
|
+
export interface ResolvedWorkspace {
|
|
1869
|
+
/** The task's effective working root — the same value every fence / LSP / prompt consumer uses. */
|
|
1870
|
+
cwd: string;
|
|
1871
|
+
/** True only for an accepted `isolation: "worktree"` request (⇒ `cwd` is this agent's own worktree). */
|
|
1872
|
+
isolated: boolean;
|
|
1873
|
+
/** True when the task's execution env is REMOTE (#197 BGW-7): `cwd` then names a path INSIDE the remote
|
|
1874
|
+
* target (a per-task sandbox on the shape-(d) exemption leg, e.g. `/workspace`), which does not exist —
|
|
1875
|
+
* or names an unrelated directory — on the host. `isolated && !remote` is the only combination under
|
|
1876
|
+
* which `cwd` is a host path a consumer may diff / merge / remove. */
|
|
1877
|
+
remote: boolean;
|
|
1878
|
+
}
|