claude-flow
Version:
Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)
475 lines (396 loc) • 12.7 kB
text/typescript
import { getErrorMessage } from '../utils/error-handler.js';
/**
* Agent Registry with Memory Integration
* Provides persistent storage and coordination for agent management
*/
import type { DistributedMemorySystem } from '../memory/distributed-memory.js';
import type { AgentState, AgentId, AgentType, AgentStatus } from '../swarm/types.js';
import { EventEmitter } from 'node:events';
export interface AgentRegistryEntry {
agent: AgentState;
createdAt: Date;
lastUpdated: Date;
tags: string[];
metadata: Record<string, any>;
}
export interface AgentQuery {
type?: AgentType;
status?: AgentStatus;
tags?: string[];
healthThreshold?: number;
namePattern?: string;
createdAfter?: Date;
lastActiveAfter?: Date;
}
export interface AgentStatistics {
totalAgents: number;
byType: Record<AgentType, number>;
byStatus: Record<AgentStatus, number>;
averageHealth: number;
activeAgents: number;
totalUptime: number;
tasksCompleted: number;
successRate: number;
}
/**
* Centralized agent registry with persistent storage
*/
export class AgentRegistry extends EventEmitter {
private memory: DistributedMemorySystem;
private namespace: string;
private cache = new Map<string, AgentRegistryEntry>();
private cacheExpiry = 60000; // 1 minute
private lastCacheUpdate = 0;
constructor(memory: DistributedMemorySystem, namespace: string = 'agents') {
super();
this.memory = memory;
this.namespace = namespace;
}
async initialize(): Promise<void> {
await this.loadFromMemory();
this.emit('registry:initialized');
}
/**
* Register a new agent in the registry
*/
async registerAgent(agent: AgentState, tags: string[] = []): Promise<void> {
const entry: AgentRegistryEntry = {
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: string, updates: Partial<AgentState>): Promise<void> {
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: string, preserveHistory: boolean = true): Promise<void> {
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: string): Promise<AgentState | null> {
const entry = await this.getAgentEntry(agentId);
return entry?.agent || null;
}
/**
* Get agent entry with metadata
*/
async getAgentEntry(agentId: string): Promise<AgentRegistryEntry | null> {
// 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 && memoryEntry.value) {
// Convert MemoryEntry to AgentRegistryEntry
const registryEntry: AgentRegistryEntry = memoryEntry.value as AgentRegistryEntry;
this.cache.set(agentId, registryEntry);
return registryEntry;
}
return null;
}
/**
* Query agents by criteria
*/
async queryAgents(query: AgentQuery = {}): Promise<AgentState[]> {
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) {
agents = agents.filter(agent => agent.health >= query.healthThreshold!);
}
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 entries = Array.from(this.cache.values());
const matchingEntries = entries.filter(entry =>
query.tags!.some(tag => entry.tags.includes(tag))
);
agents = matchingEntries.map(entry => entry.agent);
}
if (query.createdAfter) {
const entries = Array.from(this.cache.values());
const matchingEntries = entries.filter(entry =>
entry.createdAt >= query.createdAfter!
);
agents = matchingEntries.map(entry => entry.agent);
}
if (query.lastActiveAfter) {
agents = agents.filter(agent =>
agent.metrics.lastActivity >= query.lastActiveAfter!
);
}
return agents;
}
/**
* Get all registered agents
*/
async getAllAgents(): Promise<AgentState[]> {
return this.queryAgents();
}
/**
* Get agents by type
*/
async getAgentsByType(type: AgentType): Promise<AgentState[]> {
return this.queryAgents({ type });
}
/**
* Get agents by status
*/
async getAgentsByStatus(status: AgentStatus): Promise<AgentState[]> {
return this.queryAgents({ status });
}
/**
* Get healthy agents
*/
async getHealthyAgents(threshold: number = 0.7): Promise<AgentState[]> {
return this.queryAgents({ healthThreshold: threshold });
}
/**
* Get registry statistics
*/
async getStatistics(): Promise<AgentStatistics> {
const agents = await this.getAllAgents();
const stats: AgentStatistics = {
totalAgents: agents.length,
byType: {} as Record<AgentType, number>,
byStatus: {} as Record<AgentStatus, number>,
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: string[]): Promise<AgentState[]> {
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: string,
requiredCapabilities: string[] = [],
preferredAgent?: string
): Promise<AgentState | null> {
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: string, data: any): Promise<void> {
const key = `coordination:${agentId}`;
await this.memory.store(key, {
agentId,
data,
timestamp: new Date()
}, {
type: 'agent-coordination',
tags: ['coordination', agentId],
partition: this.namespace
});
}
/**
* Retrieve agent coordination data
*/
async getCoordinationData(agentId: string): Promise<any> {
const key = `coordination:${agentId}`;
const result = await this.memory.retrieve(key);
return result?.value || null;
}
// === PRIVATE METHODS ===
private async loadFromMemory(): Promise<void> {
try {
const entries = await this.memory.query({
type: 'state' as const,
namespace: this.namespace
});
this.cache.clear();
for (const entry of entries) {
if (entry.value && entry.value.agent) {
this.cache.set(entry.value.agent.id.id, entry.value);
}
}
this.lastCacheUpdate = Date.now();
} catch (error) {
console.warn('Failed to load agent registry from memory:', error);
}
}
private async refreshCacheIfNeeded(): Promise<void> {
if (!this.isCacheValid()) {
await this.loadFromMemory();
}
}
private isCacheValid(): boolean {
return Date.now() - this.lastCacheUpdate < this.cacheExpiry;
}
private getAgentKey(agentId: string): string {
return `agent:${agentId}`;
}
private getArchiveKey(agentId: string): string {
return `archived:${agentId}:${Date.now()}`;
}
private calculateAgentScore(
agent: AgentState,
taskType: string,
requiredCapabilities: string[]
): number {
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;
}
}