UNPKG

@clduab11/gemini-flow

Version:

Revolutionary AI agent swarm coordination platform with Google Services integration, multimedia processing, and production-ready monitoring. Features 8 Google AI services, quantum computing capabilities, and enterprise-grade security.

906 lines (774 loc) 25.2 kB
/** * Gossip Protocol for A2A Memory Propagation * * Implements epidemic-style information dissemination: * - Anti-entropy (periodic full synchronization) * - Rumor spreading (push/pull propagation) * - Failure detection and recovery * - Network partition tolerance * - Adaptive gossip based on network conditions * - Compression and batching for efficiency */ import { EventEmitter } from "events"; import { Logger } from "../../../utils/logger.js"; import { VectorClock } from "./vector-clocks.js"; export interface GossipMessage { messageId: string; type: "update" | "sync_request" | "sync_response" | "heartbeat" | "rumor"; sourceAgent: string; targetAgent?: string; vectorClock: VectorClock; payload: any; ttl: number; timestamp: Date; path: string[]; // Agents this message has visited priority: "low" | "medium" | "high" | "critical"; } export interface GossipNode { agentId: string; address: string; lastSeen: Date; isActive: boolean; failureCount: number; roundTripTime: number; reliability: number; // 0-1 score capacity: { bandwidth: number; memory: number; cpu: number; }; } export interface GossipConfig { fanout: number; // Number of nodes to gossip to per round gossipInterval: number; // Milliseconds between gossip rounds maxTTL: number; // Maximum time-to-live for messages syncInterval: number; // Anti-entropy sync interval failureThreshold: number; // Failures before marking node as down compressionThreshold: number; // Compress messages larger than this batchSize: number; // Maximum messages per batch adaptiveGossip: boolean; // Enable adaptive algorithms minQuorumThreshold: number; // Configurable quorum threshold (default 0.51) } export interface GossipStats { messagesSent: number; messagesReceived: number; duplicatesReceived: number; syncRequestsSent: number; syncResponsesSent: number; failedTransmissions: number; averageLatency: number; networkUtilization: number; compressionRatio: number; } /** * Main Gossip Protocol Implementation */ export class GossipProtocol extends EventEmitter { private logger: Logger; private localNode: GossipNode; private topology: any; // Reference to memory topology // Configuration private config: GossipConfig = { fanout: 3, gossipInterval: 5000, // 5 seconds maxTTL: 10, syncInterval: 30000, // 30 seconds failureThreshold: 3, compressionThreshold: 1024, // 1KB batchSize: 10, adaptiveGossip: true, minQuorumThreshold: 0.51, // Default 51% threshold }; // State management private nodes: Map<string, GossipNode> = new Map(); private messageHistory: Map<string, Date> = new Map(); private pendingMessages: GossipMessage[] = []; private sentMessages: Map<string, GossipMessage> = new Map(); // Timers private gossipTimer?: ReturnType<typeof setTimeout>; private syncTimer?: ReturnType<typeof setTimeout>; private cleanupTimer?: ReturnType<typeof setTimeout>; // Statistics private stats: GossipStats = { messagesSent: 0, messagesReceived: 0, duplicatesReceived: 0, syncRequestsSent: 0, syncResponsesSent: 0, failedTransmissions: 0, averageLatency: 0, networkUtilization: 0, compressionRatio: 1.0, }; constructor( localAgent: any, topology: any, config: Partial<GossipConfig> = {}, ) { super(); this.logger = new Logger(`GossipProtocol:${localAgent.agentId}`); this.topology = topology; // Initialize local node this.localNode = { agentId: localAgent.agentId, address: localAgent.address, lastSeen: new Date(), isActive: true, failureCount: 0, roundTripTime: 0, reliability: 1.0, capacity: localAgent.capacity, }; // Merge configuration this.config = { ...this.config, ...config }; // Initialize from topology this.initializeFromTopology(); // Start gossip protocol this.startGossip(); this.logger.info("Gossip protocol initialized", { agentId: localAgent.agentId, fanout: this.config.fanout, nodeCount: this.nodes.size, }); } /** * Propagate an update through the network */ async propagateUpdate(update: any): Promise<void> { try { const message: GossipMessage = { messageId: this.generateMessageId(), type: "update", sourceAgent: this.localNode.agentId, vectorClock: update.vectorClock || new VectorClock(this.localNode.agentId), payload: update, ttl: this.config.maxTTL, timestamp: new Date(), path: [this.localNode.agentId], priority: this.determinePriority(update), }; await this.gossipMessage(message); this.logger.debug("Update propagated", { messageId: message.messageId, type: update.type, priority: message.priority, }); } catch (error) { this.logger.error("Failed to propagate update", { update, error: error.message, }); } } /** * Handle incoming gossip message */ async handleMessage(message: GossipMessage): Promise<void> { try { this.stats.messagesReceived++; // Check if we've seen this message before if (this.messageHistory.has(message.messageId)) { this.stats.duplicatesReceived++; this.logger.trace("Duplicate message received", { messageId: message.messageId, sourceAgent: message.sourceAgent, }); return; } // Record message this.messageHistory.set(message.messageId, new Date()); // Update source node info this.updateNodeInfo(message.sourceAgent, { lastSeen: new Date(), isActive: true, failureCount: 0, }); // Process message based on type switch (message.type) { case "update": await this.handleUpdateMessage(message); break; case "sync_request": await this.handleSyncRequest(message); break; case "sync_response": await this.handleSyncResponse(message); break; case "heartbeat": await this.handleHeartbeat(message); break; case "rumor": await this.handleRumor(message); break; } // Continue propagation if TTL allows if (message.ttl > 1 && !message.path.includes(this.localNode.agentId)) { await this.continueGossip(message); } } catch (error) { this.logger.error("Failed to handle message", { messageId: message.messageId, error: error.message, }); } } /** * Request synchronization with a specific node */ async requestSync(targetAgent: string): Promise<void> { try { const message: GossipMessage = { messageId: this.generateMessageId(), type: "sync_request", sourceAgent: this.localNode.agentId, targetAgent, vectorClock: new VectorClock(this.localNode.agentId), payload: { requestedData: "all", lastSyncVector: this.getLastSyncVector(targetAgent), }, ttl: 1, // Direct message timestamp: new Date(), path: [this.localNode.agentId], priority: "medium", }; await this.sendDirectMessage(targetAgent, message); this.stats.syncRequestsSent++; this.logger.debug("Sync requested", { targetAgent }); } catch (error) { this.logger.error("Failed to request sync", { targetAgent, error: error.message, }); } } /** * Add a new node to the gossip network */ addNode(agentId: string, address: string, capacity?: any): void { const node: GossipNode = { agentId, address, lastSeen: new Date(), isActive: true, failureCount: 0, roundTripTime: 0, reliability: 0.5, // Start with medium reliability capacity: capacity || { bandwidth: 100, memory: 100, cpu: 100 }, }; this.nodes.set(agentId, node); this.logger.info("Node added to gossip network", { agentId, nodeCount: this.nodes.size, }); this.emit("node_added", node); } /** * Remove a node from the gossip network */ removeNode(agentId: string): void { const node = this.nodes.get(agentId); if (node) { this.nodes.delete(agentId); this.logger.info("Node removed from gossip network", { agentId, nodeCount: this.nodes.size, }); this.emit("node_removed", { agentId, node }); } } /** * Get active nodes in the network */ getActiveNodes(): GossipNode[] { return Array.from(this.nodes.values()).filter( (node) => node.isActive && node.agentId !== this.localNode.agentId, ); } /** * Get gossip statistics */ getStats(): GossipStats { this.updateNetworkUtilization(); return { ...this.stats }; } /** * Calculate minimum quorum size based on threshold */ getMinQuorum(): number { return Math.ceil(this.nodes.size * this.config.minQuorumThreshold); } /** * Check if we have sufficient active nodes for quorum */ hasQuorum(): boolean { const activeCount = this.getActiveNodes().length; return activeCount >= this.getMinQuorum(); } /** * Update quorum threshold */ updateQuorumThreshold(threshold: number): void { if (threshold <= 0 || threshold > 1) { throw new Error("Quorum threshold must be between 0 and 1"); } this.config.minQuorumThreshold = threshold; this.logger.info("Quorum threshold updated", { threshold }); } /** * Update gossip configuration */ updateConfig(newConfig: Partial<GossipConfig>): void { this.config = { ...this.config, ...newConfig }; // Restart timers with new intervals this.stopGossip(); this.startGossip(); this.logger.info("Gossip configuration updated", newConfig); } /** * Perform manual anti-entropy synchronization */ async performAntiEntropy(): Promise<void> { const activeNodes = this.getActiveNodes(); if (activeNodes.length === 0) { this.logger.debug("No active nodes for anti-entropy sync"); return; } // Select random subset for synchronization const syncNodes = this.selectNodesForSync(activeNodes); for (const node of syncNodes) { try { await this.requestSync(node.agentId); } catch (error) { this.logger.warn("Anti-entropy sync failed", { targetAgent: node.agentId, error: error.message, }); } } this.logger.debug("Anti-entropy synchronization completed", { syncedNodes: syncNodes.length, }); } /** * Shutdown gossip protocol */ shutdown(): void { this.stopGossip(); // Mark local node as inactive this.localNode.isActive = false; // Send farewell messages this.sendFarewellMessages(); // Clear state this.nodes.clear(); this.messageHistory.clear(); this.pendingMessages = []; this.sentMessages.clear(); this.logger.info("Gossip protocol shut down"); } /** * Private methods */ private initializeFromTopology(): void { if (this.topology && this.topology.nodes) { for (const node of this.topology.nodes) { if (node.agentId !== this.localNode.agentId) { this.addNode(node.agentId, node.address, node.capacity); } } } } private startGossip(): void { // Start gossip timer this.gossipTimer = setInterval(() => { this.performGossipRound(); }, this.config.gossipInterval); // Start anti-entropy timer this.syncTimer = setInterval(() => { this.performAntiEntropy(); }, this.config.syncInterval); // Start cleanup timer this.cleanupTimer = setInterval(() => { this.cleanupOldMessages(); }, 60000); // Clean up every minute this.logger.debug("Gossip timers started"); } private stopGossip(): void { if (this.gossipTimer) { clearInterval(this.gossipTimer); this.gossipTimer = undefined; } if (this.syncTimer) { clearInterval(this.syncTimer); this.syncTimer = undefined; } if (this.cleanupTimer) { clearInterval(this.cleanupTimer); this.cleanupTimer = undefined; } this.logger.debug("Gossip timers stopped"); } private async performGossipRound(): Promise<void> { try { // Process pending messages if (this.pendingMessages.length > 0) { const batch = this.pendingMessages.splice(0, this.config.batchSize); for (const message of batch) { await this.gossipMessage(message); } } // Send heartbeats await this.sendHeartbeats(); // Check for failed nodes this.checkNodeFailures(); } catch (error) { this.logger.error("Gossip round failed", { error: error.message }); } } private async gossipMessage(message: GossipMessage): Promise<void> { const targets = this.selectGossipTargets(message); if (targets.length === 0) { this.logger.warn("No gossip targets available", { messageId: message.messageId, }); return; } // Send to selected targets const promises = targets.map((target) => this.sendMessage(target.agentId, message), ); try { await Promise.allSettled(promises); this.stats.messagesSent += targets.length; } catch (error) { this.logger.error("Failed to gossip message", { messageId: message.messageId, targets: targets.length, error: error.message, }); } } private selectGossipTargets(message: GossipMessage): GossipNode[] { const activeNodes = this.getActiveNodes(); if (activeNodes.length === 0) { return []; } let fanout = Math.min(this.config.fanout, activeNodes.length); // Adaptive fanout based on message priority if (this.config.adaptiveGossip) { switch (message.priority) { case "critical": fanout = Math.min(activeNodes.length, fanout * 2); break; case "high": fanout = Math.min(activeNodes.length, Math.ceil(fanout * 1.5)); break; case "low": fanout = Math.max(1, Math.floor(fanout * 0.5)); break; } } // Select nodes based on reliability and network conditions const candidates = activeNodes .filter((node) => !message.path.includes(node.agentId)) .sort((a, b) => { // Sort by reliability and inverse round-trip time const scoreA = a.reliability - a.roundTripTime / 1000; const scoreB = b.reliability - b.roundTripTime / 1000; return scoreB - scoreA; }); return candidates.slice(0, fanout); } private async sendMessage( targetAgent: string, message: GossipMessage, ): Promise<void> { const startTime = Date.now(); try { // In a real implementation, this would use actual network communication // For now, we'll simulate the send operation const node = this.nodes.get(targetAgent); if (!node || !node.isActive) { throw new Error(`Target agent ${targetAgent} is not available`); } // Compress message if needed let finalMessage = message; if (this.shouldCompressMessage(message)) { finalMessage = await this.compressMessage(message); } // Simulate network delay await this.simulateNetworkDelay(node); // Update node statistics const latency = Date.now() - startTime; this.updateNodeLatency(targetAgent, latency); // Store sent message for tracking this.sentMessages.set(message.messageId, message); this.logger.trace("Message sent", { messageId: message.messageId, targetAgent, latency, }); } catch (error) { this.stats.failedTransmissions++; this.updateNodeFailure(targetAgent); this.logger.warn("Failed to send message", { messageId: message.messageId, targetAgent, error: error.message, }); throw error; } } private async sendDirectMessage( targetAgent: string, message: GossipMessage, ): Promise<void> { await this.sendMessage(targetAgent, message); } private async continueGossip(message: GossipMessage): Promise<void> { // Decrease TTL and add to path const continuedMessage: GossipMessage = { ...message, ttl: message.ttl - 1, path: [...message.path, this.localNode.agentId], }; // Add to pending messages for next gossip round this.pendingMessages.push(continuedMessage); } private async handleUpdateMessage(message: GossipMessage): Promise<void> { // Emit event for the memory manager to handle this.emit("update_received", message.payload); this.logger.debug("Update message processed", { messageId: message.messageId, sourceAgent: message.sourceAgent, }); } private async handleSyncRequest(message: GossipMessage): Promise<void> { // Prepare sync response with requested data const responsePayload = await this.prepareSyncResponse(message.payload); const response: GossipMessage = { messageId: this.generateMessageId(), type: "sync_response", sourceAgent: this.localNode.agentId, targetAgent: message.sourceAgent, vectorClock: new VectorClock(this.localNode.agentId), payload: responsePayload, ttl: 1, timestamp: new Date(), path: [this.localNode.agentId], priority: "medium", }; await this.sendDirectMessage(message.sourceAgent, response); this.stats.syncResponsesSent++; this.logger.debug("Sync response sent", { targetAgent: message.sourceAgent, dataSize: JSON.stringify(responsePayload).length, }); } private async handleSyncResponse(message: GossipMessage): Promise<void> { // Apply sync data this.emit("sync_data_received", { sourceAgent: message.sourceAgent, data: message.payload, }); this.logger.debug("Sync response processed", { sourceAgent: message.sourceAgent, }); } private async handleHeartbeat(message: GossipMessage): Promise<void> { // Update node liveness this.updateNodeInfo(message.sourceAgent, { lastSeen: new Date(), isActive: true, failureCount: 0, }); this.logger.trace("Heartbeat processed", { sourceAgent: message.sourceAgent, }); } private async handleRumor(message: GossipMessage): Promise<void> { // Process rumor (could be node discovery, failure notification, etc.) this.emit("rumor_received", { sourceAgent: message.sourceAgent, rumor: message.payload, }); this.logger.debug("Rumor processed", { sourceAgent: message.sourceAgent, rumorType: message.payload.type, }); } private async sendHeartbeats(): Promise<void> { const heartbeat: GossipMessage = { messageId: this.generateMessageId(), type: "heartbeat", sourceAgent: this.localNode.agentId, vectorClock: new VectorClock(this.localNode.agentId), payload: { nodeInfo: this.localNode, timestamp: Date.now(), }, ttl: 2, // Limited propagation timestamp: new Date(), path: [this.localNode.agentId], priority: "low", }; await this.gossipMessage(heartbeat); } private checkNodeFailures(): void { const now = new Date(); const failureTimeout = this.config.gossipInterval * 3; // 3 missed heartbeats for (const [agentId, node] of this.nodes) { if ( node.isActive && now.getTime() - node.lastSeen.getTime() > failureTimeout ) { node.failureCount++; if (node.failureCount >= this.config.failureThreshold) { node.isActive = false; node.reliability = Math.max(0, node.reliability - 0.1); this.logger.warn("Node marked as failed", { agentId, failureCount: node.failureCount, lastSeen: node.lastSeen, }); this.emit("node_failed", { agentId, node }); } } } } private selectNodesForSync(nodes: GossipNode[]): GossipNode[] { // Select nodes for anti-entropy sync based on reliability and staleness const candidates = nodes.sort((a, b) => { const stalnessA = Date.now() - a.lastSeen.getTime(); const stalnessB = Date.now() - b.lastSeen.getTime(); return stalnessB - stalnessA; // Prefer staler nodes }); const syncCount = Math.min(3, candidates.length); // Sync with up to 3 nodes return candidates.slice(0, syncCount); } private shouldCompressMessage(message: GossipMessage): boolean { const messageSize = JSON.stringify(message).length; return messageSize > this.config.compressionThreshold; } private async compressMessage( message: GossipMessage, ): Promise<GossipMessage> { // Simulate compression (in real implementation, use actual compression) const compressedPayload = { ...message.payload, compressed: true, originalSize: JSON.stringify(message.payload).length, }; const compressionRatio = 0.7; // Simulate 30% compression this.updateCompressionStats(compressionRatio); return { ...message, payload: compressedPayload, }; } private async simulateNetworkDelay(node: GossipNode): Promise<void> { // Simulate network delay based on node round-trip time const delay = Math.max(10, node.roundTripTime + Math.random() * 50); await new Promise((resolve) => setTimeout(resolve, delay)); } private updateNodeInfo(agentId: string, updates: Partial<GossipNode>): void { const node = this.nodes.get(agentId); if (node) { Object.assign(node, updates); } } private updateNodeLatency(agentId: string, latency: number): void { const node = this.nodes.get(agentId); if (node) { // Exponential moving average node.roundTripTime = node.roundTripTime * 0.8 + latency * 0.2; node.reliability = Math.min(1.0, node.reliability + 0.01); // Increase reliability on success } } private updateNodeFailure(agentId: string): void { const node = this.nodes.get(agentId); if (node) { node.failureCount++; node.reliability = Math.max(0, node.reliability - 0.05); } } private updateCompressionStats(ratio: number): void { this.stats.compressionRatio = (this.stats.compressionRatio + ratio) / 2; } private updateNetworkUtilization(): void { // Calculate network utilization based on message throughput const totalMessages = this.stats.messagesSent + this.stats.messagesReceived; const timeWindow = 60; // 1 minute window this.stats.networkUtilization = totalMessages / timeWindow; } private determinePriority(update: any): GossipMessage["priority"] { // Determine message priority based on update type if (update.type === "emergency" || update.critical) { return "critical"; } else if (update.type === "security" || update.important) { return "high"; } else if (update.type === "heartbeat" || update.routine) { return "low"; } return "medium"; } private generateMessageId(): string { return `msg_${this.localNode.agentId}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } private getLastSyncVector(agentId: string): VectorClock { // In real implementation, this would track per-agent sync vectors return new VectorClock(this.localNode.agentId); } private async prepareSyncResponse(request: any): Promise<any> { // Prepare sync response data based on request return { type: "sync_data", timestamp: new Date(), data: {}, // Would contain actual sync data }; } private cleanupOldMessages(): void { const cutoffTime = new Date(Date.now() - 300000); // 5 minutes let cleaned = 0; for (const [messageId, timestamp] of this.messageHistory) { if (timestamp < cutoffTime) { this.messageHistory.delete(messageId); cleaned++; } } // Clean up sent messages for (const [messageId, message] of this.sentMessages) { if (message.timestamp < cutoffTime) { this.sentMessages.delete(messageId); } } if (cleaned > 0) { this.logger.debug("Cleaned up old messages", { cleaned }); } } private sendFarewellMessages(): void { // Send goodbye messages to known nodes const farewell: GossipMessage = { messageId: this.generateMessageId(), type: "rumor", sourceAgent: this.localNode.agentId, vectorClock: new VectorClock(this.localNode.agentId), payload: { type: "node_leaving", agentId: this.localNode.agentId, timestamp: Date.now(), }, ttl: 3, timestamp: new Date(), path: [this.localNode.agentId], priority: "medium", }; // Send farewell (best effort, no waiting) this.gossipMessage(farewell).catch((error) => { this.logger.warn("Failed to send farewell messages", { error: error.message, }); }); } }