@tangle-network/agent-runtime 0.165.0 → 0.167.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/{activation-DmUIq51m.js → activation-CIwer0zU.js} +3 -3
  2. package/dist/{activation-DmUIq51m.js.map → activation-CIwer0zU.js.map} +1 -1
  3. package/dist/agent.js +2 -2
  4. package/dist/{authoring-Cm122Duf.js → authoring-CUTgAxxE.js} +2 -2
  5. package/dist/{authoring-Cm122Duf.js.map → authoring-CUTgAxxE.js.map} +1 -1
  6. package/dist/candidate-execution/index.js +4 -4
  7. package/dist/{candidate-execution-DC0T8l2p.js → candidate-execution-BbMbyhgC.js} +4 -4
  8. package/dist/{candidate-execution-DC0T8l2p.js.map → candidate-execution-BbMbyhgC.js.map} +1 -1
  9. package/dist/durable.js +1 -1
  10. package/dist/{graph-DGri_zJm.js → graph-ZDMygFIo.js} +21 -7
  11. package/dist/graph-ZDMygFIo.js.map +1 -0
  12. package/dist/graph.d.ts +161 -16
  13. package/dist/graph.js +990 -736
  14. package/dist/graph.js.map +1 -1
  15. package/dist/{improvement-cycle-BOd5DFeG.js → improvement-cycle-BAKN_f4x.js} +4 -4
  16. package/dist/{improvement-cycle-BOd5DFeG.js.map → improvement-cycle-BAKN_f4x.js.map} +1 -1
  17. package/dist/index.js +11 -11
  18. package/dist/intelligence.js +5 -5
  19. package/dist/kernel.js +6 -6
  20. package/dist/{knowledge-7_IfyD9R.js → knowledge-D-7PXKUV.js} +5 -5
  21. package/dist/{knowledge-7_IfyD9R.js.map → knowledge-D-7PXKUV.js.map} +1 -1
  22. package/dist/knowledge.js +1 -1
  23. package/dist/{loop-runner-bin-Dx-5GZQK.js → loop-runner-bin-DHxQ_VQD.js} +3 -3
  24. package/dist/{loop-runner-bin-Dx-5GZQK.js.map → loop-runner-bin-DHxQ_VQD.js.map} +1 -1
  25. package/dist/loop-runner-bin.js +1 -1
  26. package/dist/mcp/bin.js +3 -3
  27. package/dist/mcp/index.js +4 -4
  28. package/dist/{openai-tools-CDS7eSww.js → openai-tools-UyXVO0kd.js} +2 -2
  29. package/dist/{openai-tools-CDS7eSww.js.map → openai-tools-UyXVO0kd.js.map} +1 -1
  30. package/dist/{prepare-BCScSDCv.js → prepare-BAyaNoZu.js} +3 -3
  31. package/dist/prepare-BAyaNoZu.js.map +1 -0
  32. package/dist/{protected-model-port-D-5E3HBe.js → protected-model-port-T2UWrDNQ.js} +2 -2
  33. package/dist/{protected-model-port-D-5E3HBe.js.map → protected-model-port-T2UWrDNQ.js.map} +1 -1
  34. package/dist/{runtime-DESENUe1.js → runtime-D5l-enW9.js} +6 -6
  35. package/dist/{runtime-DESENUe1.js.map → runtime-D5l-enW9.js.map} +1 -1
  36. package/dist/{structural-rollout-DZGDg0yX.js → structural-rollout-C7nNNhri.js} +2 -2
  37. package/dist/{structural-rollout-DZGDg0yX.js.map → structural-rollout-C7nNNhri.js.map} +1 -1
  38. package/dist/{supervise-Da20R0PO.js → supervise-rEE46ruH.js} +2 -2
  39. package/dist/{supervise-Da20R0PO.js.map → supervise-rEE46ruH.js.map} +1 -1
  40. package/dist/{supervisor-DpIKMlPJ.js → supervisor-B-mmmiOU.js} +24 -4
  41. package/dist/supervisor-B-mmmiOU.js.map +1 -0
  42. package/dist/testing.js +12 -12
  43. package/dist/{workspace-archive-CHY5HvrF.js → workspace-archive-B0Hqp2eJ.js} +12 -7
  44. package/dist/workspace-archive-B0Hqp2eJ.js.map +1 -0
  45. package/package.json +3 -3
  46. package/dist/graph-DGri_zJm.js.map +0 -1
  47. package/dist/prepare-BCScSDCv.js.map +0 -1
  48. package/dist/supervisor-DpIKMlPJ.js.map +0 -1
  49. package/dist/workspace-archive-CHY5HvrF.js.map +0 -1
package/dist/graph.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { m as ValidationError } from "./errors-CDZ8XsVj.js";
2
2
  import { d as replaySpawnTree, i as InMemorySpawnJournal, l as materializeTreeView, o as closesCursorSlot, r as InMemoryResultBlobStore, u as pendingWaits, x as contentAddress } from "./spawn-journal-Tgvp5VS5.js";
3
3
  import { t as addSpend } from "./util-D6ZEuBMi.js";
4
- import { N as createBudgetPool, U as createExecutorRegistry, a as uncertainSpawnBudgets, i as sumMeasuredSpendFromEvents, l as runFinalizer, o as bestDelivered, p as createScope, r as maxSeqOf, s as collectDelivered } from "./supervisor-DpIKMlPJ.js";
5
- import { i as workerFromBackend, s as supervisorAgent } from "./supervise-Da20R0PO.js";
6
- import { t as GraphEdgeCapError } from "./graph-DGri_zJm.js";
4
+ import { N as createBudgetPool, U as createExecutorRegistry, a as uncertainSpawnBudgets, i as sumMeasuredSpendFromEvents, l as runFinalizer, o as bestDelivered, p as createScope, r as maxSeqOf, s as collectDelivered } from "./supervisor-B-mmmiOU.js";
5
+ import { i as workerFromBackend, s as supervisorAgent } from "./supervise-rEE46ruH.js";
6
+ import { t as GraphEdgeCapError } from "./graph-ZDMygFIo.js";
7
7
  //#region src/runtime/graph/condition.ts
8
8
  /**
9
9
  * The ONE predicate tree: every guard in a graph — an edge `guard`, a filter projection's
@@ -327,7 +327,7 @@ function validateEngineGraphSpec(spec, context = "compileGraph") {
327
327
  validateProjection(edge.projection, `${who} projection`);
328
328
  }
329
329
  if (edge.maxTraversals !== void 0) {
330
- if (!Number.isSafeInteger(edge.maxTraversals) || edge.maxTraversals < 1) throw new ValidationError(`${who}: maxTraversals must be a positive integer`);
330
+ if (!Number.isSafeInteger(edge.maxTraversals) || edge.maxTraversals < 0) throw new ValidationError(`${who}: maxTraversals must be a non-negative integer`);
331
331
  }
332
332
  if (edge.kind === "data" && edge.directive !== void 0) throw new ValidationError(`${who}: a data edge carries a port binding, never a directive`);
333
333
  }
@@ -346,6 +346,12 @@ function validateEngineGraphSpec(spec, context = "compileGraph") {
346
346
  */
347
347
  /** A node's ports: its kind's declared outputs plus the two implicit ones every node has. */
348
348
  const IMPLICIT_OUTPUT_PORTS = ["out", "trace"];
349
+ /** `delegates` is the one MODEL-fired edge kind (agent-runtime#971): its payload is a directive and
350
+ * its target is spawned by the source supervisor through the coordination protocol, not released
351
+ * by the scheduler. `data` and `analyzes` are engine-fired. */
352
+ function isEngineFired(edge) {
353
+ return edge.spec.kind !== "delegates";
354
+ }
349
355
  /** A node's ports: node-level declarations merged OVER its kind's (the node wins on a name). */
