@exellix/graph-engine 7.7.8 → 7.8.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## 7.8.1
4
+
5
+ ### Changed
6
+
7
+ - **Dependencies:** `@exellix/ai-tasks` ^8.7.0 (transitive floors: `@exellix/ai-skills` ≥6.3.7, `@exellix/xynthesis` ≥4.4.9, `@x12i/ai-gateway` ≥10.0.6, `@x12i/ai-profiles` ≥2.1.1).
8
+ - **Synthesize finalizer:** outbound `runTask` includes `executionStrategies: []`.
9
+ - **Step retry:** token-limit retries no longer mutate `llmCall` completion budget fields (Optimixer-owned).
10
+ - **`buildMainLlmCallForRunTask`:** strips `maxTokens`, `maxTokensCap`, and legacy token keys from forwarded `llmCall`.
11
+ - **`buildAiTasksRunTaskRequest`:** rejects provider API key passthrough on `taskConfiguration` (env-only).
12
+ - **Default graph profile triplet:** `cheap/default`, `pro/default`, `cheap/default`.
13
+ - **testkit:** removed `xynthesisModel` debug fallback in `RealTasksClient`.
14
+
15
+ ## 7.7.9
16
+
17
+ ### Changed
18
+
19
+ - **Documentation:** Align README, [`.docs/exellix-graph-engine-format.md`](.docs/exellix-graph-engine-format.md), format docs, and [`.docs/ai-tasks-model-profile-aliases-7x.md`](.docs/ai-tasks-model-profile-aliases-7x.md) with ai-tasks **8.6.8** / funcx **4.4.4**: three-slot `modelConfig`, graph-only selection (7.7+), partial task overrides (7.7.8+), studio preflight via `@exellix/graph-composer`, funcx-for-runx vs xynthesis-for-execution-strategies.
20
+ - **Cross-package boundary:** [`.docs/graph-engine-ai-tasks-boundary.md`](.docs/graph-engine-ai-tasks-boundary.md) — normative graph-engine ↔ ai-tasks contract; [upstream-doc-patches/@exellix/ai-tasks/](upstream-doc-patches/@exellix/ai-tasks/README.md) — copy-ready ai-tasks doc fixes (RUNTASK_REQUEST, flow-io xynthesis-pre/post, workplan status).
21
+
22
+ ### Dependencies
23
+
24
+ - **`@exellix/ai-tasks`:** ^8.6.8
25
+ - **`@x12i/funcx`:** 4.4.4
26
+
3
27
  ## 7.7.8
4
28
 
5
29
  ### Added (CR-11 execute-time model routing)
@@ -212,7 +236,7 @@
212
236
  ### Changed
213
237
 
214
238
  - **Dual memory roots `input` and `inputs`:** Both are allowlisted for `smartInput.paths` and `inputSynthesis.sources`; paths are no longer rewritten to `input.*` at validation or runtime. Optional migration via `mapInputsMemoryPathToInputRoot` / `mapSmartInputPathsInputsToInput`.
215
- - **`runTask` wire:** Forwards `inputs` (caller bag) alongside coalesced `input` when present. See [CR-input-and-inputs-dual-root.md](.docs/CR-input-and-inputs-dual-root.md) for downstream `@exellix/ai-tasks` and graphs-studio work.
239
+ - **`runTask` wire:** Forwards `inputs` (caller bag) alongside coalesced `input` when present.
216
240
 
217
241
  ## 5.4.0
218
242
 
package/README.md CHANGED
@@ -27,22 +27,21 @@ A minimal, focused SDK for executing graphs in the exellix ecosystem.
27
27
  | Topic | Document |
28
28
  |--------|----------|
29
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
30
  | **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
31
  | 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
32
  | Layer 01 / 08 graph entry & response contracts | [`.docs/graph-io-visibility.md`](.docs/graph-io-visibility.md) |
34
33
  | Graph entry `dataFilters` v1 / public evaluator | [`.docs/data-filters-evaluation.md`](.docs/data-filters-evaluation.md) |
35
34
  | Task-node `conditions` + conditional `modelConfig.cases` (runx) | [`.docs/task-node-conditions-evaluation.md`](.docs/task-node-conditions-evaluation.md) |
