UNPKG

@sethdouglasford/claude-flow

Version:

Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology

456 lines 18 kB
import { EventEmitter } from "node:events"; import { Logger } from "../core/logger.js"; import { MemoryManager } from "./manager.js"; import { EventBus } from "../core/event-bus.js"; import { generateId } from "../utils/helpers.js"; import * as fs from "node:fs/promises"; import * as path from "node:path"; export class SwarmMemoryManager extends EventEmitter { logger; config; baseMemory; entries; knowledgeBases; agentMemories; // agentId -> set of entry IDs syncTimer; isInitialized = false; constructor(config = {}) { super(); this.logger = new Logger({ level: "info", format: "text", destination: "console" }, { component: "SwarmMemoryManager" }); this.config = { namespace: "swarm", enableDistribution: true, enableReplication: true, syncInterval: 10000, // 10 seconds maxEntries: 10000, compressionThreshold: 1000, enableKnowledgeBase: true, enableCrossAgentSharing: true, persistencePath: "./swarm-memory", ...config, }; this.entries = new Map(); this.knowledgeBases = new Map(); this.agentMemories = new Map(); const memoryConfig = { backend: "markdown", // Use markdown as default backend (no external deps) cacheSizeMB: 100, syncInterval: 30000, conflictResolution: "last-write", retentionDays: 30, sqlitePath: path.join(this.config.persistencePath, "memory"), }; const eventBus = EventBus.getInstance(); this.baseMemory = new MemoryManager(memoryConfig, eventBus, this.logger); } async initialize() { if (this.isInitialized) return; this.logger.info("Initializing swarm memory manager..."); // Initialize base memory await this.baseMemory.initialize(); // Create persistence directory await fs.mkdir(this.config.persistencePath, { recursive: true }); // Load existing memory await this.loadMemoryState(); // Start sync timer if (this.config.syncInterval > 0) { this.syncTimer = setInterval(() => { void this.syncMemoryState(); }, this.config.syncInterval); } this.isInitialized = true; this.emit("memory:initialized"); } async shutdown() { if (!this.isInitialized) return; this.logger.info("Shutting down swarm memory manager..."); // Stop sync timer if (this.syncTimer) { clearInterval(this.syncTimer); this.syncTimer = undefined; } // Save final state await this.saveMemoryState(); this.isInitialized = false; this.emit("memory:shutdown"); } async remember(agentId, type, content, metadata = {}) { const entryId = generateId("mem"); const entry = { id: entryId, agentId, type, content, timestamp: new Date(), metadata: { shareLevel: "team", priority: 1, ...metadata, }, }; this.entries.set(entryId, entry); // Associate with agent if (!this.agentMemories.has(agentId)) { this.agentMemories.set(agentId, new Set()); } const agentMemorySet = this.agentMemories.get(agentId); if (agentMemorySet) { agentMemorySet.add(entryId); } // Store in base memory for persistence await this.baseMemory.store({ id: entryId, agentId, sessionId: "swarm-session", // Default session ID for swarm operations type: "artifact", // Map swarm entries to artifact type content: JSON.stringify(entry), context: { namespace: this.config.namespace, swarmType: type, shareLevel: entry.metadata.shareLevel, }, timestamp: entry.timestamp, tags: entry.metadata.tags ?? [], version: 1, metadata: { type: "swarm-memory", entryType: type, shareLevel: entry.metadata.shareLevel, }, }); this.logger.debug(`Agent ${agentId} remembered: ${type} - ${entryId}`); this.emit("memory:added", entry); // Update knowledge base if applicable if (type === "knowledge" && this.config.enableKnowledgeBase) { await this.updateKnowledgeBase(entry); } // Check for memory limits await this.enforceMemoryLimits(); return entryId; } async recall(query) { let results = Array.from(this.entries.values()); // Apply filters if (query.agentId) { results = results.filter(e => e.agentId === query.agentId); } if (query.type) { results = results.filter(e => e.type === query.type); } if (query.taskId) { results = results.filter(e => e.metadata.taskId === query.taskId); } if (query.objectiveId) { results = results.filter(e => e.metadata.objectiveId === query.objectiveId); } if (query.tags && query.tags.length > 0) { results = results.filter(e => e.metadata.tags && query.tags.some(tag => e.metadata.tags?.includes(tag))); } if (query.since) { results = results.filter(e => e.timestamp >= query.since); } if (query.before) { results = results.filter(e => e.timestamp <= query.before); } if (query.shareLevel) { results = results.filter(e => e.metadata.shareLevel === query.shareLevel); } // Sort by timestamp (newest first) results.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()); // Apply limit if (query.limit) { results = results.slice(0, query.limit); } this.logger.debug(`Recalled ${results.length} memories for query`); return results; } async shareMemory(entryId, targetAgentId) { const entry = this.entries.get(entryId); if (!entry) { throw new Error("Memory entry not found"); } if (!this.config.enableCrossAgentSharing) { throw new Error("Cross-agent sharing is disabled"); } // Check share level permissions if (entry.metadata.shareLevel === "private") { throw new Error("Memory entry is private and cannot be shared"); } // Create a shared copy for the target agent const sharedEntry = { ...entry, id: generateId("mem"), metadata: { ...entry.metadata, originalId: entryId, sharedFrom: entry.agentId, sharedTo: targetAgentId, sharedAt: new Date(), }, }; this.entries.set(sharedEntry.id, sharedEntry); // Associate with target agent if (!this.agentMemories.has(targetAgentId)) { this.agentMemories.set(targetAgentId, new Set()); } const targetAgentMemorySet = this.agentMemories.get(targetAgentId); if (targetAgentMemorySet) { targetAgentMemorySet.add(sharedEntry.id); } this.logger.info(`Shared memory ${entryId} from ${entry.agentId} to ${targetAgentId}`); this.emit("memory:shared", { original: entry, shared: sharedEntry }); } async broadcastMemory(entryId, agentIds) { const entry = this.entries.get(entryId); if (!entry) { throw new Error("Memory entry not found"); } if (entry.metadata.shareLevel === "private") { throw new Error("Cannot broadcast private memory"); } const targets = agentIds ?? Array.from(this.agentMemories.keys()) .filter(id => id !== entry.agentId); for (const targetId of targets) { try { await this.shareMemory(entryId, targetId); } catch (error) { this.logger.warn(`Failed to share memory to ${targetId}:`, error); } } this.logger.info(`Broadcasted memory ${entryId} to ${targets.length} agents`); } async createKnowledgeBase(name, description, domain, expertise) { const kbId = generateId("kb"); const knowledgeBase = { id: kbId, name, description, entries: [], metadata: { domain, expertise, contributors: [], lastUpdated: new Date(), }, }; this.knowledgeBases.set(kbId, knowledgeBase); this.logger.info(`Created knowledge base: ${name} (${kbId})`); this.emit("knowledgebase:created", knowledgeBase); return kbId; } async updateKnowledgeBase(entry) { if (!this.config.enableKnowledgeBase) return; // Find relevant knowledge bases const relevantKBs = Array.from(this.knowledgeBases.values()) .filter(kb => { // Simple matching based on tags and content const tags = entry.metadata.tags ?? []; return tags.some(tag => kb.metadata.expertise.some(exp => exp.toLowerCase().includes(tag.toLowerCase()) || tag.toLowerCase().includes(exp.toLowerCase()))); }); for (const kb of relevantKBs) { // Add entry to knowledge base kb.entries.push(entry); kb.metadata.lastUpdated = new Date(); // Add contributor if (!kb.metadata.contributors.includes(entry.agentId)) { kb.metadata.contributors.push(entry.agentId); } this.logger.debug(`Updated knowledge base ${kb.id} with entry ${entry.id}`); } } async searchKnowledge(query, domain, expertise) { const allEntries = []; // Search in knowledge bases for (const kb of this.knowledgeBases.values()) { if (domain && kb.metadata.domain !== domain) continue; if (expertise && !expertise.some(exp => kb.metadata.expertise.includes(exp))) { continue; } allEntries.push(...kb.entries); } // Simple text search (in real implementation, use better search) const queryLower = query.toLowerCase(); const results = allEntries.filter(entry => { const contentStr = JSON.stringify(entry.content).toLowerCase(); return contentStr.includes(queryLower); }); return results.slice(0, 50); // Limit results } async getAgentMemorySnapshot(agentId) { const agentEntryIds = this.agentMemories.get(agentId) ?? new Set(); const agentEntries = Array.from(agentEntryIds) .map(id => this.entries.get(id)) .filter(Boolean); const recentEntries = agentEntries .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()) .slice(0, 10); const knowledgeContributions = agentEntries .filter(e => e.type === "knowledge").length; const sharedEntries = agentEntries .filter(e => e.metadata.shareLevel === "public" || e.metadata.shareLevel === "team").length; return { totalEntries: agentEntries.length, recentEntries, knowledgeContributions, sharedEntries, }; } async loadMemoryState() { try { // Load entries const entriesFile = path.join(this.config.persistencePath, "entries.json"); try { const entriesData = await fs.readFile(entriesFile, "utf-8"); const entriesArray = JSON.parse(entriesData); for (const entry of entriesArray) { this.entries.set(entry.id, { ...entry, timestamp: new Date(entry.timestamp), }); // Rebuild agent memory associations if (!this.agentMemories.has(entry.agentId)) { this.agentMemories.set(entry.agentId, new Set()); } const entryAgentMemorySet = this.agentMemories.get(entry.agentId); if (entryAgentMemorySet) { entryAgentMemorySet.add(entry.id); } } this.logger.info(`Loaded ${entriesArray.length} memory entries`); } catch (error) { this.logger.warn("No existing memory entries found"); } // Load knowledge bases const kbFile = path.join(this.config.persistencePath, "knowledge-bases.json"); try { const kbData = await fs.readFile(kbFile, "utf-8"); const kbArray = JSON.parse(kbData); for (const kb of kbArray) { this.knowledgeBases.set(kb.id, { ...kb, metadata: { ...kb.metadata, lastUpdated: new Date(kb.metadata.lastUpdated), }, entries: kb.entries.map((e) => ({ ...e, timestamp: new Date(e.timestamp), })), }); } this.logger.info(`Loaded ${kbArray.length} knowledge bases`); } catch (error) { this.logger.warn("No existing knowledge bases found"); } } catch (error) { this.logger.error("Error loading memory state:", error); } } async saveMemoryState() { try { // Save entries const entriesArray = Array.from(this.entries.values()); const entriesFile = path.join(this.config.persistencePath, "entries.json"); await fs.writeFile(entriesFile, JSON.stringify(entriesArray, null, 2)); // Save knowledge bases const kbArray = Array.from(this.knowledgeBases.values()); const kbFile = path.join(this.config.persistencePath, "knowledge-bases.json"); await fs.writeFile(kbFile, JSON.stringify(kbArray, null, 2)); this.logger.debug("Saved memory state to disk"); } catch (error) { this.logger.error("Error saving memory state:", error); } } async syncMemoryState() { try { await this.saveMemoryState(); this.emit("memory:synced"); } catch (error) { this.logger.error("Error syncing memory state:", error); } } async enforceMemoryLimits() { if (this.entries.size <= this.config.maxEntries) return; this.logger.info("Enforcing memory limits..."); // Remove oldest entries that are not marked as important const entries = Array.from(this.entries.values()) .filter(e => (e.metadata.priority ?? 1) <= 1) // Only remove low priority .sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); const toRemove = entries.slice(0, this.entries.size - this.config.maxEntries); for (const entry of toRemove) { this.entries.delete(entry.id); // Remove from agent memory const agentEntries = this.agentMemories.get(entry.agentId); if (agentEntries) { agentEntries.delete(entry.id); } this.logger.debug(`Removed old memory entry: ${entry.id}`); } this.emit("memory:cleaned", toRemove.length); } // Public API methods getMemoryStats() { const entries = Array.from(this.entries.values()); const entriesByType = {}; const entriesByAgent = {}; for (const entry of entries) { entriesByType[entry.type] = (entriesByType[entry.type] ?? 0) + 1; entriesByAgent[entry.agentId] = (entriesByAgent[entry.agentId] ?? 0) + 1; } // Rough memory usage calculation const memoryUsage = JSON.stringify(entries).length; return { totalEntries: entries.length, entriesByType, entriesByAgent, knowledgeBases: this.knowledgeBases.size, memoryUsage, }; } async exportMemory(agentId) { const entries = agentId ? await this.recall({ agentId }) : Array.from(this.entries.values()); return { entries, knowledgeBases: agentId ? Array.from(this.knowledgeBases.values()).filter(kb => kb.metadata.contributors.includes(agentId)) : Array.from(this.knowledgeBases.values()), exportedAt: new Date(), stats: this.getMemoryStats(), }; } async clearMemory(agentId) { if (agentId) { // Clear specific agent's memory const entryIds = this.agentMemories.get(agentId) ?? new Set(); for (const entryId of entryIds) { this.entries.delete(entryId); } this.agentMemories.delete(agentId); this.logger.info(`Cleared memory for agent ${agentId}`); } else { // Clear all memory this.entries.clear(); this.agentMemories.clear(); this.knowledgeBases.clear(); this.logger.info("Cleared all swarm memory"); } this.emit("memory:cleared", { agentId }); } } //# sourceMappingURL=swarm-memory.js.map