/** * Advanced Memory Manager for Distributed Memory System * * Provides enhanced memory management capabilities: * - Cognitive weighting of memory entries * - Advanced indexing and retrieval * - Flexible namespace support * - Comprehensive metadata tracking * - Integration with distributed memory */ import { EventEmitter } from 'node:events'; import { ILogger } from '../core/logger.ts'; import { generateId } from '../utils/helpers.ts'; // === INTERFACES === export interface AdvancedMemoryEntry { id: string; key: string; value: any; size: number; namespace: string; type?: string; tags?: string[]; owner?: string; accessLevel?: string; createdAt: Date; updatedAt: Date; lastAccessedAt: Date; accessCount: number; cognitiveWeight: number; expiresAt?: Date; vectorEmbedding?: number[]; } export interface MemoryRetrievalOptions { namespace?: string; type?: string; tags?: string[]; owner?: string; similarityThreshold?: number; maxResults?: number; sortBy?: 'relevance' | 'recency' | 'cognitiveWeight' | 'accessCount'; } export interface MemoryStorageOptions { namespace?: string; type?: string; tags?: string[]; owner?: string; accessLevel?: string; ttl?: number; cognitiveWeight?: number; } // === MAIN CLASS === export class AdvancedMemoryManager extends EventEmitter { private logger: ILogger; private memory = new Map(); private keyToId = new Map(); private tagIndex = new Map>(); private namespaceIndex = new Map>(); private typeIndex = new Map>(); private ownerIndex = new Map>(); constructor(logger: ILogger) { super(); this.logger = logger; } /** * Store a value in memory with advanced indexing */ async store( key: string, value: any, options: MemoryStorageOptions = {} ): Promise { // Handle existing entry update const existingId = this.keyToId.get(`${options.namespace || 'default'}:${key}`); if (existingId) { return this.update(existingId, value, options); } // Generate entry ID const id = generateId('mem'); // Prepare entry with defaults const entry: AdvancedMemoryEntry = { id, key, value, size: this.calculateSize(value), namespace: options.namespace || 'default', type: options.type, tags: options.tags || [], owner: options.owner, accessLevel: options.accessLevel || 'private', createdAt: new Date(), updatedAt: new Date(), lastAccessedAt: new Date(), accessCount: 0, cognitiveWeight: options.cognitiveWeight || 1.0 }; // Add TTL if specified if (options.ttl) { entry.expiresAt = new Date(Date.now() + options.ttl); } // Store entry this.memory.set(id, entry); this.keyToId.set(`${entry.namespace}:${key}`, id); // Update indexes this.updateIndexes(entry); this.logger.debug('Memory entry stored', { id, key, namespace: entry.namespace }); this.emit('memory:stored', { entry }); return id; } /** * Retrieve a value from memory by key */ async retrieve( key: string | string[], options: MemoryRetrievalOptions = {} ): Promise { try { const namespace = options.namespace || 'default'; // Handle array of keys with namespace if (Array.isArray(key)) { const entries = key .map(k => this.getEntryByKey(k, namespace)) .filter(Boolean) as AdvancedMemoryEntry[]; if (entries.length === 0) return null; // Sort by relevance (if multiple found) if (entries.length > 1) { entries.sort((a, b) => b.cognitiveWeight - a.cognitiveWeight); } const entry = entries[0]; this.updateAccessStats(entry); return entry; } // Simple key lookup const entry = this.getEntryByKey(key, namespace); if (!entry) return null; this.updateAccessStats(entry); return entry; } catch (error) { this.logger.error('Error retrieving memory entry', { error, key }); return null; } } /** * Search for entries based on various criteria */ async search(options: MemoryRetrievalOptions): Promise { try { let results = Array.from(this.memory.values()); // Apply filters if (options.namespace) { results = results.filter(entry => entry.namespace === options.namespace); } if (options.type) { results = results.filter(entry => entry.type === options.type); } if (options.tags && options.tags.length > 0) { results = results.filter(entry => options.tags!.some(tag => entry.tags?.includes(tag)) ); } if (options.owner) { results = results.filter(entry => entry.owner === options.owner); } // Apply sorting if (options.sortBy) { switch (options.sortBy) { case 'recency': results.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()); break; case 'cognitiveWeight': results.sort((a, b) => b.cognitiveWeight - a.cognitiveWeight); break; case 'accessCount': results.sort((a, b) => b.accessCount - a.accessCount); break; case 'relevance': default: // Combine factors for relevance results.sort((a, b) => { const scoreA = a.cognitiveWeight * 5 + a.accessCount * 0.1 + (1 / (Date.now() - a.lastAccessedAt.getTime() + 1)) * 1000; const scoreB = b.cognitiveWeight * 5 + b.accessCount * 0.1 + (1 / (Date.now() - b.lastAccessedAt.getTime() + 1)) * 1000; return scoreB - scoreA; }); } } // Apply limit if (options.maxResults) { results = results.slice(0, options.maxResults); } // Update access stats for all returned entries results.forEach(entry => this.updateAccessStats(entry)); return results; } catch (error) { this.logger.error('Error searching memory entries', { error, options }); return []; } } /** * Delete an entry from memory */ async delete(id: string): Promise { try { const entry = this.memory.get(id); if (!entry) return false; // Remove from main storage this.memory.delete(id); this.keyToId.delete(`${entry.namespace}:${entry.key}`); // Remove from indexes this.removeFromIndexes(entry); this.logger.debug('Memory entry deleted', { id }); this.emit('memory:deleted', { id, entry }); return true; } catch (error) { this.logger.error('Error deleting memory entry', { error, id }); return false; } } /** * Update an existing memory entry */ async update( id: string, value: any, options: MemoryStorageOptions = {} ): Promise { const entry = this.memory.get(id); if (!entry) { throw new Error(`Memory entry not found: ${id}`); } // Remove from old indexes before updating this.removeFromIndexes(entry); // Update entry entry.value = value; entry.size = this.calculateSize(value); entry.updatedAt = new Date(); // Update optional fields if provided if (options.tags) entry.tags = options.tags; if (options.type) entry.type = options.type; if (options.owner) entry.owner = options.owner; if (options.accessLevel) entry.accessLevel = options.accessLevel; if (options.cognitiveWeight) entry.cognitiveWeight = options.cognitiveWeight; // Update TTL if specified if (options.ttl) { entry.expiresAt = new Date(Date.now() + options.ttl); } // Update indexes this.updateIndexes(entry); this.logger.debug('Memory entry updated', { id, key: entry.key, namespace: entry.namespace }); this.emit('memory:updated', { entry }); return id; } /** * Get stats about memory usage */ async getStats(): Promise<{ entryCount: number; totalSize: number; namespaces: Record; types: Record; }> { const stats = { entryCount: this.memory.size, totalSize: Array.from(this.memory.values()).reduce((sum, entry) => sum + entry.size, 0), namespaces: {} as Record, types: {} as Record }; // Count entries by namespace for (const [namespace, entries] of this.namespaceIndex.entries()) { stats.namespaces[namespace] = entries.size; } // Count entries by type for (const [type, entries] of this.typeIndex.entries()) { stats.types[type] = entries.size; } return stats; } /** * Clean expired entries */ async cleanup(): Promise { const now = new Date(); let removed = 0; for (const [id, entry] of this.memory.entries()) { if (entry.expiresAt && entry.expiresAt < now) { if (await this.delete(id)) { removed++; } } } return removed; } // === PRIVATE HELPER METHODS === private getEntryByKey(key: string, namespace: string): AdvancedMemoryEntry | null { const id = this.keyToId.get(`${namespace}:${key}`); if (!id) return null; const entry = this.memory.get(id); if (!entry) { // Clean up orphaned reference this.keyToId.delete(`${namespace}:${key}`); return null; } // Check if expired if (entry.expiresAt && entry.expiresAt < new Date()) { this.delete(id).catch(err => this.logger.error('Failed to delete expired entry', { err, id }) ); return null; } return entry; } private updateAccessStats(entry: AdvancedMemoryEntry): void { entry.lastAccessedAt = new Date(); entry.accessCount++; // Slight boost to cognitive weight on access entry.cognitiveWeight *= 1.01; this.emit('memory:accessed', { id: entry.id, key: entry.key, namespace: entry.namespace }); } private updateIndexes(entry: AdvancedMemoryEntry): void { // Namespace index if (!this.namespaceIndex.has(entry.namespace)) { this.namespaceIndex.set(entry.namespace, new Set()); } this.namespaceIndex.get(entry.namespace)!.add(entry.id); // Type index if (entry.type) { if (!this.typeIndex.has(entry.type)) { this.typeIndex.set(entry.type, new Set()); } this.typeIndex.get(entry.type)!.add(entry.id); } // Tag index if (entry.tags) { for (const tag of entry.tags) { if (!this.tagIndex.has(tag)) { this.tagIndex.set(tag, new Set()); } this.tagIndex.get(tag)!.add(entry.id); } } // Owner index if (entry.owner) { if (!this.ownerIndex.has(entry.owner)) { this.ownerIndex.set(entry.owner, new Set()); } this.ownerIndex.get(entry.owner)!.add(entry.id); } } private removeFromIndexes(entry: AdvancedMemoryEntry): void { // Namespace index this.namespaceIndex.get(entry.namespace)?.delete(entry.id); // Type index if (entry.type) { this.typeIndex.get(entry.type)?.delete(entry.id); } // Tag index if (entry.tags) { for (const tag of entry.tags) { this.tagIndex.get(tag)?.delete(entry.id); } } // Owner index if (entry.owner) { this.ownerIndex.get(entry.owner)?.delete(entry.id); } } private calculateSize(value: any): number { try { // Simple size estimate in bytes const json = JSON.stringify(value); return json.length * 2; // Approximate UTF-16 encoding } catch (error) { // Fallback for values that can't be stringified return 1000; } } }