@exellix/graph-engine 7.8.2 → 8.0.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 (37) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +16 -13
  3. package/dist/src/adapters/compileExellixExecutablePlan.d.ts +8 -0
  4. package/dist/src/adapters/compileExellixExecutablePlan.js +18 -0
  5. package/dist/src/adapters/migrateExellixGraphModelToAuthoring.d.ts +6 -0
  6. package/dist/src/adapters/migrateExellixGraphModelToAuthoring.js +273 -0
  7. package/dist/src/adapters/patchFinalizerPlans.d.ts +7 -0
  8. package/dist/src/adapters/patchFinalizerPlans.js +63 -0
  9. package/dist/src/errors/exellixGraphErrorCodes.d.ts +5 -0
  10. package/dist/src/errors/exellixGraphErrorCodes.js +5 -0
  11. package/dist/src/index.d.ts +9 -2
  12. package/dist/src/index.js +6 -2
  13. package/dist/src/plan/aiModelSelectionWire.d.ts +11 -0
  14. package/dist/src/plan/aiModelSelectionWire.js +39 -0
  15. package/dist/src/plan/applyNodePlanInvoke.d.ts +10 -0
  16. package/dist/src/plan/applyNodePlanInvoke.js +67 -0
  17. package/dist/src/plan/embeddedGraphToExellixGraph.d.ts +5 -0
  18. package/dist/src/plan/embeddedGraphToExellixGraph.js +131 -0
  19. package/dist/src/plan/planDeferredGates.d.ts +16 -0
  20. package/dist/src/plan/planDeferredGates.js +118 -0
  21. package/dist/src/plan/planExecuteEntry.d.ts +12 -0
  22. package/dist/src/plan/planExecuteEntry.js +73 -0
  23. package/dist/src/plan/planExecutionPipeline.d.ts +11 -0
  24. package/dist/src/plan/planExecutionPipeline.js +54 -0
  25. package/dist/src/plan/planModelConfig.d.ts +10 -0
  26. package/dist/src/plan/planModelConfig.js +46 -0
  27. package/dist/src/runtime/ExellixGraphRuntime.d.ts +9 -6
  28. package/dist/src/runtime/ExellixGraphRuntime.js +134 -96
  29. package/dist/src/runtime/executionMatrixHost.js +2 -1
  30. package/dist/src/runtime/studioGraphExecuteRequest.d.ts +51 -0
  31. package/dist/src/runtime/studioGraphExecuteRequest.js +78 -0
  32. package/dist/testkit/buildExecuteGraphInput.d.ts +4 -0
  33. package/dist/testkit/buildExecuteGraphInput.js +8 -0
  34. package/dist/testkit/index.d.ts +1 -0
  35. package/dist/testkit/index.js +1 -0
  36. package/dist/testkit/testModelAliasRuntime.js +2 -2
  37. package/package.json +7 -1
@@ -1,3 +1,10 @@
1
+ import { createExecutionTrace, appendExecutionEvent, validateExecutionTrace } from "@x12i/graphenix-trace-format";
2
+ import { preparePlanExecuteEntry, computePlanAudit } from "../plan/planExecuteEntry.js";
3
+ import { embeddedGraphToExellixGraph } from "../plan/embeddedGraphToExellixGraph.js";
4
+ import { graphAiModelConfigFromFinalizerPlan, graphAiModelConfigFromNodePlan } from "../plan/planModelConfig.js";
5
+ import { executionPipelineFromNodePlan } from "../plan/planExecutionPipeline.js";
6
+ import { applyNodePlanInvoke, finalizerPlanForId, nodePlanForId } from "../plan/applyNodePlanInvoke.js";
7
+ import { evaluateDeferredNodeGate, evaluateDeferredEdgeGate, nodeDeferredGate, hasPlanConditionalEdges, evaluatePlanEntryGates, planNeedsRunx, } from "../plan/planDeferredGates.js";
1
8
  import { mergeExecutionObject, copyExecutionContextFields, } from "./memory.js";
2
9
  import { buildAiTasksRunTaskRequest, extractRunTaskStrategyOverrides, resolveJobTypeId, } from "./buildAiTasksRunTaskRequest.js";
3
10
  import { runTaskResponseSucceeded } from "./runTaskResponse.js";
@@ -16,11 +23,9 @@ import { resolveNarrixForTaskNode } from "./resolveNarrixForTaskNode.js";
16
23
  import { assertFinalizerRequiredReadsResolvable, validateGraphFinalizer, } from "./finalizers/validateFinalizer.js";
17
24
  import { executeDeterministicFinalizer, executeSynthesizeFinalizer } from "./finalizers/executeFinalizer.js";
18
25
  import { createFinalizerError } from "./finalizers/errors.js";
19
- import { assertCanonicalGraphDocument } from "./validateCanonicalGraphDocument.js";
20
26
  import { assertCanonicalGraphRuntimeObject } from "./validateCanonicalGraphRuntime.js";
21
27
  import { applyGraphResponseDefinition } from "./graphResponseMapping.js";
22
28
  import { migrateLegacyGraphResponse } from "./graphResponseMigration.js";
23
- import { computeGraphDocumentContentSha256 } from "./graphDocumentFingerprint.js";
24
29
  import { buildAiTasksObservabilityRecord } from "./aiTasksObservability.js";