36
- | **Model profile aliases** (7.x: graph = profile names, runtime = concrete models) | [`BREAKING-CHANGES.md`](BREAKING-CHANGES.md) §7.0.0, [`.docs/ai-tasks-model-profile-aliases-7x.md`](.docs/ai-tasks-model-profile-aliases-7x.md) (ai-tasks), [`.docs/fr-model-alias-descriptors.md`](.docs/fr-model-alias-descriptors.md) (upstream FRs) |
35
+ | **Model profile aliases** (7.x+: graph = aliases / `profile/choice`; ai-tasks resolves per phase) | [`BREAKING-CHANGES.md`](BREAKING-CHANGES.md) §7.6 / §7.7, [`.docs/ai-tasks-model-profile-aliases-7x.md`](.docs/ai-tasks-model-profile-aliases-7x.md), [`.docs/graph-engine-ai-tasks-boundary.md`](.docs/graph-engine-ai-tasks-boundary.md) |
37
36
  | Platform vs implementation (no domain operators in schema) | [`.docs/platform-generic-vs-implementation.md`](.docs/platform-generic-vs-implementation.md) |
38
37
  | Activix records, `runContext`, collection tracking (`@x12i/activix` 8.4+) | [`.docs/activix-records.md`](.docs/activix-records.md) |
39
38
  | Bundled graph examples & bundle README | [`graphs/README.md`](graphs/README.md) |
40
39
 
41
- 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
+ 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 **`^8.6.8`**); 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 **`@exellix/xynthesis`** directly; **`@x12i/funcx`** is used only for optional **runx** (conditional edges / `modelConfig.cases`), not for ai-tasks execution strategies (those run via xynthesis inside ai-tasks **8.5+**).
42
41
 
43
42
  ### Canonical executable graph document (strict boundary)
44
43
 
45
- 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
+ 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`, per-run options) belongs under the execution request’s **`runtime`** object and is rejected on the model. **`runtime.modelConfig`**, **`runtime.aliasConfig`**, and **`runtime.nodes[id].modelConfig`** were removed in **7.7** — model profiles belong on the graph document only.
46
45
 
47
46
  `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`.
48
47
 
@@ -54,7 +53,7 @@ Enforcement: `executeGraph`, `createExellixGraphRuntime().executeGraph`, `execut
54
53
  npm install @exellix/graph-engine
55
54
  ```
56
55
 
57
- **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
+ **Upstream tasks SDK:** This package depends on **`@exellix/ai-tasks` ^8.6.8** and **`@x12i/funcx` 4.4.4** (see **`package.json`**). Graph-engine emits **`RunTaskRequest`** shapes that match the ai-tasks **8.x** closed schema (mandatory **`executionStrategies`**, three-slot **`modelConfig`**, **`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).
58
57
 
59
58
  ### Execution matrix hosts (`@exellix/exellix-runtime`) — documentation only for the engine
60
59
 
@@ -83,7 +82,7 @@ For Firebase Admin, `createCataloxFromEnv`, `listAiSkillsCatalogItems`, and rela
83
82
 
84
83
  ### Task-node preflight (validation & analysis, no `runTask`)
85
84
 
86
- 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
+ Graph-engine exposes the **`@exellix/ai-tasks`** preflight surface so studios and matrix hosts can validate a node **before** execution without a second dependency on ai-tasks:
87
86
 
88
87
  | Export | Purpose |
89
88
  |--------|---------|
@@ -91,7 +90,7 @@ Graph-engine exposes the **`@exellix/ai-tasks` ≥ 7.6** preflight surface so st
91
90
  | **`validateTaskNodeRunTaskConfig`** | Static config checks (`agentId`, pipeline, `smartInput`, `llmCall`, …). |
92
91
  | **`validateTaskNodeRunTaskInvoke`** | Config + payload path resolution + optional template/smart-input render checks. |
93
92
 
94
- Lower-level helpers (`validateRunTaskConfig`, `validateRunTaskInvoke`, `analyzeExpectedRunTaskInput`, Rendrix `listTokens` / `analyzeTemplateResolution`, …) are **re-exported** from the package root when you already have a `RunTaskRequest`. Catalox-backed skill invoke packet analysis was removed from `@exellix/ai-tasks` — use studio / skills-manager preflight (`@x12i/funcx` analyze-gateway-invoke-request) instead.
93
+ Lower-level helpers (`validateRunTaskConfig`, `validateRunTaskInvoke`, `analyzeExpectedRunTaskInput`, Rendrix `listTokens` / `analyzeTemplateResolution`, …) are **re-exported** from the package root when you already have a `RunTaskRequest`. Catalox-backed skill invoke packet analysis was removed from `@exellix/ai-tasks` **8.6** — use **`@exellix/graph-composer`** for studio / skills-manager invoke preflight (see `@exellix/ai-tasks` `documenations/studio-skill-invoke-preflight.md`).
95
94
 
96
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.
97
96
 
@@ -106,6 +105,22 @@ const runtime = createExellixGraphRuntime({
106
105
  tasksClient: myTasksClient, // TasksClientLike — responses may use `ok` or `success` (both accepted)
107
106
  });
108
107
 
