Skip to main content
Glama
waypoint-builder.test.ts38.3 kB
/** * Waypoint Graph Builder Tests * * Tests for waypoint connection creation system. * Following TDD principles - these tests define expected behavior before implementation. * * Tests cover: * - Finding relevant connections (max 1-3 per memory) * - Connection type classification (causal, associative, temporal, hierarchical) * - Connection strength calculation * - Bidirectional connection creation * - Self-connection prevention * * Requirements: 2.3, 2.4, 2.5 */ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import type { Memory, WaypointGraphConfig } from "../../../graph/types"; import { LinkType, SelfLinkError } from "../../../graph/types"; import { WaypointGraphBuilder } from "../../../graph/waypoint-builder"; // Mock database and embedding storage will be injected const mockDbManager = { pool: { query: async () => ({ rows: [] }), }, } as any; const mockEmbeddingStorage = { retrieveEmbeddings: async () => ({ episodic: createTestEmbedding(768, 0.1), semantic: createTestEmbedding(768, 0.2), procedural: createTestEmbedding(768, 0.3), emotional: createTestEmbedding(768, 0.4), reflective: createTestEmbedding(768, 0.5), }), vectorSimilaritySearch: async () => [], } as any; describe("WaypointGraphBuilder - Connection Discovery", () => { let builder: WaypointGraphBuilder; let config: WaypointGraphConfig; beforeEach(() => { config = { similarityThreshold: 0.7, maxLinksPerNode: 3, minLinksPerNode: 1, enableBidirectional: true, }; builder = new WaypointGraphBuilder(mockDbManager, mockEmbeddingStorage, config); }); afterEach(() => { // Cleanup if needed }); it("should find relevant connections with max 1-3 per memory", async () => { // Requirement 2.3: Max 1-3 links per memory const newMemory = createTestMemory("mem-001", "New memory content"); const existingMemories = createTestMemories(10); const result = await builder.createWaypointLinks(newMemory, existingMemories); expect(result.links).toBeDefined(); expect(result.links.length).toBeGreaterThanOrEqual(1); expect(result.links.length).toBeLessThanOrEqual(3); }); it("should only create links above similarity threshold", async () => { // Requirement 2.3: Similarity threshold >0.7 const newMemory = createTestMemory("mem-001", "New memory content"); const existingMemories = createTestMemories(10); const result = await builder.createWaypointLinks(newMemory, existingMemories); // All created links should have weight >= threshold result.links.forEach((link) => { expect(link.weight).toBeGreaterThanOrEqual(config.similarityThreshold); }); }); it("should find best matches from candidate pool", async () => { // Requirement 2.3: Find top matches const memory = createTestMemory("mem-001", "Test memory"); const candidates = createTestMemories(20); const bestMatches = await builder.findBestMatches(memory, candidates, 3); expect(bestMatches).toBeDefined(); expect(bestMatches.length).toBeLessThanOrEqual(3); }); it("should handle case with no good matches", async () => { // Requirement 2.3: Handle no matches above threshold const newMemory = createTestMemory("mem-001", "Completely unique content"); const existingMemories = createTestMemories(5); // Mock low similarity for all candidates const result = await builder.createWaypointLinks(newMemory, existingMemories); // Should return empty or minimal links if none meet threshold expect(result.links.length).toBeLessThanOrEqual(3); expect(result.skippedCount).toBeGreaterThanOrEqual(0); }); it("should prioritize highest similarity matches", async () => { // Requirement 2.3: Select top matches by similarity const memory = createTestMemory("mem-001", "Test memory"); const candidates = createTestMemories(10); const bestMatches = await builder.findBestMatches(memory, candidates, 3); // Verify matches are sorted by similarity (implementation will determine order) expect(bestMatches.length).toBeGreaterThan(0); }); it("should handle empty candidate pool", async () => { // Edge case: No existing memories const newMemory = createTestMemory("mem-001", "First memory"); const existingMemories: Memory[] = []; const result = await builder.createWaypointLinks(newMemory, existingMemories); expect(result.links).toEqual([]); expect(result.skippedCount).toBe(0); }); it("should handle single candidate", async () => { // Edge case: Only one existing memory const newMemory = createTestMemory("mem-001", "New memory"); const existingMemories = [createTestMemory("mem-002", "Existing memory")]; const result = await builder.createWaypointLinks(newMemory, existingMemories); // Should create link if similarity is above threshold expect(result.links.length).toBeLessThanOrEqual(1); }); }); describe("WaypointGraphBuilder - Connection Type Classification", () => { let builder: WaypointGraphBuilder; beforeEach(() => { const config: WaypointGraphConfig = { similarityThreshold: 0.7, maxLinksPerNode: 3, minLinksPerNode: 1, enableBidirectional: true, }; builder = new WaypointGraphBuilder(mockDbManager, mockEmbeddingStorage, config); }); it("should classify causal links correctly", async () => { // Requirement 2.4: Causal link detection const memory1 = createTestMemory("mem-001", "User clicked button"); const memory2 = createTestMemory("mem-002", "Form was submitted"); // Add causal indicators to metadata memory1.metadata.keywords = ["action", "click", "trigger"]; memory2.metadata.keywords = ["result", "submit", "effect"]; const linkType = await builder.classifyLinkType(memory1, memory2); expect(linkType).toBe(LinkType.Causal); }); it("should classify associative links correctly", async () => { // Requirement 2.4: Associative link detection const memory1 = createTestMemory("mem-001", "Machine learning algorithms"); const memory2 = createTestMemory("mem-002", "Neural network architectures"); // Similar semantic content memory1.metadata.keywords = ["ml", "algorithms", "ai"]; memory2.metadata.keywords = ["neural", "networks", "ai"]; memory1.metadata.category = "technology"; memory2.metadata.category = "technology"; const linkType = await builder.classifyLinkType(memory1, memory2); expect(linkType).toBe(LinkType.Semantic); }); it("should classify temporal links correctly", async () => { // Requirement 2.4: Temporal link detection const now = new Date(); const fiveMinutesAgo = new Date(now.getTime() - 5 * 60 * 1000); const memory1 = createTestMemory("mem-001", "Started task"); const memory2 = createTestMemory("mem-002", "Completed task"); memory1.createdAt = fiveMinutesAgo; memory2.createdAt = now; memory1.sessionId = "session-123"; memory2.sessionId = "session-123"; const linkType = await builder.classifyLinkType(memory1, memory2); expect(linkType).toBe(LinkType.Temporal); }); it("should classify hierarchical links correctly", async () => { // Requirement 2.4: Hierarchical link detection const memory1 = createTestMemory("mem-001", "Project overview"); const memory2 = createTestMemory("mem-002", "Project task details"); // Hierarchical relationship memory1.metadata.category = "project"; memory2.metadata.category = "task"; memory2.metadata.parentId = memory1.id; const linkType = await builder.classifyLinkType(memory1, memory2); expect(linkType).toBe(LinkType.Analogical); }); it("should handle multi-type classification", async () => { // Requirement 2.4: Handle memories with multiple relationship types const memory1 = createTestMemory("mem-001", "User action"); const memory2 = createTestMemory("mem-002", "System response"); // Both causal and temporal memory1.createdAt = new Date(Date.now() - 1000); memory2.createdAt = new Date(); memory1.sessionId = "session-123"; memory2.sessionId = "session-123"; memory1.metadata.keywords = ["action", "trigger"]; memory2.metadata.keywords = ["response", "result"]; const linkType = await builder.classifyLinkType(memory1, memory2); // Should prioritize one type (implementation decides priority) expect([LinkType.Causal, LinkType.Temporal, LinkType.Semantic, LinkType.Analogical]).toContain( linkType ); }); it("should default to semantic for unclear relationships", async () => { // Requirement 2.4: Default classification const memory1 = createTestMemory("mem-001", "Random content A"); const memory2 = createTestMemory("mem-002", "Random content B"); const linkType = await builder.classifyLinkType(memory1, memory2); // Should default to semantic when relationship is unclear expect(linkType).toBe(LinkType.Semantic); }); }); describe("WaypointGraphBuilder - Connection Strength Calculation", () => { let builder: WaypointGraphBuilder; beforeEach(() => { const config: WaypointGraphConfig = { similarityThreshold: 0.7, maxLinksPerNode: 3, minLinksPerNode: 1, enableBidirectional: true, }; builder = new WaypointGraphBuilder(mockDbManager, mockEmbeddingStorage, config); }); it("should calculate strength based on embedding similarity", async () => { // Requirement 2.5: Similarity-based strength const memory1 = createTestMemory("mem-001", "Similar content"); const memory2 = createTestMemory("mem-002", "Similar content"); const weight = await builder.calculateLinkWeight(memory1, memory2); expect(weight).toBeGreaterThanOrEqual(0); expect(weight).toBeLessThanOrEqual(1); }); it("should calculate strength based on metadata overlap", async () => { // Requirement 2.5: Metadata-based strength const memory1 = createTestMemory("mem-001", "Content A"); const memory2 = createTestMemory("mem-002", "Content B"); // High metadata overlap memory1.metadata.keywords = ["ai", "ml", "neural"]; memory2.metadata.keywords = ["ai", "ml", "deep"]; memory1.metadata.tags = ["tech", "research"]; memory2.metadata.tags = ["tech", "research"]; const weight = await builder.calculateLinkWeight(memory1, memory2); expect(weight).toBeGreaterThan(0.5); // High overlap should give high weight }); it("should calculate strength based on temporal proximity", async () => { // Requirement 2.5: Temporal-based strength const now = new Date(); const oneMinuteAgo = new Date(now.getTime() - 60 * 1000); const memory1 = createTestMemory("mem-001", "Recent memory"); const memory2 = createTestMemory("mem-002", "Very recent memory"); memory1.createdAt = oneMinuteAgo; memory2.createdAt = now; memory1.sessionId = "session-123"; memory2.sessionId = "session-123"; const weight = await builder.calculateLinkWeight(memory1, memory2); expect(weight).toBeGreaterThan(0); // Temporal proximity should contribute }); it("should use composite scoring for strength", async () => { // Requirement 2.5: Composite strength calculation const memory1 = createTestMemory("mem-001", "Test memory"); const memory2 = createTestMemory("mem-002", "Related memory"); // Set up multiple factors memory1.metadata.keywords = ["test", "memory"]; memory2.metadata.keywords = ["test", "related"]; memory1.createdAt = new Date(Date.now() - 5000); memory2.createdAt = new Date(); memory1.sessionId = "session-123"; memory2.sessionId = "session-123"; const weight = await builder.calculateLinkWeight(memory1, memory2); // Should combine multiple factors expect(weight).toBeGreaterThanOrEqual(0); expect(weight).toBeLessThanOrEqual(1); }); it("should normalize strength to 0-1 range", async () => { // Requirement 2.5: Normalized weights const memory1 = createTestMemory("mem-001", "Memory A"); const memory2 = createTestMemory("mem-002", "Memory B"); const weight = await builder.calculateLinkWeight(memory1, memory2); expect(weight).toBeGreaterThanOrEqual(0); expect(weight).toBeLessThanOrEqual(1); }); it("should give higher weight to more similar memories", async () => { // Requirement 2.5: Weight proportional to similarity const memory1 = createTestMemory("mem-001", "Base memory"); const memory2 = createTestMemory("mem-002", "Very similar memory"); const memory3 = createTestMemory("mem-003", "Somewhat similar memory"); // memory2 is more similar than memory3 memory1.metadata.keywords = ["a", "b", "c", "d"]; memory2.metadata.keywords = ["a", "b", "c", "e"]; // 3/4 overlap memory3.metadata.keywords = ["a", "b", "x", "y"]; // 2/4 overlap const weight12 = await builder.calculateLinkWeight(memory1, memory2); const weight13 = await builder.calculateLinkWeight(memory1, memory3); expect(weight12).toBeGreaterThan(weight13); }); }); describe("WaypointGraphBuilder - Bidirectional Connection Creation", () => { let builder: WaypointGraphBuilder; beforeEach(() => { const config: WaypointGraphConfig = { similarityThreshold: 0.7, maxLinksPerNode: 3, minLinksPerNode: 1, enableBidirectional: true, }; builder = new WaypointGraphBuilder(mockDbManager, mockEmbeddingStorage, config); }); it("should create bidirectional links", async () => { // Requirement 2.3: Bidirectional connections const memory1 = createTestMemory("mem-001", "Memory A"); const memory2 = createTestMemory("mem-002", "Memory B"); const links = await builder.createBidirectionalLink(memory1, memory2, LinkType.Semantic, 0.8); expect(links).toHaveLength(2); expect(links[0].sourceId).toBe(memory1.id); expect(links[0].targetId).toBe(memory2.id); expect(links[1].sourceId).toBe(memory2.id); expect(links[1].targetId).toBe(memory1.id); }); it("should assign symmetric weights to bidirectional links", async () => { // Requirement 2.3: Symmetric weights const memory1 = createTestMemory("mem-001", "Memory A"); const memory2 = createTestMemory("mem-002", "Memory B"); const links = await builder.createBidirectionalLink(memory1, memory2, LinkType.Semantic, 0.75); expect(links[0].weight).toBe(links[1].weight); expect(links[0].weight).toBe(0.75); }); it("should maintain graph consistency", async () => { // Requirement 2.3: Graph consistency const memory1 = createTestMemory("mem-001", "Memory A"); const memory2 = createTestMemory("mem-002", "Memory B"); const links = await builder.createBidirectionalLink(memory1, memory2, LinkType.Temporal, 0.9); // Both links should have same type expect(links[0].linkType).toBe(links[1].linkType); expect(links[0].linkType).toBe(LinkType.Temporal); }); it("should handle existing connections gracefully", async () => { // Requirement 2.3: Handle existing links const memory1 = createTestMemory("mem-001", "Memory A"); const memory2 = createTestMemory("mem-002", "Memory B"); // Create link first time const links1 = await builder.createBidirectionalLink(memory1, memory2, LinkType.Semantic, 0.8); expect(links1).toHaveLength(2); // Attempt to create again - should handle gracefully const links2 = await builder.createBidirectionalLink(memory1, memory2, LinkType.Semantic, 0.8); // Implementation should either skip or update existing links expect(links2).toBeDefined(); }); it("should support unidirectional mode when configured", async () => { // Requirement 2.3: Optional unidirectional links const config: WaypointGraphConfig = { similarityThreshold: 0.7, maxLinksPerNode: 3, minLinksPerNode: 1, enableBidirectional: false, }; const unidirectionalBuilder = new WaypointGraphBuilder( mockDbManager, mockEmbeddingStorage, config ); const memory1 = createTestMemory("mem-001", "Memory A"); const memory2 = createTestMemory("mem-002", "Memory B"); const links = await unidirectionalBuilder.createBidirectionalLink( memory1, memory2, LinkType.Semantic, 0.8 ); // Should only create one link when bidirectional is disabled expect(links.length).toBeLessThanOrEqual(1); }); }); describe("WaypointGraphBuilder - Self-Connection Prevention", () => { let builder: WaypointGraphBuilder; beforeEach(() => { const config: WaypointGraphConfig = { similarityThreshold: 0.7, maxLinksPerNode: 3, minLinksPerNode: 1, enableBidirectional: true, }; builder = new WaypointGraphBuilder(mockDbManager, mockEmbeddingStorage, config); }); it("should reject self-links at creation time", async () => { // Requirement 2.3: Prevent self-connections const memory = createTestMemory("mem-001", "Test memory"); await expect( builder.createBidirectionalLink(memory, memory, LinkType.Semantic, 0.9) ).rejects.toThrow(SelfLinkError); }); it("should validate source and target are different", async () => { // Requirement 2.3: Validation const memory = createTestMemory("mem-001", "Test memory"); const isValid = await builder.validateLink(memory.id, memory.id); expect(isValid).toBe(false); }); it("should filter out self from candidate pool", async () => { // Requirement 2.3: Exclude self from candidates const memory = createTestMemory("mem-001", "Test memory"); const candidates = [memory, ...createTestMemories(5)]; const bestMatches = await builder.findBestMatches(memory, candidates, 3); // Should not include self in matches expect(bestMatches.every((m) => m.id !== memory.id)).toBe(true); }); it("should enforce database constraint for self-links", async () => { // Requirement 2.3: Database-level prevention const memory = createTestMemory("mem-001", "Test memory"); // Attempt to create self-link should fail at validation level await expect( builder.createBidirectionalLink(memory, memory, LinkType.Semantic, 0.8) ).rejects.toThrow(); }); }); describe("WaypointGraphBuilder - Configuration Edge Cases", () => { it("should handle unidirectional mode with maxLinksPerNode=1", async () => { // Test unidirectional configuration const config: WaypointGraphConfig = { similarityThreshold: 0.7, maxLinksPerNode: 1, minLinksPerNode: 1, enableBidirectional: false, }; const builder = new WaypointGraphBuilder(mockDbManager, mockEmbeddingStorage, config); const memory = createTestMemory("mem-001", "Test memory"); const candidates = createTestMemories(5); const result = await builder.createWaypointLinks(memory, candidates); // Should create only 1 link (unidirectional) expect(result.links.length).toBeLessThanOrEqual(1); }); it("should handle maxLinksPerNode=2 with bidirectional mode", async () => { // Test bidirectional with even maxLinksPerNode const config: WaypointGraphConfig = { similarityThreshold: 0.7, maxLinksPerNode: 2, minLinksPerNode: 1, enableBidirectional: true, }; const builder = new WaypointGraphBuilder(mockDbManager, mockEmbeddingStorage, config); const memory = createTestMemory("mem-001", "Test memory"); const candidates = createTestMemories(5); const result = await builder.createWaypointLinks(memory, candidates); // Should create at most 2 links (1 bidirectional pair) expect(result.links.length).toBeLessThanOrEqual(2); }); it("should handle maxLinksPerNode=1 with bidirectional mode", async () => { // Test edge case: bidirectional with maxLinksPerNode=1 const config: WaypointGraphConfig = { similarityThreshold: 0.7, maxLinksPerNode: 1, minLinksPerNode: 1, enableBidirectional: true, }; const builder = new WaypointGraphBuilder(mockDbManager, mockEmbeddingStorage, config); const memory = createTestMemory("mem-001", "Test memory"); const candidates = createTestMemories(3); const result = await builder.createWaypointLinks(memory, candidates); // With maxLinksPerNode=1 and bidirectional, effectiveMaxConnections=0 // Should handle gracefully expect(result.links.length).toBeLessThanOrEqual(1); }); it("should enforce connection limit with many high-similarity candidates", async () => { // Test connection limit enforcement const config: WaypointGraphConfig = { similarityThreshold: 0.5, // Lower threshold to ensure matches maxLinksPerNode: 4, // 2 bidirectional pairs minLinksPerNode: 1, enableBidirectional: true, }; const builder = new WaypointGraphBuilder(mockDbManager, mockEmbeddingStorage, config); const memory = createTestMemory("mem-001", "Test memory"); const candidates = createTestMemories(10); // Many candidates const result = await builder.createWaypointLinks(memory, candidates); // Should respect maxLinksPerNode limit expect(result.links.length).toBeLessThanOrEqual(4); // Should have skipped some candidates if (result.links.length === 4) { expect(result.skippedCount).toBeGreaterThan(0); } }); it("should handle link overflow when bidirectional pair exceeds limit", async () => { // Test partial link addition when full pair doesn't fit const config: WaypointGraphConfig = { similarityThreshold: 0.5, maxLinksPerNode: 3, // Odd number - can't fit 2 full pairs minLinksPerNode: 1, enableBidirectional: true, }; const builder = new WaypointGraphBuilder(mockDbManager, mockEmbeddingStorage, config); const memory = createTestMemory("mem-001", "Test memory"); const candidates = createTestMemories(5); const result = await builder.createWaypointLinks(memory, candidates); // Should respect maxLinksPerNode limit expect(result.links.length).toBeLessThanOrEqual(3); }); it("should handle all candidates being self-references", async () => { // Test empty candidates after filtering const config: WaypointGraphConfig = { similarityThreshold: 0.7, maxLinksPerNode: 3, minLinksPerNode: 1, enableBidirectional: true, }; const builder = new WaypointGraphBuilder(mockDbManager, mockEmbeddingStorage, config); const memory = createTestMemory("mem-001", "Test memory"); // All candidates are the same memory (self-references) const candidates = [memory, memory, memory]; const bestMatches = await builder.findBestMatches(memory, candidates, 3); // Should return empty array after filtering out self-references expect(bestMatches).toEqual([]); }); it("should handle embedding retrieval errors gracefully", async () => { // Test error handling in embedding similarity calculation const mockEmbeddingStorageWithError = { retrieveEmbeddings: async () => { throw new Error("Embedding retrieval failed"); }, vectorSimilaritySearch: async () => [], } as any; const config: WaypointGraphConfig = { similarityThreshold: 0.7, maxLinksPerNode: 3, minLinksPerNode: 1, enableBidirectional: true, }; const builder = new WaypointGraphBuilder(mockDbManager, mockEmbeddingStorageWithError, config); const memory1 = createTestMemory("mem-001", "Test memory"); const memory2 = createTestMemory("mem-002", "Another memory"); const weight = await builder.calculateLinkWeight(memory1, memory2); // Should return default similarity of 0.5 on error expect(weight).toBeGreaterThan(0); expect(weight).toBeLessThanOrEqual(1); }); it("should handle temporal proximity for distant memories in same session", async () => { // Test temporal proximity calculation for distant memories const config: WaypointGraphConfig = { similarityThreshold: 0.7, maxLinksPerNode: 3, minLinksPerNode: 1, enableBidirectional: true, }; const builder = new WaypointGraphBuilder(mockDbManager, mockEmbeddingStorage, config); const now = new Date(); const fortyMinutesAgo = new Date(now.getTime() - 40 * 60 * 1000); const memory1 = createTestMemory("mem-001", "Old memory"); const memory2 = createTestMemory("mem-002", "Recent memory"); memory1.createdAt = fortyMinutesAgo; memory2.createdAt = now; memory1.sessionId = "session-123"; memory2.sessionId = "session-123"; const weight = await builder.calculateLinkWeight(memory1, memory2); // Should calculate weight with low temporal proximity score expect(weight).toBeGreaterThanOrEqual(0); expect(weight).toBeLessThanOrEqual(1); }); }); describe("WaypointGraphBuilder - Database Access", () => { it("should return database connection manager", () => { // Requirement 2.3: Test database access method const config: WaypointGraphConfig = { similarityThreshold: 0.7, maxLinksPerNode: 3, minLinksPerNode: 1, enableBidirectional: true, }; const builder = new WaypointGraphBuilder(mockDbManager, mockEmbeddingStorage, config); const db = builder.getDatabase(); expect(db).toBe(mockDbManager); }); it("should respect maxLinksPerNode limit with bidirectional links", async () => { // Requirement 2.3: Test link limit enforcement with bidirectional pairs // When maxLinksPerNode is odd, should handle partial pairs correctly // Create a mock that ensures high similarity for multiple candidates const mockEmbeddingStorageHighSim = { retrieveEmbeddings: async () => ({ episodic: createTestEmbedding(768, 0.9), semantic: createTestEmbedding(768, 0.9), procedural: createTestEmbedding(768, 0.9), emotional: createTestEmbedding(768, 0.9), reflective: createTestEmbedding(768, 0.9), }), vectorSimilaritySearch: async () => [], } as any; const config: WaypointGraphConfig = { similarityThreshold: 0.3, // Very low threshold to ensure all candidates match maxLinksPerNode: 3, // Odd number - after first pair (2 links), only 1 slot remains minLinksPerNode: 1, enableBidirectional: true, }; const builder = new WaypointGraphBuilder(mockDbManager, mockEmbeddingStorageHighSim, config); const memory = createTestMemory("mem-001", "Test memory"); // Create multiple candidates with high metadata overlap to ensure high weights const candidates = createTestMemories(3); candidates.forEach((c, i) => { c.metadata.keywords = ["test", "memory", "similar"]; c.metadata.tags = ["tag1", "tag2"]; c.metadata.category = "test"; c.sessionId = memory.sessionId; c.createdAt = new Date(memory.createdAt.getTime() + i * 1000); }); const result = await builder.createWaypointLinks(memory, candidates); // Should respect maxLinksPerNode=3 limit exactly expect(result.links.length).toBeLessThanOrEqual(3); // Should have created links (at least 1 full pair) expect(result.links.length).toBeGreaterThan(0); // Should have skipped some candidates due to limit expect(result.skippedCount).toBeGreaterThan(0); }); it("should calculate link weight with default similarity when embeddings unavailable", async () => { // Requirement 2.3: Test fallback behavior when embeddings are missing const mockEmbeddingStorageNoEmbeddings = { retrieveEmbeddings: async () => ({ episodic: null, semantic: null, procedural: null, emotional: null, reflective: null, }), vectorSimilaritySearch: async () => [], } as any; const config: WaypointGraphConfig = { similarityThreshold: 0.3, maxLinksPerNode: 3, minLinksPerNode: 1, enableBidirectional: true, }; const builder = new WaypointGraphBuilder( mockDbManager, mockEmbeddingStorageNoEmbeddings, config ); const memory1 = createTestMemory("mem-001", "Test memory"); const memory2 = createTestMemory("mem-002", "Another memory"); const weight = await builder.calculateLinkWeight(memory1, memory2); // Should return default similarity of 0.5 when no embeddings expect(weight).toBeGreaterThan(0); expect(weight).toBeLessThanOrEqual(1); }); it("should apply temporal proximity bonus for memories created within 30 minutes", async () => { // Requirement 2.3: Test temporal proximity calculation in link weight const config: WaypointGraphConfig = { similarityThreshold: 0.7, maxLinksPerNode: 3, minLinksPerNode: 1, enableBidirectional: true, }; const builder = new WaypointGraphBuilder(mockDbManager, mockEmbeddingStorage, config); const now = new Date(); const fifteenMinutesAgo = new Date(now.getTime() - 15 * 60 * 1000); const memory1 = createTestMemory("mem-001", "Old memory"); const memory2 = createTestMemory("mem-002", "Recent memory"); memory1.createdAt = fifteenMinutesAgo; memory2.createdAt = now; memory1.sessionId = "session-123"; memory2.sessionId = "session-123"; const weight = await builder.calculateLinkWeight(memory1, memory2); // Should calculate weight with moderate temporal proximity (0.5) expect(weight).toBeGreaterThanOrEqual(0); expect(weight).toBeLessThanOrEqual(1); }); it("should not apply temporal proximity bonus for memories more than 30 minutes apart", async () => { // Requirement 2.3: Test temporal proximity calculation for distant memories const config: WaypointGraphConfig = { similarityThreshold: 0.7, maxLinksPerNode: 3, minLinksPerNode: 1, enableBidirectional: true, }; const builder = new WaypointGraphBuilder(mockDbManager, mockEmbeddingStorage, config); const now = new Date(); const fortyFiveMinutesAgo = new Date(now.getTime() - 45 * 60 * 1000); const memory1 = createTestMemory("mem-001", "Old memory"); const memory2 = createTestMemory("mem-002", "Recent memory"); memory1.createdAt = fortyFiveMinutesAgo; memory2.createdAt = now; memory1.sessionId = "session-123"; memory2.sessionId = "session-123"; const weight = await builder.calculateLinkWeight(memory1, memory2); // Should calculate weight with low temporal proximity (0.1) expect(weight).toBeGreaterThanOrEqual(0); expect(weight).toBeLessThanOrEqual(1); }); it("should calculate metadata overlap when both memories have no keywords or tags", async () => { // Requirement 2.3: Test metadata overlap calculation with empty arrays const config: WaypointGraphConfig = { similarityThreshold: 0.7, maxLinksPerNode: 3, minLinksPerNode: 1, enableBidirectional: true, }; const builder = new WaypointGraphBuilder(mockDbManager, mockEmbeddingStorage, config); const memory1 = createTestMemory("mem-001", "Memory A"); const memory2 = createTestMemory("mem-002", "Memory B"); // Empty metadata arrays memory1.metadata.keywords = []; memory1.metadata.tags = []; memory2.metadata.keywords = []; memory2.metadata.tags = []; const weight = await builder.calculateLinkWeight(memory1, memory2); // Should handle empty arrays gracefully expect(weight).toBeGreaterThanOrEqual(0); expect(weight).toBeLessThanOrEqual(1); }); it("should handle metadata overlap with one array empty", async () => { // Test array overlap edge case const config: WaypointGraphConfig = { similarityThreshold: 0.7, maxLinksPerNode: 3, minLinksPerNode: 1, enableBidirectional: true, }; const builder = new WaypointGraphBuilder(mockDbManager, mockEmbeddingStorage, config); const memory1 = createTestMemory("mem-001", "Memory A"); const memory2 = createTestMemory("mem-002", "Memory B"); // One empty, one with values memory1.metadata.keywords = []; memory2.metadata.keywords = ["test", "memory"]; memory1.metadata.tags = ["tag1"]; memory2.metadata.tags = []; const weight = await builder.calculateLinkWeight(memory1, memory2); // Should handle mixed empty/non-empty arrays expect(weight).toBeGreaterThanOrEqual(0); expect(weight).toBeLessThanOrEqual(1); }); }); describe("WaypointGraphBuilder - Edge Cases", () => { let builder: WaypointGraphBuilder; beforeEach(() => { const config: WaypointGraphConfig = { similarityThreshold: 0.7, maxLinksPerNode: 3, minLinksPerNode: 1, enableBidirectional: true, }; builder = new WaypointGraphBuilder(mockDbManager, mockEmbeddingStorage, config); }); it("should handle duplicate candidates", async () => { // Edge case: Duplicate memories in candidate pool const memory = createTestMemory("mem-001", "Test memory"); const duplicate = createTestMemory("mem-002", "Duplicate"); const candidates = [duplicate, duplicate, ...createTestMemories(3)]; const bestMatches = await builder.findBestMatches(memory, candidates, 3); // Should deduplicate candidates const uniqueIds = new Set(bestMatches.map((m) => m.id)); expect(uniqueIds.size).toBe(bestMatches.length); }); it("should handle all low-similarity candidates", async () => { // Edge case: No candidates above threshold const memory = createTestMemory("mem-001", "Unique content"); const candidates = createTestMemories(10); // Mock low similarity const result = await builder.createWaypointLinks(memory, candidates); // Should return empty or minimal links expect(result.links.length).toBeLessThanOrEqual(3); if (result.links.length === 0) { expect(result.skippedCount).toBeGreaterThan(0); } }); it("should handle memory with max connections already", async () => { // Edge case: Memory already has max links const memory = createTestMemory("mem-001", "Test memory"); const candidates = createTestMemories(5); // Simulate memory already having 3 links // Implementation should check existing link count const result = await builder.createWaypointLinks(memory, candidates); // Should respect max links constraint expect(result.links.length).toBeLessThanOrEqual(3); }); it("should handle invalid memory data", async () => { // Edge case: Missing required fields const invalidMemory = { id: "mem-001", content: "", // Missing other required fields } as any; const candidates = createTestMemories(3); await expect(builder.createWaypointLinks(invalidMemory, candidates)).rejects.toThrow(); }); it("should handle concurrent link creation", async () => { // Edge case: Multiple links being created simultaneously const memory = createTestMemory("mem-001", "Test memory"); const candidates = createTestMemories(10); // Create links concurrently const promises = [ builder.createWaypointLinks(memory, candidates.slice(0, 5)), builder.createWaypointLinks(memory, candidates.slice(5, 10)), ]; const results = await Promise.all(promises); // Should handle concurrent creation without errors expect(results).toHaveLength(2); results.forEach((result) => { expect(result.links).toBeDefined(); }); }); }); describe("WaypointGraphBuilder - Performance", () => { let builder: WaypointGraphBuilder; beforeEach(() => { const config: WaypointGraphConfig = { similarityThreshold: 0.7, maxLinksPerNode: 3, minLinksPerNode: 1, enableBidirectional: true, }; builder = new WaypointGraphBuilder(mockDbManager, mockEmbeddingStorage, config); }); it("should complete link creation within 500ms for 100 candidates", async () => { // Requirement 2.5: Performance target const memory = createTestMemory("mem-001", "Test memory"); const candidates = createTestMemories(100); const startTime = Date.now(); await builder.createWaypointLinks(memory, candidates); const duration = Date.now() - startTime; expect(duration).toBeLessThan(500); }); it("should use cached embeddings for similarity calculations", async () => { // Requirement 2.5: Caching optimization const memory = createTestMemory("mem-001", "Test memory"); const candidates = createTestMemories(10); // First call - may cache embeddings await builder.createWaypointLinks(memory, candidates); // Second call - should use cache const startTime = Date.now(); await builder.createWaypointLinks(memory, candidates); const duration = Date.now() - startTime; // Should be faster with cache expect(duration).toBeLessThan(200); }); it("should batch database operations efficiently", async () => { // Requirement 2.5: Efficient database operations const memory = createTestMemory("mem-001", "Test memory"); const candidates = createTestMemories(20); const result = await builder.createWaypointLinks(memory, candidates); // Should create links in batch, not one-by-one expect(result.links).toBeDefined(); }); }); // Helper functions function createTestMemory(id: string, content: string): Memory { return { id, content, createdAt: new Date(), lastAccessed: new Date(), accessCount: 0, salience: 0.5, strength: 1.0, userId: "test-user", sessionId: "test-session", primarySector: "semantic", metadata: { keywords: [], tags: [], category: "general", context: "", importance: 0.5, isAtomic: true, }, }; } function createTestMemories(count: number): Memory[] { return Array.from({ length: count }, (_, i) => createTestMemory(`mem-${String(i + 1).padStart(3, "0")}`, `Test memory ${i + 1}`) ); } function createTestEmbedding(dimension: number, seed: number): number[] { const embedding = new Array(dimension); for (let i = 0; i < dimension; i++) { embedding[i] = seed + i * 0.001; } // Normalize to unit vector const magnitude = Math.sqrt(embedding.reduce((sum, val) => sum + val * val, 0)); return embedding.map((val) => val / magnitude); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/keyurgolani/ThoughtMcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server