UNPKG

claude-flow-tbowman01

Version:

Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)

454 lines 17.9 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('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 eventBus = EventBus.getInstance(); this.baseMemory = new MemoryManager({ backend: 'sqlite', namespace: this.config.namespace, cacheSizeMB: 50, syncOnExit: true, maxEntries: this.config.maxEntries, ttlMinutes: 60, }, 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(() => { 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()); } this.agentMemories.get(agentId).add(entryId); // Store in base memory for persistence await this.baseMemory.remember({ namespace: this.config.namespace, key: `entry:${entryId}`, content: JSON.stringify(entry), metadata: { type: 'swarm-memory', agentId, 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()); } this.agentMemories.get(targetAgentId).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()); } this.agentMemories.get(entry.agentId).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 }); } // Compatibility methods for hive.ts async store(key, value) { // Extract namespace and actual key from the path const parts = key.split('/'); const type = parts[0] || 'state'; const agentId = parts[1] || 'system'; await this.remember(agentId, type, value, { tags: [parts[0], parts[1]].filter(Boolean), shareLevel: 'team', }); } async search(pattern, limit = 10) { // Simple pattern matching on stored keys/content const results = []; for (const entry of this.entries.values()) { const entryString = JSON.stringify(entry); if (entryString.includes(pattern.replace('*', ''))) { results.push(entry.content); if (results.length >= limit) break; } } return results; } } //# sourceMappingURL=swarm-memory.js.map