Establish baseline: core documentation and memory system
Adds foundational workspace documentation: - Core identity and operating principles (SOUL.md, IDENTITY.md, AGENTS.md, USER.md) - Memory management system specification (MEMORY.md) - Development team context and workflow (CONTEXT.md, PROJECTS.md) - Environment notes template (TOOLS.md) - Heartbeat task template (HEARTBEAT.md) Includes super-enhanced memory system: - JavaScript memory engine implementation - Memory initialization and test scripts - Daily memory logs for 2026-07-03, 2026-07-04 - Project-level memory structure for site-survey-ai Adds project templates: - Standard Python Flask application template
This commit is contained in:
@@ -0,0 +1,404 @@
|
||||
/**
|
||||
* 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}`);
|
||||
*/
|
||||
Reference in New Issue
Block a user