@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.
- package/dist/{activation-DmUIq51m.js → activation-CIwer0zU.js} +3 -3
- package/dist/{activation-DmUIq51m.js.map → activation-CIwer0zU.js.map} +1 -1
- package/dist/agent.js +2 -2
- package/dist/{authoring-Cm122Duf.js → authoring-CUTgAxxE.js} +2 -2
- package/dist/{authoring-Cm122Duf.js.map → authoring-CUTgAxxE.js.map} +1 -1
- package/dist/candidate-execution/index.js +4 -4
- package/dist/{candidate-execution-DC0T8l2p.js → candidate-execution-BbMbyhgC.js} +4 -4
- package/dist/{candidate-execution-DC0T8l2p.js.map → candidate-execution-BbMbyhgC.js.map} +1 -1
- package/dist/durable.js +1 -1
- package/dist/{graph-DGri_zJm.js → graph-ZDMygFIo.js} +21 -7
- package/dist/graph-ZDMygFIo.js.map +1 -0
- package/dist/graph.d.ts +161 -16
- package/dist/graph.js +990 -736
- package/dist/graph.js.map +1 -1
- package/dist/{improvement-cycle-BOd5DFeG.js → improvement-cycle-BAKN_f4x.js} +4 -4
- package/dist/{improvement-cycle-BOd5DFeG.js.map → improvement-cycle-BAKN_f4x.js.map} +1 -1
- package/dist/index.js +11 -11
- package/dist/intelligence.js +5 -5
- package/dist/kernel.js +6 -6
- package/dist/{knowledge-7_IfyD9R.js → knowledge-D-7PXKUV.js} +5 -5
- package/dist/{knowledge-7_IfyD9R.js.map → knowledge-D-7PXKUV.js.map} +1 -1
- package/dist/knowledge.js +1 -1
- package/dist/{loop-runner-bin-Dx-5GZQK.js → loop-runner-bin-DHxQ_VQD.js} +3 -3
- package/dist/{loop-runner-bin-Dx-5GZQK.js.map → loop-runner-bin-DHxQ_VQD.js.map} +1 -1
- package/dist/loop-runner-bin.js +1 -1
- package/dist/mcp/bin.js +3 -3
- package/dist/mcp/index.js +4 -4
- package/dist/{openai-tools-CDS7eSww.js → openai-tools-UyXVO0kd.js} +2 -2
- package/dist/{openai-tools-CDS7eSww.js.map → openai-tools-UyXVO0kd.js.map} +1 -1
- package/dist/{prepare-BCScSDCv.js → prepare-BAyaNoZu.js} +3 -3
- package/dist/prepare-BAyaNoZu.js.map +1 -0
- package/dist/{protected-model-port-D-5E3HBe.js → protected-model-port-T2UWrDNQ.js} +2 -2
- package/dist/{protected-model-port-D-5E3HBe.js.map → protected-model-port-T2UWrDNQ.js.map} +1 -1
- package/dist/{runtime-DESENUe1.js → runtime-D5l-enW9.js} +6 -6
- package/dist/{runtime-DESENUe1.js.map → runtime-D5l-enW9.js.map} +1 -1
- package/dist/{structural-rollout-DZGDg0yX.js → structural-rollout-C7nNNhri.js} +2 -2
- package/dist/{structural-rollout-DZGDg0yX.js.map → structural-rollout-C7nNNhri.js.map} +1 -1
- package/dist/{supervise-Da20R0PO.js → supervise-rEE46ruH.js} +2 -2
- package/dist/{supervise-Da20R0PO.js.map → supervise-rEE46ruH.js.map} +1 -1
- package/dist/{supervisor-DpIKMlPJ.js → supervisor-B-mmmiOU.js} +24 -4
- package/dist/supervisor-B-mmmiOU.js.map +1 -0
- package/dist/testing.js +12 -12
- package/dist/{workspace-archive-CHY5HvrF.js → workspace-archive-B0Hqp2eJ.js} +12 -7
- package/dist/workspace-archive-B0Hqp2eJ.js.map +1 -0
- package/package.json +3 -3
- package/dist/graph-DGri_zJm.js.map +0 -1
- package/dist/prepare-BCScSDCv.js.map +0 -1
- package/dist/supervisor-DpIKMlPJ.js.map +0 -1
- 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-
|
|
5
|
-
import { i as workerFromBackend, s as supervisorAgent } from "./supervise-
|
|
6
|
-
import { t as GraphEdgeCapError } from "./graph-
|
|
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 <
|
|
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
|
|
461
|
-
|
|
462
|
-
const
|
|
463
|
-
|
|
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
|
|
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:
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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/
|
|
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
|
-
*
|
|
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
|
-
*
|
|
1015
|
-
*
|
|
1016
|
-
*
|
|
1017
|
-
*
|
|
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
|
-
*
|
|
1023
|
-
*
|
|
1024
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
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
|
-
/**
|
|
1059
|
-
|
|
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 =
|
|
1515
|
+
const compiled = asCompiled(engine, spec);
|
|
1069
1516
|
const wakes = [];
|
|
1070
|
-
let
|
|
1071
|
-
|
|
1072
|
-
const done = runGraphLoop(engine, compiled, task, options, wakes, (
|
|
1073
|
-
|
|
1517
|
+
let signalWake = () => {};
|
|
1518
|
+
let finished = false;
|
|
1519
|
+
const done = runGraphLoop(engine, compiled, task, options, wakes, (fn) => {
|
|
1520
|
+
signalWake = fn;
|
|
1074
1521
|
}).finally(() => {
|
|
1075
|
-
|
|
1522
|
+
finished = true;
|
|
1076
1523
|
});
|
|
1077
1524
|
const queue = (token, payload, expire) => {
|
|
1078
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
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
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
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
|
|
1131
|
-
const
|
|
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
|
-
|
|
1149
|
-
executors: createExecutorRegistry(),
|
|
1150
|
-
seams: {},
|
|
1151
|
-
depth: 0,
|
|
1152
|
-
signal: abort.signal,
|
|
1577
|
+
runId,
|
|
1153
1578
|
now,
|
|
1154
|
-
|
|
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
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
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
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
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
|
-
|
|
1401
|
-
|
|
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
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
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:
|
|
1428
|
-
edge: edge.id,
|
|
1429
|
-
fired,
|
|
1430
|
-
sourceStatus:
|
|
1431
|
-
|
|
1767
|
+
id: `graph:${nodeId}`,
|
|
1768
|
+
edge: entry.edge.id,
|
|
1769
|
+
fired: false,
|
|
1770
|
+
sourceStatus: "done",
|
|
1771
|
+
capped: true,
|
|
1432
1772
|
seq: engineSeq++,
|
|
1433
|
-
at:
|
|
1773
|
+
at: stamp()
|
|
1434
1774
|
});
|
|
1775
|
+
return;
|
|
1435
1776
|
}
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
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
|
|
1452
|
-
kind: "
|
|
1453
|
-
id:
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
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:
|
|
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
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
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
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
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
|
-
|
|
1548
|
-
|
|
1549
|
-
},
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
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.
|
|
1572
|
-
|
|
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
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
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:
|
|
1594
|
-
node:
|
|
1595
|
-
instance:
|
|
1596
|
-
inputRef,
|
|
1979
|
+
id: label,
|
|
1980
|
+
node: instance.node,
|
|
1981
|
+
instance: label,
|
|
1982
|
+
inputRef: instance.inputRef,
|
|
1597
1983
|
seq: engineSeq++,
|
|
1598
|
-
at:
|
|
1984
|
+
at: stamp()
|
|
1599
1985
|
});
|
|
1600
|
-
await spawnInstance(
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
const
|
|
1608
|
-
if (
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
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
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
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
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
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
|
-
|
|
1720
|
-
if (
|
|
1721
|
-
await
|
|
1722
|
-
|
|
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
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|