@tangle-network/agent-runtime 0.163.0 → 0.165.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 (70) hide show
  1. package/dist/{activation-DxGP0h_p.js → activation-DmUIq51m.js} +3 -3
  2. package/dist/{activation-DxGP0h_p.js.map → activation-DmUIq51m.js.map} +1 -1
  3. package/dist/agent.d.ts +1 -1
  4. package/dist/agent.js +2 -2
  5. package/dist/{authoring-kO7vE_c9.js → authoring-Cm122Duf.js} +2 -2
  6. package/dist/{authoring-kO7vE_c9.js.map → authoring-Cm122Duf.js.map} +1 -1
  7. package/dist/candidate-execution/index.js +4 -4
  8. package/dist/{candidate-execution-izJj02pU.js → candidate-execution-DC0T8l2p.js} +4 -4
  9. package/dist/{candidate-execution-izJj02pU.js.map → candidate-execution-DC0T8l2p.js.map} +1 -1
  10. package/dist/conversation.d.ts +1 -1
  11. package/dist/durable.d.ts +2 -2
  12. package/dist/durable.js +1 -1
  13. package/dist/{environment-provider-BXFFK_rZ.d.ts → environment-provider-DWjbGH8W.d.ts} +2 -2
  14. package/dist/environment-provider.d.ts +1 -1
  15. package/dist/{graph-COikUkrj.js → graph-DGri_zJm.js} +3 -3
  16. package/dist/{graph-COikUkrj.js.map → graph-DGri_zJm.js.map} +1 -1
  17. package/dist/graph.d.ts +155 -53
  18. package/dist/graph.js +946 -330
  19. package/dist/graph.js.map +1 -1
  20. package/dist/{improvement-cycle-dGtgX1vr.js → improvement-cycle-BOd5DFeG.js} +4 -4
  21. package/dist/{improvement-cycle-dGtgX1vr.js.map → improvement-cycle-BOd5DFeG.js.map} +1 -1
  22. package/dist/{index-CbyKCjR7.d.ts → index-BGQtpQuJ.d.ts} +5 -5
  23. package/dist/{index-CykKDPnB.d.ts → index-BgptUCKl.d.ts} +4 -4
  24. package/dist/{index-OFOvnNc2.d.ts → index-CFMB9ux3.d.ts} +2 -2
  25. package/dist/index.d.ts +7 -7
  26. package/dist/index.js +11 -11
  27. package/dist/intelligence.d.ts +4 -4
  28. package/dist/intelligence.js +5 -5
  29. package/dist/kernel.d.ts +5 -5
  30. package/dist/kernel.js +7 -7
  31. package/dist/{knowledge-CZT8ahp6.js → knowledge-7_IfyD9R.js} +5 -5
  32. package/dist/{knowledge-CZT8ahp6.js.map → knowledge-7_IfyD9R.js.map} +1 -1
  33. package/dist/knowledge.d.ts +1 -1
  34. package/dist/knowledge.js +1 -1
  35. package/dist/{loop-runner-bin-DrN092ew.d.ts → loop-runner-bin-C2NXQK7J.d.ts} +3 -3
  36. package/dist/{loop-runner-bin-CIjmKf7v.js → loop-runner-bin-Dx-5GZQK.js} +3 -3
  37. package/dist/{loop-runner-bin-CIjmKf7v.js.map → loop-runner-bin-Dx-5GZQK.js.map} +1 -1
  38. package/dist/loop-runner-bin.d.ts +1 -1
  39. package/dist/loop-runner-bin.js +1 -1
  40. package/dist/mcp/bin.js +3 -3
  41. package/dist/mcp/index.d.ts +3 -3
  42. package/dist/mcp/index.js +4 -4
  43. package/dist/{openai-tools-DG779Z6S.js → openai-tools-CDS7eSww.js} +2 -2
  44. package/dist/{openai-tools-DG779Z6S.js.map → openai-tools-CDS7eSww.js.map} +1 -1
  45. package/dist/{prepare-VLGnSyav.js → prepare-BCScSDCv.js} +7 -5
  46. package/dist/prepare-BCScSDCv.js.map +1 -0
  47. package/dist/primeintellect/index.d.ts +1 -1
  48. package/dist/{protected-model-port-CcCH0Uji.js → protected-model-port-D-5E3HBe.js} +2 -2
  49. package/dist/{protected-model-port-CcCH0Uji.js.map → protected-model-port-D-5E3HBe.js.map} +1 -1
  50. package/dist/{redact-DL_ZtFvR.d.ts → redact-DkObldQP.d.ts} +3 -3
  51. package/dist/{runtime-CCbeY1KO.d.ts → runtime-B0HZbuEv.d.ts} +4 -4
  52. package/dist/{runtime-BT00zfIS.js → runtime-DESENUe1.js} +7 -7
  53. package/dist/{runtime-BT00zfIS.js.map → runtime-DESENUe1.js.map} +1 -1
  54. package/dist/{spawn-journal-oewq1g2h.js → spawn-journal-Tgvp5VS5.js} +8 -2
  55. package/dist/{spawn-journal-oewq1g2h.js.map → spawn-journal-Tgvp5VS5.js.map} +1 -1
  56. package/dist/{stream-agent-turn-Dx2YrEWW.d.ts → stream-agent-turn-DxQ3GOuR.d.ts} +2 -2
  57. package/dist/{structural-rollout-DAenyNls.js → structural-rollout-DZGDg0yX.js} +3 -3
  58. package/dist/{structural-rollout-DAenyNls.js.map → structural-rollout-DZGDg0yX.js.map} +1 -1
  59. package/dist/{supervise-Di6xvnOL.js → supervise-Da20R0PO.js} +3 -3
  60. package/dist/{supervise-Di6xvnOL.js.map → supervise-Da20R0PO.js.map} +1 -1
  61. package/dist/{supervisor-DmWq4vkU.js → supervisor-DpIKMlPJ.js} +114 -40
  62. package/dist/supervisor-DpIKMlPJ.js.map +1 -0
  63. package/dist/testing.d.ts +2 -2
  64. package/dist/testing.js +12 -12
  65. package/dist/{types-C2l1jaos.d.ts → types-DjVO-p7R.d.ts} +58 -3
  66. package/dist/{workspace-archive-BptlGCEU.js → workspace-archive-CHY5HvrF.js} +2 -2
  67. package/dist/{workspace-archive-BptlGCEU.js.map → workspace-archive-CHY5HvrF.js.map} +1 -1
  68. package/package.json +1 -1
  69. package/dist/prepare-VLGnSyav.js.map +0 -1
  70. package/dist/supervisor-DmWq4vkU.js.map +0 -1
