@muxoai/platform 1.4.2 → 1.4.3

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/dist/engine.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import crypto from "node:crypto";
2
2
  import { isStripeProjectsExclusiveProvider, normalizeStripeProjectsProviderSlug, wrapMuxoConnectUrl, } from "./composio-catalog.js";
3
3
  import { APPROVED_PLAN_STATUSES, approvalRequiredError, buildApprovalHumanHandoff, buildApprovalUrl, buildOAuthHumanHandoff, clampWaitSeconds, looksLikeToolSlug, unknownToolError, } from "./mcp-schema.js";
4
- import { completeCloudProvisioningWorkflow, createCloudProvisioningWorkflow, normalizeWorkflowTarget, workflowNotFoundError, } from "./durable-workflow.js";
4
+ import { completeCloudProvisioningWorkflow, createCloudProvisioningWorkflow, exportTemporalEventHistory, normalizeWorkflowTarget, rollbackDurableWorkflow as runSagaRollbackOnWorkflow, signalDurableWorkflow as sendSignalToWorkflow, workflowNotFoundError, } from "./durable-workflow.js";
5
5
  import { buildMemoryKnowledgeGraph } from "./memory-graph.js";
6
+ import { MUXO_COMMERCIAL_PLAYBOOKS, buildCommercialRoiReport, buildOtelTraceExport, buildPlaybookSagaExecution, compileWorkspacePolicyToCedarAndRego, evaluateCedarAndOpaPolicy, getCommercialPlaybook, } from "./oss-suite.js";
6
7
  import { resolveNaturalLanguageIntent, } from "./nl.js";
7
8
  import { DEFAULT_WORKSPACE_POLICY, buildLlmPolicyAndGovernanceBrief, compileNaturalLanguageGuardrailWithLlm, describePolicyInPlainEnglish, formatHumanFriendlyExplanation, parseNaturalLanguageGuardrail, simulatePolicyEvaluation, } from "./policy.js";
8
9
  import { ComposioInstantAdapter } from "./providers/composio-instant.js";
@@ -1130,18 +1131,25 @@ export class MuxoEngine {
1130
1131
  }
1131
1132
  /**
1132
1133
  * Synthesizes a Bi-Temporal Knowledge Graph (entities, active relationships, superseded historical relationships,
1133
- * Mermaid diagram, and human + technical explanations) directly from SQLite temporal_facts, memories, resources, and connections.
1134
+ * Mermaid diagram, point-in-time `asOf` time-travel, 2-hop BFS subgraph, and human + technical explanations)
1135
+ * directly from SQLite temporal_facts, memories, resources, and connections.
1134
1136
  */
