100 lines
3.7 KiB
JavaScript
100 lines
3.7 KiB
JavaScript
// ═══════════════════════════════════════════
|
|
// 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 };
|