← back to Designer Wallcoverings

DW-Agents/dw-agents/agent-todays-highlights/shared-memory-system.ts

216 lines

/**
 * Shared Memory System for DW-Agents
 *
 * Provides persistent memory storage for all agents.
 * Each agent can store and retrieve memories that persist across restarts.
 */

import * as fs from 'fs';
import * as path from 'path';

export interface Memory {
  id: string;
  timestamp: Date;
  type: 'note' | 'reminder' | 'preference' | 'learning' | 'conversation' | 'task';
  content: string;
  metadata?: Record<string, any>;
}

export class AgentMemory {
  private memoryFile: string;
  private memories: Memory[] = [];
  private agentName: string;

  constructor(agentName: string) {
    this.agentName = agentName;
    this.memoryFile = path.join(__dirname, `agent-${agentName}`, 'memory.json');
    this.loadMemories();
  }

  /**
   * Load memories from disk
   */
  private loadMemories(): void {
    try {
      if (fs.existsSync(this.memoryFile)) {
        const data = fs.readFileSync(this.memoryFile, 'utf8');
        const parsed = JSON.parse(data);

        // Ensure parsed data is an array
        if (Array.isArray(parsed)) {
          this.memories = parsed;
          console.log(`📚 Loaded ${this.memories.length} memories for ${this.agentName}`);
        } else {
          console.warn(`⚠️  Invalid memory data format for ${this.agentName}, expected array but got ${typeof parsed}`);
          this.memories = [];
        }
      } else {
        console.log(`📝 No existing memories for ${this.agentName}, starting fresh`);
        this.memories = [];
      }
    } catch (error) {
      console.error(`❌ Error loading memories for ${this.agentName}:`, error);
      this.memories = [];
    }
  }

  /**
   * Save memories to disk
   */
  private saveMemories(): void {
    try {
      const dir = path.dirname(this.memoryFile);
      if (!fs.existsSync(dir)) {
        fs.mkdirSync(dir, { recursive: true });
      }
      fs.writeFileSync(this.memoryFile, JSON.stringify(this.memories, null, 2));
      console.log(`💾 Saved ${this.memories.length} memories for ${this.agentName}`);
    } catch (error) {
      console.error(`❌ Error saving memories for ${this.agentName}:`, error);
    }
  }

  /**
   * Add a new memory
   */
  add(content: string, type: Memory['type'] = 'note', metadata?: Record<string, any>): Memory {
    const memory: Memory = {
      id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
      timestamp: new Date(),
      type,
      content,
      metadata
    };

    this.memories.push(memory);
    this.saveMemories();

    return memory;
  }

  /**
   * Get all memories
   */
  getAll(): Memory[] {
    return this.memories;
  }

  /**
   * Get memories by type
   */
  getByType(type: Memory['type']): Memory[] {
    // Defensive check to ensure memories is an array
    if (!Array.isArray(this.memories)) {
      console.error('❌ memories is not an array, resetting to empty array');
      this.memories = [];
    }
    return this.memories.filter(m => m.type === type);
  }

  /**
   * Search memories by content
   */
  search(query: string): Memory[] {
    // Defensive check to ensure memories is an array
    if (!Array.isArray(this.memories)) {
      console.error('❌ memories is not an array, resetting to empty array');
      this.memories = [];
    }
    const lowerQuery = query.toLowerCase();
    return this.memories.filter(m =>
      m.content.toLowerCase().includes(lowerQuery)
    );
  }

  /**
   * Delete a memory by ID
   */
  delete(id: string): boolean {
    const index = this.memories.findIndex(m => m.id === id);
    if (index !== -1) {
      this.memories.splice(index, 1);
      this.saveMemories();
      return true;
    }
    return false;
  }

  /**
   * Clear all memories
   */
  clear(): void {
    this.memories = [];
    this.saveMemories();
  }

  /**
   * Get recent memories (last N)
   */
  getRecent(count: number = 10): Memory[] {
    return this.memories.slice(-count).reverse();
  }

  /**
   * Get formatted summary of all memories for AI context
   */
  getContextSummary(): string {
    if (this.memories.length === 0) {
      return 'No stored memories yet.';
    }

    const byType: Record<string, Memory[]> = {};
    this.memories.forEach(m => {
      if (!byType[m.type]) byType[m.type] = [];
      byType[m.type].push(m);
    });

    let summary = `Agent Memory (${this.memories.length} total):\n\n`;

    Object.entries(byType).forEach(([type, mems]) => {
      summary += `${type.toUpperCase()} (${mems.length}):\n`;
      mems.slice(-5).forEach(m => {
        summary += `- ${m.content}\n`;
      });
      summary += '\n';
    });

    return summary;
  }
}

/**
 * Example usage in an agent:
 *
 * import { AgentMemory } from '../shared-memory-system';
 *
 * const memory = new AgentMemory('marketing');
 *
 * // Add memories
 * memory.add('Always use purple for primary buttons', 'preference');
 * memory.add('Check inventory every Monday', 'reminder');
 * memory.add('Customer John prefers email contact', 'note');
 *
 * // Retrieve in AI prompts
 * const context = memory.getContextSummary();
 * const systemPrompt = `You are the Marketing Agent. ${context}`;
 *
 * // Search
 * const buttonPrefs = memory.search('button');
 *
 * // Use in chat endpoint
 * app.post('/api/chat', async (req, res) => {
 *   const { message } = req.body;
 *
 *   // Auto-detect memory commands
 *   if (message.toLowerCase().includes('remember:') || message.toLowerCase().includes('note:')) {
 *     const content = message.split(/remember:|note:/i)[1].trim();
 *     memory.add(content, 'note');
 *     return res.json({ response: 'Memory saved!', success: true });
 *   }
 *
 *   // Include memories in AI context
 *   const systemPrompt = `You are the Agent. ${memory.getContextSummary()}`;
 *   // ... rest of AI call
 * });
 */