@exellix/graph-engine 6.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.
- package/.env.example +3 -0
- package/CHANGELOG.md +208 -0
- package/README.md +827 -0
- package/dist/src/errors/ExellixGraphError.d.ts +38 -0
- package/dist/src/errors/ExellixGraphError.js +21 -0
- package/dist/src/errors/exellixGraphErrorCodes.d.ts +31 -0
- package/dist/src/errors/exellixGraphErrorCodes.js +32 -0
- package/dist/src/index.d.ts +100 -0
- package/dist/src/index.js +75 -0
- package/dist/src/inspection/contractInspection.d.ts +21 -0
- package/dist/src/inspection/contractInspection.js +526 -0
- package/dist/src/inspection/contractTypes.d.ts +137 -0
- package/dist/src/inspection/contractTypes.js +1 -0
- package/dist/src/inspection/controlInspection.d.ts +22 -0
- package/dist/src/inspection/controlInspection.js +130 -0
- package/dist/src/inspection/graphInspection.d.ts +51 -0
- package/dist/src/inspection/graphInspection.js +467 -0
- package/dist/src/inspection/index.d.ts +21 -0
- package/dist/src/inspection/index.js +17 -0
- package/dist/src/inspection/nodeInspection.d.ts +42 -0
- package/dist/src/inspection/nodeInspection.js +474 -0
- package/dist/src/inspection/types.d.ts +321 -0
- package/dist/src/inspection/types.js +14 -0
- package/dist/src/inspection/validateAiTasksNodeExtensions.d.ts +12 -0
- package/dist/src/inspection/validateAiTasksNodeExtensions.js +119 -0
- package/dist/src/inspection/validateCatalogPlanning.d.ts +21 -0
- package/dist/src/inspection/validateCatalogPlanning.js +187 -0
- package/dist/src/integrations/ActivityTrackerIntegration.d.ts +86 -0
- package/dist/src/integrations/ActivityTrackerIntegration.js +134 -0
- package/dist/src/integrations/ActivixGraphRunIntegration.d.ts +34 -0
- package/dist/src/integrations/ActivixGraphRunIntegration.js +338 -0
- package/dist/src/integrations/ActivixNodeActivityIntegration.d.ts +33 -0
- package/dist/src/integrations/ActivixNodeActivityIntegration.js +220 -0
- package/dist/src/integrations/cataloxGraphCatalog.d.ts +21 -0
- package/dist/src/integrations/cataloxGraphCatalog.js +30 -0
- package/dist/src/integrations/createActivixExellixIntegration.d.ts +14 -0
- package/dist/src/integrations/createActivixExellixIntegration.js +16 -0
- package/dist/src/integrations/createActivixFromEnv.d.ts +31 -0
- package/dist/src/integrations/createActivixFromEnv.js +53 -0
- package/dist/src/loaders/FileGraphLoader.d.ts +23 -0
- package/dist/src/loaders/FileGraphLoader.js +31 -0
- package/dist/src/playground/PlaygroundReporter.d.ts +40 -0
- package/dist/src/playground/PlaygroundReporter.js +480 -0
- package/dist/src/playground/index.d.ts +1 -0
- package/dist/src/playground/index.js +1 -0
- package/dist/src/runtime/ExellixGraphRuntime.d.ts +263 -0
- package/dist/src/runtime/ExellixGraphRuntime.js +1716 -0
- package/dist/src/runtime/GraphEngine.d.ts +33 -0
- package/dist/src/runtime/GraphEngine.js +4 -0
- package/dist/src/runtime/aiTasksObservability.d.ts +6 -0
- package/dist/src/runtime/aiTasksObservability.js +37 -0
- package/dist/src/runtime/aiTasksStrategyPhases.d.ts +46 -0
- package/dist/src/runtime/aiTasksStrategyPhases.js +93 -0
- package/dist/src/runtime/applyAiTaskProfileWebScopingToNarrix.d.ts +17 -0
- package/dist/src/runtime/applyAiTaskProfileWebScopingToNarrix.js +46 -0
- package/dist/src/runtime/buildAiTasksRunTaskRequest.d.ts +67 -0
- package/dist/src/runtime/buildAiTasksRunTaskRequest.js +164 -0
- package/dist/src/runtime/buildRunLog.d.ts +27 -0
- package/dist/src/runtime/buildRunLog.js +234 -0
- package/dist/src/runtime/buildRunTaskTaskConfigurationForward.d.ts +9 -0
- package/dist/src/runtime/buildRunTaskTaskConfigurationForward.js +80 -0
- package/dist/src/runtime/buildTaskNodeJobContext.d.ts +11 -0
- package/dist/src/runtime/buildTaskNodeJobContext.js +30 -0
- package/dist/src/runtime/canonicalModelUsed.d.ts +6 -0
- package/dist/src/runtime/canonicalModelUsed.js +36 -0
- package/dist/src/runtime/contextualScope.d.ts +7 -0
- package/dist/src/runtime/contextualScope.js +121 -0
- package/dist/src/runtime/dataFiltersEvaluation.d.ts +60 -0
- package/dist/src/runtime/dataFiltersEvaluation.js +169 -0
- package/dist/src/runtime/deepMerge.d.ts +5 -0
- package/dist/src/runtime/deepMerge.js +22 -0
- package/dist/src/runtime/events.d.ts +92 -0
- package/dist/src/runtime/events.js +122 -0
- package/dist/src/runtime/executionMatrixHost.d.ts +98 -0
- package/dist/src/runtime/executionMatrixHost.js +134 -0
- package/dist/src/runtime/executionVariableBuckets.d.ts +67 -0
- package/dist/src/runtime/executionVariableBuckets.js +96 -0
- package/dist/src/runtime/finalizers/errors.d.ts +9 -0
- package/dist/src/runtime/finalizers/errors.js +10 -0
- package/dist/src/runtime/finalizers/executeFinalizer.d.ts +40 -0
- package/dist/src/runtime/finalizers/executeFinalizer.js +471 -0
- package/dist/src/runtime/finalizers/schema.d.ts +18 -0
- package/dist/src/runtime/finalizers/schema.js +63 -0
- package/dist/src/runtime/finalizers/validateFinalizer.d.ts +16 -0
- package/dist/src/runtime/finalizers/validateFinalizer.js +534 -0
- package/dist/src/runtime/graphDocumentFingerprint.d.ts +8 -0
- package/dist/src/runtime/graphDocumentFingerprint.js +21 -0
- package/dist/src/runtime/graphEngineMemoryPaths.d.ts +12 -0
- package/dist/src/runtime/graphEngineMemoryPaths.js +55 -0
- package/dist/src/runtime/graphResponseMapping.d.ts +23 -0
- package/dist/src/runtime/graphResponseMapping.js +156 -0
- package/dist/src/runtime/graphResponseMigration.d.ts +7 -0
- package/dist/src/runtime/graphResponseMigration.js +44 -0
- package/dist/src/runtime/graphRunExecutionSeed.d.ts +29 -0
- package/dist/src/runtime/graphRunExecutionSeed.js +61 -0
- package/dist/src/runtime/graphRunIdentity.d.ts +7 -0
- package/dist/src/runtime/graphRunIdentity.js +18 -0
- package/dist/src/runtime/localSkills/deterministicRule.d.ts +137 -0
- package/dist/src/runtime/localSkills/deterministicRule.js +196 -0
- package/dist/src/runtime/localSkills/index.d.ts +12 -0
- package/dist/src/runtime/localSkills/index.js +14 -0
- package/dist/src/runtime/localSkills/memorixItemToScopedOutput.d.ts +7 -0
- package/dist/src/runtime/localSkills/memorixItemToScopedOutput.js +104 -0
- package/dist/src/runtime/localSkills/memorixRuntime.d.ts +9 -0
- package/dist/src/runtime/localSkills/memorixRuntime.js +70 -0
- package/dist/src/runtime/localSkills/memorixScopedConfig.d.ts +16 -0
- package/dist/src/runtime/localSkills/memorixScopedConfig.js +18 -0
- package/dist/src/runtime/localSkills/scopedAnswerAssembler.d.ts +23 -0
- package/dist/src/runtime/localSkills/scopedAnswerAssembler.js +35 -0
- package/dist/src/runtime/localSkills/scopedAnswerFields.d.ts +12 -0
- package/dist/src/runtime/localSkills/scopedAnswerFields.js +66 -0
- package/dist/src/runtime/localSkills/scopedAnswerWriter.d.ts +32 -0
- package/dist/src/runtime/localSkills/scopedAnswerWriter.js +156 -0
- package/dist/src/runtime/localSkills/scopedDataReader.d.ts +47 -0
- package/dist/src/runtime/localSkills/scopedDataReader.js +89 -0
- package/dist/src/runtime/localSkills/utils.d.ts +12 -0
- package/dist/src/runtime/localSkills/utils.js +39 -0
- package/dist/src/runtime/materializeStructuredRunTaskInput.d.ts +9 -0
- package/dist/src/runtime/materializeStructuredRunTaskInput.js +34 -0
- package/dist/src/runtime/memory.d.ts +51 -0
- package/dist/src/runtime/memory.js +250 -0
- package/dist/src/runtime/mergeExellixGraphRuntimeInvocation.d.ts +18 -0
- package/dist/src/runtime/mergeExellixGraphRuntimeInvocation.js +32 -0
- package/dist/src/runtime/modelConfigSelection.d.ts +7 -0
- package/dist/src/runtime/modelConfigSelection.js +37 -0
- package/dist/src/runtime/narrixIngestEnv.d.ts +9 -0
- package/dist/src/runtime/narrixIngestEnv.js +18 -0
- package/dist/src/runtime/pathExpr.d.ts +36 -0
- package/dist/src/runtime/pathExpr.js +131 -0
- package/dist/src/runtime/predicates.d.ts +14 -0
- package/dist/src/runtime/predicates.js +86 -0
- package/dist/src/runtime/readTaskNodeInputsConfig.d.ts +23 -0
- package/dist/src/runtime/readTaskNodeInputsConfig.js +27 -0
- package/dist/src/runtime/resolveExecutionPipelineForTaskNode.d.ts +11 -0
- package/dist/src/runtime/resolveExecutionPipelineForTaskNode.js +93 -0
- package/dist/src/runtime/resolveGraphEngineMemoryPaths.d.ts +63 -0
- package/dist/src/runtime/resolveGraphEngineMemoryPaths.js +213 -0
- package/dist/src/runtime/resolveModelConfigForNode.d.ts +20 -0
- package/dist/src/runtime/resolveModelConfigForNode.js +69 -0
- package/dist/src/runtime/resolveNarrixForTaskNode.d.ts +14 -0
- package/dist/src/runtime/resolveNarrixForTaskNode.js +19 -0
- package/dist/src/runtime/resolveTaskKey.d.ts +11 -0
- package/dist/src/runtime/resolveTaskKey.js +28 -0
- package/dist/src/runtime/resolveTaskNodeInputs.d.ts +25 -0
- package/dist/src/runtime/resolveTaskNodeInputs.js +140 -0
- package/dist/src/runtime/runTaskAugments.d.ts +17 -0
- package/dist/src/runtime/runTaskAugments.js +37 -0
- package/dist/src/runtime/runTaskResponse.d.ts +4 -0
- package/dist/src/runtime/runTaskResponse.js +13 -0
- package/dist/src/runtime/runtimeObjects.d.ts +85 -0
- package/dist/src/runtime/runtimeObjects.js +50 -0
- package/dist/src/runtime/smartInputPaths.d.ts +13 -0
- package/dist/src/runtime/smartInputPaths.js +38 -0
- package/dist/src/runtime/stepRetry.d.ts +21 -0
- package/dist/src/runtime/stepRetry.js +238 -0
- package/dist/src/runtime/synthesizedContextPipeline.d.ts +12 -0
- package/dist/src/runtime/synthesizedContextPipeline.js +28 -0
- package/dist/src/runtime/taskNodeConditionsEvaluation.d.ts +27 -0
- package/dist/src/runtime/taskNodeConditionsEvaluation.js +140 -0
- package/dist/src/runtime/taskNodeMainReadiness.d.ts +45 -0
- package/dist/src/runtime/taskNodeMainReadiness.js +164 -0
- package/dist/src/runtime/taskNodeRunTaskPreflight.d.ts +89 -0
- package/dist/src/runtime/taskNodeRunTaskPreflight.js +204 -0
- package/dist/src/runtime/validateCanonicalGraphDocument.d.ts +25 -0
- package/dist/src/runtime/validateCanonicalGraphDocument.js +567 -0
- package/dist/src/runtime/variables.d.ts +2 -0
- package/dist/src/runtime/variables.js +1 -0
- package/dist/src/runtime/withTimeout.d.ts +5 -0
- package/dist/src/runtime/withTimeout.js +20 -0
- package/dist/src/types/aiTaskProfile.d.ts +41 -0
- package/dist/src/types/aiTaskProfile.js +6 -0
- package/dist/src/types/aiTasksDerivedTypes.d.ts +5 -0
- package/dist/src/types/aiTasksDerivedTypes.js +1 -0
- package/dist/src/types/events.d.ts +23 -0
- package/dist/src/types/events.js +1 -0
- package/dist/src/types/job.d.ts +9 -0
- package/dist/src/types/job.js +1 -0
- package/dist/src/types/narrix.d.ts +60 -0
- package/dist/src/types/narrix.js +1 -0
- package/dist/src/types/options.d.ts +122 -0
- package/dist/src/types/options.js +1 -0
- package/dist/src/types/refs.d.ts +747 -0
- package/dist/src/types/refs.js +12 -0
- package/dist/src/types/results.d.ts +103 -0
- package/dist/src/types/results.js +1 -0
- package/dist/src/types/runLog.d.ts +72 -0
- package/dist/src/types/runLog.js +18 -0
- package/dist/src/types/taskNodeConfiguration.d.ts +95 -0
- package/dist/src/types/taskNodeConfiguration.js +3 -0
- package/dist/src/util/packageVersion.d.ts +2 -0
- package/dist/src/util/packageVersion.js +12 -0
- package/dist/testkit/RealTasksClient.d.ts +16 -0
- package/dist/testkit/RealTasksClient.js +143 -0
- package/dist/testkit/depGraphEngineFactory.d.ts +6 -0
- package/dist/testkit/depGraphEngineFactory.js +54 -0
- package/dist/testkit/exellixRuntimeObjects.d.ts +7 -0
- package/dist/testkit/exellixRuntimeObjects.js +25 -0
- package/dist/testkit/inMemoryGraphLoader.d.ts +6 -0
- package/dist/testkit/inMemoryGraphLoader.js +12 -0
- package/dist/testkit/index.d.ts +4 -0
- package/dist/testkit/index.js +4 -0
- package/package.json +70 -0
package/README.md
ADDED
|
@@ -0,0 +1,827 @@
|
|
|
1
|
+
# `@exellix/graph-engine` — Clean Graph Executor SDK
|
|
2
|
+
|
|
3
|
+
A minimal, focused SDK for executing graphs in the exellix ecosystem.
|
|
4
|
+
|
|
5
|
+
## What this package does and does not
|
|
6
|
+
|
|
7
|
+
**In scope — what this package does:** On each **`createExellixGraphRuntime(...).executeGraph({ model, runtime })`** invocation, it runs **exactly one graph run** (validate model → plan → nodes → finalizer) until that run **completes or fails**, then returns the result. There is **no** batching, queueing, or multi-job orchestration inside this package — each call is one logical run. You supply the static **`model: GraphModelObject`**, planner (`GraphEngineFactory`), tasks client, and dynamic **`runtime: GraphRuntimeObject`** with the mandatory host `jobId`, `job` envelope, active `input`, memory, variables, and per-run options. The engine generates a fresh **`taskId`** (UUID) per invocation and sends it on **every** `runTask` request together with **`jobId`**. That is the **entire** product role of graph-engine.
|
|
8
|
+
|
|
9
|
+
**Out of scope — what this package does not do:** It does **not** schedule work, own **execution matrices**, manage **claims** or **rows**, persist **job** lifecycle, implement **retry/requeue policy**, or **track** runs across tenants or sessions. Optional helpers and docs for matrix *hosts* only help **build arguments** for the same single-run API; they do **not** move orchestration into this package. Integrations (e.g. Activix graph-run events) emit data **for that call** when you wire an `eventEmitter` — they do not make graph-engine a workflow or matrix service.
|
|
10
|
+
|
|
11
|
+
## Core Responsibilities
|
|
12
|
+
|
|
13
|
+
`exellix-graph-engine` does exactly this loop:
|
|
14
|
+
|
|
15
|
+
1. **Validate graph model:** The caller supplies **`model: GraphModelObject`** directly in the execution request. The effective correlation **`graphId`** on the result and in telemetry is **`model.id`**.
|
|
16
|
+
2. `plan = graphenix.plan(...)`
|
|
17
|
+
3. For each runnable node:
|
|
18
|
+
- Map node → `skillKey` (strict rules)
|
|
19
|
+
- If `skillKey` is a **local skill** (`scoped-data-reader`, `deterministic-rule`, `scoped-answer-writer`, `scoped-answer-assembler`), run it in-process (no `ai-tasks` call); otherwise call `ai-tasks.runTask(...)`
|
|
20
|
+
- Commit output into graphenix
|
|
21
|
+
4. Repeat until done/fail
|
|
22
|
+
|
|
23
|
+
**Nothing else** beyond orchestration, memory mapping, finalizers, and the small local-skill surface above — and **not** matrix/worker/queue ownership (see [above](#what-this-package-does-and-does-not)).
|
|
24
|
+
|
|
25
|
+
### Specification & documentation
|
|
26
|
+
|
|
27
|
+
| Topic | Document |
|
|
28
|
+
|--------|----------|
|
|
29
|
+
| **Object contracts** (model × runtime + per-call resolution view) | Index: [`formats-documentations/README.md`](formats-documentations/README.md) — [`graph-model`](formats-documentations/graph-model-object-format.md), [`graph-runtime`](formats-documentations/graph-runtime-object-format.md), [`task-node-model`](formats-documentations/task-node-model-object-format.md), [`task-node-runtime`](formats-documentations/task-node-runtime-object-format.md), [`ExellixRuntimeObject`](formats-documentations/exellix-runtime-object-format.md) |
|
|
30
|
+
| **Ecosystem acceptance** (ai-tasks, studio, matrix parity) | [`.docs/ecosystem-acceptance-criteria.md`](.docs/ecosystem-acceptance-criteria.md) |
|
|
31
|
+
| **Executable graph JSON** (top-level shape, task/finalizer nodes, edges, variables, `metadata.graphExecution`, canonical root enforcement, execution memory, local skills, Narrix / web scope (**forwarded `narrix` → `runTask`**), **graph JSON vs outbound `runTask`**) | **[`.docs/exellix-graph-engine-format.md`](.docs/exellix-graph-engine-format.md)** — start here for authors and schema tooling |
|
|
32
|
+
| Task node bridge (shell, `metadata`, `executionPipeline`, `aiTaskProfile` → Narrix web merge, composer alignment) | [`.docs/task-node-exellix-graph-engine-and-graph-composer.md`](.docs/task-node-exellix-graph-engine-and-graph-composer.md) |
|
|
33
|
+
| Layer 01 / 08 graph entry & response contracts | [`.docs/graph-io-visibility.md`](.docs/graph-io-visibility.md) |
|
|
34
|
+
| Graph entry `dataFilters` v1 / public evaluator | [`.docs/data-filters-evaluation.md`](.docs/data-filters-evaluation.md) |
|
|
35
|
+
| Task-node `conditions` + conditional `modelConfig.cases` (runx) | [`.docs/task-node-conditions-evaluation.md`](.docs/task-node-conditions-evaluation.md) |
|
|
36
|
+
| Platform vs implementation (no domain operators in schema) | [`.docs/platform-generic-vs-implementation.md`](.docs/platform-generic-vs-implementation.md) |
|
|
37
|
+
| Bundled graph examples & bundle README | [`graphs/README.md`](graphs/README.md) |
|
|
38
|
+
|
|
39
|
+
The **`runTask`** wire contract (identity, canonical **`input`**, optional extra ai-tasks PRE/POST utility calls vs `executionPipeline`) is summarized later under **Run identity** and **`runTask` request contract**, and expanded in the graph format doc’s [**Graph JSON vs outbound `runTask`**](.docs/exellix-graph-engine-format.md#graph-json-vs-outbound-runtask) section. Graph-engine integrates with **`@exellix/ai-tasks`** at the semver range declared in **`package.json`** (currently **`^7.6.4`**); use your **lockfile** as the tested line. Variable buckets align with ai-tasks **≥ 7.6.2** (two-scope passthrough). There is **no** minimum graph-engine ↔ ai-tasks matrix published inside ai-tasks — follow dependency semver and upstream **`CHANGELOG.md`**. Graph-engine does **not** import or invoke the Xynthesis SDK directly.
|
|
40
|
+
|
|
41
|
+
### Canonical executable graph document (strict boundary)
|
|
42
|
+
|
|
43
|
+
An executable graph model object may have **only** these **top-level** keys: `id`, `version`, `modelConfig`, `jobKnowledge`, `nodes`, `edges`, `variables`, `response`, `metadata`. The required root **`response`** is the single executable final response contract used to build `ExecuteGraphResult.finalOutput`. Document-model and authoring fields (`name`, `description`, `exellixContractTarget`, `graphExecution`, `graphEntry`, `catalogRequests`, and similar) belong **under `metadata`** only. Node-scoped `taskKnowledge` belongs under each task node, not at the model root. Runtime state (`input`, `jobMemory`, `taskMemory`, `executionMemory`, `outputsMemory`, `aliasConfig`, runtime node overrides) belongs under the execution request’s **`runtime`** object and is rejected on the model.
|
|
44
|
+
|
|
45
|
+
`metadata.graphExecution` can document graph execution defaults and labels, including `mode: 'forward' | 'backward' | 'hybrid'`, optional `goalNodeId`, optional `dimension`, `outputMode: 'mappedAggregation' | 'lastExitNode'`, `coreObjective`, optional `nodesResponses`, and metadata-only `flowOutline: 'linearSequence' | 'convergingParallelFlow'`. Planner mode still comes from `executeGraph({ model, runtime }).runtime.mode`; `outputMode` does not decide the returned `finalOutput`.
|
|
46
|
+
|
|
47
|
+
Enforcement: `executeGraph`, `createExellixGraphRuntime().executeGraph`, `executeNode` when a graph is passed in context, `inspectGraph`, `inspectGraphContracts`, `validateCatalogPlanning`, and Catalox graph validators call this check. Failures throw `ExellixGraphError` with code `NON_CANONICAL_GRAPH_DOCUMENT`. For CI or custom loaders, call **`assertCanonicalGraphDocument`** (exported from the package root) on parsed JSON before execution. **`loadGraph`** returns whatever the loader parsed; validation runs when you execute or inspect, not necessarily on load.
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm install @exellix/graph-engine
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Upstream tasks SDK:** This package depends on **`@exellix/ai-tasks` ^7.4.0** (see **`package.json`**). Graph-engine emits **`RunTaskRequest`** shapes that match the v7 closed schema (mandatory **`executionStrategies`**, **`xynthesized`**, optional **`smartInput`**, no legacy root mirrors). Pin compatible versions in your app lockfile. Optional CI improvement: fail or warn when the resolved ai-tasks major/minor drifts outside an allowlist (not enforced in-repo today).
|
|
56
|
+
|
|
57
|
+
### Execution matrix hosts (`@exellix/exellix-runtime`) — documentation only for the engine
|
|
58
|
+
|
|
59
|
+
Matrix **claim**, **rows**, and **retry policy** live outside this package. If your host wires **matrix → graph run**, see [.docs/execution-matrix-handoff.md](.docs/execution-matrix-handoff.md) for how **you** should inject `runtime.executeGraph`, resolve **`metadata.graphEntry` per model**, seed **`runtime.executionMemory`**, and pass **`runtime.jobId`** (see [Run identity](#run-identity-host-jobid-and-engine-taskid) below). Helpers such as **`buildMatrixJobForGraphRun`** align **`id`** and **`job.jobId`** on the `job` object so you can call **`runtime.executeGraph({ model, runtime: { jobId: job.jobId, job, … } })`**. Those exports are **optional helpers for callers**; they do **not** expand graph-engine’s role beyond **executing the single run** when invoked.
|
|
60
|
+
|
|
61
|
+
### Configuration (`.env`)
|
|
62
|
+
|
|
63
|
+
- **Template:** [`.env.example`](.env.example) — copy to `.env` at the project root for local tests and scripts that load `dotenv`.
|
|
64
|
+
- **Semantics:** [`.docs/environment-and-xmemory-databases.md`](.docs/environment-and-xmemory-databases.md) (must-have vs nice-to-have, Mongo / xmemory / Narrix / Activix).
|
|
65
|
+
- **Resolvable path after install:** subpath export `@exellix/graph-engine/env.example` points at the same file (e.g. `require.resolve('@exellix/graph-engine/env.example')` in Node).
|
|
66
|
+
|
|
67
|
+
### Package entrypoints
|
|
68
|
+
|
|
69
|
+
- **`@exellix/graph-engine`** — Graph executor, types, loaders, integrations (platform).
|
|
70
|
+
- **`@exellix/graph-engine/testkit`** — Harness helpers (`InMemoryGraphLoader`, `DepGraphEngineFactory`, `RealTasksClient`) and **sample** `registerNarrixGraphTasks` for repo graphs that still call `narrix/load-input`, `narrix/to-cni`, etc. This is not re-exported from the root entry.
|
|
71
|
+
|
|
72
|
+
### Catalox and graph planning catalog IDs
|
|
73
|
+
|
|
74
|
+
Skill templates and Catalox wiring for packaged runs live in **`@exellix/ai-tasks`** (and its upstream stack). This package only offers an optional **planning-descriptor** check when you already have a Catalox client:
|
|
75
|
+
|
|
76
|
+
- **`validateGraphPlanningCatalogDescriptorsInCatalox(graph, catalox, ctx)`** — For every `catalogId` collected from `metadata.catalogBinding` / planning metadata (see [`getGraphCatalogs`](src/inspection/graphInspection.ts)), asserts `catalox.getCatalogDescriptor(ctx, catalogId)` is non-null.
|
|
77
|
+
|
|
78
|
+
For Firebase Admin, `createCataloxFromEnv`, `listAiSkillsCatalogItems`, and related helpers, import from **`@exellix/ai-tasks`** or the graph-engine re-exports below instead of adding `@exellix/ai-skills` here.
|
|
79
|
+
|
|
80
|
+
**`taskConfiguration.aiTasksOutputValidation`** (shape: `{ schema, mode?, validateWhenMissing? }`) is forwarded on `runTask` as `outputValidation` for server-side checks in `@exellix/ai-tasks`. The top-level node field **`outputValidation`** with **`rules`** remains a **local** post-check in `executeNode` only.
|
|
81
|
+
|
|
82
|
+
### Task-node preflight (validation & analysis, no `runTask`)
|
|
83
|
+
|
|
84
|
+
Graph-engine exposes the **`@exellix/ai-tasks` ≥ 7.6** preflight surface so studios and matrix hosts can validate a node **before** execution without a second dependency:
|
|
85
|
+
|
|
86
|
+
| Export | Purpose |
|
|
87
|
+
|--------|---------|
|
|
88
|
+
| **`buildTaskNodeRunTaskRequest`** | Build the same outbound `RunTaskRequest` as `executeNode` (skips local skills and finalizers). |
|
|
89
|
+
| **`validateTaskNodeRunTaskConfig`** | Static config checks (`agentId`, pipeline, `smartInput`, `llmCall`, …). |
|
|
90
|
+
| **`validateTaskNodeRunTaskInvoke`** | Config + payload path resolution + optional template/smart-input render checks. |
|
|
91
|
+
| **`analyzeTaskNodeRunTaskRequest`** | Catalox-backed skill request analysis (templates, gateway packet preview) plus optional config validation. |
|
|
92
|
+
|
|
93
|
+
Lower-level helpers (`validateRunTaskConfig`, `validateRunTaskInvoke`, `analyzeRunTaskRequest`, `analyzeSkillRequest`, `formatSkillRequestAnalysisMarkdown`, Rendrix `listTokens` / `analyzeTemplateResolution`, …) are **re-exported** from the package root when you already have a `RunTaskRequest`.
|
|
94
|
+
|
|
95
|
+
**Testing safety:** Default `npm test` uses **mocked** Catalox in catalog validation tests and does not open Firestore. Do **not** point `FIRESTORE_LIVE_TESTS` / integration flags at a **production** Firebase project; Catalox’s own docs recommend a dedicated test project for live integration runs.
|
|
96
|
+
|
|
97
|
+
## Quick Start
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { createExellixGraphRuntime } from '@exellix/graph-engine';
|
|
101
|
+
|
|
102
|
+
const runtime = createExellixGraphRuntime({
|
|
103
|
+
graphLoader: myGraphLoader,
|
|
104
|
+
engineFactory: myEngineFactory, // GraphEngineFactory (e.g. DepGraphEngineFactory)
|
|
105
|
+
tasksClient: myTasksClient, // TasksClientLike — responses may use `ok` or `success` (both accepted)
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Host correlation id (required). Engine sets `job.id` / `job.jobId` from it and generates `result.taskId`.
|
|
109
|
+
const result = await runtime.executeGraph({
|
|
110
|
+
model: graphModel,
|
|
111
|
+
runtime: {
|
|
112
|
+
jobId: 'job-123',
|
|
113
|
+
job: { agentId: 'agent-1', input: {} },
|
|
114
|
+
input: { question: 'Analyze this record' },
|
|
115
|
+
modelConfig: { xynthesisModel: 'xynth-default', skillModel: 'skill-default' },
|
|
116
|
+
aliasConfig: {
|
|
117
|
+
'xynth-default': 'openrouter/xynthesis-default',
|
|
118
|
+
'skill-default': 'openrouter/skill-default',
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Canonical business output + per-run ids:
|
|
124
|
+
console.log(result.finalOutput, result.jobId, result.taskId);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Public API
|
|
128
|
+
|
|
129
|
+
### `runtime.executeGraph({ model, runtime })`
|
|
130
|
+
|
|
131
|
+
Execute a complete graph through the **single canonical client API**: `createExellixGraphRuntime(...)`. The runtime owns local-skill interception, conditional edge filtering, optional `eventEmitter`, optional `debugMode`, and produces one `ExecuteGraphResult` shape.
|
|
132
|
+
|
|
133
|
+
Every call requires a static **`model: GraphModelObject`** and a dynamic **`runtime: GraphRuntimeObject`**. `runtime.jobId` is mandatory and non-empty. The engine also generates a **`taskId`** (UUID) per invocation. Together they form the **identity** forwarded to **`@exellix/ai-tasks`** (`runTask({ jobId, taskId, … })`), graph/node **`eventEmitter`** payloads, structured **`runLog`**, and Activix **`runContext`** / record metadata.
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import { createExellixGraphRuntime } from '@exellix/graph-engine';
|
|
137
|
+
|
|
138
|
+
const runtime = createExellixGraphRuntime({
|
|
139
|
+
graphLoader,
|
|
140
|
+
engineFactory,
|
|
141
|
+
tasksClient,
|
|
142
|
+
modelConfig, // optional explicit fallback: { xynthesisModel, skillModel }
|
|
143
|
+
eventEmitter, // optional graph/node lifecycle events
|
|
144
|
+
playgroundReporter, // optional
|
|
145
|
+
// …stepRetryPolicy, runLogMode, concurrency, runTaskDiagnostics, etc.
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
interface GraphExecutionRequest {
|
|
149
|
+
model: GraphModelObject; // static graph definition
|
|
150
|
+
runtime: GraphRuntimeObject; // dynamic run state
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 2x2 object split:
|
|
154
|
+
// Graph model: GraphModelObject
|
|
155
|
+
// Graph runtime: GraphRuntimeObject
|
|
156
|
+
// Task-node model: TaskNode
|
|
157
|
+
// Task-node runtime: TaskNodeRuntimeObject at runtime.nodes[nodeId]
|
|
158
|
+
|
|
159
|
+
interface GraphRuntimeObject {
|
|
160
|
+
jobId: string; // required: host correlation id
|
|
161
|
+
job: any; // host envelope: agentId, input, jobType, …
|
|
162
|
+
input?: Record<string, any>; // active execution input
|
|
163
|
+
jobMemory?: any;
|
|
164
|
+
taskMemory?: any;
|
|
165
|
+
executionMemory?: any;
|
|
166
|
+
variables?: Record<string, any>;
|
|
167
|
+
modelConfig?: { xynthesisModel: string; skillModel: string };
|
|
168
|
+
aliasConfig?: Record<string, string>;
|
|
169
|
+
nodes?: Record<string, {
|
|
170
|
+
modelConfig?: { xynthesisModel: string; skillModel: string };
|
|
171
|
+
aliasConfig?: Record<string, string>;
|
|
172
|
+
}>;
|
|
173
|
+
mode?: 'forward' | 'backward' | 'hybrid';
|
|
174
|
+
goalNodeId?: string; // required when mode === 'backward'
|
|
175
|
+
debugMode?: boolean; // include per-node trace on result.debug
|
|
176
|
+
failFast?: boolean; // default: false
|
|
177
|
+
// …llmCall, stepRetryPolicy, runLogMode, runtimeObjects, runTaskDiagnostics, etc.
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
#### Return shape
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
interface ExecuteGraphResult {
|
|
185
|
+
jobId: string;
|
|
186
|
+
taskId: string;
|
|
187
|
+
graphId: string;
|
|
188
|
+
status: 'completed' | 'failed';
|
|
189
|
+
finalOutput?: unknown;
|
|
190
|
+
finalizerNodeId?: string;
|
|
191
|
+
finalizerType?: string;
|
|
192
|
+
outputsByNodeId: Record<string, unknown>;
|
|
193
|
+
stepsResponses: Record<string, unknown>[];
|
|
194
|
+
engineSnapshot: unknown;
|
|
195
|
+
errors?: Array<{ nodeId?: string; error: unknown }>;
|
|
196
|
+
execution?: unknown; // includes _trace.nodes
|
|
197
|
+
runLog?: RunLogEntry[];
|
|
198
|
+
runLogTruncated?: boolean;
|
|
199
|
+
runLogOmittedCount?: number;
|
|
200
|
+
logxerCorrelationId?: string;
|
|
201
|
+
debug?: { nodes: NodeTraceEntry[] }; // populated only when debugMode: true
|
|
202
|
+
graphAudit?: {
|
|
203
|
+
source: 'model';
|
|
204
|
+
contentSha256: string; // stable JSON + SHA-256 of supplied model (audit / matrix persistence)
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Host HTTP handlers or workers should accept the same **`GraphExecutionRequest`** shape in the request body so payloads stay aligned with `runtime.executeGraph`.
|
|
210
|
+
|
|
211
|
+
#### Memory-only playground reporter
|
|
212
|
+
|
|
213
|
+
Use `createPlaygroundReporter()` when you want rich per-run debugging without filesystem output. The reporter is in-memory only: it does not accept an output directory, does not expose `writeReport`, and never writes request/response payloads to `playground/`, `reports/`, or any other path.
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
import { createExellixGraphRuntime, createPlaygroundReporter } from '@exellix/graph-engine';
|
|
217
|
+
|
|
218
|
+
const playgroundReporter = createPlaygroundReporter({ runId: 'local-debug-run' });
|
|
219
|
+
const runtime = createExellixGraphRuntime({
|
|
220
|
+
graphLoader,
|
|
221
|
+
engineFactory,
|
|
222
|
+
tasksClient,
|
|
223
|
+
playgroundReporter,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
await runtime.executeGraph({
|
|
227
|
+
model,
|
|
228
|
+
runtime: {
|
|
229
|
+
jobId: 'job-123',
|
|
230
|
+
job,
|
|
231
|
+
input,
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Inspect full node request/response payloads in memory.
|
|
236
|
+
const artifacts = playgroundReporter.getArtifacts();
|
|
237
|
+
const snapshot = playgroundReporter.getDebugSnapshot();
|
|
238
|
+
const markdown = playgroundReporter.getMarkdown();
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
`getArtifacts()` returns entries such as `01-<nodeId>-request` and `01-<nodeId>-response` with the full payload attached as `payload`. Use `getDebugSnapshot()` when a UI, debugger panel, or test wants steps, artifacts, and rendered markdown as one object.
|
|
242
|
+
|
|
243
|
+
**Removed in 5.0:** the legacy functional `executeGraph` from `runtime/executeGraph`, the `ExellixGraphClient` class, and their option/result types (`ExecuteGraphOptions`, `ExecuteGraphResponse`, `ExecuteGraphFinalizedResponse`, `ExecuteGraphDebugResponse`, `GraphExecutionResult`). Migrate to `createExellixGraphRuntime(...).executeGraph(...)` — the runtime now covers the same semantics and exposes the same diagnostics through `debugMode: true`. See [`BREAKING-CHANGES.md`](BREAKING-CHANGES.md).
|
|
244
|
+
|
|
245
|
+
### Run identity (host `jobId` and engine `taskId`)
|
|
246
|
+
|
|
247
|
+
- **`jobId` (mandatory):** You must pass **`runtime.jobId: string`** on the `executeGraph` input. It must be non-empty after trim. If it is missing or blank, the call fails with **`ExellixGraphErrorCode.JOB_ID_REQUIRED`**. The runtime sets **`job.id`** and **`job.jobId`** to this value for the duration of the run (so templates, events, and memory see a consistent id even if **`job`** omitted **`id`** on input).
|
|
248
|
+
|
|
249
|
+
- **`taskId` (mandatory on the wire, generated here):** At the start of each `executeGraph`, the engine allocates **`taskId = randomUUID()`**. The same value is attached to **every** `runTask` request in that run (including synthesize-finalizer paths) as **`taskId`**, and is returned on **`ExecuteGraphResult.taskId`**. This satisfies downstream expectations that each graph execution has a stable **per-run** task identity distinct from the host **`jobId`**.
|
|
250
|
+
|
|
251
|
+
- **Results:** `ExecuteGraphResult` includes both **`jobId`** and **`taskId`** so callers and logs can correlate host scope vs engine run instance.
|
|
252
|
+
|
|
253
|
+
- **Activix:** Graph-run integration persists **`jobId`**, **`taskId`**, and **`graphId`** on the record and in **`runContext`** (with **`sessionId`** still aligned to **`jobId`** for Activix v5). The in-memory correlation key for start → complete/fail is **`jobId:graphId:taskId`** so concurrent retries of the same host job against the same graph do not collide. Node activity integration keys rows by **`graphId:nodeId:taskId`** and includes **`taskId`** in **`runContext`** and top-level metadata.
|
|
254
|
+
|
|
255
|
+
- **Helpers:** **`assertHostJobId`** and **`newGraphRunTaskId`** are exported from the package root for hosts/tests that build inputs outside `executeGraph`.
|
|
256
|
+
|
|
257
|
+
**Standalone node debugging:** The runtime also exposes **`runtime.executeNode(...)`** for single-node test runs. Provide `node`, `job`, optional `graph` / `execution`, and (when continuing an existing run) `graphRunTaskId` from the parent `ExecuteGraphResult.taskId` so `runTask` and Activix stay aligned.
|
|
258
|
+
|
|
259
|
+
### `runTask` request contract (`@exellix/ai-tasks` v7.x)
|
|
260
|
+
|
|
261
|
+
Graph-engine builds a canonical `RunTaskRequest` for every outbound task call it owns: MAIN task-node invokes, engine PRE/POST utility invokes, and `synthesize` finalizer invokes. MAIN request assembly lives in [`src/runtime/buildAiTasksRunTaskRequest.ts`](src/runtime/buildAiTasksRunTaskRequest.ts); all paths follow **`RUNTASK_REQUEST.md`** in `@exellix/ai-tasks`. Types align with **`RunTaskRequest`** / **`ExellixGraphRunTaskRequest`** exported from this package.
|
|
262
|
+
|
|
263
|
+
#### Identity and payload
|
|
264
|
+
|
|
265
|
+
- **Required correlation:** `agentId`, **`jobTypeId`**, **`taskTypeId`** (in addition to `skillKey`, `input`). Defaults: `jobTypeId` ← `job.jobType` or `job.jobTypeId` or `exellix-graph-job`; `taskTypeId` ← `node.taskConfiguration.taskTypeId` or the node’s **`skillKey`**.
|
|
266
|
+
- **Canonical task payload:** **`input`** object only (merged execution slice + **materialized** `node.inputs`). Root-level **`question`**, **`raw`**, **`jobInput`**, duplicate **`inputs`**, and legacy **`executionType`** are **not** sent on the request object.
|
|
267
|
+
- **Graph telemetry:** `graphId`, **`nodeId`**, **`coreSkillId`** (node id), `masterSkillId`, `masterSkillActivityId`, `jobId`, `taskId`, optional `identity`.
|
|
268
|
+
|
|
269
|
+
To build `RunTaskRequest` without fallback defaults, the execution request must provide both sides of the contract: `model.id`, `node.id`, `node.skillKey`, explicit `node.taskConfiguration.taskTypeId` (even when it matches `skillKey`), `node.taskConfiguration.executionStrategies` (use `[]` for plain MAIN), `runtime.jobId`, `runtime.job.agentId`, `runtime.job.jobTypeId` or `runtime.job.jobType`, active input/memory, and any model/LLM/diagnostic options needed by the task. Model selection uses the exact shape `{ xynthesisModel, skillModel }` from `runtime.nodes[nodeId].modelConfig`, `node.taskConfiguration.modelConfig`, `runtime.modelConfig`, or `model.modelConfig`; aliases are runtime-only and resolve through `runtime.aliasConfig` plus `runtime.nodes[nodeId].aliasConfig`. The resolved model config is forwarded to MAIN, engine PRE/POST utility calls, and `synthesize` finalizer calls. Graph-engine still derives correlation fields such as `graphId`, `nodeId`, `coreSkillId`, `masterSkillId`, `taskId`, and `masterSkillActivityId` from those authored values.
|
|
270
|
+
|
|
271
|
+
#### Mandatory `executionStrategies` (breaking vs pre–v7 authoring)
|
|
272
|
+
|
|
273
|
+
- Every MAIN (and engine PRE/POST utility) **`runTask`** includes **`executionStrategies`**: an array of **`ExecutionStrategyInvocation`** objects (semantics defined by **`@exellix/ai-tasks`** / `RUNTASK_REQUEST.md`).
|
|
274
|
+
- **Plain MAIN** (no wrappers): graph-engine sends **`executionStrategies: []`**.
|
|
275
|
+
- Optional task-node authoring: **`taskConfiguration.executionStrategies`** — when present and non-empty, it overrides the default **`[]`** for that node’s MAIN call.
|
|
276
|
+
- Optional catalog metadata: **`taskConfiguration.executionStrategyCatalogItems`** is forwarded to `ai-tasks`; `ai-tasks` still validates the runtime invocation shape and consumes only safe catalog fields such as wrapper default function ids.
|
|
277
|
+
- **Removed in 5.0:** `metadata.executionStrategyKey` typing and code branches. Configure planners/optimizers through **`executionStrategies`** per ai-tasks (see upstream docs / Catalox task-strategy catalogs).
|
|
278
|
+
|
|
279
|
+
#### `xynthesized` and internal `execution.xynthesized`
|
|
280
|
+
|
|
281
|
+
- **Outbound `runTask.xynthesized`:** `{ job, task }` snapshot from durable graph-engine memory — **`job`** = `execution.xynthesized.job`, **`task`** = `execution.xynthesized.taskByNode[nodeId]` (never another node’s task bucket).
|
|
282
|
+
- **After a successful MAIN `runTask`**, graph-engine deep-merges **`response.xynthesizedPatch`** into **`execution.xynthesized`** (`patch.job` → `job`, `patch.task` → `taskByNode[nodeId]`).
|
|
283
|
+
- On graph start, **`seedGraphRunExecutionState`** ensures **`execution.xynthesized`** exists with **`job`** and **`taskByNode`** objects.
|
|
284
|
+
|
|
285
|
+
#### `smartInput`
|
|
286
|
+
|
|
287
|
+
- Optional task-node field **`smartInput`** (`paths: string[]`, optional **`strict`**) is forwarded on **`RunTaskRequest.smartInput`** when set. Paths are validated against graph-engine allowlists (see catalog planning / `validateAiTasksNodeExtensions`).
|
|
288
|
+
|
|
289
|
+
#### Optional `taskConfiguration` → `runTask` (strategy / Narrix)
|
|
290
|
+
|
|
291
|
+
Forwarded from `taskConfiguration`: **`narrixMode`**, **`inputStrategyKey`**, **`narrixInput`**, and **`executionStrategyCatalogItems`**. If both **`taskConfiguration.narrix`** and **`taskConfiguration.narrixInput`** are set, set **`taskConfiguration.narrixMode`** to **`preprocessor`** or **`handler`** (see [format doc](.docs/exellix-graph-engine-format.md#metadataaitasks-runtime-strategy--narrix-wiring)).
|
|
292
|
+
|
|
293
|
+
#### Input bindings before `runTask`
|
|
294
|
+
|
|
295
|
+
Values in **`node.inputsConfig`** (or deprecated **`node.inputs`**) shaped as **`{ type: 'executionMemoryPath', path }`** (optional **`optional: true`**) or **`{ $path: '…' }`** are resolved against the live **`execution`** object **before** building **`runTask.input`**, so chained graphs see concrete values (e.g. **`graphOutputs.*`**) instead of binding objects.
|
|
296
|
+
|
|
297
|
+
#### Graph-run execution seeding
|
|
298
|
+
|
|
299
|
+
When **`executeGraph`** / **`createExellixGraphRuntime().executeGraph`** starts, graph-engine seeds **`runtime.executionMemory.xynthesized`** and mirrors **`runtime.input`** to **`runtime.executionMemory.input`** (flat fields on the object; no `input.raw` wrapper).
|
|
300
|
+
|
|
301
|
+
**Where hosts put the record:** [`.docs/graph-execution-record-input.md`](.docs/graph-execution-record-input.md) — peer contract (`runtime.input` only, flat paths, matrix vs BFF).
|
|
302
|
+
|
|
303
|
+
Model knowledge references are resolved inside graph-engine without changing the ai-tasks wire contract. The merge happens when graph-engine builds the outbound **`RunTaskRequest`**: **`model.jobKnowledge`** is applied to the request copy at **`jobMemory.knowledge`**, and **`node.taskKnowledge`** is applied to that task node's request copy at **`taskMemory.knowledge`**. The shared `runtime.jobMemory` / `runtime.taskMemory` objects are not rewritten just to attach model knowledge.
|
|
304
|
+
|
|
305
|
+
Scope matters: **`jobKnowledge` is graph-run scoped** and is available to every task request through `jobMemory.knowledge`. **`taskKnowledge` is node/task scoped** and must be declared on the task node; root `model.taskKnowledge` is not part of the graph model contract.
|
|
306
|
+
|
|
307
|
+
#### PRE `inputSynthesis` (authoring → pipeline)
|
|
308
|
+
|
|
309
|
+
When **`taskConfiguration.aiTaskProfile.inputSynthesis`** is enabled, graph-engine may synthesize or merge a PRE **`synthesized-context`** step (still **one** outbound MAIN `runTask`). Conflicts with an explicit **`node.executionPipeline`** that already defines PRE synthesis are rejected at runtime / validation with **`INPUT_SYNTHESIS_PIPELINE_CONFLICT`**. Details: [.docs/exellix-graph-engine-format.md](.docs/exellix-graph-engine-format.md).
|
|
310
|
+
|
|
311
|
+
#### Three layers (do not confuse)
|
|
312
|
+
|
|
313
|
+
1. **Engine PRE/POST strategy utilities (ai-tasks only):** `taskConfiguration.aiTaskProfile.preStrategyKey` / `postStrategyKey` → extra **`runTask`** calls via **`@exellix/ai-tasks`** before/after MAIN; outputs stored at **`execution.xynthesis.pre`** / **`execution.xynthesis.post`** (historical slot names).
|
|
314
|
+
2. **`executionPipeline` inside ai-tasks:** e.g. PRE **`synthesized-context`** + MAIN direct — still **one** outbound MAIN `runTask` handled inside `@exellix/ai-tasks`.
|
|
315
|
+
3. **Narrix web scope inside ai-tasks:** when **`taskConfiguration.aiTaskProfile.webScoping.enabled`** is true, graph-engine forwards a `narrix` payload with `enableWebScope` / `webScopeQuestions`. The actual web fetch + skip rules run **inside `@exellix/ai-tasks`** (`@exellix/narrix-web-scoper`); graph-engine has **no local web phase** in 5.x.
|
|
316
|
+
|
|
317
|
+
`synthesize` finalizers are a fourth outbound task-call shape: the finalizer is still a graph model node, but its terminal utility `runTask` uses the same run identity, diagnostics, `llmCall`, and resolved model config as the rest of the graph run.
|
|
318
|
+
|
|
319
|
+
## Finalizer nodes & Final Response
|
|
320
|
+
|
|
321
|
+
Executable graphs must include **exactly one** terminal **finalizer node** (`type: "finalizer"`) with **no outgoing edges**.
|
|
322
|
+
|
|
323
|
+
Required finalizer reads must resolve from the selected memory lane. A non-optional `executionMemoryPath` read must either be seeded by `runtime.executionMemory` before the run starts or be written by a reachable upstream task node through `executionMapping.path`. A non-optional `outputsMemoryPath` read must either be seeded by `runtime.outputsMemory` or be written by a reachable upstream task node through `outputMapping.path`. If the value may be absent because a branch is conditional, mark that finalizer input, section, or item `optional: true`.
|
|
324
|
+
|
|
325
|
+
The finalizer is a terminal computation or fan-in barrier. It does not own the returned API response. The returned `ExecuteGraphResult.finalOutput` is always resolved from root `model.response` after node `executionMapping`/`outputMapping` writes and finalizer/barrier execution complete:
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
type GraphResponseDefinition = {
|
|
329
|
+
missing?: 'omit' | 'null';
|
|
330
|
+
shape: unknown;
|
|
331
|
+
};
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Supported selectors are `outputsMemoryPath`, `executionMemoryPath`, `executionPath`, `nodeMetadata`, `nodeInputsConfig`, `literal`, and `firstPresent`. Legacy `nodeInputs` is still accepted. Missing selector values are omitted by default; `missing: "null"` returns `null` for missing mapped fields.
|
|
335
|
+
|
|
336
|
+
Example:
|
|
337
|
+
|
|
338
|
+
```json
|
|
339
|
+
{
|
|
340
|
+
"response": {
|
|
341
|
+
"missing": "omit",
|
|
342
|
+
"shape": {
|
|
343
|
+
"answer": {
|
|
344
|
+
"type": "executionMemoryPath",
|
|
345
|
+
"path": "answers.q1.shortAnswer"
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
`metadata.graphExecution.outputMode`, finalizer output, and legacy `metadata.graphResponse.responseMapping` are not final-output selectors for new graphs. Migrate legacy response mappings by copying `missing` and `shape` to root `response` and dropping `version`, `target`, `primaryResponsePaths`, `debugResponsePaths`, `notableExecutionPaths`, and `mappingPreset`.
|
|
353
|
+
|
|
354
|
+
`coreObjective` is resolved once from graph/run context, not from each node response. Supported `sourcePath` roots are `input.*`, `execution.*`, `variables.*` / `jobVariables.*`, `taskVariables.*`, `jobMemory.*`, `taskMemory.*`, and `job.*`.
|
|
355
|
+
|
|
356
|
+
The selected canonical business output is exposed as:
|
|
357
|
+
|
|
358
|
+
- `ExecuteGraphResult.finalOutput` (the only API in 5.x).
|
|
359
|
+
|
|
360
|
+
### Deterministic finalizers (runtime-owned)
|
|
361
|
+
|
|
362
|
+
Implemented deterministic finalizer types:
|
|
363
|
+
|
|
364
|
+
- `aggregate`
|
|
365
|
+
- `bundle`
|
|
366
|
+
- `select`
|
|
367
|
+
|
|
368
|
+
#### `aggregate` — `strategy: "object-map"`
|
|
369
|
+
|
|
370
|
+
Build an object by mapping named inputs (from `executionMemoryPath` or literals) into output keys.
|
|
371
|
+
|
|
372
|
+
#### `aggregate` — `strategy: "report-schema"` (multi-section report from execution memory)
|
|
373
|
+
|
|
374
|
+
Builds a single object whose keys come from **`config.sections`**: each section specifies a **dot-path** into `executionMemory` (`path`), optional **`title`**, and **`optional`** (when `true`, missing values become `null` instead of throwing).
|
|
375
|
+
|
|
376
|
+
- **`collect_tags`**: when `true`, walks all section values and collects unique epistemic strings among `CONFIRMED`, `INFERRED`, `ASSUMED`, `UNKNOWN` into **`collected_tags`** (array).
|
|
377
|
+
- **`meta`**: optional literal key/value pairs merged at the top level of the parsed output.
|
|
378
|
+
|
|
379
|
+
Bundled graphs under [`graphs/`](graphs/) (see [graphs/README.md](graphs/README.md)) use this strategy for multi-section reports, sometimes after **`scoped-answer-assembler`** and **`scoped-answer-writer`** persistence (typical store: **`x-scoped-data`** in deployments that use that collection). The graph file shape is defined in [.docs/exellix-graph-engine-format.md](.docs/exellix-graph-engine-format.md).
|
|
380
|
+
|
|
381
|
+
#### `aggregate` — `strategy: "question-driven"` (generic Q→A formatting)
|
|
382
|
+
|
|
383
|
+
For “question-driven” graphs, `question-driven` formats the final output by pairing:
|
|
384
|
+
|
|
385
|
+
- **question text** from the referenced node definition (default path: `inputs.question`)
|
|
386
|
+
- **answer** from `executionMemory` at the referenced node’s `outputMapping.path` (or an explicit `answerPath`)
|
|
387
|
+
|
|
388
|
+
Example:
|
|
389
|
+
|
|
390
|
+
```json
|
|
391
|
+
{
|
|
392
|
+
"id": "finalize",
|
|
393
|
+
"type": "finalizer",
|
|
394
|
+
"finalizerType": "aggregate",
|
|
395
|
+
"inputs": {},
|
|
396
|
+
"config": {
|
|
397
|
+
"strategy": "question-driven",
|
|
398
|
+
"contractVersion": "1",
|
|
399
|
+
"items": {
|
|
400
|
+
"exploitability": { "nodeId": "q2-exploitability" },
|
|
401
|
+
"exposure": { "nodeId": "q5-exposure" },
|
|
402
|
+
"posture": { "nodeId": "q6-posture" }
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Output shape (per item key):
|
|
409
|
+
|
|
410
|
+
```json
|
|
411
|
+
{
|
|
412
|
+
"exploitability": { "question": "...", "answer": { /* whatever the node mapped */ } }
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
## Activix integration (graph run record)
|
|
417
|
+
|
|
418
|
+
When using `createActivixGraphRunIntegration()` / `createActivixExellixIntegration()`:
|
|
419
|
+
|
|
420
|
+
- **Canonical response** is stored at `outer.output.response` and is **only** the graph’s `finalOutput`.
|
|
421
|
+
- **Detailed execution data** (nodes, execution memory, errors, etc.) is stored under `outer.output.data`.
|
|
422
|
+
|
|
423
|
+
This keeps the “business output” clean while preserving full diagnostics.
|
|
424
|
+
|
|
425
|
+
### Graph start — bounded input summaries (Activix)
|
|
426
|
+
|
|
427
|
+
On `graph:start`, the graph-run integration **does not** persist raw `data.input` / `data.request` (no shallow copy of `variables`, full `GraphExecutionRequest`, or opaque host objects). Activix receives **JSON-serializable summaries** under `outer.input`:
|
|
428
|
+
|
|
429
|
+
- `inputSummary` — bounded shape for the event’s `input` (e.g. `variables` / memory handles as key counts, shallow primitive previews, nested samples).
|
|
430
|
+
- `requestSummary` — same for the merged `request` object emitted with the start event.
|
|
431
|
+
|
|
432
|
+
Correlation fields (`jobId`, `graphId`, `agentId`, `jobType`, `inputVariableKeys`, `runContext`, etc.) are unchanged.
|
|
433
|
+
|
|
434
|
+
**Recommended for `job` / `GraphExecutionRequest` / event `data` (Activix-safe):** JSON-like primitives, plain objects/arrays, string ids, `agentId`, `jobType`, and small metadata you are willing to see reflected in summaries.
|
|
435
|
+
|
|
436
|
+
**Not suitable to rely on for Activix persistence at graph start:** functions, class instances, streams, Buffers, live clients (HTTP/DB), circular graphs, or very large nested payloads — they are summarized or typed (e.g. `shape: "function"`, `shape: "instance"`) and never passed through to `startRecord` as raw references. For large memory, use `includeMemorySnapshots: true` only when you explicitly accept storing `outer.memory.start` / `end`; that remains opt-in and separate from `outer.input`.
|
|
437
|
+
|
|
438
|
+
### `runtime.executeNode(input)`
|
|
439
|
+
|
|
440
|
+
Execute a single node — useful for tests and for stepping through a node in the context of an existing graph run. Pass `node`, `job`, optional `graph` / `execution`, and (when continuing a parent run) `graphRunTaskId` from the parent `ExecuteGraphResult.taskId` so `runTask` correlation, Activix records, and `runLog` lines stay aligned.
|
|
441
|
+
|
|
442
|
+
### `loadGraph(graphId)`
|
|
443
|
+
|
|
444
|
+
Load a graph through the injected loader and validate against the canonical document schema before returning.
|
|
445
|
+
|
|
446
|
+
## Graph Entry Inputs
|
|
447
|
+
|
|
448
|
+
Graph JSON can declare the semantic type of input it is designed to work with under `metadata.graphEntry.inputs`. This is part of the graph object itself and is intended for authors, catalogs, dashboards, and future validators; the runtime does not validate or coerce it today.
|
|
449
|
+
|
|
450
|
+
```json
|
|
451
|
+
{
|
|
452
|
+
"id": "network-vuln-triage.v1",
|
|
453
|
+
"metadata": {
|
|
454
|
+
"graphEntry": {
|
|
455
|
+
"summary": "Triage one vulnerability record with an optional caller query.",
|
|
456
|
+
"inputs": [
|
|
457
|
+
{ "kind": "record", "path": "input", "required": true },
|
|
458
|
+
{ "kind": "query", "path": "input.query", "required": false }
|
|
459
|
+
],
|
|
460
|
+
"requiredExecutionPaths": ["input.subnetId"]
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
"nodes": []
|
|
464
|
+
}
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
Standard `kind` values are `record`, `query`, `user-input`, `content`, `metadata`, and `execution`. Use multiple entries for combinations, for example a graph that needs both a raw record and a user question. Paths are relative to the merged `execution` object, so `input` maps to `GraphRuntimeObject.executionMemory.input` after `runtime.input` is mirrored.
|
|
468
|
+
|
|
469
|
+
Use `kind: "execution"` when the graph is designed to consume a prior graph execution. The design contract names the source `graphId` and an optional `metadataFilter` for fields such as status values:
|
|
470
|
+
|
|
471
|
+
```json
|
|
472
|
+
{
|
|
473
|
+
"kind": "execution",
|
|
474
|
+
"graphId": "network-vuln-group-triage.v1",
|
|
475
|
+
"metadataFilter": { "status": "completed" },
|
|
476
|
+
"path": "input.upstreamExecution",
|
|
477
|
+
"required": true
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
The runtime does not perform that lookup today; host tooling resolves it and passes the selected execution under the declared path.
|
|
482
|
+
|
|
483
|
+
## Node Inputs
|
|
484
|
+
|
|
485
|
+
All dynamic per-node values — including the question text sent to the template — must be defined under `node.inputs`. This is the single canonical location.
|
|
486
|
+
|
|
487
|
+
```json
|
|
488
|
+
{
|
|
489
|
+
"id": "q1-reachability",
|
|
490
|
+
"skillKey": "professional-answer",
|
|
491
|
+
"inputs": {
|
|
492
|
+
"question": "Is this asset reachable from outside the perimeter?",
|
|
493
|
+
"record": { "type": "executionMemoryPath", "path": "input" }
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
**Execution-memory bindings (chaining):** Objects **`{ "type": "executionMemoryPath", "path": "graphOutputs.upstream.payload" }`** (and **`{ "$path": "executionMemory.…" }`** / **`input.…`** forms) are **resolved against the live `execution` object** before **`runTask`**. The outbound request carries **scalar/object values** under **`input`**, not the binding shape. Use **`optional: true`** on **`executionMemoryPath`** bindings if the path may be missing.
|
|
499
|
+
|
|
500
|
+
**Optional `smartInput`:** Top-level on the task node (sibling to **`inputsConfig`**): **`{ "paths": ["input", "graphOutputs.prev"], "strict": true }`** — forwarded on **`RunTaskRequest.smartInput`**; paths must satisfy graph-engine allowlists.
|
|
501
|
+
|
|
502
|
+
**Rules:**
|
|
503
|
+
|
|
504
|
+
- `node.inputs.question` — the question text sent to the skill template as `{{question}}`. Always set it here.
|
|
505
|
+
- Any other per-node dynamic value (e.g. `record`, `context`) also belongs in `inputs`.
|
|
506
|
+
- Path references in `outputMapping` use `node.inputs.question` (e.g. `"question": "node.inputs.question"`).
|
|
507
|
+
- `node.inputs` is the only place the runtime reads `question` from. There is no fallback to `node.question` or `node.data.question`.
|
|
508
|
+
|
|
509
|
+
**What does NOT belong in `inputs`:**
|
|
510
|
+
|
|
511
|
+
- `skillKey` — top-level node field.
|
|
512
|
+
- `taskConfiguration.narrix` — NARRIX engine config (`datasetId`, `questionId`, `layer`, etc.). These are routing/filter fields consumed by the NARRIX pre-processor, not by the template.
|
|
513
|
+
- `taskConfiguration.aiTaskProfile.webScoping` — opt-in web context. Graph-engine forwards web intent on the outbound `narrix` payload; actual web fetch and skip rules run inside `@exellix/ai-tasks`. See [.docs/exellix-graph-engine-format.md — Web scoping behavior](.docs/exellix-graph-engine-format.md#web-scoping-behavior).
|
|
514
|
+
- Template variables — use `node.variables` + paths `taskVariables.*`, or `model.variables` + `jobVariables.*` (see [Variables (two buckets)](#variables-two-buckets--job--task)). Use `node.taskVariable` for prompts, not `inputs`.
|
|
515
|
+
|
|
516
|
+
> `taskConfiguration.narrix.questionId` (e.g. `"q1"`, `"q6"`) is a NARRIX internal routing key that tells the pre-processor which framework question slot this node fills. It has nothing to do with `inputs.question` (the human-readable question text). Do not confuse them.
|
|
517
|
+
|
|
518
|
+
**First-class: question and questionId.** For NARRIX nodes you can set `inputs.question`, or `taskConfiguration.narrix.questionId`, or both. Once NARRIX provides a question ↔ questionId resolver, the missing one can be filled automatically. See [.docs/question-questionId-first-class-experience.md](.docs/question-questionId-first-class-experience.md).
|
|
519
|
+
|
|
520
|
+
## Skill Key Resolution
|
|
521
|
+
|
|
522
|
+
Canonical graph models must specify `node.skillKey` for remote task nodes. Legacy aliases `node.data.skillKey` and `node.metadata.skillKey` are rejected by canonical validation. Optional fallback to `tasks/${node.id}` exists only when `allowFallbackToNodeId: true` is explicitly enabled (default: **disabled**).
|
|
523
|
+
|
|
524
|
+
If `skillKey` is missing, `exellix-graph-engine` throws `NODE_SKILLKEY_MISSING` error **before** execution begins.
|
|
525
|
+
|
|
526
|
+
### Configuration
|
|
527
|
+
|
|
528
|
+
```typescript
|
|
529
|
+
skillKeyResolution: {
|
|
530
|
+
allowFallbackToNodeId?: boolean; // default: false (strict mode)
|
|
531
|
+
fallbackPrefix?: string; // default: "tasks/"
|
|
532
|
+
aliases?: Record<string, string>; // optional explicit remaps
|
|
533
|
+
}
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
## Variables (two buckets — job + task)
|
|
537
|
+
|
|
538
|
+
Template variables are **not** merged into one bag. Graph-engine mirrors two scopes on `executionMemory`:
|
|
539
|
+
|
|
540
|
+
| Scope | Runtime inputs | Execution mirror | Memory paths |
|
|
541
|
+
|-------|----------------|------------------|--------------|
|
|
542
|
+
| **Job / graph** (whole run) | `model.variables`, `runtime.variables`, `runtime.jobVariables`, legacy `job.jobVariables` | `execution.jobVariables` | `jobVariables.*` (legacy alias `variables.*`) |
|
|
543
|
+
| **Task / node** (current node) | `node.variables`, `runtime.taskVariables` | `execution.taskVariables` | `taskVariables.*` |
|
|
544
|
+
|
|
545
|
+
Outbound `runTask` sends **`variables`** (ai-tasks field name) = job bucket only; node scope stays on **`executionMemory.taskVariables`**. Requires **graph-engine ≥ 5.13** and **@exellix/ai-tasks ≥ 7.6.2** (passthrough, no flattening).
|
|
546
|
+
|
|
547
|
+
> **Important**: graph-engine does not define skill templates; it forwards buckets as-is. See [`formats-documentations/graph-runtime-object-format.md`](formats-documentations/graph-runtime-object-format.md).
|
|
548
|
+
|
|
549
|
+
### Upstream template rendering (`ai-gateway` / `athenix-parser`)
|
|
550
|
+
|
|
551
|
+
The stack that eventually renders skill prompts pulls in **`@athenices/ai-gateway`** (transitively via `@exellix/ai-tasks` / `@exellix/ai-skills`), which depends on **`@athenices/athenix-parser` v4+**. That parser implements the **v4 template protocol**: required `{{path}}` values throw **`TemplateResolutionError`** when missing; optional tokens use **`{{path |}}`** or **`{{path | fallback text}}`**; **`subPathSearch`** is opt-in for alternate root lookup. See the parser’s README under `node_modules/@athenices/ai-gateway/node_modules/@athenices/athenix-parser/` when debugging template failures.
|
|
552
|
+
|
|
553
|
+
**Web research in prompts:** when this repo runs **local** question-driven web scope in `executeNode`, it still injects a flat **`webContextMarkdown`** string into `jobContext` so templates can use `{{#if webContextMarkdown}}` with a **bounded** markdown block. That remains the default pattern here even though deep `execution.*` paths are now supported upstream if you pass full context and configure the gateway/parser accordingly.
|
|
554
|
+
|
|
555
|
+
Web scoping now runs inside `@exellix/ai-tasks`, not as a local graph-engine web-scoper phase. Author web intent under **`taskConfiguration.aiTaskProfile.webScoping`**; downstream ai-tasks controls source snippets, markdown, skip rules, and any enrichment from `execution.input`.
|
|
556
|
+
|
|
557
|
+
## Memory Wiring
|
|
558
|
+
|
|
559
|
+
Memory is passed through consistently:
|
|
560
|
+
|
|
561
|
+
- `jobMemory = runtime.jobMemory`; model knowledge is added only to the outbound `RunTaskRequest.jobMemory.knowledge` copy.
|
|
562
|
+
- `taskMemory = runtime.taskMemory`; node task knowledge is added only to that node's outbound `RunTaskRequest.taskMemory.knowledge` copy.
|
|
563
|
+
|
|
564
|
+
Optional per-node memory overrides:
|
|
565
|
+
- `node.memory?.taskMemory`
|
|
566
|
+
- `node.memory?.jobMemory` (rare, but allowed)
|
|
567
|
+
- `node.jobContextMapping` (see below)
|
|
568
|
+
|
|
569
|
+
## Input Grouping (`inputs`)
|
|
570
|
+
|
|
571
|
+
Nodes can group their dynamic inputs under an `inputs` object for better structure and to avoid conflicts with system fields.
|
|
572
|
+
|
|
573
|
+
```json
|
|
574
|
+
{
|
|
575
|
+
"id": "node-1",
|
|
576
|
+
"type": "task",
|
|
577
|
+
"skillKey": "my-skill",
|
|
578
|
+
"inputs": {
|
|
579
|
+
"question": "What is the capital of France?",
|
|
580
|
+
"detailed": true
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
## Job Context Mapping (`jobContextMapping`)
|
|
586
|
+
|
|
587
|
+
`jobContextMapping` allows a node to pull specific data from `jobMemory` into its local execution context (`jobContext`).
|
|
588
|
+
|
|
589
|
+
```json
|
|
590
|
+
"jobContextMapping": {
|
|
591
|
+
"map": {
|
|
592
|
+
"customer": "jobMemory.profiles.current",
|
|
593
|
+
"history": "jobMemory.activityLog"
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
The runtime will resolve these paths from `jobMemory` and provide a `jobContext` object to the underlying task.
|
|
599
|
+
|
|
600
|
+
Path expressions in `map` support:
|
|
601
|
+
- **Dot paths**: `jobMemory.profiles.current`, `executionMemory.graphOutputs.vulnInstances.records`
|
|
602
|
+
- **Array wildcard** `[*]`: e.g. `executionMemory.graphOutputs.vulnInstances.records[*]` — selects all array elements
|
|
603
|
+
- **Object values** `{*}`: e.g. `recordsById{*}` — selects all values of an object (deterministic key order)
|
|
604
|
+
|
|
605
|
+
Sources can be `jobMemory.*` or `executionMemory.*` (current execution state from previous nodes).
|
|
606
|
+
|
|
607
|
+
## Execution Object & Trace
|
|
608
|
+
|
|
609
|
+
### Execution Object
|
|
610
|
+
|
|
611
|
+
The execution object is a shared state container that persists across all nodes in a graph execution. It stores:
|
|
612
|
+
- **Output mappings**: Data written by nodes via `outputMapping`
|
|
613
|
+
- **`xynthesized` memory**: **`execution.xynthesized.job`** (run-wide) and **`execution.xynthesized.taskByNode[nodeId]`** (per node). Outbound **`runTask.xynthesized`** exposes the current node’s slice; **`xynthesizedPatch`** on responses is merged after successful MAIN calls.
|
|
614
|
+
- **Execution trace**: Per-node execution metadata (see below)
|
|
615
|
+
- **Custom state**: Any application-specific data
|
|
616
|
+
|
|
617
|
+
The execution object is passed to each node as `executionMemory` in the task request and updated through `outputMapping` configurations. Edge predicates may also read **`input.*`** and **`xynthesized.*`** roots via the same evaluation context (see [`src/runtime/predicates.ts`](src/runtime/predicates.ts)).
|
|
618
|
+
|
|
619
|
+
### Execution Trace (FR-6)
|
|
620
|
+
|
|
621
|
+
Each graph run records a **structured trace** per node in the execution object:
|
|
622
|
+
|
|
623
|
+
- **Location**: `execution._trace.nodes[nodeId]`
|
|
624
|
+
- **Shape**: `{ startedAt, endedAt, skillKey, ok, durationMs, activityId?, summary?, error? }`
|
|
625
|
+
|
|
626
|
+
**Trace fields:**
|
|
627
|
+
- `startedAt`: Unix timestamp (ms) when node execution started
|
|
628
|
+
- `endedAt`: Unix timestamp (ms) when node execution ended
|
|
629
|
+
- `skillKey`: The resolved skill key for this node
|
|
630
|
+
- `ok`: `true` if successful, `false` if failed
|
|
631
|
+
- `durationMs`: Execution duration in milliseconds
|
|
632
|
+
- `activityId`: Optional activity/task identifier from ai-tasks
|
|
633
|
+
- `summary`: Optional metadata from task response (on success)
|
|
634
|
+
- `error`: Error details with `message`, optional `code`, and optional `stack` (on failure)
|
|
635
|
+
|
|
636
|
+
Trace is written by the runtime for every node (both `executeGraph` and `createExellixGraphRuntime` flows), regardless of success or failure.
|
|
637
|
+
|
|
638
|
+
### Execution Object in Events (FR-3)
|
|
639
|
+
|
|
640
|
+
All node execution events include the execution object merged into `jobMemory`:
|
|
641
|
+
|
|
642
|
+
- **Node Start Event**: `input.jobMemory.execution` contains the current execution state
|
|
643
|
+
- **Node Complete Event**: `memoryAfter.jobMemory.execution` contains the updated execution state (after outputMapping)
|
|
644
|
+
- **Node Fail Event**: `memoryAfter.jobMemory.execution` contains the execution state at the time of failure
|
|
645
|
+
|
|
646
|
+
This ensures consistent tracking and allows activity tracking systems to access execution data for monitoring and debugging.
|
|
647
|
+
|
|
648
|
+
### ai-tasks metadata for trace
|
|
649
|
+
|
|
650
|
+
For rich structured trace, local tasks (ai-tasks) should return metadata in the response so the graph can store it in `execution._trace.nodes[nodeId].summary` and use it for `durationMs` / `activityId`:
|
|
651
|
+
|
|
652
|
+
- **`response.parsed.meta.durationMs`** — task duration in milliseconds (otherwise the runtime uses `endedAt - startedAt`)
|
|
653
|
+
- **`response.parsed.meta.localTaskId`** or **`response.parsed.meta.activityId`** — optional activity/handler identifier
|
|
654
|
+
|
|
655
|
+
MAIN **`runTask`** traces may also include bounded summaries of **`smartInput.paths`** and keys touched by **`xynthesizedPatch`** (not full synthesized payloads).
|
|
656
|
+
|
|
657
|
+
Any other fields in `response.parsed.meta` are stored in `summary`.
|
|
658
|
+
|
|
659
|
+
## Contextual Knowledge Scope (`scope`)
|
|
660
|
+
|
|
661
|
+
Nodes can define complex filtering rules to scope lists from memory into the node's context. This is useful for building localized knowledge for a task (e.g., "all assets in the same zone as the target").
|
|
662
|
+
|
|
663
|
+
```json
|
|
664
|
+
{
|
|
665
|
+
"scope": {
|
|
666
|
+
"contextualKnowledge": [
|
|
667
|
+
{
|
|
668
|
+
"list": "zonesEnriched",
|
|
669
|
+
"select": ["zone", "semantics.trust_level"],
|
|
670
|
+
"filter": {
|
|
671
|
+
"where": [
|
|
672
|
+
{ "path": "virtual_router", "eq": "{{asset.virtual_router}}" },
|
|
673
|
+
{ "path": "containsTargetIp", "eq": true }
|
|
674
|
+
],
|
|
675
|
+
"limit": 10
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
]
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
**Features:**
|
|
684
|
+
- **Dynamic Filtering**: Use placeholders like `{{asset.zone}}` to reference variables or memory.
|
|
685
|
+
- **Operators**: `eq`, `eqAny` only (precompute domain-specific fields in the host when needed).
|
|
686
|
+
- **Selection**: Pick specific fields using `select` to minimize context size.
|
|
687
|
+
- **Integration**: The result is merged into `jobContext` alongside `jobContextMapping`.
|
|
688
|
+
|
|
689
|
+
## Backward Planning
|
|
690
|
+
|
|
691
|
+
When `mode === "backward"`:
|
|
692
|
+
|
|
693
|
+
- `goalNodeId` is **required**
|
|
694
|
+
- `goalNodeId` must exist in the graph
|
|
695
|
+
|
|
696
|
+
If missing/invalid, `exellix-graph-engine` throws `BACKWARD_GOAL_REQUIRED` error **before** execution begins.
|
|
697
|
+
|
|
698
|
+
## Error Handling
|
|
699
|
+
|
|
700
|
+
All errors are structured **`ExellixGraphError`** instances with:
|
|
701
|
+
|
|
702
|
+
- `code`: **`ExellixGraphErrorCode`** (e.g., `NODE_SKILLKEY_MISSING`, `TASK_NOT_FOUND`)
|
|
703
|
+
- `message`: Human-readable message
|
|
704
|
+
- `context`: Additional context (jobId, graphId, nodeId, etc.)
|
|
705
|
+
|
|
706
|
+
### Task Not Found
|
|
707
|
+
|
|
708
|
+
When `ai-tasks.runTask()` returns "not found":
|
|
709
|
+
|
|
710
|
+
- Node is marked as `failed` with `reason: "TASK_NOT_FOUND"`
|
|
711
|
+
- Includes `skillKey` and ai-tasks diagnostics payload **as-is**
|
|
712
|
+
- Commits failure to graphenix (so history contains it)
|
|
713
|
+
- Then:
|
|
714
|
+
- If `failFast=true` → abort graph
|
|
715
|
+
- Else continue if graph allows it
|
|
716
|
+
|
|
717
|
+
## Understanding Task Content
|
|
718
|
+
|
|
719
|
+
To avoid confusion across the stack:
|
|
720
|
+
|
|
721
|
+
- **Node `skillKey`** identifies the **task** (ai-tasks layer)
|
|
722
|
+
- The task will reference or embed a **skill** (ai-skills layer)
|
|
723
|
+
- If content is missing, it's either:
|
|
724
|
+
- Missing `node.skillKey` → `exellix-graph-engine` error (`NODE_SKILLKEY_MISSING`)
|
|
725
|
+
- Task missing → `ai-tasks` not found (`TASK_NOT_FOUND`)
|
|
726
|
+
- Skill missing inside task → `ai-tasks`/`ai-skills` error
|
|
727
|
+
|
|
728
|
+
`exellix-graph-engine` surfaces these errors; it doesn't decide what tasks/skills exist.
|
|
729
|
+
|
|
730
|
+
## Types
|
|
731
|
+
|
|
732
|
+
```typescript
|
|
733
|
+
import type {
|
|
734
|
+
Graph,
|
|
735
|
+
GraphModelObject,
|
|
736
|
+
GraphAiModelConfig,
|
|
737
|
+
GraphModelAliasConfig,
|
|
738
|
+
GraphNode,
|
|
739
|
+
TaskNode,
|
|
740
|
+
TaskNodeRuntimeObject,
|
|
741
|
+
TaskNodeTaskConfiguration,
|
|
742
|
+
Job,
|
|
743
|
+
ExecuteGraphInput,
|
|
744
|
+
GraphExecutionRequest,
|
|
745
|
+
GraphRuntimeObject,
|
|
746
|
+
ExecuteGraphResult,
|
|
747
|
+
ExellixGraphRunTaskRequest,
|
|
748
|
+
ExellixGraphRunTaskResponse,
|
|
749
|
+
BuildAiTasksRunTaskRequestArgs,
|
|
750
|
+
ExecutionStepOption,
|
|
751
|
+
SmartInputConfig,
|
|
752
|
+
ExecutionStrategyInvocation,
|
|
753
|
+
XynthesizedMemory,
|
|
754
|
+
} from '@exellix/graph-engine';
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
**`RunTask` wire:** `ExellixGraphRunTaskRequest` / `ExellixGraphRunTaskResponse` are aliases of `@exellix/ai-tasks` **`RunTaskRequest`** / **`RunTaskResponse`**. **`SmartInputConfig`**, **`ExecutionStrategyInvocation`**, **`XynthesizedMemory`**, and related strategy / xynthesized types are **re-exported from `@exellix/ai-tasks`** via [`src/types/aiTasksDerivedTypes.ts`](src/types/aiTasksDerivedTypes.ts) so you can import stable names from **`@exellix/graph-engine`** without duplicating `NonNullable<RunTaskRequest[…]>` aliases.
|
|
758
|
+
|
|
759
|
+
## Error Codes
|
|
760
|
+
|
|
761
|
+
Structured errors are **`ExellixGraphError`** with **`ExellixGraphErrorCode`**:
|
|
762
|
+
|
|
763
|
+
```typescript
|
|
764
|
+
import { ExellixGraphErrorCode } from '@exellix/graph-engine';
|
|
765
|
+
|
|
766
|
+
// Examples: JOB_ID_REQUIRED, NODE_SKILLKEY_MISSING, BACKWARD_GOAL_REQUIRED,
|
|
767
|
+
// GRAPH_LOAD_FAILED, GRAPH_EXECUTION_FAILED, NODE_EXECUTION_FAILED,
|
|
768
|
+
// TASK_NOT_FOUND, INVALID_GRAPH, INVALID_NODE, NON_CANONICAL_GRAPH_DOCUMENT,
|
|
769
|
+
// NON_CANONICAL_TASK_NODE
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
Validation issues for **`smartInput`** / **`inputSynthesis`** during catalog planning use string codes such as **`SMART_INPUT_PATHS_INVALID`**, **`INPUT_SYNTHESIS_PIPELINE_CONFLICT`**, **`INPUT_SYNTHESIS_DESTINATION_INVALID`** (see [`src/inspection/validateAiTasksNodeExtensions.ts`](src/inspection/validateAiTasksNodeExtensions.ts)).
|
|
773
|
+
|
|
774
|
+
## Repository Structure
|
|
775
|
+
|
|
776
|
+
```
|
|
777
|
+
src/
|
|
778
|
+
index.ts
|
|
779
|
+
runtime/ExellixGraphRuntime.ts
|
|
780
|
+
runtime/buildAiTasksRunTaskRequest.ts
|
|
781
|
+
runtime/aiTasksStrategyPhases.ts
|
|
782
|
+
runtime/graphRunExecutionSeed.ts
|
|
783
|
+
runtime/resolveExecutionPipelineForTaskNode.ts
|
|
784
|
+
runtime/localSkills/
|
|
785
|
+
runtime/variables.ts
|
|
786
|
+
runtime/memory.ts
|
|
787
|
+
runtime/events.ts
|
|
788
|
+
loaders/FileGraphLoader.ts
|
|
789
|
+
types/refs.ts
|
|
790
|
+
types/options.ts
|
|
791
|
+
types/aiTasksDerivedTypes.ts
|
|
792
|
+
types/results.ts
|
|
793
|
+
errors/ExellixGraphError.ts
|
|
794
|
+
errors/exellixGraphErrorCodes.ts
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
## Testing
|
|
798
|
+
|
|
799
|
+
- **`npm test`** — Generic SDK checks only: deterministic finalizers, graph inspection, contract inspection (`src/tests/run-tests.ts`). Does not run bundled graph JSON from `graphs/`.
|
|
800
|
+
- **`npm run test:graphs`** — Question-breakdown sample graph (real `ai-tasks`).
|
|
801
|
+
- **`npm run test:subnet`** — `narrix-subnet-egress-triage.v1` with sample data from `graphs/tests/examples/`.
|
|
802
|
+
- **Other graph runners** (`dod`, `run:vuln-group:trace`, `test:web-scope-e2e`, …) live under **`graphs/tests/`**; see [graphs/tests/README.md](graphs/tests/README.md).
|
|
803
|
+
|
|
804
|
+
Fixtures and DOD outputs for those graphs are under **`graphs/tests/`** (`examples/`, `realdata/`, `outputs/`). The top-level **`tests/`** folder only documents the move (see [tests/README.md](tests/README.md)).
|
|
805
|
+
|
|
806
|
+
## Example graphs (`graphs/`)
|
|
807
|
+
|
|
808
|
+
**Graph JSON shape:** [.docs/exellix-graph-engine-format.md](.docs/exellix-graph-engine-format.md).
|
|
809
|
+
|
|
810
|
+
Bundled **graph definitions** (JSON DAGs) live in [`graphs/`](graphs/). **[graphs/README.md](graphs/README.md)** is the **bundle catalog** (graph IDs, product-specific notes, execution call grid, appendix for bundled v2 graphs, and **[Platform contract: respected target](graphs/README.md#platform-contract-respected-target-for-graphs-and-integrations)** for integration baseline and feature requests).
|
|
811
|
+
|
|
812
|
+
To execute a graph, supply a `graphLoader` that resolves `graphId` to JSON, build a `job` with the input shape your graph expects (often `execution.input` with `raw` and optional `metadata`), and call `executeGraph`. Further integration notes may live under [.docs/](.docs/).
|
|
813
|
+
|
|
814
|
+
**`taskConfiguration.aiTasksOutputValidation`** on task nodes is forwarded as **`outputValidation`** on the `runTask` request (**`@exellix/ai-tasks` v7+**). Root **`outputConstraints`** is not part of the closed schema.
|
|
815
|
+
|
|
816
|
+
## Integration with Other Packages
|
|
817
|
+
|
|
818
|
+
See `.reports/` directory for request documents:
|
|
819
|
+
|
|
820
|
+
- `graphenix-request-node-contract.md` - Graph node contract
|
|
821
|
+
- `ai-tasks-request-not-found-contract.md` - Task not found handling
|
|
822
|
+
- `ai-tasks-request-variable-channels.md` - Variable flow documentation
|
|
823
|
+
- `exellix-helpers-requests.md` - Helper utilities
|
|
824
|
+
|
|
825
|
+
## License
|
|
826
|
+
|
|
827
|
+
ISC
|