package/dist/graph.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { m as ValidationError } from "./errors-CDZ8XsVj.js";
2
- import { i as InMemorySpawnJournal, r as InMemoryResultBlobStore, x as contentAddress } from "./spawn-journal-oewq1g2h.js";
3
- import { A as createBudgetPool, B as createExecutorRegistry, i as collectDelivered, o as runFinalizer, r as bestDelivered, u as createScope } from "./supervisor-DmWq4vkU.js";
4
- import { i as workerFromBackend, s as supervisorAgent } from "./supervise-Di6xvnOL.js";
5
- import { t as GraphEdgeCapError } from "./graph-COikUkrj.js";
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
+ 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";
6
7
  //#region src/runtime/graph/condition.ts
7
8
  /**
8
9
  * The ONE predicate tree: every guard in a graph — an edge `guard`, a filter projection's
@@ -588,6 +589,211 @@ function createGraphEngine(options) {
588
589
  return engine;
589
590
  }
590
591
  //#endregion
592
+ //#region src/runtime/graph/fold.ts
593
+ /**
594
+ * The fold (agent-runtime#974): scheduler state is a pure function of the journal, reconstructed
595
+ * by replaying events through the SAME reducer the live scheduler feeds — fold, never checkpoint.
596
+ * Kernel events (`spawned`, `settled`, `waiting`, `woken`) carry what the kernel owns; the engine's
597
+ * events (`node-inputs-resolved`, `edge-verdict`, `join-state`) carry what the scheduler decided,
598
+ * each journaled BEFORE its effect was visible. `edge` stays observability and is skipped here
599
+ * exactly as the kernel's own replay skips it.
600
+ *
601
+ * Events are consumed in APPEND order (the journal file's line order), not by `seq`: kernel and
602
+ * engine events carry independent ordinal counters, and append order is the one total order both
603
+ * share.
604
+ */
605
+ /** The reducer's zero: every node unvisited, every edge pending, nothing suspended. */
606
+ function emptyFoldState(compiled) {
607
+ const nodes = /* @__PURE__ */ new Map();
608
+ for (const id of compiled.nodes.keys()) nodes.set(id, {
609
+ visits: 0,
610
+ blocked: false,
611
+ settles: []
612
+ });
613
+ const edges = /* @__PURE__ */ new Map();
614
+ for (const edge of compiled.edges) edges.set(edge.id, {
615
+ state: "pending",
616
+ consumedOnce: false,
617
+ traversals: 0,
618
+ judgedSourceSettles: 0,
619
+ capped: false
620
+ });
621
+ return {
622
+ nodes,
623
+ edges,
624
+ instances: /* @__PURE__ */ new Map(),
625
+ spawnedIds: /* @__PURE__ */ new Map(),
626
+ suspensions: /* @__PURE__ */ new Map(),
627
+ exhaustedEdges: /* @__PURE__ */ new Set()
628
+ };
629
+ }
630
+ function instanceOf(state, label) {
631
+ return state.instances.get(label);
632
+ }
633
+ /**
634
+ * Apply ONE journal event. The live scheduler calls this right after each append; the restart path
635
+ * calls it over the loaded journal. Unknown kernel events are ignored — the engine folds only what
636
+ * it understands, exactly as the kernel's replay skips the engine's events.
637
+ */
638
+ function applyGraphFoldEvent(state, ev, compiled) {
639
+ switch (ev.kind) {
640
+ case "node-inputs-resolved": {
641
+ const visit = Number(ev.instance.split("#").at(-1));
642
+ if (!Number.isSafeInteger(visit)) throw new ValidationError(`graph fold: instance ${ev.instance} carries no visit ordinal`);
643
+ state.instances.set(ev.instance, {
644
+ node: ev.node,
645
+ instance: ev.instance,
646
+ visit,
647
+ inputRef: ev.inputRef,
648
+ status: "released"
649
+ });
650
+ const node = state.nodes.get(ev.node);
651
+ if (node) node.visits = Math.max(node.visits, visit);
652
+ return;
653
+ }
654
+ case "spawned": {
655
+ const instance = instanceOf(state, ev.label);
656
+ if (instance) {
657
+ instance.status = "live";
658
+ state.spawnedIds.set(ev.id, ev.label);
659
+ }
660
+ return;
661
+ }
662
+ case "settled": {
663
+ const label = state.spawnedIds.get(ev.id);
664
+ const instance = label === void 0 ? void 0 : instanceOf(state, label);
665
+ if (!instance) return;
666
+ instance.status = ev.status;
667
+ const settle = {
668
+ node: instance.node,
669
+ visit: instance.visit,
670
+ status: ev.status,
671
+ ...ev.outRef !== void 0 ? { outRef: ev.outRef } : {},
672
+ ...ev.reason !== void 0 ? { reason: ev.reason } : {}
673
+ };
674
+ if (ev.trace?.status === "available") settle.traceRef = ev.trace.traceRef;
675
+ instance.settle = settle;
676
+ state.nodes.get(instance.node)?.settles.push(settle);
677
+ const node = compiled.nodes.get(instance.node);
678
+ for (const edge of node?.outbound ?? []) {
679
+ const folded = state.edges.get(edge.id);
680
+ if (folded?.consumedOnce && folded.state === "pending") {
681
+ folded.consumedOnce = false;
682
+ folded.judgedSourceSettles += 1;
683
+ }
684
+ }
685
+ return;
686
+ }
687
+ case "edge-verdict": {
688
+ const folded = state.edges.get(ev.edge);
689
+ if (!folded) return;
690
+ if (ev.capped) {
691
+ folded.capped = true;
692
+ state.exhaustedEdges.add(ev.edge);
693
+ const target = compiled.edges.find((edge) => edge.id === ev.edge)?.spec.to.node;
694
+ const compiledTarget = target === void 0 ? void 0 : compiled.nodes.get(target);
695
+ if (compiledTarget && (compiledTarget.join === "all" || compiledTarget.join === "all_done")) {
696
+ const node = state.nodes.get(target);
697
+ if (node) node.blocked = true;
698
+ }
699
+ return;
700
+ }
701
+ folded.judgedSourceSettles += 1;
702
+ if (!ev.fired) {
703
+ folded.state = ev.sourceStatus === "done" ? "dead" : "failed";
704
+ folded.payloadRef = void 0;
705
+ return;
706
+ }
707
+ folded.state = "satisfied";
708
+ folded.payloadRef = ev.inputRef;
709
+ return;
710
+ }
711
+ case "join-state": {
712
+ for (const edgeId of ev.satisfiedBy) {
713
+ const folded = state.edges.get(edgeId);
714
+ if (!folded) continue;
715
+ folded.traversals += 1;
716
+ folded.state = "pending";
717
+ folded.payloadRef = void 0;
718
+ }
719
+ const target = compiled.nodes.get(ev.node);
720
+ for (const edge of target?.inbound ?? []) {
721
+ const folded = state.edges.get(edge.id);
722
+ if (!folded) continue;
723
+ if (ev.consumedPending.includes(edge.id)) folded.consumedOnce = true;
724
+ else if (folded.state !== "pending" && !ev.satisfiedBy.includes(edge.id)) {
725
+ folded.state = "pending";
726
+ folded.payloadRef = void 0;
727
+ }
728
+ }
729
+ return;
730
+ }
731
+ case "waiting": {
732
+ if (ev.spec.kind !== "token") return;
733
+ const instance = instanceOf(state, ev.label);
734
+ if (instance) {
735
+ instance.status = "suspended";
736
+ if (instance.settle !== void 0) {
737
+ const settles = state.nodes.get(instance.node)?.settles;
738
+ if (settles && settles.at(-1) === instance.settle) settles.pop();
739
+ instance.settle = void 0;
740
+ }
741
+ }
742
+ state.suspensions.set(ev.spec.token, {
743
+ token: ev.spec.token,
744
+ node: instance?.node ?? ev.label.split("#")[0] ?? ev.label,
745
+ instance: ev.label,
746
+ onExpire: ev.spec.onExpire,
747
+ ...ev.spec.expiresAtMs !== void 0 ? { expiresAtMs: ev.spec.expiresAtMs } : {},
748
+ ...ev.spec.defaultRef !== void 0 ? { defaultRef: ev.spec.defaultRef } : {},
749
+ status: "pending"
750
+ });
751
+ return;
752
+ }
753
+ case "woken": {
754
+ const token = ev.id.startsWith("graphwait:") ? ev.id.slice(10) : void 0;
755
+ const suspension = token === void 0 ? void 0 : state.suspensions.get(token);
756
+ if (!suspension) return;
757
+ const instance = instanceOf(state, suspension.instance);
758
+ if (ev.by === "expired") {
759
+ suspension.status = "expired";
760
+ if (instance) {
761
+ instance.status = "down";
762
+ const settle = {
763
+ node: suspension.node,
764
+ visit: instance.visit,
765
+ status: "down",
766
+ reason: "suspension expired"
767
+ };
768
+ instance.settle = settle;
769
+ state.nodes.get(suspension.node)?.settles.push(settle);
770
+ }
771
+ return;
772
+ }
773
+ suspension.status = "woken";
774
+ if (instance) {
775
+ instance.status = "done";
776
+ const settle = {
777
+ node: suspension.node,
778
+ visit: instance.visit,
779
+ status: "done",
780
+ ...ev.outRef !== void 0 ? { outRef: ev.outRef } : {}
781
+ };
782
+ instance.settle = settle;
783
+ state.nodes.get(suspension.node)?.settles.push(settle);
784
+ }
785
+ return;
786
+ }
787
+ default: return;
788
+ }
789
+ }
790
+ /** Fold a loaded journal (append order) into scheduler state. */
791
+ function foldGraphJournal(events, compiled) {
792
+ const state = emptyFoldState(compiled);
793
+ for (const ev of events) applyGraphFoldEvent(state, ev, compiled);
794
+ return state;
795
+ }
796
+ //#endregion
591
797
  //#region src/runtime/graph/kinds.ts