1135
- async getMemoryGraph(ctx, query) {
1137
+ async getMemoryGraph(ctx, queryOrOptions) {
1136
1138
  const ws = this.store.getWorkspace(ctx.workspace.id);
1137
- const overview = await this.getMemoryOverview(ctx, query);
1139
+ const opts = typeof queryOrOptions === "string"
1140
+ ? { query: queryOrOptions }
1141
+ : queryOrOptions || {};
1142
+ const overview = await this.getMemoryOverview(ctx, opts.query);
1138
1143
  return buildMemoryKnowledgeGraph({
1139
1144
  workspace: ws,
1140
1145
  namespace: overview.namespace,
1141
- query,
1146
+ query: opts.query,
1147
+ asOf: opts.asOf || null,
1148
+ focalEntity: opts.focalEntity || null,
1149
+ maxHops: opts.maxHops,
1142
1150
  facts: this.store.listTemporalFacts(ws.id, {
1143
1151
  includeHistorical: true,
1144
- query,
1152
+ query: opts.query,
1145
1153
  limit: 100,
1146
1154
  }),
1147
1155
  resources: this.store.listResources(ws.id),
@@ -2473,6 +2481,139 @@ export class MuxoEngine {
2473
2481
  listDurableWorkflows(workspaceId) {
2474
2482
  return Array.from(this.durableWorkflows.values()).filter((w) => w.workspaceId === workspaceId);
2475
2483
  }
2484
+ getDurableWorkflow(ctx, workflowId) {
2485
+ const wf = this.durableWorkflows.get(workflowId);
2486
+ if (!wf || wf.workspaceId !== ctx.workspace.id) {
2487
+ throw workflowNotFoundError(workflowId);
2488
+ }
2489
+ return wf;
2490
+ }
2491
+ getDurableWorkflowHistory(ctx, workflowId) {
2492
+ const wf = this.getDurableWorkflow(ctx, workflowId);
2493
+ return exportTemporalEventHistory(wf);
2494
+ }
2495
+ signalDurableWorkflow(ctx, workflowId, params) {
2496
+ const wf = this.getDurableWorkflow(ctx, workflowId);
2497
+ return sendSignalToWorkflow(wf, params);
2498
+ }
2499
+ rollbackDurableWorkflow(ctx, workflowId, params) {
2500
+ const wf = this.getDurableWorkflow(ctx, workflowId);
2501
+ return runSagaRollbackOnWorkflow(wf, params);
2502
+ }
2503
+ /**
2504
+ * AWS Cedar & Open Policy Agent (OPA / Rego) Policy-as-Code Compiler & Evaluator.
2505
+ */
2506
+ getPolicyAsCodeBundle(ctx) {
2507
+ const ws = this.store.getWorkspace(ctx.workspace.id);
2508
+ return compileWorkspacePolicyToCedarAndRego(ws.policy, ws);
2509
+ }
2510
+ async evaluatePolicyAsCode(ctx, params) {
2511
+ const ws = this.store.getWorkspace(ctx.workspace.id);
2512
+ let resolvedSlug = String(params.toolSlug || "instant.web.search").trim();
2513
+ let riskLevel = params.riskLevel;
2514
+ let creditCost = params.creditCost;
2515
+ let lane = params.lane;
2516
+ try {
2517
+ const tool = await this.inspectTool(resolvedSlug);
2518
+ resolvedSlug = tool.slug;
2519
+ if (!riskLevel)
2520
+ riskLevel = tool.riskLevel;
2521
+ if (typeof creditCost !== "number")
2522
+ creditCost = tool.creditCost;
2523
+ if (!lane)
2524
+ lane = tool.lane;
2525
+ }
2526
+ catch {
2527
+ // Fall back to slug prefix heuristics
2528
+ }
2529
+ return evaluateCedarAndOpaPolicy({
2530
+ policy: ws.policy,
2531
+ workspace: ws,
2532
+ toolSlug: resolvedSlug,
2533
+ input: params.input || {},
2534
+ riskLevel,
2535
+ creditCost,
2536
+ lane,
2537
+ });
2538
+ }
2539
+ /**
2540
+ * OpenTelemetry (OTel GenAI / MCP Semantic Conventions) & Langfuse Trace Exporter.
2541
+ */
2542
+ exportOtelTraces(ctx, limit = 25) {
2543
+ const ws = this.store.getWorkspace(ctx.workspace.id);
2544
+ const runs = this.store.listRuns(ws.id, limit);
2545
+ const receipts = this.store.listReceipts(ws.id, limit);
2546
+ const plans = this.store.listPlans(ws.id, limit);
2547
+ return buildOtelTraceExport({
2548
+ workspace: ws,
2549
+ runs,
2550
+ receipts,
2551
+ plans,
2552
+ });
2553
+ }
2554
+ /**
2555
+ * Turnkey Commercial Business Saga Playbooks.
2556
+ */
2557
+ listCommercialPlaybooks() {
2558
+ return MUXO_COMMERCIAL_PLAYBOOKS;
2559
+ }
2560
+ runCommercialPlaybook(ctx, params) {
2561
+ const ws = this.store.getWorkspace(ctx.workspace.id);
2562
+ const playbook = getCommercialPlaybook(params.slug);
2563
+ if (!playbook) {
2564
+ const err = new Error(`Unknown commercial playbook '${params.slug}'. Available playbooks: ${MUXO_COMMERCIAL_PLAYBOOKS.map((p) => p.slug).join(", ")}`);
2565
+ err.statusCode = 404;
2566
+ err.code = "PLAYBOOK_NOT_FOUND";
2567
+ throw err;
2568
+ }
2569
+ const wfId = `wf_saga_${Date.now()}_${crypto.randomBytes(4).toString("hex")}`;
2570
+ const executed = buildPlaybookSagaExecution({
2571
+ id: wfId,
2572
+ workspaceId: ws.id,
2573
+ workspaceSlug: ws.slug,
2574
+ playbook,
2575
+ input: params.input || {},
2576
+ });
2577
+ this.durableWorkflows.set(wfId, executed.workflow);
2578
+ const savedFact = this.store.upsertTemporalFact({
2579
+ workspaceId: ws.id,
2580
+ subject: executed.temporalFact.subject,
2581
+ predicate: executed.temporalFact.predicate,
2582
+ object: executed.temporalFact.object,
2583
+ summary: executed.temporalFact.summary,
2584
+ confidence: 0.99,
2585
+ sourceKind: "execution_outcome",
2586
+ sourceReferenceId: executed.temporalFact.episodeId,
2587
+ });
2588
+ return {
2589
+ ok: true,
2590
+ playbook: executed.playbook,
2591
+ workflow: executed.workflow,
2592
+ temporalFact: savedFact,
2593
+ };
2594
+ }
2595
+ /**
2596
+ * FinOps & Commercial ROI Unit-Economics Analytics.
2597
+ */
2598
+ getCommercialRoiReport(ctx) {
2599
+ const ws = this.store.getWorkspace(ctx.workspace.id);
2600
+ const runs = this.store.listRuns(ws.id, 100);
2601
+ const plans = this.store.listPlans(ws.id, 100);
2602
+ const resources = this.store.listResources(ws.id);
2603
+ const byokKeys = this.store.listByokKeys(ws.id);
2604
+ const temporalFacts = this.store.listTemporalFacts(ws.id, {
2605
+ includeHistorical: false,
2606
+ limit: 100,
2607
+ });
2608
+ return buildCommercialRoiReport({
2609
+ workspace: ws,
2610
+ runs,
2611
+ plans,
2612
+ resources,
2613
+ byokKeys,
2614
+ temporalFacts,
2615
+ });
2616
+ }
2476
2617
  /**
2477
2618
  * Hybrid Semantic Vector + Bi-Temporal Recall (`@cf/baai/bge-base-en-v1.5` + Vectorize model).
2478
2619
  */
package/dist/index.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  export { getConfig, ROOT_DIR, type MuxoConfig } from "./config.js";
2
+ export * from "./durable-workflow.js";
2
3
  export { MuxoEngine, type ExecutionContext } from "./engine.js";
3
4
  export { handleJsonRpcMcpRequest, MUXO_MCP_TOOLS, type McpToolDefinition } from "./mcp.js";
5
+ export * from "./memory-graph.js";
6
+ export * from "./oss-suite.js";
4
7
  export { MuxoClient, type MuxoClientOptions } from "./sdk.js";
5
8
  export { createMuxoServer } from "./server.js";
6
9
  export { MuxoStore } from "./store.js";
package/dist/index.js CHANGED
@@ -1,6 +1,9 @@
1
1
  export { getConfig, ROOT_DIR } from "./config.js";
2
+ export * from "./durable-workflow.js";
2
3
  export { MuxoEngine } from "./engine.js";
3
4
  export { handleJsonRpcMcpRequest, MUXO_MCP_TOOLS } from "./mcp.js";
5
+ export * from "./memory-graph.js";
6
+ export * from "./oss-suite.js";
4
7
  export { MuxoClient } from "./sdk.js";
5
8
  export { createMuxoServer } from "./server.js";
6
9
  export { MuxoStore } from "./store.js";
@@ -1057,6 +1057,11 @@ export const MUXO_MCP_TOOLS = [
1057
1057
  "set_guardrail",
1058
1058
  "reset_guardrails",
1059
1059
  "check_policy",
1060
+ "cedar_policy",
1061
+ "otel_traces",
1062
+ "roi_analytics",
1063
+ "playbooks",
1064
+ "run_playbook",
1060
1065
  "kill_switch",
1061
1066
  "governance_status",
1062
1067
  "scan_context",
@@ -1064,7 +1069,7 @@ export const MUXO_MCP_TOOLS = [
1064
1069
  "switch_workspace",
1065
1070
  "onboard_team",
1066
1071
  ],
1067
- description: "Control-plane operation: 'inspect' (default), 'remember' (save fact/preference), 'recall' (list memories), 'forget' (delete matching memories), 'memory_graph' (bi-temporal entity/relationship lineage), 'set_guardrail' (apply plain-English or structured parameter_rules guardrail), 'reset_guardrails' (restore default safe guardrails), 'check_policy' (dry-run evaluate tool_slug + input against policy and parameter guardrails before planning), 'kill_switch' (pause/resume agent execution), 'governance_status' (Microsoft 9-module status + LLM policy brief), 'scan_context' (OWASP ASI-06 check), 'list_workspaces', 'switch_workspace', or 'onboard_team'",
1072
+ description: "Control-plane operation: 'inspect' (default), 'remember' (save fact/preference), 'recall' (list memories), 'forget' (delete matching memories), 'memory_graph' (Graphiti bi-temporal entity/relationship lineage with optional as_of time-travel & focal_entity 2-hop BFS), 'set_guardrail' (apply plain-English or structured parameter_rules guardrail), 'reset_guardrails' (restore default safe guardrails), 'check_policy' (dry-run evaluate tool_slug + input against policy and parameter guardrails before planning), 'cedar_policy' (compile/evaluate AWS Cedar & OPA Rego policy-as-code), 'otel_traces' (export W3C OpenTelemetry GenAI/MCP spans & Langfuse batch), 'roi_analytics' (FinOps & BYOK unit-economics ROI report), 'playbooks' / 'run_playbook' (list or execute turnkey multi-step Temporal Saga playbooks), 'kill_switch' (pause/resume agent execution), 'governance_status' (Microsoft 9-module status + LLM policy brief), 'scan_context' (OWASP ASI-06 check), 'list_workspaces', 'switch_workspace', or 'onboard_team'",
1068
1073
  },
1069
1074
  prompt: {
1070
1075
  type: "string",
@@ -1161,6 +1166,22 @@ export const MUXO_MCP_TOOLS = [
1161
1166
  type: "boolean",
1162
1167
  description: "If true, attach the Bi-Temporal Knowledge Graph (nodes, activeEdges, historicalEdges, mermaid) to the context response",
1163
1168
  },
1169
+ as_of: {
1170
+ type: "string",
1171
+ description: "Optional ISO-8601 timestamp for Graphiti point-in-time ('as-of') bi-temporal knowledge graph reconstruction",
1172
+ },
1173
+ focal_entity: {
1174
+ type: "string",
1175
+ description: "Optional entity ID or label for k-hop BFS neighborhood subgraph extraction",
1176
+ },
1177
+ max_hops: {
1178
+ type: "number",
1179
+ description: "Maximum BFS traversal hops around focal_entity (1 to 4, default 2)",
1180
+ },
1181
+ slug: {
1182
+ type: "string",
1183
+ description: "Optional playbook slug when action='run_playbook' (e.g. 'fullstack-cloud-bootstrap', 'competitor-seo-growth-brief', 'incident-containment-audit', 'customer-release-readiness')",
1184
+ },
1164
1185
  plan_id: {
1165
1186
  type: "string",
1166
1187
  description: "Optional plan ID to inspect context for",
package/dist/mcp.js CHANGED
@@ -223,8 +223,53 @@ export async function dispatchMcpToolCall(engine, ctx, toolName, args = {}) {
223
223
  : Array.isArray(args.parameterRules)
224
224
  ? args.parameterRules
225
225
  : undefined;
226
+ const asOf = args.as_of || args.asOf ? String(args.as_of || args.asOf) : null;
227
+ const focalEntity = args.focal_entity || args.focalEntity
228
+ ? String(args.focal_entity || args.focalEntity)
229
+ : null;
230
+ const rawMaxHops = args.max_hops ?? args.maxHops;
231
+ const maxHops = rawMaxHops !== undefined && rawMaxHops !== null
232
+ ? Number(rawMaxHops)
233
+ : undefined;
226
234
  if (action === "memory_graph") {
227
- return engine.getMemoryGraph(ctx, query);
235
+ return engine.getMemoryGraph(ctx, {
236
+ query,
237
+ asOf,
238
+ focalEntity,
239
+ maxHops,
240
+ });
241
+ }
242
+ if (action === "cedar_policy" || action === "policy_as_code") {
243
+ if (args.tool_slug || args.slug) {
244
+ return engine.evaluatePolicyAsCode(ctx, {
245
+ toolSlug: String(args.tool_slug || args.slug),
246
+ input: (args.input || args.arguments) || {},
247
+ });
248
+ }
249
+ return engine.getPolicyAsCodeBundle(ctx);
250
+ }
251
+ if (action === "otel_traces" || action === "telemetry") {
252
+ return engine.exportOtelTraces(ctx, Number(args.limit || 25));
253
+ }
254
+ if (action === "roi_analytics" || action === "roi") {
255
+ return engine.getCommercialRoiReport(ctx);
256
+ }
257
+ if (action === "playbooks") {
258
+ return {
259
+ ok: true,
260
+ playbooks: engine.listCommercialPlaybooks(),
261
+ };
262
+ }
263
+ if (action === "run_playbook") {
264
+ const slug = String(args.slug ||
265
+ args.playbook ||
266
+ query ||
267
+ prompt ||
268
+ "fullstack-cloud-bootstrap").trim();
269
+ return engine.runCommercialPlaybook(ctx, {
270
+ slug,
271
+ input: (args.input || args.arguments) || {},
272
+ });
228
273
  }
229
274
  if (action === "governance_status") {
230
275
  return engine.getGovernanceOverview(ctx);
@@ -343,7 +388,12 @@ export async function dispatchMcpToolCall(engine, ctx, toolName, args = {}) {
343
388
  }
344
389
  const runCtx = await engine.getRunContext(ctx, query);
345
390
  if (args.include_graph === true) {
346
- const memoryGraph = await engine.getMemoryGraph(ctx, query);
391
+ const memoryGraph = await engine.getMemoryGraph(ctx, {
392
+ query,
393
+ asOf,
394
+ focalEntity,
395
+ maxHops,
396
+ });
347
397
  return { ...runCtx, memoryGraph };
348
398
  }
349
399
  return runCtx;
@@ -425,7 +475,16 @@ export async function dispatchMcpToolCall(engine, ctx, toolName, args = {}) {
425
475
  return engine.forgetWorkspaceMemoryMatching(ctx, query);
426
476
  }
427
477
  case "memory_graph":
428
- return engine.getMemoryGraph(ctx, args.query ? String(args.query) : undefined);
478
+ return engine.getMemoryGraph(ctx, {
479
+ query: args.query ? String(args.query) : undefined,
480
+ asOf: args.as_of || args.asOf ? String(args.as_of || args.asOf) : null,
481
+ focalEntity: args.focal_entity || args.focalEntity
482
+ ? String(args.focal_entity || args.focalEntity)
483
+ : null,
484
+ maxHops: args.max_hops !== undefined || args.maxHops !== undefined
485
+ ? Number(args.max_hops ?? args.maxHops)
486
+ : undefined,
487
+ });
429
488
  case "set_guardrail":
430
489
  case "configure_guardrails": {
431
490
  const rawParamRules = Array.isArray(args.parameter_rules)
@@ -1,4 +1,4 @@
1
- import type { ConnectionRecord, GovernancePolicy, MemoryKnowledgeGraph, ProvisionedResource, TemporalGraphFact } from "./types.js";
1
+ import type { ConnectionRecord, GovernancePolicy, MemoryGraphEdge, MemoryGraphNode, MemoryKnowledgeGraph, ProvisionedResource, TemporalGraphFact } from "./types.js";
2
2
  export interface MemoryGraphInput {
3
3
  workspace: {
4
4
  id: string;
@@ -9,13 +9,37 @@ export interface MemoryGraphInput {
9
9
  };
10
10
  namespace: string;
11
11
  query?: string;
12
+ asOf?: string | null;
13
+ focalEntity?: string | null;
14
+ maxHops?: number;
12
15
  facts: TemporalGraphFact[];
13
16
  resources: Array<Pick<ProvisionedResource, "resourceName" | "status" | "providerName" | "serviceSlug">>;
14
17
  activeConnections: Array<Pick<ConnectionRecord, "id" | "providerSlug" | "providerName" | "createdAt">>;
15
18
  memoriesCount: number;
16
19
  }
17
20
  /**
18
- * Builds the bi-temporal knowledge graph (nodes, active/historical edges, Mermaid preview)
21
+ * Graphiti-style Point-in-Time ("As-Of") Bi-Temporal Fact Filter.
22
+ * Reconstructs which facts were active at `asOf` vs. already retired by `asOf`,
23
+ * excluding facts created after `asOf`.
24
+ */
25
+ export declare function filterFactsAsOf(facts: TemporalGraphFact[], asOf?: string | null): {
26
+ scopedFacts: TemporalGraphFact[];
27
+ isActiveAtPointInTime: (fact: TemporalGraphFact) => boolean;
28
+ };
29
+ /**
30
+ * Graphiti-style Multi-Hop Subgraph Community Traversal (BFS).
31
+ * Finds all nodes and edges within `maxHops` (default 2) of a focal entity.
32
+ */
33
+ export declare function traverseSubgraphBfs(nodes: MemoryGraphNode[], edges: MemoryGraphEdge[], focalQuery: string, maxHops?: number): {
34
+ focalEntity: string;
35
+ maxHops: number;
36
+ nodes: MemoryGraphNode[];
37
+ edges: MemoryGraphEdge[];
38
+ hopDistances: Record<string, number>;
39
+ };
40
+ /**
41
+ * Builds the Graphiti-inspired Bi-Temporal Knowledge Graph (nodes, active/historical edges,
42
+ * episodic provenance, point-in-time `asOf` time-travel, 2-hop BFS subgraph, and Mermaid preview)
19
43
  * from runtime-agnostic inputs so the Node engine and the Edge Worker render identical graphs.
20
44
  */
21
45
  export declare function buildMemoryKnowledgeGraph(input: MemoryGraphInput): MemoryKnowledgeGraph;
@@ -1,11 +1,102 @@
1
1
  import { formatHumanFriendlyExplanation } from "./policy.js";
2
2
  /**
3
- * Builds the bi-temporal knowledge graph (nodes, active/historical edges, Mermaid preview)
3
+ * Graphiti-style Point-in-Time ("As-Of") Bi-Temporal Fact Filter.
4
+ * Reconstructs which facts were active at `asOf` vs. already retired by `asOf`,
5
+ * excluding facts created after `asOf`.
6
+ */
7
+ export function filterFactsAsOf(facts, asOf) {
8
+ if (!asOf || !asOf.trim()) {
9
+ return {
10
+ scopedFacts: facts,
11
+ isActiveAtPointInTime: (fact) => fact.validTo === null,
12
+ };
13
+ }
14
+ const asOfMs = Date.parse(asOf);
15
+ if (!Number.isFinite(asOfMs)) {
16
+ return {
17
+ scopedFacts: facts,
18
+ isActiveAtPointInTime: (fact) => fact.validTo === null,
19
+ };
20
+ }
21
+ const scopedFacts = facts.filter((fact) => {
22
+ const validFromMs = Date.parse(fact.validFrom || fact.createdAt);
23
+ return !Number.isFinite(validFromMs) || validFromMs <= asOfMs;
24
+ });
25
+ return {
26
+ scopedFacts,
27
+ isActiveAtPointInTime: (fact) => {
28
+ if (!fact.validTo)
29
+ return true;
30
+ const validToMs = Date.parse(fact.validTo);
31
+ return Number.isFinite(validToMs) && validToMs > asOfMs;
32
+ },
33
+ };
34
+ }
35
+ /**
36
+ * Graphiti-style Multi-Hop Subgraph Community Traversal (BFS).
37
+ * Finds all nodes and edges within `maxHops` (default 2) of a focal entity.
38
+ */
39
+ export function traverseSubgraphBfs(nodes, edges, focalQuery, maxHops = 2) {
40
+ const needle = focalQuery.trim().toLowerCase();
41
+ const clampedHops = Math.min(4, Math.max(1, Number(maxHops) || 2));
42
+ const seedNode = nodes.find((n) => n.id.toLowerCase() === needle) ||
43
+ nodes.find((n) => n.label.toLowerCase() === needle) ||
44
+ nodes.find((n) => n.id.toLowerCase().includes(needle) ||
45
+ n.label.toLowerCase().includes(needle)) ||
46
+ nodes[0];
47
+ if (!seedNode) {
48
+ return {
49
+ focalEntity: focalQuery,
50
+ maxHops: clampedHops,
51
+ nodes: [],
52
+ edges: [],
53
+ hopDistances: {},
54
+ };
55
+ }
56
+ const hopDistances = { [seedNode.id]: 0 };
57
+ const queue = [
58
+ { nodeId: seedNode.id, depth: 0 },
59
+ ];
60
+ while (queue.length > 0) {
61
+ const current = queue.shift();
62
+ if (current.depth >= clampedHops)
63
+ continue;
64
+ for (const edge of edges) {
65
+ let neighborId = null;
66
+ if (edge.source === current.nodeId)
67
+ neighborId = edge.target;
68
+ else if (edge.target === current.nodeId)
69
+ neighborId = edge.source;
70
+ if (neighborId && hopDistances[neighborId] === undefined) {
71
+ hopDistances[neighborId] = current.depth + 1;
72
+ queue.push({ nodeId: neighborId, depth: current.depth + 1 });
73
+ }
74
+ }
75
+ }
76
+ const subNodes = nodes
77
+ .filter((n) => hopDistances[n.id] !== undefined)
78
+ .map((n) => ({ ...n, hopDistance: hopDistances[n.id] }));
79
+ const subEdges = edges.filter((e) => hopDistances[e.source] !== undefined &&
80
+ hopDistances[e.target] !== undefined);
81
+ return {
82
+ focalEntity: seedNode.id,
83
+ maxHops: clampedHops,
84
+ nodes: subNodes,
85
+ edges: subEdges,
86
+ hopDistances,
87
+ };
88
+ }
89
+ /**
90
+ * Builds the Graphiti-inspired Bi-Temporal Knowledge Graph (nodes, active/historical edges,
91
+ * episodic provenance, point-in-time `asOf` time-travel, 2-hop BFS subgraph, and Mermaid preview)
4
92
  * from runtime-agnostic inputs so the Node engine and the Edge Worker render identical graphs.
5
93
  */
6
94
  export function buildMemoryKnowledgeGraph(input) {
7
95
  const ws = input.workspace;
8
96
  const query = input.query;
97
+ const asOf = input.asOf ?? null;
98
+ const maxHops = Math.min(4, Math.max(1, Number(input.maxHops || 2)));
99
+ const { scopedFacts, isActiveAtPointInTime } = filterFactsAsOf(input.facts, asOf);
9
100
  const nodeMap = new Map();
10
101
  const rootId = `workspace:${ws.slug}`;
11
102
  nodeMap.set(rootId, {
@@ -34,7 +125,9 @@ export function buildMemoryKnowledgeGraph(input) {
34
125
  ? "tool"
35
126
  : id.startsWith("guardrail:")
36
127
  ? "guardrail"
37
- : fallbackKind;
128
+ : id.startsWith("episode:")
129
+ ? "episode"
130
+ : fallbackKind;
38
131
  const label = id.includes(":")
39
132
  ? id.split(":").slice(1).join(":")
40
133
  : id.length > 36
@@ -50,8 +143,11 @@ export function buildMemoryKnowledgeGraph(input) {
50
143
  nodeMap.set(id, created);
51
144
  return created;
52
145
  };
53
- const edges = input.facts.map((fact) => {
54
- const status = fact.validTo === null ? "active" : "historical";
146
+ let episodesCount = 0;
147
+ const edges = scopedFacts.map((fact) => {
148
+ const status = isActiveAtPointInTime(fact)
149
+ ? "active"
150
+ : "historical";
55
151
  const objKind = fact.sourceKind === "preference"
56
152
  ? "preference"
57
153
  : fact.sourceKind === "policy_decision"
@@ -61,6 +157,15 @@ export function buildMemoryKnowledgeGraph(input) {
61
157
  : "entity";
62
158
  ensureNode(fact.subject, status, "entity", fact.subject);
63
159
  ensureNode(fact.object, status, objKind, fact.summary);
160
+ const episodeId = fact.episodeId || fact.sourceReferenceId || null;
161
+ if (episodeId) {
162
+ const epNodeId = episodeId.startsWith("episode:")
163
+ ? episodeId
164
+ : `episode:${episodeId}`;
165
+ if (!nodeMap.has(epNodeId))
166
+ episodesCount += 1;
167
+ ensureNode(epNodeId, status, "episode", `Episodic provenance (${episodeId})`);
168
+ }
64
169
  return {
65
170
  id: fact.id,
66
171
  source: fact.subject,
@@ -72,7 +177,10 @@ export function buildMemoryKnowledgeGraph(input) {
72
177
  confidence: fact.confidence,
73
178
  status,
74
179
  validFrom: fact.validFrom,
75
- validTo: fact.validTo,
180
+ validTo: status === "active" && asOf ? null : fact.validTo,
181
+ transactionFrom: fact.transactionFrom || fact.createdAt,
182
+ transactionTo: fact.transactionTo ?? fact.validTo,
183
+ episodeId,
76
184
  supersededById: fact.supersededById ?? null,
77
185
  sourceKind: fact.sourceKind,
78
186
  sourceReferenceId: fact.sourceReferenceId ?? null,
@@ -95,6 +203,9 @@ export function buildMemoryKnowledgeGraph(input) {
95
203
  status: "active",
96
204
  validFrom: conn.createdAt,
97
205
  validTo: null,
206
+ transactionFrom: conn.createdAt,
207
+ transactionTo: null,
208
+ episodeId: conn.id,
98
209
  supersededById: null,
99
210
  sourceKind: "system",
100
211
  sourceReferenceId: conn.id,
@@ -104,6 +215,10 @@ export function buildMemoryKnowledgeGraph(input) {
104
215
  const activeEdges = edges.filter((e) => e.status === "active");
105
216
  const historicalEdges = edges.filter((e) => e.status === "historical");
106
217
  const nodes = Array.from(nodeMap.values());
218
+ const focalTarget = input.focalEntity || query || null;
219
+ const subgraph = focalTarget
220
+ ? traverseSubgraphBfs(nodes, edges, focalTarget, maxHops)
221
+ : null;
107
222
  const safeMermaidId = (raw, idx) => `N${idx}_${raw.replace(/[^a-zA-Z0-9]/g, "_").slice(0, 14)}`;
108
223
  const nodeIndex = new Map();
109
224
  nodes.slice(0, 24).forEach((n, idx) => {
@@ -128,7 +243,7 @@ export function buildMemoryKnowledgeGraph(input) {
128
243
  mermaidLines.push(` ${srcId} -.->|RETIRED: ${pred}| ${tgtId}`);
129
244
  }
130
245
  }
131
- const headline = `Bi-Temporal Knowledge Graph (${ws.name}): ${activeEdges.length} active relationship${activeEdges.length === 1 ? "" : "s"}, ${historicalEdges.length} superseded historical relationship${historicalEdges.length === 1 ? "" : "s"} across ${nodes.length} node${nodes.length === 1 ? "" : "s"}.`;
246
+ const headline = `Bi-Temporal Knowledge Graph (${ws.name}${asOf ? ` as of ${asOf}` : ""}): ${activeEdges.length} active relationship${activeEdges.length === 1 ? "" : "s"}, ${historicalEdges.length} superseded historical relationship${historicalEdges.length === 1 ? "" : "s"} across ${nodes.length} node${nodes.length === 1 ? "" : "s"}.`;
132
247
  const activePreview = activeEdges
133
248
  .slice(0, 4)
134
249
  .map((e) => `${e.source} --[${e.predicate}]--> ${e.target}`)
@@ -142,7 +257,7 @@ export function buildMemoryKnowledgeGraph(input) {
142
257
  ? `Time-travel lineage: ${historicalEdges.length} older fact${historicalEdges.length === 1 ? " was" : "s were"} automatically retired when newer workspace settings or facts replaced them.`
143
258
  : "No superseded historical facts yet.",
144
259
  ].join(" ");
145
- const technical = `workspace=${ws.id} | namespace=${input.namespace} | nodes=${nodes.length} | activeEdges=${activeEdges.length} | historicalEdges=${historicalEdges.length} | memories=${input.memoriesCount} | activePreview=[${activePreview || "none"}]`;
260
+ const technical = `workspace=${ws.id} | namespace=${input.namespace} | asOf=${asOf || "latest"} | nodes=${nodes.length} | activeEdges=${activeEdges.length} | historicalEdges=${historicalEdges.length} | episodes=${episodesCount} | memories=${input.memoriesCount} | activePreview=[${activePreview || "none"}]`;
146
261
  const explanation = formatHumanFriendlyExplanation({
147
262
  headline,
148
263
  nonTechnical,
@@ -154,11 +269,15 @@ export function buildMemoryKnowledgeGraph(input) {
154
269
  workspaceSlug: ws.slug,
155
270
  namespace: input.namespace,
156
271
  generatedAt: new Date().toISOString(),
272
+ asOf,
273
+ focalEntity: subgraph?.focalEntity ?? null,
274
+ maxHops,
157
275
  query: query ?? null,
158
276
  nodes,
159
277
  edges,
160
278
  activeEdges,
161
279
  historicalEdges,
280
+ subgraph,
162
281
  stats: {
163
282
  nodesCount: nodes.length,
164
283
  activeEdgesCount: activeEdges.length,
@@ -166,6 +285,7 @@ export function buildMemoryKnowledgeGraph(input) {
166
285
  memoriesCount: input.memoriesCount,
167
286
  resourcesCount: input.resources.length,
168
287
  connectionsCount: input.activeConnections.length,
288
+ episodesCount,
169
289
  },
170
290
  mermaid: mermaidLines.join("\n"),
171
291
  explanation,