@exellix/graph-engine 7.8.2 → 8.0.1

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 (35) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +26 -24
  3. package/dist/src/compile/compileExellixExecutablePlan.d.ts +9 -0
  4. package/dist/src/compile/compileExellixExecutablePlan.js +24 -0
  5. package/dist/src/errors/exellixGraphErrorCodes.d.ts +5 -0
  6. package/dist/src/errors/exellixGraphErrorCodes.js +5 -0
  7. package/dist/src/index.d.ts +10 -2
  8. package/dist/src/index.js +6 -2
  9. package/dist/src/plan/aiModelSelectionWire.d.ts +11 -0
  10. package/dist/src/plan/aiModelSelectionWire.js +39 -0
  11. package/dist/src/plan/applyNodePlanInvoke.d.ts +10 -0
  12. package/dist/src/plan/applyNodePlanInvoke.js +67 -0
  13. package/dist/src/plan/embeddedGraphToExellixGraph.d.ts +5 -0
  14. package/dist/src/plan/embeddedGraphToExellixGraph.js +131 -0
  15. package/dist/src/plan/planDeferredGates.d.ts +16 -0
  16. package/dist/src/plan/planDeferredGates.js +118 -0
  17. package/dist/src/plan/planExecuteEntry.d.ts +12 -0
  18. package/dist/src/plan/planExecuteEntry.js +73 -0
  19. package/dist/src/plan/planExecutionPipeline.d.ts +11 -0
  20. package/dist/src/plan/planExecutionPipeline.js +54 -0
  21. package/dist/src/plan/planModelConfig.d.ts +10 -0
  22. package/dist/src/plan/planModelConfig.js +46 -0
  23. package/dist/src/runtime/ExellixGraphRuntime.d.ts +9 -6
  24. package/dist/src/runtime/ExellixGraphRuntime.js +134 -97
  25. package/dist/src/runtime/executionMatrixHost.d.ts +1 -1
  26. package/dist/src/runtime/executionMatrixHost.js +3 -2
  27. package/dist/src/runtime/studioGraphExecuteRequest.d.ts +51 -0
  28. package/dist/src/runtime/studioGraphExecuteRequest.js +78 -0
  29. package/dist/src/types/refs.d.ts +1 -1
  30. package/dist/testkit/buildExecuteGraphInput.d.ts +4 -0
  31. package/dist/testkit/buildExecuteGraphInput.js +8 -0
  32. package/dist/testkit/index.d.ts +1 -0
  33. package/dist/testkit/index.js +1 -0
  34. package/dist/testkit/testModelAliasRuntime.js +2 -2
  35. package/package.json +8 -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,8 @@ 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
- import { migrateLegacyGraphResponse } from "./graphResponseMigration.js";
23
- import { computeGraphDocumentContentSha256 } from "./graphDocumentFingerprint.js";
24
28
  import { buildAiTasksObservabilityRecord } from "./aiTasksObservability.js";
25
29
  import { buildMainLlmCallForRunTask, buildRunTaskIdentityEnvelope, shouldForwardRunTaskTraceMode, } from "./runTaskAugments.js";
26
30
  import { buildRunLog, extractLogxerCorrelationFromMetadata, extractTaskRunLogFromMetadata, resolveRunLogLimits, } from "./buildRunLog.js";
@@ -33,9 +37,6 @@ import { buildPredicateEvalContextForNode, mirrorTaskVariablesOnExecution, readE
33
37
  import { mergeExellixGraphRuntimeInvocation } from "./mergeExellixGraphRuntimeInvocation.js";
34
38
  import { resolveStepRetryPolicy, runRunTaskWithRetry } from "./stepRetry.js";
35
39
  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
40
  import { resolveModelConfigForNode } from "./resolveModelConfigForNode.js";
40
41
  import { toRunTaskModelConfig } from "./graphAiModelConfig.js";
41
42
  import { isGraphAiModelConfig } from "./modelConfigSelection.js";
@@ -1155,36 +1156,45 @@ export function createExellixGraphRuntime(opts) {
1155
1156
  };
1156
1157
  }