25
30
  import { buildMainLlmCallForRunTask, buildRunTaskIdentityEnvelope, shouldForwardRunTaskTraceMode, } from "./runTaskAugments.js";
26
31
  import { buildRunLog, extractLogxerCorrelationFromMetadata, extractTaskRunLogFromMetadata, resolveRunLogLimits, } from "./buildRunLog.js";
@@ -33,9 +38,6 @@ import { buildPredicateEvalContextForNode, mirrorTaskVariablesOnExecution, readE
33
38
  import { mergeExellixGraphRuntimeInvocation } from "./mergeExellixGraphRuntimeInvocation.js";
34
39
  import { resolveStepRetryPolicy, runRunTaskWithRetry } from "./stepRetry.js";
35
40
  import { isLocalSkillKey, runScopedDataReader, runDeterministicRule, runScopedAnswerWriter, runScopedAnswerAssembler, } from "./localSkills/index.js";
36
- import { evaluateGraphPredicate } from "./predicates.js";
37
- import { evaluateStructuredDataFilters } from "./dataFiltersEvaluation.js";
38
- import { evaluateTaskNodeConditions } from "./taskNodeConditionsEvaluation.js";
39
41
  import { resolveModelConfigForNode } from "./resolveModelConfigForNode.js";
40
42
  import { toRunTaskModelConfig } from "./graphAiModelConfig.js";
41
43
  import { isGraphAiModelConfig } from "./modelConfigSelection.js";
@@ -1155,36 +1157,45 @@ export function createExellixGraphRuntime(opts) {
1155
1157
  };
1156
1158
  }
