From 4de8b8f9876a325b33cf412d9af7ad867c0e764c Mon Sep 17 00:00:00 2001 From: Vorflux AI Date: Sat, 28 Mar 2026 23:15:28 +0000 Subject: [PATCH] fix: use backend memoryRelations for edge computation in MCP app Updated MCP app edge computation to use memoryRelations from backend instead of fabricating extends edges from spaceId grouping. Falls back to parentMemoryId for backwards compatibility. --- apps/mcp/src/ui/mcp-app.ts | 58 ++++++--------- .../memory-graph/hooks/use-graph-api.ts | 13 +++- packages/memory-graph/src/api-types.ts | 17 ++++- .../memory-graph/src/hooks/use-graph-data.ts | 71 ++++++++----------- packages/memory-graph/src/mock-data.ts | 19 +++++ packages/memory-graph/src/types.ts | 12 ++++ 6 files changed, 111 insertions(+), 79 deletions(-) diff --git a/apps/mcp/src/ui/mcp-app.ts b/apps/mcp/src/ui/mcp-app.ts index 504f190d..ba20d89a 100644 --- a/apps/mcp/src/ui/mcp-app.ts +++ b/apps/mcp/src/ui/mcp-app.ts @@ -37,6 +37,8 @@ interface GraphApiMemory { rootMemoryId: string | null createdAt: string updatedAt: string + relation?: "updates" | "extends" | "derives" | null + memoryRelations?: Record | null } interface GraphApiDocument { @@ -174,9 +176,6 @@ function transformData(data: ToolResultData): { const nodeIds = new Set() const SPREAD = 50 - // Group documents by spaceId for extends edges - const spaceGroups = new Map() - for (const doc of data.documents) { const pos = initialPosition(doc.id, SPREAD) nodes.push({ @@ -214,42 +213,31 @@ function transformData(data: ToolResultData): { } as MemoryNode) nodeIds.add(mem.id) - // Derives link + // Derives link (doc -> memory) links.push({ source: doc.id, target: mem.id, edgeType: "derives" }) - // Updates link - if (mem.parentMemoryId && nodeIds.has(mem.parentMemoryId)) { - links.push({ - source: mem.parentMemoryId, - target: mem.id, - edgeType: "updates", - }) + // Memory-to-memory relation edges from backend data. + // Uses memoryRelations as primary source, falls back to parentMemoryId. + let relations: Record = {} + if ( + mem.memoryRelations && + typeof mem.memoryRelations === "object" && + Object.keys(mem.memoryRelations).length > 0 + ) { + relations = mem.memoryRelations + } else if (mem.parentMemoryId) { + relations = { [mem.parentMemoryId]: "updates" } } - // Track space groups for extends edges - if (mem.spaceId) { - const group = spaceGroups.get(mem.spaceId) - if (group) group.push(doc.id) - else spaceGroups.set(mem.spaceId, [doc.id]) - } - } - } - - // Extends edges between documents sharing a space - const addedEdges = new Set() - for (const docIds of spaceGroups.values()) { - const unique = [...new Set(docIds)] - for (let i = 0; i < unique.length; i++) { - for (let j = i + 1; j < unique.length; j++) { - const key = `${unique[i]}:${unique[j]}` - if (!addedEdges.has(key)) { - addedEdges.add(key) - links.push({ - source: unique[i]!, - target: unique[j]!, - edgeType: "extends", - }) - } + for (const [targetId, relationType] of Object.entries(relations)) { + if (!nodeIds.has(targetId)) continue + const edgeType = + relationType === "updates" || + relationType === "extends" || + relationType === "derives" + ? relationType + : "updates" + links.push({ source: targetId, target: mem.id, edgeType }) } } } diff --git a/apps/web/components/memory-graph/hooks/use-graph-api.ts b/apps/web/components/memory-graph/hooks/use-graph-api.ts index 118ed465..3984088d 100644 --- a/apps/web/components/memory-graph/hooks/use-graph-api.ts +++ b/apps/web/components/memory-graph/hooks/use-graph-api.ts @@ -18,6 +18,7 @@ interface UseGraphApiOptions { interface ApiMemoryEntry { id: string memory: string + content?: string | null spaceId: string isStatic?: boolean isLatest?: boolean @@ -29,6 +30,11 @@ interface ApiMemoryEntry { rootMemoryId?: string | null createdAt: string updatedAt: string + relation?: "updates" | "extends" | "derives" | null + updatesMemoryId?: string | null + nextVersionId?: string | null + memoryRelations?: Record | null + spaceContainerTag?: string | null } interface ApiDocument { @@ -54,7 +60,7 @@ interface ApiDocumentsResponse { function toGraphMemory(mem: ApiMemoryEntry): GraphApiMemory { return { id: mem.id, - memory: mem.memory, + memory: mem.memory ?? mem.content ?? "", isStatic: mem.isStatic ?? false, spaceId: mem.spaceId ?? "", isLatest: mem.isLatest ?? true, @@ -66,6 +72,11 @@ function toGraphMemory(mem: ApiMemoryEntry): GraphApiMemory { rootMemoryId: mem.rootMemoryId ?? null, createdAt: mem.createdAt, updatedAt: mem.updatedAt, + relation: mem.relation ?? null, + updatesMemoryId: mem.updatesMemoryId ?? null, + nextVersionId: mem.nextVersionId ?? null, + memoryRelations: mem.memoryRelations ?? null, + spaceContainerTag: mem.spaceContainerTag ?? null, } } diff --git a/packages/memory-graph/src/api-types.ts b/packages/memory-graph/src/api-types.ts index 48160d8f..631ae715 100644 --- a/packages/memory-graph/src/api-types.ts +++ b/packages/memory-graph/src/api-types.ts @@ -1,9 +1,12 @@ +export type MemoryRelation = "updates" | "extends" | "derives" + export interface MemoryEntry { id: string - content: string + content: string | null + memory?: string | null createdAt: string updatedAt: string - spaceId?: string + spaceId?: string | null embedding?: number[] isStatic?: boolean isForgotten?: boolean @@ -13,6 +16,16 @@ export interface MemoryEntry { parentMemoryId?: string | null rootMemoryId?: string | null isLatest?: boolean + // Relation fields from backend + relation?: MemoryRelation | null + updatesMemoryId?: string | null + nextVersionId?: string | null + memoryRelations?: Record | null + // Source/join fields + sourceAddedAt?: string | null + sourceRelevanceScore?: number | null + sourceMetadata?: Record | null + spaceContainerTag?: string | null } export interface DocumentWithMemories { diff --git a/packages/memory-graph/src/hooks/use-graph-data.ts b/packages/memory-graph/src/hooks/use-graph-data.ts index 68d6060d..d539e86c 100644 --- a/packages/memory-graph/src/hooks/use-graph-data.ts +++ b/packages/memory-graph/src/hooks/use-graph-data.ts @@ -180,7 +180,7 @@ export function useGraphData( for (const mem of doc.memories) allNodeIds.add(mem.id) } - // Derives edges (doc -> memory) + // 1. Derives edges: document -> memory (structural) for (const doc of documents) { for (const mem of doc.memories) { result.push({ @@ -193,50 +193,39 @@ export function useGraphData( } } - // Updates edges (version chain) + // 2. Memory-to-memory relation edges from backend data. + // Uses memoryRelations (Record) as primary source, + // falls back to parentMemoryId for legacy data. for (const doc of documents) { for (const mem of doc.memories) { - if (mem.parentMemoryId && allNodeIds.has(mem.parentMemoryId)) { - result.push({ - id: `ver-${mem.parentMemoryId}-${mem.id}`, - source: mem.parentMemoryId, - target: mem.id, - visualProps: getEdgeVisualProps("updates"), - edgeType: "updates", - }) - } - } - } + let relations: Record = {} - // Extends edges: connect documents that share a spaceId - const spaceGroups = new Map() - for (const doc of documents) { - for (const mem of doc.memories) { - const group = spaceGroups.get(mem.spaceId) - if (group) { - if (!group.includes(doc.id)) group.push(doc.id) - } else { - spaceGroups.set(mem.spaceId, [doc.id]) + if ( + mem.memoryRelations && + typeof mem.memoryRelations === "object" && + Object.keys(mem.memoryRelations).length > 0 + ) { + relations = mem.memoryRelations + } else if (mem.parentMemoryId) { + // Legacy fallback: parentMemoryId implies "updates" + relations = { [mem.parentMemoryId]: "updates" } } - } - } - const addedPairs = new Set() - for (const docIds of spaceGroups.values()) { - for (let i = 0; i < docIds.length; i++) { - for (let j = i + 1; j < docIds.length; j++) { - const a = docIds[i]! - const b = docIds[j]! - const key = a < b ? `${a}:${b}` : `${b}:${a}` - if (!addedPairs.has(key)) { - addedPairs.add(key) - result.push({ - id: `ss-${key}`, - source: a, - target: b, - visualProps: getEdgeVisualProps("extends"), - edgeType: "extends", - }) - } + + for (const [targetId, relationType] of Object.entries(relations)) { + if (!allNodeIds.has(targetId)) continue + const edgeType = + relationType === "updates" || + relationType === "extends" || + relationType === "derives" + ? relationType + : "updates" + result.push({ + id: `rel-${targetId}-${mem.id}`, + source: targetId, + target: mem.id, + visualProps: getEdgeVisualProps(edgeType), + edgeType, + }) } } } diff --git a/packages/memory-graph/src/mock-data.ts b/packages/memory-graph/src/mock-data.ts index 78ad5ff5..e32adb13 100644 --- a/packages/memory-graph/src/mock-data.ts +++ b/packages/memory-graph/src/mock-data.ts @@ -323,6 +323,8 @@ export function generateMockGraphData(options: MockGraphOptions = {}): { let version = 1 let isLatest = true let isForgotten = false + let memoryRelations: Record = + {} // Build version chain for first few memories if applicable if (hasVersionChain && m < 3) { @@ -342,12 +344,27 @@ export function generateMockGraphData(options: MockGraphOptions = {}): { chainPrevId = memId isLatest = m === 2 // last in the 3-memory chain isForgotten = !isLatest && random() < 0.2 + // Add "updates" relation to parent + if (parentMemoryId) { + memoryRelations = { [parentMemoryId]: "updates" } + } } } else { // Standalone memory isForgotten = random() < 0.1 isLatest = true version = 1 + // Randomly add extends/derives relations to earlier memories in this doc + if (m > 0 && random() < 0.2) { + const targetIdx = Math.floor(random() * m) + const targetMem = memories[targetIdx] + if (targetMem) { + const relType = random() < 0.5 ? "extends" : "derives" + memoryRelations = { + [targetMem.id]: relType as "extends" | "derives", + } + } + } } // Determine forgetAfter (for some non-forgotten memories, set a future expiry) @@ -376,6 +393,8 @@ export function generateMockGraphData(options: MockGraphOptions = {}): { rootMemoryId, createdAt: memCreatedAt, updatedAt: memUpdatedAt, + memoryRelations: + Object.keys(memoryRelations).length > 0 ? memoryRelations : undefined, }) } diff --git a/packages/memory-graph/src/types.ts b/packages/memory-graph/src/types.ts index b8b99b7d..40927a1b 100644 --- a/packages/memory-graph/src/types.ts +++ b/packages/memory-graph/src/types.ts @@ -1,8 +1,11 @@ // Graph API types matching backend response +export type MemoryRelation = "updates" | "extends" | "derives" + export interface GraphApiMemory { id: string memory: string + content?: string | null isStatic: boolean spaceId: string isLatest: boolean @@ -14,6 +17,13 @@ export interface GraphApiMemory { rootMemoryId: string | null createdAt: string updatedAt: string + // Relation fields from backend + relation?: MemoryRelation | null + updatesMemoryId?: string | null + nextVersionId?: string | null + memoryRelations?: Record | null + // Source/join fields + spaceContainerTag?: string | null } export interface GraphApiDocument { @@ -59,6 +69,8 @@ export interface MemoryNodeData { spaceId: string createdAt: string updatedAt: string + relation?: MemoryRelation | null + memoryRelations?: Record | null } export interface GraphNode {