@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.
- package/README.md +9 -1
- package/dist/{activation-DuqQhee6.js → activation-DdIpwQ0k.js} +3 -3
- package/dist/{activation-DuqQhee6.js.map → activation-DdIpwQ0k.js.map} +1 -1
- package/dist/agent.d.ts +3 -64
- package/dist/agent.js +5 -207
- package/dist/agent.js.map +1 -1
- package/dist/{analyst-loop-BoNIG2hA.js → analyst-loop-DvSciOfB.js} +2 -2
- package/dist/{analyst-loop-BoNIG2hA.js.map → analyst-loop-DvSciOfB.js.map} +1 -1
- package/dist/analyst-loop.js +1 -1
- package/dist/candidate-execution/index.d.ts +3 -3
- package/dist/candidate-execution/index.js +5 -5
- package/dist/{candidate-execution-CfpJrd3o.js → candidate-execution-DDkSRPjY.js} +4 -4
- package/dist/{candidate-execution-CfpJrd3o.js.map → candidate-execution-DDkSRPjY.js.map} +1 -1
- package/dist/{environment-provider-CWsRh6Uz.d.ts → environment-provider-Bh4nX2qt.d.ts} +306 -32
- package/dist/{environment-provider-CCaEhA-l.js → environment-provider-Cr75N-rU.js} +141 -32
- package/dist/environment-provider-Cr75N-rU.js.map +1 -0
- package/dist/environment-provider.d.ts +1 -1
- package/dist/environment-provider.js +1 -1
- package/dist/{improvement-cycle-C1cmjvPD.js → improvement-cycle-zjR-MXwK.js} +163 -22
- package/dist/improvement-cycle-zjR-MXwK.js.map +1 -0
- package/dist/{index-C-FYUuFG.d.ts → index-B3bHsVLw.d.ts} +2 -2
- package/dist/{index-CYkDeM5L.d.ts → index-BLsKcxNd.d.ts} +3 -3
- package/dist/{index-COumPQka.d.ts → index-CGADWaa_.d.ts} +864 -437
- package/dist/{index-DcLMNnG5.d.ts → index-I35151Fr.d.ts} +9 -9
- package/dist/index.d.ts +9 -9
- package/dist/index.js +13 -13
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +10 -9
- package/dist/intelligence.js +15 -9
- package/dist/intelligence.js.map +1 -1
- package/dist/kernel.d.ts +4 -4
- package/dist/kernel.js +9 -8
- package/dist/{knowledge-DOzbywZT.js → knowledge-I1e9zQGj.js} +7 -7
- package/dist/knowledge-I1e9zQGj.js.map +1 -0
- package/dist/knowledge.d.ts +1 -1
- package/dist/knowledge.js +1 -1
- package/dist/{local-harness-t6cDWDQ2.d.ts → local-harness-BIajef4A.d.ts} +29 -7
- package/dist/{loop-runner-bin-BFrhPLKt.d.ts → loop-runner-bin-BeG9vTdE.d.ts} +3 -3
- package/dist/{loop-runner-bin-BuQjc5DR.js → loop-runner-bin-L996YVPS.js} +4 -4
- package/dist/{loop-runner-bin-BuQjc5DR.js.map → loop-runner-bin-L996YVPS.js.map} +1 -1
- package/dist/loop-runner-bin.d.ts +1 -1
- package/dist/loop-runner-bin.js +1 -1
- package/dist/mcp/bin.js +43 -10
- package/dist/mcp/bin.js.map +1 -1
- package/dist/mcp/index.d.ts +5 -45
- package/dist/mcp/index.js +8 -212
- package/dist/mcp/index.js.map +1 -1
- package/dist/{openai-tools-_Wyp4udO.js → openai-tools-CvFEzw6f.js} +2 -2
- package/dist/{openai-tools-_Wyp4udO.js.map → openai-tools-CvFEzw6f.js.map} +1 -1
- package/dist/{prepare-BHQBb02e.js → prepare-DpV6np9e.js} +47 -34
- package/dist/prepare-DpV6np9e.js.map +1 -0
- package/dist/primeintellect/index.d.ts +1 -1
- package/dist/{protected-model-port-BP6Z4eau.d.ts → protected-model-port-CoJOiINX.d.ts} +11 -3
- package/dist/{protected-model-port-DqAH1Z2M.js → protected-model-port-vQLBRoAQ.js} +2 -2
- package/dist/{protected-model-port-DqAH1Z2M.js.map → protected-model-port-vQLBRoAQ.js.map} +1 -1
- package/dist/{redact-BEtQtvd6.d.ts → redact-PQzmE1Jn.d.ts} +4 -4
- package/dist/run-layout-C2FGmZ3v.js +158 -0
- package/dist/run-layout-C2FGmZ3v.js.map +1 -0
- package/dist/{runtime-Ut1pkd2n.js → runtime-D4e3GP5K.js} +147 -190
- package/dist/runtime-D4e3GP5K.js.map +1 -0
- package/dist/{sandbox-events-DeI5xX8P.js → sandbox-events-Yhd1GYWl.js} +4 -2
- package/dist/sandbox-events-Yhd1GYWl.js.map +1 -0
- package/dist/spawn-journal-IeXpidO2.js +857 -0
- package/dist/spawn-journal-IeXpidO2.js.map +1 -0
- package/dist/{structural-rollout-CVY_0hJp.js → structural-rollout-DQHO3b2Y.js} +4 -4
- package/dist/structural-rollout-DQHO3b2Y.js.map +1 -0
- package/dist/{supervise-BUR9ByF7.js → supervise-Cq50UT5-.js} +2874 -1892
- package/dist/supervise-Cq50UT5-.js.map +1 -0
- package/dist/{supervisor-BBbPBXpe.js → supervisor-CpT9yAxL.js} +2896 -983
- package/dist/supervisor-CpT9yAxL.js.map +1 -0
- package/dist/testing.js +98 -76
- package/dist/testing.js.map +1 -1
- package/dist/top-app-5unxqovu.js +1088 -0
- package/dist/top-app-5unxqovu.js.map +1 -0
- package/dist/tui/bin.d.ts +1 -0
- package/dist/tui/bin.js +19 -0
- package/dist/tui/bin.js.map +1 -0
- package/dist/tui/index.d.ts +209 -0
- package/dist/tui/index.js +2 -0
- package/dist/{util-Cc9g9Y-o.js → util-MVgdwuIS.js} +2 -2
- package/dist/{util-Cc9g9Y-o.js.map → util-MVgdwuIS.js.map} +1 -1
- package/dist/{workspace-archive-DXzJq7WP.js → workspace-archive-B4SkNJjw.js} +2 -2
- package/dist/{workspace-archive-DXzJq7WP.js.map → workspace-archive-B4SkNJjw.js.map} +1 -1
- package/package.json +8 -2
- package/dist/environment-provider-CCaEhA-l.js.map +0 -1
- package/dist/improvement-cycle-C1cmjvPD.js.map +0 -1
- package/dist/knowledge-DOzbywZT.js.map +0 -1
- package/dist/prepare-BHQBb02e.js.map +0 -1
- package/dist/runtime-Ut1pkd2n.js.map +0 -1
- package/dist/sandbox-events-DeI5xX8P.js.map +0 -1
- package/dist/spawn-journal-DCPbicXB.js +0 -457
- package/dist/spawn-journal-DCPbicXB.js.map +0 -1
- package/dist/structural-rollout-CVY_0hJp.js.map +0 -1
- package/dist/supervise-BUR9ByF7.js.map +0 -1
- 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 {
|
|
3
|
-
import {
|
|
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
|
-
|
|
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
|
|
208
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
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:
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
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/
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
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
|
-
/**
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
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/
|
|
2721
|
+
//#region src/runtime/supervise/budget.ts
|
|
2262
2722
|
/**
|
|
2263
2723
|
*
|
|
2264
|
-
*
|
|
2265
|
-
*
|
|
2266
|
-
*
|
|
2267
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
2295
|
-
*
|
|
2296
|
-
*
|
|
2297
|
-
*
|
|
2298
|
-
*
|
|
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
|
-
*
|
|
2301
|
-
*
|
|
2302
|
-
*
|
|
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
|
-
/**
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
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
|
-
|
|
2315
|
-
|
|
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
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
...
|
|
2321
|
-
|
|
2794
|
+
iterations,
|
|
2795
|
+
tokens,
|
|
2796
|
+
usd,
|
|
2797
|
+
...usdKnown ? {} : { usdKnown: false },
|
|
2798
|
+
ms: 0
|
|
2322
2799
|
};
|
|
2323
2800
|
}
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
const
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
}
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
}
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
2362
|
-
*
|
|
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
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
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
|
-
|
|
2380
|
-
|
|
2861
|
+
if (wantTokens > freeTokens) return {
|
|
2862
|
+
ok: false,
|
|
2863
|
+
reason: "budget-exhausted"
|
|
2381
2864
|
};
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
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
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
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
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
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
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
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
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
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/
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
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 (`
|
|
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
|
|
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:
|
|
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")
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
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(
|
|
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
|
|
3564
|
-
if (!
|
|
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:
|
|
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
|
|
3576
|
-
const
|
|
3577
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
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
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
4133
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
if (ev.
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
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
|
-
/**
|
|
4236
|
-
*
|
|
4237
|
-
|
|
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
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
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
|
|
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
|
-
*
|
|
4415
|
-
*
|
|
4416
|
-
*
|
|
4417
|
-
*
|
|
4418
|
-
*
|
|
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
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
}
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
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
|
-
|
|
4550
|
-
|
|
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
|
-
|
|
4554
|
-
|
|
4555
|
-
if (
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
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
|
-
*
|
|
4566
|
-
*
|
|
4567
|
-
*
|
|
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
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
const
|
|
4600
|
-
|
|
4601
|
-
let
|
|
4602
|
-
let
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
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
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
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
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
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
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
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
|
|
4642
|
-
|
|
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
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
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
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
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
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
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
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
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
|
-
|
|
4764
|
-
|
|
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
|
-
|
|
4768
|
-
|
|
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
|
|
4978
|
-
const
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
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
|
-
|
|
5046
|
-
|
|
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
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
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
|
-
|
|
5059
|
-
|
|
5060
|
-
const
|
|
5061
|
-
|
|
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
|
-
|
|
5064
|
-
kind: "winner",
|
|
5065
|
-
out,
|
|
5066
|
-
outRef,
|
|
6872
|
+
const common = {
|
|
6873
|
+
kind: "no-winner",
|
|
5067
6874
|
tree,
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
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
|
-
|
|
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))
|
|
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
|
|
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`.
|
|
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 {
|
|
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-
|
|
7191
|
+
//# sourceMappingURL=supervisor-CpT9yAxL.js.map
|