592
798
  /**
593
799
  * One profile, one run: the kernel's leaf, exactly as `supervise()` derives it from `backend`.
@@ -799,18 +1005,43 @@ function asRecord(value, context) {
799
1005
  //#endregion
800
1006
  //#region src/runtime/graph/scheduler.ts
801
1007
  /**
802
- * The engine scheduler (agent-runtime#980): run a compiled graph by hosting every node instance on
803
- * one kernel `Scope` — the pool, the journal, the blob store and cancellation are the kernel's,
804
- * never re-implemented. The scheduler owns only what a graph adds: joins over guarded edges,
805
- * traversal and visit caps, directive/payload delivery, terminals and the finalizer reduce.
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.
806
1013
  *
807
- * Edge semantics are adopted from ADC's workflow graph (agent-runtime#968): an edge settles
808
- * SATISFIED / DEAD / FAILED per its source's LATEST completion; join rules `all | any | any_failed
809
- * | all_done` decide release; a release CONSUMES the outcomes that produced it (settled edges
810
- * re-arm; a pending edge at release is consumed-once, so an OR-diamond's second completer never
811
- * double-fires). Per-edge `maxTraversals` refuses the consumption and ledgers `unpropagated`; a
812
- * node entered past `maxVisits` fails the run `cycle-budget-exceeded` (#973).
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`.
1021
+ *
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.
813
1025
  */