1157
1159
  async function executeGraph(input) {
1158
- const { model: suppliedGraph, runtime } = input;
1159
- const { graph } = migrateLegacyGraphResponse(suppliedGraph);
1160
+ const { plan: suppliedPlan, runtime } = input;
1161
+ const { plan } = preparePlanExecuteEntry(suppliedPlan, runtime);
1162
+ const materialized = embeddedGraphToExellixGraph(plan);
1163
+ const graph = migrateLegacyGraphResponse(materialized).graph;
1160
1164
  const merged = mergeExellixGraphRuntimeInvocation(runtime, opts);
1161
- const failFast = merged.failFast;
1165
+ const scheduling = plan.schedulingPolicy;
1166
+ const plannerMode = scheduling?.mode ?? runtime.mode;
1167
+ const plannerGoalNodeId = scheduling?.goalNodeId ?? runtime.goalNodeId;
1168
+ const failFast = scheduling?.failFast ?? merged.failFast;
1162
1169
  const debugMode = runtime.debugMode === true;
1163
1170
  const eventEmitter = runtime.eventEmitter ?? opts.eventEmitter;
1164
1171
  const jobId = assertHostJobId(runtime.jobId);
1165
1172
  const graphTaskId = newGraphRunTaskId();
1166
1173
  const job = { ...(runtime.job ?? {}), id: jobId, jobId };
1167
- if (runtime.mode === "backward" && !runtime.goalNodeId) {
1174
+ if (plannerMode === "backward" && !plannerGoalNodeId) {
1168
1175
  const err = new Error(`BACKWARD_GOAL_REQUIRED: mode=backward requires goalNodeId`);
1169
1176
  err.code = "BACKWARD_GOAL_REQUIRED";
1170
1177
  throw err;
1171
1178
  }
1172
- const resolvedGraphIdRaw = graph?.id;
1173
- if (resolvedGraphIdRaw == null || resolvedGraphIdRaw === '') {
1174
- throw new Error('GRAPH_ID_REQUIRED: execution request model must include a non-empty id');
1175
- }
1176
- const resolvedGraphId = String(resolvedGraphIdRaw);
1179
+ const resolvedGraphId = String(plan.source.graphId);
1177
1180
  const runLogxer = createGraphEngineLogxer({ logging: merged.logging });
1181
+ const executionTrace = createExecutionTrace(plan, runtime);
1182
+ appendExecutionEvent(executionTrace, {
1183
+ id: `evt:${graphTaskId}:graph.started`,
1184
+ ts: new Date().toISOString(),
1185
+ level: "info",
1186
+ type: "graph.started",
1187
+ message: "Graph execution started.",
1188
+ });
1189
+ const planAudit = computePlanAudit(plan);
1178
1190
  return runWithAiTasksStackLogging(merged.logging, () => runGraphWithLogContext({ jobId, taskId: graphTaskId, graphId: resolvedGraphId, runId: graphTaskId }, async () => {
1179
1191
  bindGraphEngineRunLogxer(runLogxer);
1180
1192
  try {
1181
- assertCanonicalGraphDocument(graph, { jobId, graphId: resolvedGraphId }, { mode: 'execute' });
1182
1193
  assertCanonicalGraphRuntimeObject(runtime, { jobId, graphId: resolvedGraphId });
1183
1194
  let runxClient = opts.runx;
1184
- if (graphNeedsRunxClient(graph)) {
1195
+ if (planNeedsRunx(plan)) {
1185
1196
  if (!runxClient) {
1186
1197
  if (!opts.runxCreateOptions) {
1187
- const err = new Error("RUNX_REQUIRED: graph uses jsConditionFunction, aiCondition, or conditional modelConfig cases that require runx. Pass runx or runxCreateOptions on createExellixGraphRuntime.");
1198
+ const err = new Error("RUNX_REQUIRED: executable plan deferred gates require runx. Pass runx or runxCreateOptions on createExellixGraphRuntime.");
1188
1199
  err.code = "RUNX_REQUIRED";
1189
1200
  throw err;
1190
1201
  }
@@ -1198,10 +1209,6 @@ export function createExellixGraphRuntime(opts) {
1198
1209
  await runxClient.reload();
1199
1210
  }
1200
1211
  }
1201
- const graphAudit = {
1202
- source: 'model',
1203
- contentSha256: computeGraphDocumentContentSha256(suppliedGraph),
1204
- };
1205
1212
  const graphDocumentModel = mergeGraphDocumentModel(graph);
1206
1213
  const graphExecution = graphDocumentModel.graphExecution;
1207
1214
  const nodeResponseKeys = resolveNodeResponseKeys(graphExecution);
@@ -1215,8 +1222,8 @@ export function createExellixGraphRuntime(opts) {
1215
1222
  const { finalizer } = validateGraphFinalizer(graph);
1216
1223
  const engine = opts.engineFactory.create({
1217
1224
  graph,
1218
- mode: runtime.mode,
1219
- goalNodeId: runtime.goalNodeId,
1225
+ mode: plannerMode,
1226
+ goalNodeId: plannerGoalNodeId,
1220
1227
  dimension: runtime.dimension,
1221
1228
  initialState: runtime.initialState,
1222
1229
  initialVariables: runtime.initialVariables,
@@ -1226,19 +1233,15 @@ export function createExellixGraphRuntime(opts) {
1226
1233
  const errors = [];
1227
1234
  const finalizerNodeId = String(finalizer.id);
1228
1235
  const jobCorrelation = job?.jobType != null ? { jobType: job.jobType } : undefined;
1229
- // Pre-compute incoming-edge map for conditional-edge filtering of plan.nextNodes.
1230
- const rawEdges = Array.isArray(graph.edges) ? graph.edges : [];
1231
- const hasConditionalEdges = rawEdges.some((e) => e && typeof e === "object" && e.when != null);
1236
+ const hasConditionalEdges = hasPlanConditionalEdges(plan);
1232
1237
  const incomingByTo = new Map();
1233
1238
  if (hasConditionalEdges) {
1234
- for (const e of rawEdges) {
1235
- if (!e || typeof e !== "object")
1236
- continue;
1237
- const to = e.to;
1239
+ for (const edgeRef of plan.topology.edges) {
1240
+ const to = edgeRef.to.nodeId;
1238
1241
  if (typeof to !== "string" || to.length === 0)
1239
1242
  continue;
1240
1243
  const list = incomingByTo.get(to) ?? [];
1241
- list.push(e);
1244
+ list.push(edgeRef);
1242
1245
  incomingByTo.set(to, list);
1243
1246
  }
1244
1247
  }
@@ -1397,50 +1400,67 @@ export function createExellixGraphRuntime(opts) {
1397
1400
  ? currentExecution.input
1398
1401
  : {};
1399
1402
  }
1400
- const graphEntryForGate = graphDocumentModel.graphEntry;
1401
- if (graphEntryForGate?.dataFilters !== undefined) {
1402
- const entryEv = evaluateStructuredDataFilters(graphEntryForGate.dataFilters, recordForStructuredDataFilters());
1403
- if (entryEv.status === "unsupported_shape" ||
1404
- (entryEv.status === "evaluated" && !entryEv.ok)) {
1405
- const err = new Error("GRAPH_ENTRY_DATA_FILTERS_REJECTED: metadata.graphEntry.dataFilters (structured) did not accept runtime.executionMemory.input");
1406
- err.code = ExellixGraphErrorCode.GRAPH_ENTRY_DATA_FILTERS_REJECTED;
1407
- logGraphEngineErrorCode(ExellixGraphErrorCode.GRAPH_ENTRY_DATA_FILTERS_REJECTED, err.message, {
1408
- graphId: resolvedGraphId,
1409
- jobId,
1410
- taskId: graphTaskId,
1411
- error: err,
1412
- });
1413
- const finalOutput = buildFinalOutputFromGraphResponse();
1414
- const result = {
1415
- jobId,
1416
- taskId: graphTaskId,
1417
- graphId: resolvedGraphId,
1418
- status: "failed",
1419
- outputsByNodeId,
1420
- stepsResponses,
1421
- engineSnapshot: engine.snapshot(),
1422
- ...(finalOutput !== undefined ? { finalOutput } : {}),
1423
- errors: [...errors, { error: err }],
1424
- graphAudit,
1425
- ...finalizeGraphPayload("failed"),
1426
- };
1427
- const debug = buildDebugTrace();
1428
- if (debug)
1429
- result.debug = debug;
1430
- if (eventEmitter) {
1431
- eventEmitter.emit(createGraphFailEvent(jobId, resolvedGraphId, graphTaskId, err, {
1432
- finalMemory: { jobMemory: currentJobMemory, taskMemory: currentTaskMemory, execution: currentExecution, outputsMemory: currentOutputsMemory },
1433
- ...(jobCorrelation ?? {}),
1434
- }));
1435
- }
1436
- return result;
1403
+ const entryGateEv = await evaluatePlanEntryGates(plan.deferredGates.entryGates, recordForStructuredDataFilters(), {
1404
+ executionMemory: currentExecution,
1405
+ jobMemory: currentJobMemory,
1406
+ taskMemory: currentTaskMemory,
1407
+ job,
1408
+ }, runxClient);
1409
+ if (!entryGateEv.ok) {
1410
+ const err = new Error("GRAPH_ENTRY_DATA_FILTERS_REJECTED: plan entry gates rejected runtime.executionMemory.input");
1411
+ err.code = ExellixGraphErrorCode.GRAPH_ENTRY_DATA_FILTERS_REJECTED;
1412
+ logGraphEngineErrorCode(ExellixGraphErrorCode.GRAPH_ENTRY_DATA_FILTERS_REJECTED, err.message, {
1413
+ graphId: resolvedGraphId,
1414
+ jobId,
1415
+ taskId: graphTaskId,
1416
+ error: err,
1417
+ });
1418
+ const finalOutput = buildFinalOutputFromGraphResponse();
1419
+ appendExecutionEvent(executionTrace, {
1420
+ id: `evt:${graphTaskId}:graph.failed`,
1421
+ ts: new Date().toISOString(),
1422
+ level: "error",
1423
+ type: "graph.failed",
1424
+ message: err.message,
1425
+ });
1426
+ const result = {
1427
+ jobId,
1428
+ taskId: graphTaskId,
1429
+ graphId: resolvedGraphId,
1430
+ status: "failed",
1431
+ outputsByNodeId,
1432
+ stepsResponses,
1433
+ engineSnapshot: engine.snapshot(),
1434
+ ...(finalOutput !== undefined ? { finalOutput } : {}),
1435
+ errors: [...errors, { error: err }],
1436
+ planAudit,
1437
+ trace: executionTrace,
1438
+ ...finalizeGraphPayload("failed"),
1439
+ };
1440
+ const debug = buildDebugTrace();
1441
+ if (debug)
1442
+ result.debug = debug;
1443
+ if (eventEmitter) {
1444
+ eventEmitter.emit(createGraphFailEvent(jobId, resolvedGraphId, graphTaskId, err, {
1445
+ finalMemory: { jobMemory: currentJobMemory, taskMemory: currentTaskMemory, execution: currentExecution, outputsMemory: currentOutputsMemory },
1446
+ ...(jobCorrelation ?? {}),
1447
+ }));
1437
1448
  }
1449
+ return result;
1438
1450
  }
1439
1451
  while (true) {
1440
- const plan = engine.plan();
1441
- if (plan.status === "completed") {
1452
+ const wavePlan = engine.plan();
1453
+ if (wavePlan.status === "completed") {
1442
1454
  const graphStatus = errors.length ? "failed" : "completed";
1443
1455
  const finalOutput = buildFinalOutputFromGraphResponse();
1456
+ appendExecutionEvent(executionTrace, {
1457
+ id: `evt:${graphTaskId}:graph.${graphStatus}`,
1458
+ ts: new Date().toISOString(),
1459
+ level: graphStatus === "completed" ? "info" : "error",
1460
+ type: graphStatus === "completed" ? "graph.completed" : "graph.failed",
1461
+ message: `Graph execution ${graphStatus}.`,
1462
+ });
1463
+ validateExecutionTrace(executionTrace, plan);
1444
1464
  const result = {
1445
1465
  jobId,
1446
1466
  taskId: graphTaskId,
@@ -1452,7 +1472,8 @@ export function createExellixGraphRuntime(opts) {
1452
1472
  ...(finalOutput !== undefined ? { finalOutput } : {}),
1453
1473
  ...(errors.length === 0 ? { finalizerNodeId, finalizerType: finalizer.finalizerType } : {}),
1454
1474
  errors: errors.length ? errors : undefined,
1455
- graphAudit,
1475
+ planAudit,
1476
+ trace: executionTrace,
1456
1477
  ...finalizeGraphPayload(graphStatus),
1457
1478
  };
1458
1479
  const debug = buildDebugTrace();
@@ -1476,8 +1497,8 @@ export function createExellixGraphRuntime(opts) {
1476
1497
  }
1477
1498
  return result;
1478
1499
  }
1479
- if (plan.status !== "continue") {
1480
- const err = new Error(`GRAPH_BLOCKED: status=${plan.status}`);
1500
+ if (wavePlan.status !== "continue") {
1501
+ const err = new Error(`GRAPH_BLOCKED: status=${wavePlan.status}`);
1481
1502
  err.code = "GRAPH_BLOCKED";
1482
1503
  const finalOutput = buildFinalOutputFromGraphResponse();
1483
1504
  const result = {
@@ -1490,7 +1511,8 @@ export function createExellixGraphRuntime(opts) {
1490
1511
  engineSnapshot: engine.snapshot(),
1491
1512
  ...(finalOutput !== undefined ? { finalOutput } : {}),
1492
1513
  errors: [...errors, { error: err }],
1493
- graphAudit,
1514
+ planAudit,
1515
+ trace: executionTrace,
1494
1516
  ...finalizeGraphPayload("failed"),
1495
1517
  };
1496
1518
  const debug = buildDebugTrace();
@@ -1504,7 +1526,7 @@ export function createExellixGraphRuntime(opts) {
1504
1526
  }
1505
1527
  return result;
1506
1528
  }
1507
- let runnableNodes = plan.nextNodes ?? [];
1529
+ let runnableNodes = wavePlan.nextNodes ?? [];
1508
1530
  // Conditional edge filtering: a node with incoming edges that are *all* conditional must
1509
1531
  // have at least one incoming edge whose `when` evaluates true to be runnable this round.
1510
1532
  // Roots and nodes with at least one unconditional incoming edge always pass through.
@@ -1521,7 +1543,7 @@ export function createExellixGraphRuntime(opts) {
1521
1543
  const incoming = incomingByTo.get(id) ?? [];
1522
1544
  if (incoming.length === 0)
1523
1545
  return true;
1524
- const hasUnconditional = incoming.some((e) => e?.when == null);
1546
+ const hasUnconditional = incoming.some((e) => !e?.hasDeferredGate);
1525
1547
  if (hasUnconditional)
1526
1548
  return true;
1527
1549
  const planningContext = buildPredicateEvalContextForNode({
@@ -1531,7 +1553,10 @@ export function createExellixGraphRuntime(opts) {
1531
1553
  node: n,
1532
1554
  runtimeTaskVariables: runtime.taskVariables,
1533
1555
  });
1534
- return incoming.some((e) => evaluateGraphPredicate(e.when, planningContext));
1556
+ return incoming.some((edgeRef) => {
1557
+ const gate = plan.deferredGates.edgeGates[edgeRef.edgeId];
1558
+ return evaluateDeferredEdgeGate(gate, planningContext);
1559
+ });
1535
1560
  });
1536
1561
  }
1537
1562
  const dataFiltersRecord = recordForStructuredDataFilters();
@@ -1555,7 +1580,7 @@ export function createExellixGraphRuntime(opts) {
1555
1580
  node: n,
1556
1581
  runtimeTaskVariables: runtime.taskVariables,
1557
1582
  });
1558
- const condEv = await evaluateTaskNodeConditions(n.conditions, { ...conditionCtxBase, ...nodePredicateCtx }, dataFiltersRecord, { runx: runxClient });
1583
+ const condEv = await evaluateDeferredNodeGate(nodeDeferredGate(plan, String(n.id)), { ...conditionCtxBase, ...nodePredicateCtx }, dataFiltersRecord, runxClient);
1559
1584
  if (!condEv.ok) {
1560
1585
  skippedByConditions.push({
1561
1586
  node: n,
@@ -1605,27 +1630,32 @@ export function createExellixGraphRuntime(opts) {
1605
1630
  context: { model: graph, runtime, graphId: resolvedGraphId, jobId, taskId: graphTaskId, node: node },
1606
1631
  });
1607
1632
  const nodeTaskMemory = isPlainRecord(currentTaskMemory) ? { ...currentTaskMemory } : currentTaskMemory;
1608
- const effectiveModelConfig = await resolveModelConfigForNode({
1609
- nodeModelConfig: node.taskConfiguration?.modelConfig,
1610
- graphModelConfig: graph.modelConfig,
1611
- conditionCtx: buildPredicateEvalContextForNode({
1612
- executionMemory: currentExecution,
1613
- jobMemory: currentJobMemory,
1614
- taskMemory: currentTaskMemory,
1615
- node: node,
1616
- runtimeTaskVariables: runtime.taskVariables,
1617
- }),
1618
- executionInput: dataFiltersRecord,
1619
- runx: runxClient,
1620
- graphId: resolvedGraphId,
1621
- nodeId: typeof node.id === "string" ? node.id : String(node.id),
1622
- jobId,
1633
+ const nodeIdStr = String(node.id);
1634
+ appendExecutionEvent(executionTrace, {
1635
+ id: `evt:${graphTaskId}:node.started:${nodeIdStr}`,
1636
+ ts: new Date().toISOString(),
1637
+ level: "info",
1638
+ type: "node.started",
1639
+ nodeId: nodeIdStr,
1623
1640
  });
1641
+ const nodePlanEntryResolved = nodePlanForId(plan, nodeIdStr);
1642
+ const finalizerPlanEntryResolved = node.type === "finalizer"
1643
+ ? finalizerPlanForId(plan, nodeIdStr)
1644
+ : undefined;
1645
+ const taskNodeForExecute = nodePlanEntryResolved && node.type !== "finalizer"
1646
+ ? applyNodePlanInvoke(node, nodePlanEntryResolved)
1647
+ : node;
1648
+ const effectiveModelConfig = finalizerPlanEntryResolved != null
1649
+ ? graphAiModelConfigFromFinalizerPlan(finalizerPlanEntryResolved)
1650
+ : nodePlanEntryResolved && node.type !== "finalizer"
1651
+ ? graphAiModelConfigFromNodePlan(nodePlanEntryResolved)
1652
+ : undefined;
1653
+ const nodeExecutionPipeline = nodePlanEntryResolved != null ? executionPipelineFromNodePlan(nodePlanEntryResolved) : undefined;
1624
1654
  const r = await executeNode({
1625
1655
  graphId: resolvedGraphId,
1626
1656
  graph,
1627
1657
  graphRunTaskId: graphTaskId,
1628
- node,
1658
+ node: taskNodeForExecute,
1629
1659
  job,
1630
1660
  jobMemory: currentJobMemory,
1631
1661
  taskMemory: nodeTaskMemory,
@@ -1639,7 +1669,7 @@ export function createExellixGraphRuntime(opts) {
1639
1669
  runTaskIdentity: merged.runTaskIdentity,
1640
1670
  runTaskExecutionMode: merged.runTaskExecutionMode,
1641
1671
  runTaskDiagnostics: merged.runTaskDiagnostics,
1642
- graphExecutionPipeline: merged.executionPipeline,
1672
+ graphExecutionPipeline: nodeExecutionPipeline ?? merged.executionPipeline,
1643
1673
  skillKeyResolution: merged.skillKeyResolution,
1644
1674
  nodeTimeoutMs: merged.nodeTimeoutMs,
1645
1675
  clearSynthesizedContextPerNode: merged.clearSynthesizedContextPerNode,
@@ -1651,6 +1681,13 @@ export function createExellixGraphRuntime(opts) {
1651
1681
  outputsByNodeId[r.nodeId] = r.output;
1652
1682
  appendStepResponse(node, r.output);
1653
1683
  engine.commit({ nodeId: r.nodeId, output: r.output });
1684
+ appendExecutionEvent(executionTrace, {
1685
+ id: `evt:${graphTaskId}:node.completed:${nodeIdStr}`,
1686
+ ts: new Date().toISOString(),
1687
+ level: "info",
1688
+ type: "node.completed",
1689
+ nodeId: nodeIdStr,
1690
+ });
1654
1691
  const trl = r.taskRunLog;
1655
1692
  if (trl?.length)
1656
1693
  taskRunLogBuffer.push(...trl);
@@ -1722,7 +1759,8 @@ export function createExellixGraphRuntime(opts) {
1722
1759
  engineSnapshot: engine.snapshot(),
1723
1760
  ...(finalOutput !== undefined ? { finalOutput } : {}),
1724
1761
  errors,
1725
- graphAudit,
1762
+ planAudit,
1763
+ trace: executionTrace,
1726
1764
  ...finalizeGraphPayload("failed"),
1727
1765
  };
1728
1766
  const debug = buildDebugTrace();
@@ -5,6 +5,7 @@
5
5
  * a `{ model, runtime }` request with per-graph `GraphEntryContract` + seeded
6
6
  * `runtime.executionMemory` objects built here.
7
7
  */
8
+ import { compileExellixExecutablePlan } from '../adapters/compileExellixExecutablePlan.js';
8
9
  import { mergeGraphDocumentModel } from '../types/refs.js';
9
10
  import { createExellixGraphRuntime } from './ExellixGraphRuntime.js';
10
11
  import { selectByPath, writeByPath } from './pathExpr.js';
@@ -76,7 +77,7 @@ export function buildExecutionSeedFromGraphEntry(graphEntry, sources = {}) {
76
77
  /** Builds the canonical graph-engine execution request for matrix-style hosts. */
77
78
  export function buildGraphExecutionRequest(input) {
78
79
  return {
79
- model: input.model,
80
+ plan: compileExellixExecutablePlan(input.model, input.runtime),
80
81
  runtime: input.runtime,
81
82
  };
82
83
  }
@@ -0,0 +1,51 @@
1
+ import type { Graph } from '../types/refs.js';
2
+ import type { ExecuteGraphInput, GraphRuntimeObject } from './ExellixGraphRuntime.js';
3
+ /**
4
+ * Removed from the graphs-studio execute envelope (7.8.3+). No reader, no fallback.
5
+ * Default model selection belongs on {@link Graph}.modelConfig only.
6
+ */
7
+ export declare const STUDIO_GRAPH_EXECUTE_REMOVED_KEYS: readonly ["graphDefaultModel"];
8
+ export type StudioGraphExecuteRemovedKey = (typeof STUDIO_GRAPH_EXECUTE_REMOVED_KEYS)[number];
9
+ /**
10
+ * Graphs-studio / playground graph execute envelope before host adaptation to
11
+ * {@link ExecuteGraphInput}. Credential and simulate-only fields are host concerns;
12
+ * graph-engine validates shape and forbidden keys only.
13
+ */
14
+ export type StudioGraphExecuteRequest = {
15
+ mode: 'graph';
16
+ graph: Graph;
17
+ jobId: string;
18
+ runtime: Pick<GraphRuntimeObject, 'input' | 'inputs' | 'jobMemory' | 'taskMemory' | 'executionMemory' | 'outputsMemory' | 'variables' | 'jobVariables' | 'taskVariables' | 'nodes'>;
19
+ studioId?: string;
20
+ executionMode?: string;
21
+ runLogMode?: GraphRuntimeObject['runLogMode'];
22
+ maxRunLogEntries?: number;
23
+ maxRunLogDataJsonChars?: number;
24
+ graphExecution?: {
25
+ mode?: GraphRuntimeObject['mode'];
26
+ goalNodeId?: string;
27
+ dimension?: string;
28
+ };
29
+ runTaskDiagnostics?: GraphRuntimeObject['runTaskDiagnostics'];
30
+ exampleIndex?: number;
31
+ validateGraphEntrySeed?: boolean;
32
+ openrouterApiKey?: string;
33
+ };
34
+ export declare function getStudioGraphExecuteRemovedKeyViolations(request: unknown): StudioGraphExecuteRemovedKey[];
35
+ /**
36
+ * Rejects removed studio execute keys (notably `graphDefaultModel`).
37
+ * Hosts must persist model defaults on `graph.modelConfig` and stop sending request-level mirrors.
38
+ */
39
+ export declare function assertCanonicalStudioGraphExecuteRequest(request: unknown, context?: {
40
+ jobId?: string;
41
+ graphId?: string;
42
+ }): asserts request is StudioGraphExecuteRequest;
43
+ export type BuildGraphExecutionRequestFromStudioExecuteOptions = {
44
+ agentId?: string;
45
+ jobTypeId?: string;
46
+ };
47
+ /**
48
+ * Maps a validated studio execute envelope to {@link ExecuteGraphInput}.
49
+ * Does not read or merge removed request-level model defaults — graph.modelConfig is authoritative.
50
+ */
51
+ export declare function buildGraphExecutionRequestFromStudioExecute(request: StudioGraphExecuteRequest, options?: BuildGraphExecutionRequestFromStudioExecuteOptions): ExecuteGraphInput;
@@ -0,0 +1,78 @@
1
+ import { compileExellixExecutablePlan } from '../adapters/compileExellixExecutablePlan.js';
2
+ import { ExellixGraphError } from '../errors/ExellixGraphError.js';
3
+ import { ExellixGraphErrorCode } from '../errors/exellixGraphErrorCodes.js';
4
+ /**
5
+ * Removed from the graphs-studio execute envelope (7.8.3+). No reader, no fallback.
6
+ * Default model selection belongs on {@link Graph}.modelConfig only.
7
+ */
8
+ export const STUDIO_GRAPH_EXECUTE_REMOVED_KEYS = ['graphDefaultModel'];
9
+ function isPlainObject(v) {
10
+ return v != null && typeof v === 'object' && !Array.isArray(v);
11
+ }
12
+ export function getStudioGraphExecuteRemovedKeyViolations(request) {
13
+ if (!isPlainObject(request))
14
+ return [];
15
+ return STUDIO_GRAPH_EXECUTE_REMOVED_KEYS.filter((key) => Object.prototype.hasOwnProperty.call(request, key));
16
+ }
17
+ /**
18
+ * Rejects removed studio execute keys (notably `graphDefaultModel`).
19
+ * Hosts must persist model defaults on `graph.modelConfig` and stop sending request-level mirrors.
20
+ */
21
+ export function assertCanonicalStudioGraphExecuteRequest(request, context) {
22
+ const removed = getStudioGraphExecuteRemovedKeyViolations(request);
23
+ if (removed.length > 0) {
24
+ throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_STUDIO_GRAPH_EXECUTE_REQUEST, `Studio graph execute request must not include removed field(s): ${removed.join(', ')}. Model defaults belong on graph.modelConfig only; request-level graphDefaultModel was removed with no legacy fallback.`, { ...context, removedKeys: removed });
25
+ }
26
+ if (!isPlainObject(request)) {
27
+ throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_STUDIO_GRAPH_EXECUTE_REQUEST, 'Studio graph execute request must be a plain object.', context);
28
+ }
29
+ if (request.mode !== 'graph') {
30
+ throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_STUDIO_GRAPH_EXECUTE_REQUEST, 'Studio graph execute request mode must be "graph".', context);
31
+ }
32
+ const jobId = typeof request.jobId === 'string' ? request.jobId.trim() : '';
33
+ if (!jobId) {
34
+ throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_STUDIO_GRAPH_EXECUTE_REQUEST, 'Studio graph execute request requires a non-empty jobId.', context);
35
+ }
36
+ if (!isPlainObject(request.graph)) {
37
+ throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_STUDIO_GRAPH_EXECUTE_REQUEST, 'Studio graph execute request requires graph (GraphModelObject).', { ...context, jobId });
38
+ }
39
+ if (!isPlainObject(request.runtime)) {
40
+ throw new ExellixGraphError(ExellixGraphErrorCode.NON_CANONICAL_STUDIO_GRAPH_EXECUTE_REQUEST, 'Studio graph execute request requires runtime.', { ...context, jobId });
41
+ }
42
+ }
43
+ /**
44
+ * Maps a validated studio execute envelope to {@link ExecuteGraphInput}.
45
+ * Does not read or merge removed request-level model defaults — graph.modelConfig is authoritative.
46
+ */
47
+ export function buildGraphExecutionRequestFromStudioExecute(request, options) {
48
+ assertCanonicalStudioGraphExecuteRequest(request);
49
+ const jobId = request.jobId.trim();
50
+ const graphId = request.graph != null && typeof request.graph === 'object' && typeof request.graph.id === 'string'
51
+ ? request.graph.id
52
+ : undefined;
53
+ const runtime = {
54
+ jobId,
55
+ job: {
56
+ id: jobId,
57
+ jobId,
58
+ agentId: options?.agentId ?? 'standalone-agent',
59
+ jobTypeId: options?.jobTypeId ?? 'exellix-graph-job',
60
+ },
61
+ ...request.runtime,
62
+ ...(request.graphExecution?.mode != null ? { mode: request.graphExecution.mode } : {}),
63
+ ...(request.graphExecution?.goalNodeId != null
64
+ ? { goalNodeId: request.graphExecution.goalNodeId }
65
+ : {}),
66
+ ...(request.graphExecution?.dimension != null ? { dimension: request.graphExecution.dimension } : {}),
67
+ ...(request.runLogMode != null ? { runLogMode: request.runLogMode } : {}),
68
+ ...(request.maxRunLogEntries != null ? { maxRunLogEntries: request.maxRunLogEntries } : {}),
69
+ ...(request.maxRunLogDataJsonChars != null
70
+ ? { maxRunLogDataJsonChars: request.maxRunLogDataJsonChars }
71
+ : {}),
72
+ ...(request.runTaskDiagnostics != null ? { runTaskDiagnostics: request.runTaskDiagnostics } : {}),
73
+ };
74
+ return {
75
+ plan: compileExellixExecutablePlan(request.graph, runtime),
76
+ runtime,
77
+ };
78
+ }
@@ -0,0 +1,4 @@
1
+ import type { Graph, GraphModelObject } from '../src/types/refs.js';
2
+ import type { ExecuteGraphInput, GraphRuntimeObject } from '../src/runtime/ExellixGraphRuntime.js';
3
+ /** Builds `{ plan, runtime }` for tests and hosts that still author exellix GraphModelObject JSON. */
4
+ export declare function buildExecuteGraphInput(model: Graph | GraphModelObject, runtime: GraphRuntimeObject): ExecuteGraphInput;
@@ -0,0 +1,8 @@
1
+ import { compileExellixExecutablePlan } from '../src/adapters/compileExellixExecutablePlan.js';
2
+ /** Builds `{ plan, runtime }` for tests and hosts that still author exellix GraphModelObject JSON. */
3
+ export function buildExecuteGraphInput(model, runtime) {
4
+ return {
5
+ plan: compileExellixExecutablePlan(model, runtime),
6
+ runtime,
7
+ };
8
+ }
@@ -2,4 +2,5 @@ export { InMemoryGraphLoader } from './inMemoryGraphLoader.js';
2
2
  export { DepGraphEngineFactory } from './depGraphEngineFactory.js';
3
3
  export { RealTasksClient } from './RealTasksClient.js';
4
4
  export { createTestExellixGraphRuntime } from './testModelAliasRuntime.js';
5
+ export { buildExecuteGraphInput } from './buildExecuteGraphInput.js';
5
6
  export { tryLoadExellixAiTasksRuntimeSubtree, loadExellixGraphRuntimeObjects, } from './exellixRuntimeObjects.js';
@@ -2,4 +2,5 @@ export { InMemoryGraphLoader } from './inMemoryGraphLoader.js';
2
2
  export { DepGraphEngineFactory } from './depGraphEngineFactory.js';
3
3
  export { RealTasksClient } from './RealTasksClient.js';
4
4
  export { createTestExellixGraphRuntime } from './testModelAliasRuntime.js';
5
+ export { buildExecuteGraphInput } from './buildExecuteGraphInput.js';
5
6
  export { tryLoadExellixAiTasksRuntimeSubtree, loadExellixGraphRuntimeObjects, } from './exellixRuntimeObjects.js';
@@ -9,12 +9,12 @@ export function createTestExellixGraphRuntime(opts) {
9
9
  const base = input.runtime ?? {};
10
10
  const jobId = typeof base.jobId === 'string' && base.jobId.trim() !== ''
11
11
  ? base.jobId
12
- : `job-${input.model.id ?? 'graph'}`;
12
+ : `job-${input.plan.source.graphId}`;
13
13
  const job = base.job != null && typeof base.job === 'object'
14
14
  ? base.job
15
15
  : { agentId: 'test-agent', jobType: 'unit-test' };
16
16
  return executeGraph({
17
- ...input,
17
+ plan: input.plan,
18
18
  runtime: { ...base, jobId, job },
19
19
  });
20
20
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exellix/graph-engine",
3
- "version": "7.8.2",
3
+ "version": "8.0.0",
4
4
  "type": "module",
5
5
  "description": "Graph executor SDK",
6
6
  "main": "dist/src/index.js",
@@ -58,7 +58,12 @@
58
58
  "@x12i/env": "4.0.1",
59
59
  "@x12i/funcx": "4.4.4",
60
60
  "@x12i/graphenix": "2.5.0",
61
+ "@x12i/graphenix-executable-contracts": "^1.0.0",
62
+ "@x12i/graphenix-execute-envelope": "^1.0.0",
61
63
  "@x12i/graphenix-format": "2.0.0",
64
+ "@x12i/graphenix-plan-format": "^1.0.0",
65
+ "@x12i/graphenix-plan-compiler": "^1.0.0",
66
+ "@x12i/graphenix-trace-format": "^1.0.0",
62
67
  "@x12i/logxer": "^4.6.0",
63
68
  "@x12i/memorix-descriptors": "1.6.0",
64
69
  "@x12i/memorix-retrieval": "1.11.2",
@@ -68,6 +73,7 @@
68
73
  },
69
74
  "devDependencies": {
70
75
  "@types/node": "25.9.1",
76
+ "@x12i/graphenix-authoring-format": "^1.0.1",
71
77
  "tsx": "4.22.3",
72
78
  "typescript": "6.0.3"
73
79
  }