350
356
  function nodePorts(kind, node) {
351
357
  const merge = (declared, own) => {
@@ -457,17 +463,23 @@ function compileGraph(engine, spec, context = "compileGraph") {
457
463
  if (dupes.has(edge.id)) throw new ValidationError(`${context}: duplicate edge id ${edge.id}`);
458
464
  dupes.add(edge.id);
459
465
  }
460
- const entries = spec.nodes.filter((node) => (inbound.get(node.id) ?? []).length === 0);
461
- if (entries.length === 0) throw new ValidationError(`${context}: no entry node — every node has an inbound edge`);
462
- const root = spec.root ?? (entries.length === 1 ? entries[0]?.id ?? "" : (() => {
463
- throw new ValidationError(`${context}: ${entries.length} entry nodes (${entries.map((node) => node.id).join(", ")}) name spec.root`);
466
+ const engineInbound = (id) => (inbound.get(id) ?? []).filter(isEngineFired);
467
+ const engineOutbound = (id) => (outbound.get(id) ?? []).filter(isEngineFired);
468
+ const unfed = spec.nodes.filter((node) => (inbound.get(node.id) ?? []).length === 0);
469
+ if (unfed.length === 0 && spec.root === void 0) throw new ValidationError(`${context}: no entry node every node has an inbound edge`);
470
+ const root = spec.root ?? (unfed.length === 1 ? unfed[0]?.id ?? "" : (() => {
471
+ throw new ValidationError(`${context}: ${unfed.length} entry nodes (${unfed.map((node) => node.id).join(", ")}) — name spec.root`);
464
472
  })());
473
+ const declaredOut = new Set(spec.nodes.filter((node) => node.entry === false).map((node) => node.id));
474
+ const entries = [root, ...unfed.map((node) => node.id).filter((id) => id !== root)].filter((id) => !declaredOut.has(id));
475
+ if (entries.length === 0) throw new ValidationError(`${context}: every entry node declares entry: false — nothing starts`);
465
476
  const nodes = /* @__PURE__ */ new Map();
466
477
  const terminals = [];
467
478
  for (const node of spec.nodes) {
468
479
  const kind = kinds.get(node.id);
469
480
  if (!kind) throw new ValidationError(`${context}: unresolved node ${node.id}`);
470
- const terminal = node.terminal ?? (outbound.get(node.id) ?? []).length === 0;
481
+ const modelFired = (inbound.get(node.id) ?? []).some((edge) => !isEngineFired(edge));
482
+ const terminal = node.terminal ?? (!modelFired && engineOutbound(node.id).length === 0);
471
483
  const deliverable = node.deliverable ?? (node.id === root ? spec.deliverable : void 0);
472
484
  if (terminal) terminals.push(node.id);
473
485
  nodes.set(node.id, {
@@ -480,8 +492,9 @@ function compileGraph(engine, spec, context = "compileGraph") {
480
492
  pure: node.flags?.pure ?? false,
481
493
  terminal,
482
494
  ...deliverable === void 0 ? {} : { deliverable },
483
- inbound: inbound.get(node.id) ?? [],
495
+ inbound: engineInbound(node.id),
484
496
  outbound: outbound.get(node.id) ?? [],
497
+ modelFired,
485
498
  spec: node
486
499
  });
487
500
  }
@@ -489,7 +502,7 @@ function compileGraph(engine, spec, context = "compileGraph") {
489
502
  return {
490
503
  nodes,
491
504
  edges: compiledEdges,
492
- entries: entries.map((node) => node.id),
505
+ entries,
493
506
  terminals,
494
507
  root,
495
508
  maxNodeVisits: spec.maxNodeVisits ?? 25
@@ -794,6 +807,63 @@ function foldGraphJournal(events, compiled) {
794
807
  return state;
795
808
  }
796
809
  //#endregion
810
+ //#region src/runtime/graph/join.ts
811
+ const NOTHING = {
812
+ release: false,
813
+ consuming: [],
814
+ blocked: false
815
+ };
816
+ /** Decide whether a node's gating edges release it, and which of them the release consumes. */
817
+ function decideJoin(rule, gating) {
818
+ if (gating.length === 0) return NOTHING;
819
+ const settled = gating.filter((entry) => entry.folded && entry.folded.state !== "pending");
820
+ const satisfied = gating.filter((entry) => entry.folded?.state === "satisfied");
821
+ const failed = gating.filter((entry) => entry.folded?.state === "failed");
822
+ const allSettled = settled.length === gating.length;
823
+ switch (rule) {
824
+ case "all": {
825
+ const release = !gating.some((entry) => entry.folded?.state === "dead" || entry.folded?.state === "failed") && satisfied.length === gating.length;
826
+ return {
827
+ release,
828
+ consuming: release ? settled : [],
829
+ blocked: false
830
+ };
831
+ }
832
+ case "any": {
833
+ const first = satisfied[0];
834
+ if (first === void 0) return {
835
+ release: false,
836
+ consuming: [],
837
+ blocked: allSettled
838
+ };
839
+ return {
840
+ release: true,
841
+ consuming: [first],
842
+ blocked: false
843
+ };
844
+ }
845
+ case "any_failed": {
846
+ const first = failed[0];
847
+ if (first === void 0) return {
848
+ release: false,
849
+ consuming: [],
850
+ blocked: allSettled
851
+ };
852
+ return {
853
+ release: true,
854
+ consuming: [first],
855
+ blocked: false
856
+ };
857
+ }
858
+ case "all_done": return {
859
+ release: allSettled,
860
+ consuming: allSettled ? settled : [],
861
+ blocked: false
862
+ };
863
+ default: return NOTHING;
864
+ }
865
+ }
866
+ //#endregion
797
867
  //#region src/runtime/graph/kinds.ts
798
868
  /**
799
869
  * One profile, one run: the kernel's leaf, exactly as `supervise()` derives it from `backend`.
@@ -967,12 +1037,7 @@ function scriptAgent(profile, config, inputs, kind) {
967
1037
  executorSpec: spec
968
1038
  };
969
1039
  }
970
- /**
971
- * A node carrying its own graph: the constraint on what a supervisor may spawn at depth>1. Its
972
- * executor is a nested engine run; that needs the scheduler (#980), so until then this kind is
973
- * registered and REFUSES at run time by name rather than being absent — a graph that names it
974
- * compiles, and the refusal says exactly what is missing.
975
- */
1040
+ /** A node carrying its own graph: it runs as a full engine run on the host's kinds and effects. */
976
1041
  function subgraphKind() {
977
1042
  return {
978
1043
  id: "subgraph",
@@ -981,7 +1046,11 @@ function subgraphKind() {
981
1046
  validateConfig: (raw, context) => {
982
1047
  const config = asRecord(raw, `${context}: subgraph config`);
983
1048
  if (config.graph === void 0) throw new ValidationError(`${context}: subgraph config.graph is required`);
984
- return { graph: config.graph };
1049
+ return {
1050
+ graph: config.graph,
1051
+ ...config.budget === void 0 ? {} : { budget: config.budget },
1052
+ ...config.perNode === void 0 ? {} : { perNode: config.perNode }
1053
+ };
985
1054
  },
986
1055
  configSchema: {
987
1056
  type: "object",
@@ -993,8 +1062,55 @@ function subgraphKind() {
993
1062
  effects: [],
994
1063
  onCrash: "restart",
995
1064
  budget: "metered",
996
- run: ({ profile }) => {
997
- throw new ValidationError(`subgraph kind: node ${JSON.stringify(profile.name)} cannot run yet — the scheduler that executes a nested graph lands in agent-runtime#980`);
1065
+ run: ({ config, profile, host }) => {
1066
+ const name = profile.name ?? "subgraph";
1067
+ if (!host) throw new ValidationError(`subgraph kind: node ${JSON.stringify(name)} needs its hosting engine; run it through the scheduler, which supplies one`);
1068
+ let artifact;
1069
+ return {
1070
+ name,
1071
+ act: () => Promise.reject(new ValidationError("subgraph: act() is not the execution path")),
1072
+ executorSpec: {
1073
+ profile,
1074
+ harness: null,
1075
+ executor: {
1076
+ runtime: "inline",
1077
+ async execute(_task, signal) {
1078
+ const inner = await host.runNested(config.graph, name, {
1079
+ budget: config.budget ?? {
1080
+ maxIterations: 1,
1081
+ maxTokens: 0
1082
+ },
1083
+ ...config.perNode === void 0 ? {} : { perNode: config.perNode },
1084
+ runId: `${name}:subgraph`,
1085
+ signal
1086
+ });
1087
+ const out = {
1088
+ kind: inner.kind,
1089
+ out: inner.out
1090
+ };
1091
+ artifact = {
1092
+ outRef: contentAddress(out),
1093
+ out,
1094
+ spent: {
1095
+ iterations: 1,
1096
+ tokens: {
1097
+ input: 0,
1098
+ output: 0
1099
+ },
1100
+ usd: 0,
1101
+ ms: 0
1102
+ }
1103
+ };
1104
+ return artifact;
1105
+ },
1106
+ teardown: () => Promise.resolve({ destroyed: true }),
1107
+ resultArtifact: () => {
1108
+ if (!artifact) throw new ValidationError(`subgraph: resultArtifact() read before execute()`);
1109
+ return artifact;
1110
+ }
1111
+ }
1112
+ }
1113
+ };
998
1114
  }
999
1115
  };
1000
1116
  }
@@ -1003,30 +1119,337 @@ function asRecord(value, context) {
1003
1119
  return value;
1004
1120
  }
1005
1121
  //#endregion
1006
- //#region src/runtime/graph/scheduler.ts
1122
+ //#region src/runtime/graph/ledger.ts
1123
+ /** Open a ledger for one run; its ordinals continue past whatever a prior process recorded. */
1124
+ function createEdgeLedger(args) {
1125
+ const entries = [];
1126
+ let seq = args.startSeq;
1127
+ return {
1128
+ entries,
1129
+ async record(edge, traversal, outcome, reason) {
1130
+ const spec = edge.spec;
1131
+ const entry = {
1132
+ edge: edge.id,
1133
+ kind: spec.kind,
1134
+ from: spec.from.node,
1135
+ to: spec.to.node,
1136
+ traversal,
1137
+ outcome,
1138
+ ...spec.directive !== void 0 ? { directive: `${spec.directive.surface}/v${spec.directive.version}` } : {},
1139
+ ...spec.kind === "data" ? { port: edge.toPort } : {},
1140
+ ...reason !== void 0 ? { reason } : {}
1141
+ };
1142
+ entries.push(entry);
1143
+ await args.journal.appendEvent(args.runId, {
1144
+ kind: "edge",
1145
+ id: `graph:${spec.to.node}`,
1146
+ edge: {
1147
+ kind: spec.kind,
1148
+ from: spec.from.node,
1149
+ to: spec.to.node,
1150
+ ...entry.directive !== void 0 ? { directive: entry.directive } : {},
1151
+ ...entry.port !== void 0 ? { port: entry.port } : {}
1152
+ },
1153
+ traversal,
1154
+ outcome,
1155
+ bytes: 0,
1156
+ ...reason !== void 0 ? { reason } : {},
1157
+ seq: seq++,
1158
+ at: new Date(args.now()).toISOString()
1159
+ });
1160
+ }
1161
+ };
1162
+ }
1163
+ //#endregion
1164
+ //#region src/runtime/graph/preset-run-graph.ts
1007
1165
  /**
1008
- * The engine scheduler (agent-runtime#980, durable per #974/#976): run a compiled graph by hosting
1009
- * every node instance on one kernel `Scope` — the pool, the journal, the blob store and
1010
- * cancellation are the kernel's, never re-implemented. The scheduler owns only what a graph adds:
1011
- * joins over guarded edges, traversal and visit caps, directive/payload delivery, suspensions,
1012
- * terminals and the finalizer reduce.
1166
+ * `runGraph` as an engine graph (agent-runtime#982, #975) a COMPILER, not a second runtime.
1013
1167
  *
1014
- * DURABILITY MODEL fold, never checkpoint. Every scheduling decision is journaled BEFORE its
1015
- * effect is visible (blob-then-journal where a ref is minted), then applied to in-memory state
1016
- * through the SAME pure reducer (`applyGraphFoldEvent`) a restart replays the journal through.
1017
- * Kill the process at any journal boundary and a restart with the same journal re-enters the exact
1018
- * state: settled nodes restore from `outRef` and are never re-executed; a released-but-unspawned
1019
- * instance re-enters from its pinned `inputRef`; an in-flight instance is in-doubt and re-enters
1020
- * per its kind's `onCrash`.
1168
+ * `graphFromRunGraph` lowers today's `AgentGraph` into the engine's vocabulary: one supervisor
1169
+ * root carrying the graph, one pinned `agent` node per worker, the authored `delegates`/`analyzes`
1170
+ * edges. That gives a `runGraph` consumer a first-class engine graph it can inspect, diff, and use
1171
+ * as the starting point for authoring one natively the migration path #975 asked for.
1021
1172
  *
1022
- * Engine `woken` ordinals live in a reserved high band (`ENGINE_WOKEN_SEQ_BASE`+) so they can
1023
- * never collide with the kernel scope's cursor counter, which the kernel owns and advances
1024
- * concurrently.
1173
+ * WHAT THIS DELIBERATELY DOES NOT DO: run it. `runGraph` executes through `superviseAgentGraph`,
1174
+ * exactly as it always has. An earlier version of this module wrapped that call in a one-node
1175
+ * engine run, which read as "runGraph runs on the engine" while changing nothing about execution
1176
+ * and costing a second journal tree, a second budget pool and a second `Scope`. A wrapper that
1177
+ * moves no behaviour is a second source of truth, so it is gone: the graph a consumer runs and the
1178
+ * graph they can inspect are produced from the same `AgentGraph`, and only one of them executes.
1179
+ *
1180
+ * The engine EXECUTES a graph a consumer authors directly (`runEngineGraph`), where nodes are
1181
+ * scheduled over `data` edges, guards decide traversal, and the fold makes it restartable.
1182
+ */
1183
+ /** The kind id the root node carries: a supervisor holding the whole `AgentGraph`. */
1184
+ const RUN_GRAPH_ROOT_KIND = "supervisor/v1";
1185
+ /**
1186
+ * Compile an `AgentGraph` into the engine graph that describes it. Pure: nothing runs, nothing is
1187
+ * registered, no executor is built. A `delegates` edge is MODEL-fired (#971) — its target is
1188
+ * spawned by the supervisor through the coordination protocol — so every worker node is marked
1189
+ * `entry: false`, which is exactly what the engine's scheduler would honour if this graph were
1190
+ * handed to it.
1191
+ */
1192
+ function graphFromRunGraph(graph, options) {
1193
+ const root = graph.nodes[0];
1194
+ if (root === void 0) throw new ValidationError("graphFromRunGraph: a graph needs a root node");
1195
+ return {
1196
+ nodes: graph.nodes.map((node, index) => index === 0 ? {
1197
+ id: node.id,
1198
+ kind: RUN_GRAPH_ROOT_KIND,
1199
+ config: {
1200
+ perWorker: options.perWorker ?? graph.budget,
1201
+ ...options.maxLiveWorkers === void 0 ? {} : { maxLiveWorkers: options.maxLiveWorkers }
1202
+ },
1203
+ profile: node.profile,
1204
+ budget: graph.budget,
1205
+ terminal: true,
1206
+ deliverable: graph.deliverable
1207
+ } : {
1208
+ id: node.id,
1209
+ kind: "agent/v1",
1210
+ profile: node.profile,
1211
+ entry: false,
1212
+ terminal: false,
1213
+ budget: options.perWorker ?? graph.budget
1214
+ }),
1215
+ edges: graph.edges.flatMap((edge) => edge.kind === "delegates" ? [{
1216
+ kind: "delegates",
1217
+ from: { node: edge.from },
1218
+ to: { node: edge.to },
1219
+ directive: edge.directive,
1220
+ ...edge.maxTraversals !== void 0 ? { maxTraversals: edge.maxTraversals } : {}
1221
+ }] : edge.over.map((source) => ({
1222
+ kind: "analyzes",
1223
+ from: {
1224
+ node: source,
1225
+ port: "trace"
1226
+ },
1227
+ to: { node: edge.to },
1228
+ directive: edge.directive,
1229
+ ...edge.maxTraversals !== void 0 ? { maxTraversals: edge.maxTraversals } : {}
1230
+ }))),
1231
+ root: root.id,
1232
+ deliverable: graph.deliverable
1233
+ };
1234
+ }
1235
+ //#endregion
1236
+ //#region src/runtime/graph/admit.ts
1237
+ /**
1238
+ * Edge payload admission (agent-runtime#971): every value crossing an edge is JSON round-tripped,
1239
+ * `undefined` stripped to absence, and a non-representable value (a cycle, a BigInt, a function)
1240
+ * becomes a RECORD of that fact. This is the kernel's existing findings-guard rule, and it is what
1241
+ * makes `inputRef` stable and `onCrash: 'restart'` well defined — a degraded record beats a
1242
+ * vanished edge.
1243
+ */
1244
+ function admitPayload(value) {
1245
+ if (value === void 0) return void 0;
1246
+ try {
1247
+ const text = JSON.stringify(value);
1248
+ if (text === void 0) return { nonCanonical: `payload of type ${typeof value}` };
1249
+ return JSON.parse(text);
1250
+ } catch (error) {
1251
+ return { nonCanonical: error instanceof Error ? error.message : String(error) };
1252
+ }
1253
+ }
1254
+ //#endregion
1255
+ //#region src/runtime/graph/result.ts
1256
+ /**
1257
+ * Turning a finished run into its result: rehydrate each settle's output, reduce the terminals
1258
+ * through the kernel's finalizer seam, and name the honest cause when nothing delivered
1259
+ * (agent-runtime#973). Separate from the loop so "what happened" is readable without reading
1260
+ * "what ran".
1261
+ */
1262
+ function resolveFinalizer(choice) {
1263
+ if (choice === void 0 || choice === "bestDelivered") return bestDelivered;
1264
+ if (choice === "collectDelivered") return collectDelivered;
1265
+ return choice;
1266
+ }
1267
+ /** Every node settlement with its output rehydrated and its completion check applied. */
1268
+ async function materializeSettles(compiled, state, blobs, outCache) {
1269
+ const rehydrate = async (settle) => {
1270
+ if (settle.out !== void 0 || settle.outRef === void 0) return settle;
1271
+ const out = outCache.get(settle.outRef) ?? admitPayload(await blobs.get(settle.outRef));
1272
+ const node = compiled.nodes.get(settle.node);
1273
+ let valid = settle.valid;
1274
+ if (valid === void 0 && node?.deliverable !== void 0 && settle.status === "done") try {
1275
+ valid = await node.deliverable.check(out);
1276
+ } catch {
1277
+ valid = false;
1278
+ }
1279
+ return {
1280
+ ...settle,
1281
+ out,
1282
+ ...valid !== void 0 ? { valid } : {}
1283
+ };
1284
+ };
1285
+ return Promise.all([...compiled.nodes.keys()].flatMap((id) => state.nodes.get(id)?.settles ?? []).map(rehydrate));
1286
+ }
1287
+ /** Turn a finished run into its result: rehydrate, reduce the terminals, classify a no-winner. */
1288
+ async function assembleGraphResult(args) {
1289
+ const settles = await materializeSettles(args.compiled, args.state, args.blobs, args.outCache);
1290
+ const terminals = settles.filter((settle) => args.compiled.nodes.get(settle.node)?.terminal);
1291
+ const ledger = args.ledger;
1292
+ const finish = (result) => {
1293
+ if (result.kind === "no-winner" && args.state.exhaustedEdges.size > 0) throw new GraphEdgeCapError(Object.freeze([...args.state.exhaustedEdges]), Object.freeze([...ledger]), result);
1294
+ return result;
1295
+ };
1296
+ if (args.failure) return finish({
1297
+ kind: "no-winner",
1298
+ reason: args.failure.reason,
1299
+ ...args.failure.error ? { error: args.failure.error } : {},
1300
+ terminals,
1301
+ settles,
1302
+ ledger,
1303
+ unreachable: []
1304
+ });
1305
+ const allTerminalsSettled = args.compiled.terminals.every((id) => (args.state.nodes.get(id)?.settles.length ?? 0) > 0);
1306
+ const pendingTokens = [...args.state.suspensions.values()].filter((suspension) => suspension.status === "pending").map((suspension) => suspension.token);
1307
+ if (pendingTokens.length > 0 && !allTerminalsSettled) return {
1308
+ kind: "suspended",
1309
+ tokens: pendingTokens,
1310
+ terminals,
1311
+ settles,
1312
+ ledger
1313
+ };
1314
+ if (args.aborted) return finish({
1315
+ kind: "no-winner",
1316
+ reason: "aborted",
1317
+ terminals,
1318
+ settles,
1319
+ ledger,
1320
+ unreachable: []
1321
+ });
1322
+ const out = terminals.filter((settle) => settle.status === "done" && settle.valid !== false).length === 0 ? void 0 : await runFinalizer(resolveFinalizer(args.finalizer), {
1323
+ settled: terminals.map((settle) => ({
1324
+ id: settle.node,
1325
+ status: settle.status,
1326
+ valid: settle.status === "done" && settle.valid !== false,
1327
+ ...settle.outRef !== void 0 ? { outRef: settle.outRef } : {}
1328
+ })),
1329
+ blobs: args.blobs,
1330
+ tree: args.scope.view,
1331
+ budget: args.scope.budget
1332
+ });
1333
+ if (out !== void 0) return {
1334
+ kind: "winner",
1335
+ out,
1336
+ terminals,
1337
+ settles,
1338
+ ledger
1339
+ };
1340
+ const unreachable = [...args.compiled.nodes.keys()].filter((id) => (args.state.nodes.get(id)?.settles.length ?? 0) === 0);
1341
+ return finish({
1342
+ kind: "no-winner",
1343
+ reason: args.compiled.terminals.some((id) => unreachable.includes(id)) ? unreachable.length === args.compiled.nodes.size ? "budget-exhausted" : "unreachable-terminal" : "all-children-down",
1344
+ terminals,
1345
+ settles,
1346
+ ledger,
1347
+ unreachable
1348
+ });
1349
+ }
1350
+ //#endregion
1351
+ //#region src/runtime/graph/run-context.ts
1352
+ /**
1353
+ * Opening a graph run: begin or resume the journaled tree, rebuild the pool and the kernel `Scope`
1354
+ * on the SAME recipe the kernel supervisor uses for its own resume, and fold the prior journal
1355
+ * into scheduler state. Extracted so the restart contract is one readable unit rather than a
1356
+ * preamble inside the loop.
1025
1357
  */
1026
- /** Engine-appended `woken` ordinals start here — far above any kernel cursor counter. */
1358
+ /** Engine-appended `woken` ordinals start here — far above any kernel cursor counter, so the two
1359
+ * counters can advance independently without ever colliding. */
1027
1360
  const ENGINE_WOKEN_SEQ_BASE = 1e7;
1361
+ /** Begin or resume a run's journaled tree, pool, scope and folded state. */
1362
+ async function openGraphRun(args) {
1363
+ const journal = args.journal ?? new InMemorySpawnJournal();
1364
+ const blobs = args.blobs ?? new InMemoryResultBlobStore();
1365
+ const loaded = await journal.loadTree(args.runId);
1366
+ if (loaded !== void 0 && args.resume !== true) throw new ValidationError(`runEngineGraph: runId '${args.runId}' already exists; pass resume: true to continue it or use a new runId`);
1367
+ const prior = loaded ?? [];
1368
+ const resuming = args.resume === true && prior.length > 0;
1369
+ let runEpochMs = args.now();
1370
+ let rootBudget = args.budget;
1371
+ if (loaded === void 0) await journal.beginTree(args.runId, new Date(runEpochMs).toISOString());
1372
+ if (resuming) {
1373
+ const root = prior.find((ev) => ev.kind === "spawned" && ev.id === args.runId);
1374
+ if (root?.kind === "spawned") {
1375
+ runEpochMs = Date.parse(root.at);
1376
+ rootBudget = root.budget;
1377
+ }
1378
+ } else await journal.appendEvent(args.runId, {
1379
+ kind: "spawned",
1380
+ id: args.runId,
1381
+ label: "graph-root",
1382
+ budget: args.budget,
1383
+ runtime: "inline",
1384
+ seq: 0,
1385
+ at: new Date(runEpochMs).toISOString()
1386
+ });
1387
+ const elapsed = () => args.now() - runEpochMs;
1388
+ const measured = resuming ? sumMeasuredSpendFromEvents([...prior]) : void 0;
1389
+ const pool = createBudgetPool(rootBudget, elapsed, measured === void 0 ? void 0 : {
1390
+ committed: addSpend(measured.childWork, measured.driverInference),
1391
+ uncertainReservations: uncertainSpawnBudgets([...prior]),
1392
+ ...rootBudget.deadlineMs !== void 0 ? { absoluteDeadlineMs: runEpochMs + rootBudget.deadlineMs } : {}
1393
+ });
1394
+ const abort = new AbortController();
1395
+ if (args.signal !== void 0) {
1396
+ const forward = () => abort.abort(args.signal?.reason);
1397
+ args.signal.addEventListener("abort", forward, { once: true });
1398
+ args.onAbort(() => args.signal?.removeEventListener("abort", forward));
1399
+ if (args.signal.aborted) abort.abort(args.signal.reason);
1400
+ }
1401
+ const scope = createScope({
1402
+ parentId: args.runId,
1403
+ root: args.runId,
1404
+ pool,
1405
+ journal,
1406
+ blobs,
1407
+ executors: createExecutorRegistry(),
1408
+ seams: {},
1409
+ depth: 0,
1410
+ signal: abort.signal,
1411
+ now: args.now,
1412
+ ...resuming ? { resumeFrom: {
1413
+ settled: await replaySpawnTree(journal, blobs, args.runId),
1414
+ view: materializeTreeView([...prior]),
1415
+ maxSpawnOrdinal: maxSeqOf([...prior], (ev) => ev.kind === "spawned"),
1416
+ maxCursorSeq: maxSeqOf([...prior], closesCursorSlot),
1417
+ maxWaitOrdinal: maxSeqOf([...prior], (ev) => ev.kind === "waiting"),
1418
+ waits: pendingWaits([...prior]),
1419
+ keys: /* @__PURE__ */ new Map(),
1420
+ priorSpend: sumMeasuredSpendFromEvents([...prior])
1421
+ } } : {}
1422
+ });
1423
+ const state = emptyFoldState(args.compiled);
1424
+ if (resuming) for (const ev of prior) applyGraphFoldEvent(state, ev, args.compiled);
1425
+ return {
1426
+ runId: args.runId,
1427
+ journal,
1428
+ blobs,
1429
+ scope,
1430
+ abort,
1431
+ state,
1432
+ resuming,
1433
+ prior,
1434
+ engineSeq: resuming ? maxEngineSeq(prior) + 1 : 0,
1435
+ engineWokenSeq: ENGINE_WOKEN_SEQ_BASE + (resuming ? prior.filter((ev) => ev.kind === "woken" && ev.seq >= 1e7).length : 0),
1436
+ ledgerSeq: prior.filter((ev) => ev.kind === "edge").length
1437
+ };
1438
+ }
1439
+ function maxEngineSeq(events) {
1440
+ let max = -1;
1441
+ for (const ev of events) if ((ev.kind === "node-inputs-resolved" || ev.kind === "edge-verdict" || ev.kind === "join-state" || ev.kind === "waiting" && ev.spec.kind === "token") && ev.seq > max) max = ev.seq;
1442
+ return max;
1443
+ }
1444
+ //#endregion
1445
+ //#region src/runtime/graph/suspension.ts
1446
+ /**
1447
+ * Suspensions (agent-runtime#976): a node parks on a host wake as the kernel's `waiting`/`woken`
1448
+ * pair. The engine owns the transition table; a host owns only how the wake arrives.
1449
+ */
1028
1450
  const SUSPEND_MARK = "__graphSuspension";
1029
- /** What a kind's executor returns to park its node on a host wake (agent-runtime#976). */
1451
+ /** Build a suspension request. `wait` never expires; `fail` settles the node down at its deadline;
1452
+ * `default` resolves with the given payload. */
1030
1453
  function suspended(options = {}) {
1031
1454
  const onExpire = options.onExpire ?? "wait";
1032
1455
  if (onExpire !== "wait" && options.expiresInMs === void 0) throw new ValidationError(`suspended: onExpire '${onExpire}' requires expiresInMs`);
@@ -1039,24 +1462,48 @@ function suspended(options = {}) {
1039
1462
  ...options.default !== void 0 ? { default: options.default } : {}
1040
1463
  };
1041
1464
  }
1465
+ /** Whether a node's output is a park request rather than its result. */
1042
1466
  function isSuspensionRequest(value) {
1043
1467
  return typeof value === "object" && value !== null && value[SUSPEND_MARK] === true;
1044
1468
  }
1045
- /** Admission for every value crossing an edge (#971): JSON round-trip, `undefined` stripped, a
1046
- * non-representable value becomes a RECORD of that fact — a degraded record beats a vanished
1047
- * edge. */
1048
- function admitPayload(value) {
1049
- if (value === void 0) return void 0;
1050
- try {
1051
- const text = JSON.stringify(value);
1052
- if (text === void 0) return { nonCanonical: `payload of type ${typeof value}` };
1053
- return JSON.parse(text);
1054
- } catch (error) {
1055
- return { nonCanonical: error instanceof Error ? error.message : String(error) };
1056
- }
1469
+ /** Content-addressed over the run identity, so a restart recomputes it and needs no token table. */
1470
+ function mintSuspensionToken(runId, instance) {
1471
+ return contentAddress({
1472
+ runId,
1473
+ instance,
1474
+ kind: "graph-suspension"
1475
+ });
1476
+ }
1477
+ /** The journal id one suspension's `waiting`/`woken` pair shares. */
1478
+ function suspensionNodeId(token) {
1479
+ return `graphwait:${token}`;
1057
1480
  }
1058
- /** Run a graph to its result: `createGraphRun` awaited — the one-call form for a run that needs
1059
- * no live host wakes. */
1481
+ /** The token inside a suspension node id, or `undefined` for any other id. */
1482
+ function tokenFromSuspensionNodeId(id) {
1483
+ return id.startsWith("graphwait:") ? id.slice(10) : void 0;
1484
+ }
1485
+ //#endregion
1486
+ //#region src/runtime/graph/scheduler.ts
1487
+ /**
1488
+ * The engine scheduler (agent-runtime#980, durable per #974/#976): run a compiled graph by hosting
1489
+ * every node instance on one kernel `Scope`. The pool, the journal, the blob store and
1490
+ * cancellation are the kernel's; the scheduler owns only what a graph adds — releasing nodes over
1491
+ * guarded edges, delivering payloads and directives, the two cycle caps, and suspensions.
1492
+ *
1493
+ * DURABILITY — fold, never checkpoint. Every decision is journaled BEFORE its effect is visible
1494
+ * (blob-then-journal where a ref is minted), then applied to live state through the SAME reducer
1495
+ * (`applyGraphFoldEvent`) a restart replays the journal through. Kill the process at any journal
1496
+ * boundary and a restart re-enters the exact state: settled nodes restore from `outRef` and never
1497
+ * re-execute; a released-but-unspawned instance re-enters from its pinned `inputRef`; an in-flight
1498
+ * instance is in-doubt and re-enters per its kind's `onCrash`.
1499
+ *
1500
+ * The parts this file does NOT own, so it stays a loop and not a god object: payload admission
1501
+ * (`admit.ts`), the edge ledger (`ledger.ts`), the join rule (`join.ts`), suspension vocabulary
1502
+ * (`suspension.ts`), the journal/pool/scope bootstrap (`run-context.ts`), and result assembly
1503
+ * (`result.ts`).
1504
+ */
1505
+ /** Run a graph to its result: `createGraphRun` awaited — the one-call form for a run that needs no
1506
+ * live host wakes. */
1060
1507
  async function runEngineGraph(engine, spec, task, options) {
1061
1508
  return createGraphRun(engine, spec, task, options).done;
1062
1509
  }
@@ -1065,17 +1512,17 @@ async function runEngineGraph(engine, spec, task, options) {
1065
1512
  * wakes through `resume`/`expire` while it runs (#976).
1066
1513
  */
1067
1514
  function createGraphRun(engine, spec, task, options) {
1068
- const compiled = "nodes" in spec && spec.nodes instanceof Map ? spec : compileGraph(engine, spec);
1515
+ const compiled = asCompiled(engine, spec);
1069
1516
  const wakes = [];
1070
- let notifyWake;
1071
- const handleState = { finished: false };
1072
- const done = runGraphLoop(engine, compiled, task, options, wakes, () => notifyWake?.(), (fn) => {
1073
- notifyWake = fn;
1517
+ let signalWake = () => {};
1518
+ let finished = false;
1519
+ const done = runGraphLoop(engine, compiled, task, options, wakes, (fn) => {
1520
+ signalWake = fn;
1074
1521
  }).finally(() => {
1075
- handleState.finished = true;
1522
+ finished = true;
1076
1523
  });
1077
1524
  const queue = (token, payload, expire) => {
1078
- if (handleState.finished) return Promise.reject(new ValidationError(`graph resume: run completed; start a new run over the same journal to wake '${token}'`));
1525
+ if (finished) return Promise.reject(new ValidationError(`graph resume: run completed; start a new run over the same journal to wake '${token}'`));
1079
1526
  return new Promise((settle, fail) => {
1080
1527
  wakes.push({
1081
1528
  token,
@@ -1084,7 +1531,7 @@ function createGraphRun(engine, spec, task, options) {
1084
1531
  settle,
1085
1532
  fail
1086
1533
  });
1087
- notifyWake?.();
1534
+ signalWake();
1088
1535
  });
1089
1536
  };
1090
1537
  return {
@@ -1093,727 +1540,534 @@ function createGraphRun(engine, spec, task, options) {
1093
1540
  expire: (token) => queue(token, void 0, true)
1094
1541
  };
1095
1542
  }
1096
- async function runGraphLoop(engine, compiled, task, options, wakes, _notify, onWakeSignal) {
1543
+ function asCompiled(engine, spec) {
1544
+ return "nodes" in spec && spec.nodes instanceof Map ? spec : compileGraph(engine, spec);
1545
+ }
1546
+ /** Everything a run must be able to satisfy before it spends anything. */
1547
+ function assertRunnable(engine, compiled, options) {
1097
1548
  const context = "runEngineGraph";
1098
1549
  for (const node of compiled.nodes.values()) {
1099
1550
  const missing = node.kind.effects.filter((name) => !(name in engine.effects));
1100
1551
  if (missing.length > 0) throw new ValidationError(`${context}: node ${node.id} needs effect(s) ${missing.join(", ")} the host did not provide`);
1101
1552
  if (node.spec.budget === void 0 && options.perNode === void 0) throw new ValidationError(`${context}: node ${node.id} declares no budget and options.perNode is absent — the engine invents no split`);
1102
1553
  }
1103
- for (const edge of compiled.edges) if (edge.spec.directive !== void 0 && options.prompts === void 0) throw new ValidationError(`${context}: edge ${edge.id} carries a directive but options.prompts is absent`);
1554
+ for (const edge of compiled.edges) if (isEngineFired(edge) && edge.spec.directive !== void 0 && options.prompts === void 0) throw new ValidationError(`${context}: edge ${edge.id} carries a directive but options.prompts is absent`);
1555
+ }
1556
+ async function runGraphLoop(engine, compiled, task, options, wakes, onWakeSignal) {
1557
+ assertRunnable(engine, compiled, options);
1104
1558
  const now = options.now ?? Date.now;
1105
- const journal = options.journal ?? new InMemorySpawnJournal();
1106
- const blobs = options.blobs ?? new InMemoryResultBlobStore();
1107
1559
  const runId = options.runId ?? `graph:${contentAddress({ task }).slice(0, 18)}`;
1108
- const prior = await journal.loadTree(runId);
1109
- if (prior !== void 0 && options.resume !== true) throw new ValidationError(`${context}: runId '${runId}' already exists; pass resume: true to continue it or use a new runId`);
1110
- const resuming = options.resume === true && prior !== void 0 && prior.length > 0;
1111
- let runEpochMs = now();
1112
- let rootBudget = options.budget;
1113
- if (prior === void 0) await journal.beginTree(runId, new Date(runEpochMs).toISOString());
1114
- if (!resuming) await journal.appendEvent(runId, {
1115
- kind: "spawned",
1116
- id: runId,
1117
- label: "graph-root",
1560
+ let detachOuterAbort = () => {};
1561
+ const context = await openGraphRun({
1562
+ compiled,
1563
+ runId,
1118
1564
  budget: options.budget,
1119
- runtime: "inline",
1120
- seq: 0,
1121
- at: new Date(runEpochMs).toISOString()
1122
- });
1123
- if (resuming) {
1124
- const root = prior.find((ev) => ev.kind === "spawned" && ev.id === runId);
1125
- if (root?.kind === "spawned") {
1126
- runEpochMs = Date.parse(root.at);
1127
- rootBudget = root.budget;
1565
+ ...options.journal !== void 0 ? { journal: options.journal } : {},
1566
+ ...options.blobs !== void 0 ? { blobs: options.blobs } : {},
1567
+ now,
1568
+ ...options.resume !== void 0 ? { resume: options.resume } : {},
1569
+ ...options.signal !== void 0 ? { signal: options.signal } : {},
1570
+ onAbort: (detach) => {
1571
+ detachOuterAbort = detach;
1128
1572
  }
1129
- }
1130
- const elapsed = () => now() - runEpochMs;
1131
- const pool = resuming ? createBudgetPool(rootBudget, elapsed, {
1132
- committed: (() => {
1133
- const measured = sumMeasuredSpendFromEvents(prior);
1134
- return addSpend(measured.childWork, measured.driverInference);
1135
- })(),
1136
- uncertainReservations: uncertainSpawnBudgets(prior),
1137
- ...rootBudget.deadlineMs !== void 0 ? { absoluteDeadlineMs: runEpochMs + rootBudget.deadlineMs } : {}
1138
- }) : createBudgetPool(rootBudget, elapsed);
1139
- const abort = new AbortController();
1140
- const onOuterAbort = () => abort.abort(options.signal?.reason);
1141
- options.signal?.addEventListener("abort", onOuterAbort, { once: true });
1142
- if (options.signal?.aborted) abort.abort(options.signal.reason);
1143
- const scope = createScope({
1144
- parentId: runId,
1145
- root: runId,
1146
- pool,
1573
+ });
1574
+ const { abort, blobs, journal, scope, state } = context;
1575
+ const ledger = createEdgeLedger({
1147
1576
  journal,
1148
- blobs,
1149
- executors: createExecutorRegistry(),
1150
- seams: {},
1151
- depth: 0,
1152
- signal: abort.signal,
1577
+ runId,
1153
1578
  now,
1154
- ...resuming ? { resumeFrom: {
1155
- settled: await replaySpawnTree(journal, blobs, runId),
1156
- view: materializeTreeView(prior),
1157
- maxSpawnOrdinal: maxSeqOf(prior, (ev) => ev.kind === "spawned"),
1158
- maxCursorSeq: maxSeqOf(prior, closesCursorSlot),
1159
- maxWaitOrdinal: maxSeqOf(prior, (ev) => ev.kind === "waiting"),
1160
- waits: pendingWaits(prior),
1161
- keys: /* @__PURE__ */ new Map(),
1162
- priorSpend: sumMeasuredSpendFromEvents(prior)
1163
- } } : {}
1579
+ startSeq: context.ledgerSeq
1164
1580
  });
1165
- try {
1166
- return await runToResult();
1167
- } finally {
1168
- options.signal?.removeEventListener("abort", onOuterAbort);
1169
- abort.abort("run complete");
1170
- }
1171
- async function runToResult() {
1172
- const state = emptyFoldState(compiled);
1173
- if (resuming) for (const ev of prior) applyGraphFoldEvent(state, ev, compiled);
1174
- let engineSeq = resuming ? maxEngineSeq(prior) + 1 : 0;
1175
- let engineWokenSeq = ENGINE_WOKEN_SEQ_BASE + (resuming ? prior.filter((ev) => ev.kind === "woken" && ev.seq >= 1e7).length : 0);
1176
- const appendAndApply = async (ev) => {
1177
- await journal.appendEvent(runId, ev);
1178
- applyGraphFoldEvent(state, ev, compiled);
1179
- };
1180
- const ledger = [];
1181
- const liveHandles = /* @__PURE__ */ new Map();
1182
- const waitingForBudget = [];
1183
- const outCache = /* @__PURE__ */ new Map();
1184
- let ledgerSeq = resuming ? prior.filter((ev) => ev.kind === "edge").length : 0;
1185
- let liveCount = 0;
1186
- let failure;
1187
- const recordLedger = async (edge, traversal, outcome, reason) => {
1188
- const spec = edge.spec;
1189
- const entry = {
1190
- edge: edge.id,
1191
- kind: spec.kind,
1192
- from: spec.from.node,
1193
- to: spec.to.node,
1194
- traversal,
1195
- outcome,
1196
- ...spec.directive !== void 0 ? { directive: `${spec.directive.surface}/v${spec.directive.version}` } : {},
1197
- ...spec.kind === "data" ? { port: edge.toPort } : {},
1198
- ...reason !== void 0 ? { reason } : {}
1199
- };
1200
- ledger.push(entry);
1201
- await journal.appendEvent(runId, {
1202
- kind: "edge",
1203
- id: `graph:${spec.to.node}`,
1204
- edge: {
1205
- kind: spec.kind,
1206
- from: spec.from.node,
1207
- to: spec.to.node,
1208
- ...entry.directive !== void 0 ? { directive: entry.directive } : {},
1209
- ...entry.port !== void 0 ? { port: entry.port } : {}
1210
- },
1211
- traversal,
1212
- outcome: outcome === "delivered" ? "delivered" : outcome,
1213
- bytes: 0,
1214
- ...reason !== void 0 ? { reason } : {},
1215
- seq: ledgerSeq++,
1216
- at: new Date(now()).toISOString()
1217
- });
1218
- };
1219
- /** Spawn one released instance from its journaled envelope — the ONE entry path, used for a
1220
- * fresh release, a budget-parked retry, and a restart re-entry alike. */
1221
- const spawnInstance = async (instanceLabel) => {
1222
- if (failure) return;
1223
- const instance = state.instances.get(instanceLabel);
1224
- const node = instance === void 0 ? void 0 : compiled.nodes.get(instance.node);
1225
- if (!instance || !node || instance.inputRef === void 0) throw new ValidationError(`${context}: instance ${instanceLabel} has no journaled envelope`);
1226
- const envelope = await blobs.get(instance.inputRef);
1227
- if (envelope === void 0) throw new ValidationError(`${context}: envelope ${instance.inputRef} is not in the blob store`);
1228
- const effects = narrowEffects(node.kind.effects, engine.effects, `${context}: node ${node.id}`);
1229
- const agent = node.kind.run({
1230
- config: node.config,
1231
- profile: {
1232
- name: node.id,
1233
- ...node.spec.profile ?? {}
1234
- },
1235
- inputs: envelope.inputs,
1236
- effects
1237
- });
1238
- const budget = node.spec.budget ?? options.perNode;
1239
- const spawned = scope.spawn(agent, envelope.task, {
1240
- label: instanceLabel,
1241
- budget
1242
- });
1243
- if (spawned.ok) {
1244
- liveCount += 1;
1245
- liveHandles.set(spawned.handle.id, instanceLabel);
1246
- applyGraphFoldEvent(state, {
1247
- kind: "spawned",
1248
- id: spawned.handle.id,
1249
- label: instanceLabel,
1250
- budget,
1251
- runtime: "inline",
1252
- seq: 0,
1253
- at: ""
1254
- }, compiled);
1255
- return;
1256
- }
1257
- if (spawned.reason === "budget-exhausted" || spawned.reason === "max-live-workers") {
1258
- waitingForBudget.push(instanceLabel);
1259
- return;
1260
- }
1261
- failure = {
1262
- reason: "driver-failed",
1263
- error: {
1264
- name: "SpawnRefused",
1265
- message: `node ${node.id}: ${spawned.reason}`
1266
- }
1267
- };
1268
- abort.abort(`spawn refused: ${node.id}`);
1269
- };
1270
- /** Release a node: journal the wave (join-state + envelope) then spawn. */
1271
- const release = async (nodeId, satisfiedBy, consumedPending) => {
1272
- const node = compiled.nodes.get(nodeId);
1273
- const folded = state.nodes.get(nodeId);
1274
- if (!node || !folded) return;
1275
- const visit = folded.visits + 1;
1276
- if (visit > node.maxVisits) {
1277
- failure = {
1278
- reason: "cycle-budget-exceeded",
1279
- error: {
1280
- name: "GraphCycleBudget",
1281
- message: `node ${nodeId} entered ${visit} times; maxVisits ${node.maxVisits}`
1282
- }
1283
- };
1284
- abort.abort(`cycle-budget-exceeded: ${nodeId}`);
1285
- return;
1286
- }
1287
- const instanceLabel = `${nodeId}#${visit}`;
1288
- const inputs = {};
1289
- const directives = [];
1290
- const traces = [];
1291
- for (const edge of satisfiedBy) {
1292
- const foldedEdge = state.edges.get(edge.id);
1293
- const spec = edge.spec;
1294
- if (spec.kind === "data" && foldedEdge?.state === "satisfied") {
1295
- let payload = foldedEdge.payloadRef === void 0 ? void 0 : outCache.get(foldedEdge.payloadRef);
1296
- if (payload === void 0 && foldedEdge.payloadRef !== void 0) payload = admitPayload(await blobs.get(foldedEdge.payloadRef));
1297
- let outcome = "delivered";
1298
- let reason;
1299
- if (spec.projection !== void 0) try {
1300
- payload = admitPayload(applyProjection(payload, spec.projection, `edge ${edge.id}`));
1301
- } catch (error) {
1302
- outcome = "empty";
1303
- reason = error instanceof Error ? error.message : String(error);
1304
- payload = void 0;
1305
- }
1306
- if (payload === void 0 && outcome === "delivered") outcome = "empty";
1307
- await recordLedger(edge, (foldedEdge?.traversals ?? 0) + 1, outcome, reason);
1308
- if (payload !== void 0) inputs[edge.toPort] = payload;
1309
- } else {
1310
- if (spec.directive !== void 0 && options.prompts !== void 0) directives.push(options.prompts.resolve(spec.directive).text);
1311
- if (spec.kind === "analyzes" && foldedEdge?.state === "satisfied") traces.push(`trace of ${spec.from.node}: ${foldedEdge.payloadRef ?? "(no traceRef)"}`);
1312
- await recordLedger(edge, (foldedEdge?.traversals ?? 0) + 1, "delivered");
1313
- }
1581
+ let engineSeq = context.engineSeq;
1582
+ let wokenSeq = context.engineWokenSeq;
1583
+ const liveHandles = /* @__PURE__ */ new Map();
1584
+ const waitingForBudget = [];
1585
+ const outCache = /* @__PURE__ */ new Map();
1586
+ let liveCount = 0;
1587
+ let failure;
1588
+ /** Journal one engine event, then apply it through the reducer a restart will replay. */
1589
+ const emit = async (ev) => {
1590
+ await journal.appendEvent(runId, ev);
1591
+ applyGraphFoldEvent(state, ev, compiled);
1592
+ };
1593
+ const stamp = () => new Date(now()).toISOString();
1594
+ const fail = (reason, name, message) => {
1595
+ failure = {
1596
+ reason,
1597
+ error: {
1598
+ name,
1599
+ message
1314
1600
  }
1315
- const nodeTask = [
1316
- nodeId === compiled.root ? task : "",
1317
- ...directives,
1318
- ...traces
1319
- ].filter((part) => part.length > 0).join("\n\n");
1320
- const envelope = {
1321
- task: nodeTask.length > 0 ? nodeTask : task,
1322
- inputs
1323
- };
1324
- const inputRef = contentAddress(envelope);
1325
- await blobs.put(inputRef, envelope);
1326
- const at = new Date(now()).toISOString();
1327
- await appendAndApply({
1328
- kind: "node-inputs-resolved",
1329
- id: instanceLabel,
1330
- node: nodeId,
1331
- instance: instanceLabel,
1332
- inputRef,
1333
- seq: engineSeq++,
1334
- at
1335
- });
1336
- await appendAndApply({
1337
- kind: "join-state",
1338
- id: instanceLabel,
1339
- node: nodeId,
1340
- rule: node.join,
1341
- satisfiedBy: satisfiedBy.map((edge) => edge.id),
1342
- consumedPending,
1343
- instance: instanceLabel,
1344
- seq: engineSeq++,
1345
- at
1346
- });
1347
- await spawnInstance(instanceLabel);
1348
1601
  };
1349
- /** Join evaluation per ADC's rules over the folded edge states. */
1350
- const tryRelease = async (nodeId) => {
1351
- const node = compiled.nodes.get(nodeId);
1352
- const folded = state.nodes.get(nodeId);
1353
- if (!node || !folded || folded.blocked || failure) return;
1354
- const gating = node.inbound;
1355
- if (gating.length === 0) return;
1356
- const states = gating.map((edge) => ({
1357
- edge,
1358
- folded: state.edges.get(edge.id)
1359
- }));
1360
- const settled = states.filter((entry) => entry.folded && entry.folded.state !== "pending");
1361
- const satisfied = states.filter((entry) => entry.folded?.state === "satisfied");
1362
- const failed = states.filter((entry) => entry.folded?.state === "failed");
1363
- const allSettled = settled.length === gating.length;
1364
- let releaseNow = false;
1365
- switch (node.join) {
1366
- case "all":
1367
- releaseNow = satisfied.length === gating.length && !states.some((entry) => entry.folded?.state === "dead" || entry.folded?.state === "failed");
1368
- break;
1369
- case "any":
1370
- releaseNow = satisfied.length > 0;
1371
- if (!releaseNow && allSettled) folded.blocked = true;
1372
- break;
1373
- case "any_failed":
1374
- releaseNow = failed.length > 0;
1375
- if (!releaseNow && allSettled) folded.blocked = true;
1376
- break;
1377
- case "all_done":
1378
- releaseNow = allSettled;
1379
- break;
1380
- }
1381
- if (!releaseNow) return;
1382
- const consuming = node.join === "any" ? [satisfied[0]] : node.join === "any_failed" ? [failed[0]] : settled;
1383
- for (const entry of consuming) {
1384
- const cap = entry.edge.spec.maxTraversals;
1385
- if (cap !== void 0 && (entry.folded?.traversals ?? 0) >= cap) {
1386
- recordLedger(entry.edge, (entry.folded?.traversals ?? 0) + 1, "unpropagated", `traversal-cap-exhausted (max ${cap})`);
1387
- await appendAndApply({
1388
- kind: "edge-verdict",
1389
- id: `graph:${nodeId}`,
1390
- edge: entry.edge.id,
1391
- fired: false,
1392
- sourceStatus: "done",
1393
- capped: true,
1394
- seq: engineSeq++,
1395
- at: new Date(now()).toISOString()
1396
- });
1397
- return;
1602
+ abort.abort(`${reason}: ${message}`);
1603
+ };
1604
+ /** The ONE entry path: spawn a released instance from its journaled envelope. Used by a fresh
1605
+ * release, a budget-parked retry, and a restart re-entry alike. */
1606
+ const spawnInstance = async (label) => {
1607
+ if (failure) return;
1608
+ const instance = state.instances.get(label);
1609
+ const node = instance === void 0 ? void 0 : compiled.nodes.get(instance.node);
1610
+ if (!instance || !node || instance.inputRef === void 0) throw new ValidationError(`runEngineGraph: instance ${label} has no journaled envelope`);
1611
+ const envelope = await blobs.get(instance.inputRef);
1612
+ if (envelope === void 0) throw new ValidationError(`runEngineGraph: envelope ${instance.inputRef} is not in the blob store`);
1613
+ const agent = node.kind.run({
1614
+ config: node.config,
1615
+ profile: {
1616
+ name: node.id,
1617
+ ...node.spec.profile ?? {}
1618
+ },
1619
+ inputs: envelope.inputs,
1620
+ effects: narrowEffects(node.kind.effects, engine.effects, `runEngineGraph: node ${node.id}`),
1621
+ host: { runNested: (inner, task, opts) => runEngineGraph(engine, inner, task, {
1622
+ budget: opts.budget,
1623
+ ...opts.perNode === void 0 ? {} : { perNode: opts.perNode },
1624
+ journal,
1625
+ blobs,
1626
+ ...options.prompts === void 0 ? {} : { prompts: options.prompts },
1627
+ runId: `${runId}:${opts.runId}`,
1628
+ ...opts.signal === void 0 ? {} : { signal: opts.signal },
1629
+ now
1630
+ }).then((result) => ({
1631
+ kind: result.kind,
1632
+ ...result.kind === "winner" ? { out: result.out } : {}
1633
+ })) }
1634
+ });
1635
+ const budget = node.spec.budget ?? options.perNode;
1636
+ const spawned = scope.spawn(agent, envelope.task, {
1637
+ label,
1638
+ budget
1639
+ });
1640
+ if (spawned.ok) {
1641
+ liveCount += 1;
1642
+ liveHandles.set(spawned.handle.id, label);
1643
+ applyGraphFoldEvent(state, {
1644
+ kind: "spawned",
1645
+ id: spawned.handle.id,
1646
+ label,
1647
+ budget,
1648
+ runtime: "inline",
1649
+ seq: 0,
1650
+ at: ""
1651
+ }, compiled);
1652
+ return;
1653
+ }
1654
+ if (spawned.reason === "budget-exhausted" || spawned.reason === "max-live-workers") {
1655
+ waitingForBudget.push(label);
1656
+ return;
1657
+ }
1658
+ fail("driver-failed", "SpawnRefused", `node ${node.id}: ${spawned.reason}`);
1659
+ };
1660
+ /** Pin an instance's envelope (inputs + task) by content address; answers the instance label. */
1661
+ const openInstance = async (nodeId, visit, envelope) => {
1662
+ const label = `${nodeId}#${visit}`;
1663
+ const inputRef = contentAddress(envelope);
1664
+ await blobs.put(inputRef, envelope);
1665
+ await emit({
1666
+ kind: "node-inputs-resolved",
1667
+ id: label,
1668
+ node: nodeId,
1669
+ instance: label,
1670
+ inputRef,
1671
+ seq: engineSeq++,
1672
+ at: stamp()
1673
+ });
1674
+ return label;
1675
+ };
1676
+ const enterEntryNode = async (nodeId) => {
1677
+ const node = compiled.nodes.get(nodeId);
1678
+ const folded = state.nodes.get(nodeId);
1679
+ if (!node || !folded) return;
1680
+ const visit = folded.visits + 1;
1681
+ if (visit > node.maxVisits) return;
1682
+ await spawnInstance(await openInstance(nodeId, visit, {
1683
+ task,
1684
+ inputs: {}
1685
+ }));
1686
+ };
1687
+ /** Build the released instance's envelope from its wave: data payloads projected and admitted,
1688
+ * directives resolved, trace refs lined up — every consumption ledgered as it is taken. */
1689
+ const consumeWave = async (nodeId, consuming) => {
1690
+ const inputs = {};
1691
+ const directives = [];
1692
+ const traces = [];
1693
+ for (const { edge, folded } of consuming) {
1694
+ const spec = edge.spec;
1695
+ const traversal = (folded?.traversals ?? 0) + 1;
1696
+ if (spec.kind === "data" && folded?.state === "satisfied") {
1697
+ let payload = folded.payloadRef === void 0 ? void 0 : outCache.get(folded.payloadRef) ?? admitPayload(await blobs.get(folded.payloadRef));
1698
+ let outcome = "delivered";
1699
+ let reason;
1700
+ if (spec.projection !== void 0) try {
1701
+ payload = admitPayload(applyProjection(payload, spec.projection, `edge ${edge.id}`));
1702
+ } catch (error) {
1703
+ outcome = "empty";
1704
+ reason = error instanceof Error ? error.message : String(error);
1705
+ payload = void 0;
1398
1706
  }
1707
+ if (payload === void 0) outcome = "empty";
1708
+ await ledger.record(edge, traversal, outcome, reason);
1709
+ if (payload !== void 0) inputs[edge.toPort] = payload;
1710
+ continue;
1399
1711
  }
1400
- const consumedPending = states.filter((entry) => entry.folded?.state === "pending" && hasLiveInstance(state, liveHandles, entry.edge.spec.from.node)).map((entry) => entry.edge.id);
1401
- await release(nodeId, consuming.map((entry) => entry.edge), consumedPending);
1712
+ if (spec.directive !== void 0 && options.prompts !== void 0) directives.push(options.prompts.resolve(spec.directive).text);
1713
+ if (spec.kind === "analyzes" && folded?.state === "satisfied") traces.push(`trace of ${spec.from.node}: ${folded.payloadRef ?? "(no traceRef)"}`);
1714
+ await ledger.record(edge, traversal, "delivered");
1715
+ }
1716
+ const composed = [
1717
+ nodeId === compiled.root ? task : "",
1718
+ ...directives,
1719
+ ...traces
1720
+ ].filter((part) => part.length > 0).join("\n\n");
1721
+ return {
1722
+ task: composed.length > 0 ? composed : task,
1723
+ inputs
1402
1724
  };
1403
- /** Judge every outbound edge of a settle: journal the verdict, apply, then try the joins. */
1404
- const propagate = async (instanceLabel, settle) => {
1405
- const node = compiled.nodes.get(settle.node);
1406
- if (!node) return;
1407
- const succeeded = settle.status === "done" && settle.valid !== false;
1408
- const sourceSettles = state.nodes.get(settle.node)?.settles.length ?? 0;
1409
- for (const edge of node.outbound) {
1410
- const folded = state.edges.get(edge.id);
1411
- if (!folded) continue;
1412
- if (folded.judgedSourceSettles >= sourceSettles) continue;
1413
- let fired = false;
1414
- let inputRef;
1415
- if (succeeded) {
1416
- const guardContext = {
1417
- node: settle.node,
1418
- out: settle.out,
1419
- visits: state.nodes.get(settle.node)?.visits ?? 0,
1420
- valid: settle.valid ?? true
1421
- };
1422
- fired = edge.spec.guard === void 0 || evaluateCondition(edge.spec.guard, guardContext);
1423
- if (fired) inputRef = edge.spec.kind === "analyzes" ? settle.traceRef ?? void 0 : settle.outRef;
1424
- }
1425
- await appendAndApply({
1725
+ };
1726
+ const release = async (nodeId, consuming, consumedPending) => {
1727
+ const node = compiled.nodes.get(nodeId);
1728
+ const folded = state.nodes.get(nodeId);
1729
+ if (!node || !folded) return;
1730
+ const visit = folded.visits + 1;
1731
+ if (visit > node.maxVisits) {
1732
+ fail("cycle-budget-exceeded", "GraphCycleBudget", `node ${nodeId} entered ${visit} times; maxVisits ${node.maxVisits}`);
1733
+ return;
1734
+ }
1735
+ const envelope = await consumeWave(nodeId, consuming);
1736
+ const label = await openInstance(nodeId, visit, envelope);
1737
+ await emit({
1738
+ kind: "join-state",
1739
+ id: label,
1740
+ node: nodeId,
1741
+ rule: node.join,
1742
+ satisfiedBy: consuming.map(({ edge }) => edge.id),
1743
+ consumedPending: [...consumedPending],
1744
+ instance: label,
1745
+ seq: engineSeq++,
1746
+ at: stamp()
1747
+ });
1748
+ await spawnInstance(label);
1749
+ };
1750
+ const tryRelease = async (nodeId) => {
1751
+ const node = compiled.nodes.get(nodeId);
1752
+ const folded = state.nodes.get(nodeId);
1753
+ if (!node || !folded || folded.blocked || failure) return;
1754
+ const gating = node.inbound.map((edge) => ({
1755
+ edge,
1756
+ folded: state.edges.get(edge.id)
1757
+ }));
1758
+ const decision = decideJoin(node.join, gating);
1759
+ if (decision.blocked) folded.blocked = true;
1760
+ if (!decision.release) return;
1761
+ for (const entry of decision.consuming) {
1762
+ const cap = entry.edge.spec.maxTraversals;
1763
+ if (cap !== void 0 && (entry.folded?.traversals ?? 0) >= cap) {
1764
+ await ledger.record(entry.edge, (entry.folded?.traversals ?? 0) + 1, "unpropagated", `traversal-cap-exhausted (max ${cap})`);
1765
+ await emit({
1426
1766
  kind: "edge-verdict",
1427
- id: instanceLabel,
1428
- edge: edge.id,
1429
- fired,
1430
- sourceStatus: settle.status === "down" ? "down" : succeeded ? "done" : "invalid",
1431
- ...inputRef !== void 0 ? { inputRef } : {},
1767
+ id: `graph:${nodeId}`,
1768
+ edge: entry.edge.id,
1769
+ fired: false,
1770
+ sourceStatus: "done",
1771
+ capped: true,
1432
1772
  seq: engineSeq++,
1433
- at: new Date(now()).toISOString()
1773
+ at: stamp()
1434
1774
  });
1775
+ return;
1435
1776
  }
1436
- for (const edge of node.outbound) await tryRelease(edge.spec.to.node);
1437
- };
1438
- const mintToken = (instanceLabel) => contentAddress({
1439
- runId,
1440
- instance: instanceLabel,
1441
- kind: "graph-suspension"
1442
- });
1443
- const journalSuspension = async (instanceLabel, request) => {
1444
- const token = mintToken(instanceLabel);
1445
- let defaultRef;
1446
- if (request.default !== void 0) {
1447
- const admitted = admitPayload(request.default);
1448
- defaultRef = contentAddress(admitted);
1449
- await blobs.put(defaultRef, admitted);
1777
+ }
1778
+ const consumedPending = gating.filter((entry) => entry.folded?.state === "pending" && hasLiveInstance(entry.edge.spec.from.node)).map((entry) => entry.edge.id);
1779
+ await release(nodeId, decision.consuming, consumedPending);
1780
+ };
1781
+ const hasLiveInstance = (nodeId) => {
1782
+ for (const label of liveHandles.values()) if (state.instances.get(label)?.node === nodeId) return true;
1783
+ return false;
1784
+ };
1785
+ /** Journal a verdict for every unaccounted outbound edge of a settle, then try the joins. */
1786
+ const propagate = async (label, settle) => {
1787
+ const node = compiled.nodes.get(settle.node);
1788
+ if (!node) return;
1789
+ const succeeded = settle.status === "done" && settle.valid !== false;
1790
+ const sourceSettles = state.nodes.get(settle.node)?.settles.length ?? 0;
1791
+ for (const edge of node.outbound) {
1792
+ if (!isEngineFired(edge)) continue;
1793
+ const folded = state.edges.get(edge.id);
1794
+ if (!folded || folded.judgedSourceSettles >= sourceSettles) continue;
1795
+ let fired = false;
1796
+ let inputRef;
1797
+ if (succeeded) {
1798
+ fired = edge.spec.guard === void 0 || evaluateCondition(edge.spec.guard, {
1799
+ node: settle.node,
1800
+ out: settle.out,
1801
+ visits: state.nodes.get(settle.node)?.visits ?? 0,
1802
+ valid: settle.valid ?? true
1803
+ });
1804
+ if (fired) inputRef = edge.spec.kind === "analyzes" ? settle.traceRef : settle.outRef;
1450
1805
  }
1451
- await appendAndApply({
1452
- kind: "waiting",
1453
- id: `graphwait:${token}`,
1454
- label: instanceLabel,
1455
- spec: {
1456
- kind: "token",
1457
- token,
1458
- onExpire: request.onExpire,
1459
- ...request.expiresInMs !== void 0 ? { expiresAtMs: now() + request.expiresInMs } : {},
1460
- ...defaultRef !== void 0 ? { defaultRef } : {}
1461
- },
1462
- armedAt: now(),
1806
+ await emit({
1807
+ kind: "edge-verdict",
1808
+ id: label,
1809
+ edge: edge.id,
1810
+ fired,
1811
+ sourceStatus: settle.status === "down" ? "down" : succeeded ? "done" : "invalid",
1812
+ ...inputRef !== void 0 ? { inputRef } : {},
1463
1813
  seq: engineSeq++,
1464
- at: new Date(now()).toISOString()
1465
- });
1466
- };
1467
- const wake = async (suspension, by, outRef) => {
1468
- await appendAndApply({
1469
- kind: "woken",
1470
- id: `graphwait:${suspension.token}`,
1471
- by,
1472
- ...outRef !== void 0 ? { outRef } : {},
1473
- seq: engineWokenSeq++,
1474
- at: new Date(now()).toISOString()
1814
+ at: stamp()
1475
1815
  });
1476
- const settle = state.instances.get(suspension.instance)?.settle;
1477
- if (settle !== void 0) {
1478
- const enriched = {
1479
- ...settle,
1480
- ...settle.outRef !== void 0 ? { out: admitPayload(await blobs.get(settle.outRef)) } : {}
1481
- };
1482
- if (enriched.outRef !== void 0 && enriched.out !== void 0) outCache.set(enriched.outRef, enriched.out);
1483
- await propagate(suspension.instance, enriched);
1484
- }
1485
- };
1486
- /** Expiries are engine-clocked: judged on every loop turn and before parking (#976). */
1487
- const sweepExpiries = async () => {
1488
- for (const suspension of [...state.suspensions.values()]) {
1489
- if (suspension.status !== "pending") continue;
1490
- if (suspension.expiresAtMs === void 0 || now() < suspension.expiresAtMs) continue;
1491
- if (suspension.onExpire === "fail") await wake(suspension, "expired");
1492
- else if (suspension.onExpire === "default") await wake(suspension, "fired", suspension.defaultRef);
1493
- }
1494
- };
1495
- const processWakes = async () => {
1496
- while (wakes.length > 0) {
1497
- const request = wakes.shift();
1498
- if (!request) break;
1499
- try {
1500
- const suspension = state.suspensions.get(request.token);
1501
- if (!suspension) throw new ValidationError(`graph resume: unknown token '${request.token}'`);
1502
- if (suspension.status !== "pending") throw new ValidationError(`graph resume: token '${request.token}' already woken`);
1503
- if (request.expire) if (suspension.onExpire === "fail") await wake(suspension, "expired");
1504
- else if (suspension.onExpire === "default") await wake(suspension, "fired", suspension.defaultRef);
1505
- else await wake(suspension, "expired");
1506
- else {
1507
- const admitted = admitPayload(request.payload);
1508
- const outRef = contentAddress(admitted);
1509
- await blobs.put(outRef, admitted);
1510
- outCache.set(outRef, admitted);
1511
- await wake(suspension, "fired", outRef);
1512
- }
1513
- request.settle();
1514
- } catch (error) {
1515
- request.fail(error);
1816
+ }
1817
+ for (const edge of node.outbound) if (isEngineFired(edge)) await tryRelease(edge.spec.to.node);
1818
+ };
1819
+ const park = async (label, request) => {
1820
+ const token = mintSuspensionToken(runId, label);
1821
+ let defaultRef;
1822
+ if (request.default !== void 0) {
1823
+ const admitted = admitPayload(request.default);
1824
+ defaultRef = contentAddress(admitted);
1825
+ await blobs.put(defaultRef, admitted);
1826
+ }
1827
+ await emit({
1828
+ kind: "waiting",
1829
+ id: suspensionNodeId(token),
1830
+ label,
1831
+ spec: {
1832
+ kind: "token",
1833
+ token,
1834
+ onExpire: request.onExpire,
1835
+ ...request.expiresInMs !== void 0 ? { expiresAtMs: now() + request.expiresInMs } : {},
1836
+ ...defaultRef !== void 0 ? { defaultRef } : {}
1837
+ },
1838
+ armedAt: now(),
1839
+ seq: engineSeq++,
1840
+ at: stamp()
1841
+ });
1842
+ };
1843
+ const wake = async (suspension, by, outRef) => {
1844
+ await emit({
1845
+ kind: "woken",
1846
+ id: suspensionNodeId(suspension.token),
1847
+ by,
1848
+ ...outRef !== void 0 ? { outRef } : {},
1849
+ seq: wokenSeq++,
1850
+ at: stamp()
1851
+ });
1852
+ const settle = state.instances.get(suspension.instance)?.settle;
1853
+ if (settle === void 0) return;
1854
+ const out = settle.outRef === void 0 ? void 0 : admitPayload(await blobs.get(settle.outRef));
1855
+ if (settle.outRef !== void 0 && out !== void 0) outCache.set(settle.outRef, out);
1856
+ await propagate(suspension.instance, out === void 0 ? settle : {
1857
+ ...settle,
1858
+ out
1859
+ });
1860
+ };
1861
+ /** Expiries are engine-clocked, so an offline run transitions them without a host sweep (#976). */
1862
+ const expireDue = async () => {
1863
+ let transitioned = false;
1864
+ for (const suspension of [...state.suspensions.values()]) {
1865
+ if (suspension.status !== "pending") continue;
1866
+ if (suspension.expiresAtMs === void 0 || now() < suspension.expiresAtMs) continue;
1867
+ if (suspension.onExpire === "fail") await wake(suspension, "expired");
1868
+ else if (suspension.onExpire === "default") await wake(suspension, "fired", suspension.defaultRef);
1869
+ else continue;
1870
+ transitioned = true;
1871
+ }
1872
+ return transitioned;
1873
+ };
1874
+ const drainWakes = async () => {
1875
+ while (wakes.length > 0) {
1876
+ const request = wakes.shift();
1877
+ if (!request) return;
1878
+ try {
1879
+ const suspension = state.suspensions.get(request.token);
1880
+ if (!suspension) throw new ValidationError(`graph resume: unknown token '${request.token}'`);
1881
+ if (suspension.status !== "pending") throw new ValidationError(`graph resume: token '${request.token}' already woken`);
1882
+ if (request.expire) await wake(suspension, suspension.onExpire === "default" ? "fired" : "expired", suspension.onExpire === "default" ? suspension.defaultRef : void 0);
1883
+ else {
1884
+ const admitted = admitPayload(request.payload);
1885
+ const outRef = contentAddress(admitted);
1886
+ await blobs.put(outRef, admitted);
1887
+ outCache.set(outRef, admitted);
1888
+ await wake(suspension, "fired", outRef);
1516
1889
  }
1890
+ request.settle();
1891
+ } catch (error) {
1892
+ request.fail(error);
1517
1893
  }
1518
- };
1519
- const handleSettle = async (settled) => {
1520
- const instanceLabel = liveHandles.get(settled.handle.id);
1521
- if (instanceLabel === void 0) return;
1522
- liveHandles.delete(settled.handle.id);
1523
- liveCount -= 1;
1524
- const instance = state.instances.get(instanceLabel);
1525
- const node = instance === void 0 ? void 0 : compiled.nodes.get(instance.node);
1526
- if (!instance || !node) return;
1527
- if (settled.kind === "done" && isSuspensionRequest(settled.out)) {
1528
- await journalSuspension(instanceLabel, settled.out);
1529
- return;
1530
- }
1531
- applyGraphFoldEvent(state, {
1532
- kind: "settled",
1533
- id: settled.handle.id,
1534
- status: settled.kind === "done" ? "done" : "down",
1535
- ...settled.kind === "done" ? { outRef: settled.outRef } : {},
1536
- ...settled.kind === "down" ? { reason: settled.reason } : {},
1537
- ...settled.kind === "done" && settled.trace?.status === "available" ? { trace: settled.trace } : {},
1538
- spent: {
1539
- iterations: 0,
1540
- tokens: {
1541
- input: 0,
1542
- output: 0
1543
- },
1544
- usd: 0,
1545
- ms: 0
1894
+ }
1895
+ };
1896
+ const handleSettle = async (settled) => {
1897
+ const label = liveHandles.get(settled.handle.id);
1898
+ if (label === void 0) return;
1899
+ liveHandles.delete(settled.handle.id);
1900
+ liveCount -= 1;
1901
+ const instance = state.instances.get(label);
1902
+ const node = instance === void 0 ? void 0 : compiled.nodes.get(instance.node);
1903
+ if (!instance || !node) return;
1904
+ if (settled.kind === "done" && isSuspensionRequest(settled.out)) {
1905
+ await park(label, settled.out);
1906
+ return;
1907
+ }
1908
+ applyGraphFoldEvent(state, {
1909
+ kind: "settled",
1910
+ id: settled.handle.id,
1911
+ status: settled.kind === "done" ? "done" : "down",
1912
+ ...settled.kind === "done" ? { outRef: settled.outRef } : {},
1913
+ ...settled.kind === "down" ? { reason: settled.reason } : {},
1914
+ ...settled.kind === "done" && settled.trace?.status === "available" ? { trace: settled.trace } : {},
1915
+ spent: {
1916
+ iterations: 0,
1917
+ tokens: {
1918
+ input: 0,
1919
+ output: 0
1546
1920
  },
1547
- seq: 0,
1548
- at: ""
1549
- }, compiled);
1550
- const folded = state.instances.get(instanceLabel)?.settle;
1551
- if (!folded) return;
1552
- let enriched = folded;
1553
- if (settled.kind === "done") {
1554
- const admitted = admitPayload(settled.out);
1555
- if (settled.outRef !== void 0) outCache.set(settled.outRef, admitted);
1556
- let valid;
1557
- if (node.deliverable !== void 0) try {
1558
- valid = await node.deliverable.check(admitted);
1559
- } catch {
1560
- valid = false;
1561
- }
1562
- enriched = {
1563
- ...folded,
1564
- out: admitted,
1565
- ...valid !== void 0 ? { valid } : {}
1566
- };
1567
- state.instances.get(instanceLabel).settle = enriched;
1568
- const settles = state.nodes.get(instance.node)?.settles;
1569
- if (settles) settles[settles.length - 1] = enriched;
1921
+ usd: 0,
1922
+ ms: 0
1923
+ },
1924
+ seq: 0,
1925
+ at: ""
1926
+ }, compiled);
1927
+ const folded = state.instances.get(label)?.settle;
1928
+ if (!folded) return;
1929
+ let enriched = folded;
1930
+ if (settled.kind === "done") {
1931
+ const admitted = admitPayload(settled.out);
1932
+ if (settled.outRef !== void 0) outCache.set(settled.outRef, admitted);
1933
+ enriched = {
1934
+ ...folded,
1935
+ out: admitted,
1936
+ ...await checkDeliverable(node, admitted)
1937
+ };
1938
+ const settles = state.nodes.get(instance.node)?.settles;
1939
+ if (settles) settles[settles.length - 1] = enriched;
1940
+ state.instances.get(label).settle = enriched;
1941
+ }
1942
+ if (instance.node === compiled.root && enriched.status === "done" && enriched.valid === true) {
1943
+ abort.abort("root delivered");
1944
+ return;
1945
+ }
1946
+ await propagate(label, enriched);
1947
+ };
1948
+ const checkDeliverable = async (node, out) => {
1949
+ if (node.deliverable === void 0) return {};
1950
+ try {
1951
+ return { valid: await node.deliverable.check(out) };
1952
+ } catch {
1953
+ return { valid: false };
1954
+ }
1955
+ };
1956
+ if (!context.resuming) for (const id of compiled.entries) await enterEntryNode(id);
1957
+ else await reenterAfterCrash();
1958
+ /** Kill-anywhere re-entry: finish every half-done transition the crashed process left, from the
1959
+ * pinned envelopes and journaled settles only — never by re-deriving anything (#974). */
1960
+ async function reenterAfterCrash() {
1961
+ for (const instance of [...state.instances.values()]) {
1962
+ if (instance.status === "released") {
1963
+ await spawnInstance(instance.instance);
1964
+ continue;
1570
1965
  }
1571
- if (instance.node === compiled.root && enriched.status === "done" && enriched.valid === true) {
1572
- abort.abort("root delivered");
1966
+ if (instance.status !== "live") continue;
1967
+ const node = compiled.nodes.get(instance.node);
1968
+ const folded = state.nodes.get(instance.node);
1969
+ if (!node || !folded) continue;
1970
+ const visit = folded.visits + 1;
1971
+ if (visit > node.maxVisits) {
1972
+ fail("cycle-budget-exceeded", "GraphCycleBudget", `node ${instance.node} entered ${visit} times; maxVisits ${node.maxVisits}`);
1573
1973
  return;
1574
1974
  }
1575
- await propagate(instanceLabel, enriched);
1576
- };
1577
- const enterEntryNode = async (nodeId) => {
1578
- const folded = state.nodes.get(nodeId);
1579
- if (!folded) return;
1580
- const visit = folded.visits + 1;
1581
- const node = compiled.nodes.get(nodeId);
1582
- if (!node) return;
1583
- if (visit > node.maxVisits) return;
1584
- const instanceLabel = `${nodeId}#${visit}`;
1585
- const envelope = {
1586
- task,
1587
- inputs: {}
1588
- };
1589
- const inputRef = contentAddress(envelope);
1590
- await blobs.put(inputRef, envelope);
1591
- await appendAndApply({
1975
+ instance.status = "down";
1976
+ const label = `${instance.node}#${visit}`;
1977
+ await emit({
1592
1978
  kind: "node-inputs-resolved",
1593
- id: instanceLabel,
1594
- node: nodeId,
1595
- instance: instanceLabel,
1596
- inputRef,
1979
+ id: label,
1980
+ node: instance.node,
1981
+ instance: label,
1982
+ inputRef: instance.inputRef,
1597
1983
  seq: engineSeq++,
1598
- at: new Date(now()).toISOString()
1984
+ at: stamp()
1599
1985
  });
1600
- await spawnInstance(instanceLabel);
1601
- };
1602
- if (!resuming) for (const id of compiled.entries) await enterEntryNode(id);
1603
- else {
1604
- for (const instance of [...state.instances.values()]) if (instance.status === "released") await spawnInstance(instance.instance);
1605
- else if (instance.status === "live") {
1606
- const node = compiled.nodes.get(instance.node);
1607
- const folded = state.nodes.get(instance.node);
1608
- if (!node || !folded) continue;
1609
- const visit = folded.visits + 1;
1610
- if (visit > node.maxVisits) {
1611
- failure = {
1612
- reason: "cycle-budget-exceeded",
1613
- error: {
1614
- name: "GraphCycleBudget",
1615
- message: `node ${instance.node} entered ${visit} times; maxVisits ${node.maxVisits}`
1616
- }
1617
- };
1618
- break;
1986
+ await spawnInstance(label);
1987
+ }
1988
+ for (const [nodeId, folded] of state.nodes) {
1989
+ const latest = folded.settles.at(-1);
1990
+ if (latest === void 0) continue;
1991
+ const label = `${nodeId}#${latest.visit}`;
1992
+ if (latest.status === "done" && latest.outRef !== void 0) {
1993
+ const out = admitPayload(await blobs.get(latest.outRef));
1994
+ if (isSuspensionRequest(out)) {
1995
+ folded.settles.pop();
1996
+ const instance = state.instances.get(label);
1997
+ if (instance) instance.settle = void 0;
1998
+ await park(label, out);
1999
+ continue;
1619
2000
  }
1620
- const restartLabel = `${instance.node}#${visit}`;
1621
- instance.status = "down";
1622
- const at = new Date(now()).toISOString();
1623
- await journal.appendEvent(runId, {
1624
- kind: "node-inputs-resolved",
1625
- id: restartLabel,
1626
- node: instance.node,
1627
- instance: restartLabel,
1628
- inputRef: instance.inputRef,
1629
- seq: engineSeq++,
1630
- at
2001
+ outCache.set(latest.outRef, out);
2002
+ const node = compiled.nodes.get(nodeId);
2003
+ await propagate(label, {
2004
+ ...latest,
2005
+ out,
2006
+ ...node === void 0 ? {} : await checkDeliverable(node, out)
1631
2007
  });
1632
- applyGraphFoldEvent(state, {
1633
- kind: "node-inputs-resolved",
1634
- id: restartLabel,
1635
- node: instance.node,
1636
- instance: restartLabel,
1637
- inputRef: instance.inputRef,
1638
- seq: 0,
1639
- at
1640
- }, compiled);
1641
- await spawnInstance(restartLabel);
1642
- }
1643
- for (const [nodeId, foldedNode] of state.nodes) {
1644
- const latest = foldedNode.settles.at(-1);
1645
- if (latest === void 0) continue;
1646
- const instanceLabel = `${nodeId}#${latest.visit}`;
1647
- let enriched = latest;
1648
- if (latest.status === "done" && latest.outRef !== void 0) {
1649
- const out = admitPayload(await blobs.get(latest.outRef));
1650
- if (isSuspensionRequest(out)) {
1651
- foldedNode.settles.pop();
1652
- const instance = state.instances.get(instanceLabel);
1653
- if (instance) instance.settle = void 0;
1654
- await journalSuspension(instanceLabel, out);
1655
- continue;
1656
- }
1657
- outCache.set(latest.outRef, out);
1658
- const nodeSpec = compiled.nodes.get(nodeId);
1659
- let valid;
1660
- if (nodeSpec?.deliverable !== void 0) try {
1661
- valid = await nodeSpec.deliverable.check(out);
1662
- } catch {
1663
- valid = false;
1664
- }
1665
- enriched = {
1666
- ...latest,
1667
- out,
1668
- ...valid !== void 0 ? { valid } : {}
1669
- };
1670
- }
1671
- await propagate(instanceLabel, enriched);
1672
- }
1673
- for (const id of compiled.entries) if ((state.nodes.get(id)?.visits ?? 0) === 0) await enterEntryNode(id);
1674
- for (const id of compiled.nodes.keys()) await tryRelease(id);
1675
- }
1676
- const allTerminalsSettled = () => compiled.terminals.every((id) => (state.nodes.get(id)?.settles.length ?? 0) > 0);
1677
- let wakeSignal = Promise.resolve();
1678
- let armWake = () => {};
1679
- const rearmWakeSignal = () => {
1680
- wakeSignal = new Promise((fire) => {
1681
- armWake = fire;
1682
- });
1683
- onWakeSignal(() => armWake());
1684
- };
1685
- rearmWakeSignal();
1686
- let inFlightNext;
1687
- while (!failure) {
1688
- await sweepExpiries();
1689
- if (wakes.length > 0) {
1690
- await processWakes();
1691
2008
  continue;
1692
2009
  }
1693
- if (allTerminalsSettled()) break;
1694
- if (liveCount === 0) {
1695
- const pendingSuspensions = [...state.suspensions.values()].filter((suspension) => suspension.status === "pending");
1696
- if (pendingSuspensions.length > 0) {
1697
- const nearest = Math.min(...pendingSuspensions.map((suspension) => suspension.expiresAtMs ?? Number.POSITIVE_INFINITY));
1698
- if (Number.isFinite(nearest) && now() >= nearest) continue;
1699
- if (options.waitForWakes) {
1700
- rearmWakeSignal();
1701
- await wakeSignal;
1702
- continue;
1703
- }
1704
- const defaulting = pendingSuspensions.filter((suspension) => suspension.onExpire === "default");
1705
- if (defaulting.length > 0) {
1706
- for (const suspension of defaulting) await wake(suspension, "fired", suspension.defaultRef);
1707
- continue;
1708
- }
1709
- break;
1710
- }
1711
- break;
1712
- }
1713
- inFlightNext ??= scope.next();
1714
- const raced = await Promise.race([inFlightNext.then((settle) => ({ settle })), wakeSignal.then(() => "wake")]);
1715
- if (raced === "wake") {
2010
+ await propagate(label, latest);
2011
+ }
2012
+ for (const id of compiled.entries) if ((state.nodes.get(id)?.visits ?? 0) === 0) await enterEntryNode(id);
2013
+ for (const id of compiled.nodes.keys()) await tryRelease(id);
2014
+ }
2015
+ const allTerminalsSettled = () => compiled.terminals.every((id) => (state.nodes.get(id)?.settles.length ?? 0) > 0);
2016
+ let wakeSignal = Promise.resolve();
2017
+ const rearmWakeSignal = () => {
2018
+ wakeSignal = new Promise((fire) => {
2019
+ onWakeSignal(fire);
2020
+ });
2021
+ };
2022
+ rearmWakeSignal();
2023
+ let pendingNext;
2024
+ while (!failure) {
2025
+ if (await expireDue()) continue;
2026
+ if (wakes.length > 0) {
2027
+ await drainWakes();
2028
+ continue;
2029
+ }
2030
+ if (allTerminalsSettled()) break;
2031
+ if (liveCount === 0) {
2032
+ const parked = [...state.suspensions.values()].filter((suspension) => suspension.status === "pending");
2033
+ if (parked.length === 0) break;
2034
+ if (options.waitForWakes) {
1716
2035
  rearmWakeSignal();
2036
+ await wakeSignal;
1717
2037
  continue;
1718
2038
  }
1719
- inFlightNext = void 0;
1720
- if (raced.settle === null) break;
1721
- await handleSettle(raced.settle);
1722
- for (const parked of waitingForBudget.splice(0)) await spawnInstance(parked);
2039
+ const defaulting = parked.filter((suspension) => suspension.onExpire === "default");
2040
+ if (defaulting.length === 0) break;
2041
+ for (const suspension of defaulting) await wake(suspension, "fired", suspension.defaultRef);
2042
+ continue;
1723
2043
  }
1724
- abort.abort("graph loop complete");
1725
- while (await (inFlightNext ?? scope.next()) !== null) inFlightNext = void 0;
1726
- const rehydrate = async (settle) => {
1727
- if (settle.out !== void 0 || settle.outRef === void 0) return settle;
1728
- const out = outCache.get(settle.outRef) ?? admitPayload(await blobs.get(settle.outRef));
1729
- const node = compiled.nodes.get(settle.node);
1730
- let valid = settle.valid;
1731
- if (valid === void 0 && node?.deliverable !== void 0 && settle.status === "done") try {
1732
- valid = await node.deliverable.check(out);
1733
- } catch {
1734
- valid = false;
1735
- }
1736
- return {
1737
- ...settle,
1738
- out,
1739
- ...valid !== void 0 ? { valid } : {}
1740
- };
1741
- };
1742
- const settles = await Promise.all([...compiled.nodes.keys()].flatMap((id) => state.nodes.get(id)?.settles ?? []).map(rehydrate));
1743
- const terminals = settles.filter((settle) => compiled.nodes.get(settle.node)?.terminal);
1744
- const finish = (result) => {
1745
- if (result.kind === "no-winner" && state.exhaustedEdges.size > 0) throw new GraphEdgeCapError(Object.freeze([...state.exhaustedEdges]), Object.freeze([...ledger]), result);
1746
- return result;
1747
- };
1748
- if (failure) return finish({
1749
- kind: "no-winner",
1750
- reason: failure.reason,
1751
- ...failure.error ? { error: failure.error } : {},
1752
- terminals,
1753
- settles,
1754
- ledger,
1755
- unreachable: []
1756
- });
1757
- const pendingTokens = [...state.suspensions.values()].filter((suspension) => suspension.status === "pending").map((suspension) => suspension.token);
1758
- if (pendingTokens.length > 0 && !allTerminalsSettled()) return {
1759
- kind: "suspended",
1760
- tokens: pendingTokens,
1761
- terminals,
1762
- settles,
1763
- ledger
1764
- };
1765
- if (options.signal?.aborted) return finish({
1766
- kind: "no-winner",
1767
- reason: "aborted",
1768
- terminals,
1769
- settles,
1770
- ledger,
1771
- unreachable: []
1772
- });
1773
- const out = terminals.some((settle) => settle.status === "done" && settle.valid !== false) ? await runFinalizer(options.finalizer === void 0 || options.finalizer === "bestDelivered" ? bestDelivered : options.finalizer === "collectDelivered" ? collectDelivered : options.finalizer, {
1774
- settled: terminals.map((settle) => ({
1775
- id: settle.node,
1776
- status: settle.status,
1777
- valid: settle.status === "done" && settle.valid !== false,
1778
- ...settle.outRef !== void 0 ? { outRef: settle.outRef } : {}
1779
- })),
1780
- blobs,
1781
- tree: scope.view,
1782
- budget: scope.budget
1783
- }) : void 0;
1784
- if (out !== void 0) return {
1785
- kind: "winner",
1786
- out,
1787
- terminals,
1788
- settles,
1789
- ledger
1790
- };
1791
- const unreachable = [...compiled.nodes.keys()].filter((id) => (state.nodes.get(id)?.settles.length ?? 0) === 0);
1792
- return finish({
1793
- kind: "no-winner",
1794
- reason: compiled.terminals.some((id) => unreachable.includes(id)) ? unreachable.length === compiled.nodes.size ? "budget-exhausted" : "unreachable-terminal" : "all-children-down",
1795
- terminals,
1796
- settles,
1797
- ledger,
1798
- unreachable
1799
- });
1800
- }
1801
- }
1802
- function hasLiveInstance(state, liveHandles, node) {
1803
- for (const label of liveHandles.values()) if (state.instances.get(label)?.node === node) return true;
1804
- return false;
1805
- }
1806
- function maxEngineSeq(events) {
1807
- let max = -1;
1808
- for (const ev of events) {
1809
- if (ev.kind === "node-inputs-resolved" || ev.kind === "edge-verdict" || ev.kind === "join-state") {
1810
- if (ev.seq > max) max = ev.seq;
2044
+ pendingNext ??= scope.next();
2045
+ const raced = await Promise.race([pendingNext.then((settle) => ({ settle })), wakeSignal.then(() => "wake")]);
2046
+ if (raced === "wake") {
2047
+ rearmWakeSignal();
2048
+ continue;
1811
2049
  }
1812
- if (ev.kind === "waiting" && ev.spec.kind === "token" && ev.seq > max) max = ev.seq;
2050
+ pendingNext = void 0;
2051
+ if (raced.settle === null) break;
2052
+ await handleSettle(raced.settle);
2053
+ for (const label of waitingForBudget.splice(0)) await spawnInstance(label);
1813
2054
  }
1814
- return max;
2055
+ detachOuterAbort();
2056
+ abort.abort("graph loop complete");
2057
+ while (await (pendingNext ?? scope.next()) !== null) pendingNext = void 0;
2058
+ return assembleGraphResult({
2059
+ compiled,
2060
+ state,
2061
+ blobs,
2062
+ scope,
2063
+ outCache,
2064
+ ledger: ledger.entries,
2065
+ ...options.finalizer !== void 0 ? { finalizer: options.finalizer } : {},
2066
+ ...failure !== void 0 ? { failure } : {},
2067
+ aborted: options.signal?.aborted ?? false
2068
+ });
1815
2069
  }
1816
2070
  //#endregion
1817
- export { CONDITION_OPS, DEFAULT_MAX_NODE_VISITS, ENGINE_WOKEN_SEQ_BASE, JOIN_RULES, MAX_MAX_NODE_VISITS, admitPayload, agentKind, applyGraphFoldEvent, applyProjection, compileGraph, createGraphEngine, createGraphRun, createRegistry, emptyFoldState, evaluateCondition, foldGraphJournal, formatRegistryHandle, kindHandle, narrowEffects, parseRegistryHandle, runEngineGraph, schemaAccepts, scriptKind, subgraphKind, supervisorKind, suspended, validateCondition, validateNodeKind, validateProjection };
2071
+ export { CONDITION_OPS, DEFAULT_MAX_NODE_VISITS, ENGINE_WOKEN_SEQ_BASE, JOIN_RULES, MAX_MAX_NODE_VISITS, RUN_GRAPH_ROOT_KIND, admitPayload, agentKind, applyGraphFoldEvent, applyProjection, assembleGraphResult, compileGraph, createEdgeLedger, createGraphEngine, createGraphRun, createRegistry, decideJoin, emptyFoldState, evaluateCondition, foldGraphJournal, formatRegistryHandle, graphFromRunGraph, isEngineFired, isSuspensionRequest, kindHandle, materializeSettles, mintSuspensionToken, narrowEffects, openGraphRun, parseRegistryHandle, runEngineGraph, schemaAccepts, scriptKind, subgraphKind, supervisorKind, suspended, suspensionNodeId, tokenFromSuspensionNodeId, validateCondition, validateNodeKind, validateProjection };
1818
2072
 
1819
2073
  //# sourceMappingURL=graph.js.map