1157
1158
  async function executeGraph(input) {
1158
- const { model: suppliedGraph, runtime } = input;
1159
- const { graph } = migrateLegacyGraphResponse(suppliedGraph);
1159
+ const { plan: suppliedPlan, runtime } = input;
1160
+ const { plan } = preparePlanExecuteEntry(suppliedPlan, runtime);
1161
+ const materialized = embeddedGraphToExellixGraph(plan);
1162
+ const graph = materialized;
1160
1163
  const merged = mergeExellixGraphRuntimeInvocation(runtime, opts);
1161
- const failFast = merged.failFast;
1164
+ const scheduling = plan.schedulingPolicy;
1165
+ const plannerMode = scheduling?.mode ?? runtime.mode;
1166
+ const plannerGoalNodeId = scheduling?.goalNodeId ?? runtime.goalNodeId;
1167
+ const failFast = scheduling?.failFast ?? merged.failFast;
1162
1168
  const debugMode = runtime.debugMode === true;
1163
1169
  const eventEmitter = runtime.eventEmitter ?? opts.eventEmitter;
1164
1170
  const jobId = assertHostJobId(runtime.jobId);
1165
1171
  const graphTaskId = newGraphRunTaskId();
1166
1172
  const job = { ...(runtime.job ?? {}), id: jobId, jobId };
1167
- if (runtime.mode === "backward" && !runtime.goalNodeId) {
1173
+ if (plannerMode === "backward" && !plannerGoalNodeId) {
1168
1174
  const err = new Error(`BACKWARD_GOAL_REQUIRED: mode=backward requires goalNodeId`);
1169
1175
  err.code = "BACKWARD_GOAL_REQUIRED";
1170
1176
  throw err;
1171
1177
  }
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);
1178
+ const resolvedGraphId = String(plan.source.graphId);
1177
1179
  const runLogxer = createGraphEngineLogxer({ logging: merged.logging });
1180
+ const executionTrace = createExecutionTrace(plan, runtime);
1181
+ appendExecutionEvent(executionTrace, {
1182
+ id: `evt:${graphTaskId}:graph.started`,
1183
+ ts: new Date().toISOString(),
1184
+ level: "info",
1185
+ type: "graph.started",
1186
+ message: "Graph execution started.",
1187
+ });
1188
+ const planAudit = computePlanAudit(plan);
1178
1189
  return runWithAiTasksStackLogging(merged.logging, () => runGraphWithLogContext({ jobId, taskId: graphTaskId, graphId: resolvedGraphId, runId: graphTaskId }, async () => {
1179
1190
  bindGraphEngineRunLogxer(runLogxer);
1180
1191
  try {
1181
- assertCanonicalGraphDocument(graph, { jobId, graphId: resolvedGraphId }, { mode: 'execute' });
1182
1192
  assertCanonicalGraphRuntimeObject(runtime, { jobId, graphId: resolvedGraphId });
1183
1193
  let runxClient = opts.runx;
1184
- if (graphNeedsRunxClient(graph)) {
1194
+ if (planNeedsRunx(plan)) {
1185
1195
  if (!runxClient) {
1186
1196
  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.");
1197
+ const err = new Error("RUNX_REQUIRED: executable plan deferred gates require runx. Pass runx or runxCreateOptions on createExellixGraphRuntime.");
1188
1198
  err.code = "RUNX_REQUIRED";
1189
1199
  throw err;
1190
1200
  }
@@ -1198,10 +1208,6 @@ export function createExellixGraphRuntime(opts) {
1198
1208
  await runxClient.reload();
1199
1209
  }
1200
1210
  }
1201
- const graphAudit = {
1202
- source: 'model',
1203
- contentSha256: computeGraphDocumentContentSha256(suppliedGraph),
1204
- };
1205
1211
  const graphDocumentModel = mergeGraphDocumentModel(graph);
1206
1212
  const graphExecution = graphDocumentModel.graphExecution;
1207
1213
  const nodeResponseKeys = resolveNodeResponseKeys(graphExecution);
@@ -1215,8 +1221,8 @@ export function createExellixGraphRuntime(opts) {
1215
1221
  const { finalizer } = validateGraphFinalizer(graph);
1216
1222
  const engine = opts.engineFactory.create({
1217
1223
  graph,
1218
- mode: runtime.mode,
1219
- goalNodeId: runtime.goalNodeId,
1224
+ mode: plannerMode,
1225
+ goalNodeId: plannerGoalNodeId,
1220
1226
  dimension: runtime.dimension,
1221
1227
  initialState: runtime.initialState,
1222
1228
  initialVariables: runtime.initialVariables,
@@ -1226,19 +1232,15 @@ export function createExellixGraphRuntime(opts) {
1226
1232
  const errors = [];
1227
1233
  const finalizerNodeId = String(finalizer.id);
1228
1234
  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);
1235
+ const hasConditionalEdges = hasPlanConditionalEdges(plan);
1232
1236
  const incomingByTo = new Map();
1233
1237
  if (hasConditionalEdges) {
1234
- for (const e of rawEdges) {
1235
- if (!e || typeof e !== "object")
1236
- continue;
1237
- const to = e.to;
1238
+ for (const edgeRef of plan.topology.edges) {
1239
+ const to = edgeRef.to.nodeId;
1238
1240
  if (typeof to !== "string" || to.length === 0)
1239
1241
  continue;
1240
1242
  const list = incomingByTo.get(to) ?? [];
1241
- list.push(e);
1243
+ list.push(edgeRef);
1242
1244
  incomingByTo.set(to, list);
1243
1245
  }
1244
1246
  }
@@ -1397,50 +1399,67 @@ export function createExellixGraphRuntime(opts) {
1397
1399
  ? currentExecution.input
1398
1400
  : {};
1399
1401
  }
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;
1402
+ const entryGateEv = await evaluatePlanEntryGates(plan.deferredGates.entryGates, recordForStructuredDataFilters(), {
1403
+ executionMemory: currentExecution,
1404
+ jobMemory: currentJobMemory,
1405
+ taskMemory: currentTaskMemory,
1406
+ job,
1407
+ }, runxClient);
1408
+ if (!entryGateEv.ok) {
1409
+ const err = new Error("GRAPH_ENTRY_DATA_FILTERS_REJECTED: plan entry gates rejected runtime.executionMemory.input");
1410
+ err.code = ExellixGraphErrorCode.GRAPH_ENTRY_DATA_FILTERS_REJECTED;
1411
+ logGraphEngineErrorCode(ExellixGraphErrorCode.GRAPH_ENTRY_DATA_FILTERS_REJECTED, err.message, {
1412
+ graphId: resolvedGraphId,
1413
+ jobId,
1414
+ taskId: graphTaskId,
1415
+ error: err,
1416
+ });
1417
+ const finalOutput = buildFinalOutputFromGraphResponse();
1418
+ appendExecutionEvent(executionTrace, {
1419
+ id: `evt:${graphTaskId}:graph.failed`,
1420
+ ts: new Date().toISOString(),
1421
+ level: "error",
1422
+ type: "graph.failed",
1423
+ message: err.message,
1424
+ });
1425
+ const result = {
1426
+ jobId,
1427
+ taskId: graphTaskId,
1428
+ graphId: resolvedGraphId,
1429
+ status: "failed",
1430
+ outputsByNodeId,
1431
+ stepsResponses,
1432
+ engineSnapshot: engine.snapshot(),
1433
+ ...(finalOutput !== undefined ? { finalOutput } : {}),
1434
+ errors: [...errors, { error: err }],
1435
+ planAudit,
1436
+ trace: executionTrace,
1437
+ ...finalizeGraphPayload("failed"),
1438
+ };
1439
+ const debug = buildDebugTrace();
1440
+ if (debug)
1441
+ result.debug = debug;
1442
+ if (eventEmitter) {
1443
+ eventEmitter.emit(createGraphFailEvent(jobId, resolvedGraphId, graphTaskId, err, {
1444
+ finalMemory: { jobMemory: currentJobMemory, taskMemory: currentTaskMemory, execution: currentExecution, outputsMemory: currentOutputsMemory },
1445
+ ...(jobCorrelation ?? {}),
1446
+ }));
1437
1447
  }
1448
+ return result;
1438
1449
  }
1439
1450
  while (true) {
1440
- const plan = engine.plan();
1441
- if (plan.status === "completed") {
1451
+ const wavePlan = engine.plan();
1452
+ if (wavePlan.status === "completed") {
1442
1453
  const graphStatus = errors.length ? "failed" : "completed";
1443
1454
  const finalOutput = buildFinalOutputFromGraphResponse();
1455
+ appendExecutionEvent(executionTrace, {
1456
+ id: `evt:${graphTaskId}:graph.${graphStatus}`,
1457
+ ts: new Date().toISOString(),
1458
+ level: graphStatus === "completed" ? "info" : "error",
1459
+ type: graphStatus === "completed" ? "graph.completed" : "graph.failed",
1460
+ message: `Graph execution ${graphStatus}.`,
1461
+ });
1462
+ validateExecutionTrace(executionTrace, plan);
1444
1463
  const result = {
1445
1464
  jobId,
1446
1465
  taskId: graphTaskId,
@@ -1452,7 +1471,8 @@ export function createExellixGraphRuntime(opts) {
1452
1471
  ...(finalOutput !== undefined ? { finalOutput } : {}),
1453
1472
  ...(errors.length === 0 ? { finalizerNodeId, finalizerType: finalizer.finalizerType } : {}),
1454
1473
  errors: errors.length ? errors : undefined,
1455
- graphAudit,
1474
+ planAudit,
1475
+ trace: executionTrace,
1456
1476
  ...finalizeGraphPayload(graphStatus),
1457
1477
  };
1458
1478
  const debug = buildDebugTrace();
@@ -1476,8 +1496,8 @@ export function createExellixGraphRuntime(opts) {
1476
1496
  }
1477
1497
  return result;
1478
1498
  }
1479
- if (plan.status !== "continue") {
1480
- const err = new Error(`GRAPH_BLOCKED: status=${plan.status}`);
1499
+ if (wavePlan.status !== "continue") {
1500
+ const err = new Error(`GRAPH_BLOCKED: status=${wavePlan.status}`);
1481
1501
  err.code = "GRAPH_BLOCKED";
1482
1502
  const finalOutput = buildFinalOutputFromGraphResponse();
1483
1503
  const result = {
@@ -1490,7 +1510,8 @@ export function createExellixGraphRuntime(opts) {
1490
1510
  engineSnapshot: engine.snapshot(),
1491
1511
  ...(finalOutput !== undefined ? { finalOutput } : {}),
1492
1512
  errors: [...errors, { error: err }],
1493
- graphAudit,
1513
+ planAudit,
1514
+ trace: executionTrace,
1494
1515
  ...finalizeGraphPayload("failed"),
1495
1516
  };
1496
1517
  const debug = buildDebugTrace();
@@ -1504,7 +1525,7 @@ export function createExellixGraphRuntime(opts) {
1504
1525
  }
1505
1526
  return result;
1506
1527
  }
1507
- let runnableNodes = plan.nextNodes ?? [];
1528
+ let runnableNodes = wavePlan.nextNodes ?? [];
1508
1529
  // Conditional edge filtering: a node with incoming edges that are *all* conditional must
1509
1530
  // have at least one incoming edge whose `when` evaluates true to be runnable this round.
1510
1531
  // Roots and nodes with at least one unconditional incoming edge always pass through.
@@ -1521,7 +1542,7 @@ export function createExellixGraphRuntime(opts) {
1521
1542
  const incoming = incomingByTo.get(id) ?? [];
1522
1543
  if (incoming.length === 0)
1523
1544
  return true;
1524
- const hasUnconditional = incoming.some((e) => e?.when == null);
1545
+ const hasUnconditional = incoming.some((e) => !e?.hasDeferredGate);
1525
1546
  if (hasUnconditional)
1526
1547
  return true;
1527
1548
  const planningContext = buildPredicateEvalContextForNode({
@@ -1531,7 +1552,10 @@ export function createExellixGraphRuntime(opts) {
1531
1552
  node: n,
1532
1553
  runtimeTaskVariables: runtime.taskVariables,
1533
1554
  });
1534
- return incoming.some((e) => evaluateGraphPredicate(e.when, planningContext));
1555
+ return incoming.some((edgeRef) => {
1556
+ const gate = plan.deferredGates.edgeGates[edgeRef.edgeId];
1557
+ return evaluateDeferredEdgeGate(gate, planningContext);
1558
+ });
1535
1559
  });
1536
1560
  }
1537
1561
  const dataFiltersRecord = recordForStructuredDataFilters();
@@ -1555,7 +1579,7 @@ export function createExellixGraphRuntime(opts) {
1555
1579
  node: n,
1556
1580
  runtimeTaskVariables: runtime.taskVariables,
1557
1581
  });
1558
- const condEv = await evaluateTaskNodeConditions(n.conditions, { ...conditionCtxBase, ...nodePredicateCtx }, dataFiltersRecord, { runx: runxClient });
1582
+ const condEv = await evaluateDeferredNodeGate(nodeDeferredGate(plan, String(n.id)), { ...conditionCtxBase, ...nodePredicateCtx }, dataFiltersRecord, runxClient);
1559
1583
  if (!condEv.ok) {
1560
1584
  skippedByConditions.push({
1561
1585
  node: n,
@@ -1605,27 +1629,32 @@ export function createExellixGraphRuntime(opts) {
1605
1629
  context: { model: graph, runtime, graphId: resolvedGraphId, jobId, taskId: graphTaskId, node: node },
1606
1630
  });
1607
1631
  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,
1632
+ const nodeIdStr = String(node.id);
1633
+ appendExecutionEvent(executionTrace, {
1634
+ id: `evt:${graphTaskId}:node.started:${nodeIdStr}`,
1635
+ ts: new Date().toISOString(),
1636
+ level: "info",
1637
+ type: "node.started",
1638
+ nodeId: nodeIdStr,
1623
1639
  });
1640
+ const nodePlanEntryResolved = nodePlanForId(plan, nodeIdStr);
1641
+ const finalizerPlanEntryResolved = node.type === "finalizer"
1642
+ ? finalizerPlanForId(plan, nodeIdStr)
1643
+ : undefined;
1644
+ const taskNodeForExecute = nodePlanEntryResolved && node.type !== "finalizer"
1645
+ ? applyNodePlanInvoke(node, nodePlanEntryResolved)
1646
+ : node;
1647
+ const effectiveModelConfig = finalizerPlanEntryResolved != null
1648
+ ? graphAiModelConfigFromFinalizerPlan(finalizerPlanEntryResolved)
1649
+ : nodePlanEntryResolved && node.type !== "finalizer"
1650
+ ? graphAiModelConfigFromNodePlan(nodePlanEntryResolved)
1651
+ : undefined;
1652
+ const nodeExecutionPipeline = nodePlanEntryResolved != null ? executionPipelineFromNodePlan(nodePlanEntryResolved) : undefined;
1624
1653
  const r = await executeNode({
1625
1654
  graphId: resolvedGraphId,
1626
1655
  graph,
1627
1656
  graphRunTaskId: graphTaskId,
1628
- node,
1657
+ node: taskNodeForExecute,
1629
1658
  job,
1630
1659
  jobMemory: currentJobMemory,
1631
1660
  taskMemory: nodeTaskMemory,
@@ -1639,7 +1668,7 @@ export function createExellixGraphRuntime(opts) {
1639
1668
  runTaskIdentity: merged.runTaskIdentity,
1640
1669
  runTaskExecutionMode: merged.runTaskExecutionMode,
1641
1670
  runTaskDiagnostics: merged.runTaskDiagnostics,
1642
- graphExecutionPipeline: merged.executionPipeline,
1671
+ graphExecutionPipeline: nodeExecutionPipeline ?? merged.executionPipeline,
1643
1672
  skillKeyResolution: merged.skillKeyResolution,
1644
1673
  nodeTimeoutMs: merged.nodeTimeoutMs,
1645
1674
  clearSynthesizedContextPerNode: merged.clearSynthesizedContextPerNode,
@@ -1651,6 +1680,13 @@ export function createExellixGraphRuntime(opts) {
1651
1680
  outputsByNodeId[r.nodeId] = r.output;
1652
1681
  appendStepResponse(node, r.output);
1653
1682
  engine.commit({ nodeId: r.nodeId, output: r.output });
1683
+ appendExecutionEvent(executionTrace, {
1684
+ id: `evt:${graphTaskId}:node.completed:${nodeIdStr}`,
1685
+ ts: new Date().toISOString(),
1686
+ level: "info",
1687
+ type: "node.completed",
1688
+ nodeId: nodeIdStr,
1689
+ });
1654
1690
  const trl = r.taskRunLog;
1655
1691
  if (trl?.length)
1656
1692
  taskRunLogBuffer.push(...trl);
@@ -1722,7 +1758,8 @@ export function createExellixGraphRuntime(opts) {
1722
1758
  engineSnapshot: engine.snapshot(),
1723
1759
  ...(finalOutput !== undefined ? { finalOutput } : {}),
1724
1760
  errors,
1725
- graphAudit,
1761
+ planAudit,
1762
+ trace: executionTrace,
1726
1763
  ...finalizeGraphPayload("failed"),
1727
1764
  };
1728
1765
  const debug = buildDebugTrace();
@@ -2,7 +2,7 @@
2
2
  * Host helpers for execution-matrix style orchestrators (e.g. `@exellix/exellix-runtime`).
3
3
  *
4
4
  * Graph-engine does not call matrix claim APIs; the host injects `executeGraph` and supplies
5
- * a `{ model, runtime }` request with per-graph `GraphEntryContract` + seeded
5
+ * a `{ plan, runtime }` request with per-graph `GraphEntryContract` + seeded
6
6
  * `runtime.executionMemory` objects built here.
7
7
  */
8
8
  import type { Graph, GraphEntryContract, GraphEntryExecutionInputSpec, Job } from '../types/refs.js';
@@ -2,9 +2,10 @@
2
2
  * Host helpers for execution-matrix style orchestrators (e.g. `@exellix/exellix-runtime`).
3
3
  *
4
4
  * Graph-engine does not call matrix claim APIs; the host injects `executeGraph` and supplies
5
- * a `{ model, runtime }` request with per-graph `GraphEntryContract` + seeded
5
+ * a `{ plan, runtime }` request with per-graph `GraphEntryContract` + seeded
6
6
  * `runtime.executionMemory` objects built here.
7
7
  */
8
+ import { compileExellixExecutablePlan } from '../compile/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 '../compile/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
+ }
@@ -680,7 +680,7 @@ export interface GraphDocumentMetadata {
680
680
  graphResponse?: GraphResponseContract;
681
681
  /**
682
682
  * Graph execution defaults and labels.
683
- * Planner mode is still selected by `executeGraph({ model, runtime }).runtime.mode` and defaults to `forward`;
683
+ * Planner mode is still selected by `executeGraph({ plan, runtime }).runtime.mode` (or plan.schedulingPolicy) and defaults to `forward`;
684
684
  * output labels in this block shape `stepsResponses`, not `ExecuteGraphResult.finalOutput`.
685
685
  */
686
686
  graphExecution?: GraphExecutionDefaults;
@@ -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/compile/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.1",
4
4
  "type": "module",
5
5
  "description": "Graph executor SDK",
6
6
  "main": "dist/src/index.js",
@@ -58,7 +58,14 @@
58
58
  "@x12i/env": "4.0.1",
59
59
  "@x12i/funcx": "4.4.4",
60
60
  "@x12i/graphenix": "2.5.0",
61
+ "@x12i/graphenix-authoring-format": "^1.0.2",
62
+ "@x12i/graphenix-executable-contracts": "^1.0.0",
63
+ "@x12i/graphenix-executable-format": "^2.0.1",
64
+ "@x12i/graphenix-execute-envelope": "^1.0.0",
61
65
  "@x12i/graphenix-format": "2.0.0",
66
+ "@x12i/graphenix-plan-compiler": "^1.0.1",
67
+ "@x12i/graphenix-plan-format": "^1.0.0",
68
+ "@x12i/graphenix-trace-format": "^1.0.0",
62
69
  "@x12i/logxer": "^4.6.0",
63
70
  "@x12i/memorix-descriptors": "1.6.0",
64
71
  "@x12i/memorix-retrieval": "1.11.2",