1026
+ /** Engine-appended `woken` ordinals start here — far above any kernel cursor counter. */
1027
+ const ENGINE_WOKEN_SEQ_BASE = 1e7;
1028
+ const SUSPEND_MARK = "__graphSuspension";
1029
+ /** What a kind's executor returns to park its node on a host wake (agent-runtime#976). */
1030
+ function suspended(options = {}) {
1031
+ const onExpire = options.onExpire ?? "wait";
1032
+ if (onExpire !== "wait" && options.expiresInMs === void 0) throw new ValidationError(`suspended: onExpire '${onExpire}' requires expiresInMs`);
1033
+ if (onExpire === "wait" && options.expiresInMs !== void 0) throw new ValidationError("suspended: onExpire 'wait' never expires — remove expiresInMs");
1034
+ if (onExpire === "default" && options.default === void 0) throw new ValidationError("suspended: onExpire 'default' requires a default payload");
1035
+ return {
1036
+ [SUSPEND_MARK]: true,
1037
+ onExpire,
1038
+ ...options.expiresInMs !== void 0 ? { expiresInMs: options.expiresInMs } : {},
1039
+ ...options.default !== void 0 ? { default: options.default } : {}
1040
+ };
1041
+ }
1042
+ function isSuspensionRequest(value) {
1043
+ return typeof value === "object" && value !== null && value[SUSPEND_MARK] === true;
1044
+ }
814
1045
  /** Admission for every value crossing an edge (#971): JSON round-trip, `undefined` stripped, a
815
1046
  * non-representable value becomes a RECORD of that fact — a degraded record beats a vanished
816
1047
  * edge. */
@@ -824,12 +1055,45 @@ function admitPayload(value) {
824
1055
  return { nonCanonical: error instanceof Error ? error.message : String(error) };
825
1056
  }
826
1057
  }
1058
+ /** Run a graph to its result: `createGraphRun` awaited — the one-call form for a run that needs
1059
+ * no live host wakes. */
1060
+ async function runEngineGraph(engine, spec, task, options) {
1061
+ return createGraphRun(engine, spec, task, options).done;
1062
+ }
827
1063
  /**
828
- * Run a graph: host every node instance on one kernel `Scope`, resolve joins over guarded edges,
829
- * enforce the traversal and visit caps, and reduce the terminal settlements through the finalizer.
1064
+ * Start (or resume) a graph run and return its handle: await `done` for the result; deliver host
1065
+ * wakes through `resume`/`expire` while it runs (#976).
830
1066
  */
