@tangle-network/agent-runtime 0.116.0 → 0.119.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/README.md +9 -1
  2. package/dist/{activation-DuqQhee6.js → activation-DdIpwQ0k.js} +3 -3
  3. package/dist/{activation-DuqQhee6.js.map → activation-DdIpwQ0k.js.map} +1 -1
  4. package/dist/agent.d.ts +3 -64
  5. package/dist/agent.js +5 -207
  6. package/dist/agent.js.map +1 -1
  7. package/dist/{analyst-loop-BoNIG2hA.js → analyst-loop-DvSciOfB.js} +2 -2
  8. package/dist/{analyst-loop-BoNIG2hA.js.map → analyst-loop-DvSciOfB.js.map} +1 -1
  9. package/dist/analyst-loop.js +1 -1
  10. package/dist/candidate-execution/index.d.ts +3 -3
  11. package/dist/candidate-execution/index.js +5 -5
  12. package/dist/{candidate-execution-CfpJrd3o.js → candidate-execution-DDkSRPjY.js} +4 -4
  13. package/dist/{candidate-execution-CfpJrd3o.js.map → candidate-execution-DDkSRPjY.js.map} +1 -1
  14. package/dist/{environment-provider-CWsRh6Uz.d.ts → environment-provider-Bh4nX2qt.d.ts} +306 -32
  15. package/dist/{environment-provider-CCaEhA-l.js → environment-provider-Cr75N-rU.js} +141 -32
  16. package/dist/environment-provider-Cr75N-rU.js.map +1 -0
  17. package/dist/environment-provider.d.ts +1 -1
  18. package/dist/environment-provider.js +1 -1
  19. package/dist/{improvement-cycle-C1cmjvPD.js → improvement-cycle-zjR-MXwK.js} +163 -22
  20. package/dist/improvement-cycle-zjR-MXwK.js.map +1 -0
  21. package/dist/{index-C-FYUuFG.d.ts → index-B3bHsVLw.d.ts} +2 -2
  22. package/dist/{index-CYkDeM5L.d.ts → index-BLsKcxNd.d.ts} +3 -3
  23. package/dist/{index-COumPQka.d.ts → index-CGADWaa_.d.ts} +864 -437
  24. package/dist/{index-DcLMNnG5.d.ts → index-I35151Fr.d.ts} +9 -9
  25. package/dist/index.d.ts +9 -9
  26. package/dist/index.js +13 -13
  27. package/dist/index.js.map +1 -1
  28. package/dist/intelligence.d.ts +10 -9
  29. package/dist/intelligence.js +15 -9
  30. package/dist/intelligence.js.map +1 -1
  31. package/dist/kernel.d.ts +4 -4
  32. package/dist/kernel.js +9 -8
  33. package/dist/{knowledge-DOzbywZT.js → knowledge-I1e9zQGj.js} +7 -7
  34. package/dist/knowledge-I1e9zQGj.js.map +1 -0
  35. package/dist/knowledge.d.ts +1 -1
  36. package/dist/knowledge.js +1 -1
  37. package/dist/{local-harness-t6cDWDQ2.d.ts → local-harness-BIajef4A.d.ts} +29 -7
  38. package/dist/{loop-runner-bin-BFrhPLKt.d.ts → loop-runner-bin-BeG9vTdE.d.ts} +3 -3
  39. package/dist/{loop-runner-bin-BuQjc5DR.js → loop-runner-bin-L996YVPS.js} +4 -4
  40. package/dist/{loop-runner-bin-BuQjc5DR.js.map → loop-runner-bin-L996YVPS.js.map} +1 -1
  41. package/dist/loop-runner-bin.d.ts +1 -1
  42. package/dist/loop-runner-bin.js +1 -1
  43. package/dist/mcp/bin.js +43 -10
  44. package/dist/mcp/bin.js.map +1 -1
  45. package/dist/mcp/index.d.ts +5 -45
  46. package/dist/mcp/index.js +8 -212
  47. package/dist/mcp/index.js.map +1 -1
  48. package/dist/{openai-tools-_Wyp4udO.js → openai-tools-CvFEzw6f.js} +2 -2
  49. package/dist/{openai-tools-_Wyp4udO.js.map → openai-tools-CvFEzw6f.js.map} +1 -1
  50. package/dist/{prepare-BHQBb02e.js → prepare-DpV6np9e.js} +47 -34
  51. package/dist/prepare-DpV6np9e.js.map +1 -0
  52. package/dist/primeintellect/index.d.ts +1 -1
  53. package/dist/{protected-model-port-BP6Z4eau.d.ts → protected-model-port-CoJOiINX.d.ts} +11 -3
  54. package/dist/{protected-model-port-DqAH1Z2M.js → protected-model-port-vQLBRoAQ.js} +2 -2
  55. package/dist/{protected-model-port-DqAH1Z2M.js.map → protected-model-port-vQLBRoAQ.js.map} +1 -1
  56. package/dist/{redact-BEtQtvd6.d.ts → redact-PQzmE1Jn.d.ts} +4 -4
  57. package/dist/run-layout-C2FGmZ3v.js +158 -0
  58. package/dist/run-layout-C2FGmZ3v.js.map +1 -0
  59. package/dist/{runtime-Ut1pkd2n.js → runtime-D4e3GP5K.js} +147 -190
  60. package/dist/runtime-D4e3GP5K.js.map +1 -0
  61. package/dist/{sandbox-events-DeI5xX8P.js → sandbox-events-Yhd1GYWl.js} +4 -2
  62. package/dist/sandbox-events-Yhd1GYWl.js.map +1 -0
  63. package/dist/spawn-journal-IeXpidO2.js +857 -0
  64. package/dist/spawn-journal-IeXpidO2.js.map +1 -0
  65. package/dist/{structural-rollout-CVY_0hJp.js → structural-rollout-DQHO3b2Y.js} +4 -4
  66. package/dist/structural-rollout-DQHO3b2Y.js.map +1 -0
  67. package/dist/{supervise-BUR9ByF7.js → supervise-Cq50UT5-.js} +2874 -1892
  68. package/dist/supervise-Cq50UT5-.js.map +1 -0
  69. package/dist/{supervisor-BBbPBXpe.js → supervisor-CpT9yAxL.js} +2896 -983
  70. package/dist/supervisor-CpT9yAxL.js.map +1 -0
  71. package/dist/testing.js +98 -76
  72. package/dist/testing.js.map +1 -1
  73. package/dist/top-app-5unxqovu.js +1088 -0
  74. package/dist/top-app-5unxqovu.js.map +1 -0
  75. package/dist/tui/bin.d.ts +1 -0
  76. package/dist/tui/bin.js +19 -0
  77. package/dist/tui/bin.js.map +1 -0
  78. package/dist/tui/index.d.ts +209 -0
  79. package/dist/tui/index.js +2 -0
  80. package/dist/{util-Cc9g9Y-o.js → util-MVgdwuIS.js} +2 -2
  81. package/dist/{util-Cc9g9Y-o.js.map → util-MVgdwuIS.js.map} +1 -1
  82. package/dist/{workspace-archive-DXzJq7WP.js → workspace-archive-B4SkNJjw.js} +2 -2
  83. package/dist/{workspace-archive-DXzJq7WP.js.map → workspace-archive-B4SkNJjw.js.map} +1 -1
  84. package/package.json +8 -2
  85. package/dist/environment-provider-CCaEhA-l.js.map +0 -1
  86. package/dist/improvement-cycle-C1cmjvPD.js.map +0 -1
  87. package/dist/knowledge-DOzbywZT.js.map +0 -1
  88. package/dist/prepare-BHQBb02e.js.map +0 -1
  89. package/dist/runtime-Ut1pkd2n.js.map +0 -1
  90. package/dist/sandbox-events-DeI5xX8P.js.map +0 -1
  91. package/dist/spawn-journal-DCPbicXB.js +0 -457
  92. package/dist/spawn-journal-DCPbicXB.js.map +0 -1
  93. package/dist/structural-rollout-CVY_0hJp.js.map +0 -1
  94. package/dist/supervise-BUR9ByF7.js.map +0 -1
  95. package/dist/supervisor-BBbPBXpe.js.map +0 -1
@@ -1,12 +1,13 @@
1
1
  import { c as RuntimeRunStateError, u as ValidationError } from "./errors-DEAvWQPy.js";
2
- import { n as addTokenUsage, p as zeroTokenUsage } from "./util-Cc9g9Y-o.js";
3
- import { a as contentAddress, c as replaySpawnTree, o as materializeTreeView, s as pendingWaits } from "./spawn-journal-DCPbicXB.js";
2
+ import { S as detachedSnapshot, _ as captureWorkerTraceEvidence, c as replaySpawnTree, f as attestNestedDriverTreeOwner, h as runtimeOwnedNestedDriverTreeRoot, m as nestedDriverTreeRoot, o as materializeTreeView, p as driverRuntime, s as pendingWaits, x as contentAddress } from "./spawn-journal-IeXpidO2.js";
3
+ import { f as zeroTokenUsage, t as addTokenUsage } from "./util-MVgdwuIS.js";
4
4
  import { i as notifyRuntimeHookEvent } from "./runtime-hooks-C7iJOWm3.js";
5
5
  import { estimateCost, isModelPriced } from "@tangle-network/agent-eval";
6
+ import { AGENT_PROFILE_MATERIALIZATION_AXES, agentProfileSchema, canonicalCandidateDigest, profileMaterializationAxes, sha256DigestSchema } from "@tangle-network/agent-interface";
6
7
  import { createHash, randomUUID } from "node:crypto";
7
- import { applyWorkspacePlan, materializeProfile, resolveAgentProfileResources } from "@tangle-network/agent-profile-materialize";
8
+ import { applyWorkspacePlan, hashWorkspacePlan, materializeProfile, resolveAgentProfileResources } from "@tangle-network/agent-profile-materialize";
8
9
  import { access, chmod, copyFile, mkdir, mkdtemp, open, readFile, realpath, rm, symlink, writeFile } from "node:fs/promises";
9
- import { constants, createReadStream } from "node:fs";
10
+ import { appendFileSync, constants, createReadStream } from "node:fs";
10
11
  import { basename, delimiter, dirname, isAbsolute, join, resolve, sep, win32 } from "node:path";
11
12
  import { spawn } from "node:child_process";
12
13
  import { homedir, tmpdir } from "node:os";
