// ═══════════════════════════════════════════ // PROJECT MNEMOSYNE — MEMORY OPTIMIZER (GOFAI) // ═══════════════════════════════════════════ // // Heuristic-based memory pruning and organization. // Operates without LLMs to maintain a lean, high-signal spatial index. // // Heuristics: // 1. Strength Decay: Memories lose strength over time if not accessed. // 2. Redundancy: Simple string similarity to identify duplicates. // 3. Isolation: Memories with no connections are lower priority. // 4. Aging: Old memories in 'working' are moved to 'archive'. // ═══════════════════════════════════════════ const MemoryOptimizer = (() => { const DECAY_RATE = 0.01; // Strength lost per optimization cycle const PRUNE_THRESHOLD = 0.1; // Remove if strength < this const SIMILARITY_THRESHOLD = 0.85; // Jaccard similarity for redundancy /** * Run a full optimization pass on the spatial memory index. * @param {object} spatialMemory - The SpatialMemory component instance. * @returns {object} Summary of actions taken. */ function optimize(spatialMemory) { const memories = spatialMemory.getAllMemories(); const results = { pruned: 0, moved: 0, updated: 0 }; // 1. Strength Decay & Aging memories.forEach(mem => { let strength = mem.strength || 0.7; strength -= DECAY_RATE; if (strength < PRUNE_THRESHOLD) { spatialMemory.removeMemory(mem.id); results.pruned++; return; } // Move old working memories to archive if (mem.category === 'working') { const timestamp = mem.timestamp || new Date().toISOString(); const age = Date.now() - new Date(timestamp).getTime(); if (age > 1000 * 60 * 60 * 24) { // 24 hours spatialMemory.removeMemory(mem.id); spatialMemory.placeMemory({ ...mem, category: 'archive', strength }); results.moved++; return; } } spatialMemory.updateMemory(mem.id, { strength }); results.updated++; }); // 2. Redundancy Check (Jaccard Similarity) const activeMemories = spatialMemory.getAllMemories(); for (let i = 0; i < activeMemories.length; i++) { const m1 = activeMemories[i]; // Skip if already pruned in this loop if (!spatialMemory.getAllMemories().find(m => m.id === m1.id)) continue; for (let j = i + 1; j < activeMemories.length; j++) { const m2 = activeMemories[j]; if (m1.category !== m2.category) continue; const sim = _calculateSimilarity(m1.content, m2.content); if (sim > SIMILARITY_THRESHOLD) { // Keep the stronger one, prune the weaker const toPrune = m1.strength >= m2.strength ? m2.id : m1.id; spatialMemory.removeMemory(toPrune); results.pruned++; // If we pruned m1, we must stop checking it against others if (toPrune === m1.id) break; } } } console.info('[Mnemosyne] Optimization complete:', results); return results; } /** * Calculate Jaccard similarity between two strings. * @private */ function _calculateSimilarity(s1, s2) { if (!s1 || !s2) return 0; const set1 = new Set(s1.toLowerCase().split(/\s+/)); const set2 = new Set(s2.toLowerCase().split(/\s+/)); const intersection = new Set([...set1].filter(x => set2.has(x))); const union = new Set([...set1, ...set2]); return intersection.size / union.size; } return { optimize }; })(); export { MemoryOptimizer };