831
- async function runEngineGraph(engine, spec, task, options) {
1067
+ function createGraphRun(engine, spec, task, options) {
832
1068
  const compiled = "nodes" in spec && spec.nodes instanceof Map ? spec : compileGraph(engine, spec);
1069
+ const wakes = [];
1070
+ let notifyWake;
1071
+ const handleState = { finished: false };
1072
+ const done = runGraphLoop(engine, compiled, task, options, wakes, () => notifyWake?.(), (fn) => {
1073
+ notifyWake = fn;
1074
+ }).finally(() => {
1075
+ handleState.finished = true;
1076
+ });
1077
+ 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}'`));
1079
+ return new Promise((settle, fail) => {
1080
+ wakes.push({
1081
+ token,
1082
+ payload,
1083
+ expire,
1084
+ settle,
1085
+ fail
1086
+ });
1087
+ notifyWake?.();
1088
+ });
1089
+ };
1090
+ return {
1091
+ done,
1092
+ resume: (token, payload) => queue(token, payload, false),
1093
+ expire: (token) => queue(token, void 0, true)
1094
+ };
1095
+ }
1096
+ async function runGraphLoop(engine, compiled, task, options, wakes, _notify, onWakeSignal) {
833
1097
  const context = "runEngineGraph";
834
1098
  for (const node of compiled.nodes.values()) {
835
1099
  const missing = node.kind.effects.filter((name) => !(name in engine.effects));
@@ -838,15 +1102,40 @@ async function runEngineGraph(engine, spec, task, options) {
838
1102
  }
839
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`);
840
1104
  const now = options.now ?? Date.now;
841
- const started = now();
842
- const runId = options.runId ?? `graph:${contentAddress({
843
- task,
844
- at: started
845
- }).slice(0, 18)}`;
846
- const pool = createBudgetPool(options.budget, () => now() - started);
847
1105
  const journal = options.journal ?? new InMemorySpawnJournal();
848
1106
  const blobs = options.blobs ?? new InMemoryResultBlobStore();
849
- await journal.beginTree(runId, new Date(started).toISOString());
1107
+ 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",
1118
+ 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;
1128
+ }
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);
850
1139
  const abort = new AbortController();
851
1140
  const onOuterAbort = () => abort.abort(options.signal?.reason);
852
1141
  options.signal?.addEventListener("abort", onOuterAbort, { once: true });
@@ -861,343 +1150,670 @@ async function runEngineGraph(engine, spec, task, options) {
861
1150
  seams: {},
862
1151
  depth: 0,
863
1152
  signal: abort.signal,
864
- now
865
- });
866
- const nodes = /* @__PURE__ */ new Map();
867
- for (const id of compiled.nodes.keys()) nodes.set(id, {
868
- visits: 0,
869
- live: 0,
870
- settles: [],
871
- inputs: /* @__PURE__ */ new Map(),
872
- directives: [],
873
- traces: [],
874
- blocked: false
875
- });
876
- const edges = /* @__PURE__ */ new Map();
877
- for (const edge of compiled.edges) edges.set(edge.id, {
878
- edge,
879
- state: "pending",
880
- consumedOnce: false,
881
- traversals: 0,
882
- payload: void 0
1153
+ 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
+ } } : {}
883
1164
  });
884
- const ledger = [];
885
- const exhaustedEdges = /* @__PURE__ */ new Set();
886
- const settles = [];
887
- const liveHandles = /* @__PURE__ */ new Map();
888
- const waitingForBudget = [];
889
- let ledgerSeq = 0;
890
- let liveCount = 0;
891
- let failure;
892
- const record = (runtime, outcome, reason) => {
893
- runtime.traversals += 1;
894
- const spec = runtime.edge.spec;
895
- const entry = {
896
- edge: runtime.edge.id,
897
- kind: spec.kind,
898
- from: spec.from.node,
899
- to: spec.to.node,
900
- traversal: runtime.traversals,
901
- outcome,
902
- ...spec.directive !== void 0 ? { directive: `${spec.directive.surface}/v${spec.directive.version}` } : {},
903
- ...spec.kind === "data" ? { port: runtime.edge.toPort } : {},
904
- ...reason !== void 0 ? { reason } : {}
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);
905
1179
  };
906
- ledger.push(entry);
907
- journal.appendEvent(runId, {
908
- kind: "edge",
909
- id: `graph:${spec.to.node}`,
910
- edge: {
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,
911
1191
  kind: spec.kind,
912
1192
  from: spec.from.node,
913
1193
  to: spec.to.node,
914
- ...entry.directive !== void 0 ? { directive: entry.directive } : {},
915
- ...entry.port !== void 0 ? { port: entry.port } : {}
916
- },
917
- traversal: entry.traversal,
918
- outcome: outcome === "delivered" ? "delivered" : outcome,
919
- bytes: 0,
920
- ...reason !== void 0 ? { reason } : {},
921
- seq: ledgerSeq++,
922
- at: new Date(now()).toISOString()
923
- });
924
- };
925
- /** Enter a node: count the visit, build its Agent from consumed inputs, spawn on the Scope. */
926
- const enter = (id) => {
927
- if (failure) return;
928
- const node = compiled.nodes.get(id);
929
- const runtime = nodes.get(id);
930
- if (!node || !runtime) throw new ValidationError(`${context}: unknown node ${id}`);
931
- runtime.visits += 1;
932
- if (runtime.visits > node.maxVisits) {
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
+ }
933
1261
  failure = {
934
- reason: "cycle-budget-exceeded",
1262
+ reason: "driver-failed",
935
1263
  error: {
936
- name: "GraphCycleBudget",
937
- message: `node ${id} entered ${runtime.visits} times; maxVisits ${node.maxVisits}`
1264
+ name: "SpawnRefused",
1265
+ message: `node ${node.id}: ${spawned.reason}`
938
1266
  }
939
1267
  };
940
- abort.abort(`cycle-budget-exceeded: ${id}`);
941
- return;
942
- }
943
- const inputs = {};
944
- for (const [port, value] of runtime.inputs) inputs[port] = value;
945
- const directives = runtime.directives.splice(0);
946
- const traces = runtime.traces.splice(0);
947
- const nodeTask = [
948
- id === compiled.root ? task : "",
949
- ...directives,
950
- ...traces.map((trace) => `trace of ${trace.node}: ${trace.traceRef ?? "(no traceRef)"}`)
951
- ].filter((part) => part.length > 0).join("\n\n");
952
- const effects = narrowEffects(node.kind.effects, engine.effects, `${context}: node ${id}`);
953
- const agent = node.kind.run({
954
- config: node.config,
955
- profile: {
956
- name: id,
957
- ...node.spec.profile ?? {}
958
- },
959
- inputs,
960
- effects
961
- });
962
- const budget = node.spec.budget ?? options.perNode;
963
- const spawned = scope.spawn(agent, nodeTask.length > 0 ? nodeTask : task, {
964
- label: id,
965
- budget
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
+ }
1314
+ }
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
+ };
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;
1398
+ }
1399
+ }
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);
1402
+ };
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({
1426
+ 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 } : {},
1432
+ seq: engineSeq++,
1433
+ at: new Date(now()).toISOString()
1434
+ });
1435
+ }
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"
966
1442
  });
967
- if (spawned.ok) {
968
- liveCount += 1;
969
- runtime.live += 1;
970
- liveHandles.set(spawned.handle.id, id);
971
- return;
972
- }
973
- if (spawned.reason === "budget-exhausted" || spawned.reason === "max-live-workers") {
974
- waitingForBudget.push(id);
975
- runtime.visits -= 1;
976
- runtime.directives.unshift(...directives);
977
- runtime.traces.unshift(...traces);
978
- return;
979
- }
980
- failure = {
981
- reason: "driver-failed",
982
- error: {
983
- name: "SpawnRefused",
984
- message: `node ${id}: ${spawned.reason}`
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);
985
1450
  }
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(),
1463
+ seq: engineSeq++,
1464
+ at: new Date(now()).toISOString()
1465
+ });
986
1466
  };
987
- abort.abort(`spawn refused: ${id}`);
988
- };
989
- /** Join evaluation per ADC's rules; returns whether the node released. */
990
- const tryRelease = (id) => {
991
- const node = compiled.nodes.get(id);
992
- const runtime = nodes.get(id);
993
- if (!node || !runtime || runtime.blocked || failure) return false;
994
- const gating = node.inbound.map((edge) => edges.get(edge.id));
995
- if (gating.length === 0) return false;
996
- const settled = gating.filter((edge) => edge.state !== "pending");
997
- const satisfied = gating.filter((edge) => edge.state === "satisfied");
998
- const failed = gating.filter((edge) => edge.state === "failed");
999
- const allSettled = settled.length === gating.length;
1000
- let release = false;
1001
- switch (node.join) {
1002
- case "all":
1003
- if (gating.some((edge) => edge.state === "dead" || edge.state === "failed")) release = false;
1004
- else release = satisfied.length === gating.length;
1005
- break;
1006
- case "any":
1007
- release = satisfied.length > 0;
1008
- if (!release && allSettled) runtime.blocked = true;
1009
- break;
1010
- case "any_failed":
1011
- release = failed.length > 0;
1012
- if (!release && allSettled) runtime.blocked = true;
1013
- break;
1014
- case "all_done":
1015
- release = allSettled;
1016
- break;
1017
- }
1018
- if (!release) return false;
1019
- const consuming = node.join === "any" ? [satisfied[0]] : node.join === "any_failed" ? [failed[0]] : settled;
1020
- for (const runtimeEdge of consuming) {
1021
- const cap = runtimeEdge.edge.spec.maxTraversals;
1022
- if (cap !== void 0 && runtimeEdge.traversals >= cap) {
1023
- record(runtimeEdge, "unpropagated", `traversal-cap-exhausted (max ${cap})`);
1024
- runtimeEdge.traversals -= 1;
1025
- exhaustedEdges.add(runtimeEdge.edge.id);
1026
- runtime.blocked = node.join === "any" || node.join === "any_failed" ? runtime.blocked : true;
1027
- return false;
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()
1475
+ });
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);
1028
1484
  }
1029
- }
1030
- for (const runtimeEdge of consuming) {
1031
- const spec = runtimeEdge.edge.spec;
1032
- if (runtimeEdge.state === "satisfied" && spec.kind === "data") {
1033
- let payload = runtimeEdge.payload;
1034
- let outcome = "delivered";
1035
- let reason;
1036
- if (spec.projection !== void 0) try {
1037
- payload = admitPayload(applyProjection(payload, spec.projection, `edge ${runtimeEdge.edge.id}`));
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();
1038
1514
  } catch (error) {
1039
- outcome = "empty";
1040
- reason = error instanceof Error ? error.message : String(error);
1041
- payload = void 0;
1515
+ request.fail(error);
1042
1516
  }
1043
- if (payload === void 0 && outcome === "delivered") outcome = "empty";
1044
- record(runtimeEdge, outcome, reason);
1045
- if (payload !== void 0) runtime.inputs.set(runtimeEdge.edge.toPort, payload);
1046
- } else if (runtimeEdge.state === "satisfied" || node.join === "any_failed" || node.join === "all_done") {
1047
- if (spec.directive !== void 0 && options.prompts !== void 0) runtime.directives.push(options.prompts.resolve(spec.directive).text);
1048
- if (spec.kind === "analyzes" && runtimeEdge.state === "satisfied") {
1049
- const trace = runtimeEdge.payload;
1050
- runtime.traces.push({
1051
- node: spec.from.node,
1052
- ...trace?.traceRef !== void 0 ? { traceRef: trace.traceRef } : {}
1053
- });
1517
+ }
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
1546
+ },
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;
1570
+ }
1571
+ if (instance.node === compiled.root && enriched.status === "done" && enriched.valid === true) {
1572
+ abort.abort("root delivered");
1573
+ return;
1574
+ }
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({
1592
+ kind: "node-inputs-resolved",
1593
+ id: instanceLabel,
1594
+ node: nodeId,
1595
+ instance: instanceLabel,
1596
+ inputRef,
1597
+ seq: engineSeq++,
1598
+ at: new Date(now()).toISOString()
1599
+ });
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;
1619
+ }
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
1631
+ });
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
+ };
1054
1670
  }
1055
- record(runtimeEdge, "delivered");
1671
+ await propagate(instanceLabel, enriched);
1056
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);
1057
1675
  }
1058
- for (const runtimeEdge of gating) if (runtimeEdge.state !== "pending") {
1059
- runtimeEdge.state = "pending";
1060
- runtimeEdge.payload = void 0;
1061
- } else if ((nodes.get(runtimeEdge.edge.spec.from.node)?.live ?? 0) > 0) runtimeEdge.consumedOnce = true;
1062
- enter(id);
1063
- return true;
1064
- };
1065
- /** Settle every outbound edge of a completed source per its LATEST completion, then try joins. */
1066
- const propagate = (id, settle) => {
1067
- const node = compiled.nodes.get(id);
1068
- if (!node) return;
1069
- const succeeded = settle.status === "done" && settle.valid !== false;
1070
- for (const edge of node.outbound) {
1071
- const runtime = edges.get(edge.id);
1072
- if (!runtime) continue;
1073
- if (runtime.consumedOnce && runtime.state === "pending") {
1074
- runtime.consumedOnce = false;
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();
1075
1691
  continue;
1076
1692
  }
1077
- if (!succeeded) {
1078
- runtime.state = "failed";
1079
- runtime.payload = void 0;
1080
- } else {
1081
- const guardContext = {
1082
- node: id,
1083
- out: settle.out,
1084
- visits: nodes.get(id)?.visits ?? 0,
1085
- valid: settle.valid ?? true
1086
- };
1087
- const pass = edge.spec.guard === void 0 || evaluateCondition(edge.spec.guard, guardContext);
1088
- runtime.state = pass ? "satisfied" : "dead";
1089
- runtime.payload = pass ? edge.spec.kind === "analyzes" ? { traceRef: settle.traceRef } : settle.out : void 0;
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;
1090
1712
  }
1713
+ inFlightNext ??= scope.next();
1714
+ const raced = await Promise.race([inFlightNext.then((settle) => ({ settle })), wakeSignal.then(() => "wake")]);
1715
+ if (raced === "wake") {
1716
+ rearmWakeSignal();
1717
+ continue;
1718
+ }
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);
1091
1723
  }
1092
- for (const edge of node.outbound) tryRelease(edge.spec.to.node);
1093
- };
1094
- const allTerminalsSettled = () => compiled.terminals.every((id) => (nodes.get(id)?.settles.length ?? 0) > 0);
1095
- for (const id of compiled.entries) enter(id);
1096
- while (!failure && liveCount > 0) {
1097
- const settled = await scope.next();
1098
- if (settled === null) break;
1099
- const nodeId = liveHandles.get(settled.handle.id);
1100
- if (nodeId === void 0) continue;
1101
- liveHandles.delete(settled.handle.id);
1102
- liveCount -= 1;
1103
- const node = compiled.nodes.get(nodeId);
1104
- const runtime = nodes.get(nodeId);
1105
- if (!node || !runtime) continue;
1106
- runtime.live -= 1;
1107
- let entry;
1108
- if (settled.kind === "done") {
1109
- const admitted = admitPayload(settled.out);
1110
- let valid;
1111
- if (node.deliverable !== void 0) try {
1112
- valid = await node.deliverable.check(admitted);
1113
- } catch (error) {
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 {
1114
1734
  valid = false;
1115
1735
  }
1116
- entry = {
1117
- node: nodeId,
1118
- visit: runtime.visits,
1119
- status: "done",
1120
- ...valid !== void 0 ? { valid } : {},
1121
- out: admitted,
1122
- outRef: settled.outRef
1736
+ return {
1737
+ ...settle,
1738
+ out,
1739
+ ...valid !== void 0 ? { valid } : {}
1123
1740
  };
1124
- if (settled.trace?.status === "available") entry.traceRef = settled.trace.traceRef;
1125
- } else if (settled.kind === "down") entry = {
1126
- node: nodeId,
1127
- visit: runtime.visits,
1128
- status: "down",
1129
- reason: settled.reason
1130
1741
  };
1131
- else continue;
1132
- runtime.settles.push(entry);
1133
- settles.push(entry);
1134
- if (nodeId === compiled.root && entry.status === "done" && entry.valid === true) {
1135
- abort.abort("root delivered");
1136
- break;
1137
- }
1138
- propagate(nodeId, entry);
1139
- if (allTerminalsSettled()) break;
1140
- for (const parked of waitingForBudget.splice(0)) enter(parked);
1141
- if (liveCount === 0 && !failure) break;
1142
- }
1143
- options.signal?.removeEventListener("abort", onOuterAbort);
1144
- abort.abort("run complete");
1145
- while (await scope.next() !== null);
1146
- const finalize = async () => {
1147
- const rows = compiled.terminals.flatMap((id) => (nodes.get(id)?.settles ?? []).map((settle) => ({
1148
- id: settle.node,
1149
- status: settle.status,
1150
- valid: settle.status === "done" && settle.valid !== false,
1151
- ...settle.outRef !== void 0 ? { outRef: settle.outRef } : {}
1152
- })));
1153
- return runFinalizer(options.finalizer === void 0 || options.finalizer === "bestDelivered" ? bestDelivered : options.finalizer === "collectDelivered" ? collectDelivered : options.finalizer, {
1154
- settled: rows,
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
+ })),
1155
1780
  blobs,
1156
1781
  tree: scope.view,
1157
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
1158
1799
  });
1159
- };
1160
- const terminals = compiled.terminals.flatMap((id) => nodes.get(id)?.settles ?? []);
1161
- const finish = (result) => {
1162
- if (result.kind === "no-winner" && exhaustedEdges.size > 0) throw new GraphEdgeCapError(Object.freeze([...exhaustedEdges]), Object.freeze([...ledger]), result);
1163
- return result;
1164
- };
1165
- if (failure) return finish({
1166
- kind: "no-winner",
1167
- reason: failure.reason,
1168
- ...failure.error ? { error: failure.error } : {},
1169
- terminals,
1170
- settles,
1171
- ledger,
1172
- unreachable: []
1173
- });
1174
- if (options.signal?.aborted) return finish({
1175
- kind: "no-winner",
1176
- reason: "aborted",
1177
- terminals,
1178
- settles,
1179
- ledger,
1180
- unreachable: []
1181
- });
1182
- const out = compiled.terminals.some((id) => (nodes.get(id)?.settles ?? []).some((settle) => settle.status === "done" && settle.valid !== false)) ? await finalize() : void 0;
1183
- if (out !== void 0) return {
1184
- kind: "winner",
1185
- out,
1186
- terminals,
1187
- settles,
1188
- ledger
1189
- };
1190
- const unreachable = [...compiled.nodes.keys()].filter((id) => (nodes.get(id)?.settles.length ?? 0) === 0);
1191
- return finish({
1192
- kind: "no-winner",
1193
- reason: compiled.terminals.some((id) => unreachable.includes(id)) ? unreachable.length === compiled.nodes.size ? "budget-exhausted" : "unreachable-terminal" : "all-children-down",
1194
- terminals,
1195
- settles,
1196
- ledger,
1197
- unreachable
1198
- });
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;
1811
+ }
1812
+ if (ev.kind === "waiting" && ev.spec.kind === "token" && ev.seq > max) max = ev.seq;
1813
+ }
1814
+ return max;
1199
1815
  }
1200
1816
  //#endregion
1201
- export { CONDITION_OPS, DEFAULT_MAX_NODE_VISITS, JOIN_RULES, MAX_MAX_NODE_VISITS, admitPayload, agentKind, applyProjection, compileGraph, createGraphEngine, createRegistry, evaluateCondition, formatRegistryHandle, kindHandle, narrowEffects, parseRegistryHandle, runEngineGraph, schemaAccepts, scriptKind, subgraphKind, supervisorKind, validateCondition, validateNodeKind, validateProjection };
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 };
1202
1818
 
1203
1819
  //# sourceMappingURL=graph.js.map