@codai/memorai-mcp
Version:
MemorAI CBD-based MCP Server - High-Performance Vector Memory System
756 lines (752 loc) • 31.9 kB
JavaScript
/**
* 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