UNPKG

@sethdouglasford/claude-flow

Version:

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

374 lines 13.3 kB
/** * Agent Registry with Memory Integration * Provides persistent storage and coordination for agent management */ import { EventEmitter } from "node:events"; /** * Centralized agent registry with persistent storage */ export class AgentRegistry extends EventEmitter { memory; namespace; cache = new Map(); cacheExpiry = 60000; // 1 minute lastCacheUpdate = 0; constructor(memory, namespace = "agents") { super(); this.memory = memory; this.namespace = namespace; } async initialize() { await this.loadFromMemory(); this.emit("registry:initialized"); } /** * Register a new agent in the registry */ async registerAgent(agent, tags = []) { const entry = { agent, createdAt: new Date(), lastUpdated: new Date(), tags: [...tags, agent.type, agent.status], metadata: { registeredBy: "agent-manager", version: "1.0.0", }, }; // Store in memory const key = this.getAgentKey(agent.id.id); await this.memory.store(key, entry, { type: "agent-registry", tags: entry.tags, partition: this.namespace, }); // Update cache this.cache.set(agent.id.id, entry); this.emit("agent:registered", { agentId: agent.id.id, agent }); } /** * Update agent information in registry */ async updateAgent(agentId, updates) { const entry = await this.getAgentEntry(agentId); if (!entry) { throw new Error(`Agent ${agentId} not found in registry`); } // Merge updates entry.agent = { ...entry.agent, ...updates }; entry.lastUpdated = new Date(); entry.tags = [entry.agent.type, entry.agent.status, ...entry.tags.filter(t => t !== entry.agent.type && t !== entry.agent.status)]; // Store updated entry const key = this.getAgentKey(agentId); await this.memory.store(key, entry, { type: "agent-registry", tags: entry.tags, partition: this.namespace, }); // Update cache this.cache.set(agentId, entry); this.emit("agent:updated", { agentId, agent: entry.agent }); } /** * Remove agent from registry */ async unregisterAgent(agentId, preserveHistory = true) { const entry = await this.getAgentEntry(agentId); if (!entry) { return; // Already removed } if (preserveHistory) { // Move to archived partition const archiveKey = this.getArchiveKey(agentId); await this.memory.store(archiveKey, { ...entry, archivedAt: new Date(), reason: "agent_removed", }, { type: "agent-archive", tags: [...entry.tags, "archived"], partition: "archived", }); } // Remove from active registry const key = this.getAgentKey(agentId); await this.memory.deleteEntry(key); // Remove from cache this.cache.delete(agentId); this.emit("agent:unregistered", { agentId, preserved: preserveHistory }); } /** * Get agent by ID */ async getAgent(agentId) { const entry = await this.getAgentEntry(agentId); return entry?.agent ?? null; } /** * Get agent entry with metadata */ async getAgentEntry(agentId) { // Check cache first if (this.cache.has(agentId) && this.isCacheValid()) { return this.cache.get(agentId) ?? null; } // Load from memory const key = this.getAgentKey(agentId); const memoryEntry = await this.memory.retrieve(key); if (memoryEntry && this.isValidAgentRegistryEntry(memoryEntry.value)) { const entry = memoryEntry.value; this.cache.set(agentId, entry); return entry; } return null; } /** * Query agents by criteria */ async queryAgents(query = {}) { await this.refreshCacheIfNeeded(); let agents = Array.from(this.cache.values()).map(entry => entry.agent); // Apply filters if (query.type) { agents = agents.filter(agent => agent.type === query.type); } if (query.status) { agents = agents.filter(agent => agent.status === query.status); } if (query.healthThreshold !== undefined) { const threshold = query.healthThreshold; agents = agents.filter(agent => agent.health >= threshold); } if (query.namePattern) { const pattern = new RegExp(query.namePattern, "i"); agents = agents.filter(agent => pattern.test(agent.name)); } if (query.tags && query.tags.length > 0) { const { tags } = query; const entries = Array.from(this.cache.values()); const matchingEntries = entries.filter(entry => tags.some(tag => entry.tags.includes(tag))); agents = matchingEntries.map(entry => entry.agent); } if (query.createdAfter) { const { createdAfter } = query; const entries = Array.from(this.cache.values()); const matchingEntries = entries.filter(entry => entry.createdAt >= createdAfter); agents = matchingEntries.map(entry => entry.agent); } if (query.lastActiveAfter) { const { lastActiveAfter } = query; agents = agents.filter(agent => agent.metrics.lastActivity >= lastActiveAfter); } return agents; } /** * Get all registered agents */ async getAllAgents() { return this.queryAgents(); } /** * Get agents by type */ async getAgentsByType(type) { return this.queryAgents({ type }); } /** * Get agents by status */ async getAgentsByStatus(status) { return this.queryAgents({ status }); } /** * Get healthy agents */ async getHealthyAgents(threshold = 0.7) { return this.queryAgents({ healthThreshold: threshold }); } /** * Get registry statistics */ async getStatistics() { const agents = await this.getAllAgents(); const stats = { totalAgents: agents.length, byType: {}, byStatus: {}, averageHealth: 0, activeAgents: 0, totalUptime: 0, tasksCompleted: 0, successRate: 0, }; if (agents.length === 0) { return stats; } // Count by type and status for (const agent of agents) { stats.byType[agent.type] = (stats.byType[agent.type] ?? 0) + 1; stats.byStatus[agent.status] = (stats.byStatus[agent.status] ?? 0) + 1; if (agent.status === "idle" || agent.status === "busy") { stats.activeAgents++; } stats.totalUptime += agent.metrics.totalUptime; stats.tasksCompleted += agent.metrics.tasksCompleted; } // Calculate averages stats.averageHealth = agents.reduce((sum, agent) => sum + agent.health, 0) / agents.length; const totalTasks = agents.reduce((sum, agent) => sum + agent.metrics.tasksCompleted + agent.metrics.tasksFailed, 0); if (totalTasks > 0) { stats.successRate = stats.tasksCompleted / totalTasks; } return stats; } /** * Search agents by capabilities */ async searchByCapabilities(requiredCapabilities) { const agents = await this.getAllAgents(); return agents.filter(agent => { const capabilities = [ ...agent.capabilities.languages, ...agent.capabilities.frameworks, ...agent.capabilities.domains, ...agent.capabilities.tools, ]; return requiredCapabilities.every(required => capabilities.some(cap => cap.toLowerCase().includes(required.toLowerCase()))); }); } /** * Find best agent for task */ async findBestAgent(taskType, requiredCapabilities = [], preferredAgent) { let candidates = await this.getHealthyAgents(0.5); // Filter by capabilities if specified if (requiredCapabilities.length > 0) { candidates = await this.searchByCapabilities(requiredCapabilities); } // Prefer specific agent if available and healthy if (preferredAgent) { const preferred = candidates.find(agent => agent.id.id === preferredAgent || agent.name === preferredAgent); if (preferred) return preferred; } // Filter by availability candidates = candidates.filter(agent => agent.status === "idle" && agent.workload < 0.8 && agent.capabilities.maxConcurrentTasks > 0); if (candidates.length === 0) return null; // Score candidates const scored = candidates.map(agent => ({ agent, score: this.calculateAgentScore(agent, taskType, requiredCapabilities), })); // Sort by score (highest first) scored.sort((a, b) => b.score - a.score); return scored[0]?.agent ?? null; } /** * Store agent coordination data */ async storeCoordinationData(agentId, data) { const key = `coordination:${agentId}`; const coordinationData = { agentId, data, timestamp: new Date(), }; await this.memory.store(key, coordinationData, { type: "agent-coordination", tags: ["coordination", agentId], partition: this.namespace, }); } /** * Retrieve agent coordination data */ async getCoordinationData(agentId) { const key = `coordination:${agentId}`; const result = await this.memory.retrieve(key); if (result?.value && this.isValidCoordinationData(result.value)) { return result.value; } return null; } // === PRIVATE METHODS === isValidAgentRegistryEntry(value) { if (typeof value !== "object" || value === null) return false; const candidate = value; return (typeof candidate.agent === "object" && candidate.agent !== null && candidate.createdAt instanceof Date && candidate.lastUpdated instanceof Date && Array.isArray(candidate.tags) && typeof candidate.metadata === "object" && candidate.metadata !== null); } isValidCoordinationData(value) { if (typeof value !== "object" || value === null) return false; const candidate = value; return (typeof candidate.agentId === "string" && typeof candidate.data === "object" && candidate.data !== null && candidate.timestamp instanceof Date); } async loadFromMemory() { try { const entries = await this.memory.query({ type: "state", partition: this.namespace, }); this.cache.clear(); for (const entry of entries) { if (entry.value && this.isValidAgentRegistryEntry(entry.value)) { const registryEntry = entry.value; this.cache.set(registryEntry.agent.id.id, registryEntry); } } this.lastCacheUpdate = Date.now(); } catch (error) { console.warn("Failed to load agent registry from memory:", error); } } async refreshCacheIfNeeded() { if (!this.isCacheValid()) { await this.loadFromMemory(); } } isCacheValid() { return Date.now() - this.lastCacheUpdate < this.cacheExpiry; } getAgentKey(agentId) { return `agent:${agentId}`; } getArchiveKey(agentId) { return `archived:${agentId}:${Date.now()}`; } calculateAgentScore(agent, taskType, requiredCapabilities) { let score = 0; // Base health score (0-40 points) score += agent.health * 40; // Success rate score (0-30 points) score += agent.metrics.successRate * 30; // Availability score (0-20 points) const availability = 1 - agent.workload; score += availability * 20; // Capability match score (0-10 points) if (requiredCapabilities.length > 0) { const agentCaps = [ ...agent.capabilities.languages, ...agent.capabilities.frameworks, ...agent.capabilities.domains, ...agent.capabilities.tools, ]; const matches = requiredCapabilities.filter(required => agentCaps.some(cap => cap.toLowerCase().includes(required.toLowerCase()))); score += (matches.length / requiredCapabilities.length) * 10; } return score; } } //# sourceMappingURL=agent-registry.js.map