UNPKG

@codai/memorai-mcp

Version:

MemorAI CBD-based MCP Server - High-Performance Vector Memory System

756 lines (752 loc) 31.9 kB
/** * Phase 3: AI-Powered Memory Intelligence - Memory Evolution Engine * * Automatic memory evolution and conflict resolution: * - Automatically updates memories based on new information * - Detects and resolves memory conflicts intelligently * - Manages memory lifecycle and version control * - Provides semantic memory merging and consolidation */ export class MemoryEvolutionEngine { openaiClient; memories; memoryVersions; evolutionRules; conflictHistory; evolutionHistory; constructor(openaiClient, memories) { this.openaiClient = openaiClient; this.memories = memories || new Map(); this.memoryVersions = new Map(); this.evolutionRules = new Map(); this.conflictHistory = new Map(); this.evolutionHistory = new Map(); this.initializeDefaultRules(); } /** * Automatically update memory based on new information * Core evolution capability with AI-powered analysis */ async evolveMemory(memoryId, newInformation, context) { try { const memory = this.memories.get(memoryId); if (!memory) { throw new Error(`Memory ${memoryId} not found`); } // Create version before modification const previousVersion = await this.createMemoryVersion(memory, 'pre_evolution'); // Analyze the new information and determine evolution strategy const evolutionStrategy = await this.analyzeEvolutionStrategy(memory, newInformation, context); // Apply evolution based on strategy const evolutionResult = await this.applyEvolution(memory, newInformation, evolutionStrategy, previousVersion); // Store evolution history this.evolutionHistory.set(evolutionResult.memoryId, evolutionResult); return evolutionResult; } catch (error) { console.error('Error evolving memory:', error); throw error; } } /** * Detect and resolve conflicts between memories * Intelligent conflict resolution with multiple strategies */ async resolveMemoryConflicts(conflictingMemoryIds, resolutionStrategy) { try { const memories = conflictingMemoryIds .map(id => this.memories.get(id)) .filter(m => m !== undefined); if (memories.length < 2) { throw new Error('At least 2 memories required for conflict resolution'); } // Detect conflict type and severity const conflict = await this.detectConflictType(memories); // Determine resolution strategy const strategy = resolutionStrategy || await this.determineOptimalStrategy(conflict, memories); // Apply resolution const resolution = await this.applyConflictResolution(conflict, memories, strategy); // Store resolution history this.conflictHistory.set(resolution.conflictId, resolution); return resolution; } catch (error) { console.error('Error resolving memory conflicts:', error); throw error; } } /** * Detect potential conflicts across all memories * Proactive conflict detection system */ async detectPotentialConflicts(agentId) { try { const agentMemories = Array.from(this.memories.values()) .filter(m => m.metadata.agentId === agentId); const conflicts = []; // Check for content contradictions const contentConflicts = await this.detectContentConflicts(agentMemories); conflicts.push(...contentConflicts); // Check for metadata mismatches const metadataConflicts = await this.detectMetadataConflicts(agentMemories); conflicts.push(...metadataConflicts); // Check for temporal inconsistencies const temporalConflicts = await this.detectTemporalConflicts(agentMemories); conflicts.push(...temporalConflicts); // Check for relationship conflicts const relationshipConflicts = await this.detectRelationshipConflicts(agentMemories); conflicts.push(...relationshipConflicts); return conflicts.sort((a, b) => this.getSeverityScore(b.severity) - this.getSeverityScore(a.severity)); } catch (error) { console.error('Error detecting conflicts:', error); return []; } } /** * Consolidate related memories for better organization * Smart memory consolidation with preservation of important information */ async consolidateMemories(memoryIds, consolidationType = 'merge') { try { const memories = memoryIds .map(id => this.memories.get(id)) .filter(m => m !== undefined); if (memories.length < 2) { throw new Error('At least 2 memories required for consolidation'); } // Analyze consolidation feasibility const feasibility = await this.analyzeConsolidationFeasibility(memories, consolidationType); if (feasibility.score < 0.6) { throw new Error(`Consolidation not recommended (score: ${feasibility.score})`); } // Perform consolidation const consolidation = await this.performConsolidation(memories, consolidationType); return consolidation; } catch (error) { console.error('Error consolidating memories:', error); throw error; } } /** * Manage memory lifecycle automatically * Archive old, promote important, and clean up redundant memories */ async manageMemoryLifecycle(agentId) { try { const agentMemories = Array.from(this.memories.values()) .filter(m => m.metadata.agentId === agentId); const result = { archived: [], promoted: [], cleaned: [], enhanced: [] }; // Archive old, unused memories const archiveCandidates = await this.identifyArchiveCandidates(agentMemories); for (const memory of archiveCandidates) { await this.archiveMemory(memory); result.archived.push(memory.id); } // Promote important memories const promotionCandidates = await this.identifyPromotionCandidates(agentMemories); for (const memory of promotionCandidates) { await this.promoteMemory(memory); result.promoted.push(memory.id); } // Clean up redundant memories const duplicateCandidates = await this.identifyDuplicates(agentMemories); for (const duplicate of duplicateCandidates) { await this.cleanupDuplicate(duplicate); result.cleaned.push(duplicate.id); } // Enhance incomplete memories const enhancementCandidates = await this.identifyEnhancementCandidates(agentMemories); for (const memory of enhancementCandidates) { await this.enhanceMemory(memory); result.enhanced.push(memory.id); } return result; } catch (error) { console.error('Error managing memory lifecycle:', error); return { archived: [], promoted: [], cleaned: [], enhanced: [] }; } } // Private implementation methods async createMemoryVersion(memory, changeType) { const version = { versionId: `${memory.id}_${Date.now()}`, timestamp: new Date().toISOString(), content: memory.content, metadata: { ...memory.metadata }, changesSummary: `Version created for ${changeType}`, createdBy: 'system' }; const versions = this.memoryVersions.get(memory.id) || []; versions.push(version); this.memoryVersions.set(memory.id, versions); return version; } async analyzeEvolutionStrategy(memory, newInformation, context) { // Use AI analysis if available if (this.openaiClient) { return await this.aiAnalyzeEvolution(memory, newInformation, context); } // Fallback to rule-based analysis return await this.ruleBasedEvolutionAnalysis(memory, newInformation, context); } async aiAnalyzeEvolution(memory, newInformation, context) { if (!this.openaiClient) { throw new Error('OpenAI client not available'); } try { const prompt = ` Analyze how to evolve this memory with new information: EXISTING MEMORY: Content: ${memory.content} Created: ${memory.metadata.timestamp} Importance: ${memory.metadata.importance} Type: ${memory.metadata.entityType || 'general'} NEW INFORMATION: ${newInformation} Source: ${context?.source || 'unknown'} Confidence: ${context?.confidence || 0.5} Determine the best evolution strategy: 1. "update" - Simple content update/addition 2. "merge" - Combine with related information 3. "split" - Break into separate memories 4. "archive" - Mark as outdated 5. "enhance" - Add structure/metadata Respond in JSON format: { "strategy": "update|merge|split|archive|enhance", "confidence": 0.0-1.0, "reasoning": "explanation of recommendation" } `; const response = await this.openaiClient.chat.completions.create({ model: 'gpt-4', messages: [{ role: 'user', content: prompt }], temperature: 0.2, max_tokens: 500 }); const result = JSON.parse(response.choices[0]?.message?.content || '{}'); return { strategy: result.strategy || 'update', confidence: result.confidence || 0.5, reasoning: result.reasoning || 'AI analysis completed' }; } catch (error) { console.error('Error in AI evolution analysis:', error); return { strategy: 'update', confidence: 0.3, reasoning: 'Fallback to simple update due to AI analysis error' }; } } async ruleBasedEvolutionAnalysis(memory, newInformation, context) { // Simple rule-based logic const newInfoLength = newInformation.length; const existingLength = memory.content.length; const confidence = context?.confidence || 0.5; // Determine strategy based on simple heuristics if (newInfoLength > existingLength * 2) { return { strategy: 'merge', confidence: confidence * 0.8, reasoning: 'New information is substantial, merging recommended' }; } else if (newInformation.toLowerCase().includes('outdated') || newInformation.toLowerCase().includes('incorrect')) { return { strategy: 'archive', confidence: confidence * 0.9, reasoning: 'Content indicates information is outdated' }; } else if (newInfoLength < existingLength * 0.1) { return { strategy: 'enhance', confidence: confidence * 0.7, reasoning: 'Minor addition, enhancing existing memory' }; } else { return { strategy: 'update', confidence: confidence * 0.8, reasoning: 'Standard content update' }; } } async applyEvolution(memory, newInformation, strategy, previousVersion) { const changes = []; let newContent = memory.content; let newMetadata = { ...memory.metadata }; switch (strategy.strategy) { case 'update': newContent = await this.updateContent(memory.content, newInformation); changes.push({ field: 'content', previousValue: memory.content, newValue: newContent, changeType: 'modification', confidence: strategy.confidence, source: 'ai_analysis' }); break; case 'merge': newContent = await this.mergeContent(memory.content, newInformation); newMetadata.importance = Math.min(1.0, (newMetadata.importance || 0.5) + 0.1); changes.push({ field: 'content', previousValue: memory.content, newValue: newContent, changeType: 'addition', confidence: strategy.confidence, source: 'ai_analysis' }); break; case 'enhance': newMetadata = await this.enhanceMetadata(newMetadata, newInformation); changes.push({ field: 'metadata', previousValue: memory.metadata, newValue: newMetadata, changeType: 'addition', confidence: strategy.confidence, source: 'ai_analysis' }); break; case 'archive': newMetadata.archived = true; newMetadata.archiveReason = 'Marked as outdated by new information'; changes.push({ field: 'metadata.archived', previousValue: false, newValue: true, changeType: 'addition', confidence: strategy.confidence, source: 'ai_analysis' }); break; } // Update the memory memory.content = newContent; memory.metadata = newMetadata; // Create new version const newVersion = await this.createMemoryVersion(memory, 'evolution'); return { memoryId: memory.id, evolutionType: strategy.strategy, changes, confidence: strategy.confidence, reasoning: strategy.reasoning, previousVersion, newVersion }; } async updateContent(existingContent, newInformation) { // Simple content update - could be enhanced with AI return `${existingContent}\n\nUpdated: ${newInformation}`; } async mergeContent(existingContent, newInformation) { // Simple merge - could be enhanced with AI semantic merging return `${existingContent}\n\nAdditional Information:\n${newInformation}`; } async enhanceMetadata(metadata, newInformation) { const enhanced = { ...metadata }; // Extract potential tags from new information const words = newInformation.toLowerCase().split(/\s+/); const potentialTags = words.filter(word => word.length > 3 && !['the', 'and', 'for', 'are', 'but', 'not', 'you', 'all', 'can', 'had', 'her', 'was', 'one', 'our', 'out', 'day', 'get', 'has', 'him', 'his', 'how', 'man', 'new', 'now', 'old', 'see', 'two', 'way', 'who', 'boy', 'did', 'its', 'let', 'put', 'say', 'she', 'too', 'use'].includes(word)).slice(0, 3); if (potentialTags.length > 0) { enhanced.tags = [...(enhanced.tags || []), ...potentialTags]; } // Update timestamp enhanced.lastModified = new Date().toISOString(); return enhanced; } async detectConflictType(memories) { // Analyze memories to determine conflict type const conflictId = `conflict_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // Simple conflict detection - could be enhanced with AI const hasContentSimilarity = await this.checkContentSimilarity(memories); const hasTemporalIssues = this.checkTemporalConsistency(memories); const hasMetadataMismatch = this.checkMetadataConsistency(memories); let conflictType; let severity; if (hasContentSimilarity > 0.8) { conflictType = 'content_contradiction'; severity = hasContentSimilarity > 0.95 ? 'high' : 'medium'; } else if (!hasTemporalIssues) { conflictType = 'temporal_inconsistency'; severity = 'medium'; } else if (!hasMetadataMismatch) { conflictType = 'metadata_mismatch'; severity = 'low'; } else { conflictType = 'relationship_conflict'; severity = 'low'; } return { id: conflictId, type: conflictType, memoryIds: memories.map(m => m.id), severity, description: `Detected ${conflictType.replace('_', ' ')} between ${memories.length} memories`, detectedAt: new Date().toISOString(), autoResolvable: severity !== 'high' }; } async checkContentSimilarity(memories) { if (memories.length !== 2 || !memories[0] || !memories[1]) return 0; const content1 = memories[0].content.toLowerCase(); const content2 = memories[1].content.toLowerCase(); // Simple Jaccard similarity const words1 = new Set(content1.split(/\s+/)); const words2 = new Set(content2.split(/\s+/)); const intersection = new Set([...words1].filter(word => words2.has(word))); const union = new Set([...words1, ...words2]); return intersection.size / union.size; } checkTemporalConsistency(memories) { // Check if memories have logical temporal order const timestamps = memories.map(m => new Date(m.metadata.timestamp).getTime()); // Simple check - could be enhanced with content analysis return timestamps.every((ts, i) => i === 0 || ts >= (timestamps[i - 1] || 0)); } checkMetadataConsistency(memories) { // Check if metadata is consistent across memories if (memories.length < 2 || !memories[0] || !memories[1]) return true; const firstProject = memories[0].metadata.project; const firstSession = memories[0].metadata.session; return memories.every(m => m.metadata.project === firstProject && m.metadata.session === firstSession); } async determineOptimalStrategy(conflict, memories) { // Determine strategy based on conflict severity and memory importance const avgImportance = memories.reduce((sum, m) => sum + (m.metadata.importance || 0.5), 0) / memories.length; if (conflict.severity === 'low' && avgImportance < 0.6) { return 'auto'; } else if (conflict.severity === 'high' || avgImportance > 0.8) { return 'conservative'; } else { return 'aggressive'; } } async applyConflictResolution(conflict, memories, strategy) { const resolution = { strategy: 'merge', primaryMemoryId: memories[0]?.id || 'unknown', secondaryMemoryIds: memories.slice(1).map(m => m.id), preservedElements: [], discardedElements: [] }; const appliedChanges = []; switch (strategy) { case 'auto': // Automatically merge memories resolution.strategy = 'merge'; if (memories[0]) { resolution.newContent = await this.mergeContent(memories[0].content, memories.slice(1).map(m => m.content).join('\n\n')); } break; case 'conservative': // Prioritize most important memory const mostImportant = memories.reduce((prev, current) => (current.metadata.importance || 0) > (prev.metadata.importance || 0) ? current : prev); resolution.strategy = 'prioritize'; resolution.primaryMemoryId = mostImportant.id; break; case 'aggressive': // Archive older memories const sorted = memories.sort((a, b) => new Date(b.metadata.timestamp).getTime() - new Date(a.metadata.timestamp).getTime()); resolution.strategy = 'archive_old'; resolution.primaryMemoryId = sorted[0]?.id || 'unknown'; break; } return { conflictId: conflict.id, conflictType: conflict.type, memoryIds: memories.map(m => m.id), resolution, confidence: 0.8, reasoning: `Applied ${strategy} strategy for ${conflict.type}`, appliedChanges }; } async detectContentConflicts(memories) { const conflicts = []; for (let i = 0; i < memories.length; i++) { for (let j = i + 1; j < memories.length; j++) { if (memories[i] && memories[j]) { const similarity = await this.checkContentSimilarity([memories[i], memories[j]]); if (similarity > 0.8) { conflicts.push({ id: `content_conflict_${i}_${j}`, type: 'content_contradiction', memoryIds: [memories[i].id, memories[j].id], severity: similarity > 0.95 ? 'high' : 'medium', description: `High content similarity detected (${(similarity * 100).toFixed(1)}%)`, detectedAt: new Date().toISOString(), autoResolvable: similarity < 0.95 }); } } } } return conflicts; } async detectMetadataConflicts(memories) { // Implementation for metadata conflict detection return []; } async detectTemporalConflicts(memories) { // Implementation for temporal conflict detection return []; } async detectRelationshipConflicts(memories) { // Implementation for relationship conflict detection return []; } getSeverityScore(severity) { switch (severity) { case 'critical': return 4; case 'high': return 3; case 'medium': return 2; case 'low': return 1; default: return 0; } } async analyzeConsolidationFeasibility(memories, type) { // Analyze if consolidation is feasible and beneficial let score = 0.5; const reasons = []; // Check content similarity if (memories.length === 2) { const similarity = await this.checkContentSimilarity(memories); score += similarity * 0.4; if (similarity > 0.6) { reasons.push('High content similarity supports consolidation'); } } // Check temporal proximity const timestamps = memories.map(m => new Date(m.metadata.timestamp).getTime()); const maxTimeDiff = Math.max(...timestamps) - Math.min(...timestamps); const daysDiff = maxTimeDiff / (1000 * 60 * 60 * 24); if (daysDiff < 7) { score += 0.2; reasons.push('Memories created within a week'); } // Check metadata consistency const hasConsistentMetadata = this.checkMetadataConsistency(memories); if (hasConsistentMetadata) { score += 0.2; reasons.push('Consistent metadata across memories'); } return { score: Math.min(1.0, score), reasoning: reasons.join('; ') || 'Basic consolidation analysis' }; } async performConsolidation(memories, type) { const consolidationId = `consolidation_${Date.now()}`; // Create consolidated memory based on type let consolidatedContent = ''; let consolidatedMetadata = memories[0] ? { ...memories[0].metadata } : { agentId: 'unknown', timestamp: new Date().toISOString(), importance: 0.5 }; switch (type) { case 'merge': consolidatedContent = memories.map(m => m.content).join('\n\n---\n\n'); break; case 'summarize': consolidatedContent = await this.summarizeMemories(memories); break; case 'restructure': consolidatedContent = await this.restructureMemories(memories); break; case 'cross_reference': consolidatedContent = await this.createCrossReference(memories); break; } const consolidatedMemory = { id: `consolidated_${consolidationId}`, content: consolidatedContent, contentHash: '', // Would need to calculate structuredKey: `${consolidatedMetadata.project}_consolidated_${Date.now()}`, projectName: consolidatedMetadata.project || 'unknown', sessionName: consolidatedMetadata.session || 'consolidated', sequenceNumber: 1, metadata: { ...consolidatedMetadata, ...(memories.length > 0 && { consolidatedFrom: memories.map(m => m.id), consolidationType: type }), timestamp: new Date().toISOString() }, relationships: [], embedding: undefined, relatedMemoryIds: new Set(), accessCount: 0, lastAccessed: new Date().toISOString() }; return { consolidationId, sourceMemoryIds: memories.map(m => m.id), consolidatedMemory, consolidationType: type, preservedInformation: ['All original content', 'Metadata', 'Relationships'], lostInformation: [], qualityScore: 0.85 // Would calculate based on consolidation success }; } async summarizeMemories(memories) { // Implementation for memory summarization const contents = memories.map(m => m.content).join('\n\n'); return `Summary of ${memories.length} memories:\n\n${contents.substring(0, 500)}...`; } async restructureMemories(memories) { // Implementation for memory restructuring return memories.map((m, i) => `## Memory ${i + 1}\n${m.content}`).join('\n\n'); } async createCrossReference(memories) { // Implementation for cross-reference creation return `Cross-reference of ${memories.length} related memories:\n\n` + memories.map((m, i) => `[${i + 1}] ${m.id}: ${m.content.substring(0, 100)}...`).join('\n'); } // Lifecycle management methods async identifyArchiveCandidates(memories) { // Identify memories that should be archived return memories.filter(m => { const daysSinceAccess = (Date.now() - new Date(m.lastAccessed).getTime()) / (1000 * 60 * 60 * 24); return daysSinceAccess > 90 && (m.metadata.importance || 0.5) < 0.3; }); } async identifyPromotionCandidates(memories) { // Identify memories that should be promoted in importance return memories.filter(m => { return m.accessCount > 10 && (m.metadata.importance || 0.5) < 0.8; }); } async identifyDuplicates(memories) { // Identify duplicate memories const duplicates = []; for (let i = 0; i < memories.length; i++) { for (let j = i + 1; j < memories.length; j++) { if (memories[i] && memories[j]) { const similarity = await this.checkContentSimilarity([memories[i], memories[j]]); if (similarity > 0.95) { // Keep the more important one, mark other as duplicate const lessImportant = (memories[i].metadata.importance || 0.5) < (memories[j].metadata.importance || 0.5) ? memories[i] : memories[j]; if (!duplicates.includes(lessImportant)) { duplicates.push(lessImportant); } } } } } return duplicates; } async identifyEnhancementCandidates(memories) { // Identify memories that could be enhanced return memories.filter(m => { return m.content.length < 100 || // Short content !m.metadata.tags || // No tags !m.metadata.entityType; // No type }); } async archiveMemory(memory) { memory.metadata.archived = true; memory.metadata.archiveReason = 'Automatic lifecycle management'; memory.metadata.archivedAt = new Date().toISOString(); } async promoteMemory(memory) { memory.metadata.importance = Math.min(1.0, (memory.metadata.importance || 0.5) + 0.2); memory.metadata.promoted = true; memory.metadata.promotionReason = 'High access frequency'; } async cleanupDuplicate(memory) { memory.metadata.duplicate = true; memory.metadata.cleanupReason = 'Duplicate content detected'; } async enhanceMemory(memory) { // Add missing metadata if (!memory.metadata.entityType) { memory.metadata.entityType = 'note'; // Default type } if (!memory.metadata.tags) { // Extract simple tags from content const words = memory.content.toLowerCase().split(/\s+/); memory.metadata.tags = words.filter(w => w.length > 4).slice(0, 3); } memory.metadata.enhanced = true; memory.metadata.enhancementReason = 'Automatic metadata enhancement'; } initializeDefaultRules() { // Initialize default evolution rules const defaultRules = [ { id: 'update_outdated', name: 'Update Outdated Information', description: 'Automatically update memories when new information contradicts old', conditions: [ { field: 'newInformation', operator: 'contains', value: ['outdated', 'incorrect', 'wrong', 'changed'], weight: 0.8 } ], actions: [ { type: 'update_field', parameters: { field: 'content', strategy: 'replace' }, confidence_threshold: 0.7 } ], priority: 9, enabled: true }, { id: 'enhance_short_content', name: 'Enhance Short Content', description: 'Add metadata and structure to short memories', conditions: [ { field: 'content.length', operator: 'less_than', value: 100, weight: 0.6 } ], actions: [ { type: 'add_metadata', parameters: { enhance: true }, confidence_threshold: 0.5 } ], priority: 5, enabled: true } ]; defaultRules.forEach(rule => { this.evolutionRules.set(rule.id, rule); }); } } //# sourceMappingURL=evolution-engine.js.map