@exellix/graph-engine 7.3.8 → 7.3.10

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/README.md CHANGED
@@ -35,6 +35,7 @@ A minimal, focused SDK for executing graphs in the exellix ecosystem.
35
35
  | Task-node `conditions` + conditional `modelConfig.cases` (runx) | [`.docs/task-node-conditions-evaluation.md`](.docs/task-node-conditions-evaluation.md) |
36
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) |
37
37
  | Platform vs implementation (no domain operators in schema) | [`.docs/platform-generic-vs-implementation.md`](.docs/platform-generic-vs-implementation.md) |
38
+ | Activix records, `runContext`, collection tracking (`@x12i/activix` 8.4+) | [`.docs/activix-records.md`](.docs/activix-records.md) |
38
39
  | Bundled graph examples & bundle README | [`graphs/README.md`](graphs/README.md) |
39
40
 
40
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.
@@ -259,7 +260,7 @@ const markdown = playgroundReporter.getMarkdown();
259
260
 
260
261
  - **Results:** `ExecuteGraphResult` includes both **`jobId`** and **`taskId`** so callers and logs can correlate host scope vs engine run instance.
261
262
 
262
- - **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.
263
+ - **Activix (`@x12i/activix` 8.4+):** Graph-run and node integrations pass a top-level **`runContext`** on every `startRecord` (`sessionId` = **`jobId`**, plus **`taskId`**, **`graphId`**, and for nodes **`nodeId`** / **`skillKey`**). Activix owns **collection tracking state** (`track` / `off` on legend rows) graph-engine **always** calls lifecycle APIs; do not branch on persistence in app code. In-process maps correlate start complete/fail by **`activityId`** (`jobId:graphId:taskId` for graph runs; `graphId:nodeId:taskId` for nodes). Query rows via **`activixClient.getJobActivities`** (see [Activix integration](#activix-integration-graph-run-record)).
263
264
 
264
265
  - **Helpers:** **`assertHostJobId`** and **`newGraphRunTaskId`** are exported from the package root for hosts/tests that build inputs outside `executeGraph`.
265
266
 
@@ -428,12 +429,81 @@ Output shape (per item key):
428
429
 
429
430
  ## Activix integration (graph run record)
430
431
 
431
- When using `createActivixGraphRunIntegration()` / `createActivixExellixIntegration()`:
432
+ Graph-engine depends on **`@x12i/activix`** (see **`package.json`**, currently **8.4.x**). Full wiring guide: [`.docs/activix-records.md`](.docs/activix-records.md). Activix README (collection tracking, `getJobActivities`, phased `outer` / `inner` I/O): `node_modules/@x12i/activix/README.md`.
433
+
434
+ ### Wiring (recommended)
435
+
436
+ ```typescript
437
+ import {
438
+ createActivixFromEnv,
439
+ createActivixExellixIntegration,
440
+ createExellixGraphRuntime,
441
+ resolveActivixExellixCollectionNamesFromEnv,
442
+ } from '@exellix/graph-engine';
443
+ import { buildExellixGraphRuntimeObjects } from '@exellix/graph-engine/testkit';
444
+
445
+ const ax = createActivixFromEnv({ strict: true });
446
+ await ax.init();
447
+
448
+ const { graphRuns, nodeActivity } = resolveActivixExellixCollectionNamesFromEnv();
449
+ const { eventEmitter, activixClient } = createActivixExellixIntegration(ax, {
450
+ activixGraphRun: { collection: graphRuns },
451
+ activixNodeActivity: {
452
+ collection: nodeActivity,
453
+ includeInputSnapshot: true,
454
+ extractNarrixOutcome: true,
455
+ },
456
+ });
457
+
458
+ const runtime = createExellixGraphRuntime({
459
+ graphLoader,
460
+ engineFactory,
461
+ tasksClient,
462
+ eventEmitter,
463
+ });
432
464
 
433
- - **Canonical response** is stored at `outer.output.response` and is **only** the graph’s `finalOutput`.
434
- - **Detailed execution data** (nodes, execution memory, errors, etc.) is stored under `outer.output.data`.
465
+ const result = await runtime.executeGraph({
466
+ model: graph,
467
+ runtime: {
468
+ jobId: 'job-123',
469
+ job: { agentId: 'agent-1', jobType: 'analysis' },
470
+ input: entryInput,
471
+ aliasConfig: { /* … */ },
472
+ runtimeObjects: buildExellixGraphRuntimeObjects({ graphActivixClient: activixClient }),
473
+ },
474
+ });
435
475
 
436
- This keeps the “business output” clean while preserving full diagnostics.
476
+ // Playground / debug UI no direct Mongo:
477
+ const activities = await activixClient.getJobActivities({
478
+ jobId: result.jobId,
479
+ graphId: result.graphId,
480
+ limit: 500,
481
+ });
482
+ ```
483
+
484
+ **Exports:** `createActivixFromEnv`, `resolveActivixExellixCollectionNamesFromEnv`, `createActivixExellixIntegration`, `createActivixGraphRunIntegration`, `createActivixNodeActivityIntegration`, `createActivixQueryableClient`, `ensureActivixInitialized`, `ensureActivixClientOnRuntimeObjects`, `buildGraphRunActivixRunContext`, `buildNodeActivixRunContext`.
485
+
486
+ ### Collection tracking state (Activix 8.4+)
487
+
488
+ Legend rows in **`activix-collections`** may set **`state: 'track' | 'off'`**.
489
+
490
+ | Value | Meaning |
491
+ |-------|---------|
492
+ | **`track`** (default) | Mongo persistence for that collection |
493
+ | **`off`** | Same APIs; Activix skips Mongo (ephemeral in-process rows only) |
494
+
495
+ **Always** call `startRecord` / `completeRecord` / `failRecord` from integrations — never skip Activix in graph-engine when `off`. Toggle at runtime: `ax.setCollectionTrackingState(collectionName, 'off' | 'track')`. After external DB edits: `ax.refreshCollectionTrackingStates()`.
496
+
497
+ `createActivixFromEnv()` registers both exellix streams with **`runContext`** indexes and **`collectionRegistry.owner`** = `@exellix/graph-engine`. Collection names default to **`exellix-graph-runs`** and **`exellix-node-activity`** (override with **`ACTIVIX_GRAPH_RUNS_COLLECTION`** / **`ACTIVIX_NODE_ACTIVITY_COLLECTION`**).
498
+
499
+ ### Record shape (graph run)
500
+
501
+ When using `createActivixGraphRunIntegration()` / `createActivixExellixIntegration()`:
502
+
503
+ - **Top-level `runContext`** on `startRecord` (not only nested inside custom fields).
504
+ - **Canonical response** at `outer.output.response` — only the graph’s **`finalOutput`**.
505
+ - **Diagnostics** under `outer.output.data` (nodes, execution, jobMemory, errors, …).
506
+ - **Top-level `metadata`** on the row for orchestrator observability (e.g. `graphNodeCount`, `inputVariableKeys`); **`outer.metadata`** carries **`kind`** / **`type`** (`exellix-graph-run`, `graph-run`).
437
507
 
438
508
  ### Graph start — bounded input summaries (Activix)
439
509
 
@@ -448,6 +518,19 @@ Correlation fields (`jobId`, `graphId`, `agentId`, `jobType`, `inputVariableKeys
448
518
 
449
519
  **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`.
450
520
 
521
+ ## Logxer integration (runtime logs)
522
+
523
+ Graph-engine uses **`@x12i/logxer`** (4.6+) for structured logs and in-process **`getJobLogs`** (playground Logger, diagnostics).
524
+
525
+ - **Per-run logger:** Each `executeGraph` creates a run-scoped Logxer (`createGraphEngineLogxer({ logging })`) and binds it for the duration of the run.
526
+ - **Stack pass-through:** Pass **`logging?: StackLoggingOptions`** on `createExellixGraphRuntime` and/or per `executeGraph` to control downstream packages (`GRAPH_ENGINE`, `AI_TASKS`, …). The run is wrapped in **`runWithAiTasksStackLogging`** from `@exellix/ai-tasks`.
527
+ - **Service filter (Logger UI):** Top-level **`runtimeIdentity.service`** identifies the component that emitted the line. Graph-engine native events use **`graph-engine`**. Proxied downstream records keep the **origin** service (e.g. `ai-skills`, `@exellix/ai-tasks`) with wrapper attribution in **`proxyRuntimeIdentity`** — do not rely on **`data.runtimeIdentity.service`** for filtering.
528
+ - **Env:** `GRAPH_ENGINE_LOGS_LEVEL` (canonical); bulk stack levels via **`LOGXER_PACKAGE_LEVELS`** (see `@x12i/logxer` docs).
529
+
530
+ **Exports:** `createGraphEngineLogxer`, `getGraphEngineLogxer`, `createGraphEngineLogxerClient`, `ensureGraphEngineLogxerOnRuntimeObjects`, `runGraphWithLogContext`, `GRAPH_ENGINE_RUNTIME_SERVICE`, `normalizeGraphEngineLogMeta`.
531
+
532
+ Wire query surface on **`runtime.runtimeObjects.logxerClient`** (same pattern as Activix — see `buildExellixGraphRuntimeObjects` in testkit).
533
+
451
534
  ### `runtime.executeNode(input)`
452
535
 
453
536
  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.
@@ -826,6 +909,18 @@ To execute a graph, supply a `graphLoader` that resolves `graphId` to JSON, buil
826
909
 
827
910
  **`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.
828
911
 
912
+ ## Runtime observability (`runtimeObjects`)
913
+
914
+ Hosts and playgrounds compose a single **`runtimeObjects`** tree instead of querying Mongo or Logxer stores directly:
915
+
916
+ | Client | Source | Query API |
917
+ |--------|--------|-----------|
918
+ | **`activixClient`** | Your `Activix` instance via `createActivixQueryableClient` / `createActivixExellixIntegration` | `getJobActivities({ jobId, graphId?, nodeId?, limit? })` |
919
+ | **`logxerClient`** | Per-run graph-engine Logxer via `ensureGraphEngineLogxerOnRuntimeObjects` | `getJobLogs({ jobId, graphId?, nodeId?, level?, … })` |
920
+ | **`packagesRuntimeObjects`** | Optional subtree from `@exellix/ai-tasks` | Package-owned clients when exported |
921
+
922
+ Use **`buildExellixGraphRuntimeObjects({ graphActivixClient, graphLogxerClient, aiTasksRuntimeObjects })`** (testkit) and pass the result on **`runtime.runtimeObjects`**. **`setRuntimeObjectsLastJobId`** is applied automatically during `executeGraph`.
923
+
829
924
  ## Integration with Other Packages
830
925
 
831
926
  See `.reports/` directory for request documents:
@@ -23,7 +23,7 @@ export type { NodeExecutionResult, NodeTraceEntry, StepAttemptRecord, StepFailur
23
23
  export type { RunLogEntry, RunLogLevel, RunLogScope, RunLogMode, RunLogBuildOptions, RunLogBuildResult, } from './types/runLog.js';
24
24
  export { AI_TASKS_RUN_LOG_METADATA_KEY, AI_TASKS_LOGXER_RUN_ID_METADATA_KEY, DEFAULT_MAX_RUN_LOG_ENTRIES, DEFAULT_MAX_RUN_LOG_DATA_JSON_CHARS, } from './types/runLog.js';
25
25
  export { buildRunLog, resolveRunLogLimits, truncateRunLogData, parseRunLogEntryTs, normalizeExternalRunLogEntries, extractTaskRunLogFromMetadata, extractLogxerCorrelationFromMetadata, } from './runtime/buildRunLog.js';
26
- export { GRAPH_ENGINE_LOGXER_ENV_PREFIX, createGraphEngineLogxer, getGraphEngineLogxer, createGraphEngineLogxerClient, ensureGraphEngineLogxerOnRuntimeObjects, bindGraphEngineRunLogxer, clearGraphEngineRunLogxer, runGraphWithLogContext, patchGraphNodeLogContext, traceExecutionMemory, logGraphEngineErrorCode, DebugLogAbstract as GraphEngineDebugLogAbstract, fieldEvidence as graphEngineFieldEvidence, exceptionEvidence as graphEngineExceptionEvidence, } from './runtime/graphEngineLogxer.js';
26
+ export { GRAPH_ENGINE_LOGXER_ENV_PREFIX, createGraphEngineLogxer, getGraphEngineLogxer, createGraphEngineLogxerClient, ensureGraphEngineLogxerOnRuntimeObjects, bindGraphEngineRunLogxer, clearGraphEngineRunLogxer, runGraphWithLogContext, patchGraphNodeLogContext, traceExecutionMemory, logGraphEngineErrorCode, DebugLogAbstract as GraphEngineDebugLogAbstract, fieldEvidence as graphEngineFieldEvidence, exceptionEvidence as graphEngineExceptionEvidence, GRAPH_ENGINE_RUNTIME_SERVICE, GRAPH_ENGINE_PROXY_RUNTIME_IDENTITY, extractOriginRuntimeIdentity, normalizeGraphEngineLogMeta, } from './runtime/graphEngineLogxer.js';
27
27
  export type { GetJobLogsInput as GraphEngineGetJobLogsInput, GetJobLogsResult as GraphEngineGetJobLogsResult, LogRuntimeContext as GraphEngineLogRuntimeContext, StackLoggingOptions as GraphEngineStackLoggingOptions, } from './runtime/graphEngineLogxer.js';
28
28
  export type { GraphExecutionEvent, NodeExecutionEvent, } from './types/events.js';
29
29
  export { ExellixGraphError } from './errors/ExellixGraphError.js';
@@ -99,6 +99,8 @@ export type { CreateActivixGraphRunIntegrationOptions } from './integrations/Act
99
99
  export { createActivixFromEnv, resolveActivixExellixCollectionNamesFromEnv, } from './integrations/createActivixFromEnv.js';
100
100
  export { createActivixExellixIntegration } from './integrations/createActivixExellixIntegration.js';
101
101
  export type { CreateActivixExellixIntegrationOptions } from './integrations/createActivixExellixIntegration.js';
102
+ export { createActivixQueryableClient, ensureActivixInitialized, buildGraphRunActivixRunContext, buildNodeActivixRunContext, } from './integrations/activixExellixShared.js';
103
+ export { ensureActivixClientOnRuntimeObjects } from './integrations/ensureActivixOnRuntimeObjects.js';
102
104
  export { validateGraphPlanningCatalogDescriptorsInCatalox } from './integrations/cataloxGraphCatalog.js';
103
105
  export type { GraphCatalogValidationIssue } from './integrations/cataloxGraphCatalog.js';
104
106
  export { composeEventEmitters } from './runtime/events.js';
package/dist/src/index.js CHANGED
@@ -16,7 +16,7 @@ export { mergeGraphDocumentModel, EXELLIX_GRAPH_MODEL_VARIABLE_KEY, EXELLIX_STRU
16
16
  export { getTaskConfiguration } from './types/taskNodeConfiguration.js';
17
17
  export { AI_TASKS_RUN_LOG_METADATA_KEY, AI_TASKS_LOGXER_RUN_ID_METADATA_KEY, DEFAULT_MAX_RUN_LOG_ENTRIES, DEFAULT_MAX_RUN_LOG_DATA_JSON_CHARS, } from './types/runLog.js';
18
18
  export { buildRunLog, resolveRunLogLimits, truncateRunLogData, parseRunLogEntryTs, normalizeExternalRunLogEntries, extractTaskRunLogFromMetadata, extractLogxerCorrelationFromMetadata, } from './runtime/buildRunLog.js';
19
- export { GRAPH_ENGINE_LOGXER_ENV_PREFIX, createGraphEngineLogxer, getGraphEngineLogxer, createGraphEngineLogxerClient, ensureGraphEngineLogxerOnRuntimeObjects, bindGraphEngineRunLogxer, clearGraphEngineRunLogxer, runGraphWithLogContext, patchGraphNodeLogContext, traceExecutionMemory, logGraphEngineErrorCode, DebugLogAbstract as GraphEngineDebugLogAbstract, fieldEvidence as graphEngineFieldEvidence, exceptionEvidence as graphEngineExceptionEvidence, } from './runtime/graphEngineLogxer.js';
19
+ export { GRAPH_ENGINE_LOGXER_ENV_PREFIX, createGraphEngineLogxer, getGraphEngineLogxer, createGraphEngineLogxerClient, ensureGraphEngineLogxerOnRuntimeObjects, bindGraphEngineRunLogxer, clearGraphEngineRunLogxer, runGraphWithLogContext, patchGraphNodeLogContext, traceExecutionMemory, logGraphEngineErrorCode, DebugLogAbstract as GraphEngineDebugLogAbstract, fieldEvidence as graphEngineFieldEvidence, exceptionEvidence as graphEngineExceptionEvidence, GRAPH_ENGINE_RUNTIME_SERVICE, GRAPH_ENGINE_PROXY_RUNTIME_IDENTITY, extractOriginRuntimeIdentity, normalizeGraphEngineLogMeta, } from './runtime/graphEngineLogxer.js';
20
20
  // Errors
21
21
  export { ExellixGraphError } from './errors/ExellixGraphError.js';
22
22
  export { ExellixGraphErrorCode } from './errors/exellixGraphErrorCodes.js';
@@ -74,6 +74,8 @@ export { createActivixNodeActivityIntegration } from './integrations/ActivixNode
74
74
  export { createActivixGraphRunIntegration } from './integrations/ActivixGraphRunIntegration.js';
75
75
  export { createActivixFromEnv, resolveActivixExellixCollectionNamesFromEnv, } from './integrations/createActivixFromEnv.js';
76
76
  export { createActivixExellixIntegration } from './integrations/createActivixExellixIntegration.js';
77
+ export { createActivixQueryableClient, ensureActivixInitialized, buildGraphRunActivixRunContext, buildNodeActivixRunContext, } from './integrations/activixExellixShared.js';
78
+ export { ensureActivixClientOnRuntimeObjects } from './integrations/ensureActivixOnRuntimeObjects.js';
77
79
  export { validateGraphPlanningCatalogDescriptorsInCatalox } from './integrations/cataloxGraphCatalog.js';
78
80
  export { composeEventEmitters } from './runtime/events.js';
79
81
  // Playground (full-visibility MD report)
@@ -23,8 +23,9 @@ export interface CreateActivixGraphRunIntegrationOptions {
23
23
  }
24
24
  /**
25
25
  * Returns an EventEmitter that persists graph lifecycle rows via Activix.
26
- * Correlates start → complete/fail via an in-memory map keyed by `jobId:graphId:taskId`
27
- * (host job id + graph id + engine-generated graph-run UUID).
26
+ * Correlates start → complete/fail via in-process `activityId` (returned from `startRecord`).
27
+ * Activix 8.4+ owns collection tracking state (`track` / `off` on legend rows); always call
28
+ * `startRecord` / `completeRecord` / `failRecord` — persistence is decided inside Activix.
28
29
  *
29
30
  * On `graph:start`, `outer.input` contains only bounded **`inputSummary`** / **`requestSummary`**
30
31
  * (JSON-serializable); raw event input/request objects are not copied to Activix.
@@ -1,3 +1,4 @@
1
+ import { buildGraphRunActivixRunContext, ensureActivixInitialized, } from './activixExellixShared.js';
1
2
  function summarizeValueForRecord(value) {
2
3
  if (value === null || value === undefined) {
3
4
  return { shape: 'empty' };
@@ -167,8 +168,9 @@ function summarizeForActivix(value, depth = 0, seen = new WeakSet()) {
167
168
  }
168
169
  /**
169
170
  * Returns an EventEmitter that persists graph lifecycle rows via Activix.
170
- * Correlates start → complete/fail via an in-memory map keyed by `jobId:graphId:taskId`
171
- * (host job id + graph id + engine-generated graph-run UUID).
171
+ * Correlates start → complete/fail via in-process `activityId` (returned from `startRecord`).
172
+ * Activix 8.4+ owns collection tracking state (`track` / `off` on legend rows); always call
173
+ * `startRecord` / `completeRecord` / `failRecord` — persistence is decided inside Activix.
172
174
  *
173
175
  * On `graph:start`, `outer.input` contains only bounded **`inputSummary`** / **`requestSummary`**
174
176
  * (JSON-serializable); raw event input/request objects are not copied to Activix.
@@ -179,22 +181,12 @@ export function createActivixGraphRunIntegration(ax, options) {
179
181
  const includeMemorySnapshots = options?.includeMemorySnapshots === true;
180
182
  const colOpts = collection ? { collection } : undefined;
181
183
  const graphRecordIds = new Map();
182
- let initPromise = null;
183
- function ensureInit() {
184
- if (!initPromise) {
185
- initPromise = ax.init().catch((err) => {
186
- initPromise = null;
187
- throw err;
188
- });
189
- }
190
- return initPromise;
191
- }
192
184
  const eventEmitter = {
193
185
  emit(event) {
194
186
  setImmediate(async () => {
195
187
  try {
196
188
  if (event.type === 'graph:start') {
197
- await ensureInit();
189
+ await ensureActivixInitialized(ax);
198
190
  const graphEvent = event;
199
191
  const data = graphEvent.data;
200
192
  const key = `${graphEvent.jobId}:${graphEvent.graphId}:${graphEvent.taskId}`;
@@ -203,56 +195,49 @@ export function createActivixGraphRunIntegration(ax, options) {
203
195
  const agentId = data?.agentId;
204
196
  const jobType = data?.jobType;
205
197
  const graphNodeCount = getGraphNodeCount(data?.graphDefinition);
206
- // Activix v5+ correlation object is `runContext` and must include `sessionId`.
207
- // This SDK does not have a separate session id today, so we align it to `jobId`.
208
- const runContext = {
209
- sessionId: graphEvent.jobId,
198
+ const runContext = buildGraphRunActivixRunContext({
210
199
  jobId: graphEvent.jobId,
211
200
  taskId: graphEvent.taskId,
212
201
  graphId: graphEvent.graphId,
213
202
  agentId,
214
- ...(jobType != null ? { jobType } : {}),
215
- };
203
+ jobType,
204
+ });
216
205
  const outerMemoryStart = data?.memoryStart;
217
206
  const outerMetadata = {
218
207
  kind: 'exellix-graph-run',
208
+ type: 'graph-run',
219
209
  jobId: graphEvent.jobId,
220
210
  taskId: graphEvent.taskId,
221
211
  graphId: graphEvent.graphId,
222
- agentId,
212
+ ...(agentId != null ? { agentId } : {}),
223
213
  ...(jobType != null ? { jobType } : {}),
224
214
  graphNodeCount,
225
215
  inputVariableKeys,
226
216
  eventTimestamp: graphEvent.timestamp,
227
217
  };
228
- const base = {
229
- kind: 'exellix-graph-run',
230
- jobId: graphEvent.jobId,
231
- taskId: graphEvent.taskId,
232
- graphId: graphEvent.graphId,
233
- agentId,
234
- ...(jobType != null ? { jobType } : {}),
235
- graphNodeCount,
236
- inputVariableKeys,
237
- eventTimestamp: graphEvent.timestamp,
218
+ const { activityId } = await ax.startRecord({
238
219
  runContext,
239
220
  outer: {
240
221
  input: {
241
222
  inputSummary: summarizeForActivix(data?.input),
242
223
  requestSummary: summarizeForActivix(data?.request),
243
224
  },
244
- output: undefined,
245
- memory: {
246
- ...(includeMemorySnapshots ? { start: outerMemoryStart } : {}),
247
- },
225
+ output: null,
248
226
  metadata: outerMetadata,
227
+ ...(includeMemorySnapshots && outerMemoryStart != null
228
+ ? { memory: { start: outerMemoryStart } }
229
+ : {}),
249
230
  },
250
- };
251
- const { recordId } = await ax.startRecord(base, colOpts);
252
- graphRecordIds.set(key, recordId);
231
+ metadata: {
232
+ graphNodeCount,
233
+ inputVariableKeys,
234
+ eventTimestamp: graphEvent.timestamp,
235
+ },
236
+ }, colOpts);
237
+ graphRecordIds.set(key, activityId);
253
238
  }
254
239
  else if (event.type === 'graph:complete') {
255
- await ensureInit();
240
+ await ensureActivixInitialized(ax);
256
241
  const graphEvent = event;
257
242
  const key = `${graphEvent.jobId}:${graphEvent.graphId}:${graphEvent.taskId}`;
258
243
  const recordId = graphRecordIds.get(key);
@@ -291,7 +276,7 @@ export function createActivixGraphRunIntegration(ax, options) {
291
276
  graphRecordIds.delete(key);
292
277
  }
293
278
  else if (event.type === 'graph:fail') {
294
- await ensureInit();
279
+ await ensureActivixInitialized(ax);
295
280
  const graphEvent = event;
296
281
  const key = `${graphEvent.jobId}:${graphEvent.graphId}:${graphEvent.taskId}`;
297
282
  const recordId = graphRecordIds.get(key);
@@ -21,7 +21,8 @@ export interface CreateActivixNodeActivityIntegrationOptions {
21
21
  }
22
22
  /**
23
23
  * Returns an EventEmitter that persists node lifecycle rows via Activix.
24
- * Correlates start → complete/fail using `graphId:nodeId:taskId` (includes engine-generated graph-run `taskId`).
24
+ * Correlates start → complete/fail via in-process `activityId` from `startRecord`.
25
+ * Activix 8.4+ owns collection tracking state; always call lifecycle APIs (see `activixExellixShared`).
25
26
  */
26
27
  export declare function createActivixNodeActivityIntegration(ax: Activix, options?: CreateActivixNodeActivityIntegrationOptions): {
27
28
  eventEmitter: EventEmitter;
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * Listens for `node:start`, `node:complete`, and `node:fail` only. Graph-level events are ignored.
5
5
  */
6
+ import { buildNodeActivixRunContext, ensureActivixInitialized, } from './activixExellixShared.js';
6
7
  function summarizeValueForRecord(value) {
7
8
  if (value === null || value === undefined) {
8
9
  return { shape: 'empty' };
@@ -24,7 +25,8 @@ function isPlainObject(v) {
24
25
  }
25
26
  /**
26
27
  * Returns an EventEmitter that persists node lifecycle rows via Activix.
27
- * Correlates start → complete/fail using `graphId:nodeId:taskId` (includes engine-generated graph-run `taskId`).
28
+ * Correlates start → complete/fail via in-process `activityId` from `startRecord`.
29
+ * Activix 8.4+ owns collection tracking state; always call lifecycle APIs (see `activixExellixShared`).
28
30
  */
29
31
  export function createActivixNodeActivityIntegration(ax, options) {
30
32
  const collection = options?.collection;
@@ -32,16 +34,6 @@ export function createActivixNodeActivityIntegration(ax, options) {
32
34
  const extractNarrixOutcome = options?.extractNarrixOutcome === true;
33
35
  const colOpts = collection ? { collection } : undefined;
34
36
  const nodeRecordIds = new Map();
35
- let initPromise = null;
36
- function ensureInit() {
37
- if (!initPromise) {
38
- initPromise = ax.init().catch((err) => {
39
- initPromise = null;
40
- throw err;
41
- });
42
- }
43
- return initPromise;
44
- }
45
37
  const eventEmitter = {
46
38
  emit(event) {
47
39
  setImmediate(async () => {
@@ -52,22 +44,20 @@ export function createActivixNodeActivityIntegration(ax, options) {
52
44
  const jobType = nodeEvent.data != null && typeof nodeEvent.data === 'object' && 'jobType' in nodeEvent.data
53
45
  ? nodeEvent.data.jobType
54
46
  : undefined;
55
- // Activix v5+ correlation object is `runContext` and must include `sessionId`.
56
- // This SDK does not have a separate session id today, so we align it to `jobId`.
57
- const runContext = {
58
- sessionId: nodeEvent.jobId,
47
+ const runContext = buildNodeActivixRunContext({
59
48
  jobId: nodeEvent.jobId,
60
49
  taskId: nodeEvent.taskId,
61
50
  graphId: nodeEvent.graphId,
62
51
  nodeId: nodeEvent.nodeId,
63
52
  skillKey: nodeEvent.skillKey,
64
- ...(jobType != null ? { jobType } : {}),
65
- };
53
+ jobType,
54
+ });
66
55
  const outerInput = isPlainObject(input) ? input : {};
67
56
  const outerRequest = nodeEvent.data?.input?.request;
68
57
  const outerMemoryStart = nodeEvent.data?.input?.memoryStart;
69
58
  const outerMetadata = {
70
59
  kind: 'exellix-graph-node',
60
+ type: 'task',
71
61
  jobId: nodeEvent.jobId,
72
62
  taskId: nodeEvent.taskId,
73
63
  graphId: nodeEvent.graphId,
@@ -76,52 +66,42 @@ export function createActivixNodeActivityIntegration(ax, options) {
76
66
  ...(jobType != null ? { jobType } : {}),
77
67
  eventTimestamp: nodeEvent.timestamp,
78
68
  };
79
- const base = {
80
- kind: 'exellix-graph-node',
81
- jobId: nodeEvent.jobId,
82
- taskId: nodeEvent.taskId,
83
- graphId: nodeEvent.graphId,
84
- nodeId: nodeEvent.nodeId,
85
- skillKey: nodeEvent.skillKey,
86
- ...(jobType != null ? { jobType } : {}),
69
+ const topMetadata = {
87
70
  eventTimestamp: nodeEvent.timestamp,
88
- runContext,
89
- outer: {
90
- input: {
91
- ...outerInput,
92
- request: outerRequest,
93
- },
94
- output: undefined,
95
- memory: {
96
- start: outerMemoryStart,
97
- },
98
- metadata: outerMetadata,
99
- },
100
71
  };
101
72
  if (includeInputSnapshot && input && typeof input === 'object') {
102
73
  const vars = input.variables;
103
74
  if (vars && typeof vars === 'object') {
104
- base.inputVariableKeys = Object.keys(vars);
105
- if (isPlainObject(base.outer?.metadata)) {
106
- base.outer.metadata.inputVariableKeys = base.inputVariableKeys;
107
- }
75
+ const inputVariableKeys = Object.keys(vars);
76
+ outerMetadata.inputVariableKeys = inputVariableKeys;
77
+ topMetadata.inputVariableKeys = inputVariableKeys;
108
78
  }
109
79
  const jm = input.jobMemory;
110
80
  const ex = jm?.execution;
111
81
  if (ex != null && typeof ex === 'object') {
112
- base.executionKeys = Object.keys(ex);
113
- if (isPlainObject(base.outer?.metadata)) {
114
- base.outer.metadata.executionKeys = base.executionKeys;
115
- }
82
+ const executionKeys = Object.keys(ex);
83
+ outerMetadata.executionKeys = executionKeys;
84
+ topMetadata.executionKeys = executionKeys;
116
85
  }
117
86
  }
87
+ const startPayload = {
88
+ runContext,
89
+ outer: {
90
+ input: {
91
+ ...outerInput,
92
+ ...(outerRequest !== undefined ? { request: outerRequest } : {}),
93
+ },
94
+ output: null,
95
+ metadata: outerMetadata,
96
+ ...(outerMemoryStart !== undefined ? { memory: { start: outerMemoryStart } } : {}),
97
+ },
98
+ metadata: topMetadata,
99
+ };
118
100
  const key = `${nodeEvent.graphId}:${nodeEvent.nodeId}:${nodeEvent.taskId}`;
119
- // Start the record insert immediately (promise inserted into the map synchronously),
120
- // but defer actual DB work until `ax.init()` is ready.
121
101
  const recordPromise = (async () => {
122
- await ensureInit();
123
- const { recordId } = await ax.startRecord(base, colOpts);
124
- return recordId;
102
+ await ensureActivixInitialized(ax);
103
+ const { activityId } = await ax.startRecord(startPayload, colOpts);
104
+ return activityId;
125
105
  })();
126
106
  nodeRecordIds.set(key, recordPromise);
127
107
  await recordPromise;
@@ -0,0 +1,58 @@
1
+ import type { Activix, ActivixRunContext } from '@x12i/activix';
2
+ import type { ActivixQueryableClient } from '../runtime/runtimeObjects.js';
3
+ /** Recommended Mongo indexes for exellix-graph Activix streams (see `@x12i/activix` README). */
4
+ export declare const ACTIVIX_EXELLIX_RUN_CONTEXT_INDEXES: readonly [{
5
+ readonly keys: {
6
+ readonly 'runContext.jobId': 1;
7
+ };
8
+ }, {
9
+ readonly keys: {
10
+ readonly 'runContext.graphId': 1;
11
+ };
12
+ }, {
13
+ readonly keys: {
14
+ readonly 'runContext.taskId': 1;
15
+ };
16
+ }];
17
+ export declare const ACTIVIX_EXELLIX_NODE_RUN_CONTEXT_INDEXES: readonly [{
18
+ readonly keys: {
19
+ readonly 'runContext.jobId': 1;
20
+ };
21
+ }, {
22
+ readonly keys: {
23
+ readonly 'runContext.graphId': 1;
24
+ };
25
+ }, {
26
+ readonly keys: {
27
+ readonly 'runContext.taskId': 1;
28
+ };
29
+ }, {
30
+ readonly keys: {
31
+ readonly 'runContext.nodeId': 1;
32
+ };
33
+ }];
34
+ /**
35
+ * Single `init()` per Activix instance. Integrators always call Activix lifecycle APIs;
36
+ * collection `state` (track/off) is resolved inside Activix — do not branch in graph-engine.
37
+ */
38
+ export declare function ensureActivixInitialized(ax: Activix): Promise<void>;
39
+ /** Official query surface: delegate to `Activix.getJobActivities` (8.x). */
40
+ export declare function createActivixQueryableClient(ax: Activix): ActivixQueryableClient;
41
+ export declare function buildGraphRunActivixRunContext(args: {
42
+ jobId: string;
43
+ taskId: string;
44
+ graphId: string;
45
+ agentId?: string;
46
+ jobType?: string;
47
+ }): ActivixRunContext;
48
+ export declare function buildNodeActivixRunContext(args: {
49
+ jobId: string;
50
+ taskId: string;
51
+ graphId: string;
52
+ nodeId: string;
53
+ skillKey?: string;
54
+ jobType?: string;
55
+ }): ActivixRunContext;
56
+ export declare function activixExellixCollectionRegistryOwner(): {
57
+ package: "@exellix/graph-engine";
58
+ };
@@ -0,0 +1,59 @@
1
+ import { EXELLIX_GRAPH_RUNTIME_PACKAGE_NAME } from '../runtime/runtimeObjects.js';
2
+ /** Recommended Mongo indexes for exellix-graph Activix streams (see `@x12i/activix` README). */
3
+ export const ACTIVIX_EXELLIX_RUN_CONTEXT_INDEXES = [
4
+ { keys: { 'runContext.jobId': 1 } },
5
+ { keys: { 'runContext.graphId': 1 } },
6
+ { keys: { 'runContext.taskId': 1 } },
7
+ ];
8
+ export const ACTIVIX_EXELLIX_NODE_RUN_CONTEXT_INDEXES = [
9
+ ...ACTIVIX_EXELLIX_RUN_CONTEXT_INDEXES,
10
+ { keys: { 'runContext.nodeId': 1 } },
11
+ ];
12
+ let sharedInitByActivix = new WeakMap();
13
+ /**
14
+ * Single `init()` per Activix instance. Integrators always call Activix lifecycle APIs;
15
+ * collection `state` (track/off) is resolved inside Activix — do not branch in graph-engine.
16
+ */
17
+ export function ensureActivixInitialized(ax) {
18
+ let p = sharedInitByActivix.get(ax);
19
+ if (!p) {
20
+ p = ax.init().catch((err) => {
21
+ sharedInitByActivix.delete(ax);
22
+ throw err;
23
+ });
24
+ sharedInitByActivix.set(ax, p);
25
+ }
26
+ return p;
27
+ }
28
+ /** Official query surface: delegate to `Activix.getJobActivities` (8.x). */
29
+ export function createActivixQueryableClient(ax) {
30
+ return {
31
+ getJobActivities(input) {
32
+ return ax.getJobActivities(input);
33
+ },
34
+ };
35
+ }
36
+ export function buildGraphRunActivixRunContext(args) {
37
+ return {
38
+ sessionId: args.jobId,
39
+ jobId: args.jobId,
40
+ taskId: args.taskId,
41
+ graphId: args.graphId,
42
+ ...(args.agentId != null && args.agentId !== '' ? { agentId: args.agentId } : {}),
43
+ ...(args.jobType != null ? { jobType: args.jobType } : {}),
44
+ };
45
+ }
46
+ export function buildNodeActivixRunContext(args) {
47
+ return {
48
+ sessionId: args.jobId,
49
+ jobId: args.jobId,
50
+ taskId: args.taskId,
51
+ graphId: args.graphId,
52
+ nodeId: args.nodeId,
53
+ ...(args.skillKey != null && args.skillKey !== '' ? { skillKey: args.skillKey } : {}),
54
+ ...(args.jobType != null ? { jobType: args.jobType } : {}),
55
+ };
56
+ }
57
+ export function activixExellixCollectionRegistryOwner() {
58
+ return { package: EXELLIX_GRAPH_RUNTIME_PACKAGE_NAME };
59
+ }
@@ -1,5 +1,6 @@
1
1
  import type { Activix } from '@x12i/activix';
2
2
  import type { EventEmitter } from '../runtime/events.js';
3
+ import type { ActivixQueryableClient } from '../runtime/runtimeObjects.js';
3
4
  import { type CreateActivixNodeActivityIntegrationOptions } from './ActivixNodeActivityIntegration.js';
4
5
  import { type CreateActivixGraphRunIntegrationOptions } from './ActivixGraphRunIntegration.js';
5
6
  export interface CreateActivixExellixIntegrationOptions {
@@ -11,4 +12,6 @@ export interface CreateActivixExellixIntegrationOptions {
11
12
  */
12
13
  export declare function createActivixExellixIntegration(ax: Activix, options?: CreateActivixExellixIntegrationOptions): {
13
14
  eventEmitter: EventEmitter | undefined;
15
+ /** Use with {@link buildExellixGraphRuntimeObjects} as `graphActivixClient`. */
16
+ activixClient: ActivixQueryableClient;
14
17
  };
@@ -1,4 +1,5 @@
1
1
  import { composeEventEmitters } from '../runtime/events.js';
2
+ import { createActivixQueryableClient } from './activixExellixShared.js';
2
3
  import { createActivixNodeActivityIntegration, } from './ActivixNodeActivityIntegration.js';
3
4
  import { createActivixGraphRunIntegration, } from './ActivixGraphRunIntegration.js';
4
5
  /**
@@ -12,5 +13,5 @@ export function createActivixExellixIntegration(ax, options) {
12
13
  ? createActivixGraphRunIntegration(ax, options.activixGraphRun)
13
14
  : undefined;
14
15
  const eventEmitter = composeEventEmitters(node?.eventEmitter, graph?.eventEmitter);
15
- return { eventEmitter };
16
+ return { eventEmitter, activixClient: createActivixQueryableClient(ax) };
16
17
  }
@@ -1,4 +1,5 @@
1
1
  import { Activix } from '@x12i/activix';
2
+ import { ACTIVIX_EXELLIX_NODE_RUN_CONTEXT_INDEXES, ACTIVIX_EXELLIX_RUN_CONTEXT_INDEXES, activixExellixCollectionRegistryOwner, } from './activixExellixShared.js';
2
3
  function requiredEnv(name, strict) {
3
4
  const v = process.env[name];
4
5
  if (v && v.trim().length > 0)
@@ -45,9 +46,18 @@ export function createActivixFromEnv(options) {
45
46
  return new Activix({
46
47
  mongoUri,
47
48
  defaultCollection: nodeActivityCollection,
49
+ collectionRegistry: {
50
+ owner: activixExellixCollectionRegistryOwner(),
51
+ },
48
52
  collections: [
49
- { name: graphRunsCollection },
50
- { name: nodeActivityCollection },
53
+ {
54
+ name: graphRunsCollection,
55
+ indexes: [...ACTIVIX_EXELLIX_RUN_CONTEXT_INDEXES],
56
+ },
57
+ {
58
+ name: nodeActivityCollection,
59
+ indexes: [...ACTIVIX_EXELLIX_NODE_RUN_CONTEXT_INDEXES],
60
+ },
51
61
  ],
52
62
  });
53
63
  }
@@ -0,0 +1,4 @@
1
+ import type { Activix } from '@x12i/activix';
2
+ import type { RuntimeObjects } from '../runtime/runtimeObjects.js';
3
+ /** Wires graph-level `getJobActivities` when a host supplies an Activix instance on `runtimeObjects`. */
4
+ export declare function ensureActivixClientOnRuntimeObjects(runtimeObjects: RuntimeObjects | undefined, ax?: Activix): void;
@@ -0,0 +1,7 @@
1
+ import { createActivixQueryableClient } from './activixExellixShared.js';
2
+ /** Wires graph-level `getJobActivities` when a host supplies an Activix instance on `runtimeObjects`. */
3
+ export function ensureActivixClientOnRuntimeObjects(runtimeObjects, ax) {
4
+ if (!runtimeObjects || runtimeObjects.activixClient != null || ax == null)
5
+ return;
6
+ runtimeObjects.activixClient = createActivixQueryableClient(ax);
7
+ }
@@ -25,6 +25,7 @@ import { buildRunTaskIdentityEnvelope, mergeDefinedLlmCallParts, shouldForwardRu
25
25
  import { buildRunLog, extractLogxerCorrelationFromMetadata, extractTaskRunLogFromMetadata, resolveRunLogLimits, } from "./buildRunLog.js";
26
26
  import { setRuntimeObjectsLastJobId, summarizeRuntimeObjectsForPlayground } from "./runtimeObjects.js";
27
27
  import { DebugLogAbstract, bindGraphEngineRunLogxer, clearGraphEngineRunLogxer, createGraphEngineLogxer, ensureGraphEngineLogxerOnRuntimeObjects, logGraphEngineErrorCode, patchGraphNodeLogContext, runGraphWithLogContext, traceExecutionMemory, } from "./graphEngineLogxer.js";
28
+ import { runWithAiTasksStackLogging } from "@exellix/ai-tasks";
28
29
  import { assertHostJobId, newGraphRunTaskId } from "./graphRunIdentity.js";
29
30
  import { resolveTaskKey } from "./resolveTaskKey.js";
30
31
  import { buildPredicateEvalContextForNode, mirrorTaskVariablesOnExecution, readExecutionVariableBuckets, seedGraphVariableBucketsFromRuntime, } from "./variables.js";
@@ -1157,7 +1158,7 @@ export function createExellixGraphRuntime(opts) {
1157
1158
  }
1158
1159
  const resolvedGraphId = String(resolvedGraphIdRaw);
1159
1160
  const runLogxer = createGraphEngineLogxer({ logging: merged.logging });
1160
- return runGraphWithLogContext({ jobId, taskId: graphTaskId, graphId: resolvedGraphId, runId: graphTaskId }, async () => {
1161
+ return runWithAiTasksStackLogging(merged.logging, () => runGraphWithLogContext({ jobId, taskId: graphTaskId, graphId: resolvedGraphId, runId: graphTaskId }, async () => {
1161
1162
  bindGraphEngineRunLogxer(runLogxer);
1162
1163
  try {
1163
1164
  assertCanonicalGraphDocument(graph, { jobId, graphId: resolvedGraphId });
@@ -1721,7 +1722,7 @@ export function createExellixGraphRuntime(opts) {
1721
1722
  finally {
1722
1723
  clearGraphEngineRunLogxer();
1723
1724
  }
1724
- });
1725
+ }));
1725
1726
  }
1726
1727
  return {
1727
1728
  executeGraph,
@@ -0,0 +1,16 @@
1
+ import type { LogMeta, RuntimeIdentity } from '@x12i/logxer';
2
+ /** Canonical filter key for graph-engine's own log lines. */
3
+ export declare const GRAPH_ENGINE_RUNTIME_SERVICE: "graph-engine";
4
+ /** Wrapper attribution when graph-engine proxies a downstream Logxer record. */
5
+ export declare const GRAPH_ENGINE_PROXY_RUNTIME_IDENTITY: RuntimeIdentity;
6
+ /**
7
+ * Resolve the originating component identity from meta or nested payload shapes
8
+ * (e.g. proxied envelopes stored under `data.runtimeIdentity`).
9
+ */
10
+ export declare function extractOriginRuntimeIdentity(meta: LogMeta | undefined): RuntimeIdentity | undefined;
11
+ /**
12
+ * Ensures top-level `runtimeIdentity.service` is the log origin for filtering.
13
+ * Graph-engine-native lines use `graph-engine`; proxied downstream lines keep the
14
+ * downstream service and record wrapper attribution on `proxyRuntimeIdentity`.
15
+ */
16
+ export declare function normalizeGraphEngineLogMeta(meta?: LogMeta): LogMeta | undefined;
@@ -0,0 +1,75 @@
1
+ import { EXELLIX_GRAPH_RUNTIME_PACKAGE_NAME } from './runtimeObjects.js';
2
+ /** Canonical filter key for graph-engine's own log lines. */
3
+ export const GRAPH_ENGINE_RUNTIME_SERVICE = 'graph-engine';
4
+ /** Wrapper attribution when graph-engine proxies a downstream Logxer record. */
5
+ export const GRAPH_ENGINE_PROXY_RUNTIME_IDENTITY = {
6
+ service: GRAPH_ENGINE_RUNTIME_SERVICE,
7
+ libraryPackage: EXELLIX_GRAPH_RUNTIME_PACKAGE_NAME,
8
+ };
9
+ function isRecord(value) {
10
+ return value != null && typeof value === 'object' && !Array.isArray(value);
11
+ }
12
+ function readRuntimeIdentity(value) {
13
+ if (!isRecord(value))
14
+ return undefined;
15
+ const service = value.service;
16
+ if (typeof service !== 'string' || service.length === 0)
17
+ return undefined;
18
+ return value;
19
+ }
20
+ function isDownstreamOrigin(identity) {
21
+ const service = identity.service;
22
+ return typeof service === 'string' && service.length > 0 && service !== GRAPH_ENGINE_RUNTIME_SERVICE;
23
+ }
24
+ /**
25
+ * Resolve the originating component identity from meta or nested payload shapes
26
+ * (e.g. proxied envelopes stored under `data.runtimeIdentity`).
27
+ */
28
+ export function extractOriginRuntimeIdentity(meta) {
29
+ if (!meta)
30
+ return undefined;
31
+ const candidates = [];
32
+ if (meta.runtimeIdentity != null)
33
+ candidates.push(meta.runtimeIdentity);
34
+ const data = meta.data;
35
+ if (isRecord(data)) {
36
+ if (data.runtimeIdentity != null)
37
+ candidates.push(data.runtimeIdentity);
38
+ if (isRecord(data.data) && data.data.runtimeIdentity != null) {
39
+ candidates.push(data.data.runtimeIdentity);
40
+ }
41
+ }
42
+ for (const raw of candidates) {
43
+ const identity = readRuntimeIdentity(raw);
44
+ if (identity != null && isDownstreamOrigin(identity))
45
+ return identity;
46
+ }
47
+ return undefined;
48
+ }
49
+ /**
50
+ * Ensures top-level `runtimeIdentity.service` is the log origin for filtering.
51
+ * Graph-engine-native lines use `graph-engine`; proxied downstream lines keep the
52
+ * downstream service and record wrapper attribution on `proxyRuntimeIdentity`.
53
+ */
54
+ export function normalizeGraphEngineLogMeta(meta) {
55
+ if (meta == null)
56
+ return meta;
57
+ const origin = extractOriginRuntimeIdentity(meta);
58
+ if (origin != null) {
59
+ return {
60
+ ...meta,
61
+ runtimeIdentity: origin,
62
+ proxyRuntimeIdentity: GRAPH_ENGINE_PROXY_RUNTIME_IDENTITY,
63
+ };
64
+ }
65
+ const existing = readRuntimeIdentity(meta.runtimeIdentity);
66
+ const runtimeIdentity = {
67
+ ...GRAPH_ENGINE_PROXY_RUNTIME_IDENTITY,
68
+ ...(existing ?? {}),
69
+ service: GRAPH_ENGINE_RUNTIME_SERVICE,
70
+ };
71
+ return {
72
+ ...meta,
73
+ runtimeIdentity,
74
+ };
75
+ }
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import type { GetJobLogsInput, GetJobLogsResult, Logxer, LogRuntimeContext, StackLoggingOptions } from '@x12i/logxer';
6
6
  import type { LogxerQueryableClient, RuntimeObjects } from './runtimeObjects.js';
7
+ export { GRAPH_ENGINE_RUNTIME_SERVICE, GRAPH_ENGINE_PROXY_RUNTIME_IDENTITY, extractOriginRuntimeIdentity, normalizeGraphEngineLogMeta, } from './graphEngineLogMeta.js';
7
8
  /** Env prefix for package-level log level: `GRAPH_ENGINE_LOGS_LEVEL` (canonical). */
8
9
  export declare const GRAPH_ENGINE_LOGXER_ENV_PREFIX: "GRAPH_ENGINE";
9
10
  /** Factory for a graph-engine Logxer instance (logxer 4.5+ stack pass-through). */
@@ -1,6 +1,8 @@
1
1
  const logxer = await import('@x12i/logxer');
2
2
  import { ExellixGraphErrorCode } from '../errors/exellixGraphErrorCodes.js';
3
3
  import { EXELLIX_GRAPH_RUNTIME_PACKAGE_NAME } from './runtimeObjects.js';
4
+ import { normalizeGraphEngineLogMeta, } from './graphEngineLogMeta.js';
5
+ export { GRAPH_ENGINE_RUNTIME_SERVICE, GRAPH_ENGINE_PROXY_RUNTIME_IDENTITY, extractOriginRuntimeIdentity, normalizeGraphEngineLogMeta, } from './graphEngineLogMeta.js';
4
6
  /** Env prefix for package-level log level: `GRAPH_ENGINE_LOGS_LEVEL` (canonical). */
5
7
  export const GRAPH_ENGINE_LOGXER_ENV_PREFIX = 'GRAPH_ENGINE';
6
8
  const GRAPH_ENGINE_DIAGNOSTIC_CATALOG = {
@@ -75,20 +77,50 @@ let defaultLogxer;
75
77
  let activeRunLogxer;
76
78
  function buildGraphEngineLogxerConfig(logging) {
77
79
  return {
78
- runtimeIdentity: { service: 'graph-engine' },
79
80
  diagnostics: {
80
81
  catalog: GRAPH_ENGINE_DIAGNOSTIC_CATALOG,
81
82
  },
82
83
  ...(logging ? { stack: logging } : {}),
83
84
  };
84
85
  }
86
+ const GRAPH_ENGINE_LOG_METHODS = new Set([
87
+ 'verbose',
88
+ 'debug',
89
+ 'info',
90
+ 'warn',
91
+ 'error',
92
+ 'success',
93
+ 'infoCode',
94
+ 'warnCode',
95
+ 'errorCode',
96
+ 'errorFromCaught',
97
+ 'diagnostic',
98
+ ]);
99
+ /** Preserve downstream `runtimeIdentity.service` on proxied records; tag graph-engine wrapper separately. */
100
+ function wrapGraphEngineLogxer(inner) {
101
+ return new Proxy(inner, {
102
+ get(target, prop, receiver) {
103
+ const value = Reflect.get(target, prop, receiver);
104
+ if (typeof value !== 'function' || !GRAPH_ENGINE_LOG_METHODS.has(String(prop))) {
105
+ return typeof value === 'function' ? value.bind(target) : value;
106
+ }
107
+ return (...args) => {
108
+ if (args.length >= 2 && args[1] != null) {
109
+ args[1] = normalizeGraphEngineLogMeta(args[1]);
110
+ }
111
+ return Reflect.apply(value, target, args);
112
+ };
113
+ },
114
+ });
115
+ }
85
116
  /** Factory for a graph-engine Logxer instance (logxer 4.5+ stack pass-through). */
86
117
  export function createGraphEngineLogxer(options) {
87
- return logxer.createLogxer({
118
+ const inner = logxer.createLogxer({
88
119
  packageName: EXELLIX_GRAPH_RUNTIME_PACKAGE_NAME,
89
120
  envPrefix: GRAPH_ENGINE_LOGXER_ENV_PREFIX,
90
121
  debugNamespace: 'graph-engine',
91
122
  }, buildGraphEngineLogxerConfig(options?.logging));
123
+ return wrapGraphEngineLogxer(inner);
92
124
  }
93
125
  /** Default process logger (env / host registry). Lazily created without stack overrides. */
94
126
  export function getGraphEngineLogxer() {
@@ -127,11 +159,11 @@ export function traceExecutionMemory(scope, message, data) {
127
159
  process.env.DEBUG_OUTPUT_MAPPING !== 'true') {
128
160
  return;
129
161
  }
130
- getGraphEngineLogxer().verbose(message, {
162
+ getGraphEngineLogxer().verbose(message, normalizeGraphEngineLogMeta({
131
163
  scope,
132
164
  ...data,
133
165
  debugKind: logxer.DebugLogAbstract.STATE,
134
- });
166
+ }));
135
167
  }
136
168
  export function logGraphEngineErrorCode(code, _message, ctx) {
137
169
  const instance = getGraphEngineLogxer();
@@ -139,14 +171,14 @@ export function logGraphEngineErrorCode(code, _message, ctx) {
139
171
  ...(ctx.error != null ? [logxer.exceptionEvidence(ctx.error)] : []),
140
172
  ...Object.entries(ctx.evidence ?? {}).map(([path, value]) => logxer.fieldEvidence(path, value)),
141
173
  ];
142
- instance.errorCode(code, {
174
+ instance.errorCode(code, normalizeGraphEngineLogMeta({
143
175
  graphId: ctx.graphId,
144
176
  nodeId: ctx.nodeId,
145
177
  jobId: ctx.jobId,
146
178
  taskId: ctx.taskId,
147
179
  debugKind: logxer.DebugLogAbstract.ANOMALY,
148
180
  ...(evidence.length ? { evidence } : {}),
149
- });
181
+ }));
150
182
  }
151
183
  export const DebugLogAbstract = logxer.DebugLogAbstract;
152
184
  export const fieldEvidence = logxer.fieldEvidence;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exellix/graph-engine",
3
- "version": "7.3.8",
3
+ "version": "7.3.10",
4
4
  "type": "module",
5
5
  "description": "Graph executor SDK",
6
6
  "main": "dist/src/index.js",
@@ -50,12 +50,12 @@
50
50
  "access": "public"
51
51
  },
52
52
  "dependencies": {
53
- "@exellix/ai-tasks": "^8.2.2",
54
- "@x12i/activix": "8.3.1",
53
+ "@exellix/ai-tasks": "^8.2.4",
54
+ "@x12i/activix": "8.5.0",
55
55
  "@x12i/ai-profiles": "1.7.2",
56
56
  "@x12i/catalox": "5.1.3",
57
57
  "@x12i/env": "4.0.1",
58
- "@x12i/funcx": "4.2.7",
58
+ "@x12i/funcx": "4.2.8",
59
59
  "@x12i/graphenix": "2.5.0",
60
60
  "@x12i/graphenix-format": "2.0.0",
61
61
  "@x12i/logxer": "^4.6.0",