108
+ // graphModel carries static modelConfig (profile aliases on the graph document — not on runtime).
109
+ const graphModel = {
110
+ id: 'my-graph',
111
+ modelConfig: {
112
+ cases: [{
113
+ modelConfig: {
114
+ preActionModel: 'cheap',
115
+ skillModel: 'balanced',
116
+ postActionModel: 'cheap',
117
+ },
118
+ }],
119
+ },
120
+ nodes: [/* … */],
121
+ // …
122
+ };
123
+
109
124
  // Host correlation id (required). Engine sets `job.id` / `job.jobId` from it and generates `result.taskId`.
110
125
  const result = await runtime.executeGraph({
111
126
  model: graphModel,
@@ -113,15 +128,7 @@ const result = await runtime.executeGraph({
113
128
  jobId: 'job-123',
114
129
  job: { agentId: 'agent-1', input: {} },
115
130
  input: { question: 'Analyze this record' },
116
- // 7.x: graph modelConfig values are profile aliases; concrete models bind here (required).
117
- modelConfig: {
118
- cases: [{ modelConfig: { xynthesisModel: 'weak', skillModel: 'strong' } }],
119
- },
120
- aliasConfig: {
121
- strong: 'anthropic/claude-sonnet-4',
122
- weak: 'google/gemini-2.5-flash',
123
- default: 'google/gemini-2.5-flash',
124
- },
131
+ // no modelConfig or aliasConfig on runtime (removed in 7.7)
125
132
  },
126
133
  });
127
134
 
@@ -144,9 +151,9 @@ const runtime = createExellixGraphRuntime({
144
151
  graphLoader,
145
152
  engineFactory,
146
153
  tasksClient,
147
- modelConfig, // optional explicit fallback: { xynthesisModel, skillModel }
148
154
  eventEmitter, // optional graph/node lifecycle events
149
155
  playgroundReporter, // optional
156
+ runxCreateOptions, // optional — lazy runx + funcx when graph needs conditional modelConfig / conditions
150
157
  // …stepRetryPolicy, runLogMode, concurrency, runTaskDiagnostics, etc.
151
158
  });
152
159
 
@@ -161,6 +168,9 @@ interface GraphExecutionRequest {
161
168
  // Task-node model: TaskNode
162
169
  // Task-node runtime: TaskNodeRuntimeObject at runtime.nodes[nodeId]
163
170
 
171
+ // Task-node runtime: TaskNodeRuntimeObject at runtime.nodes[nodeId] — extensible bag;
172
+ // modelConfig / aliasConfig on runtime.nodes were removed in 7.7.
173
+
164
174
  interface GraphRuntimeObject {
165
175
  jobId: string; // required: host correlation id
166
176
  job: any; // host envelope: agentId, input, jobType, …
@@ -169,21 +179,13 @@ interface GraphRuntimeObject {
169
179
  taskMemory?: any;
170
180
  executionMemory?: any;
171
181
  variables?: Record<string, any>;
172
- /** Run-level model profile override (`cases`); values are alias names, not provider ids. */
173
- modelConfig?: { cases: Array<{ when?: unknown; modelConfig: { xynthesisModel: string; skillModel: string } }> };
174
- /** Required (7.x): profile alias → concrete provider model id for this execution. */
175
- aliasConfig: Record<string, string>;
176
- nodes?: Record<string, {
177
- /** Per-node profile override (alias names). */
178
- modelConfig?: { xynthesisModel: string; skillModel: string };
179
- /** Per-node alias bindings (overlay `aliasConfig`). */
180
- aliasConfig?: Record<string, string>;
181
- }>;
182
+ /** Per-node runtime bag (must not carry modelConfig or aliasConfig since 7.7). */
183
+ nodes?: Record<string, Record<string, unknown>>;
182
184
  mode?: 'forward' | 'backward' | 'hybrid';
183
185
  goalNodeId?: string; // required when mode === 'backward'
184
186
  debugMode?: boolean; // include per-node trace on result.debug
185
187
  failFast?: boolean; // default: false
186
- // …llmCall, stepRetryPolicy, runLogMode, runtimeObjects, runTaskDiagnostics, etc.
188
+ // …stepRetryPolicy, runLogMode, runtimeObjects, runTaskDiagnostics, etc.
187
189
  }
188
190
  ```
189
191
 
@@ -265,7 +267,7 @@ const markdown = playgroundReporter.getMarkdown();
265
267
 
266
268
  **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.
267
269
 
268
- ### `runTask` request contract (`@exellix/ai-tasks` v7.x)
270
+ ### `runTask` request contract (`@exellix/ai-tasks` 8.x)
269
271
 
270
272
  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.
271
273
 
@@ -277,7 +279,7 @@ Graph-engine builds a canonical `RunTaskRequest` for every outbound task call it
277
279
 
278
280
  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.
279
281
 
280
- **Model profiles (7.x):** Graph and node `modelConfig` carry **profile alias names** only (`strong`, `weak`, `default`, ) — never provider model ids in graph JSON. **`runtime.aliasConfig` is required** and maps every alias used by the run (including `default` when fallback applies) to concrete provider models. Selection order: `runtime.nodes[nodeId].modelConfig` `node.taskConfiguration.modelConfig` `runtime.modelConfig` `model.modelConfig` implicit `{ default, default }`; then strict resolution through `runtime.aliasConfig` plus `runtime.nodes[nodeId].aliasConfig`. The **resolved** `{ xynthesisModel, skillModel }` is forwarded to MAIN, engine PRE/POST utility calls, and `synthesize` finalizer calls. Snapshot `aliasConfig` on execution records for reproducibility. See [`BREAKING-CHANGES.md`](BREAKING-CHANGES.md) §7.0.0.
282
+ **Model profiles (7.x+):** Graph and node `modelConfig` carry **profile alias names** only (`cheap`, `balanced`, `deep`, or `profile/choice` keys like `cheap/default`, `cyber/deep_forensics`) — never provider model ids in graph JSON. **Since 7.7**, model selection is **graph-document only**: merge `node.taskConfiguration.modelConfig` (partial override per slot) over `model.modelConfig`, then engine defaults (`cheap` / `balanced` / `cheap`). Graph-engine forwards the three-slot triplet `{ preActionModel, skillModel, postActionModel }` on every outbound `runTask`; **`@exellix/ai-tasks`** resolves aliases via `@x12i/ai-profiles`. **7.7.8+** execute-mode validation accepts partial per-task overrides and `profile/choice` encoding without host-side pre-merge. See [`BREAKING-CHANGES.md`](BREAKING-CHANGES.md) §7.6 / §7.7 / §7.7.8.
281
283
 
282
284
  Graph-engine still derives correlation fields such as `graphId`, `nodeId`, `coreSkillId`, `masterSkillId`, `taskId`, and `masterSkillActivityId` from those authored values.
283
285
 
@@ -286,7 +288,7 @@ Graph-engine still derives correlation fields such as `graphId`, `nodeId`, `core
286
288
  - Every MAIN (and engine PRE/POST utility) **`runTask`** includes **`executionStrategies`**: an array of **`ExecutionStrategyInvocation`** objects (semantics defined by **`@exellix/ai-tasks`** / `RUNTASK_REQUEST.md`).
287
289
  - **Plain MAIN** (no wrappers): graph-engine sends **`executionStrategies: []`**.
288
290
  - Optional task-node authoring: **`taskConfiguration.executionStrategies`** — when present and non-empty, it overrides the default **`[]`** for that node’s MAIN call.
289
- - 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.
291
+ - Optional catalog metadata: **`taskConfiguration.executionStrategyCatalogItems`** is forwarded to `ai-tasks`; planner/optimizer rows use **`runtimeKind: "xynthesis-action"`** in ai-tasks **8.5+** (not funcx). `ai-tasks` still validates the runtime invocation shape and consumes only safe catalog fields such as wrapper default sidekick actions.
290
292
  - **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).
291
293
 
292
294
  #### `xynthesized` and internal `execution.xynthesized`
@@ -480,7 +482,6 @@ const result = await runtime.executeGraph({
480
482
  jobId: 'job-123',
481
483
  job: { agentId: 'agent-1', jobType: 'analysis' },
482
484
  input: entryInput,
483
- aliasConfig: { /* … */ },
484
485
  runtimeObjects: buildExellixGraphRuntimeObjects({ graphActivixClient: activixClient }),
485
486
  },
486
487
  });
@@ -919,7 +920,7 @@ Bundled **graph definitions** (JSON DAGs) live in [`graphs/`](graphs/). **[graph
919
920
 
920
921
  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/).
921
922
 
922
- **`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.
923
+ **`taskConfiguration.aiTasksOutputValidation`** on task nodes is forwarded as **`outputValidation`** on the `runTask` request (**`@exellix/ai-tasks` 8.x**). Root **`outputConstraints`** is not part of the closed schema.
923
924
 
924
925
  ## Runtime observability (`runtimeObjects`)
925
926
 
@@ -422,6 +422,7 @@ export function createExellixGraphRuntime(opts) {
422
422
  },
423
423
  modelConfig: wireModelConfig,
424
424
  llmCall: effectiveLlmCall,
425
+ executionStrategies: [],
425
426
  ...(forwardRunTaskTrace ? { executionMode: "trace" } : {}),
426
427
  ...((input.runTaskDiagnostics ?? opts.runTaskDiagnostics) != null
427
428
  ? { diagnostics: input.runTaskDiagnostics ?? opts.runTaskDiagnostics }
@@ -13,6 +13,8 @@ import type { Job } from '../types/refs.js';
13
13
  import type { RunTaskModelConfigWire } from './graphAiModelConfig.js';
14
14
  import type { ExecutionStepOption } from '../types/options.js';
15
15
  import type { ResolvedNarrixWirePayload } from '../types/narrix.js';
16
+ /** Request/metadata keys that must never carry provider API secrets (env-only). */
17
+ export declare const FORBIDDEN_RUN_TASK_SECRET_KEYS: readonly ["openrouterApiKey", "OPENROUTER_API_KEY", "OPEN_ROUTER_KEY", "apiKey", "api_key", "openRouterApiKey"];
16
18
  /** Reads task-node `taskConfiguration` keys forwarded to `@exellix/ai-tasks` `RunTaskRequest` (runtime strategy / Narrix wiring). */
17
19
  export declare function extractRunTaskStrategyOverrides(taskConfiguration: Record<string, unknown> | undefined | null): Partial<Pick<BuildAiTasksRunTaskRequestArgs, 'narrixMode' | 'inputStrategyKey' | 'narrixInput'>>;
18
20
  /** Optional `RunTaskRequest` fields commonly authored on task-node `taskConfiguration` (whitelist — closed merge order). */
@@ -1,7 +1,27 @@
1
1
  import { buildRunTaskTaskConfigurationForward } from './buildRunTaskTaskConfigurationForward.js';
2
2
  import { normalizeSmartInputConfigForRunTask } from './smartInputPaths.js';
3
+ /** Request/metadata keys that must never carry provider API secrets (env-only). */
4
+ export const FORBIDDEN_RUN_TASK_SECRET_KEYS = [
5
+ 'openrouterApiKey',
6
+ 'OPENROUTER_API_KEY',
7
+ 'OPEN_ROUTER_KEY',
8
+ 'apiKey',
9
+ 'api_key',
10
+ 'openRouterApiKey',
11
+ ];
12
+ function assertNoSecretPassthrough(taskConfiguration, context) {
13
+ if (taskConfiguration == null || typeof taskConfiguration !== 'object' || Array.isArray(taskConfiguration)) {
14
+ return;
15
+ }
16
+ for (const key of FORBIDDEN_RUN_TASK_SECRET_KEYS) {
17
+ if (key in taskConfiguration && taskConfiguration[key] !== undefined) {
18
+ throw new Error(`${context}: taskConfiguration.${key} is forbidden — provider API keys belong in process environment only (e.g. OPENROUTER_API_KEY).`);
19
+ }
20
+ }
21
+ }
3
22
  /** Reads task-node `taskConfiguration` keys forwarded to `@exellix/ai-tasks` `RunTaskRequest` (runtime strategy / Narrix wiring). */
4
23
  export function extractRunTaskStrategyOverrides(taskConfiguration) {
24
+ assertNoSecretPassthrough(taskConfiguration, 'extractRunTaskStrategyOverrides');
5
25
  if (taskConfiguration == null || typeof taskConfiguration !== 'object' || Array.isArray(taskConfiguration))
6
26
  return {};
7
27
  const m = taskConfiguration;
@@ -25,6 +45,7 @@ export function extractRunTaskStrategyOverrides(taskConfiguration) {
25
45
  * (`RunSkillRequest` / `RunTaskRequest` extras not covered by {@link extractRunTaskStrategyOverrides}).
26
46
  */
27
47
  export function extractRunTaskMetadataPassthrough(taskConfiguration) {
48
+ assertNoSecretPassthrough(taskConfiguration, 'extractRunTaskMetadataPassthrough');
28
49
  if (taskConfiguration == null || typeof taskConfiguration !== 'object' || Array.isArray(taskConfiguration))
29
50
  return {};
30
51
  const m = taskConfiguration;
@@ -3,9 +3,9 @@ import { ExellixGraphErrorCode } from '../errors/exellixGraphErrorCodes.js';
3
3
  import { isGraphAiModelConfig } from './modelConfigSelection.js';
4
4
  /** Profile alias names used when no modelConfig tier resolves. */
5
5
  export const DEFAULT_GRAPH_AI_MODEL_PROFILE_CONFIG = {
6
- preActionModel: 'cheap',
7
- skillModel: 'balanced',
8
- postActionModel: 'cheap',
6
+ preActionModel: 'cheap/default',
7
+ skillModel: 'pro/default',
8
+ postActionModel: 'cheap/default',
9
9
  };
10
10
  /**
11
11
  * Illustrative concrete ids for {@link DEFAULT_GRAPH_AI_MODEL_PROFILE_CONFIG}.
@@ -3,6 +3,9 @@
3
3
  */
4
4
  import type { LlmCallConfig } from '@exellix/ai-tasks';
5
5
  import type { RunTaskModelConfigWire } from './graphAiModelConfig.js';
6
+ /** Completion budget fields removed from outbound runTask wires (ai-tasks 8.6+ / Optimixer-owned). */
7
+ export declare const STRIPPED_LLM_CALL_TOKEN_KEYS: readonly ["maxTokens", "maxTokensCap", "max_tokens", "max_completion_tokens", "max_output_tokens"];
8
+ export declare function stripLlmCallTokenCapFields(llmCall: Record<string, unknown> | undefined | null): Record<string, unknown> | undefined;
6
9
  export declare function mergeDefinedLlmCallParts(...parts: Array<Record<string, unknown> | undefined | null>): Record<string, unknown> | undefined;
7
10
  /**
8
11
  * MAIN-only `llmCall` for ai-tasks 8.4+: `model` is always {@link RunTaskModelConfigWire.skillModel};
@@ -1,3 +1,20 @@
1
+ /** Completion budget fields removed from outbound runTask wires (ai-tasks 8.6+ / Optimixer-owned). */
2
+ export const STRIPPED_LLM_CALL_TOKEN_KEYS = [
3
+ 'maxTokens',
4
+ 'maxTokensCap',
5
+ 'max_tokens',
6
+ 'max_completion_tokens',
7
+ 'max_output_tokens',
8
+ ];
9
+ export function stripLlmCallTokenCapFields(llmCall) {
10
+ if (!llmCall || typeof llmCall !== 'object' || Array.isArray(llmCall))
11
+ return undefined;
12
+ const out = { ...llmCall };
13
+ for (const key of STRIPPED_LLM_CALL_TOKEN_KEYS) {
14
+ delete out[key];
15
+ }
16
+ return Object.keys(out).length > 0 ? out : undefined;
17
+ }
1
18
  export function mergeDefinedLlmCallParts(...parts) {
2
19
  const out = {};
3
20
  for (const p of parts) {
@@ -8,6 +25,9 @@ export function mergeDefinedLlmCallParts(...parts) {
8
25
  out[k] = v;
9
26
  }
10
27
  }
28
+ for (const key of STRIPPED_LLM_CALL_TOKEN_KEYS) {
29
+ delete out[key];
30
+ }
11
31
  return Object.keys(out).length > 0 ? out : undefined;
12
32
  }
13
33
  /**
@@ -2,13 +2,8 @@ import type { RunLogEntry } from '../types/runLog.js';
2
2
  import type { RunTaskRequest, RunTaskResponse, StepRetryPolicy } from '../types/options.js';
3
3
  import type { TaskNode } from '../types/refs.js';
4
4
  import type { StepAttemptRecord } from '../types/results.js';
5
- export type ResolvedStepRetryPolicy = Required<Pick<StepRetryPolicy, 'maxAttempts' | 'retryOnTimeout' | 'retryOnTokenLimit' | 'tokenBumpMultiplier' | 'tokenBumpCap' | 'baseMaxTokensFallback'>>;
5
+ export type ResolvedStepRetryPolicy = Required<Pick<StepRetryPolicy, 'maxAttempts' | 'retryOnTimeout' | 'retryOnTokenLimit'>>;
6
6
  export declare function resolveStepRetryPolicy(graphPolicy: StepRetryPolicy | undefined, node: Pick<TaskNode, 'taskConfiguration'> | undefined): ResolvedStepRetryPolicy;
7
- /**
8
- * Reads the effective completion cap from outbound `llmCall`.
9
- * ai-tasks v8 canonical field is `maxTokensCap`; legacy `maxTokens` / `max_tokens` still honored.
10
- */
11
- export declare function readMaxTokensFromRunTaskRequest(req: RunTaskRequest): number | undefined;
12
7
  export type RunTaskWithRetryResult = {
13
8
  response: RunTaskResponse;
14
9
  attempts: StepAttemptRecord[];
@@ -4,9 +4,6 @@ const DEFAULT_POLICY = {
4
4
  maxAttempts: 3,
5
5
  retryOnTimeout: true,
6
6
  retryOnTokenLimit: true,
7
- tokenBumpMultiplier: 2,
8
- tokenBumpCap: 16_000,
9
- baseMaxTokensFallback: 2000,
10
7
  };
11
8
  export function resolveStepRetryPolicy(graphPolicy, node) {
12
9
  const nodeRaw = node?.taskConfiguration?.stepRetryPolicy;
@@ -17,24 +14,8 @@ export function resolveStepRetryPolicy(graphPolicy, node) {
17
14
  maxAttempts,
18
15
  retryOnTimeout: merged.retryOnTimeout !== false,
19
16
  retryOnTokenLimit: merged.retryOnTokenLimit !== false,
20
- tokenBumpMultiplier: Math.max(1, merged.tokenBumpMultiplier ?? DEFAULT_POLICY.tokenBumpMultiplier),
21
- tokenBumpCap: Math.max(1, merged.tokenBumpCap ?? DEFAULT_POLICY.tokenBumpCap),
22
- baseMaxTokensFallback: Math.max(1, merged.baseMaxTokensFallback ?? DEFAULT_POLICY.baseMaxTokensFallback),
23
17
  };
24
18
  }
25
- function readNum(v) {
26
- return typeof v === 'number' && Number.isFinite(v) ? v : undefined;
27
- }
28
- /**
29
- * Reads the effective completion cap from outbound `llmCall`.
30
- * ai-tasks v8 canonical field is `maxTokensCap`; legacy `maxTokens` / `max_tokens` still honored.
31
- */
32
- export function readMaxTokensFromRunTaskRequest(req) {
33
- const llm = req.llmCall;
34
- return (readNum(llm?.maxTokensCap) ??
35
- readNum(llm?.maxTokens) ??
36
- readNum(llm?.max_tokens));
37
- }
38
19
  function shallowCloneRunTaskRequest(req) {
39
20
  return {
40
21
  ...req,
@@ -49,16 +30,6 @@ function shallowCloneRunTaskRequest(req) {
49
30
  : {}),
50
31
  };
51
32
  }
52
- function applyTokenBump(workingReq, policy) {
53
- const current = readMaxTokensFromRunTaskRequest(workingReq) ?? policy.baseMaxTokensFallback;
54
- const after = Math.min(policy.tokenBumpCap, Math.floor(current * policy.tokenBumpMultiplier));
55
- const bumped = workingReq.llmCall != null && typeof workingReq.llmCall === 'object' && !Array.isArray(workingReq.llmCall)
56
- ? { ...workingReq.llmCall }
57
- : {};
58
- bumped.maxTokensCap = after;
59
- workingReq.llmCall = bumped;
60
- return { before: current, after };
61
- }
62
33
  function classifyThrownError(err) {
63
34
  if (err == null || typeof err !== 'object')
64
35
  return 'other';
@@ -140,7 +111,6 @@ export async function runRunTaskWithRetry(args) {
140
111
  }
141
112
  const endedAt = Date.now();
142
113
  const durationMs = endedAt - startedAt;
143
- const maxTok = readMaxTokensFromRunTaskRequest(workingReq);
144
114
  if (thrown != null) {
145
115
  const c = classifyThrownError(thrown);
146
116
  attempts.push({
@@ -152,7 +122,6 @@ export async function runRunTaskWithRetry(args) {
152
122
  classification: c,
153
123
  errorCode: thrown?.code,
154
124
  errorMessage: thrown?.message,
155
- maxTokensRequested: maxTok,
156
125
  });
157
126
  pushSyntheticRunLog(syntheticRunLog, {
158
127
  nodeId,
@@ -162,7 +131,6 @@ export async function runRunTaskWithRetry(args) {
162
131
  data: {
163
132
  classification: c,
164
133
  errorCode: thrown?.code,
165
- maxTokensRequested: maxTok,
166
134
  },
167
135
  });
168
136
  if (!shouldRetryClassification(c, policy, attemptIndex)) {
@@ -172,13 +140,12 @@ export async function runRunTaskWithRetry(args) {
172
140
  throw err;
173
141
  }
174
142
  if (c === 'token-limit' && policy.retryOnTokenLimit) {
175
- const { before, after } = applyTokenBump(workingReq, policy);
176
143
  pushSyntheticRunLog(syntheticRunLog, {
177
144
  nodeId,
178
145
  skillKey,
179
146
  level: 'info',
180
- message: `step retry ${attemptIndex + 2}/${policy.maxAttempts}: token-limit bump maxTokensCap ${before} -> ${after}`,
181
- data: { classification: c, maxTokensBefore: before, maxTokensAfter: after, attempt: attemptIndex + 1 },
147
+ message: `step retry ${attemptIndex + 2}/${policy.maxAttempts}: token-limit (completion budget is Optimixer-owned; request unchanged)`,
148
+ data: { classification: c, attempt: attemptIndex + 1 },
182
149
  });
183
150
  }
184
151
  continue;
@@ -196,7 +163,6 @@ export async function runRunTaskWithRetry(args) {
196
163
  startedAt,
197
164
  endedAt,
198
165
  durationMs,
199
- maxTokensRequested: maxTok,
200
166
  });
201
167
  return { response, attempts, syntheticRunLog, lastRequest: workingReq };
202
168
  }
@@ -210,7 +176,6 @@ export async function runRunTaskWithRetry(args) {
210
176
  classification: c,
211
177
  errorCode: response.error?.code,
212
178
  errorMessage: response.error?.message,
213
- maxTokensRequested: maxTok,
214
179
  });
215
180
  pushSyntheticRunLog(syntheticRunLog, {
216
181
  nodeId,
@@ -220,20 +185,18 @@ export async function runRunTaskWithRetry(args) {
220
185
  data: {
221
186
  classification: c,
222
187
  errorCode: response.error?.code,
223
- maxTokensRequested: maxTok,
224
188
  },
225
189
  });
226
190
  if (!shouldRetryClassification(c, policy, attemptIndex)) {
227
191
  return { response, attempts, syntheticRunLog, lastRequest: workingReq };
228
192
  }
229
193
  if (c === 'token-limit' && policy.retryOnTokenLimit) {
230
- const { before, after } = applyTokenBump(workingReq, policy);
231
194
  pushSyntheticRunLog(syntheticRunLog, {
232
195
  nodeId,
233
196
  skillKey,
234
197
  level: 'info',
235
- message: `step retry ${attemptIndex + 2}/${policy.maxAttempts}: token-limit bump maxTokensCap ${before} -> ${after}`,
236
- data: { classification: c, maxTokensBefore: before, maxTokensAfter: after, attempt: attemptIndex + 1 },
198
+ message: `step retry ${attemptIndex + 2}/${policy.maxAttempts}: token-limit (completion budget is Optimixer-owned; request unchanged)`,
199
+ data: { classification: c, attempt: attemptIndex + 1 },
237
200
  });
238
201
  }
239
202
  }
@@ -31,17 +31,8 @@ export interface StepRetryPolicy {
31
31
  maxAttempts?: number;
32
32
  /** When false, timeouts do not trigger an automatic retry. Default **true**. */
33
33
  retryOnTimeout?: boolean;
34
- /** When false, token-limit signals do not trigger retry / bump. Default **true**. */
34
+ /** When false, token-limit signals do not trigger retry. Default **true**. Completion budget is Optimixer-owned — retries re-run unchanged. */
35
35
  retryOnTokenLimit?: boolean;
36
- /** Applied to effective max tokens before each token-limit retry (default **2**). */
37
- tokenBumpMultiplier?: number;
38
- /** Upper bound for `maxTokens` after bump (default **16000**). */
39
- tokenBumpCap?: number;
40
- /**
41
- * Used when `llmCall.maxTokensCap` (or legacy `maxTokens` / `max_tokens`) is unset on the outbound request
42
- * before the first token-limit retry bump (default **2000**).
43
- */
44
- baseMaxTokensFallback?: number;
45
36
  }
46
37
  /** MAIN gate before `runTask`: empty execution input / synthesis context (graphs-studio G1, G4). */
47
38
  export type MainReadinessPolicy = 'fail' | 'warn' | 'off';
@@ -83,16 +83,11 @@ export class RealTasksClient {
83
83
  const mc = aiTasksRequest.modelConfig;
84
84
  const effective = mc
85
85
  ? {
86
- preActionModel: mc.preActionModel ?? mc.xynthesisModel,
86
+ preActionModel: mc.preActionModel,
87
87
  skillModel: mc.skillModel,
88
- postActionModel: mc.postActionModel ?? mc.skillModel,
88
+ postActionModel: mc.postActionModel,
89
89
  }
90
- : aiTasksRequest.config
91
- ? {
92
- provider: aiTasksRequest.config.provider,
93
- model: aiTasksRequest.config.model,
94
- }
95
- : { provider: '(none)', model: '(none)' };
90
+ : { preActionModel: '(none)', skillModel: '(none)', postActionModel: '(none)' };
96
91
  console.log('[AI_PROVIDER] RealTasksClient → ai-tasks:', effective);
97
92
  }
98
93
  const sdk = await getPackagedClientPromise();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exellix/graph-engine",
3
- "version": "7.7.8",
3
+ "version": "7.8.1",
4
4
  "type": "module",
5
5
  "description": "Graph executor SDK",
6
6
  "main": "dist/src/index.js",
@@ -52,11 +52,11 @@
52
52
  "access": "public"
53
53
  },
54
54
  "dependencies": {
55
- "@exellix/ai-tasks": "^8.6.2",
55
+ "@exellix/ai-tasks": "^8.7.0",
56
56
  "@x12i/activix": "8.5.0",
57
57
  "@x12i/catalox": "5.1.3",
58
58
  "@x12i/env": "4.0.1",
59
- "@x12i/funcx": "4.4.0",
59
+ "@x12i/funcx": "4.4.4",
60
60
  "@x12i/graphenix": "2.5.0",
61
61
  "@x12i/graphenix-format": "2.0.0",
62
62
  "@x12i/logxer": "^4.6.0",