@@ -139,20 +140,6 @@ function collectStringLeaves(value, output) {
139
140
  *
140
141
  * @experimental
141
142
  */
142
- const codexReasoningEffort = {
143
- none: "none",
144
- minimal: "minimal",
145
- low: "low",
146
- medium: "medium",
147
- high: "high",
148
- xhigh: "xhigh",
149
- ultracode: "xhigh"
150
- };
151
- function codexReasoningArgs(reasoningEffort) {
152
- const mapped = codexReasoningEffort[reasoningEffort];
153
- if (mapped === void 0) throw new Error(`harnessInvocation: unsupported Codex reasoning effort ${String(reasoningEffort)}`);
154
- return ["-c", `model_reasoning_effort="${mapped}"`];
155
- }
156
143
  /**
157
144
  * Default per-harness command + arg shape. `buildArgs` takes ONLY the task prompt and
158
145
  * emits the prompt-only invocation (no model, no system prompt) — the safe default shape
@@ -160,25 +147,94 @@ function codexReasoningArgs(reasoningEffort) {
160
147
  * the harness's selector flag (every supported harness takes `-m <model>`). The §1.5
161
148
  * profile-aware mapper `harnessInvocation` composes these to thread the full
162
149
  * supervisor-authored profile (systemPrompt + model) into argv.
150
+ *
151
+ * Every per-harness difference is a FIELD on this row, never a test on the harness name at a
152
+ * call site: adding harness N+1 is one entry here, and a caller asking for a capability the
153
+ * row does not declare is refused up front instead of silently losing the request.
163
154
  */
164
155
  const HARNESS_INVOCATIONS = {
165
- claude: {
156
+ "claude-code": {
166
157
  command: "claude",
167
158
  buildArgs: (taskPrompt) => ["-p", taskPrompt],
168
- modelArgs: (model) => ["-m", model]
159
+ modelArgs: (model) => ["-m", model],
160
+ reasoning: {
161
+ levels: {
162
+ low: "low",
163
+ medium: "medium",
164
+ high: "high",
165
+ xhigh: "xhigh",
166
+ ultracode: "max"
167
+ },
168
+ args: (level) => ["--effort", level]
169
+ },
170
+ permissionBypassArgs: () => ["--dangerously-skip-permissions"]
169
171
  },
170
172
  codex: {
171
173
  command: "codex",
172
174
  buildArgs: (taskPrompt) => ["exec", taskPrompt],
173
175
  modelArgs: (model) => ["-m", model],
174
- reasoningArgs: codexReasoningArgs
176
+ reasoning: {
177
+ levels: {
178
+ none: "none",
179
+ minimal: "minimal",
180
+ low: "low",
181
+ medium: "medium",
182
+ high: "high",
183
+ xhigh: "xhigh",
184
+ ultracode: "xhigh"
185
+ },
186
+ args: (level) => ["-c", `model_reasoning_effort="${level}"`]
187
+ },
188
+ permissionBypassArgs: () => [
189
+ "--sandbox",
190
+ "workspace-write",
191
+ "-c",
192
+ "approval_policy=\"never\""
193
+ ]
175
194
  },
176
195
  opencode: {
177
196
  command: "opencode",
178
197
  buildArgs: (taskPrompt) => ["run", taskPrompt],
179
- modelArgs: (model) => ["-m", model]
198
+ modelArgs: (model) => ["-m", model],
199
+ reasoning: {
200
+ levels: {
201
+ minimal: "minimal",
202
+ low: "low",
203
+ medium: "medium",
204
+ high: "high",
205
+ xhigh: "xhigh",
206
+ ultracode: "max"
207
+ },
208
+ args: (level) => ["--variant", level]
209
+ }
180
210
  }
181
211
  };
212
+ /** Every local harness, in table order — the one list `AGENT_RUNTIME_LOCAL_HARNESSES` and any
213
+ * other harness enumeration reads, so adding a row above is the only edit a new harness needs. */
214
+ const LOCAL_HARNESSES = Object.keys(HARNESS_INVOCATIONS);
215
+ /** The harness a caller gets when it expresses no preference. A composition-root default, not a
216
+ * capability claim: one constant so the several entry points cannot drift apart. */
217
+ const DEFAULT_LOCAL_HARNESS = "claude-code";
218
+ /** The CLI binary a harness id runs. The two are NOT the same string (`claude-code` runs `claude`),
219
+ * so anything spawning a harness — a version probe, a login check — reads it from here rather than
220
+ * passing the harness id as a command. */
221
+ function localHarnessExecutable(harness) {
222
+ return HARNESS_INVOCATIONS[harness].command;
223
+ }
224
+ /**
225
+ * Whether the harness's native control can express this reasoning effort. Admission checks read
226
+ * this so a profile the invocation would later refuse is rejected BEFORE any workspace state is
227
+ * created, against the same table that emits the argv.
228
+ */
229
+ function harnessSupportsReasoningEffort(harness, reasoningEffort) {
230
+ return HARNESS_INVOCATIONS[harness].reasoning?.levels[reasoningEffort] !== void 0;
231
+ }
232
+ function harnessReasoningArgs(harness, reasoningEffort) {
233
+ const reasoning = HARNESS_INVOCATIONS[harness].reasoning;
234
+ const level = reasoning?.levels[reasoningEffort];
235
+ if (reasoning === void 0 || level === void 0) throw new Error(`harnessInvocation: ${harness} cannot express reasoning effort ${String(reasoningEffort)}`);
236
+ return reasoning.args(level);
237
+ }
182
238
  const CODEX_REPRODUCIBLE_ARGS = [
183
239
  "--ephemeral",
184
240
  "--ignore-rules",
@@ -204,8 +260,9 @@ const CODEX_REPRODUCIBLE_ARGS = [
204
260
  "--strict-config"
205
261
  ];
206
262
  function buildHarnessArgs(harness, taskPrompt, options = {}) {
207
- const args = HARNESS_INVOCATIONS[harness].buildArgs(taskPrompt);
208
- if (harness === "claude" && options.dangerouslySkipPermissions) args.push("--dangerously-skip-permissions");
263
+ const invocation = HARNESS_INVOCATIONS[harness];
264
+ const args = invocation.buildArgs(taskPrompt);
265
+ if (options.dangerouslySkipPermissions && !options.codexReproducible) args.push(...invocation.permissionBypassArgs?.() ?? []);
209
266
  if (options.codexReproducible) {
210
267
  if (harness !== "codex") throw new Error("harnessInvocation: codexReproducible requires the Codex harness");
211
268
  args.push(...CODEX_REPRODUCIBLE_ARGS);
@@ -248,7 +305,7 @@ function harnessInvocation(harness, profile, taskPrompt, options = {}) {
248
305
  const model = profile.model?.default;
249
306
  if (typeof model === "string" && model.length > 0) args.push(...invocation.modelArgs(model));
250
307
  const reasoningEffort = profile.model?.reasoningEffort;
251
- if (reasoningEffort !== void 0 && invocation.reasoningArgs) args.push(...invocation.reasoningArgs(reasoningEffort));
308
+ if (reasoningEffort !== void 0) args.push(...harnessReasoningArgs(harness, reasoningEffort));
252
309
  return {
253
310
  command: invocation.command,
254
311
  args,
@@ -1140,6 +1197,255 @@ function naturalNumber(value, field) {
1140
1197
  return value;
1141
1198
  }
1142
1199
  //#endregion
1200
+ //#region src/agent/profile-materialization.ts
1201
+ const KNOWN_AXIS_SET = new Set(AGENT_PROFILE_MATERIALIZATION_AXES);
1202
+ /**
1203
+ * Compound AgentProfile properties and the canonical leaves they cover.
1204
+ *
1205
+ * agent-interface publishes two axis vocabularies, and this module has to sit between them:
1206
+ * `profileMaterializationAxes` emits canonical LEAVES, while `changedAgentProfileAxes` emits
1207
+ * DIFF axes, which are compound property names (`model`, `prompt`, `resources`, `identity`) for
1208
+ * everything except the already-scalar properties. A changed-axis input is therefore expanded
1209
+ * through this map, so both producers compose with this validator.
1210
+ *
1211
+ * A CONTRACT may still only name leaves. Expanding an input is safe — it asks about more, never
1212
+ * less — whereas letting a contract claim a parent is what silently swallows a dropped child.
1213
+ */
1214
+ const compoundAxisLeaves = {
1215
+ identity: [
1216
+ "name",
1217
+ "description",
1218
+ "version",
1219
+ "tags"
1220
+ ],
1221
+ prompt: ["systemPrompt", "instructions"],
1222
+ model: [
1223
+ "modelDefault",
1224
+ "modelSmall",
1225
+ "modelProvider",
1226
+ "modelReasoningEffort",
1227
+ "modelMetadata"
1228
+ ],
1229
+ resources: [
1230
+ "files",
1231
+ "resourceTools",
1232
+ "skills",
1233
+ "resourceAgents",
1234
+ "commands",
1235
+ "resourceInstructions",
1236
+ "resourceFailOnError"
1237
+ ],
1238
+ mcpConnections: ["mcp"]
1239
+ };
1240
+ /** Materialization contract for a run path that executes every canonical AgentProfile leaf. */
1241
+ const fullProfileMaterialization = defineProfileMaterializationContract({
1242
+ name: "full-profile-execution",
1243
+ axes: AGENT_PROFILE_MATERIALIZATION_AXES
1244
+ });
1245
+ /**
1246
+ * Materialization contract for an intentionally limited prompt-and-model execution path.
1247
+ * Identity, harness, and metadata are control fields consumed for naming, placement,
1248
+ * authorization, and durable attribution; they are carried without adding worker behavior.
1249
+ * Every behavioral axis other than prompt and model remains unsupported.
1250
+ */
1251
+ const promptModelProfileMaterialization = defineProfileMaterializationContract({
1252
+ name: "prompt-model-execution",
1253
+ axes: [
1254
+ "name",
1255
+ "systemPrompt",
1256
+ "instructions",
1257
+ "modelDefault",
1258
+ "harness",
1259
+ "metadata"
1260
+ ]
1261
+ });
1262
+ /**
1263
+ * Materialization contract for a local coding CLI in an isolated git worktree.
1264
+ * The shared workspace materializer carries native tools, permissions, MCP, hooks, subagents,
1265
+ * modes, and file-backed resources when the selected CLI supports their exact values.
1266
+ * `resourceFailOnError` is carried: it is the fail-closed policy the pre-worktree resource
1267
+ * RESOLUTION step (`resolveAgentProfileResources`) applies to remote profile resources. Runtime
1268
+ * placement concerns (hub connections and confidential execution), provider-native extensions,
1269
+ * and unused model hints are deliberately absent so they fail before a worktree or executor is
1270
+ * created rather than being mistaken for an effective candidate change.
1271
+ */
1272
+ const worktreeCliProfileMaterialization = defineProfileMaterializationContract({
1273
+ name: "worktree-cli-execution",
1274
+ axes: [
1275
+ "name",
1276
+ "systemPrompt",
1277
+ "instructions",
1278
+ "modelDefault",
1279
+ "modelReasoningEffort",
1280
+ "harness",
1281
+ "permissions",
1282
+ "tools",
1283
+ "mcp",
1284
+ "subagents",
1285
+ "files",
1286
+ "resourceTools",
1287
+ "skills",
1288
+ "resourceAgents",
1289
+ "commands",
1290
+ "resourceInstructions",
1291
+ "resourceFailOnError",
1292
+ "hooks",
1293
+ "modes",
1294
+ "metadata"
1295
+ ]
1296
+ });
1297
+ /** Materialization contract for a raw process path that carries only control/identity fields. */
1298
+ const controlProfileMaterialization = defineProfileMaterializationContract({
1299
+ name: "control-only-execution",
1300
+ axes: [
1301
+ "name",
1302
+ "harness",
1303
+ "metadata"
1304
+ ]
1305
+ });
1306
+ /** Materialization contract for an injected inference function whose surrounding driver still
1307
+ * applies the profile prompt, name, placement, and metadata, but not model selection. */
1308
+ const promptControlProfileMaterialization = defineProfileMaterializationContract({
1309
+ name: "prompt-control-execution",
1310
+ axes: [
1311
+ "name",
1312
+ "systemPrompt",
1313
+ "instructions",
1314
+ "harness",
1315
+ "metadata"
1316
+ ]
1317
+ });
1318
+ /**
1319
+ * Materialization contract for `createSandboxAct`.
1320
+ *
1321
+ * `createSandboxAct` hands the whole `AgentProfile` to the sandbox as `backend.profile`, so every
1322
+ * profile leaf crosses the boundary. `buildBackendOptions` resolves the runner from an explicit
1323
+ * `sandboxOverrides.backend.type`, then `profile.metadata.backendType`, then `profile.harness`,
1324
+ * so a candidate that changes only `harness` runs on the harness it declares — and one declaring
1325
+ * a harness the sandbox cannot run throws rather than running elsewhere and reporting success.
1326
+ */
1327
+ const sandboxActProfileMaterialization = defineProfileMaterializationContract({
1328
+ name: "createSandboxAct",
1329
+ axes: fullProfileMaterialization.axes
1330
+ });
1331
+ /** Materialization contract for a run path that only injects prompt text. */
1332
+ const promptOnlyProfileMaterialization = defineProfileMaterializationContract({
1333
+ name: "prompt-only-message",
1334
+ axes: ["systemPrompt", "instructions"]
1335
+ });
1336
+ /**
1337
+ * Materialization contract for a run path that injects prompt text plus inline resources.
1338
+ *
1339
+ * `resourceFailOnError` is absent: it is a resolution POLICY the attaching path would have to
1340
+ * enforce, and inlining resource content does not carry it.
1341
+ */
1342
+ const promptResourceProfileMaterialization = defineProfileMaterializationContract({
1343
+ name: "prompt-resource-attachment",
1344
+ axes: [
1345
+ "systemPrompt",
1346
+ "instructions",
1347
+ "files",
1348
+ "resourceTools",
1349
+ "skills",
1350
+ "resourceAgents",
1351
+ "commands",
1352
+ "resourceInstructions"
1353
+ ]
1354
+ });
1355
+ /** Define the profile axes a concrete run path actually carries into execution. */
1356
+ function defineProfileMaterializationContract(options) {
1357
+ const name = options.name.trim();
1358
+ if (!name) throw new ValidationError("defineProfileMaterializationContract: name is required");
1359
+ return {
1360
+ name,
1361
+ axes: normalizeContractAxes(options.axes, `${name}.axes`)
1362
+ };
1363
+ }
1364
+ /** Return every changed profile axis that the selected run path would drop. */
1365
+ function validateProfileMaterialization(options) {
1366
+ const changedAxes = normalizeChangedAxes(options.changedAxes, "changedAxes");
1367
+ const supported = new Set(normalizeContractAxes(options.contract.axes, `${options.contract.name}.axes`));
1368
+ const issues = [];
1369
+ for (const axis of changedAxes) {
1370
+ if (isAxisSupported(axis, supported)) continue;
1371
+ issues.push({
1372
+ contract: options.contract.name,
1373
+ axis,
1374
+ reason: "unsupported-axis",
1375
+ supportedAxes: [...supported]
1376
+ });
1377
+ }
1378
+ return issues;
1379
+ }
1380
+ /** Throw when a candidate changes axes the selected run path cannot carry. */
1381
+ function assertProfileMaterialization(options) {
1382
+ const issues = validateProfileMaterialization(options);
1383
+ if (issues.length === 0) return;
1384
+ throw new ValidationError(renderProfileMaterializationIssues(issues, options.context));
1385
+ }
1386
+ /** Format profile-axis drop issues into a concise operator-facing error. */
1387
+ function renderProfileMaterializationIssues(issues, context) {
1388
+ if (issues.length === 0) return "";
1389
+ const contract = issues[0]?.contract ?? "<unknown>";
1390
+ const prefix = context ? `${context}: ` : "";
1391
+ const droppedAxes = issues.map((issue) => issue.axis).join(", ");
1392
+ const supportedAxes = issues[0]?.supportedAxes.join(", ") || "<none>";
1393
+ return [
1394
+ `${prefix}profile materialization would drop axis changes on "${contract}": ${droppedAxes}.`,
1395
+ `Supported axes: ${supportedAxes}.`,
1396
+ "Use a run path that carries those AgentProfile axes, or remove them from the candidate."
1397
+ ].join("\n");
1398
+ }
1399
+ /** Contract side: leaves (or `custom:`) only. A parent claim would hide a dropped child. */
1400
+ function normalizeContractAxes(axes, label) {
1401
+ return dedupe(axes.map((raw) => assertLeafAxis(raw, label)));
1402
+ }
1403
+ /**
1404
+ * Input side: leaves, `custom:`, and compound diff axes, which expand to their leaves so
1405
+ * `changedAgentProfileAxes` output composes with a leaf-only contract.
1406
+ */
1407
+ function normalizeChangedAxes(axes, label) {
1408
+ const out = [];
1409
+ for (const raw of axes) {
1410
+ const axis = readAxisName(raw, label);
1411
+ const leaves = compoundAxisLeaves[axis];
1412
+ if (leaves && !KNOWN_AXIS_SET.has(axis)) {
1413
+ out.push(...leaves);
1414
+ continue;
1415
+ }
1416
+ out.push(assertLeafAxis(raw, label));
1417
+ }
1418
+ return dedupe(out);
1419
+ }
1420
+ function dedupe(axes) {
1421
+ const out = [];
1422
+ const seen = /* @__PURE__ */ new Set();
1423
+ for (const axis of axes) {
1424
+ if (seen.has(axis)) continue;
1425
+ seen.add(axis);
1426
+ out.push(axis);
1427
+ }
1428
+ return out;
1429
+ }
1430
+ function readAxisName(raw, label) {
1431
+ if (typeof raw !== "string") throw new ValidationError(`${label}: profile axis must be a string`);
1432
+ const axis = raw.trim();
1433
+ if (!axis) throw new ValidationError(`${label}: profile axis must be non-empty`);
1434
+ return axis;
1435
+ }
1436
+ function assertLeafAxis(raw, label) {
1437
+ const axis = readAxisName(raw, label);
1438
+ if (!KNOWN_AXIS_SET.has(axis) && !axis.startsWith("custom:")) {
1439
+ const leaves = compoundAxisLeaves[axis];
1440
+ if (leaves) throw new ValidationError(`${label}: "${axis}" is a compound AgentProfile property, not a materialization axis. Name the exact leaves this path carries: ${leaves.join(", ")}.`);
1441
+ throw new ValidationError(`${label}: unknown profile axis "${axis}". Use a known axis or custom:<name>.`);
1442
+ }
1443
+ return axis;
1444
+ }
1445
+ function isAxisSupported(axis, supported) {
1446
+ return supported.has(axis);
1447
+ }
1448
+ //#endregion
1143
1449
  //#region src/mcp/worktree.ts
1144
1450
  /**
1145
1451
  *
@@ -1327,9 +1633,6 @@ async function removeWorktree(options) {
1327
1633
  * @experimental
1328
1634
  */
1329
1635
  const defaultCheckOutputCap = 16e3;
1330
- function materializerHarness(harness) {
1331
- return harness === "claude" ? "claude-code" : harness;
1332
- }
1333
1636
  /** This harness runs public plans only — it has no secret provider, so any
1334
1637
  * templated argument or secret-ref env value is refused rather than leaked
1335
1638
  * or silently stringified. */
@@ -1355,6 +1658,11 @@ async function runWorktreeHarness(opts) {
1355
1658
  const runCommand = opts.runCommand ?? defaultRunCommand;
1356
1659
  const checkTimeoutMs = opts.checkTimeoutMs ?? opts.harnessTimeoutMs ?? 300 * 1e3;
1357
1660
  const cap = opts.checkOutputCap ?? defaultCheckOutputCap;
1661
+ assertSupportedWorktreeProfile(opts.profile, opts.harness);
1662
+ assertSafeProfileResourcePaths(opts.profile);
1663
+ const profile = await resolveAgentProfileResources(opts.profile, resourceResolutionOptions(opts.resourceResolution, opts.signal));
1664
+ opts.signal?.throwIfAborted();
1665
+ const { plan, resourceInstructions } = prepareWorktreeProfile(profile, opts.harness);
1358
1666
  const worktree = await createWorktree({
1359
1667
  repoRoot: opts.repoRoot,
1360
1668
  runId: opts.runId,
@@ -1367,19 +1675,11 @@ async function runWorktreeHarness(opts) {
1367
1675
  ...opts.runGit ? { runGit: opts.runGit } : {}
1368
1676
  });
1369
1677
  try {
1370
- assertSupportedWorktreeProfile(opts.profile, opts.harness);
1371
- assertSafeProfileResourcePaths(opts.profile);
1372
- const profile = await resolveAgentProfileResources(opts.profile, resourceResolutionOptions(opts.resourceResolution, opts.signal));
1373
- opts.signal?.throwIfAborted();
1374
- const resourceInstructions = resolveResourceInstructions(profile);
1375
- const plan = materializeProfile(materializationOnlyProfile(profile), materializerHarness(opts.harness));
1376
- if (plan.unsupported.length > 0) throw new Error(`runWorktreeHarness: profile cannot be materialized for ${opts.harness}: ${plan.unsupported.map(({ dimension, reason }) => `${dimension}: ${reason}`).join("; ")}`);
1377
- assertSafeMaterializedPaths(plan);
1378
1678
  const applied = applyWorkspacePlan(plan, worktree.path, { existingFiles: "reject" });
1379
1679
  if (applied.unsupported.length > 0) throw new Error("runWorktreeHarness: applied profile unexpectedly retained unsupported rows");
1380
1680
  const invocationProfile = profileWithResourceInstructions(profile, resourceInstructions);
1381
1681
  const { command, args } = harnessInvocation(opts.harness, invocationProfile, opts.taskPrompt, {
1382
- dangerouslySkipPermissions: opts.harness === "claude",
1682
+ dangerouslySkipPermissions: true,
1383
1683
  ...opts.codexReproducible ? { codexReproducible: true } : {}
1384
1684
  });
1385
1685
  const harnessResult = await runHarness({
@@ -1459,6 +1759,28 @@ async function runWorktreeHarness(opts) {
1459
1759
  throw err;
1460
1760
  }
1461
1761
  }
1762
+ /** Compute the same plan identity `runWorktreeHarness` consumes, with no repository or process IO. */
1763
+ function worktreeProfileExecutionPlan(profile, harness) {
1764
+ const { plan, resourceInstructions } = prepareWorktreeProfile(profile, harness);
1765
+ return Object.freeze({
1766
+ workspacePlanDigest: hashWorkspacePlan(plan),
1767
+ harness: plan.harness,
1768
+ resourceInstructions: resourceInstructionReceipt(resourceInstructions)
1769
+ });
1770
+ }
1771
+ function prepareWorktreeProfile(profile, harness) {
1772
+ const resourceInstructions = resolveResourceInstructions(profile);
1773
+ const workspaceProfile = materializationOnlyProfile(profile);
1774
+ assertSupportedWorktreeProfile(profile, harness);
1775
+ assertSafeProfileResourcePaths(profile);
1776
+ const plan = materializeProfile(workspaceProfile, harness);
1777
+ if (plan.unsupported.length > 0) throw new Error(`runWorktreeHarness: profile cannot be materialized for ${harness}: ${plan.unsupported.map(({ dimension, reason }) => `${dimension}: ${reason}`).join("; ")}`);
1778
+ assertSafeMaterializedPaths(plan);
1779
+ return {
1780
+ plan,
1781
+ resourceInstructions
1782
+ };
1783
+ }
1462
1784
  function resourceResolutionOptions(options, signal) {
1463
1785
  if (!signal) return options;
1464
1786
  const fetchResource = options?.fetch ?? globalThis.fetch;
@@ -1509,37 +1831,16 @@ function materializationOnlyProfile(profile) {
1509
1831
  };
1510
1832
  }
1511
1833
  function assertSupportedWorktreeProfile(profile, harness) {
1512
- const unsupportedAxes = [
1513
- hasEntries(profile.tools) ? "tools" : null,
1514
- hasEntries(profile.permissions) ? "permissions" : null,
1515
- profile.connections && profile.connections.length > 0 ? "connections" : null,
1516
- hasEntries(profile.confidential) ? "confidential" : null,
1517
- hasEntries(profile.modes) ? "modes" : null,
1518
- hasEntries(profile.extensions) ? "extensions" : null
1519
- ].filter((axis) => axis !== null);
1520
- if (profile.model?.reasoningEffort !== void 0 && harness !== "codex") unsupportedAxes.push("model.reasoningEffort");
1521
- for (const [name, server] of Object.entries(profile.mcp ?? {})) {
1522
- const path = `mcp[${JSON.stringify(name)}]`;
1523
- if (server.enabled === false && harness !== "opencode") unsupportedAxes.push(`${path}.enabled`);
1524
- if (hasEntries(server.headers) && harness === "codex") unsupportedAxes.push(`${path}.headers`);
1525
- if (server.cwd !== void 0 && harness === "opencode") unsupportedAxes.push(`${path}.cwd`);
1526
- }
1527
- if (harness === "claude") for (const [event, commands] of Object.entries(profile.hooks ?? {})) for (const [index, command] of commands.entries()) {
1528
- const path = `hooks[${JSON.stringify(event)}][${index}]`;
1529
- if (hasEntries(command.env)) unsupportedAxes.push(`${path}.env`);
1530
- if (command.blocking !== void 0) unsupportedAxes.push(`${path}.blocking`);
1531
- }
1532
- for (const [name, subagent] of Object.entries(profile.subagents ?? {})) {
1533
- const path = `subagents[${JSON.stringify(name)}]`;
1534
- if (hasEntries(subagent.permissions)) unsupportedAxes.push(`${path}.permissions`);
1535
- if (subagent.maxSteps !== void 0) unsupportedAxes.push(`${path}.maxSteps`);
1536
- if (hasEntries(subagent.tools) && harness !== "claude") unsupportedAxes.push(`${path}.tools`);
1537
- }
1834
+ assertProfileMaterialization({
1835
+ contract: worktreeCliProfileMaterialization,
1836
+ changedAxes: profileMaterializationAxes(profile),
1837
+ context: "runWorktreeHarness"
1838
+ });
1839
+ const unsupportedAxes = [];
1840
+ const reasoningEffort = profile.model?.reasoningEffort;
1841
+ if (reasoningEffort !== void 0 && !harnessSupportsReasoningEffort(harness, reasoningEffort)) unsupportedAxes.push("model.reasoningEffort");
1538
1842
  if (unsupportedAxes.length > 0) throw new Error(`runWorktreeHarness: profile requests unsupported worktree behavior: ${unsupportedAxes.join(", ")}`);
1539
1843
  }
1540
- function hasEntries(value) {
1541
- return value !== void 0 && Object.keys(value).length > 0;
1542
- }
1543
1844
  function assertSafeMaterializedPaths(plan) {
1544
1845
  for (const file of plan.files) if (file.relPath.split("/").some(isGitMetadataSegment)) throw new Error(`runWorktreeHarness: profile file cannot target reserved Git metadata: ${file.relPath}`);
1545
1846
  }
@@ -1551,22 +1852,26 @@ function isGitMetadataSegment(segment) {
1551
1852
  return windowsCanonical === ".git" || windowsCanonical.startsWith(".git:");
1552
1853
  }
1553
1854
  function profileMaterializationReceipt(applied, resourceInstructions) {
1554
- const instructionBytes = resourceInstructions === void 0 ? null : Buffer.from(resourceInstructions, "utf8");
1555
1855
  return {
1556
1856
  workspacePlanDigest: applied.workspacePlanDigest,
1557
1857
  writtenPaths: [...applied.written],
1558
1858
  unsupported: [...applied.unsupported],
1559
1859
  environmentNames: Object.keys(applied.env).sort(),
1560
1860
  flags: applied.flags.map(publicPlanString),
1561
- resourceInstructions: instructionBytes ? {
1562
- delivery: "invocation-prompt",
1563
- sha256: `sha256:${createHash("sha256").update(instructionBytes).digest("hex")}`,
1564
- byteLength: instructionBytes.byteLength
1565
- } : {
1566
- delivery: "none",
1567
- sha256: null,
1568
- byteLength: 0
1569
- }
1861
+ resourceInstructions: resourceInstructionReceipt(resourceInstructions)
1862
+ };
1863
+ }
1864
+ function resourceInstructionReceipt(resourceInstructions) {
1865
+ if (resourceInstructions === void 0) return {
1866
+ delivery: "none",
1867
+ sha256: null,
1868
+ byteLength: 0
1869
+ };
1870
+ const instructionBytes = Buffer.from(resourceInstructions, "utf8");
1871
+ return {
1872
+ delivery: "invocation-prompt",
1873
+ sha256: `sha256:${createHash("sha256").update(instructionBytes).digest("hex")}`,
1874
+ byteLength: instructionBytes.byteLength
1570
1875
  };
1571
1876
  }
1572
1877
  /** Run the configured test + typecheck commands in the live worktree, projecting exit codes into
@@ -1918,7 +2223,7 @@ function splitReasoning(rawContent, fieldReasoning) {
1918
2223
  */
1919
2224
  async function routerChatWithTools(cfg, messages, tools, opts) {
1920
2225
  const body = toolCompletionBody(cfg, messages, tools, opts);
1921
- const data = cfg.complete ? await cfg.complete(body) : await (async () => {
2226
+ const data = cfg.complete ? await cfg.complete(structuredClone(body)) : await (async () => {
1922
2227
  const res = await fetch(`${cfg.routerBaseUrl.replace(/\/$/, "")}/chat/completions`, {
1923
2228
  method: "POST",
1924
2229
  headers: {
@@ -2209,240 +2514,1010 @@ function routerBrain(cfg, opts = {}) {
2209
2514
  });
2210
2515
  }
2211
2516
  //#endregion
2212
- //#region src/runtime/supervise/progress.ts
2213
- /** How long a worker may produce no metered activity before a `progress()` read calls it stalled.
2214
- * Deliberately generous: a coding harness routinely spends minutes inside one tool call, and a
2215
- * false stall that provokes a steer is worse than a late one. */
2216
- const DEFAULT_STALL_AFTER_MS = 18e4;
2217
- /** Create a bounded activity ring. `limit` caps memory for a worker that runs thousands of tools. */
2218
- function createActivityLog(limit = 12) {
2219
- const notes = [];
2220
- const cap = Math.max(1, Math.floor(limit));
2221
- return {
2222
- push(note) {
2223
- notes.push(note);
2224
- if (notes.length > cap) notes.splice(0, notes.length - cap);
2225
- },
2226
- read: () => [...notes],
2227
- last: () => notes[notes.length - 1],
2228
- size: () => notes.length
2229
- };
2517
+ //#region src/runtime/supervise/materialization.ts
2518
+ const runtimeOwnedExecutorMaterializations = /* @__PURE__ */ new WeakMap();
2519
+ const runtimeOwnedScopeOwners = /* @__PURE__ */ new WeakMap();
2520
+ const declarationKeys = /* @__PURE__ */ new Set([
2521
+ "effectiveProfile",
2522
+ "backend",
2523
+ "model",
2524
+ "execution",
2525
+ "materializer",
2526
+ "plan",
2527
+ "platformAttachments"
2528
+ ]);
2529
+ /**
2530
+ * Bind a declaration to a runtime-owned executor without putting an attestable method on the
2531
+ * public Executor interface. This module is not a package export: arbitrary caller executors stay
2532
+ * unknown even if they add a lookalike `materialization()` property.
2533
+ */
2534
+ function attestRuntimeOwnedExecutor(executor, declaration, binding) {
2535
+ runtimeOwnedExecutorMaterializations.set(executor, Object.freeze({
2536
+ kind: "known",
2537
+ declaration: detachedSnapshot(declaration, "runtime-owned executor materialization"),
2538
+ ...binding === void 0 ? {} : { binding: detachedSnapshot(binding, "runtime-owned executor execution binding") }
2539
+ }));
2540
+ return executor;
2541
+ }
2542
+ /** Mark a runtime-owned orchestration executor whose exact leaf declaration is published through
2543
+ * its private nested Scope before the first backend inference. Arbitrary executors cannot opt in. */
2544
+ function attestRuntimeOwnedDeferredExecutor(executor, runtime) {
2545
+ runtimeOwnedExecutorMaterializations.set(executor, Object.freeze({
2546
+ kind: "deferred",
2547
+ runtime
2548
+ }));
2549
+ return executor;
2550
+ }
2551
+ /** Preserve the runtime-owned attestation when a trusted wrapper changes result semantics only. */
2552
+ function inheritRuntimeOwnedExecutorAttestation(source, wrapper) {
2553
+ const attestation = runtimeOwnedExecutorMaterializations.get(source);
2554
+ if (attestation !== void 0) runtimeOwnedExecutorMaterializations.set(wrapper, attestation);
2555
+ return wrapper;
2556
+ }
2557
+ /** Read a declaration only when Runtime itself branded this exact executor object. */
2558
+ function runtimeOwnedExecutorMaterialization(executor) {
2559
+ const attestation = runtimeOwnedExecutorMaterializations.get(executor);
2560
+ return attestation?.kind === "known" ? attestation.declaration : void 0;
2561
+ }
2562
+ /** Read one attempt binding only from the private brand on this exact executor object. */
2563
+ function runtimeOwnedExecutorExecutionBinding(executor) {
2564
+ const attestation = runtimeOwnedExecutorMaterializations.get(executor);
2565
+ return attestation?.kind === "known" ? attestation.binding : void 0;
2566
+ }
2567
+ /** Read the expected leaf runtime only for a privately branded deferred orchestration executor. */
2568
+ function runtimeOwnedDeferredExecutorRuntime(executor) {
2569
+ const attestation = runtimeOwnedExecutorMaterializations.get(executor);
2570
+ return attestation?.kind === "deferred" ? attestation.runtime : void 0;
2571
+ }
2572
+ /** Propagate trust from a built-in driver adapter to the Agent and recursive executor it owns. */
2573
+ function attestRuntimeOwnedScopeOwner(owner, runtime) {
2574
+ runtimeOwnedScopeOwners.set(owner, runtime);
2575
+ return owner;
2576
+ }
2577
+ /** Return a built-in scope owner's expected concrete leaf runtime; caller-owned functions/Agents
2578
+ * deliberately have no entry even if they add lookalike fields. */
2579
+ function runtimeOwnedScopeOwnerRuntime(owner) {
2580
+ return runtimeOwnedScopeOwners.get(owner);
2581
+ }
2582
+ /** Build one kernel-owned known receipt from a data-only executor declaration. */
2583
+ function knownMaterializationReceipt(input) {
2584
+ const declaration = detachedSnapshot(input.declaration, "executor materialization");
2585
+ assertExactDeclaration(declaration);
2586
+ const effectiveProfile = agentProfileSchema.safeParse(declaration.effectiveProfile);
2587
+ if (!effectiveProfile.success) throw new ValidationError("executor materialization: effectiveProfile must be an AgentProfile");
2588
+ assertNonEmpty(declaration.backend, "backend");
2589
+ assertNonEmpty(declaration.materializer, "materializer");
2590
+ assertNonEmpty(declaration.execution?.kind, "execution.kind");
2591
+ assertNonEmpty(declaration.execution?.id, "execution.id");
2592
+ assertModel(declaration.model);
2593
+ let effectiveProfileDigest;
2594
+ let materializationPlanDigest;
2595
+ let platformAttachmentsDigest;
2596
+ try {
2597
+ effectiveProfileDigest = canonicalCandidateDigest(jsonWireSnapshot(effectiveProfile.data));
2598
+ materializationPlanDigest = canonicalCandidateDigest(jsonWireSnapshot(declaration.plan));
2599
+ platformAttachmentsDigest = declaration.platformAttachments === void 0 ? void 0 : canonicalCandidateDigest(jsonWireSnapshot(declaration.platformAttachments));
2600
+ } catch (error) {
2601
+ throw new ValidationError("executor materialization: profile, plan, and attachments must be finite RFC 8785 JSON", { cause: error });
2602
+ }
2603
+ return Object.freeze({
2604
+ status: "known",
2605
+ authoredProfileDigest: input.authoredProfileDigest,
2606
+ effectiveProfileDigest,
2607
+ materializationPlanDigest,
2608
+ ...platformAttachmentsDigest === void 0 ? {} : { platformAttachmentsDigest },
2609
+ runtime: input.runtime,
2610
+ backend: declaration.backend,
2611
+ model: declaration.model,
2612
+ execution: declaration.execution,
2613
+ materializer: declaration.materializer
2614
+ });
2230
2615
  }
2231
- /** Fold the scope-derived facts and the executor's optional enrichment into one read. Pure: the
2232
- * caller supplies `now`, so a test can observe a stall without waiting for one. */
2233
- function readWorkerProgress(scope, executor, now, stallAfterMs = DEFAULT_STALL_AFTER_MS) {
2234
- const live = scope.status !== "done" && scope.status !== "failed" && scope.status !== "cancelled";
2235
- const executorActivityAt = executor?.recentActivity?.length ? executor.recentActivity[executor.recentActivity.length - 1].at : 0;
2236
- const lastActivityAt = Math.max(scope.lastActivityAt, executorActivityAt);
2237
- const idleMs = Math.max(0, now - lastActivityAt);
2238
- const note = executor?.note;
2239
- return {
2240
- id: scope.id,
2241
- status: scope.status,
2242
- live,
2243
- steerable: scope.steerable,
2244
- startedAt: scope.startedAt,
2245
- lastActivityAt,
2246
- idleMs,
2247
- stalled: live && idleMs > stallAfterMs,
2248
- stallAfterMs,
2249
- turns: executor?.turns ?? scope.turns,
2250
- tokens: scope.tokens,
2251
- ...scope.tokensKnown === false ? { tokensKnown: false } : {},
2252
- usd: scope.usd,
2253
- ...scope.usdKnown === false ? { usdKnown: false } : {},
2254
- pendingMessages: executor?.pendingMessages ?? 0,
2255
- recentActivity: executor?.recentActivity ?? [],
2256
- ...executor?.derived?.length ? { derived: executor.derived } : {},
2257
- ...note ? { note } : {}
2258
- };
2616
+ /** Build an explicit unknown receipt without fabricating any executor-owned identity. */
2617
+ function unknownMaterializationReceipt(input) {
2618
+ return Object.freeze({
2619
+ status: "unknown",
2620
+ ...input.authoredProfileDigest === void 0 ? {} : { authoredProfileDigest: input.authoredProfileDigest },
2621
+ runtime: input.runtime,
2622
+ reason: input.reason
2623
+ });
2624
+ }
2625
+ /** Mint a process-unique attempt id before executor construction. */
2626
+ function newExecutionAttemptId(nodeId) {
2627
+ return `${nodeId}:attempt:${randomUUID()}`;
2628
+ }
2629
+ /** Build the immutable safe receipt for a full, transient execution binding. */
2630
+ function knownExecutionBindingReceipt(materialization, bindingInput) {
2631
+ const binding = detachedSnapshot(bindingInput, "executor execution binding");
2632
+ assertNonEmpty(binding.attemptId, "binding.attemptId");
2633
+ assertSafeDescriptor(binding.descriptor);
2634
+ let materializationReceiptDigest;
2635
+ let bindingDigest;
2636
+ try {
2637
+ materializationReceiptDigest = canonicalCandidateDigest(materialization);
2638
+ bindingDigest = canonicalCandidateDigest(jsonWireSnapshot(binding.binding));
2639
+ } catch (error) {
2640
+ throw new ValidationError("executor binding must be finite RFC 8785 JSON", { cause: error });
2641
+ }
2642
+ return Object.freeze({
2643
+ status: "known",
2644
+ attemptId: binding.attemptId,
2645
+ materializationReceiptDigest,
2646
+ bindingDigest,
2647
+ descriptor: binding.descriptor
2648
+ });
2649
+ }
2650
+ /** Preserve an attempt boundary even when its caller-owned transport cannot be attested. */
2651
+ function unknownExecutionBindingReceipt(materialization, attemptId, reason) {
2652
+ assertNonEmpty(attemptId, "binding.attemptId");
2653
+ return Object.freeze({
2654
+ status: "unknown",
2655
+ attemptId,
2656
+ materializationReceiptDigest: canonicalCandidateDigest(materialization),
2657
+ reason
2658
+ });
2659
+ }
2660
+ /** Derive an authored-profile digest without turning non-canonical input into a fake identity. */
2661
+ function authoredProfileDigest(profile) {
2662
+ try {
2663
+ return canonicalCandidateDigest(profile);
2664
+ } catch {
2665
+ return;
2666
+ }
2667
+ }
2668
+ function assertExactDeclaration(value) {
2669
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new ValidationError("executor materialization: declaration must be an object");
2670
+ const unknown = Object.keys(value).filter((key) => !declarationKeys.has(key));
2671
+ if (unknown.length > 0) throw new ValidationError(`executor materialization: unknown declaration fields: ${unknown.sort().join(", ")}`);
2672
+ if (!Object.hasOwn(value, "plan")) throw new ValidationError("executor materialization: plan is required");
2673
+ }
2674
+ function assertModel(model) {
2675
+ if (typeof model !== "object" || model === null || Array.isArray(model)) throw new ValidationError("executor materialization: model must be a known/unknown identity");
2676
+ const keys = Object.keys(model).sort();
2677
+ if (model.status === "known") {
2678
+ if (keys.join(",") !== "id,status") throw new ValidationError("executor materialization: known model accepts only status and id");
2679
+ assertNonEmpty(model.id, "model.id");
2680
+ return;
2681
+ }
2682
+ if (model.status === "unknown") {
2683
+ if (keys.join(",") !== "reason,status") throw new ValidationError("executor materialization: unknown model accepts only status and reason");
2684
+ assertNonEmpty(model.reason, "model.reason");
2685
+ return;
2686
+ }
2687
+ throw new ValidationError("executor materialization: model.status must be known or unknown");
2688
+ }
2689
+ function assertNonEmpty(value, field) {
2690
+ if (typeof value !== "string" || value.trim().length === 0) throw new ValidationError(`executor materialization: ${field} must be a non-empty string`);
2691
+ }
2692
+ function assertSafeDescriptor(descriptor) {
2693
+ if (typeof descriptor !== "object" || descriptor === null || Array.isArray(descriptor)) throw new ValidationError("executor binding: descriptor must be an object");
2694
+ for (const [key, value] of Object.entries(descriptor)) {
2695
+ if (/(url|uri|token|key|secret|bearer|credential|authorization)/i.test(key)) throw new ValidationError(`executor binding: unsafe descriptor key ${JSON.stringify(key)}`);
2696
+ if (value !== null && typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") throw new ValidationError(`executor binding: descriptor ${JSON.stringify(key)} must be scalar`);
2697
+ if (typeof value === "string" && (value.includes("://") || value.includes("@"))) throw new ValidationError(`executor binding: descriptor ${JSON.stringify(key)} contains transport or credential text`);
2698
+ }
2699
+ }
2700
+ function jsonWireSnapshot(value, path = "$") {
2701
+ if (value === null || typeof value === "string" || typeof value === "boolean") return value;
2702
+ if (typeof value === "number") {
2703
+ if (!Number.isFinite(value)) throw new ValidationError(`executor binding: ${path} must be finite`);
2704
+ return value;
2705
+ }
2706
+ if (Array.isArray(value)) return value.map((item, index) => {
2707
+ if (item === void 0) throw new ValidationError(`executor binding: ${path}[${index}] cannot be undefined`);
2708
+ return jsonWireSnapshot(item, `${path}[${index}]`);
2709
+ });
2710
+ if (typeof value !== "object" || value === null) throw new ValidationError(`executor binding: ${path} must be JSON data`);
2711
+ const prototype = Object.getPrototypeOf(value);
2712
+ if (prototype !== Object.prototype && prototype !== null) throw new ValidationError(`executor binding: ${path} must be a plain object`);
2713
+ const out = {};
2714
+ for (const [key, item] of Object.entries(value)) {
2715
+ if (item === void 0) continue;
2716
+ out[key] = jsonWireSnapshot(item, `${path}.${key}`);
2717
+ }
2718
+ return out;
2259
2719
  }
2260
2720
  //#endregion
2261
- //#region src/runtime/supervise/wait.ts
2721
+ //#region src/runtime/supervise/budget.ts
2262
2722
  /**
2263
2723
  *
2264
- * WAIT-STATES a supervision-tree node that waits on wall-clock time or an external condition
2265
- * without holding a worker, an executor, a sandbox, or a single LLM turn.
2266
- *
2267
- * A long-horizon run spends most of its wall-clock NOT computing: waiting for CI to finish,
2268
- * for a nightly job to land, for a human to reply, for a rate-limit window to reopen. Before
2269
- * this, the only way to express that was to keep something alive and re-ask — which spends
2270
- * driver tokens per re-ask and pins a process to the wait.
2271
- *
2272
- * Two node kinds, both first-class:
2273
- * - `timer` — wake at an ABSOLUTE wall-clock instant (`untilMs`).
2274
- * - `poll` — re-run a named predicate every `intervalMs` until it returns true, or until an
2275
- * absolute `timeoutAtMs` passes (CI status, file existence, an HTTP probe, an
2276
- * inbox message).
2277
- *
2278
- * ── How this differs from `await_event`'s 15s poll fence (`DEFAULT_AWAIT_EVENT_TIMEOUT_MS`) ──
2279
- *
2280
- * They look similar and are not the same mechanism. `await_event` is an IN-RUN RENDEZVOUS: the
2281
- * driver blocks on the coordination bus for the next event from a live worker, and the 15s fence
2282
- * exists only so a remote MCP request does not exceed the client's timeout — the caller re-polls.
2283
- * Every re-poll is another driver inference turn (real tokens) against a process that must stay
2284
- * up, and nothing about that wait is recorded: kill the process and the wait is simply gone.
2724
+ * The conserved budget reservation pool the invariant the whole instrument
2725
+ * rests on (critique M5/B3). One root `Budget` becomes a conserved pool of three
2726
+ * quantities (tokens, usd, iterations) plus an absolute deadline. Children reserve
2727
+ * atomically at spawn and reconcile at settle:
2285
2728
  *
2286
- * A wait-state is a NODE, not a call. It has a node id in the tree, a journal record carrying its
2287
- * ABSOLUTE deadline, and it settles through the same `Scope.next()` cursor as any worker. Nobody
2288
- * is blocked on it — the driver can stop reasoning entirely, and the process can die. Cost while
2289
- * waiting: zero LLM calls, zero executor, zero sandbox, zero conserved budget (a wait reserves
2290
- * nothing from the pool). The only in-process residue is one timer entry.
2729
+ * total free + reserved + committed (for every known quantity)
2291
2730
  *
2292
- * ── Why probes are NAMED, not passed as closures ────────────────────────────────────────────
2731
+ * `reserve` moves a child's whole ceiling from `free` `reserved` and fails closed
2732
+ * when `free` can't cover it (never read-then-spawn overcommit, so `Σk(treatment) ≡
2733
+ * Σk(blind)` by construction). `reconcile` releases the reservation, commits ACTUAL
2734
+ * spend, and refunds the unspent remainder to `free`. Tokens and usd are separate
2735
+ * channels (`LoopTokenUsage` has no `usd`); iterations are conserved alongside them.
2293
2736
  *
2294
- * A wait must survive a process restart with its original deadline intact, and a closure cannot
2295
- * be journaled. So a `poll` names its predicate (`probe: 'ci-green'`) and the run resolves it
2296
- * through a `WaitProbeRegistry`. A brand-new process re-resolves the SAME name against its own
2297
- * registry and re-arms the wait which is what makes "kill the box, the wait keeps waiting"
2298
- * true rather than aspirational.
2737
+ * Pure and deterministic: `now()` is injected, there is no I/O, and no wall-clock or
2738
+ * RNG read. A `reserve`/`reconcile` ticket is single-use (fail-loud on double or
2739
+ * unknown reconcile) so a child can never refund twice. Reconciling an OPEN ticket always
2740
+ * closes it: a fail-loud condition settles the reservation first and throws afterwards, so no
2741
+ * error path can strand a reservation past the join barrier's `assertNoOpenTickets`.
2742
+ * A child that declared no `maxUsd` reserved no dollar allocation, so its real dollars are
2743
+ * committed as observed spend and debited from the root's balance rather than treated as an
2744
+ * overspend of a $0 ceiling it never asked for. If dollar cost is unknowable under a
2745
+ * dollar limit, reconciliation closes the known token/iteration work, marks the dollar channel
2746
+ * unusable, and refuses later reservations; inventing a numeric dollar total would be worse.
2747
+ * If a TOKEN count is unknowable (a provider that reported no usage for work that ran), the pool
2748
+ * records the work, debits what it knows, and marks `readout().tokensKnown` false — the balance is
2749
+ * then a ceiling, not a measurement. It does not close admission the way the dollar channel does:
2750
+ * tokens are always capped, so one unreported turn must not end the run.
2299
2751
  *
2300
- * Absolute instants for the same reason: `untilMs` / `timeoutAtMs` are epoch ms, not durations,
2301
- * so a resumed wait counts down from the original arm, not from the restart. `timerAt`/`pollFor`
2302
- * build them from a duration when that is what the caller has.
2752
+ * A pool can be RESTORED from a prior process's durable record (same-host restart): measured
2753
+ * committed spend is debited exactly once, and each child journaled as started but never settled
2754
+ * is charged at its full declared ceiling restart never mints capacity, and the unknown flags
2755
+ * stay visible in every later readout. The original absolute deadline never slides.
2303
2756
  *
2304
2757
  * @experimental
2305
2758
  */
2306
- /** Build a `timer` spec from a DURATION. The instant is resolved once, at arm time — a resumed
2307
- * wait re-uses the journaled instant, never a fresh `now + ms`. */
2308
- function timerAt(ms, now) {
2309
- return {
2310
- kind: "timer",
2311
- untilMs: now + ms
2759
+ /** Reject malformed ceilings before they can mint capacity through negative reservations. */
2760
+ function assertValidBudget(budget, label = "budget") {
2761
+ const safeInteger = (value, field) => {
2762
+ if (!Number.isSafeInteger(value) || value < 0) throw new Error(`${label}.${field} must be a non-negative safe integer`);
2763
+ };
2764
+ const finiteNonNegative = (value, field) => {
2765
+ if (!Number.isFinite(value) || value < 0) throw new Error(`${label}.${field} must be a non-negative finite number`);
2312
2766
  };
2767
+ safeInteger(budget.maxIterations, "maxIterations");
2768
+ safeInteger(budget.maxTokens, "maxTokens");
2769
+ if (budget.maxUsd !== void 0) finiteNonNegative(budget.maxUsd, "maxUsd");
2770
+ if (budget.deadlineMs !== void 0) finiteNonNegative(budget.deadlineMs, "deadlineMs");
2313
2771
  }
2314
- /** Build a bounded `poll` spec from a duration. */
2315
- function pollFor(probe, opts, now) {
2772
+ function assertValidSpend(spend, label) {
2773
+ if (!Number.isSafeInteger(spend.iterations) || spend.iterations < 0) throw new Error(`${label}.iterations must be a non-negative safe integer`);
2774
+ for (const [field, value] of Object.entries(spend.tokens)) if (!Number.isSafeInteger(value) || value < 0) throw new Error(`${label}.tokens.${field} must be a non-negative safe integer`);
2775
+ for (const [field, value] of [["usd", spend.usd], ["ms", spend.ms]]) if (!Number.isFinite(value) || value < 0) throw new Error(`${label}.${field} must be a non-negative finite number`);
2776
+ }
2777
+ /** Fold a normalized `UsageEvent` array into a `Spend`. Tokens and usd are separate
2778
+ * channels; iterations come from `'iteration'` events. Pure; `ms` stays zero (the
2779
+ * pool does not read wall-clock). */
2780
+ function spendFromUsageEvents(events) {
2781
+ const tokens = zeroTokenUsage();
2782
+ let usd = 0;
2783
+ let usdKnown = true;
2784
+ let iterations = 0;
2785
+ for (const ev of events) if (ev.kind === "tokens") addTokenUsage(tokens, {
2786
+ input: ev.input,
2787
+ output: ev.output
2788
+ });
2789
+ else if (ev.kind === "cost") {
2790
+ usd += ev.usd;
2791
+ if (ev.usdKnown === false) usdKnown = false;
2792
+ } else iterations += 1;
2316
2793
  return {
2317
- kind: "poll",
2318
- probe,
2319
- intervalMs: opts.intervalMs,
2320
- ...opts.timeoutMs !== void 0 ? { timeoutAtMs: now + opts.timeoutMs } : {},
2321
- ...opts.args ? { args: opts.args } : {}
2794
+ iterations,
2795
+ tokens,
2796
+ usd,
2797
+ ...usdKnown ? {} : { usdKnown: false },
2798
+ ms: 0
2322
2799
  };
2323
2800
  }
2324
- /** Registry over a plain name→predicate record. */
2325
- function createWaitProbes(entries) {
2326
- const map = new Map(Object.entries(entries));
2327
- return { resolve: (name) => map.get(name) };
2328
- }
2329
- /** Narrow a settlement's `out` to a wait outcome — a wait settles on the SAME cursor as workers,
2330
- * so a driver that mixes them tags them apart with this. */
2331
- function isWaitOutcome(value) {
2332
- return typeof value === "object" && value !== null && value.waitOutcome === true;
2333
- }
2334
- /** The absolute instant a spec is bounded by, or `undefined` for an unbounded poll. */
2335
- function waitUntil(spec) {
2336
- return spec.kind === "timer" ? spec.untilMs : spec.timeoutAtMs;
2337
- }
2338
- /** Structural validation, independent of the run. Returns null when the spec is usable. */
2339
- function validateWaitSpec(spec) {
2340
- if (spec.kind === "timer") return Number.isFinite(spec.untilMs) ? null : "timer.untilMs must be a finite epoch-ms instant";
2341
- if (typeof spec.probe !== "string" || spec.probe.length === 0) return "poll.probe must be a non-empty probe name";
2342
- if (!Number.isFinite(spec.intervalMs) || spec.intervalMs <= 0) return "poll.intervalMs must be > 0";
2343
- if (spec.timeoutAtMs !== void 0 && !Number.isFinite(spec.timeoutAtMs)) return "poll.timeoutAtMs must be a finite epoch-ms instant";
2344
- return null;
2801
+ async function foldUsage(events) {
2802
+ if (Array.isArray(events)) return spendFromUsageEvents(events);
2803
+ const tokens = zeroTokenUsage();
2804
+ let usd = 0;
2805
+ let usdKnown = true;
2806
+ let iterations = 0;
2807
+ for await (const ev of events) if (ev.kind === "tokens") addTokenUsage(tokens, {
2808
+ input: ev.input,
2809
+ output: ev.output
2810
+ });
2811
+ else if (ev.kind === "cost") {
2812
+ usd += ev.usd;
2813
+ if (ev.usdKnown === false) usdKnown = false;
2814
+ } else iterations += 1;
2815
+ return {
2816
+ iterations,
2817
+ tokens,
2818
+ usd,
2819
+ ...usdKnown ? {} : { usdKnown: false },
2820
+ ms: 0
2821
+ };
2345
2822
  }
2346
- /**
2347
- * A wait must not silently outlive the run's HARD wall-clock ceiling. When the conserved pool
2348
- * carries an absolute `deadlineMs`, a wait bounded past it — or an unbounded poll — is REFUSED at
2349
- * arm time (`deadline-exceeded`) rather than sleeping through a ceiling the caller already set.
2350
- * The alternative (extending the pool's deadline to cover the wait) would let a wait override a
2351
- * hard budget guard, which is exactly what mechanic D forbids; a run that intends week-long waits
2352
- * either sets a deadline that covers them or sets none.
2353
- */
2354
- function assertWaitWithinDeadline(spec, deadlineMs) {
2355
- if (deadlineMs <= 0) return true;
2356
- const until = waitUntil(spec);
2357
- if (until === void 0) return false;
2358
- return until <= deadlineMs;
2823
+ function totalTokens(usage) {
2824
+ return usage.input + usage.output;
2359
2825
  }
2360
2826
  /**
2361
- * Drive one wait to its resolution. Spends nothing: a `timer` is one sleep, a `poll` is a sleep
2362
- * between predicate checks. No LLM call, no executor, no box, no budget reservation.
2827
+ * Create a conserved reservation pool from a root `Budget`. `now()` is injected so the
2828
+ * deadline readout is deterministic; defaults to `Date.now` for non-test callers. The
2829
+ * absolute deadline for a fresh pool is fixed at construction (`now() + budget.deadlineMs`). A
2830
+ * restored pool instead retains `restore.absoluteDeadlineMs`, so restart never slides the original
2831
+ * wall-clock limit. The readout is an absolute instant, not a shrinking remainder.
2363
2832
  */
2364
- async function runWait(args) {
2365
- const { spec, label, armedAt, resumed, signal, now } = args;
2366
- const sleep = args.sleep ?? defaultSleep;
2367
- if (signal.aborted) return {
2368
- kind: "cancelled",
2369
- reason: "aborted before arming"
2370
- };
2371
- if (spec.kind === "timer") {
2372
- const remaining = spec.untilMs - now();
2373
- if (remaining > 0) await sleep(remaining, signal);
2374
- if (signal.aborted) return {
2375
- kind: "cancelled",
2376
- reason: "wait aborted"
2833
+ function createBudgetPool(root, now = Date.now, restore = {}) {
2834
+ assertValidBudget(root, "root budget");
2835
+ if (restore.absoluteDeadlineMs !== void 0 && (!Number.isFinite(restore.absoluteDeadlineMs) || restore.absoluteDeadlineMs < 0)) throw new Error("budget restore.absoluteDeadlineMs must be a non-negative finite number");
2836
+ let freeTokens = root.maxTokens;
2837
+ let reservedTokens = 0;
2838
+ let committedTokens = 0;
2839
+ let tokensTainted = false;
2840
+ const usdCapped = root.maxUsd !== void 0;
2841
+ let freeUsd = root.maxUsd ?? 0;
2842
+ let reservedUsd = 0;
2843
+ let committedUsd = 0;
2844
+ let usdTainted = false;
2845
+ let usdMeasured = true;
2846
+ let freeIterations = root.maxIterations;
2847
+ let reservedIterations = 0;
2848
+ let committedIterations = 0;
2849
+ const absoluteDeadlineMs = restore.absoluteDeadlineMs ?? (root.deadlineMs !== void 0 ? now() + root.deadlineMs : 0);
2850
+ let nextTicketId = 0;
2851
+ const open = /* @__PURE__ */ new Set();
2852
+ function reserve(b) {
2853
+ assertValidBudget(b, "reservation budget");
2854
+ const wantTokens = b.maxTokens;
2855
+ const wantUsd = b.maxUsd ?? 0;
2856
+ const wantIterations = b.maxIterations;
2857
+ if (usdCapped && usdTainted) return {
2858
+ ok: false,
2859
+ reason: "budget-exhausted"
2377
2860
  };
2378
- return {
2379
- kind: "woke",
2380
- outcome: outcome("timer", "fired", label, spec.untilMs, armedAt, now(), 0, 0, resumed)
2861
+ if (wantTokens > freeTokens) return {
2862
+ ok: false,
2863
+ reason: "budget-exhausted"
2381
2864
  };
2382
- }
2383
- const probe = args.probes?.resolve(spec.probe);
2384
- if (!probe) throw new ValidationError(`wait '${label}': poll probe '${spec.probe}' is not registered in this run's WaitProbeRegistry`);
2385
- let polls = 0;
2386
- let probeErrors = 0;
2387
- for (;;) {
2388
- if (signal.aborted) return {
2389
- kind: "cancelled",
2390
- reason: "wait aborted"
2865
+ if (wantIterations > freeIterations) return {
2866
+ ok: false,
2867
+ reason: "budget-exhausted"
2391
2868
  };
2392
- polls += 1;
2393
- let fired = false;
2394
- try {
2395
- fired = await probe(spec.args, signal);
2396
- } catch {
2397
- probeErrors += 1;
2869
+ if (wantUsd > 0 && !usdCapped) return {
2870
+ ok: false,
2871
+ reason: "usd-unbudgeted"
2872
+ };
2873
+ if (wantUsd > freeUsd) return {
2874
+ ok: false,
2875
+ reason: "budget-exhausted"
2876
+ };
2877
+ freeTokens -= wantTokens;
2878
+ reservedTokens += wantTokens;
2879
+ freeIterations -= wantIterations;
2880
+ reservedIterations += wantIterations;
2881
+ if (wantUsd > 0) {
2882
+ freeUsd -= wantUsd;
2883
+ reservedUsd += wantUsd;
2398
2884
  }
2399
- if (fired) return {
2400
- kind: "woke",
2401
- outcome: outcome("poll", "fired", label, spec.timeoutAtMs, armedAt, now(), polls, probeErrors, resumed)
2885
+ const id = nextTicketId++;
2886
+ open.add(id);
2887
+ return {
2888
+ ok: true,
2889
+ ticket: {
2890
+ id,
2891
+ reserved: {
2892
+ tokens: wantTokens,
2893
+ usd: wantUsd,
2894
+ iterations: wantIterations,
2895
+ usdBudgeted: b.maxUsd !== void 0
2896
+ }
2897
+ }
2402
2898
  };
2403
- if (spec.timeoutAtMs !== void 0 && now() >= spec.timeoutAtMs) return {
2404
- kind: "woke",
2405
- outcome: outcome("poll", "timeout", label, spec.timeoutAtMs, armedAt, now(), polls, probeErrors, resumed)
2899
+ }
2900
+ function reconcile(ticket, spent) {
2901
+ if (!open.has(ticket.id)) throw new Error(`budget pool: reconcile of unknown or already-settled ticket ${ticket.id}`);
2902
+ assertValidSpend(spent, `budget pool ticket ${ticket.id} spend`);
2903
+ const { tokens: rTokens, usd: rUsd, iterations: rIterations } = ticket.reserved;
2904
+ const unknownUnderCap = usdCapped && spent.usdKnown === false;
2905
+ const spentTokens = totalTokens(spent.tokens);
2906
+ const usdBudgeted = ticket.reserved.usdBudgeted !== false;
2907
+ let violation;
2908
+ if (spentTokens > rTokens) violation = `ticket ${ticket.id} spent ${spentTokens} tokens > reserved ${rTokens}`;
2909
+ else if (spent.iterations > rIterations) violation = `ticket ${ticket.id} spent ${spent.iterations} iterations > reserved ${rIterations}`;
2910
+ else if (usdCapped && usdBudgeted && spent.usd > rUsd) violation = `ticket ${ticket.id} spent $${spent.usd} > reserved $${rUsd}`;
2911
+ else if (unknownUnderCap) violation = `ticket ${ticket.id} reported unknown dollar cost under a dollar-capped budget`;
2912
+ open.delete(ticket.id);
2913
+ if (spent.tokensKnown === false) tokensTainted = true;
2914
+ if (spent.usdKnown === false) usdMeasured = false;
2915
+ reservedTokens -= rTokens;
2916
+ committedTokens += spentTokens;
2917
+ freeTokens += rTokens - spentTokens;
2918
+ reservedIterations -= rIterations;
2919
+ committedIterations += spent.iterations;
2920
+ freeIterations += rIterations - spent.iterations;
2921
+ if (usdCapped) {
2922
+ reservedUsd -= rUsd;
2923
+ committedUsd += spent.usd;
2924
+ if (unknownUnderCap) {
2925
+ usdTainted = true;
2926
+ freeUsd = 0;
2927
+ } else freeUsd += rUsd - spent.usd;
2928
+ } else committedUsd += spent.usd;
2929
+ if (violation !== void 0) throw new Error(`budget pool: ${violation}`);
2930
+ }
2931
+ function observe(spend) {
2932
+ assertValidSpend(spend, "observed spend");
2933
+ if (usdCapped && spend.usdKnown === false) throw new Error("budget pool: cannot observe unknown dollar cost under a dollar-capped budget");
2934
+ if (spend.tokensKnown === false) tokensTainted = true;
2935
+ if (spend.usdKnown === false) usdMeasured = false;
2936
+ const tokens = totalTokens(spend.tokens);
2937
+ freeTokens -= tokens;
2938
+ committedTokens += tokens;
2939
+ freeIterations -= spend.iterations;
2940
+ committedIterations += spend.iterations;
2941
+ committedUsd += spend.usd;
2942
+ if (usdCapped) freeUsd -= spend.usd;
2943
+ }
2944
+ function readout() {
2945
+ return {
2946
+ tokensLeft: freeTokens,
2947
+ tokensKnown: !tokensTainted,
2948
+ usdLeft: usdCapped ? usdTainted ? 0 : freeUsd : 0,
2949
+ usdCapped,
2950
+ usdKnown: usdMeasured && !usdTainted,
2951
+ iterationsLeft: freeIterations,
2952
+ deadlineMs: absoluteDeadlineMs,
2953
+ reservedTokens
2406
2954
  };
2407
- await sleep(spec.timeoutAtMs === void 0 ? spec.intervalMs : Math.min(spec.intervalMs, Math.max(0, spec.timeoutAtMs - now())), signal);
2408
2955
  }
2409
- }
2410
- function outcome(kind, settled, label, untilMs, armedAt, wokenAt, polls, probeErrors, resumed) {
2956
+ function assertNoOpenTickets() {
2957
+ if (open.size > 0) throw new Error(`budget pool: ${open.size} reservation(s) still open at join barrier (leaked ticket ids: ${[...open].join(", ")}) — conserved-pool invariant violated`);
2958
+ }
2959
+ if (restore.committed !== void 0) {
2960
+ assertValidSpend(restore.committed, "budget restore committed");
2961
+ const committed = restore.committed;
2962
+ if (committed.tokensKnown === false) tokensTainted = true;
2963
+ if (committed.usdKnown === false) usdMeasured = false;
2964
+ const tokens = totalTokens(committed.tokens);
2965
+ freeTokens -= tokens;
2966
+ committedTokens += tokens;
2967
+ freeIterations -= committed.iterations;
2968
+ committedIterations += committed.iterations;
2969
+ committedUsd += committed.usd;
2970
+ if (usdCapped) {
2971
+ freeUsd -= committed.usd;
2972
+ if (committed.usdKnown === false) {
2973
+ usdTainted = true;
2974
+ if (freeUsd > 0) committedUsd += freeUsd;
2975
+ freeUsd = 0;
2976
+ }
2977
+ }
2978
+ }
2979
+ for (const [index, uncertain] of (restore.uncertainReservations ?? []).entries()) {
2980
+ assertValidBudget(uncertain, `budget restore uncertainReservations[${index}]`);
2981
+ tokensTainted = true;
2982
+ usdMeasured = false;
2983
+ freeTokens -= uncertain.maxTokens;
2984
+ committedTokens += uncertain.maxTokens;
2985
+ freeIterations -= uncertain.maxIterations;
2986
+ committedIterations += uncertain.maxIterations;
2987
+ if (usdCapped) if (uncertain.maxUsd === void 0) {
2988
+ usdTainted = true;
2989
+ committedUsd += freeUsd > 0 ? freeUsd : 0;
2990
+ freeUsd = 0;
2991
+ } else {
2992
+ freeUsd -= uncertain.maxUsd;
2993
+ committedUsd += uncertain.maxUsd;
2994
+ }
2995
+ }
2411
2996
  return {
2412
- waitOutcome: true,
2413
- kind,
2414
- settled,
2415
- label,
2416
- ...untilMs !== void 0 ? { untilMs } : {},
2417
- armedAt,
2418
- wokenAt,
2419
- polls,
2420
- probeErrors,
2421
- resumed
2997
+ reserve,
2998
+ reconcile,
2999
+ spendFrom: foldUsage,
3000
+ readout,
3001
+ observe,
3002
+ assertNoOpenTickets
2422
3003
  };
2423
3004
  }
2424
- /** `setTimeout` that resolves early (and clears) when the scope aborts, so a cancelled wait
2425
- * releases the event loop immediately instead of holding the process to its deadline. */
2426
- function defaultSleep(ms, signal) {
2427
- if (ms <= 0 || signal.aborted) return Promise.resolve();
2428
- return new Promise((resolve) => {
2429
- const timer = setTimeout(() => {
2430
- signal.removeEventListener("abort", onAbort);
2431
- resolve();
2432
- }, ms);
2433
- function onAbort() {
2434
- clearTimeout(timer);
2435
- resolve();
2436
- }
2437
- signal.addEventListener("abort", onAbort, { once: true });
2438
- });
3005
+ //#endregion
3006
+ //#region src/runtime/supervise/budget-floor.ts
3007
+ const WORKER_TOKEN_FLOOR = {
3008
+ pi: 31211,
3009
+ opencode: null,
3010
+ "claude-code": null,
3011
+ "kimi-code": null,
3012
+ codex: null,
3013
+ amp: null,
3014
+ "factory-droids": null,
3015
+ hermes: null,
3016
+ forge: null,
3017
+ openclaw: null,
3018
+ nanoclaw: null,
3019
+ acp: null,
3020
+ cursor: null,
3021
+ "cli-base": null
3022
+ };
3023
+ /**
3024
+ * The floor for a spawn's harness, or `null` when there is none to enforce.
3025
+ *
3026
+ * A `null` harness is the router/inline arm: it holds only coordination verbs and re-sends no
3027
+ * harness scaffolding, so it has no floor of this kind.
3028
+ *
3029
+ * Accepts a bare `string` because `AgentProfile.harness` is not exactly `BackendType` — an
3030
+ * unrecognised name resolves to `null` (no floor) rather than throwing, which keeps an unknown
3031
+ * harness admissible instead of refusing work on a name this table has not heard of.
3032
+ */
3033
+ function workerTokenFloor(harness) {
3034
+ if (harness === null || harness === void 0) return null;
3035
+ return WORKER_TOKEN_FLOOR[harness] ?? null;
2439
3036
  }
2440
3037
  //#endregion
2441
- //#region src/sanitize.ts
2442
- /** Strip PII and large blobs from a `KnowledgeReadinessReport` for safe telemetry emission. @stable */
2443
- function sanitizeKnowledgeReadinessReport(report, options = {}) {
2444
- return {
2445
- taskId: report.taskId,
3038
+ //#region src/runtime/supervise/deadline.ts
3039
+ const MAX_TIMER_DELAY_MS = 2147483647;
3040
+ const DEFAULT_SUCCESSFUL_SHUTDOWN_MS = 5e3;
3041
+ const TEARDOWN_ACKNOWLEDGEMENT_MS = 250;
3042
+ /** Arm one wall-clock deadline without keeping the Node.js process alive. */
3043
+ function armDeadlineTimer(delayMs, onDeadline) {
3044
+ const deadlineAtMs = Date.now() + Math.max(0, delayMs);
3045
+ let cleared = false;
3046
+ let timer;
3047
+ const arm = () => {
3048
+ if (cleared) return;
3049
+ const remainingMs = Math.max(0, deadlineAtMs - Date.now());
3050
+ timer = setTimeout(() => {
3051
+ if (cleared) return;
3052
+ if (Date.now() >= deadlineAtMs) onDeadline();
3053
+ else arm();
3054
+ }, Math.min(remainingMs, MAX_TIMER_DELAY_MS));
3055
+ if (typeof timer.unref === "function") timer.unref();
3056
+ };
3057
+ arm();
3058
+ return () => {
3059
+ cleared = true;
3060
+ if (timer !== void 0) clearTimeout(timer);
3061
+ };
3062
+ }
3063
+ /**
3064
+ * Resolve a spawned child's duration into one absolute cutoff. An omitted child deadline inherits
3065
+ * the parent cutoff; an explicit child duration may shorten, but never extend, that cutoff.
3066
+ */
3067
+ function boundedChildDeadlineAt(parentDeadlineAtMs, childDeadlineMs, nowMs) {
3068
+ const parent = parentDeadlineAtMs > 0 ? parentDeadlineAtMs : void 0;
3069
+ const child = childDeadlineMs === void 0 ? void 0 : nowMs + childDeadlineMs;
3070
+ if (parent === void 0) return child;
3071
+ if (child === void 0) return parent;
3072
+ return Math.min(parent, child);
3073
+ }
3074
+ /** Invoke teardown once and bound how long the runtime waits for its acknowledgement. A hard
3075
+ * execution deadline always wins; without one, numeric grace gets a short acknowledgement
3076
+ * allowance and a brutal kill gets only that allowance. Explicit `infinity` remains unbounded
3077
+ * only when no execution deadline exists. The losing promise stays observed. */
3078
+ async function teardownExecutor(executor, grace, deadlineAtMs, now) {
3079
+ const work = Promise.resolve(executor.teardown(grace));
3080
+ const requestedWaitMs = grace === "infinity" ? void 0 : grace === "brutalKill" ? TEARDOWN_ACKNOWLEDGEMENT_MS : grace + TEARDOWN_ACKNOWLEDGEMENT_MS;
3081
+ const deadlineWaitMs = deadlineAtMs === void 0 ? void 0 : Math.max(0, deadlineAtMs - now()) + TEARDOWN_ACKNOWLEDGEMENT_MS;
3082
+ const waitMs = requestedWaitMs === void 0 ? deadlineWaitMs : deadlineWaitMs === void 0 ? requestedWaitMs : Math.min(requestedWaitMs, deadlineWaitMs);
3083
+ let receipt;
3084
+ if (waitMs === void 0) receipt = await work;
3085
+ else if (waitMs <= 0) {
3086
+ work.catch(() => void 0);
3087
+ throw new ValidationError("executor teardown did not acknowledge before its deadline");
3088
+ } else {
3089
+ let timer;
3090
+ const timedOut = new Promise((_resolve, reject) => {
3091
+ timer = setTimeout(() => reject(new ValidationError(`executor teardown did not acknowledge within ${waitMs}ms`)), waitMs);
3092
+ });
3093
+ try {
3094
+ receipt = await Promise.race([work, timedOut]);
3095
+ } finally {
3096
+ if (timer !== void 0) clearTimeout(timer);
3097
+ }
3098
+ }
3099
+ if (!receipt.destroyed) throw new ValidationError("executor teardown reported destroyed=false");
3100
+ }
3101
+ //#endregion
3102
+ //#region src/runtime/supervise/dispatch.ts
3103
+ /**
3104
+ *
3105
+ * `rollingDispatch` — the REFILLING dispatch policy over a `Scope`. It keeps `width` children in
3106
+ * flight and admits the next queued unit of work the instant one settles, instead of waiting for
3107
+ * a whole round to drain (`fanout`) or for a driver to decide again (`spawn → await → spawn`).
3108
+ *
3109
+ * The gap it closes: `fanout` opens every item at once and then drains — one round, no refill —
3110
+ * and a driver brain's manual loop opens one worker per model turn. Between those two there was
3111
+ * nothing that holds N slots full, which is why a 5-worker run can peak at 2 live workers and sit
3112
+ * ~50% idle. This is a policy over the EXISTING `Scope.spawn` / `Scope.next` primitives: it adds
3113
+ * no second admission path, so the conserved budget pool stays the only fence on total work and
3114
+ * `width` is only a fence on simultaneous work.
3115
+ *
3116
+ * Fail-closed by construction: an admission rejection (`budget-exhausted` / `depth-exceeded`) is
3117
+ * recorded and STOPS further admission — the loop then drains what is already live and returns.
3118
+ * It never retries a rejected spawn against the same pool, and it never spawns past `width`.
3119
+ *
3120
+ * ── The concurrency-cap ledger (read this before adding a fourth cap) ──────────────────────────
3121
+ *
3122
+ * Three unrelated caps bound "how much runs at once" in this stack, at three different layers.
3123
+ * They are NOT aware of each other, and the smallest one silently wins:
3124
+ *
3125
+ * 1. `SuperviseOptions.maxLiveWorkers` (`src/runtime/supervise/supervise.ts`) — supervised-tree
3126
+ * level. One shared Scope counter bounds every spawned manager and leaf in the recursive tree;
3127
+ * `spawn_agent` fails closed with `error: 'max-live-workers'` past it. Unset ⇒ NO tree cap.
3128
+ * `CoordinationToolsOptions.maxLiveWorkers` remains the local form for a toolbox mounted on a
3129
+ * caller-owned Scope that has no tree limit.
3130
+ * 2. `SandboxLineage`'s `maxConcurrency` / `DEFAULT_FORK_CONCURRENCY = 4`
3131
+ * (`src/runtime/sandbox-lineage.ts`) — kernel level. How many BOXES one `runAgentRounds` fork wave
3132
+ * provisions at once. It bounds a single leaf's fanout, not the supervisor's worker count.
3133
+ * 3. A host's own live-box governor (e.g. the kernel's `ComputeGovernor`, `maxSandboxes = 4`) — fleet
3134
+ * level. How many sandboxes may exist across the whole host process.
3135
+ *
3136
+ * The honest effective limit on simultaneous WORKERS is the minimum of the caps that apply to the
3137
+ * worker layer — (1) and (3). (2) is a different unit (boxes per fork wave inside one leaf) and
3138
+ * must not be min'd into it, or a 4-way fork inside one worker reads as a 4-worker ceiling.
3139
+ * `effectiveConcurrency` computes that minimum in one place so a host derives ONE number and
3140
+ * passes it to BOTH `maxLiveWorkers` and this dispatcher's `width`, instead of leaving a fleet
3141
+ * governor of 4 and an unset worker fence as unrelated numbers.
3142
+ *
3143
+ * ── Why this is not a copy of the kernel's batch loop ──────────────────────────────────────────
3144
+ *
3145
+ * `runBatch` (`src/runtime/run-loop.ts`) runs the same hold-N-slots-full shape over the KERNEL's
3146
+ * substrate: bare promises in a `Set`, raced with `Promise.race`, bounded by `maxConcurrency`.
3147
+ * This one runs it over the SUPERVISOR's substrate — `Scope.spawn`'s atomic reservation against
3148
+ * the conserved pool, `Scope.next`'s journaled settlement cursor, and the blob store behind each
3149
+ * result. Neither can be expressed in the other's terms without dragging its whole substrate
3150
+ * along: the kernel loop has no budget to reserve and no cursor to order settlements by, and this
3151
+ * one cannot race raw promises because a settlement is only real once it is journaled. The shape
3152
+ * repeating across the two deliberate layers is resonance, not duplication — do not "unify" them
3153
+ * into a shared helper that would have to know about both.
3154
+ *
3155
+ * @experimental
3156
+ */
3157
+ /**
3158
+ * Run the refilling dispatch loop over `scope` until the queue is dry (or a stop fires) and every
3159
+ * admitted child has settled. Returns the settlements in cursor order plus the admission ledger.
3160
+ *
3161
+ * The loop is: fill free slots from `nextUnit` → `await scope.next()` → deliver the settlement →
3162
+ * refill → repeat. Because the refill happens immediately after each settlement rather than after
3163
+ * a whole round, a slow child never idles the other slots.
3164
+ */
3165
+ async function rollingDispatch(scope, opts) {
3166
+ if (!Number.isInteger(opts.width) || opts.width < 1) throw new ValidationError(`rollingDispatch: width must be a positive integer, got ${String(opts.width)}`);
3167
+ const settled = [];
3168
+ const rejected = [];
3169
+ const live = /* @__PURE__ */ new Set();
3170
+ let admitted = 0;
3171
+ let peakLive = 0;
3172
+ let queueDry = false;
3173
+ let stopReason = "drained";
3174
+ const fill = async () => {
3175
+ while (live.size < opts.width) {
3176
+ if (queueDry) return;
3177
+ if (scope.signal.aborted) {
3178
+ stopReason = "aborted";
3179
+ queueDry = true;
3180
+ return;
3181
+ }
3182
+ if (opts.shouldStop?.() === true) {
3183
+ stopReason = "stopped";
3184
+ queueDry = true;
3185
+ return;
3186
+ }
3187
+ const unit = await opts.nextUnit();
3188
+ if (unit === void 0) {
3189
+ queueDry = true;
3190
+ return;
3191
+ }
3192
+ const res = scope.spawn(unit.agent, unit.task, unit.opts);
3193
+ if (!res.ok) {
3194
+ rejected.push(`${unit.opts.label}: ${res.reason}`);
3195
+ stopReason = "not-admitted";
3196
+ queueDry = true;
3197
+ return;
3198
+ }
3199
+ live.add(res.handle.id);
3200
+ admitted += 1;
3201
+ if (live.size > peakLive) peakLive = live.size;
3202
+ }
3203
+ };
3204
+ await fill();
3205
+ while (live.size > 0) {
3206
+ const s = await scope.next();
3207
+ if (s === null) break;
3208
+ live.delete(s.handle.id);
3209
+ settled.push(s);
3210
+ await opts.onSettled?.(s);
3211
+ await fill();
3212
+ }
3213
+ return {
3214
+ settled,
3215
+ admitted,
3216
+ rejected,
3217
+ stopReason,
3218
+ peakLive
3219
+ };
3220
+ }
3221
+ /**
3222
+ * Free worker slots under a simultaneity cap: `cap - live`, floored at 0, or `null` when there is
3223
+ * no cap (the conserved pool is then the only fence and "free slots" is not a finite number).
3224
+ * The one place the answer is computed, so the driver-facing tool payload and a dispatcher agree.
3225
+ */
3226
+ function freeSlots(liveCount, cap) {
3227
+ if (cap === void 0 || cap <= 0) return null;
3228
+ return Math.max(0, cap - liveCount);
3229
+ }
3230
+ /**
3231
+ * The ONE honest effective limit on simultaneous workers: the minimum of the caps that actually
3232
+ * bound the worker layer. Ignores unset/non-positive caps; returns `undefined` when no cap applies
3233
+ * (uncapped — the conserved pool remains the only fence).
3234
+ *
3235
+ * Deliberately does NOT fold in `SandboxLineage`'s fork concurrency: that bounds boxes inside ONE
3236
+ * leaf's fork wave, a different unit. Folding it in would report a 4-worker ceiling for what is
3237
+ * really a 4-box fanout inside a single worker.
3238
+ *
3239
+ * Use it once, at the top of a run, and pass the result to BOTH `maxLiveWorkers` and a
3240
+ * dispatcher's `width` — that is what turns three unrelated numbers into one.
3241
+ */
3242
+ function effectiveConcurrency(caps) {
3243
+ const applicable = [caps.maxLiveWorkers, caps.maxSandboxes].filter((c) => typeof c === "number" && c > 0);
3244
+ if (applicable.length === 0) return void 0;
3245
+ return Math.min(...applicable);
3246
+ }
3247
+ /** Convenience: a `DispatchUnit` factory over a fixed array of tasks, for the common case where
3248
+ * the queue is known up front and only the refill behavior is wanted. */
3249
+ function queueOf(units, budget) {
3250
+ let i = 0;
3251
+ return () => {
3252
+ const u = units[i];
3253
+ if (u === void 0) return void 0;
3254
+ i += 1;
3255
+ return {
3256
+ agent: u.agent,
3257
+ task: u.task,
3258
+ opts: {
3259
+ budget,
3260
+ label: u.label
3261
+ }
3262
+ };
3263
+ };
3264
+ }
3265
+ //#endregion
3266
+ //#region src/runtime/supervise/progress.ts
3267
+ /** How long a worker may produce no metered activity before a `progress()` read calls it stalled.
3268
+ * Deliberately generous: a coding harness routinely spends minutes inside one tool call, and a
3269
+ * false stall that provokes a steer is worse than a late one. */
3270
+ const DEFAULT_STALL_AFTER_MS = 18e4;
3271
+ /** A short, already-truncated descriptor of a tool call's target — the file/command a driver
3272
+ * needs to tell "editing the right module" from "re-reading the same file for the fifth time",
3273
+ * without pulling the worker's transcript across the wire. Shared by every executor that fills an
3274
+ * `ActivityLog` (pi RPC, sandbox session parts, cli-bridge tool-call deltas) so one harness's
3275
+ * argument naming never reads differently from another's in the same driver's window. */
3276
+ function describeToolArgs(argsValue) {
3277
+ if (!argsValue || typeof argsValue !== "object") return void 0;
3278
+ const a = argsValue;
3279
+ for (const key of [
3280
+ "filePath",
3281
+ "file_path",
3282
+ "path",
3283
+ "file",
3284
+ "command",
3285
+ "cmd",
3286
+ "pattern"
3287
+ ]) {
3288
+ const v = a[key];
3289
+ if (typeof v === "string" && v.length > 0) return v.length > 120 ? `${v.slice(0, 117)}...` : v;
3290
+ }
3291
+ }
3292
+ /** Create a bounded activity ring. `limit` caps memory for a worker that runs thousands of tools. */
3293
+ function createActivityLog(limit = 12) {
3294
+ const notes = [];
3295
+ const cap = Math.max(1, Math.floor(limit));
3296
+ return {
3297
+ push(note) {
3298
+ notes.push(note);
3299
+ if (notes.length > cap) notes.splice(0, notes.length - cap);
3300
+ },
3301
+ read: () => [...notes],
3302
+ last: () => notes[notes.length - 1],
3303
+ size: () => notes.length
3304
+ };
3305
+ }
3306
+ /** Fold the scope-derived facts and the executor's optional enrichment into one read. Pure: the
3307
+ * caller supplies `now`, so a test can observe a stall without waiting for one. */
3308
+ function readWorkerProgress(scope, executor, now, stallAfterMs = DEFAULT_STALL_AFTER_MS) {
3309
+ const live = scope.status !== "done" && scope.status !== "failed" && scope.status !== "cancelled";
3310
+ const executorActivityAt = executor?.recentActivity?.length ? executor.recentActivity[executor.recentActivity.length - 1].at : 0;
3311
+ const lastActivityAt = Math.max(scope.lastActivityAt, executorActivityAt);
3312
+ const idleMs = Math.max(0, now - lastActivityAt);
3313
+ const note = executor?.note;
3314
+ return {
3315
+ id: scope.id,
3316
+ status: scope.status,
3317
+ live,
3318
+ steerable: scope.steerable,
3319
+ startedAt: scope.startedAt,
3320
+ lastActivityAt,
3321
+ idleMs,
3322
+ stalled: live && idleMs > stallAfterMs,
3323
+ stallAfterMs,
3324
+ turns: executor?.turns ?? scope.turns,
3325
+ tokens: scope.tokens,
3326
+ ...scope.tokensKnown === false ? { tokensKnown: false } : {},
3327
+ usd: scope.usd,
3328
+ ...scope.usdKnown === false ? { usdKnown: false } : {},
3329
+ pendingMessages: executor?.pendingMessages ?? 0,
3330
+ recentActivity: executor?.recentActivity ?? [],
3331
+ ...executor?.derived?.length ? { derived: executor.derived } : {},
3332
+ ...note ? { note } : {}
3333
+ };
3334
+ }
3335
+ //#endregion
3336
+ //#region src/runtime/supervise/wait.ts
3337
+ /**
3338
+ *
3339
+ * WAIT-STATES — a supervision-tree node that waits on wall-clock time or an external condition
3340
+ * without holding a worker, an executor, a sandbox, or a single LLM turn.
3341
+ *
3342
+ * A long-horizon run spends most of its wall-clock NOT computing: waiting for CI to finish,
3343
+ * for a nightly job to land, for a human to reply, for a rate-limit window to reopen. Before
3344
+ * this, the only way to express that was to keep something alive and re-ask — which spends
3345
+ * driver tokens per re-ask and pins a process to the wait.
3346
+ *
3347
+ * Two node kinds, both first-class:
3348
+ * - `timer` — wake at an ABSOLUTE wall-clock instant (`untilMs`).
3349
+ * - `poll` — re-run a named predicate every `intervalMs` until it returns true, or until an
3350
+ * absolute `timeoutAtMs` passes (CI status, file existence, an HTTP probe, an
3351
+ * inbox message).
3352
+ *
3353
+ * ── How this differs from `await_event`'s 15s poll fence (`DEFAULT_AWAIT_EVENT_TIMEOUT_MS`) ──
3354
+ *
3355
+ * They look similar and are not the same mechanism. `await_event` is an IN-RUN RENDEZVOUS: the
3356
+ * driver blocks on the coordination bus for the next event from a live worker, and the 15s fence
3357
+ * exists only so a remote MCP request does not exceed the client's timeout — the caller re-polls.
3358
+ * Every re-poll is another driver inference turn (real tokens) against a process that must stay
3359
+ * up, and nothing about that wait is recorded: kill the process and the wait is simply gone.
3360
+ *
3361
+ * A wait-state is a NODE, not a call. It has a node id in the tree, a journal record carrying its
3362
+ * ABSOLUTE deadline, and it settles through the same `Scope.next()` cursor as any worker. Nobody
3363
+ * is blocked on it — the driver can stop reasoning entirely, and the process can die. Cost while
3364
+ * waiting: zero LLM calls, zero executor, zero sandbox, zero conserved budget (a wait reserves
3365
+ * nothing from the pool). The only in-process residue is one timer entry.
3366
+ *
3367
+ * ── Why probes are NAMED, not passed as closures ────────────────────────────────────────────
3368
+ *
3369
+ * A wait must survive a process restart with its original deadline intact, and a closure cannot
3370
+ * be journaled. So a `poll` names its predicate (`probe: 'ci-green'`) and the run resolves it
3371
+ * through a `WaitProbeRegistry`. A brand-new process re-resolves the SAME name against its own
3372
+ * registry and re-arms the wait — which is what makes "kill the box, the wait keeps waiting"
3373
+ * true rather than aspirational.
3374
+ *
3375
+ * Absolute instants for the same reason: `untilMs` / `timeoutAtMs` are epoch ms, not durations,
3376
+ * so a resumed wait counts down from the original arm, not from the restart. `timerAt`/`pollFor`
3377
+ * build them from a duration when that is what the caller has.
3378
+ *
3379
+ * @experimental
3380
+ */
3381
+ /** Build a `timer` spec from a DURATION. The instant is resolved once, at arm time — a resumed
3382
+ * wait re-uses the journaled instant, never a fresh `now + ms`. */
3383
+ function timerAt(ms, now) {
3384
+ return {
3385
+ kind: "timer",
3386
+ untilMs: now + ms
3387
+ };
3388
+ }
3389
+ /** Build a bounded `poll` spec from a duration. */
3390
+ function pollFor(probe, opts, now) {
3391
+ return {
3392
+ kind: "poll",
3393
+ probe,
3394
+ intervalMs: opts.intervalMs,
3395
+ ...opts.timeoutMs !== void 0 ? { timeoutAtMs: now + opts.timeoutMs } : {},
3396
+ ...opts.args ? { args: opts.args } : {}
3397
+ };
3398
+ }
3399
+ /** Registry over a plain name→predicate record. */
3400
+ function createWaitProbes(entries) {
3401
+ const map = new Map(Object.entries(entries));
3402
+ return { resolve: (name) => map.get(name) };
3403
+ }
3404
+ /** Narrow a settlement's `out` to a wait outcome — a wait settles on the SAME cursor as workers,
3405
+ * so a driver that mixes them tags them apart with this. */
3406
+ function isWaitOutcome(value) {
3407
+ return typeof value === "object" && value !== null && value.waitOutcome === true;
3408
+ }
3409
+ /** The absolute instant a spec is bounded by, or `undefined` for an unbounded poll. */
3410
+ function waitUntil(spec) {
3411
+ return spec.kind === "timer" ? spec.untilMs : spec.timeoutAtMs;
3412
+ }
3413
+ /** Structural validation, independent of the run. Returns null when the spec is usable. */
3414
+ function validateWaitSpec(spec) {
3415
+ if (spec.kind === "timer") return Number.isFinite(spec.untilMs) ? null : "timer.untilMs must be a finite epoch-ms instant";
3416
+ if (typeof spec.probe !== "string" || spec.probe.length === 0) return "poll.probe must be a non-empty probe name";
3417
+ if (!Number.isFinite(spec.intervalMs) || spec.intervalMs <= 0) return "poll.intervalMs must be > 0";
3418
+ if (spec.timeoutAtMs !== void 0 && !Number.isFinite(spec.timeoutAtMs)) return "poll.timeoutAtMs must be a finite epoch-ms instant";
3419
+ return null;
3420
+ }
3421
+ /**
3422
+ * A wait must not silently outlive the run's HARD wall-clock ceiling. When the conserved pool
3423
+ * carries an absolute `deadlineMs`, a wait bounded past it — or an unbounded poll — is REFUSED at
3424
+ * arm time (`deadline-exceeded`) rather than sleeping through a ceiling the caller already set.
3425
+ * The alternative (extending the pool's deadline to cover the wait) would let a wait override a
3426
+ * hard budget guard, which is exactly what mechanic D forbids; a run that intends week-long waits
3427
+ * either sets a deadline that covers them or sets none.
3428
+ */
3429
+ function assertWaitWithinDeadline(spec, deadlineMs) {
3430
+ if (deadlineMs <= 0) return true;
3431
+ const until = waitUntil(spec);
3432
+ if (until === void 0) return false;
3433
+ return until <= deadlineMs;
3434
+ }
3435
+ /**
3436
+ * Drive one wait to its resolution. Spends nothing: a `timer` is one sleep, a `poll` is a sleep
3437
+ * between predicate checks. No LLM call, no executor, no box, no budget reservation.
3438
+ */
3439
+ async function runWait(args) {
3440
+ const { spec, label, armedAt, resumed, signal, now } = args;
3441
+ const sleep = args.sleep ?? defaultSleep;
3442
+ if (signal.aborted) return {
3443
+ kind: "cancelled",
3444
+ reason: "aborted before arming"
3445
+ };
3446
+ if (spec.kind === "timer") {
3447
+ const remaining = spec.untilMs - now();
3448
+ if (remaining > 0) await sleep(remaining, signal);
3449
+ if (signal.aborted) return {
3450
+ kind: "cancelled",
3451
+ reason: "wait aborted"
3452
+ };
3453
+ return {
3454
+ kind: "woke",
3455
+ outcome: outcome("timer", "fired", label, spec.untilMs, armedAt, now(), 0, 0, resumed)
3456
+ };
3457
+ }
3458
+ const probe = args.probes?.resolve(spec.probe);
3459
+ if (!probe) throw new ValidationError(`wait '${label}': poll probe '${spec.probe}' is not registered in this run's WaitProbeRegistry`);
3460
+ let polls = 0;
3461
+ let probeErrors = 0;
3462
+ for (;;) {
3463
+ if (signal.aborted) return {
3464
+ kind: "cancelled",
3465
+ reason: "wait aborted"
3466
+ };
3467
+ polls += 1;
3468
+ let fired = false;
3469
+ try {
3470
+ fired = await probe(spec.args, signal);
3471
+ } catch {
3472
+ probeErrors += 1;
3473
+ }
3474
+ if (fired) return {
3475
+ kind: "woke",
3476
+ outcome: outcome("poll", "fired", label, spec.timeoutAtMs, armedAt, now(), polls, probeErrors, resumed)
3477
+ };
3478
+ if (spec.timeoutAtMs !== void 0 && now() >= spec.timeoutAtMs) return {
3479
+ kind: "woke",
3480
+ outcome: outcome("poll", "timeout", label, spec.timeoutAtMs, armedAt, now(), polls, probeErrors, resumed)
3481
+ };
3482
+ await sleep(spec.timeoutAtMs === void 0 ? spec.intervalMs : Math.min(spec.intervalMs, Math.max(0, spec.timeoutAtMs - now())), signal);
3483
+ }
3484
+ }
3485
+ function outcome(kind, settled, label, untilMs, armedAt, wokenAt, polls, probeErrors, resumed) {
3486
+ return {
3487
+ waitOutcome: true,
3488
+ kind,
3489
+ settled,
3490
+ label,
3491
+ ...untilMs !== void 0 ? { untilMs } : {},
3492
+ armedAt,
3493
+ wokenAt,
3494
+ polls,
3495
+ probeErrors,
3496
+ resumed
3497
+ };
3498
+ }
3499
+ /** `setTimeout` that resolves early (and clears) when the scope aborts, so a cancelled wait
3500
+ * releases the event loop immediately instead of holding the process to its deadline. */
3501
+ function defaultSleep(ms, signal) {
3502
+ if (ms <= 0 || signal.aborted) return Promise.resolve();
3503
+ return new Promise((resolve) => {
3504
+ const timer = setTimeout(() => {
3505
+ signal.removeEventListener("abort", onAbort);
3506
+ resolve();
3507
+ }, ms);
3508
+ function onAbort() {
3509
+ clearTimeout(timer);
3510
+ resolve();
3511
+ }
3512
+ signal.addEventListener("abort", onAbort, { once: true });
3513
+ });
3514
+ }
3515
+ //#endregion
3516
+ //#region src/sanitize.ts
3517
+ /** Strip PII and large blobs from a `KnowledgeReadinessReport` for safe telemetry emission. @stable */
3518
+ function sanitizeKnowledgeReadinessReport(report, options = {}) {
3519
+ return {
3520
+ taskId: report.taskId,
2446
3521
  readinessScore: report.readinessScore,
2447
3522
  recommendedAction: report.recommendedAction,
2448
3523
  severity: report.severity,
@@ -2830,6 +3905,94 @@ const GEN_AI = {
2830
3905
  outputTokens: "gen_ai.usage.output_tokens"
2831
3906
  };
2832
3907
  /**
3908
+ * Create an exporter that APPENDS spans to a local OpenInference-JSONL file, one complete span per
3909
+ * line, instead of posting them to a collector.
3910
+ *
3911
+ * Why this exists beside {@link createOtelExporter}: that one needs an OTLP endpoint, so a run on a
3912
+ * laptop, in CI, or inside a sandbox with no collector emits nothing and its per-turn shape is
3913
+ * simply lost. The journal records the TREE (who spawned whom, what settled, what it spent); it
3914
+ * does not record what happened inside a turn. A run whose tree is readable but whose turns are not
3915
+ * is exactly the state that made an observability gap invisible until someone went looking.
3916
+ *
3917
+ * The line shape is the one `@tangle-network/traces` reads (`spans.otlp.jsonl`) and is a standard
3918
+ * OpenInference representation, so the same file feeds any OpenInference tool with no conversion:
3919
+ * snake_case identity fields, ISO-8601 times, `parent_span_id` empty at the root, and attributes as
3920
+ * a plain object rather than OTLP's key/value array.
3921
+ *
3922
+ * Appends synchronously per span so a killed process keeps every span it had already finished —
3923
+ * matching the spawn journal's durability posture, since a trace that only survives a clean exit is
3924
+ * useless for the runs you most want to look at.
3925
+ */
3926
+ function createOpenInferenceFileExporter(filePath) {
3927
+ const lines = [];
3928
+ let failed;
3929
+ const append = (line) => {
3930
+ try {
3931
+ appendFileSync(filePath, `${line}\n`, "utf8");
3932
+ } catch (error) {
3933
+ failed ??= error instanceof Error ? error : new Error(String(error));
3934
+ }
3935
+ };
3936
+ return {
3937
+ exportSpan(span) {
3938
+ append(JSON.stringify(toOpenInferenceLine(span)));
3939
+ lines.push("");
3940
+ },
3941
+ async flush() {
3942
+ if (failed) throw new Error(`OpenInference file exporter failed writing ${filePath}: ${failed.message}`);
3943
+ },
3944
+ async shutdown() {
3945
+ lines.length = 0;
3946
+ }
3947
+ };
3948
+ }
3949
+ /** OTLP's numeric status codes in the NAMES OpenInference requires. */
3950
+ const OTEL_STATUS_NAME = {
3951
+ 0: "UNSET",
3952
+ 1: "OK",
3953
+ 2: "ERROR"
3954
+ };
3955
+ /** Nanosecond string -> ISO-8601, the time format an OpenInference reader parses with `Date.parse`. */
3956
+ function nanosToIso(nanos) {
3957
+ const ms = Number(BigInt(nanos) / 1000000n);
3958
+ return new Date(ms).toISOString();
3959
+ }
3960
+ /** OTLP's key/value attribute array -> the plain object OpenInference carries. */
3961
+ function attributesToObject(attributes) {
3962
+ const out = {};
3963
+ for (const a of attributes ?? []) {
3964
+ const v = a.value;
3965
+ out[a.key] = v.stringValue ?? (v.intValue !== void 0 ? Number(v.intValue) : void 0) ?? v.doubleValue ?? v.boolValue;
3966
+ }
3967
+ return out;
3968
+ }
3969
+ /** One `OtelSpan` as the OpenInference JSONL line `traces` and other OpenInference tools read. */
3970
+ function toOpenInferenceLine(span) {
3971
+ const attributes = attributesToObject(span.attributes);
3972
+ const kind = attributes["openinference.span.kind"];
3973
+ const resource = {};
3974
+ for (const key of ["service.name", "agent.name"]) if (attributes[key] !== void 0) resource[key] = attributes[key];
3975
+ return {
3976
+ trace_id: span.traceId,
3977
+ span_id: span.spanId,
3978
+ parent_span_id: span.parentSpanId ?? "",
3979
+ name: span.name,
3980
+ kind: typeof kind === "string" ? kind : "CHAIN",
3981
+ start_time: nanosToIso(span.startTimeUnixNano),
3982
+ end_time: nanosToIso(span.endTimeUnixNano),
3983
+ status: {
3984
+ code: OTEL_STATUS_NAME[span.status?.code ?? 0] ?? "UNSET",
3985
+ ...span.status?.message === void 0 ? {} : { message: span.status.message }
3986
+ },
3987
+ resource: { attributes: resource },
3988
+ scope: {
3989
+ name: "agent-runtime",
3990
+ version: ""
3991
+ },
3992
+ attributes
3993
+ };
3994
+ }
3995
+ /**
2833
3996
  * Create an OTEL exporter. Returns undefined when no endpoint is configured.
2834
3997
  */
2835
3998
  function createOtelExporter(config) {
@@ -3380,7 +4543,7 @@ function generateTraceId() {
3380
4543
  * PRECEDENCE, from lowest to highest:
3381
4544
  * 1. the supervisor process's own `process.env` (ambient inheritance),
3382
4545
  * 2. the trace context stamped here,
3383
- * 3. the caller's own seam env (`PiSeam.env`, `CliSeam.env`).
4546
+ * 3. the caller's own seam env (`CliSeam.env`).
3384
4547
  * A caller who sets `TRACE_ID` / `PARENT_SPAN_ID` on a seam wins — theirs is a deliberate
3385
4548
  * declaration about the worker. Ambient `process.env` does NOT win: when the supervisor process was
3386
4549
  * itself launched as someone's worker, its inherited ids describe the SUPERVISOR's place in an
@@ -3391,7 +4554,6 @@ function generateTraceId() {
3391
4554
  *
3392
4555
  * WHICH BACKENDS PROPAGATE. Only a backend with a real environment channel to the worker can carry
3393
4556
  * this, and the ones that cannot say so here rather than dropping it silently:
3394
- * - `pi` YES — `PiSeam.env` → the `pi --mode rpc` subprocess.
3395
4557
  * - `cli` YES — `CliSeam.env` → the spawned subprocess.
3396
4558
  * - `sandbox` YES — `CreateSandboxOptions.env` on the box the worker runs in (single-shot
3397
4559
  * and steerable). This is the cross-MACHINE case the feature exists for.
@@ -3464,30 +4626,38 @@ function workerTraceEnv(ctx) {
3464
4626
  /**
3465
4627
  * The recursion seam key. A `Scope` seeds a value of this on each child's
3466
4628
  * `ExecutorContext.seams` so a child whose executor is a DRIVER can mount a NESTED `Scope`
3467
- * over the SAME conserved pool at `depth+1`. A leaf executor never reads it. Single-sourced
4629
+ * over the driver's reserved child allocation at `depth+1`. A leaf executor never reads it. Single-sourced
3468
4630
  * here so the scope and the driver-executor agree on the seam without a circular import.
3469
4631
  */
3470
4632
  const nestedScopeSeamKey = "nested-scope";
3471
- function makeNestedScopeSeam(args, childNodeId) {
4633
+ function makeNestedScopeSeam(args, liveWorkerCapacity, childNodeId, childBudget, childDeadlineAtMs, deferredOwner) {
4634
+ const now = args.now ?? Date.now;
3472
4635
  return {
4636
+ nodeId: childNodeId,
3473
4637
  depth: args.depth,
3474
4638
  ...args.maxDepth !== void 0 ? { maxDepth: args.maxDepth } : {},
3475
4639
  journalRoot: args.root,
3476
4640
  mount(nestedRoot, signal) {
4641
+ const deadlineMs = childDeadlineAtMs === void 0 ? void 0 : Math.max(0, childDeadlineAtMs - now());
3477
4642
  return createScope({
3478
4643
  parentId: childNodeId,
3479
4644
  root: nestedRoot,
3480
- pool: args.pool,
4645
+ pool: createBudgetPool({
4646
+ ...childBudget,
4647
+ ...deadlineMs !== void 0 ? { deadlineMs } : {}
4648
+ }, now),
3481
4649
  journal: args.journal,
3482
4650
  blobs: args.blobs,
3483
4651
  executors: args.executors,
3484
4652
  seams: args.seams,
3485
4653
  depth: args.depth + 1,
3486
4654
  ...args.maxDepth !== void 0 ? { maxDepth: args.maxDepth } : {},
4655
+ liveWorkerCapacity,
3487
4656
  signal,
3488
4657
  ...args.now ? { now: args.now } : {},
3489
4658
  ...args.hooks ? { hooks: args.hooks } : {},
3490
- ...args.workerTrace ? { workerTrace: args.workerTrace } : {}
4659
+ ...args.workerTrace ? { workerTrace: args.workerTrace } : {},
4660
+ ...deferredOwner.ownerMaterialization === void 0 ? {} : { ownerMaterialization: deferredOwner.ownerMaterialization }
3491
4661
  });
3492
4662
  }
3493
4663
  };
@@ -3495,6 +4665,10 @@ function makeNestedScopeSeam(args, childNodeId) {
3495
4665
  /** Create the reactive `Scope` a driver's `Agent.act` runs inside: spawn children on an atomically reserved conserved budget, settle via the `next()` cursor, journal for replay. */
3496
4666
  function createScope(args) {
3497
4667
  const children = /* @__PURE__ */ new Map();
4668
+ const liveWorkerCapacity = args.liveWorkerCapacity ?? {
4669
+ max: normalizeLiveWorkerLimit(args.maxLiveWorkers),
4670
+ live: 0
4671
+ };
3498
4672
  let spawnOrdinal = args.resumeFrom ? args.resumeFrom.maxSpawnOrdinal + 1 : 0;
3499
4673
  let cursorSeq = args.resumeFrom ? args.resumeFrom.maxCursorSeq + 1 : 0;
3500
4674
  let waitOrdinal = args.resumeFrom ? args.resumeFrom.maxWaitOrdinal + 1 : 0;
@@ -3502,38 +4676,78 @@ function createScope(args) {
3502
4676
  const now = args.now ?? Date.now;
3503
4677
  const unclaimedWaits = new Map((args.resumeFrom?.waits ?? []).map((w) => [w.label, w]));
3504
4678
  const keyed = /* @__PURE__ */ new Map();
3505
- for (const [key, prior] of args.resumeFrom?.keys ?? []) if (prior.state === "completed" && prior.settled?.kind === "done") keyed.set(key, {
3506
- state: "done",
3507
- id: prior.id,
3508
- settled: prior.settled
3509
- });
3510
- else if (prior.state === "down" && prior.settled?.kind === "down") keyed.set(key, {
3511
- state: "down",
3512
- id: prior.id,
3513
- reason: prior.settled.reason
3514
- });
3515
- else keyed.set(key, {
4679
+ for (const [key, prior] of args.resumeFrom?.keys ?? []) if (prior.state === "completed" && prior.settled?.kind === "done") {
4680
+ if (prior.identity === void 0) continue;
4681
+ keyed.set(key, {
4682
+ state: "done",
4683
+ id: prior.id,
4684
+ identity: prior.identity,
4685
+ settled: prior.settled
4686
+ });
4687
+ } else if (prior.state === "down" && prior.settled?.kind === "down") {
4688
+ if (prior.identity === void 0) continue;
4689
+ keyed.set(key, {
4690
+ state: "down",
4691
+ id: prior.id,
4692
+ identity: prior.identity,
4693
+ reason: prior.settled.reason
4694
+ });
4695
+ } else if (prior.identity !== void 0) keyed.set(key, {
3516
4696
  state: "in-doubt",
3517
- id: prior.id
4697
+ id: prior.id,
4698
+ identity: prior.identity
3518
4699
  });
3519
4700
  /** Fold a keyed child's terminal settlement back into the registry, so a later spawn with the
3520
4701
  * same key resolves to it (done → committed result; down → explicit retry). */
3521
4702
  const recordKeyedSettlement = (key, settled) => {
4703
+ const identity = settled.handle.identity;
4704
+ if (identity === void 0) throw new ValidationError(`scope: keyed settlement '${key}' lost its execution identity`);
3522
4705
  if (settled.kind === "done") keyed.set(key, {
3523
4706
  state: "done",
3524
4707
  id: settled.handle.id,
4708
+ identity,
3525
4709
  settled
3526
4710
  });
3527
4711
  else keyed.set(key, {
3528
4712
  state: "down",
3529
4713
  id: settled.handle.id,
4714
+ identity,
3530
4715
  reason: settled.reason
3531
4716
  });
3532
4717
  };
3533
- function spawn(agent, task, opts) {
4718
+ function spawn(agentOrFactory, rawTask, rawOpts) {
4719
+ if (args.signal.aborted) return {
4720
+ ok: false,
4721
+ reason: "scope-aborted"
4722
+ };
4723
+ const task = detachedSnapshot(rawTask, "scope.spawn task");
4724
+ const opts = detachedSnapshot(rawOpts, "scope.spawn options");
3534
4725
  let prior;
4726
+ let prepared;
4727
+ const prepare = () => {
4728
+ const agent = typeof agentOrFactory === "function" ? agentOrFactory() : agentOrFactory;
4729
+ const rawSpec = agent.executorSpec;
4730
+ if (!isAgentSpec(rawSpec)) throw new ValidationError(`scope.spawn: agent "${agent.name}" exposes no \`executorSpec\` (AgentSpec) to resolve a Executor`);
4731
+ const spec = snapshotAgentSpec(rawSpec);
4732
+ return {
4733
+ agent,
4734
+ spec,
4735
+ identity: deriveNodeExecutionIdentity(spec, task)
4736
+ };
4737
+ };
3535
4738
  if (opts.key !== void 0) {
3536
4739
  const existing = keyed.get(opts.key);
4740
+ if (existing !== void 0) {
4741
+ prepared = prepare();
4742
+ if (!isCompleteIdentity(prepared.identity)) return {
4743
+ ok: false,
4744
+ reason: "invalid-identity"
4745
+ };
4746
+ if (!sameNodeExecutionIdentity(existing.identity, prepared.identity)) return {
4747
+ ok: false,
4748
+ reason: "key-conflict"
4749
+ };
4750
+ }
3537
4751
  if (existing?.state === "live") return {
3538
4752
  ok: false,
3539
4753
  reason: "duplicate-key"
@@ -3560,74 +4774,203 @@ function createScope(args) {
3560
4774
  ok: false,
3561
4775
  reason: "depth-exceeded"
3562
4776
  };
3563
- const spec = agent.executorSpec;
3564
- if (!isAgentSpec(spec)) throw new ValidationError(`scope.spawn: agent "${agent.name}" exposes no \`executorSpec\` (AgentSpec) to resolve a Executor`);
3565
- const resolved = args.executors.resolve(spec);
3566
- if (!resolved.succeeded) throw new ValidationError(`scope.spawn: ${resolved.error}`);
3567
- const reservation = args.pool.reserve(opts.budget);
3568
- if (!reservation.ok) return {
4777
+ const permit = acquireLiveWorker(liveWorkerCapacity);
4778
+ if (!permit.ok) return {
3569
4779
  ok: false,
3570
- reason: reservation.reason
4780
+ reason: "max-live-workers"
3571
4781
  };
4782
+ let reservation;
4783
+ try {
4784
+ reservation = args.pool.reserve(opts.budget);
4785
+ } catch (error) {
4786
+ permit.release();
4787
+ throw error;
4788
+ }
4789
+ if (!reservation.ok) {
4790
+ permit.release();
4791
+ return {
4792
+ ok: false,
4793
+ reason: reservation.reason
4794
+ };
4795
+ }
4796
+ let spec;
4797
+ let resolved;
4798
+ let identity;
4799
+ try {
4800
+ prepared ??= prepare();
4801
+ spec = prepared.spec;
4802
+ identity = prepared.identity;
4803
+ if (opts.key !== void 0 && !isCompleteIdentity(identity)) {
4804
+ args.pool.reconcile(reservation.ticket, zeroSpend$1());
4805
+ permit.release();
4806
+ return {
4807
+ ok: false,
4808
+ reason: "invalid-identity"
4809
+ };
4810
+ }
4811
+ const floor = workerTokenFloor(spec.harness ?? spec.profile.harness ?? null);
4812
+ if (floor !== null && opts.budget.maxTokens < floor) {
4813
+ args.pool.reconcile(reservation.ticket, zeroSpend$1());
4814
+ permit.release();
4815
+ return {
4816
+ ok: false,
4817
+ reason: "below-runtime-floor"
4818
+ };
4819
+ }
4820
+ const outcome = args.executors.resolve(spec);
4821
+ if (!outcome.succeeded) throw new ValidationError(`scope.spawn: ${outcome.error}`);
4822
+ resolved = outcome;
4823
+ } catch (error) {
4824
+ args.pool.reconcile(reservation.ticket, zeroSpend$1());
4825
+ permit.release();
4826
+ throw error;
4827
+ }
4828
+ let cascadeAbort;
4829
+ let clearChildDeadline;
3572
4830
  try {
3573
4831
  const ordinal = spawnOrdinal++;
3574
4832
  const id = `${args.parentId}:s${ordinal}`;
3575
- const childAbort = new AbortController();
3576
- const cascadeAbort = () => childAbort.abort();
3577
- if (args.signal.aborted) childAbort.abort();
4833
+ const attemptId = newExecutionAttemptId(id);
4834
+ const startedAt = now();
4835
+ const childDeadlineAtMs = boundedChildDeadlineAt(args.pool.readout().deadlineMs, opts.budget.deadlineMs, startedAt);
4836
+ const controller = new AbortController();
4837
+ cascadeAbort = () => controller.abort(args.signal.reason);
4838
+ if (args.signal.aborted) controller.abort(args.signal.reason);
3578
4839
  else args.signal.addEventListener("abort", cascadeAbort, { once: true });
4840
+ if (childDeadlineAtMs !== void 0) clearChildDeadline = armDeadlineTimer(Math.max(0, childDeadlineAtMs - now()), () => controller.abort("child deadline exceeded"));
4841
+ const deferredOwner = {};
3579
4842
  const workerTrace = args.workerTrace?.(args.parentId);
3580
4843
  const ctx = {
3581
- signal: childAbort.signal,
4844
+ signal: controller.signal,
4845
+ node: {
4846
+ rootId: args.root,
4847
+ parentId: args.parentId,
4848
+ nodeId: id,
4849
+ attemptId,
4850
+ ...identity ? { identity } : {}
4851
+ },
3582
4852
  seams: {
3583
4853
  ...args.seams,
3584
- [nestedScopeSeamKey]: makeNestedScopeSeam(args, id),
4854
+ [nestedScopeSeamKey]: makeNestedScopeSeam(args, liveWorkerCapacity, id, opts.budget, childDeadlineAtMs, deferredOwner),
3585
4855
  ...workerTrace ? { [workerTraceSeamKey]: workerTrace } : {}
3586
4856
  }
3587
4857
  };
3588
4858
  const executor = resolved.value(spec, ctx);
4859
+ const ownedTreeRoot = runtimeOwnedNestedDriverTreeRoot(executor, args.root, id);
3589
4860
  const handle = {
3590
4861
  id,
3591
4862
  label: opts.label,
4863
+ ...opts.assignmentId === void 0 ? {} : { assignmentId: opts.assignmentId },
4864
+ ...identity ? { identity } : {},
3592
4865
  get status() {
3593
4866
  return children.get(id)?.status ?? "cancelled";
3594
4867
  },
4868
+ get materialization() {
4869
+ return children.get(id)?.materialization;
4870
+ },
4871
+ get executionBindings() {
4872
+ const bindings = children.get(id)?.executionBindings;
4873
+ return bindings && bindings.length > 0 ? Object.freeze([...bindings]) : void 0;
4874
+ },
3595
4875
  abort(reason) {
3596
- childAbort.abort(reason);
4876
+ controller.abort(reason);
3597
4877
  }
3598
4878
  };
3599
4879
  const live = {
3600
4880
  id,
3601
4881
  status: "acquiring",
3602
4882
  runtime: executor.runtime,
4883
+ ...ownedTreeRoot === void 0 ? {} : { ownedTreeRoot },
4884
+ ...identity ? { identity } : {},
3603
4885
  budget: opts.budget,
3604
4886
  label: opts.label,
4887
+ ...opts.assignmentId === void 0 ? {} : { assignmentId: opts.assignmentId },
3605
4888
  ...opts.key !== void 0 ? { key: opts.key } : {},
3606
- spent: zeroSpend(),
4889
+ spent: zeroSpend$1(),
3607
4890
  settled: void 0,
3608
4891
  delivered: false,
3609
4892
  executorDone: false,
3610
- startedAt: now(),
3611
- lastActivityAt: now(),
3612
- ...executor.deliver ? { deliver: executor.deliver.bind(executor) } : {},
4893
+ cleanupConfirmed: false,
4894
+ executionBindings: [],
4895
+ startedAt,
4896
+ lastActivityAt: startedAt,
4897
+ ...executor.deliver ? { deliver: (message) => executor.deliver?.(message) !== false } : {},
3613
4898
  ...executor.progress ? { readProgress: executor.progress.bind(executor) } : {},
3614
4899
  ...executor.traceSource ? { readTraceSource: executor.traceSource.bind(executor) } : {}
3615
4900
  };
3616
4901
  children.set(id, live);
3617
4902
  if (opts.key !== void 0) keyed.set(opts.key, {
3618
4903
  state: "live",
3619
- id
4904
+ id,
4905
+ identity
3620
4906
  });
3621
- args.journal.appendEvent(args.root, {
4907
+ const materializationCommitted = args.journal.appendEvent(args.root, {
3622
4908
  kind: "spawned",
3623
4909
  id,
3624
4910
  parent: args.parentId,
3625
4911
  label: opts.label,
3626
4912
  ...opts.key !== void 0 ? { key: opts.key } : {},
4913
+ ...opts.assignmentId === void 0 ? {} : { assignmentId: opts.assignmentId },
3627
4914
  budget: opts.budget,
3628
4915
  runtime: executor.runtime,
4916
+ ...ownedTreeRoot === void 0 ? {} : { ownedTreeRoot },
4917
+ ...identity ? { identity } : {},
3629
4918
  seq: ordinal,
3630
4919
  at: new Date(now()).toISOString()
4920
+ }).then(async () => {
4921
+ const profileDigest = identity?.profileDigest ?? authoredProfileDigest(spec.profile);
4922
+ let receipt;
4923
+ let binding;
4924
+ const declaration = runtimeOwnedExecutorMaterialization(executor);
4925
+ const deferredRuntime = runtimeOwnedDeferredExecutorRuntime(executor);
4926
+ if (deferredRuntime !== void 0) {
4927
+ deferredOwner.ownerMaterialization = {
4928
+ runtime: deferredRuntime,
4929
+ authoredProfile: spec.profile,
4930
+ attemptId,
4931
+ journalRoot: args.root,
4932
+ nodeId: id,
4933
+ requiredKnown: true,
4934
+ onReceipt(materialization, executionBinding) {
4935
+ live.runtime = materialization.runtime;
4936
+ live.materialization = materialization;
4937
+ live.executionBindings.push(executionBinding);
4938
+ }
4939
+ };
4940
+ return;
4941
+ }
4942
+ if (declaration === void 0) {
4943
+ receipt = unknownMaterializationReceipt({
4944
+ ...profileDigest === void 0 ? {} : { authoredProfileDigest: profileDigest },
4945
+ runtime: executor.runtime,
4946
+ reason: "executor-did-not-report"
4947
+ });
4948
+ binding = unknownExecutionBindingReceipt(receipt, attemptId, "executor-did-not-report");
4949
+ } else try {
4950
+ if (profileDigest === void 0) throw new ValidationError("scope.spawn: a known materialization requires a canonical authored profile");
4951
+ receipt = knownMaterializationReceipt({
4952
+ authoredProfileDigest: profileDigest,
4953
+ runtime: executor.runtime,
4954
+ declaration
4955
+ });
4956
+ const reportedBinding = runtimeOwnedExecutorExecutionBinding(executor);
4957
+ if (reportedBinding === void 0 || reportedBinding.attemptId !== attemptId) throw new ValidationError("scope.spawn: trusted executor did not bind the kernel-minted attempt id");
4958
+ binding = knownExecutionBindingReceipt(receipt, reportedBinding);
4959
+ } catch (error) {
4960
+ receipt = unknownMaterializationReceipt({
4961
+ ...profileDigest === void 0 ? {} : { authoredProfileDigest: profileDigest },
4962
+ runtime: executor.runtime,
4963
+ reason: "invalid-executor-report"
4964
+ });
4965
+ binding = unknownExecutionBindingReceipt(receipt, attemptId, "invalid-executor-report");
4966
+ await appendNodeMaterialization(args, id, ordinal, receipt, binding, now);
4967
+ live.materialization = receipt;
4968
+ live.executionBindings.push(binding);
4969
+ throw new ValidationError(`scope.spawn: executor ${JSON.stringify(executor.runtime)} returned invalid materialization evidence`, { cause: error });
4970
+ }
4971
+ await appendNodeMaterialization(args, id, ordinal, receipt, binding, now);
4972
+ live.materialization = receipt;
4973
+ live.executionBindings.push(binding);
3631
4974
  });
3632
4975
  notifyRuntimeHookEvent(args.hooks, {
3633
4976
  id: `${id}:spawn`,
@@ -3640,16 +4983,20 @@ function createScope(args) {
3640
4983
  payload: {
3641
4984
  childId: id,
3642
4985
  label: opts.label,
4986
+ ...opts.assignmentId === void 0 ? {} : { assignmentId: opts.assignmentId },
3643
4987
  runtime: executor.runtime,
4988
+ ...identity ? { identity } : {},
3644
4989
  budget: opts.budget,
3645
4990
  depth: args.depth
3646
4991
  }
3647
4992
  }, { signal: args.signal });
3648
- live.settled = runChild(live, executor, childAbort, task, opts, args.pool, reservation.ticket, args.blobs, now).then((s) => {
4993
+ live.settled = runChild(live, executor, controller, task, opts, args.pool, reservation.ticket, args.blobs, now, materializationCommitted, childDeadlineAtMs).then((s) => {
3649
4994
  live.resolved = s;
3650
4995
  return s;
3651
4996
  }).finally(() => {
3652
- args.signal.removeEventListener("abort", cascadeAbort);
4997
+ if (live.cleanupConfirmed) permit.release();
4998
+ clearChildDeadline?.();
4999
+ if (cascadeAbort) args.signal.removeEventListener("abort", cascadeAbort);
3653
5000
  });
3654
5001
  return {
3655
5002
  ok: true,
@@ -3657,7 +5004,10 @@ function createScope(args) {
3657
5004
  ...prior ? { prior } : {}
3658
5005
  };
3659
5006
  } catch (err) {
3660
- args.pool.reconcile(reservation.ticket, zeroSpend());
5007
+ permit.release();
5008
+ clearChildDeadline?.();
5009
+ if (cascadeAbort) args.signal.removeEventListener("abort", cascadeAbort);
5010
+ args.pool.reconcile(reservation.ticket, zeroSpend$1());
3661
5011
  throw err;
3662
5012
  }
3663
5013
  }
@@ -3692,9 +5042,10 @@ function createScope(args) {
3692
5042
  }
3693
5043
  }
3694
5044
  function send(nodeId, msg) {
5045
+ if (args.signal.aborted) return false;
3695
5046
  const child = children.get(nodeId);
3696
5047
  if (!child || child.delivered || !child.deliver) return false;
3697
- child.deliver(msg);
5048
+ if (!(child.deliver(msg) !== false)) return false;
3698
5049
  child.lastActivityAt = now();
3699
5050
  return true;
3700
5051
  }
@@ -3709,6 +5060,10 @@ function createScope(args) {
3709
5060
  * extend that ceiling either (that would make a wait override a budget guard).
3710
5061
  */
3711
5062
  function wait(spec, opts) {
5063
+ if (args.signal.aborted) return {
5064
+ ok: false,
5065
+ reason: "deadline-exceeded"
5066
+ };
3712
5067
  if (validateWaitSpec(spec) !== null) return {
3713
5068
  ok: false,
3714
5069
  reason: "invalid-spec"
@@ -3751,20 +5106,23 @@ function createScope(args) {
3751
5106
  maxTokens: 0
3752
5107
  },
3753
5108
  label: opts.label,
3754
- spent: zeroSpend(),
5109
+ spent: zeroSpend$1(),
3755
5110
  settled: void 0,
3756
5111
  delivered: false,
3757
5112
  executorDone: false,
5113
+ cleanupConfirmed: true,
5114
+ executionBindings: [],
3758
5115
  startedAt: armedAt,
3759
5116
  lastActivityAt: now(),
3760
5117
  wait: {
3761
5118
  spec: effectiveSpec,
3762
5119
  armedAt,
3763
- label: opts.label
5120
+ label: opts.label,
5121
+ armCommitted: adopted !== void 0
3764
5122
  }
3765
5123
  };
3766
5124
  children.set(id, live);
3767
- if (!adopted) args.journal.appendEvent(args.root, {
5125
+ live.settled = (adopted ? Promise.resolve() : args.journal.appendEvent(args.root, {
3768
5126
  kind: "waiting",
3769
5127
  id,
3770
5128
  parent: args.parentId,
@@ -3773,33 +5131,35 @@ function createScope(args) {
3773
5131
  armedAt,
3774
5132
  seq: ordinal,
3775
5133
  at: new Date(now()).toISOString()
3776
- });
3777
- notifyRuntimeHookEvent(args.hooks, {
3778
- id: `${id}:waiting`,
3779
- runId: args.root,
3780
- target: "agent.spawn",
3781
- phase: "after",
3782
- timestamp: now(),
3783
- stepIndex: ordinal,
3784
- parentId: args.parentId,
3785
- payload: {
3786
- childId: id,
5134
+ })).then(() => {
5135
+ if (live.wait) live.wait.armCommitted = true;
5136
+ notifyRuntimeHookEvent(args.hooks, {
5137
+ id: `${id}:waiting`,
5138
+ runId: args.root,
5139
+ target: "agent.spawn",
5140
+ phase: "after",
5141
+ timestamp: now(),
5142
+ stepIndex: ordinal,
5143
+ parentId: args.parentId,
5144
+ payload: {
5145
+ childId: id,
5146
+ label: opts.label,
5147
+ runtime: "wait",
5148
+ wait: effectiveSpec,
5149
+ armedAt,
5150
+ resumed: adopted !== void 0
5151
+ }
5152
+ }, { signal: args.signal });
5153
+ return runWait({
5154
+ spec: effectiveSpec,
3787
5155
  label: opts.label,
3788
- runtime: "wait",
3789
- wait: effectiveSpec,
3790
5156
  armedAt,
3791
- resumed: adopted !== void 0
3792
- }
3793
- }, { signal: args.signal });
3794
- live.settled = runWait({
3795
- spec: effectiveSpec,
3796
- label: opts.label,
3797
- armedAt,
3798
- resumed: adopted !== void 0,
3799
- signal: waitAbort.signal,
3800
- ...args.probes ? { probes: args.probes } : {},
3801
- now,
3802
- ...args.waitSleep ? { sleep: args.waitSleep } : {}
5157
+ resumed: adopted !== void 0,
5158
+ signal: waitAbort.signal,
5159
+ ...args.probes ? { probes: args.probes } : {},
5160
+ now,
5161
+ ...args.waitSleep ? { sleep: args.waitSleep } : {}
5162
+ });
3803
5163
  }).then(async (resolution) => {
3804
5164
  live.executorDone = true;
3805
5165
  live.lastActivityAt = now();
@@ -3807,7 +5167,11 @@ function createScope(args) {
3807
5167
  kind: "down",
3808
5168
  reason: resolution.reason,
3809
5169
  infra: false,
3810
- restartCount: 0
5170
+ restartCount: 0,
5171
+ trace: {
5172
+ status: "unavailable",
5173
+ reason: "not-an-executor"
5174
+ }
3811
5175
  };
3812
5176
  const outRef = contentAddress(resolution.outcome);
3813
5177
  await args.blobs.put(outRef, resolution.outcome);
@@ -3815,7 +5179,11 @@ function createScope(args) {
3815
5179
  kind: "done",
3816
5180
  out: resolution.outcome,
3817
5181
  outRef,
3818
- spent: zeroSpend()
5182
+ spent: zeroSpend$1(),
5183
+ trace: {
5184
+ status: "unavailable",
5185
+ reason: "not-an-executor"
5186
+ }
3819
5187
  };
3820
5188
  }).catch((err) => {
3821
5189
  live.executorDone = true;
@@ -3823,7 +5191,11 @@ function createScope(args) {
3823
5191
  kind: "down",
3824
5192
  reason: errMessage(err),
3825
5193
  infra: true,
3826
- restartCount: 0
5194
+ restartCount: 0,
5195
+ trace: {
5196
+ status: "unavailable",
5197
+ reason: "not-an-executor"
5198
+ }
3827
5199
  };
3828
5200
  }).then((s) => {
3829
5201
  live.resolved = s;
@@ -3868,8 +5240,14 @@ function createScope(args) {
3868
5240
  }
3869
5241
  }
3870
5242
  async function meter(spend, detail) {
5243
+ if (args.signal.aborted) throw new ValidationError("scope.meter: cannot record new driver work after scope abort");
3871
5244
  const seq = meterSeq++;
3872
- args.pool.observe(spend);
5245
+ let observeError;
5246
+ try {
5247
+ args.pool.observe(spend);
5248
+ } catch (error) {
5249
+ observeError = error;
5250
+ }
3873
5251
  await args.journal.appendEvent(args.root, {
3874
5252
  kind: "metered",
3875
5253
  id: args.parentId,
@@ -3889,6 +5267,7 @@ function createScope(args) {
3889
5267
  ...detail ?? {}
3890
5268
  }
3891
5269
  }, { signal: args.signal });
5270
+ if (observeError !== void 0) throw observeError;
3892
5271
  }
3893
5272
  const resume = args.resumeFrom ? {
3894
5273
  settled: args.resumeFrom.settled,
@@ -3897,7 +5276,7 @@ function createScope(args) {
3897
5276
  keys: args.resumeFrom.keys,
3898
5277
  priorSpend: args.resumeFrom.priorSpend
3899
5278
  } : void 0;
3900
- return {
5279
+ const scope = {
3901
5280
  spawn,
3902
5281
  next,
3903
5282
  nextResolved,
@@ -3913,8 +5292,177 @@ function createScope(args) {
3913
5292
  },
3914
5293
  get budget() {
3915
5294
  return args.pool.readout();
5295
+ },
5296
+ get workerCapacity() {
5297
+ return {
5298
+ live: liveWorkerCapacity.live,
5299
+ freeSlots: freeSlots(liveWorkerCapacity.live, liveWorkerCapacity.max)
5300
+ };
3916
5301
  }
3917
5302
  };
5303
+ if (args.ownerMaterialization !== void 0) {
5304
+ const authoredProfile = args.ownerMaterialization.authoredProfile === void 0 ? void 0 : detachedSnapshot(args.ownerMaterialization.authoredProfile, "scope owner authored profile");
5305
+ ownerMaterializationStates.set(scope, {
5306
+ journal: args.journal,
5307
+ root: args.ownerMaterialization.journalRoot ?? args.root,
5308
+ nodeId: args.ownerMaterialization.nodeId ?? args.parentId,
5309
+ runtime: args.ownerMaterialization.runtime,
5310
+ attemptId: args.ownerMaterialization.attemptId,
5311
+ ...authoredProfile === void 0 ? {} : { authoredProfile },
5312
+ ...authoredProfile === void 0 ? {} : { authoredProfileDigest: authoredProfileDigest(authoredProfile) },
5313
+ ...args.ownerMaterialization.prior === void 0 ? {} : { prior: args.ownerMaterialization.prior },
5314
+ requiredKnown: args.ownerMaterialization.requiredKnown === true,
5315
+ ...args.ownerMaterialization.onReceipt === void 0 ? {} : { onReceipt: args.ownerMaterialization.onReceipt },
5316
+ now,
5317
+ receipt: args.ownerMaterialization.prior,
5318
+ bindingPublished: false,
5319
+ publishedThisProcess: false
5320
+ });
5321
+ }
5322
+ return scope;
5323
+ }
5324
+ const ownerMaterializationStates = /* @__PURE__ */ new WeakMap();
5325
+ /**
5326
+ * @internal Publish exact root-manager materialization from a runtime-owned adapter after dynamic
5327
+ * attachments exist and before its executor starts. This is deliberately a module function backed
5328
+ * by a private WeakMap, not a Scope method an Agent can call.
5329
+ */
5330
+ async function recordScopeOwnerMaterialization(scope, runtime, declaration, bindingInput) {
5331
+ const state = ownerMaterializationState(scope);
5332
+ if (runtime !== state.runtime) {
5333
+ await rejectOwnerMaterialization(state);
5334
+ throw new ValidationError(`scope owner materialization runtime ${JSON.stringify(runtime)} does not match ${JSON.stringify(state.runtime)}`);
5335
+ }
5336
+ if (state.authoredProfileDigest === void 0) {
5337
+ await rejectOwnerMaterialization(state);
5338
+ throw new ValidationError("scope owner materialization requires an exact authored profile");
5339
+ }
5340
+ let receipt;
5341
+ let binding;
5342
+ try {
5343
+ if (bindingInput.attemptId !== state.attemptId) throw new ValidationError("scope owner execution binding does not use the kernel-minted attempt id");
5344
+ if (canonicalCandidateDigest(declaration.effectiveProfile) !== state.authoredProfileDigest) throw new ValidationError("scope owner stable effective profile conflicts with its admitted authored profile");
5345
+ receipt = knownMaterializationReceipt({
5346
+ authoredProfileDigest: state.authoredProfileDigest,
5347
+ runtime,
5348
+ declaration
5349
+ });
5350
+ binding = knownExecutionBindingReceipt(receipt, bindingInput);
5351
+ } catch (error) {
5352
+ await rejectOwnerMaterialization(state);
5353
+ throw new ValidationError("scope owner returned invalid materialization evidence", { cause: error });
5354
+ }
5355
+ if (state.prior !== void 0) {
5356
+ if (canonicalCandidateDigest(state.prior) !== canonicalCandidateDigest(receipt)) {
5357
+ await rejectOwnerMaterialization(state);
5358
+ throw new ValidationError("scope owner materialization changed across resume; backend, model, execution identity, and plan must match");
5359
+ }
5360
+ state.receipt = state.prior;
5361
+ await appendOwnerBinding(state, binding);
5362
+ state.onReceipt?.(state.prior, binding);
5363
+ state.publishedThisProcess = true;
5364
+ return;
5365
+ }
5366
+ if (state.receipt !== void 0) throw new ValidationError("scope owner materialization was already recorded");
5367
+ await appendOwnerMaterialization(state, receipt, binding);
5368
+ state.onReceipt?.(receipt, binding);
5369
+ state.publishedThisProcess = true;
5370
+ }
5371
+ /** @internal Kernel identity for constructing the exact deferred owner executor. */
5372
+ function scopeOwnerExecutorNodeContext(scope) {
5373
+ const state = ownerMaterializationState(scope);
5374
+ return Object.freeze({
5375
+ rootId: state.root,
5376
+ parentId: state.nodeId,
5377
+ nodeId: state.nodeId,
5378
+ attemptId: state.attemptId
5379
+ });
5380
+ }
5381
+ /** @internal Ensure a deferred root that never published evidence remains visibly unknown. */
5382
+ async function finalizeScopeOwnerMaterialization(scope) {
5383
+ const state = ownerMaterializationStates.get(scope);
5384
+ if (state === void 0 || state.publishedThisProcess) return;
5385
+ if (state.prior !== void 0) {
5386
+ await appendUnknownOwnerBinding(state, state.prior, "root-agent-did-not-report");
5387
+ throw new ValidationError("resumed scope owner did not re-attest its prior materialization before execution");
5388
+ }
5389
+ if (state.receipt !== void 0) return;
5390
+ const receipt = unknownMaterializationReceipt({
5391
+ ...state.authoredProfileDigest === void 0 ? {} : { authoredProfileDigest: state.authoredProfileDigest },
5392
+ runtime: state.runtime,
5393
+ reason: "root-agent-did-not-report"
5394
+ });
5395
+ const binding = unknownExecutionBindingReceipt(receipt, state.attemptId, "root-agent-did-not-report");
5396
+ await appendOwnerMaterialization(state, receipt, binding);
5397
+ state.onReceipt?.(receipt, binding);
5398
+ if (state.requiredKnown) throw new ValidationError("runtime-owned scope owner did not publish materialization before completing");
5399
+ }
5400
+ function ownerMaterializationState(scope) {
5401
+ const state = ownerMaterializationStates.get(scope);
5402
+ if (state === void 0) throw new ValidationError("scope has no deferred runtime-owned root materialization channel");
5403
+ return state;
5404
+ }
5405
+ async function rejectOwnerMaterialization(state) {
5406
+ if (state.bindingPublished) return;
5407
+ if (state.prior !== void 0) {
5408
+ await appendUnknownOwnerBinding(state, state.prior, "invalid-executor-report");
5409
+ return;
5410
+ }
5411
+ if (state.receipt !== void 0) {
5412
+ await appendUnknownOwnerBinding(state, state.receipt, "invalid-executor-report");
5413
+ return;
5414
+ }
5415
+ const receipt = unknownMaterializationReceipt({
5416
+ ...state.authoredProfileDigest === void 0 ? {} : { authoredProfileDigest: state.authoredProfileDigest },
5417
+ runtime: state.runtime,
5418
+ reason: "invalid-executor-report"
5419
+ });
5420
+ const binding = unknownExecutionBindingReceipt(receipt, state.attemptId, "invalid-executor-report");
5421
+ await appendOwnerMaterialization(state, receipt, binding);
5422
+ state.onReceipt?.(receipt, binding);
5423
+ }
5424
+ async function appendOwnerMaterialization(state, receipt, binding) {
5425
+ await state.journal.appendEvent(state.root, {
5426
+ kind: "materialized",
5427
+ id: state.nodeId,
5428
+ receipt,
5429
+ seq: 0,
5430
+ at: new Date(state.now()).toISOString()
5431
+ });
5432
+ state.receipt = receipt;
5433
+ await appendOwnerBinding(state, binding);
5434
+ }
5435
+ async function appendOwnerBinding(state, binding) {
5436
+ await state.journal.appendEvent(state.root, {
5437
+ kind: "execution-bound",
5438
+ id: state.nodeId,
5439
+ binding,
5440
+ seq: 0,
5441
+ at: new Date(state.now()).toISOString()
5442
+ });
5443
+ state.bindingPublished = true;
5444
+ }
5445
+ async function appendUnknownOwnerBinding(state, receipt, reason) {
5446
+ const binding = unknownExecutionBindingReceipt(receipt, state.attemptId, reason);
5447
+ await appendOwnerBinding(state, binding);
5448
+ state.onReceipt?.(receipt, binding);
5449
+ }
5450
+ async function appendNodeMaterialization(args, id, seq, receipt, binding, now) {
5451
+ const at = new Date(now()).toISOString();
5452
+ await args.journal.appendEvent(args.root, {
5453
+ kind: "materialized",
5454
+ id,
5455
+ receipt,
5456
+ seq,
5457
+ at
5458
+ });
5459
+ await args.journal.appendEvent(args.root, {
5460
+ kind: "execution-bound",
5461
+ id,
5462
+ binding,
5463
+ seq,
5464
+ at
5465
+ });
3918
5466
  }
3919
5467
  /** Await whichever pending child settles first, returning the child (its `resolved` is set
3920
5468
  * by the time this resolves because `runChild`'s `.then` sets it before the promise
@@ -3927,30 +5475,36 @@ async function raceFirstSettled(pending) {
3927
5475
  async function finalizeSettlement(child, settlement, seq, args, now) {
3928
5476
  const handle = frozenHandle(child);
3929
5477
  if (child.wait) return finalizeWait(child, settlement, seq, args, now, handle);
5478
+ const settledAt = now();
5479
+ child.settledAt = settledAt;
5480
+ const at = new Date(settledAt).toISOString();
3930
5481
  if (settlement.kind === "down") {
3931
5482
  child.status = "failed";
5483
+ child.trace = settlement.trace;
3932
5484
  await args.journal.appendEvent(args.root, {
3933
5485
  kind: "settled",
3934
5486
  id: child.id,
3935
5487
  status: "down",
3936
5488
  spent: child.spent,
3937
5489
  infra: settlement.infra,
5490
+ reason: settlement.reason,
5491
+ trace: settlement.trace,
3938
5492
  seq,
3939
- at: new Date(now()).toISOString()
5493
+ at
3940
5494
  });
3941
5495
  if (settlement.metered) await args.journal.appendEvent(args.root, {
3942
5496
  kind: "metered",
3943
5497
  id: child.id,
3944
5498
  spend: settlement.metered,
3945
5499
  seq,
3946
- at: new Date(now()).toISOString()
5500
+ at
3947
5501
  });
3948
5502
  notifyRuntimeHookEvent(args.hooks, {
3949
5503
  id: `${child.id}:settled`,
3950
5504
  runId: args.root,
3951
5505
  target: "agent.child",
3952
5506
  phase: "after",
3953
- timestamp: now(),
5507
+ timestamp: settledAt,
3954
5508
  stepIndex: seq,
3955
5509
  parentId: args.parentId,
3956
5510
  payload: {
@@ -3967,12 +5521,15 @@ async function finalizeSettlement(child, settlement, seq, args, now) {
3967
5521
  reason: settlement.reason,
3968
5522
  infra: settlement.infra,
3969
5523
  restartCount: settlement.restartCount,
5524
+ trace: settlement.trace,
5525
+ settledAt,
3970
5526
  seq
3971
5527
  };
3972
5528
  }
3973
5529
  child.status = "done";
3974
5530
  child.outRef = settlement.outRef;
3975
5531
  child.spent = settlement.spent;
5532
+ child.trace = settlement.trace;
3976
5533
  await args.journal.appendEvent(args.root, {
3977
5534
  kind: "settled",
3978
5535
  id: child.id,
@@ -3980,22 +5537,23 @@ async function finalizeSettlement(child, settlement, seq, args, now) {
3980
5537
  outRef: settlement.outRef,
3981
5538
  ...settlement.verdict ? { verdict: settlement.verdict } : {},
3982
5539
  spent: settlement.spent,
5540
+ trace: settlement.trace,
3983
5541
  seq,
3984
- at: new Date(now()).toISOString()
5542
+ at
3985
5543
  });
3986
5544
  if (settlement.metered) await args.journal.appendEvent(args.root, {
3987
5545
  kind: "metered",
3988
5546
  id: child.id,
3989
5547
  spend: settlement.metered,
3990
5548
  seq,
3991
- at: new Date(now()).toISOString()
5549
+ at
3992
5550
  });
3993
5551
  notifyRuntimeHookEvent(args.hooks, {
3994
5552
  id: `${child.id}:settled`,
3995
5553
  runId: args.root,
3996
5554
  target: "agent.child",
3997
5555
  phase: "after",
3998
- timestamp: now(),
5556
+ timestamp: settledAt,
3999
5557
  stepIndex: seq,
4000
5558
  parentId: args.parentId,
4001
5559
  payload: {
@@ -4014,6 +5572,8 @@ async function finalizeSettlement(child, settlement, seq, args, now) {
4014
5572
  outRef: settlement.outRef,
4015
5573
  ...settlement.verdict ? { verdict: settlement.verdict } : {},
4016
5574
  spent: settlement.spent,
5575
+ trace: settlement.trace,
5576
+ settledAt,
4017
5577
  seq
4018
5578
  };
4019
5579
  }
@@ -4021,10 +5581,12 @@ async function finalizeSettlement(child, settlement, seq, args, now) {
4021
5581
  * the driver branches on. `by` names WHY it woke — `fired` / `timeout` / `cancelled` — which is
4022
5582
  * the fact a resumed reader needs and cannot recover from a payload it may never fetch. */
4023
5583
  async function finalizeWait(child, settlement, seq, args, now, handle) {
4024
- const at = new Date(now()).toISOString();
5584
+ const settledAt = now();
5585
+ child.settledAt = settledAt;
5586
+ const at = new Date(settledAt).toISOString();
4025
5587
  if (settlement.kind === "down") {
4026
5588
  child.status = "cancelled";
4027
- await args.journal.appendEvent(args.root, {
5589
+ if (child.wait?.armCommitted) await args.journal.appendEvent(args.root, {
4028
5590
  kind: "woken",
4029
5591
  id: child.id,
4030
5592
  by: "cancelled",
@@ -4037,6 +5599,8 @@ async function finalizeWait(child, settlement, seq, args, now, handle) {
4037
5599
  reason: settlement.reason,
4038
5600
  infra: settlement.infra,
4039
5601
  restartCount: settlement.restartCount,
5602
+ trace: settlement.trace,
5603
+ settledAt,
4040
5604
  seq
4041
5605
  };
4042
5606
  }
@@ -4056,7 +5620,7 @@ async function finalizeWait(child, settlement, seq, args, now, handle) {
4056
5620
  runId: args.root,
4057
5621
  target: "agent.child",
4058
5622
  phase: "after",
4059
- timestamp: now(),
5623
+ timestamp: settledAt,
4060
5624
  stepIndex: seq,
4061
5625
  parentId: args.parentId,
4062
5626
  payload: {
@@ -4071,6 +5635,8 @@ async function finalizeWait(child, settlement, seq, args, now, handle) {
4071
5635
  out: settlement.out,
4072
5636
  outRef: settlement.outRef,
4073
5637
  spent: settlement.spent,
5638
+ trace: settlement.trace,
5639
+ settledAt,
4074
5640
  seq
4075
5641
  };
4076
5642
  }
@@ -4085,54 +5651,101 @@ async function finalizeWait(child, settlement, seq, args, now, handle) {
4085
5651
  * applied per child). The reservation is reconciled on EVERY path (success, abort, throw)
4086
5652
  * so the conserved pool can never leak a reservation.
4087
5653
  */
4088
- async function runChild(live, executor, childAbort, task, opts, pool, ticket, blobs, now) {
5654
+ async function runChild(live, executor, childAbort, task, opts, pool, ticket, blobs, now, executionReady, deadlineAtMs) {
4089
5655
  let reconciled = false;
5656
+ let started = false;
5657
+ let terminalTelemetryCaptured = false;
5658
+ let teardownStarted = false;
5659
+ let traceEvidence;
5660
+ const captureTraceOnce = async () => {
5661
+ traceEvidence ??= await captureWorkerTraceEvidence(live.readTraceSource, blobs, started);
5662
+ return traceEvidence;
5663
+ };
5664
+ const teardownOnce = async (grace) => {
5665
+ if (teardownStarted) return;
5666
+ teardownStarted = true;
5667
+ await teardownExecutor(executor, grace, deadlineAtMs, now);
5668
+ live.cleanupConfirmed = true;
5669
+ };
4090
5670
  const reconcileOnce = (spend) => {
4091
- if (reconciled) return;
5671
+ if (reconciled) return void 0;
4092
5672
  reconciled = true;
4093
- pool.reconcile(ticket, clampSpend(spend, opts.budget));
5673
+ try {
5674
+ pool.reconcile(ticket, spend);
5675
+ return;
5676
+ } catch (error) {
5677
+ return error;
5678
+ }
4094
5679
  };
4095
5680
  try {
5681
+ await executionReady;
5682
+ if (childAbort.signal.aborted) throw abortError(childAbort.signal);
4096
5683
  live.status = "running";
5684
+ started = true;
4097
5685
  const ran = executor.execute(task, childAbort.signal);
4098
5686
  let artifact;
4099
5687
  if (isAsyncIterable(ran)) {
4100
5688
  const spend = await foldStream(ran, (running) => {
4101
5689
  live.spent = running;
4102
5690
  live.lastActivityAt = now();
4103
- });
5691
+ }, childAbort.signal);
4104
5692
  live.spent = spend;
4105
5693
  artifact = executor.resultArtifact();
4106
- reconcileOnce(spend);
5694
+ const accounting = executor.accounting?.();
5695
+ const terminalSpend = preserveUnknownTelemetry(spend, artifact.spent);
5696
+ live.spent = accounting?.reported ?? terminalSpend;
5697
+ terminalTelemetryCaptured = true;
5698
+ live.executorDone = true;
5699
+ const reconcileError = reconcileOnce(accounting?.reservation ?? terminalSpend);
5700
+ if (reconcileError !== void 0) throw reconcileError;
4107
5701
  } else {
4108
- const terminal = await ran;
4109
- live.spent = terminal.spent;
5702
+ const terminal = await awaitAbortable(Promise.resolve(ran), childAbort.signal);
5703
+ const accounting = executor.accounting?.();
5704
+ live.spent = accounting?.reported ?? terminal.spent;
4110
5705
  artifact = terminal;
4111
- reconcileOnce(terminal.spent);
5706
+ terminalTelemetryCaptured = true;
5707
+ live.executorDone = true;
5708
+ const reconcileError = reconcileOnce(accounting?.reservation ?? terminal.spent);
5709
+ if (reconcileError !== void 0) throw reconcileError;
4112
5710
  }
4113
5711
  live.executorDone = true;
4114
5712
  const ownMetered = executor.metered?.();
5713
+ const trace = await captureTraceOnce();
4115
5714
  if (childAbort.signal.aborted) {
4116
- await teardownSafe(executor, opts.shutdown ?? "brutalKill");
4117
- return downRecord("aborted before settle", true, ownMetered);
5715
+ await teardownOnce(opts.shutdown ?? "brutalKill");
5716
+ return downRecord("aborted before settle", true, trace, ownMetered);
4118
5717
  }
4119
5718
  const outRef = contentAddress(artifact.out);
4120
5719
  await blobs.put(outRef, artifact.out);
4121
- await teardownSafe(executor, opts.shutdown ?? "infinity");
5720
+ await teardownOnce(opts.shutdown ?? 5e3);
4122
5721
  return {
4123
5722
  kind: "done",
4124
5723
  out: artifact.out,
4125
5724
  outRef,
4126
5725
  ...artifact.verdict ? { verdict: artifact.verdict } : {},
4127
5726
  spent: live.spent,
5727
+ trace,
4128
5728
  ...ownMetered ? { metered: ownMetered } : {}
4129
5729
  };
4130
5730
  } catch (err) {
4131
5731
  live.executorDone = true;
4132
- reconcileOnce(live.spent);
4133
- await teardownSafe(executor, "brutalKill");
5732
+ const trace = await captureTraceOnce();
5733
+ let teardownError;
5734
+ try {
5735
+ await teardownOnce("brutalKill");
5736
+ } catch (error) {
5737
+ teardownError = error;
5738
+ }
5739
+ const accounting = executor.accounting?.();
5740
+ if (accounting) live.spent = accounting.reported;
4134
5741
  const aborted = childAbort.signal.aborted || isAbortError(err);
4135
- return downRecord(errMessage(err), aborted || isInfraError(err), executor.metered?.());
5742
+ if (started && !terminalTelemetryCaptured && accounting === void 0) live.spent = {
5743
+ ...live.spent,
5744
+ tokensKnown: false,
5745
+ usdKnown: false
5746
+ };
5747
+ const reconcileError = reconcileOnce(accounting?.reservation ?? live.spent);
5748
+ return downRecord(errMessage(err), teardownError !== void 0 || reconcileError !== void 0 || aborted || isInfraError(err), trace, executor.metered?.());
4136
5749
  }
4137
5750
  }
4138
5751
  /**
@@ -4164,6 +5777,25 @@ function settledToIteration(settled) {
4164
5777
  }
4165
5778
  };
4166
5779
  }
5780
+ function normalizeLiveWorkerLimit(value) {
5781
+ if (value === void 0 || value <= 0) return void 0;
5782
+ if (!Number.isSafeInteger(value)) throw new ValidationError(`createScope: maxLiveWorkers must be a positive safe integer or <= 0 for uncapped, got ${String(value)}`);
5783
+ return value;
5784
+ }
5785
+ function acquireLiveWorker(capacity) {
5786
+ if (capacity.max !== void 0 && capacity.live >= capacity.max) return { ok: false };
5787
+ capacity.live += 1;
5788
+ let released = false;
5789
+ return {
5790
+ ok: true,
5791
+ release() {
5792
+ if (released) return;
5793
+ released = true;
5794
+ capacity.live -= 1;
5795
+ if (capacity.live < 0) throw new ValidationError("scope: live-worker capacity released more than once");
5796
+ }
5797
+ };
5798
+ }
4167
5799
  function makeTreeView(root, children) {
4168
5800
  const nodes = [...children.values()].map((c) => ({
4169
5801
  id: c.id,
@@ -4172,8 +5804,15 @@ function makeTreeView(root, children) {
4172
5804
  status: c.status,
4173
5805
  runtime: c.runtime,
4174
5806
  budget: c.budget,
5807
+ ...c.ownedTreeRoot === void 0 ? {} : { ownedTreeRoot: c.ownedTreeRoot },
5808
+ ...c.assignmentId === void 0 ? {} : { assignmentId: c.assignmentId },
5809
+ ...c.identity ? { identity: c.identity } : {},
5810
+ ...c.materialization ? { materialization: c.materialization } : {},
5811
+ ...c.executionBindings.length > 0 ? { executionBindings: Object.freeze([...c.executionBindings]) } : {},
4175
5812
  spent: c.spent,
4176
- ...c.outRef ? { outRef: c.outRef } : {}
5813
+ ...c.settledAt === void 0 ? {} : { settledAt: c.settledAt },
5814
+ ...c.outRef ? { outRef: c.outRef } : {},
5815
+ ...c.trace ? { trace: c.trace } : {}
4177
5816
  }));
4178
5817
  return {
4179
5818
  root,
@@ -4187,9 +5826,49 @@ function frozenHandle(child) {
4187
5826
  id: child.id,
4188
5827
  label: child.label,
4189
5828
  status: child.status,
5829
+ ...child.assignmentId === void 0 ? {} : { assignmentId: child.assignmentId },
5830
+ ...child.identity ? { identity: child.identity } : {},
5831
+ ...child.materialization ? { materialization: child.materialization } : {},
5832
+ ...child.executionBindings.length > 0 ? { executionBindings: Object.freeze([...child.executionBindings]) } : {},
4190
5833
  abort() {}
4191
5834
  };
4192
5835
  }
5836
+ /** Derive the portable identity of the exact profile and task a node will execute. Caller-owned
5837
+ * candidate/correlation fields are checked at this boundary before they enter the durable log. */
5838
+ function deriveNodeExecutionIdentity(spec, task) {
5839
+ const digest = (value) => {
5840
+ try {
5841
+ return canonicalCandidateDigest(value);
5842
+ } catch {
5843
+ return;
5844
+ }
5845
+ };
5846
+ const profileDigest = digest(spec.profile);
5847
+ const taskDigest = digest(task);
5848
+ const candidateDigest = spec.execution?.candidateDigest;
5849
+ if (candidateDigest !== void 0 && !sha256DigestSchema.safeParse(candidateDigest).success) throw new ValidationError("scope.spawn: execution.candidateDigest must be a sha256 digest");
5850
+ const correlation = freezeCorrelation(spec.execution?.correlation);
5851
+ if (!profileDigest && !taskDigest && !candidateDigest && !correlation) return void 0;
5852
+ return Object.freeze({
5853
+ ...profileDigest ? { profileDigest } : {},
5854
+ ...taskDigest ? { taskDigest } : {},
5855
+ ...candidateDigest ? { candidateDigest } : {},
5856
+ ...correlation ? { correlation } : {}
5857
+ });
5858
+ }
5859
+ function isCompleteIdentity(identity) {
5860
+ return identity?.profileDigest !== void 0 && identity.taskDigest !== void 0;
5861
+ }
5862
+ function sameNodeExecutionIdentity(a, b) {
5863
+ return canonicalCandidateDigest(a) === canonicalCandidateDigest(b);
5864
+ }
5865
+ function freezeCorrelation(value) {
5866
+ if (value === void 0) return void 0;
5867
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new ValidationError("scope.spawn: execution.correlation must be a string record");
5868
+ const entries = Object.entries(value);
5869
+ for (const [key, item] of entries) if (key.length === 0 || typeof item !== "string" || item.length === 0) throw new ValidationError("scope.spawn: execution.correlation keys and values must be non-empty strings");
5870
+ return Object.freeze(Object.fromEntries(entries));
5871
+ }
4193
5872
  /**
4194
5873
  * Fold a streaming executor's normalized usage into the conserved `Spend`, publishing the
4195
5874
  * running total after EVERY event via `onProgress`.
@@ -4200,7 +5879,7 @@ function frozenHandle(child) {
4200
5879
  * turns deep from one that had produced nothing. It also means a worker that THROWS mid-stream
4201
5880
  * now reconciles its real partial spend instead of refunding work the provider already billed.
4202
5881
  */
4203
- async function foldStream(stream, onProgress) {
5882
+ async function foldStream(stream, onProgress, signal) {
4204
5883
  const tokens = {
4205
5884
  input: 0,
4206
5885
  output: 0
@@ -4208,21 +5887,30 @@ async function foldStream(stream, onProgress) {
4208
5887
  let usd = 0;
4209
5888
  let usdKnown = true;
4210
5889
  let iterations = 0;
4211
- for await (const ev of stream) {
4212
- if (ev.kind === "tokens") {
4213
- tokens.input += ev.input;
4214
- tokens.output += ev.output;
4215
- } else if (ev.kind === "cost") {
4216
- usd += ev.usd;
4217
- if (ev.usdKnown === false) usdKnown = false;
4218
- } else iterations += 1;
4219
- onProgress?.({
4220
- iterations,
4221
- tokens: { ...tokens },
4222
- usd,
4223
- ...usdKnown ? {} : { usdKnown: false },
4224
- ms: 0
4225
- });
5890
+ const iterator = stream[Symbol.asyncIterator]();
5891
+ try {
5892
+ for (;;) {
5893
+ const next = signal ? await awaitAbortable(Promise.resolve(iterator.next()), signal) : await iterator.next();
5894
+ if (next.done) break;
5895
+ const ev = next.value;
5896
+ if (ev.kind === "tokens") {
5897
+ tokens.input += ev.input;
5898
+ tokens.output += ev.output;
5899
+ } else if (ev.kind === "cost") {
5900
+ usd += ev.usd;
5901
+ if (ev.usdKnown === false) usdKnown = false;
5902
+ } else iterations += 1;
5903
+ onProgress?.({
5904
+ iterations,
5905
+ tokens: { ...tokens },
5906
+ usd,
5907
+ ...usdKnown ? {} : { usdKnown: false },
5908
+ ms: 0
5909
+ });
5910
+ }
5911
+ } catch (error) {
5912
+ Promise.resolve(iterator.return?.()).catch(() => void 0);
5913
+ throw error;
4226
5914
  }
4227
5915
  return {
4228
5916
  iterations,
@@ -4232,43 +5920,63 @@ async function foldStream(stream, onProgress) {
4232
5920
  ms: 0
4233
5921
  };
4234
5922
  }
4235
- /** Clamp a child's reported spend to its reservation so the pool's fail-loud over-spend
4236
- * guard never trips on a benign overshoot from an external usage report; the difference
4237
- * refunds to the pool as if the child stopped at its ceiling. */
4238
- function clampSpend(spend, budget) {
4239
- const totalTokens = spend.tokens.input + spend.tokens.output;
4240
- const tokensOk = totalTokens <= budget.maxTokens;
4241
- const itersOk = spend.iterations <= budget.maxIterations;
4242
- const usdOk = budget.maxUsd === void 0 || spend.usd <= budget.maxUsd;
4243
- if (tokensOk && itersOk && usdOk) return spend;
4244
- const ratio = !tokensOk && totalTokens > 0 ? budget.maxTokens / totalTokens : 1;
5923
+ /** Usage events carry measured increments; the terminal artifact carries whether a provider omitted
5924
+ * a whole accounting channel. Preserve those unknowns on the common streaming path. */
5925
+ function preserveUnknownTelemetry(streamed, terminal) {
4245
5926
  return {
4246
- iterations: Math.min(spend.iterations, budget.maxIterations),
4247
- tokens: ratio < 1 ? {
4248
- input: Math.floor(spend.tokens.input * ratio),
4249
- output: Math.floor(spend.tokens.output * ratio)
4250
- } : spend.tokens,
4251
- usd: budget.maxUsd === void 0 ? spend.usd : Math.min(spend.usd, budget.maxUsd),
4252
- ...spend.tokensKnown === false ? { tokensKnown: false } : {},
4253
- ...spend.usdKnown === false ? { usdKnown: false } : {},
4254
- ms: spend.ms
4255
- };
4256
- }
4257
- async function teardownSafe(executor, grace) {
4258
- try {
4259
- await executor.teardown(grace);
4260
- } catch {}
5927
+ ...streamed,
5928
+ ...terminal.tokensKnown === false ? { tokensKnown: false } : {},
5929
+ ...terminal.usdKnown === false ? { usdKnown: false } : {},
5930
+ ms: terminal.ms
5931
+ };
4261
5932
  }
4262
- function downRecord(reason, infra, metered) {
5933
+ async function awaitAbortable(work, signal) {
5934
+ if (signal.aborted) {
5935
+ work.catch(() => void 0);
5936
+ throw abortError(signal);
5937
+ }
5938
+ return await new Promise((resolve, reject) => {
5939
+ let settled = false;
5940
+ const onAbort = () => {
5941
+ queueMicrotask(() => {
5942
+ if (settled) return;
5943
+ settled = true;
5944
+ cleanup();
5945
+ reject(abortError(signal));
5946
+ });
5947
+ };
5948
+ const cleanup = () => signal.removeEventListener("abort", onAbort);
5949
+ signal.addEventListener("abort", onAbort, { once: true });
5950
+ work.then((value) => {
5951
+ if (settled) return;
5952
+ settled = true;
5953
+ cleanup();
5954
+ resolve(value);
5955
+ }, (error) => {
5956
+ if (settled) return;
5957
+ settled = true;
5958
+ cleanup();
5959
+ reject(error);
5960
+ });
5961
+ });
5962
+ }
5963
+ function abortError(signal) {
5964
+ const reason = signal.reason;
5965
+ const error = new Error(typeof reason === "string" && reason.length > 0 ? reason : "execution aborted");
5966
+ error.name = "AbortError";
5967
+ return error;
5968
+ }
5969
+ function downRecord(reason, infra, trace, metered) {
4263
5970
  return {
4264
5971
  kind: "down",
4265
5972
  reason,
4266
5973
  infra,
4267
5974
  restartCount: 0,
5975
+ trace,
4268
5976
  ...metered ? { metered } : {}
4269
5977
  };
4270
5978
  }
4271
- function zeroSpend() {
5979
+ function zeroSpend$1() {
4272
5980
  return {
4273
5981
  iterations: 0,
4274
5982
  tokens: {
@@ -4289,6 +5997,20 @@ function isAgentSpec(value) {
4289
5997
  const v = value;
4290
5998
  return "profile" in v && "harness" in v;
4291
5999
  }
6000
+ /** Snapshot every data field whose bytes affect identity, admission, or materialization while
6001
+ * preserving the executable callbacks by reference. The executor implementation is trusted code;
6002
+ * its profile and attribution inputs are not. */
6003
+ function snapshotAgentSpec(raw) {
6004
+ const { profile: rawProfile, harness, execution: rawExecution, ...runtimeExtensions } = raw;
6005
+ const profile = detachedSnapshot(rawProfile, "scope.spawn profile");
6006
+ const execution = rawExecution === void 0 ? void 0 : detachedSnapshot(rawExecution, "scope.spawn execution");
6007
+ return Object.freeze({
6008
+ ...runtimeExtensions,
6009
+ profile,
6010
+ harness,
6011
+ ...execution === void 0 ? {} : { execution }
6012
+ });
6013
+ }
4292
6014
  function isAbortError(err) {
4293
6015
  return typeof err === "object" && err !== null && "name" in err && err.name === "AbortError";
4294
6016
  }
@@ -4304,474 +6026,358 @@ function errMessage(err) {
4304
6026
  }
4305
6027
  //#endregion
4306
6028
  //#region src/runtime/supervise/driver-executor.ts
4307
- /**
4308
- *
4309
- * The recursive driver-executor — the seam that lets a SPAWNED child be a DRIVER, so
4310
- * agents drive agents drive agents over the one keystone atom.
4311
- *
4312
- * A spawned child resolves through the open registry to an `Executor`; the built-in
4313
- * executors (router/inline, sandbox, cli) are LEAVES — `execute(task, signal)` runs the
4314
- * work and settles. This executor is the recursive case: on `execute`, it mounts a NESTED
4315
- * `Scope` (the scope hands it the mount via the `nested-scope` seam) over the SAME
4316
- * conserved pool + shared journal/blobs + the same open registry, one `depth` deeper, then
4317
- * runs the wrapped driver `Agent.act(task, nestedScope)`. The driver spawns its own
4318
- * children into that nested scope; each resolves to EITHER a leaf executor (a worker child)
4319
- * OR this same driver-executor (a driver child) — recursively. So a driver spawns a driver
4320
- * spawns a worker, all on one budget-conserving tree.
4321
- *
4322
- * Why this preserves every keystone invariant (the scope owns the sharing; this executor
4323
- * only runs the driver over what the scope mounts):
4324
- * - Conserved budget: the nested scope reserves from the SAME `BudgetPool` the root owns
4325
- * (the scope mounts it over `args.pool`), so `Σk` is conserved ACROSS depth by
4326
- * construction — a deep tree cannot overspend the root ceiling (reserve-on-spawn fails
4327
- * closed at any depth).
4328
- * - Journal: the nested scope writes to its OWN tree key (`${journalRoot}/${nodeId}`) so
4329
- * its cursor `seq`s never collide with the parent's in the per-tree uniqueness guard,
4330
- * while every nested tree shares the one `SpawnJournal` — the whole recursion is one
4331
- * journal, queryable tree by tree.
4332
- * - Settlement bubbling: the driver child settles into its PARENT scope with the conserved
4333
- * spend summed off its nested tree's settled events, so the parent's pool reconcile +
4334
- * the supervisor's `spentTotal` see the whole sub-tree's spend rolled up — settlements
4335
- * bubble to the root.
4336
- * - Depth ceiling: the nested scope runs at `depth+1`, so the supervisor's `maxDepth`
4337
- * (paired with the conserved pool per R3) fails a spawn closed once the recursion is too
4338
- * deep — exactly as it does for a flat tree.
4339
- *
4340
- * Layering: pure keystone composition. It reuses the scope's `NestedScopeSeam` + the shared
4341
- * `SpawnJournal`; it builds NO new budget, journal, or selection logic. The recursion rides
4342
- * the existing atom.
4343
- *
4344
- * @experimental
4345
- */
4346
- /** The runtime tag the registry maps a driver child to. */
4347
- const driverRuntime = "driver";
4348
6029
  /** The metadata marker on a driver child's spec the recursive registry routes on. */
4349
6030
  const driverRole = "driver";
4350
- /** True when a spec is a driver child (carries the role marker + a driver Agent). */
4351
- function isDriverSpec(spec) {
4352
- if (spec.profile.metadata?.role !== driverRole) return false;
4353
- const driver = spec.driver;
4354
- if (!isAgent(driver)) throw new ValidationError("driverExecutor: a driver-role spec must carry a `driver` Agent to run inside its nested scope");
4355
- return true;
4356
- }
4357
- /**
4358
- * The recursive driver-executor factory. `withDriverExecutor` routes a child marked
4359
- * `role: 'driver'` here; any other child resolves to a leaf built-in. On `execute`, it
4360
- * reads the `nested-scope` seam the SCOPE seeded, mounts a nested `Scope` one `depth`
4361
- * deeper over the shared pool/journal/blobs/registry, runs the driver
4362
- * `Agent.act(task, nestedScope)`, and reports the conserved spend summed off the nested
4363
- * tree's settled events — so the parent scope's reconcile rolls the whole sub-tree's spend
4364
- * into the conserved total.
4365
- *
4366
- * A `down` from the nested driver (a thrown `act` or an aborted scope) propagates as a
4367
- * thrown executor, which the parent scope types into a `down` settlement — the same
4368
- * fail-loud-into-typed-down discipline a leaf gets.
4369
- */
4370
- const driverExecutorFactory = (spec, ctx) => {
4371
- if (!isDriverSpec(spec)) throw new ValidationError("driverExecutorFactory: spec is not a driver child (no role:\"driver\" marker)");
4372
- const driver = spec.driver;
4373
- const journal = spec.journal;
4374
- const seam = readNestedScopeSeam(ctx);
4375
- let artifact;
4376
- let meteredSpend;
4377
- return {
4378
- runtime: driverRuntime,
4379
- async execute(task, signal) {
4380
- const nestedRoot = nestedTreeKey(seam, journal);
4381
- await journal.beginTree(nestedRoot, (/* @__PURE__ */ new Date(0)).toISOString());
4382
- const nestedScope = seam.mount(nestedRoot, signal);
4383
- try {
4384
- const out = await driver.act(task, nestedScope);
4385
- const events = await loadTreeEvents(journal, nestedRoot);
4386
- const settled = events.filter(isSettled);
4387
- meteredSpend = nonZeroOrUndef(sumMetered(events));
4388
- const verdict = deriveDeliveryVerdict(settled);
4389
- artifact = {
4390
- outRef: `${driverRuntime}:${nestedRoot}`,
4391
- out,
4392
- spent: sumSpend(settled),
4393
- ...verdict ? { verdict } : {}
4394
- };
4395
- return artifact;
4396
- } catch (err) {
4397
- meteredSpend = await safeSumMetered(journal, nestedRoot);
4398
- throw err;
4399
- }
4400
- },
4401
- metered() {
4402
- return meteredSpend;
4403
- },
4404
- teardown() {
4405
- return Promise.resolve({ destroyed: true });
4406
- },
4407
- resultArtifact() {
4408
- if (!artifact) throw new ValidationError("driverExecutor: resultArtifact() read before execute()");
4409
- return artifact;
4410
- }
4411
- };
4412
- };
4413
6031
  /**
4414
- * Register the driver-executor so a child marked `role: 'driver'` resolves to it. The base
4415
- * registry resolves by harness alone (it does not read `role`), so a recursive run needs a
4416
- * registry that routes the driver tag here FIRST. Returns a registry decorator: a
4417
- * driver-role spec the driver-executor; everything else the base registry's resolution
4418
- * (leaf built-ins + BYO).
6032
+ * Mark + carry a driver `Agent` so the recursive registry resolves it to the
6033
+ * driver-executor. The returned agent is SPAWNED (never run directly): its
6034
+ * `executorSpec` is marked `role: 'driver'` and carries the driver agent + the shared
6035
+ * journal so the executor can run its `act` inside a nested scope. `act` fails loud if
6036
+ * called directly — a driver child runs THROUGH its nested-scope executor, never as a root.
4419
6037
  */
4420
- function withDriverExecutor(base) {
4421
- return {
4422
- register: base.register.bind(base),
4423
- resolve(spec) {
4424
- if (spec.profile.metadata?.role === driverRole && !spec.executor) return {
4425
- succeeded: true,
4426
- value: driverExecutorFactory
4427
- };
4428
- return base.resolve(spec);
4429
- }
4430
- };
4431
- }
4432
- /** Mint a unique nested-tree key under the parent's journal root. Uses the parent's
4433
- * `journalRoot` + a per-journal monotonic ordinal so two sibling driver trees never
4434
- * collide their keys (each driver child mints exactly one nested tree). */
4435
- function nestedTreeKey(seam, journal) {
4436
- return `${seam.journalRoot}/d${nextNestOrdinal(journal)}`;
4437
- }
4438
- /** Per-journal monotonic nest counter — keyed on the journal instance so a single run's
4439
- * nested-tree keys are unique without a shared module global. */
4440
- const nestCounters = /* @__PURE__ */ new WeakMap();
4441
- function nextNestOrdinal(journal) {
4442
- let c = nestCounters.get(journal);
4443
- if (!c) {
4444
- c = { n: 0 };
4445
- nestCounters.set(journal, c);
4446
- }
4447
- return c.n++;
4448
- }
4449
- /** The nested tree's full event list — the one evidence the spend, verdict, AND driver-inference
4450
- * roll-ups read off the same journal the supervisor sums. */
4451
- async function loadTreeEvents(journal, nestedRoot) {
4452
- const events = await journal.loadTree(nestedRoot);
4453
- if (events === void 0) throw new ValidationError(`driverExecutor: nested tree '${nestedRoot}' missing from the journal after run (corrupted log)`);
4454
- return events;
4455
- }
4456
- function isSettled(ev) {
4457
- return ev.kind === "settled";
4458
- }
4459
- /** Sum the conserved spend over the nested tree's settled events — the honest per-channel
4460
- * roll-up of the whole sub-tree's child WORK. */
4461
- function sumSpend(settled) {
4462
- const total = {
4463
- iterations: 0,
4464
- tokens: {
4465
- input: 0,
4466
- output: 0
4467
- },
4468
- usd: 0,
4469
- ms: 0
4470
- };
4471
- for (const ev of settled) {
4472
- total.iterations += ev.spent.iterations;
4473
- total.tokens.input += ev.spent.tokens.input;
4474
- total.tokens.output += ev.spent.tokens.output;
4475
- total.usd += ev.spent.usd;
4476
- if (ev.spent.tokensKnown === false) total.tokensKnown = false;
4477
- if (ev.spent.usdKnown === false) total.usdKnown = false;
4478
- total.ms += ev.spent.ms;
4479
- }
4480
- return total;
4481
- }
4482
- /** Sum the nested tree's `metered` events — the sub-tree's whole driver INFERENCE (this driver's
4483
- * own turns + any sub-driver inference already re-homed into this tree). Re-homed up to the parent
4484
- * as one `metered` event; never reconciled (already pool-debited live via `observe`). */
4485
- function sumMetered(events) {
4486
- const total = {
4487
- iterations: 0,
4488
- tokens: {
4489
- input: 0,
4490
- output: 0
4491
- },
4492
- usd: 0,
4493
- ms: 0
4494
- };
4495
- for (const ev of events) {
4496
- if (ev.kind !== "metered") continue;
4497
- total.iterations += ev.spend.iterations;
4498
- total.tokens.input += ev.spend.tokens.input;
4499
- total.tokens.output += ev.spend.tokens.output;
4500
- total.usd += ev.spend.usd;
4501
- if (ev.spend.tokensKnown === false) total.tokensKnown = false;
4502
- if (ev.spend.usdKnown === false) total.usdKnown = false;
4503
- total.ms += ev.spend.ms;
4504
- }
4505
- return total;
4506
- }
4507
- /** An all-zero spend that carries an UNKNOWN marker counts as non-zero: a sub-driver whose turns
4508
- * went unmeasured did real work, and dropping its `metered` event here would re-hide upstream the
4509
- * very turn the driver refused to skip. */
4510
- function isNonZeroSpend(s) {
4511
- return s.iterations > 0 || s.tokens.input > 0 || s.tokens.output > 0 || s.usd > 0 || s.ms > 0 || s.tokensKnown === false || s.usdKnown === false;
4512
- }
4513
- /** A spend, or `undefined` when it is all-zero — so `metered()` returns undefined for a driver
4514
- * whose sub-tree did no inference (and the parent journals no empty `metered` event). */
4515
- function nonZeroOrUndef(s) {
4516
- return isNonZeroSpend(s) ? s : void 0;
4517
- }
4518
- /** Sum the nested tree's metered events, tolerating a missing tree (a crash before `beginTree`
4519
- * landed) — never throw here, or it would mask the original `act` error on the crash path. */
4520
- async function safeSumMetered(journal, nestedRoot) {
4521
- try {
4522
- return nonZeroOrUndef(sumMetered(await loadTreeEvents(journal, nestedRoot)));
4523
- } catch {
4524
- return;
4525
- }
4526
- }
4527
- /** Derive the driver child's delivery verdict from its DIRECT children's settlements:
4528
- * `valid` iff any direct child settled `done` AND `valid` (the keep-best finalize's pick);
4529
- * `score` = the best delivered score. Returns `undefined` when no child settled at all (the
4530
- * driver itself produced nothing to bubble a verdict from). Fail-closed: a child whose verdict
4531
- * carried no `valid` counts as not-delivered. */
4532
- function deriveDeliveryVerdict(settled) {
4533
- let sawChild = false;
4534
- let anyValid = false;
4535
- let bestValidScore;
4536
- let bestDoneScore;
4537
- for (const ev of settled) {
4538
- sawChild = true;
4539
- if (ev.status !== "done") continue;
4540
- const score = ev.verdict?.score;
4541
- if (score !== void 0 && (bestDoneScore === void 0 || score > bestDoneScore)) bestDoneScore = score;
4542
- if (ev.verdict?.valid === true) {
4543
- anyValid = true;
4544
- if (score !== void 0 && (bestValidScore === void 0 || score > bestValidScore)) bestValidScore = score;
4545
- }
4546
- }
4547
- if (!sawChild) return void 0;
6038
+ function driverChild(profileOrName, driver, journal, execution) {
6039
+ const profile = typeof profileOrName === "string" ? {
6040
+ name: profileOrName,
6041
+ metadata: { role: driverRole }
6042
+ } : profileOrName;
6043
+ const name = profile.name ?? driver.name;
6044
+ const spec = {
6045
+ profile,
6046
+ harness: null,
6047
+ ...execution ? { execution } : {},
6048
+ driverRuntime,
6049
+ driver,
6050
+ journal
6051
+ };
6052
+ const deliver = driver.deliver?.bind(driver);
4548
6053
  return {
4549
- valid: anyValid,
4550
- score: anyValid ? bestValidScore ?? 1 : bestDoneScore ?? 0
6054
+ name,
6055
+ executorSpec: spec,
6056
+ ...deliver ? { deliver(message) {
6057
+ return deliver(message) !== false;
6058
+ } } : {},
6059
+ act() {
6060
+ throw new ValidationError(`driverChild: "${name}" was run directly; a driver child runs through its nested-scope executor`);
6061
+ }
4551
6062
  };
4552
6063
  }
4553
- function readNestedScopeSeam(ctx) {
4554
- const seam = ctx.seams[nestedScopeSeamKey];
4555
- if (!seam || typeof seam.mount !== "function") throw new ValidationError(`driverExecutor: missing required seam "${nestedScopeSeamKey}" — a driver child must be spawned through a Scope that seeds it (the keystone scope does)`);
4556
- return seam;
4557
- }
4558
- function isAgent(value) {
4559
- return typeof value === "object" && value !== null && typeof value.act === "function" && typeof value.name === "string";
6064
+ /** True when a spec is a driver child (carries the role marker + a driver Agent). */
6065
+ function isDriverSpec(spec) {
6066
+ if (spec.driverRuntime !== "driver") return false;
6067
+ const driver = spec.driver;
6068
+ if (!isAgent(driver)) throw new ValidationError("driverExecutor: a driver-role spec must carry a `driver` Agent to run inside its nested scope");
6069
+ return true;
4560
6070
  }
4561
- //#endregion
4562
- //#region src/runtime/supervise/budget.ts
4563
6071
  /**
6072
+ * The recursive driver-executor factory. `withDriverExecutor` routes a child marked
6073
+ * `role: 'driver'` here; any other child resolves to a leaf built-in. On `execute`, it
6074
+ * reads the `nested-scope` seam the SCOPE seeded, mounts a nested `Scope` one `depth`
6075
+ * deeper over the driver's reserved child pool plus shared journal/blobs/registry, runs the driver
6076
+ * `Agent.act(task, nestedScope)`, and reports the conserved spend summed off the nested
6077
+ * tree's settled events — so the parent scope's reconcile rolls the whole sub-tree's spend
6078
+ * into the conserved total.
4564
6079
  *
4565
- * The conserved budget reservation pool the invariant the whole instrument
4566
- * rests on (critique M5/B3). One root `Budget` becomes a conserved pool of three
4567
- * quantities (tokens, usd, iterations) plus an absolute deadline. Children reserve
4568
- * atomically at spawn and reconcile at settle:
4569
- *
4570
- * total ≡ free + reserved + committed (for every known quantity)
4571
- *
4572
- * `reserve` moves a child's whole ceiling from `free` → `reserved` and fails closed
4573
- * when `free` can't cover it (never read-then-spawn overcommit, so `Σk(treatment) ≡
4574
- * Σk(blind)` by construction). `reconcile` releases the reservation, commits ACTUAL
4575
- * spend, and refunds the unspent remainder to `free`. Tokens and usd are separate
4576
- * channels (`LoopTokenUsage` has no `usd`); iterations are conserved alongside them.
4577
- *
4578
- * Pure and deterministic: `now()` is injected, there is no I/O, and no wall-clock or
4579
- * RNG read. A `reserve`/`reconcile` ticket is single-use (fail-loud on double or
4580
- * unknown reconcile) so a child can never refund twice. Reconciling an OPEN ticket always
4581
- * closes it: a fail-loud condition settles the reservation first and throws afterwards, so no
4582
- * error path can strand a reservation past the join barrier's `assertNoOpenTickets`.
4583
- * A child that declared no `maxUsd` reserved no dollar allocation, so its real dollars are
4584
- * committed as observed spend and debited from the root's balance rather than treated as an
4585
- * overspend of a $0 ceiling it never asked for. If dollar cost is unknowable under a
4586
- * dollar limit, reconciliation closes the known token/iteration work, marks the dollar channel
4587
- * unusable, and refuses later reservations; inventing a numeric dollar total would be worse.
4588
- * If a TOKEN count is unknowable (a provider that reported no usage for work that ran), the pool
4589
- * records the work, debits what it knows, and marks `readout().tokensKnown` false — the balance is
4590
- * then a ceiling, not a measurement. It does not close admission the way the dollar channel does:
4591
- * tokens are always capped, so one unreported turn must not end the run.
4592
- *
4593
- * @experimental
6080
+ * A `down` from the nested driver (a thrown `act` or an aborted scope) propagates as a
6081
+ * thrown executor, which the parent scope types into a `down` settlement the same
6082
+ * fail-loud-into-typed-down discipline a leaf gets.
4594
6083
  */
4595
- /** Fold a normalized `UsageEvent` array into a `Spend`. Tokens and usd are separate
4596
- * channels; iterations come from `'iteration'` events. Pure; `ms` stays zero (the
4597
- * pool does not read wall-clock). */
4598
- function spendFromUsageEvents(events) {
4599
- const tokens = zeroTokenUsage();
4600
- let usd = 0;
4601
- let usdKnown = true;
4602
- let iterations = 0;
4603
- for (const ev of events) if (ev.kind === "tokens") addTokenUsage(tokens, {
4604
- input: ev.input,
4605
- output: ev.output
4606
- });
4607
- else if (ev.kind === "cost") {
4608
- usd += ev.usd;
4609
- if (ev.usdKnown === false) usdKnown = false;
4610
- } else iterations += 1;
4611
- return {
4612
- iterations,
4613
- tokens,
4614
- usd,
4615
- ...usdKnown ? {} : { usdKnown: false },
4616
- ms: 0
6084
+ const driverExecutorFactory = (spec, ctx) => {
6085
+ if (!isDriverSpec(spec)) throw new ValidationError("driverExecutorFactory: spec is not a driver child (no role:\"driver\" marker)");
6086
+ const driver = spec.driver;
6087
+ const deliver = driver.deliver?.bind(driver);
6088
+ const journal = spec.journal;
6089
+ const seam = readNestedScopeSeam(ctx);
6090
+ let artifact;
6091
+ let meteredSpend;
6092
+ let accounting;
6093
+ let active;
6094
+ const closeActive = (reason) => {
6095
+ if (active === void 0) return Promise.resolve();
6096
+ active.close ??= closeNestedScope(active.scope, active.controller, reason);
6097
+ return active.close;
4617
6098
  };
4618
- }
4619
- async function foldUsage(events) {
4620
- if (Array.isArray(events)) return spendFromUsageEvents(events);
4621
- const tokens = zeroTokenUsage();
4622
- let usd = 0;
4623
- let usdKnown = true;
4624
- let iterations = 0;
4625
- for await (const ev of events) if (ev.kind === "tokens") addTokenUsage(tokens, {
4626
- input: ev.input,
4627
- output: ev.output
6099
+ const treeOwner = attestNestedDriverTreeOwner({
6100
+ runtime: driverRuntime,
6101
+ ...deliver ? { deliver(message) {
6102
+ return deliver(message) !== false;
6103
+ } } : {},
6104
+ async execute(task, signal) {
6105
+ const nestedRoot = nestedDriverTreeRoot(seam.journalRoot, seam.nodeId);
6106
+ await journal.beginTree(nestedRoot, (/* @__PURE__ */ new Date(0)).toISOString());
6107
+ const controller = new AbortController();
6108
+ const onParentAbort = () => controller.abort(signal.reason);
6109
+ if (signal.aborted) controller.abort(signal.reason);
6110
+ else signal.addEventListener("abort", onParentAbort, { once: true });
6111
+ const nestedScope = seam.mount(nestedRoot, controller.signal);
6112
+ active = {
6113
+ controller,
6114
+ scope: nestedScope
6115
+ };
6116
+ try {
6117
+ const out = await driverActAbortable(() => driver.act(task, nestedScope), controller.signal);
6118
+ await finalizeScopeOwnerMaterialization(nestedScope);
6119
+ await closeActive("driver completed");
6120
+ const events = await loadTreeEvents(journal, nestedRoot);
6121
+ const settled = events.filter(isSettled);
6122
+ const childWork = sumSpend(settled);
6123
+ meteredSpend = nonZeroOrUndef(sumMetered(events));
6124
+ accounting = {
6125
+ reported: childWork,
6126
+ reservation: addSpend$1(childWork, meteredSpend ?? zeroSpend())
6127
+ };
6128
+ const verdict = deriveDeliveryVerdict(settled, out);
6129
+ artifact = {
6130
+ outRef: `${driverRuntime}:${nestedRoot}`,
6131
+ out,
6132
+ spent: childWork,
6133
+ ...verdict ? { verdict } : {}
6134
+ };
6135
+ return artifact;
6136
+ } catch (err) {
6137
+ await finalizeScopeOwnerMaterialization(active?.scope ?? nestedScope).catch(() => void 0);
6138
+ await closeActive("driver stopped");
6139
+ const partial = await safeRollup(journal, nestedRoot);
6140
+ meteredSpend = partial?.metered;
6141
+ accounting = partial?.accounting;
6142
+ throw err;
6143
+ } finally {
6144
+ signal.removeEventListener("abort", onParentAbort);
6145
+ active = void 0;
6146
+ }
6147
+ },
6148
+ accounting() {
6149
+ return accounting;
6150
+ },
6151
+ metered() {
6152
+ return meteredSpend;
6153
+ },
6154
+ async teardown() {
6155
+ await closeActive("driver executor teardown");
6156
+ return { destroyed: true };
6157
+ },
6158
+ resultArtifact() {
6159
+ if (!artifact) throw new ValidationError("driverExecutor: resultArtifact() read before execute()");
6160
+ return artifact;
6161
+ }
4628
6162
  });
4629
- else if (ev.kind === "cost") {
4630
- usd += ev.usd;
4631
- if (ev.usdKnown === false) usdKnown = false;
4632
- } else iterations += 1;
6163
+ const ownerRuntime = runtimeOwnedScopeOwnerRuntime(driver);
6164
+ return ownerRuntime === void 0 ? treeOwner : attestRuntimeOwnedDeferredExecutor(treeOwner, ownerRuntime);
6165
+ };
6166
+ /**
6167
+ * Register the driver-executor so a child marked `role: 'driver'` resolves to it. The base
6168
+ * registry resolves by harness alone (it does not read `role`), so a recursive run needs a
6169
+ * registry that routes the driver tag here FIRST. Returns a registry decorator: a
6170
+ * driver-role spec → the driver-executor; everything else → the base registry's resolution
6171
+ * (leaf built-ins + BYO).
6172
+ */
6173
+ function withDriverExecutor(base) {
4633
6174
  return {
4634
- iterations,
4635
- tokens,
4636
- usd,
4637
- ...usdKnown ? {} : { usdKnown: false },
4638
- ms: 0
6175
+ register: base.register.bind(base),
6176
+ resolve(spec) {
6177
+ if (spec.driverRuntime === "driver" && !spec.executor) return {
6178
+ succeeded: true,
6179
+ value: driverExecutorFactory
6180
+ };
6181
+ return base.resolve(spec);
6182
+ }
4639
6183
  };
4640
6184
  }
4641
- function totalTokens(usage) {
4642
- return usage.input + usage.output;
6185
+ async function closeNestedScope(scope, controller, reason) {
6186
+ if (!controller.signal.aborted) controller.abort(reason);
6187
+ for (;;) if (await scope.next() === null) break;
6188
+ const view = scope.view;
6189
+ if (view.inFlight > 0 || view.waiting > 0) throw new ValidationError(`driverExecutor: nested cleanup left ${view.inFlight} running and ${view.waiting} waiting nodes`);
4643
6190
  }
4644
- /**
4645
- * Create a conserved reservation pool from a root `Budget`. `now()` is injected so the
4646
- * deadline readout is deterministic; defaults to `Date.now` for non-test callers. The
4647
- * absolute deadline is fixed at construction (`now() + budget.deadlineMs`) so the
4648
- * readout's `deadlineMs` is a stable wall-clock instant, not a shrinking remainder.
4649
- */
4650
- function createBudgetPool(root, now = Date.now) {
4651
- let freeTokens = root.maxTokens;
4652
- let reservedTokens = 0;
4653
- let committedTokens = 0;
4654
- let tokensTainted = false;
4655
- const usdCapped = root.maxUsd !== void 0;
4656
- let freeUsd = root.maxUsd ?? 0;
4657
- let reservedUsd = 0;
4658
- let committedUsd = 0;
4659
- let usdTainted = false;
4660
- let freeIterations = root.maxIterations;
4661
- let reservedIterations = 0;
4662
- let committedIterations = 0;
4663
- const absoluteDeadlineMs = root.deadlineMs !== void 0 ? now() + root.deadlineMs : 0;
4664
- let nextTicketId = 0;
4665
- const open = /* @__PURE__ */ new Set();
4666
- function reserve(b) {
4667
- const wantTokens = b.maxTokens;
4668
- const wantUsd = b.maxUsd ?? 0;
4669
- const wantIterations = b.maxIterations;
4670
- if (usdCapped && usdTainted) return {
4671
- ok: false,
4672
- reason: "budget-exhausted"
4673
- };
4674
- if (wantTokens > freeTokens) return {
4675
- ok: false,
4676
- reason: "budget-exhausted"
4677
- };
4678
- if (wantIterations > freeIterations) return {
4679
- ok: false,
4680
- reason: "budget-exhausted"
4681
- };
4682
- if (wantUsd > 0 && !usdCapped) return {
4683
- ok: false,
4684
- reason: "usd-unbudgeted"
4685
- };
4686
- if (wantUsd > freeUsd) return {
4687
- ok: false,
4688
- reason: "budget-exhausted"
6191
+ async function driverActAbortable(act, signal) {
6192
+ if (signal.aborted) throw driverAbortError(signal);
6193
+ return await new Promise((resolve, reject) => {
6194
+ let settled = false;
6195
+ const cleanup = () => signal.removeEventListener("abort", onAbort);
6196
+ const onAbort = () => {
6197
+ queueMicrotask(() => {
6198
+ if (settled) return;
6199
+ settled = true;
6200
+ cleanup();
6201
+ reject(driverAbortError(signal));
6202
+ });
4689
6203
  };
4690
- freeTokens -= wantTokens;
4691
- reservedTokens += wantTokens;
4692
- freeIterations -= wantIterations;
4693
- reservedIterations += wantIterations;
4694
- if (wantUsd > 0) {
4695
- freeUsd -= wantUsd;
4696
- reservedUsd += wantUsd;
6204
+ signal.addEventListener("abort", onAbort, { once: true });
6205
+ let work;
6206
+ try {
6207
+ work = Promise.resolve(act());
6208
+ } catch (error) {
6209
+ cleanup();
6210
+ reject(error);
6211
+ return;
4697
6212
  }
4698
- const id = nextTicketId++;
4699
- open.add(id);
4700
- return {
4701
- ok: true,
4702
- ticket: {
4703
- id,
4704
- reserved: {
4705
- tokens: wantTokens,
4706
- usd: wantUsd,
4707
- iterations: wantIterations,
4708
- usdBudgeted: b.maxUsd !== void 0
4709
- }
4710
- }
4711
- };
4712
- }
4713
- function reconcile(ticket, spent) {
4714
- if (!open.has(ticket.id)) throw new Error(`budget pool: reconcile of unknown or already-settled ticket ${ticket.id}`);
4715
- const { tokens: rTokens, usd: rUsd, iterations: rIterations } = ticket.reserved;
4716
- const unknownUnderCap = usdCapped && spent.usdKnown === false;
4717
- const spentTokens = totalTokens(spent.tokens);
4718
- const usdBudgeted = ticket.reserved.usdBudgeted !== false;
4719
- let violation;
4720
- if (spentTokens > rTokens) violation = `ticket ${ticket.id} spent ${spentTokens} tokens > reserved ${rTokens}`;
4721
- else if (spent.iterations > rIterations) violation = `ticket ${ticket.id} spent ${spent.iterations} iterations > reserved ${rIterations}`;
4722
- else if (usdCapped && usdBudgeted && spent.usd > rUsd) violation = `ticket ${ticket.id} spent $${spent.usd} > reserved $${rUsd}`;
4723
- else if (unknownUnderCap) violation = `ticket ${ticket.id} reported unknown dollar cost under a dollar-capped budget`;
4724
- open.delete(ticket.id);
4725
- if (spent.tokensKnown === false) tokensTainted = true;
4726
- reservedTokens -= rTokens;
4727
- committedTokens += spentTokens;
4728
- freeTokens += rTokens - spentTokens;
4729
- reservedIterations -= rIterations;
4730
- committedIterations += spent.iterations;
4731
- freeIterations += rIterations - spent.iterations;
4732
- if (usdCapped) {
4733
- reservedUsd -= rUsd;
4734
- committedUsd += spent.usd;
4735
- if (unknownUnderCap) {
4736
- usdTainted = true;
4737
- freeUsd = 0;
4738
- } else freeUsd += rUsd - spent.usd;
4739
- } else committedUsd += spent.usd;
4740
- if (violation !== void 0) throw new Error(`budget pool: ${violation}`);
6213
+ work.then((value) => {
6214
+ if (settled) return;
6215
+ settled = true;
6216
+ cleanup();
6217
+ resolve(value);
6218
+ }, (error) => {
6219
+ if (settled) return;
6220
+ settled = true;
6221
+ cleanup();
6222
+ reject(error);
6223
+ });
6224
+ });
6225
+ }
6226
+ function driverAbortError(signal) {
6227
+ const reason = signal.reason;
6228
+ const error = new Error(typeof reason === "string" && reason.length > 0 ? reason : "driver aborted");
6229
+ error.name = "AbortError";
6230
+ return error;
6231
+ }
6232
+ /** The nested tree's full event list — the one evidence the spend, verdict, AND driver-inference
6233
+ * roll-ups read off the same journal the supervisor sums. */
6234
+ async function loadTreeEvents(journal, nestedRoot) {
6235
+ const events = await journal.loadTree(nestedRoot);
6236
+ if (events === void 0) throw new ValidationError(`driverExecutor: nested tree '${nestedRoot}' missing from the journal after run (corrupted log)`);
6237
+ return events;
6238
+ }
6239
+ function isSettled(ev) {
6240
+ return ev.kind === "settled";
6241
+ }
6242
+ /** Sum the conserved spend over the nested tree's settled events — the honest per-channel
6243
+ * roll-up of the whole sub-tree's child WORK. */
6244
+ function sumSpend(settled) {
6245
+ const total = {
6246
+ iterations: 0,
6247
+ tokens: {
6248
+ input: 0,
6249
+ output: 0
6250
+ },
6251
+ usd: 0,
6252
+ ms: 0
6253
+ };
6254
+ for (const ev of settled) {
6255
+ total.iterations += ev.spent.iterations;
6256
+ total.tokens.input += ev.spent.tokens.input;
6257
+ total.tokens.output += ev.spent.tokens.output;
6258
+ if (ev.spent.tokensKnown === false) total.tokensKnown = false;
6259
+ total.usd += ev.spent.usd;
6260
+ if (ev.spent.tokensKnown === false) total.tokensKnown = false;
6261
+ if (ev.spent.usdKnown === false) total.usdKnown = false;
6262
+ total.ms += ev.spent.ms;
4741
6263
  }
4742
- function observe(spend) {
4743
- if (usdCapped && spend.usdKnown === false) throw new Error("budget pool: cannot observe unknown dollar cost under a dollar-capped budget");
4744
- if (spend.tokensKnown === false) tokensTainted = true;
4745
- const tokens = totalTokens(spend.tokens);
4746
- freeTokens -= tokens;
4747
- committedTokens += tokens;
4748
- freeIterations -= spend.iterations;
4749
- committedIterations += spend.iterations;
4750
- committedUsd += spend.usd;
4751
- if (usdCapped) freeUsd -= spend.usd;
6264
+ return total;
6265
+ }
6266
+ /** Sum the nested tree's `metered` events — the sub-tree's whole driver INFERENCE (this driver's
6267
+ * own turns + any sub-driver inference already re-homed into this tree). Re-homed up to the parent
6268
+ * as one `metered` event; never reconciled (already pool-debited live via `observe`). */
6269
+ function sumMetered(events) {
6270
+ const total = {
6271
+ iterations: 0,
6272
+ tokens: {
6273
+ input: 0,
6274
+ output: 0
6275
+ },
6276
+ usd: 0,
6277
+ ms: 0
6278
+ };
6279
+ for (const ev of events) {
6280
+ if (ev.kind !== "metered") continue;
6281
+ total.iterations += ev.spend.iterations;
6282
+ total.tokens.input += ev.spend.tokens.input;
6283
+ total.tokens.output += ev.spend.tokens.output;
6284
+ if (ev.spend.tokensKnown === false) total.tokensKnown = false;
6285
+ total.usd += ev.spend.usd;
6286
+ if (ev.spend.tokensKnown === false) total.tokensKnown = false;
6287
+ if (ev.spend.usdKnown === false) total.usdKnown = false;
6288
+ total.ms += ev.spend.ms;
4752
6289
  }
4753
- function readout() {
6290
+ return total;
6291
+ }
6292
+ function zeroSpend() {
6293
+ return {
6294
+ iterations: 0,
6295
+ tokens: {
6296
+ input: 0,
6297
+ output: 0
6298
+ },
6299
+ usd: 0,
6300
+ ms: 0
6301
+ };
6302
+ }
6303
+ function addSpend$1(a, b) {
6304
+ return {
6305
+ iterations: a.iterations + b.iterations,
6306
+ tokens: {
6307
+ input: a.tokens.input + b.tokens.input,
6308
+ output: a.tokens.output + b.tokens.output
6309
+ },
6310
+ ...a.tokensKnown === false || b.tokensKnown === false ? { tokensKnown: false } : {},
6311
+ usd: a.usd + b.usd,
6312
+ ...a.usdKnown === false || b.usdKnown === false ? { usdKnown: false } : {},
6313
+ ms: a.ms + b.ms
6314
+ };
6315
+ }
6316
+ /** An all-zero spend that carries an UNKNOWN marker counts as non-zero: a sub-driver whose turns
6317
+ * went unmeasured did real work, and dropping its `metered` event here would re-hide upstream the
6318
+ * very turn the driver refused to skip. */
6319
+ function isNonZeroSpend(s) {
6320
+ return s.iterations > 0 || s.tokens.input > 0 || s.tokens.output > 0 || s.usd > 0 || s.ms > 0 || s.tokensKnown === false || s.usdKnown === false;
6321
+ }
6322
+ /** A spend, or `undefined` when it is all-zero — so `metered()` returns undefined for a driver
6323
+ * whose sub-tree did no inference (and the parent journals no empty `metered` event). */
6324
+ function nonZeroOrUndef(s) {
6325
+ return isNonZeroSpend(s) ? s : void 0;
6326
+ }
6327
+ /** Sum the nested tree's metered events, tolerating a missing tree (a crash before `beginTree`
6328
+ * landed) — never throw here, or it would mask the original `act` error on the crash path. */
6329
+ async function safeRollup(journal, nestedRoot) {
6330
+ try {
6331
+ const events = await loadTreeEvents(journal, nestedRoot);
6332
+ const childWork = sumSpend(events.filter(isSettled));
6333
+ const metered = nonZeroOrUndef(sumMetered(events));
4754
6334
  return {
4755
- tokensLeft: freeTokens,
4756
- tokensKnown: !tokensTainted,
4757
- usdLeft: usdCapped ? usdTainted ? 0 : freeUsd : 0,
4758
- usdCapped,
4759
- deadlineMs: absoluteDeadlineMs,
4760
- reservedTokens
6335
+ metered,
6336
+ accounting: {
6337
+ reported: childWork,
6338
+ reservation: addSpend$1(childWork, metered ?? zeroSpend())
6339
+ }
4761
6340
  };
6341
+ } catch {
6342
+ return;
4762
6343
  }
4763
- function assertNoOpenTickets() {
4764
- if (open.size > 0) throw new Error(`budget pool: ${open.size} reservation(s) still open at join barrier (leaked ticket ids: ${[...open].join(", ")}) — conserved-pool invariant violated`);
6344
+ }
6345
+ /** Derive the driver child's delivery verdict from its DIRECT children's settlements and the
6346
+ * manager's actual finalized output: `valid` iff a direct child delivered AND the finalizer
6347
+ * returned a defined output;
6348
+ * `score` = the best delivered score. Returns `undefined` when no child settled at all (the
6349
+ * driver itself produced nothing to bubble a verdict from). Fail-closed: a child whose verdict
6350
+ * carried no `valid` counts as not-delivered. */
6351
+ function deriveDeliveryVerdict(settled, finalizedOutput) {
6352
+ let sawChild = false;
6353
+ let anyValid = false;
6354
+ let bestValidScore;
6355
+ let bestDoneScore;
6356
+ for (const ev of settled) {
6357
+ sawChild = true;
6358
+ if (ev.status !== "done") continue;
6359
+ const score = ev.verdict?.score;
6360
+ if (score !== void 0 && (bestDoneScore === void 0 || score > bestDoneScore)) bestDoneScore = score;
6361
+ if (ev.verdict?.valid === true) {
6362
+ anyValid = true;
6363
+ if (score !== void 0 && (bestValidScore === void 0 || score > bestValidScore)) bestValidScore = score;
6364
+ }
4765
6365
  }
6366
+ if (!sawChild) return void 0;
6367
+ const accepted = anyValid && finalizedOutput !== void 0;
4766
6368
  return {
4767
- reserve,
4768
- reconcile,
4769
- spendFrom: foldUsage,
4770
- readout,
4771
- observe,
4772
- assertNoOpenTickets
6369
+ valid: accepted,
6370
+ score: accepted ? bestValidScore ?? 1 : bestDoneScore ?? 0
4773
6371
  };
4774
6372
  }
6373
+ function readNestedScopeSeam(ctx) {
6374
+ const seam = ctx.seams[nestedScopeSeamKey];
6375
+ if (!seam || typeof seam.mount !== "function") throw new ValidationError(`driverExecutor: missing required seam "${nestedScopeSeamKey}" — a driver child must be spawned through a Scope that seeds it (the keystone scope does)`);
6376
+ return seam;
6377
+ }
6378
+ function isAgent(value) {
6379
+ return typeof value === "object" && value !== null && typeof value.act === "function" && typeof value.name === "string";
6380
+ }
4775
6381
  //#endregion
4776
6382
  //#region src/runtime/supervise/finalizer.ts
4777
6383
  /**
@@ -4923,20 +6529,99 @@ function keyedAssignments(events, settled) {
4923
6529
  const spawns = events.filter((ev) => ev.kind === "spawned").sort((a, b) => a.seq - b.seq);
4924
6530
  for (const ev of spawns) {
4925
6531
  if (ev.key === void 0) continue;
6532
+ if (ev.identity === void 0) throw new RuntimeRunStateError(`supervisor: keyed node '${ev.id}' has no durable execution identity`);
4926
6533
  const s = byId.get(ev.id);
4927
6534
  keys.set(ev.key, s === void 0 ? {
4928
6535
  id: ev.id,
4929
6536
  label: ev.label,
6537
+ identity: ev.identity,
4930
6538
  state: "in-doubt"
4931
6539
  } : {
4932
6540
  id: ev.id,
4933
6541
  label: ev.label,
6542
+ identity: ev.identity,
4934
6543
  state: s.kind === "done" ? "completed" : "down",
4935
6544
  settled: s
4936
6545
  });
4937
6546
  }
4938
6547
  return keys;
4939
6548
  }
6549
+ /** A resumed process may continue only the exact root contract first recorded for this run id. */
6550
+ function assertResumeContract(events, opts) {
6551
+ const roots = events.filter((event) => event.kind === "spawned" && event.id === opts.runId && event.parent === void 0);
6552
+ if (roots.length !== 1) throw new RuntimeRunStateError(`supervisor: resumed run '${opts.runId}' must contain exactly one root identity event; found ${roots.length}`);
6553
+ const recorded = roots[0];
6554
+ if (contentAddress(recorded.budget) !== contentAddress(opts.budget)) throw new RuntimeRunStateError(`supervisor: resume budget mismatch for run '${opts.runId}'; use a new runId to change limits`);
6555
+ if (!sameOptionalIdentity(recorded.identity, opts.rootIdentity)) throw new RuntimeRunStateError(`supervisor: resume identity mismatch for run '${opts.runId}'; task, profile, candidate, and correlation must match`);
6556
+ const receipts = events.filter((event) => event.kind === "materialized" && event.id === opts.runId);
6557
+ const expectedReceipt = rootMaterializationReceipt(opts);
6558
+ if (expectedReceipt === void 0) {
6559
+ if (receipts.length > 1) throw new RuntimeRunStateError(`supervisor: resumed run '${opts.runId}' contains duplicate root materialization evidence`);
6560
+ } else if (receipts.length !== 1 || contentAddress(receipts[0]?.receipt) !== contentAddress(expectedReceipt)) throw new RuntimeRunStateError(`supervisor: resume materialization mismatch for run '${opts.runId}'; backend, model, execution identity, and plan must match`);
6561
+ return recorded;
6562
+ }
6563
+ /** Mint root evidence at the trusted composition boundary. A generic in-process Agent root has no
6564
+ * executor declaration and remains explicitly unknown rather than inheriting fabricated details. */
6565
+ function rootMaterializationReceipt(opts) {
6566
+ const declaration = opts.rootMaterialization;
6567
+ if (declaration === void 0) return unknownMaterializationReceipt({
6568
+ ...opts.rootIdentity?.profileDigest === void 0 ? {} : { authoredProfileDigest: opts.rootIdentity.profileDigest },
6569
+ runtime: "inline",
6570
+ reason: "root-agent-did-not-report"
6571
+ });
6572
+ if (declaration.declaration === "deferred") return void 0;
6573
+ if (opts.rootIdentity?.profileDigest === void 0) throw new RuntimeRunStateError(`supervisor: run '${opts.runId}' cannot record known root materialization without an exact profileDigest`);
6574
+ try {
6575
+ return knownMaterializationReceipt({
6576
+ authoredProfileDigest: opts.rootIdentity.profileDigest,
6577
+ runtime: declaration.runtime,
6578
+ declaration: declaration.declaration
6579
+ });
6580
+ } catch (error) {
6581
+ throw new RuntimeRunStateError(`supervisor: run '${opts.runId}' has invalid root materialization evidence`, { cause: error });
6582
+ }
6583
+ }
6584
+ function rootExecutionBindingReceipt(opts, materialization, attemptId) {
6585
+ const root = opts.rootMaterialization;
6586
+ if (root?.declaration === "deferred") return void 0;
6587
+ if (root === void 0) return unknownExecutionBindingReceipt(materialization, attemptId, "root-agent-did-not-report");
6588
+ try {
6589
+ return knownExecutionBindingReceipt(materialization, {
6590
+ attemptId,
6591
+ ...root.binding
6592
+ });
6593
+ } catch (error) {
6594
+ throw new RuntimeRunStateError(`supervisor: run '${opts.runId}' has invalid root execution binding`, { cause: error });
6595
+ }
6596
+ }
6597
+ function sameOptionalIdentity(recorded, requested) {
6598
+ if (recorded === void 0 || requested === void 0) return recorded === requested;
6599
+ return contentAddress(recorded) === contentAddress(requested);
6600
+ }
6601
+ /** Validate caller-supplied root identity before any journal access. Fresh runs may omit identity,
6602
+ * but once supplied it is trusted durable evidence and therefore must be complete and canonical;
6603
+ * resumed runs always require it so the prior root can be matched exactly. */
6604
+ function assertRootIdentity(opts) {
6605
+ const identity = opts.rootIdentity;
6606
+ if (identity === void 0) {
6607
+ if (opts.resume !== true) return;
6608
+ throw new RuntimeRunStateError(`supervisor: resumed run '${opts.runId}' requires an exact rootIdentity with profileDigest and taskDigest`);
6609
+ }
6610
+ const unknownFields = Object.keys(identity).filter((key) => key !== "profileDigest" && key !== "taskDigest" && key !== "candidateDigest" && key !== "correlation");
6611
+ const correlation = identity.correlation;
6612
+ const validCorrelation = correlation === void 0 || typeof correlation === "object" && correlation !== null && !Array.isArray(correlation) && Object.entries(correlation).every(([key, value]) => key.length > 0 && typeof value === "string" && value.length > 0);
6613
+ if (unknownFields.length > 0 || !sha256DigestSchema.safeParse(identity.profileDigest).success || !sha256DigestSchema.safeParse(identity.taskDigest).success || identity.candidateDigest !== void 0 && !sha256DigestSchema.safeParse(identity.candidateDigest).success || !validCorrelation) throw new RuntimeRunStateError(`supervisor: run '${opts.runId}' requires an exact rootIdentity with valid profileDigest, taskDigest, candidateDigest, and correlation`);
6614
+ }
6615
+ function rootDeadline(root) {
6616
+ const startedAt = Date.parse(root.at);
6617
+ if (!Number.isFinite(startedAt)) throw new RuntimeRunStateError(`supervisor: root event for '${root.id}' has an invalid timestamp '${root.at}'`);
6618
+ return startedAt + (root.budget.deadlineMs ?? 0);
6619
+ }
6620
+ /** Child reservations whose spawn was durable but whose terminal record never landed. */
6621
+ function uncertainSpawnBudgets(events) {
6622
+ const terminal = new Set(events.filter((event) => event.kind === "settled" || event.kind === "cancelled" || event.kind === "woken").map((event) => event.id));
6623
+ return events.filter((event) => event.kind === "spawned" && event.parent !== void 0 && !terminal.has(event.id)).map((event) => event.budget);
6624
+ }
4940
6625
  /** Highest `seq` among events matching `pred`, or `-1` when none match (so a resumed scope's
4941
6626
  * first new ordinal/seq is 0 — the same start a fresh scope uses). */
4942
6627
  function maxSeqOf(events, pred) {
@@ -4974,129 +6659,239 @@ function describeRejection(error) {
4974
6659
  function createSupervisor() {
4975
6660
  let attached;
4976
6661
  async function run(root, task, opts) {
4977
- const now = opts.now ?? Date.now;
4978
- const pool = createBudgetPool(opts.budget, now);
4979
- const prior = opts.resume === true ? await opts.journal.loadTree(opts.runId) : void 0;
4980
- const resuming = prior !== void 0 && prior.length > 0;
4981
- let resumeFrom;
4982
- if (resuming) {
4983
- const settled = await replaySpawnTree(opts.journal, opts.blobs, opts.runId);
4984
- resumeFrom = {
4985
- settled,
4986
- view: materializeTreeView(prior),
4987
- maxSpawnOrdinal: maxSeqOf(prior, (ev) => ev.kind === "spawned"),
4988
- maxCursorSeq: maxSeqOf(prior, (ev) => ev.kind === "settled" || ev.kind === "cancelled" || ev.kind === "woken"),
4989
- maxWaitOrdinal: maxSeqOf(prior, (ev) => ev.kind === "waiting"),
4990
- waits: pendingWaits(prior),
4991
- keys: keyedAssignments(prior, settled),
4992
- priorSpend: sumSpendFromEvents(prior)
4993
- };
4994
- } else {
4995
- await opts.journal.beginTree(opts.runId, new Date(now()).toISOString());
4996
- await opts.journal.appendEvent(opts.runId, {
4997
- kind: "spawned",
4998
- id: opts.runId,
4999
- label: "root",
5000
- budget: opts.budget,
5001
- runtime: "inline",
5002
- seq: 0,
5003
- at: new Date(now()).toISOString()
5004
- });
5005
- }
5006
- const controller = new AbortController();
5007
- const cascadeAbort = (reason) => {
5008
- if (controller.signal.aborted) return;
5009
- controller.abort(reason);
5010
- };
5011
- const onCallerAbort = () => cascadeAbort("caller signal aborted");
5012
- if (opts.signal) if (opts.signal.aborted) cascadeAbort("caller signal aborted");
5013
- else opts.signal.addEventListener("abort", onCallerAbort, { once: true });
5014
- const breaker = createIntensityBreaker(opts, () => cascadeAbort("intensity breaker tripped"));
5015
- const journal = wrapJournalForBreaker(opts.journal, breaker);
5016
- const scope = createScope({
5017
- parentId: opts.runId,
5018
- root: opts.runId,
5019
- pool,
5020
- journal,
5021
- blobs: opts.blobs,
5022
- executors: opts.executors,
5023
- seams: {},
5024
- depth: 0,
5025
- maxDepth: opts.maxDepth ?? defaultMaxDepth,
5026
- signal: controller.signal,
5027
- now,
5028
- hooks: opts.hooks,
5029
- ...opts.probes ? { probes: opts.probes } : {},
5030
- ...opts.workerTrace ? { workerTrace: opts.workerTrace } : {},
5031
- ...resumeFrom ? { resumeFrom } : {}
5032
- });
5033
- const openScope = scope;
5034
- if (attached) attached.bind({
5035
- scope: openScope,
5036
- cascadeAbort,
5037
- signal: pushRootSignal(cascadeAbort)
6662
+ const { budget, rootIdentity, rootMaterialization, runId, journal: journalStore, blobs: blobStore, executors: executorRegistry, probes, maxDepth, maxLiveWorkers, maxRestarts, withinMs, resume, now: suppliedNow, signal, hooks, workerTrace } = opts;
6663
+ const input = detachedSnapshot({
6664
+ task,
6665
+ options: {
6666
+ budget,
6667
+ runId,
6668
+ ...rootIdentity === void 0 ? {} : { rootIdentity },
6669
+ ...rootMaterialization === void 0 ? {} : { rootMaterialization },
6670
+ ...maxDepth === void 0 ? {} : { maxDepth },
6671
+ ...maxLiveWorkers === void 0 ? {} : { maxLiveWorkers },
6672
+ ...maxRestarts === void 0 ? {} : { maxRestarts },
6673
+ ...withinMs === void 0 ? {} : { withinMs },
6674
+ ...resume === void 0 ? {} : { resume }
6675
+ }
6676
+ }, "supervisor.run");
6677
+ opts = Object.freeze({
6678
+ ...input.options,
6679
+ journal: journalStore,
6680
+ blobs: blobStore,
6681
+ executors: executorRegistry,
6682
+ ...probes === void 0 ? {} : { probes },
6683
+ ...suppliedNow === void 0 ? {} : { now: suppliedNow },
6684
+ ...signal === void 0 ? {} : { signal },
6685
+ ...hooks === void 0 ? {} : { hooks },
6686
+ ...workerTrace === void 0 ? {} : { workerTrace }
5038
6687
  });
5039
- let actOutcome;
6688
+ task = input.task;
6689
+ const rootAct = root.act.bind(root);
6690
+ const rootDeliver = root.deliver?.bind(root);
6691
+ const now = opts.now ?? Date.now;
6692
+ assertRootIdentity(opts);
6693
+ const rootLease = attached?.acquire();
5040
6694
  try {
5041
- actOutcome = {
5042
- ok: true,
5043
- out: await root.act(task, scope)
6695
+ const rootAttemptId = newExecutionAttemptId(opts.runId);
6696
+ const runStartedAtMs = now();
6697
+ const runStartedAt = new Date(runStartedAtMs).toISOString();
6698
+ const existing = await opts.journal.loadTree(opts.runId);
6699
+ if (opts.resume !== true && existing !== void 0) throw new RuntimeRunStateError(`supervisor: runId '${opts.runId}' already exists; pass resume: true to continue it or use a new runId`);
6700
+ const prior = opts.resume === true ? existing : void 0;
6701
+ const resuming = prior !== void 0 && prior.length > 0;
6702
+ let resumeFrom;
6703
+ let pool;
6704
+ if (resuming) {
6705
+ const rootEvent = assertResumeContract(prior, opts);
6706
+ const measured = sumMeasuredSpendFromEvents(prior);
6707
+ const uncertainReservations = uncertainSpawnBudgets(prior);
6708
+ pool = createBudgetPool(opts.budget, now, {
6709
+ committed: addSpend(measured.childWork, measured.driverInference),
6710
+ uncertainReservations,
6711
+ ...rootEvent.budget.deadlineMs !== void 0 ? { absoluteDeadlineMs: rootDeadline(rootEvent) } : {}
6712
+ });
6713
+ const settled = await replaySpawnTree(opts.journal, opts.blobs, opts.runId);
6714
+ resumeFrom = {
6715
+ settled,
6716
+ view: materializeTreeView(prior),
6717
+ maxSpawnOrdinal: maxSeqOf(prior, (ev) => ev.kind === "spawned"),
6718
+ maxCursorSeq: maxSeqOf(prior, (ev) => ev.kind === "settled" || ev.kind === "cancelled" || ev.kind === "woken"),
6719
+ maxWaitOrdinal: maxSeqOf(prior, (ev) => ev.kind === "waiting"),
6720
+ waits: pendingWaits(prior),
6721
+ keys: keyedAssignments(prior, settled),
6722
+ priorSpend: sumSpendFromEvents(prior)
6723
+ };
6724
+ } else {
6725
+ pool = createBudgetPool(opts.budget, now, { ...opts.budget.deadlineMs !== void 0 ? { absoluteDeadlineMs: runStartedAtMs + opts.budget.deadlineMs } : {} });
6726
+ await opts.journal.beginTree(opts.runId, runStartedAt);
6727
+ const rootReceipt = rootMaterializationReceipt(opts);
6728
+ const rootRuntime = opts.rootMaterialization?.runtime ?? "inline";
6729
+ await opts.journal.appendEvent(opts.runId, {
6730
+ kind: "spawned",
6731
+ id: opts.runId,
6732
+ label: "root",
6733
+ budget: opts.budget,
6734
+ runtime: rootRuntime,
6735
+ ...opts.rootIdentity ? { identity: opts.rootIdentity } : {},
6736
+ seq: 0,
6737
+ at: runStartedAt
6738
+ });
6739
+ if (rootReceipt !== void 0) await opts.journal.appendEvent(opts.runId, {
6740
+ kind: "materialized",
6741
+ id: opts.runId,
6742
+ receipt: rootReceipt,
6743
+ seq: 0,
6744
+ at: runStartedAt
6745
+ });
6746
+ }
6747
+ const stableRootReceipt = resuming ? prior?.find((event) => event.kind === "materialized" && event.id === opts.runId)?.receipt : rootMaterializationReceipt(opts);
6748
+ if (stableRootReceipt !== void 0) {
6749
+ const rootBinding = rootExecutionBindingReceipt(opts, stableRootReceipt, rootAttemptId);
6750
+ if (rootBinding !== void 0) await opts.journal.appendEvent(opts.runId, {
6751
+ kind: "execution-bound",
6752
+ id: opts.runId,
6753
+ binding: rootBinding,
6754
+ seq: 0,
6755
+ at: runStartedAt
6756
+ });
6757
+ }
6758
+ const controller = new AbortController();
6759
+ const cascadeAbort = (reason) => {
6760
+ if (controller.signal.aborted) return false;
6761
+ controller.abort(reason);
6762
+ return true;
5044
6763
  };
5045
- } catch (error) {
5046
- actOutcome = {
6764
+ const onCallerAbort = () => cascadeAbort("caller signal aborted");
6765
+ if (opts.signal) if (opts.signal.aborted) cascadeAbort("caller signal aborted");
6766
+ else opts.signal.addEventListener("abort", onCallerAbort, { once: true });
6767
+ const breaker = createIntensityBreaker(opts, () => cascadeAbort("intensity breaker tripped"));
6768
+ const journal = wrapJournalForBreaker(opts.journal, breaker);
6769
+ const priorRootMaterialization = prior?.find((event) => event.kind === "materialized" && event.id === opts.runId)?.receipt;
6770
+ const scope = createScope({
6771
+ parentId: opts.runId,
6772
+ root: opts.runId,
6773
+ pool,
6774
+ journal,
6775
+ blobs: opts.blobs,
6776
+ executors: opts.executors,
6777
+ seams: {},
6778
+ depth: 0,
6779
+ maxDepth: opts.maxDepth ?? defaultMaxDepth,
6780
+ ...opts.maxLiveWorkers !== void 0 ? { maxLiveWorkers: opts.maxLiveWorkers } : {},
6781
+ signal: controller.signal,
6782
+ now,
6783
+ hooks: opts.hooks,
6784
+ ...opts.rootMaterialization?.declaration === "deferred" ? { ownerMaterialization: {
6785
+ runtime: opts.rootMaterialization.runtime,
6786
+ authoredProfile: opts.rootMaterialization.authoredProfile,
6787
+ attemptId: rootAttemptId,
6788
+ requiredKnown: true,
6789
+ ...priorRootMaterialization === void 0 ? {} : { prior: priorRootMaterialization }
6790
+ } } : {},
6791
+ ...opts.probes ? { probes: opts.probes } : {},
6792
+ ...opts.workerTrace ? { workerTrace: opts.workerTrace } : {},
6793
+ ...resumeFrom ? { resumeFrom } : {}
6794
+ });
6795
+ const openScope = scope;
6796
+ if (rootLease) rootLease.bind({
6797
+ scope: openScope,
6798
+ cascadeAbort,
6799
+ signal: pushRootSignal(cascadeAbort),
6800
+ deliver: rootDeliver ? (message) => rootDeliver(message) !== false : () => false
6801
+ });
6802
+ let deadlineExceeded = false;
6803
+ const rootDeadlineAtMs = pool.readout().deadlineMs;
6804
+ if (rootDeadlineAtMs > 0 && now() >= rootDeadlineAtMs) deadlineExceeded = cascadeAbort("root budget deadline exceeded");
6805
+ const clearRootDeadline = opts.budget.deadlineMs === void 0 ? void 0 : armDeadlineTimer(Math.max(0, rootDeadlineAtMs - now()), () => {
6806
+ deadlineExceeded = cascadeAbort("root budget deadline exceeded");
6807
+ });
6808
+ let actOutcome = {
5047
6809
  ok: false,
5048
- error
6810
+ error: new RuntimeRunStateError("supervisor: root execution did not start")
5049
6811
  };
5050
- } finally {
5051
- await drainLiveChildren(openScope, controller);
5052
- if (opts.signal) opts.signal.removeEventListener("abort", onCallerAbort);
5053
- if (attached) attached.unbind();
5054
- }
5055
- const tree = runTree(scope);
5056
- if (actOutcome.ok) {
6812
+ let executionAborted = controller.signal.aborted;
6813
+ try {
6814
+ actOutcome = {
6815
+ ok: true,
6816
+ out: await runAbortable(() => rootAct(task, scope), controller.signal)
6817
+ };
6818
+ } catch (error) {
6819
+ actOutcome = {
6820
+ ok: false,
6821
+ error
6822
+ };
6823
+ } finally {
6824
+ executionAborted = controller.signal.aborted;
6825
+ if (!controller.signal.aborted && rootDeadlineAtMs > 0 && now() >= rootDeadlineAtMs) deadlineExceeded = true;
6826
+ clearRootDeadline?.();
6827
+ try {
6828
+ await drainLiveChildren(openScope, controller);
6829
+ } catch (error) {
6830
+ if (actOutcome?.ok !== false) actOutcome = {
6831
+ ok: false,
6832
+ error
6833
+ };
6834
+ }
6835
+ try {
6836
+ await finalizeScopeOwnerMaterialization(openScope);
6837
+ } catch (error) {
6838
+ if (actOutcome?.ok !== false) actOutcome = {
6839
+ ok: false,
6840
+ error
6841
+ };
6842
+ }
6843
+ if (opts.signal) opts.signal.removeEventListener("abort", onCallerAbort);
6844
+ rootLease?.release();
6845
+ }
6846
+ const tree = runTree(scope);
5057
6847
  pool.assertNoOpenTickets();
5058
- const out = actOutcome.out;
5059
- if (out !== void 0) {
5060
- const outRef = contentAddress(out);
5061
- await opts.blobs.put(outRef, out);
6848
+ if (actOutcome.ok) {
6849
+ if (executionAborted) return noWinner();
6850
+ const out = actOutcome.out;
6851
+ if (out !== void 0) {
6852
+ const outRef = contentAddress(out);
6853
+ await opts.blobs.put(outRef, out);
6854
+ const { childWork, driverInference } = await spentFromJournal(journal, opts.runId);
6855
+ return {
6856
+ kind: "winner",
6857
+ out,
6858
+ outRef,
6859
+ tree,
6860
+ spentTotal: addSpend(childWork, driverInference),
6861
+ ...isNonEmptySpend(driverInference) ? { spentBreakdown: {
6862
+ driverInference,
6863
+ childWork
6864
+ } } : {}
6865
+ };
6866
+ }
6867
+ return noWinner();
6868
+ }
6869
+ return noWinner({ error: actOutcome.error });
6870
+ async function noWinner(rejection) {
5062
6871
  const { childWork, driverInference } = await spentFromJournal(journal, opts.runId);
5063
- return {
5064
- kind: "winner",
5065
- out,
5066
- outRef,
6872
+ const common = {
6873
+ kind: "no-winner",
5067
6874
  tree,
5068
- spentTotal: addSpend(childWork, driverInference),
5069
- ...isNonEmptySpend(driverInference) ? { spentBreakdown: {
5070
- driverInference,
5071
- childWork
5072
- } } : {}
6875
+ downCount: breaker.downCount(),
6876
+ spentTotal: addSpend(childWork, driverInference)
6877
+ };
6878
+ const lifecycle = classifyNoWinner(controller, pool, opts, breaker, deadlineExceeded, tree);
6879
+ if (lifecycle !== void 0) return {
6880
+ ...common,
6881
+ reason: lifecycle
6882
+ };
6883
+ if (rejection !== void 0) return {
6884
+ ...common,
6885
+ reason: "driver-failed",
6886
+ error: describeRejection(rejection.error)
6887
+ };
6888
+ return {
6889
+ ...common,
6890
+ reason: "all-children-down"
5073
6891
  };
5074
6892
  }
5075
- return noWinner();
5076
- }
5077
- return noWinner({ error: actOutcome.error });
5078
- async function noWinner(rejection) {
5079
- const { childWork, driverInference } = await spentFromJournal(journal, opts.runId);
5080
- const common = {
5081
- kind: "no-winner",
5082
- tree,
5083
- downCount: breaker.downCount(),
5084
- spentTotal: addSpend(childWork, driverInference)
5085
- };
5086
- const lifecycle = classifyNoWinner(controller, pool, opts, breaker);
5087
- if (lifecycle !== void 0) return {
5088
- ...common,
5089
- reason: lifecycle
5090
- };
5091
- if (rejection !== void 0) return {
5092
- ...common,
5093
- reason: "driver-failed",
5094
- error: describeRejection(rejection.error)
5095
- };
5096
- return {
5097
- ...common,
5098
- reason: "all-children-down"
5099
- };
6893
+ } finally {
6894
+ rootLease?.release();
5100
6895
  }
5101
6896
  }
5102
6897
  function attach(h) {
@@ -5113,6 +6908,56 @@ function createSupervisor() {
5113
6908
  * can prove a handle is ours and reach its binding without leaking the control onto the
5114
6909
  * frozen `RootHandle` shape. */
5115
6910
  const rootControls = /* @__PURE__ */ new WeakMap();
6911
+ /**
6912
+ * Mint a `RootHandle` plus its supervisor-private control. The handle is the substrate a
6913
+ * chat/pi-viz client attaches to (Q2): `view()` reads the live tree, `signal()` delivers
6914
+ * an out-of-band message, `abort()` cascades. Before `run` binds it (and after `run`
6915
+ * unbinds it) the handle is fail-loud: a client that talks to a handle that is not
6916
+ * driving a live run gets a typed error, never a silent no-op.
6917
+ */
6918
+ function createRootHandle() {
6919
+ let binding;
6920
+ let activeLease;
6921
+ const handle = {
6922
+ view() {
6923
+ if (!binding) throw new RuntimeRunStateError("RootHandle.view: handle is not bound to a live run (attach it before run, read after run starts)");
6924
+ return binding.scope.view;
6925
+ },
6926
+ deliver(msg) {
6927
+ if (!binding) throw new RuntimeRunStateError("RootHandle.deliver: handle is not bound to a live run");
6928
+ return binding.deliver(msg);
6929
+ },
6930
+ signal(msg) {
6931
+ if (!binding) throw new RuntimeRunStateError("RootHandle.signal: handle is not bound to a live run");
6932
+ binding.signal(msg);
6933
+ },
6934
+ abort(reason) {
6935
+ if (!binding) throw new RuntimeRunStateError("RootHandle.abort: handle is not bound to a live run");
6936
+ binding.cascadeAbort(reason ?? "root handle aborted");
6937
+ }
6938
+ };
6939
+ rootControls.set(handle, { acquire() {
6940
+ if (activeLease !== void 0) throw new RuntimeRunStateError("RootHandle: handle already controls a live run; use one handle per concurrent run");
6941
+ const token = Symbol("root-handle-lease");
6942
+ activeLease = token;
6943
+ let released = false;
6944
+ return {
6945
+ bind(next) {
6946
+ if (released || activeLease !== token) throw new RuntimeRunStateError("RootHandle: live-run lease is no longer active");
6947
+ if (binding !== void 0) throw new RuntimeRunStateError("RootHandle: live-run lease is already bound");
6948
+ binding = next;
6949
+ },
6950
+ release() {
6951
+ if (released) return;
6952
+ released = true;
6953
+ if (activeLease !== token) return;
6954
+ binding = void 0;
6955
+ activeLease = void 0;
6956
+ }
6957
+ };
6958
+ } });
6959
+ return handle;
6960
+ }
5116
6961
  /** A `RootSignal` sink: `cancel` cascades an abort; pause/resume/ask are observability
5117
6962
  * signals the substrate accepts but does not act on here (the chat/pi-viz client owns
5118
6963
  * pause semantics — building them now would be mechanism ahead of the gate). */
@@ -5172,27 +7017,82 @@ function wrapJournalForBreaker(journal, breaker) {
5172
7017
  */
5173
7018
  async function drainLiveChildren(scope, controller) {
5174
7019
  const view = scope.view;
5175
- if (!(view.inFlight > 0 || view.waiting > 0)) return;
7020
+ if (!(view.inFlight > 0 || view.waiting > 0)) {
7021
+ if (scope.workerCapacity.live > 0) throw new RuntimeRunStateError(`supervisor: cleanup ended with ${scope.workerCapacity.live} executor resource(s) not confirmed destroyed`);
7022
+ return;
7023
+ }
5176
7024
  if (!controller.signal.aborted) controller.abort();
5177
- await Promise.allSettled([drainCursor(scope)]);
7025
+ await drainCursor(scope);
7026
+ const after = scope.view;
7027
+ if (after.inFlight > 0 || after.waiting > 0 || scope.workerCapacity.live > 0) throw new RuntimeRunStateError(`supervisor: cleanup ended with ${after.inFlight} running, ${after.waiting} waiting, and ${scope.workerCapacity.live} executor resource(s) not confirmed destroyed`);
5178
7028
  }
5179
7029
  async function drainCursor(scope) {
5180
7030
  for (;;) if (await scope.next() === null) return;
5181
7031
  }
7032
+ /** Race root policy execution against the same signal that stops every child. The losing promise
7033
+ * remains observed, so a late rejection cannot surface as an unhandled process error. */
7034
+ async function runAbortable(act, signal) {
7035
+ if (signal.aborted) throw supervisorAbortError(signal);
7036
+ return await new Promise((resolve, reject) => {
7037
+ let settled = false;
7038
+ const cleanup = () => signal.removeEventListener("abort", onAbort);
7039
+ const onAbort = () => {
7040
+ queueMicrotask(() => {
7041
+ if (settled) return;
7042
+ settled = true;
7043
+ cleanup();
7044
+ reject(supervisorAbortError(signal));
7045
+ });
7046
+ };
7047
+ signal.addEventListener("abort", onAbort, { once: true });
7048
+ let work;
7049
+ try {
7050
+ work = Promise.resolve(act());
7051
+ } catch (error) {
7052
+ cleanup();
7053
+ reject(error);
7054
+ return;
7055
+ }
7056
+ work.then((value) => {
7057
+ if (settled) return;
7058
+ settled = true;
7059
+ cleanup();
7060
+ resolve(value);
7061
+ }, (error) => {
7062
+ if (settled) return;
7063
+ settled = true;
7064
+ cleanup();
7065
+ reject(error);
7066
+ });
7067
+ });
7068
+ }
7069
+ function supervisorAbortError(signal) {
7070
+ const reason = signal.reason;
7071
+ const error = new Error(typeof reason === "string" && reason.length > 0 ? reason : "supervisor aborted");
7072
+ error.name = "AbortError";
7073
+ return error;
7074
+ }
5182
7075
  /**
5183
7076
  * The lifecycle cause of a no-winner, or `undefined` when the supervisor's own state proves
5184
7077
  * nothing. Returning `undefined` rather than falling through to `all-children-down` is what lets
5185
7078
  * the caller decide between the `driver-failed` arm (a rejection was captured) and the residual
5186
7079
  * bucket — and it is why `driver-failed` cannot be produced without an `error` to attach.
5187
7080
  */
5188
- function classifyNoWinner(controller, pool, opts, breaker) {
7081
+ function classifyNoWinner(controller, pool, opts, breaker, deadlineExceeded, tree) {
5189
7082
  if (breaker.tripped()) return "all-children-down";
7083
+ if (deadlineExceeded) return "budget-exhausted";
5190
7084
  if (controller.signal.aborted) return "aborted";
7085
+ if (allSpawnedChildrenDown(tree)) return "all-children-down";
5191
7086
  if (poolExhausted(pool, opts)) return "budget-exhausted";
5192
7087
  if (breaker.downCount() > 0) return "all-children-down";
5193
7088
  }
7089
+ function allSpawnedChildrenDown(tree) {
7090
+ const children = tree.nodes.filter((node) => node.id !== tree.root);
7091
+ return children.length > 0 && children.every((node) => node.status === "failed");
7092
+ }
5194
7093
  function poolExhausted(pool, opts) {
5195
7094
  const r = pool.readout();
7095
+ if (r.iterationsLeft <= 0) return true;
5196
7096
  if (r.tokensLeft <= 0) return true;
5197
7097
  if (opts.budget.maxUsd !== void 0 && r.usdLeft <= 0) return true;
5198
7098
  if (opts.budget.deadlineMs !== void 0 && r.deadlineMs > 0 && (opts.now ?? Date.now)() >= r.deadlineMs) return true;
@@ -5214,6 +7114,21 @@ async function spentFromJournal(journal, root) {
5214
7114
  * `metered` = driver inference (re-homed up the tree, so a single root-tree pass already
5215
7115
  * includes every nested driver's inference). */
5216
7116
  function sumSpendFromEvents(events) {
7117
+ const totals = sumMeasuredSpendFromEvents(events);
7118
+ const rootBudget = events.find((event) => event.kind === "spawned" && event.parent === void 0)?.budget;
7119
+ let remainingRootUsd = Math.max(0, (rootBudget?.maxUsd ?? 0) - totals.childWork.usd - totals.driverInference.usd);
7120
+ for (const budget of uncertainSpawnBudgets(events)) {
7121
+ totals.childWork.iterations += budget.maxIterations;
7122
+ totals.childWork.tokens.input += budget.maxTokens;
7123
+ totals.childWork.tokensKnown = false;
7124
+ const usdCharge = budget.maxUsd ?? (rootBudget?.maxUsd !== void 0 ? remainingRootUsd : 0);
7125
+ totals.childWork.usd += usdCharge;
7126
+ totals.childWork.usdKnown = false;
7127
+ remainingRootUsd = Math.max(0, remainingRootUsd - usdCharge);
7128
+ }
7129
+ return totals;
7130
+ }
7131
+ function sumMeasuredSpendFromEvents(events) {
5217
7132
  const childWork = {
5218
7133
  iterations: 0,
5219
7134
  tokens: {
@@ -5244,8 +7159,8 @@ function accumulate(a, b) {
5244
7159
  a.iterations += b.iterations;
5245
7160
  a.tokens.input += b.tokens.input;
5246
7161
  a.tokens.output += b.tokens.output;
5247
- a.usd += b.usd;
5248
7162
  if (b.tokensKnown === false) a.tokensKnown = false;
7163
+ a.usd += b.usd;
5249
7164
  if (b.usdKnown === false) a.usdKnown = false;
5250
7165
  a.ms += b.ms;
5251
7166
  }
@@ -5258,21 +7173,19 @@ function addSpend(a, b) {
5258
7173
  input: a.tokens.input + b.tokens.input,
5259
7174
  output: a.tokens.output + b.tokens.output
5260
7175
  },
5261
- usd: a.usd + b.usd,
5262
7176
  ...a.tokensKnown === false || b.tokensKnown === false ? { tokensKnown: false } : {},
7177
+ usd: a.usd + b.usd,
5263
7178
  ...a.usdKnown === false || b.usdKnown === false ? { usdKnown: false } : {},
5264
7179
  ms: a.ms + b.ms
5265
7180
  };
5266
7181
  }
5267
7182
  /** True when any driver metered inference this run (so the winner carries a `spentBreakdown`).
5268
7183
  * Checks every channel `addSpend` sums — including `ms` — so the gate stays consistent with the
5269
- * total even though the coordination driver currently stamps `ms: 0`. An all-zero spend that
5270
- * carries an UNKNOWN marker is non-empty: work happened and went unmeasured, which is exactly the
5271
- * fact a breakdown must not hide by looking like no work at all. */
7184
+ * total even though the coordination driver currently stamps `ms: 0`. */
5272
7185
  function isNonEmptySpend(s) {
5273
7186
  return s.iterations > 0 || s.tokens.input > 0 || s.tokens.output > 0 || s.usd > 0 || s.ms > 0 || s.tokensKnown === false || s.usdKnown === false;
5274
7187
  }
5275
7188
  //#endregion
5276
- export { removeWorktree as $, sanitizeAgentRuntimeEvent as A, createActivityLog as B, exportEvalRuns as C, toOtelAttributes as D, loopEventToOtelSpan as E, pollFor as F, routerToolLoop as G, routerBrain as H, timerAt as I, runSettledCommand as J, streamRouterChatWithTools as K, validateWaitSpec as L, sanitizeRuntimeStreamEvent as M, createWaitProbes as N, createRuntimeEventCollector as O, isWaitOutcome as P, createWorktree as Q, waitUntil as R, createOtelExporter as S, generateSpanId as T, routerChatWithTools as U, readWorkerProgress as V, routerChatWithUsage as W, runWorktreeHarness as X, runWorktreeChecks as Y, captureWorktreeDiff as Z, traceContextToEnv as _, runFinalizer as a, buildLoopSpanNodes as b, spendFromUsageEvents as c, settledToIteration as d, harnessInvocation as et, readWorkerTraceContext as f, readTraceContextFromEnv as g, createPropagatingTraceEmitter as h, pickBestDelivered as i, sanitizeKnowledgeReadinessReport as j, createRuntimeStreamEventCollector as k, withDriverExecutor as l, workerTraceSeamKey as m, bestDelivered as n, runLocalHarness as nt, runTree as o, workerTraceEnv as p, runBrainLoop as q, collectDelivered as r, CodexExecutionDiagnosticError as rt, createBudgetPool as s, createSupervisor as t, parseCodexTokenUsage as tt, createScope as u, INTELLIGENCE_WIRE_VERSION as v, flatOtelSpan as w, buildRuntimeEventOtelSpans as x, buildLoopOtelSpans as y, DEFAULT_STALL_AFTER_MS as z };
7189
+ export { WORKER_TOKEN_FLOOR as $, loopEventToOtelSpan as A, promptControlProfileMaterialization as At, timerAt as B, harnessInvocation as Bt, buildLoopSpanNodes as C, removeWorktree as Ct, exportEvalRuns as D, defineProfileMaterializationContract as Dt, createOtelExporter as E, controlProfileMaterialization as Et, sanitizeKnowledgeReadinessReport as F, sandboxActProfileMaterialization as Ft, describeToolArgs as G, CodexExecutionDiagnosticError as Gt, waitUntil as H, localHarnessExecutable as Ht, sanitizeRuntimeStreamEvent as I, validateProfileMaterialization as It, freeSlots as J, readWorkerProgress as K, createWaitProbes as L, worktreeCliProfileMaterialization as Lt, createRuntimeEventCollector as M, promptOnlyProfileMaterialization as Mt, createRuntimeStreamEventCollector as N, promptResourceProfileMaterialization as Nt, flatOtelSpan as O, fullProfileMaterialization as Ot, sanitizeAgentRuntimeEvent as P, renderProfileMaterializationIssues as Pt, teardownExecutor as Q, isWaitOutcome as R, DEFAULT_LOCAL_HARNESS as Rt, buildLoopOtelSpans as S, createWorktree as St, createOpenInferenceFileExporter as T, assertProfileMaterialization as Tt, DEFAULT_STALL_AFTER_MS as U, parseCodexTokenUsage as Ut, validateWaitSpec as V, harnessSupportsReasoningEffort as Vt, createActivityLog as W, runLocalHarness as Wt, rollingDispatch as X, queueOf as Y, DEFAULT_SUCCESSFUL_SHUTDOWN_MS as Z, workerTraceSeamKey as _, runSettledCommand as _t, pickBestDelivered as a, attestRuntimeOwnedScopeOwner as at, traceContextToEnv as b, worktreeProfileExecutionPlan as bt, driverChild as c, runtimeOwnedExecutorExecutionBinding as ct, deriveNodeExecutionIdentity as d, routerBrain as dt, workerTokenFloor as et, recordScopeOwnerMaterialization as f, routerChatWithTools as ft, workerTraceEnv as g, runBrainLoop as gt, readWorkerTraceContext as h, streamRouterChatWithTools as ht, collectDelivered as i, attestRuntimeOwnedExecutor as it, toOtelAttributes as j, promptModelProfileMaterialization as jt, generateSpanId as k, profileMaterializationAxes as kt, withDriverExecutor as l, runtimeOwnedExecutorMaterialization as lt, settledToIteration as m, routerToolLoop as mt, createSupervisor as n, createBudgetPool as nt, runFinalizer as o, inheritRuntimeOwnedExecutorAttestation as ot, scopeOwnerExecutorNodeContext as p, routerChatWithUsage as pt, effectiveConcurrency as q, bestDelivered as r, spendFromUsageEvents as rt, runTree as s, newExecutionAttemptId as st, createRootHandle as t, assertValidBudget as tt, createScope as u, runtimeOwnedScopeOwnerRuntime as ut, createPropagatingTraceEmitter as v, runWorktreeChecks as vt, buildRuntimeEventOtelSpans as w, AGENT_PROFILE_MATERIALIZATION_AXES as wt, INTELLIGENCE_WIRE_VERSION as x, captureWorktreeDiff as xt, readTraceContextFromEnv as y, runWorktreeHarness as yt, pollFor as z, LOCAL_HARNESSES as zt };
5277
7190
 
5278
- //# sourceMappingURL=supervisor-BBbPBXpe.js.map
7191
+ //# sourceMappingURL=supervisor-CpT9yAxL.js.map