/** * Super-Enhanced Memory System Implementation * Based on the specification provided by JC Beasley */ // Memory Categories const MEMORY_CATEGORIES = { IDENTITY: 'identity', PREFERENCES: 'preferences', GOALS: 'goals', KNOWLEDGE: 'knowledge', EPISODIC: 'episodic', CORRECTION: 'correction' }; // Memory Structure class MemoryItem { constructor(key, category, content, importance = 5, tags = []) { this.key = key; this.category = category; this.content = content; this.importance = importance; this.tags = tags; this.created = new Date().toISOString(); this.updated = new Date().toISOString(); this.access_count = 0; this.expires = null; this.confidence = 1.0; } } // Memory Index class MemoryIndex { constructor() { this.index = {}; } async updateIndex(key, memory) { try { // In a real implementation, this would use window.storage // For now, we'll simulate it with file-based storage const indexPath = '/home/jcbeasley/.openclaw/workspace/memory/_index.json'; // Load existing index let index = {}; try { const indexContent = await this.readFile(indexPath); if (indexContent) { index = JSON.parse(indexContent); } } catch (e) { // Index doesn't exist yet, start fresh } // Update index index[key] = { category: memory.category, importance: memory.importance, updated: memory.updated, tags: memory.tags }; // Save index await this.writeFile(indexPath, JSON.stringify(index, null, 2)); } catch (e) { console.error(`Failed to update memory index for ${key}:`, e); } } async readFile(path) { // Simulate file reading // In real implementation, this would use actual file system or storage API return null; } async writeFile(path, content) { // Simulate file writing // In real implementation, this would use actual file system or storage API } } // Memory Manager class MemoryManager { constructor() { this.index = new MemoryIndex(); this.storagePrefix = 'mem:'; } // Create a memory item async createMemory(key, category, content, importance = 5, tags = []) { const memory = new MemoryItem(key, category, content, importance, tags); await this.saveMemory(key, memory); return memory; } // Save memory to storage async saveMemory(key, memory) { try { // In a real implementation, this would use window.storage.set // For now, we'll store in our project memory directory const memoryPath = `/home/jcbeasley/.openclaw/workspace/memory/items/${key.replace(':', '_')}.json`; // Ensure directory exists await this.ensureDirectory('/home/jcbeasley/.openclaw/workspace/memory/items/'); // Save memory item const fs = require('fs').promises; await fs.writeFile(memoryPath, JSON.stringify(memory, null, 2)); // Update index await this.index.updateIndex(key, memory); console.log(`Memory saved: ${key}`); } catch (e) { console.error(`Failed to save memory ${key}:`, e); } } // Load memory from storage async loadMemory(key) { try { // In a real implementation, this would use window.storage.get const memoryPath = `/home/jcbeasley/.openclaw/workspace/memory/items/${key.replace(':', '_')}.json`; const fs = require('fs').promises; const content = await fs.readFile(memoryPath, 'utf8'); const memory = JSON.parse(content); // Update access count memory.access_count = (memory.access_count || 0) + 1; memory.updated = new Date().toISOString(); await this.saveMemory(key, memory); return memory; } catch (e) { console.error(`Failed to load memory ${key}:`, e); return null; } } // Update existing memory (merge, don't overwrite) async updateMemory(key, newContent) { try { let memory = await this.loadMemory(key); if (memory) { // Merge content if (typeof memory.content === 'object' && typeof newContent === 'object') { memory.content = { ...memory.content, ...newContent }; } else { memory.content = newContent; } memory.updated = new Date().toISOString(); memory.access_count = (memory.access_count || 0) + 1; await this.saveMemory(key, memory); return memory; } else { // Create new memory return await this.createMemory(key, MEMORY_CATEGORIES.KNOWLEDGE, newContent); } } catch (e) { console.error(`Failed to update memory ${key}:`, e); return null; } } // Delete memory async deleteMemory(key) { try { // In a real implementation, this would use window.storage.delete const memoryPath = `/home/jcbeasley/.openclaw/workspace/memory/items/${key.replace(':', '_')}.json`; const fs = require('fs').promises; await fs.unlink(memoryPath); console.log(`Memory deleted: ${key}`); } catch (e) { console.error(`Failed to delete memory ${key}:`, e); } } // Expire memory (soft delete) async expireMemory(key) { try { let memory = await this.loadMemory(key); if (memory) { memory.expires = new Date().toISOString(); await this.saveMemory(key, memory); } } catch (e) { console.error(`Failed to expire memory ${key}:`, e); } } // Ensure directory exists async ensureDirectory(dirPath) { const fs = require('fs').promises; try { await fs.mkdir(dirPath, { recursive: true }); } catch (e) { // Directory might already exist } } // Proactive memory behaviors async conversationStart() { console.log("Starting conversation with proactive memory loading..."); // Load memory index const indexPath = '/home/jcbeasley/.openclaw/workspace/memory/_index.json'; let index = {}; try { const fs = require('fs').promises; const indexContent = await fs.readFile(indexPath, 'utf8'); index = JSON.parse(indexContent); } catch (e) { // Index doesn't exist yet } // Load high importance memories (>= 7) const highImportanceMemories = []; for (const [key, metadata] of Object.entries(index)) { if (metadata.importance >= 7) { const memory = await this.loadMemory(key); if (memory) { highImportanceMemories.push(memory); } } } console.log(`Loaded ${highImportanceMemories.length} high-importance memories`); return highImportanceMemories; } // Auto-capture memories during conversation async autoCapture(userMessage, context) { // User states their name if (userMessage.toLowerCase().includes("my name is") || userMessage.toLowerCase().includes("i'm") || userMessage.toLowerCase().includes("i am")) { const nameMatch = userMessage.match(/(?:my name is|i'm|i am)\s+([a-zA-Z]+)/i); if (nameMatch) { await this.createMemory('mem:identity:name', MEMORY_CATEGORIES.IDENTITY, nameMatch[1], 10, ['name']); } } // User corrects information if (userMessage.toLowerCase().includes("actually") || userMessage.toLowerCase().includes("no,") || userMessage.toLowerCase().includes("correction")) { await this.createMemory(`mem:correction:${Date.now()}`, MEMORY_CATEGORIES.CORRECTION, userMessage, 8, ['correction']); } // User expresses preferences if (userMessage.toLowerCase().includes("prefer") || userMessage.toLowerCase().includes("like") || userMessage.toLowerCase().includes("want")) { const prefMatch = userMessage.match(/(?:prefer|like|want)\s+(.+?)(?:\.|$)/i); if (prefMatch) { await this.createMemory(`mem:pref:${prefMatch[1].toLowerCase().replace(/\s+/g, '_')}`, MEMORY_CATEGORIES.PREFERENCES, prefMatch[1], 7, ['preference']); } } // User mentions goals/projects if (userMessage.toLowerCase().includes("goal") || userMessage.toLowerCase().includes("project") || userMessage.toLowerCase().includes("task")) { const goalMatch = userMessage.match(/(?:goal|project|task)\s+(.+?)(?:\.|$)/i); if (goalMatch) { await this.createMemory(`mem:goal:${goalMatch[1].toLowerCase().replace(/\s+/g, '_')}`, MEMORY_CATEGORIES.GOALS, goalMatch[1], 8, ['goal']); } } } // Special commands handling async handleSpecialCommand(command) { switch (command.toLowerCase()) { case "what do you remember about me?": return await this.listIdentityMemories(); case "show my memory profile": return await this.showMemoryProfile(); default: if (command.toLowerCase().startsWith("remember that")) { const content = command.substring("remember that".length).trim(); return await this.createMemory(`mem:episodic:${Date.now()}`, MEMORY_CATEGORIES.EPISODIC, content, 9, ['user-requested']); } else if (command.toLowerCase().startsWith("forget")) { const tag = command.substring("forget".length).trim(); return await this.forgetMemoriesWithTag(tag); } else if (command.toLowerCase().startsWith("update your memory:")) { const content = command.substring("update your memory:".length).trim(); return await this.updateMemoryByContent(content); } } } async listIdentityMemories() { // List high-importance identity memories const indexPath = '/home/jcbeasley/.openclaw/workspace/memory/_index.json'; let index = {}; try { const fs = require('fs').promises; const indexContent = await fs.readFile(indexPath, 'utf8'); index = JSON.parse(indexContent); } catch (e) { // Index doesn't exist yet } const identityMemories = []; for (const [key, metadata] of Object.entries(index)) { if (metadata.category === MEMORY_CATEGORIES.IDENTITY || metadata.importance >= 8) { const memory = await this.loadMemory(key); if (memory) { identityMemories.push(memory); } } } return identityMemories; } async showMemoryProfile() { const indexPath = '/home/jcbeasley/.openclaw/workspace/memory/_index.json'; let index = {}; try { const fs = require('fs').promises; const indexContent = await fs.readFile(indexPath, 'utf8'); index = JSON.parse(indexContent); } catch (e) { // Index doesn't exist yet } const profile = { total: Object.keys(index).length, byCategory: {}, byImportance: {}, recent: [] }; // Categorize memories for (const [key, metadata] of Object.entries(index)) { // By category profile.byCategory[metadata.category] = (profile.byCategory[metadata.category] || 0) + 1; // By importance const importanceLevel = Math.floor(metadata.importance / 2) * 2; // Group by 2s profile.byImportance[importanceLevel] = (profile.byImportance[importanceLevel] || 0) + 1; // Recent memories (last 5) if (profile.recent.length < 5) { profile.recent.push({ key, ...metadata }); } } return profile; } async forgetMemoriesWithTag(tag) { const indexPath = '/home/jcbeasley/.openclaw/workspace/memory/_index.json'; let index = {}; try { const fs = require('fs').promises; const indexContent = await fs.readFile(indexPath, 'utf8'); index = JSON.parse(indexContent); } catch (e) { // Index doesn't exist yet } const deletedKeys = []; for (const [key, metadata] of Object.entries(index)) { if (metadata.tags && metadata.tags.includes(tag)) { await this.deleteMemory(key); delete index[key]; deletedKeys.push(key); } } // Save updated index await this.index.writeFile(indexPath, JSON.stringify(index, null, 2)); return `Deleted ${deletedKeys.length} memories with tag '${tag}': ${deletedKeys.join(', ')}`; } async updateMemoryByContent(content) { // This would search for related memories and update them // For now, we'll create a new memory return await this.createMemory(`mem:knowledge:${Date.now()}`, MEMORY_CATEGORIES.KNOWLEDGE, content, 6, ['user-updated']); } } // Export for use module.exports = { MemoryManager, MEMORY_CATEGORIES }; // Example usage: /* const memoryManager = new MemoryManager(); // At conversation start await memoryManager.conversationStart(); // Auto-capture during conversation await memoryManager.autoCapture("My name is JC", {}); // Create specific memories await memoryManager.createMemory('mem:identity:name', MEMORY_CATEGORIES.IDENTITY, 'JC', 10, ['name']); await memoryManager.createMemory('mem:pref:format', MEMORY_CATEGORIES.PREFERENCES, 'concise answers', 7, ['format']); // Load memories const nameMemory = await memoryManager.loadMemory('mem:identity:name'); console.log(`User's name: ${nameMemory.content}`); */