/** * swarm-join.ts — Enterprise Swarm Coordination Engine * * Manages dynamic, collaborative "swarm" groups of background agents with: * - Event-driven architecture (Observable streams for UI/dashboard) * - Agent health monitoring (heartbeats, timeouts, failure detection) * - Inter-agent message routing (broadcast, unicast, multicast) * - Quorum-based delivery strategies (union, vote, consensus, merge) * - Leader election (bully algorithm with priority tiers) * - Backpressure & rate limiting on deliveries * - Graceful degradation and automatic recovery * - Comprehensive metrics and telemetry hooks * * Unlike fixed GroupJoinManager, swarms are living organisms: * agents join/leave at runtime, collaborate continuously, and * deliver results via configurable aggregation strategies. */ import type { AgentRecord } from "./types.js"; export type SwarmDeliveryCallback = (records: AgentRecord[], partial: boolean, swarmId: string, meta?: SwarmDeliveryMeta) => void; export type SwarmMessageCallback = (fromAgentId: string, toAgentId: string | "*", payload: unknown, swarmId: string) => void; export interface SwarmDeliveryMeta { /** Delivery strategy used for this batch. */ strategy: SwarmStrategy; /** Number of agents that contributed. */ contributorCount: number; /** Number of agents still pending. */ pendingCount: number; /** Epoch/tick number for this delivery. */ epoch: number; /** Whether this was triggered by timeout (stragglers). */ timedOut: boolean; /** Leader agent ID at time of delivery, if leader election enabled. */ leaderId?: string; /** Quorum achieved? */ quorumMet: boolean; } export type SwarmStrategy = "live" | "quorum" | "vote" | "merge" | "batch"; export type SwarmAgentStatus = "idle" | "running" | "completed" | "failed" | "timeout" | "left"; export interface SwarmAgentState { agentId: string; status: SwarmAgentStatus; joinedAt: number; lastHeartbeatAt: number; completedAt?: number; record?: AgentRecord; /** Priority for leader election (higher = more likely leader). */ priority: number; /** Custom metadata agents can attach to their state. */ meta: Record; } export interface SwarmConfig { /** Unique swarm identifier. */ swarmId: string; /** Human-readable name. */ name: string; /** Delivery strategy. Default: "live". */ strategy?: SwarmStrategy; /** Timeout after first completion (ms). Default: 30s. */ swarmTimeout?: number; /** Timeout for stragglers after partial delivery (ms). Default: 15s. */ stragglerTimeout?: number; /** Heartbeat interval for health checks (ms). Default: 10s. */ heartbeatInterval?: number; /** Max missed heartbeats before agent marked failed. Default: 3. */ maxMissedHeartbeats?: number; /** Minimum agents required for quorum. Default: 1. */ quorumMin?: number; /** Percentage of agents required for quorum (0-1). Default: 0.5. */ quorumPercent?: number; /** Enable leader election. Default: false. */ enableLeader?: boolean; /** Max deliveries per second (backpressure). Default: 10. */ maxDeliveryRate?: number; /** Auto-cleanup empty swarms. Default: true. */ autoCleanup?: boolean; /** Callback when swarm state changes. */ onStateChange?: (swarmId: string, event: SwarmEvent) => void; } export type SwarmEvent = { type: "agent:joined"; agentId: string; timestamp: number; } | { type: "agent:left"; agentId: string; reason: "manual" | "timeout" | "failed" | "cleanup"; timestamp: number; } | { type: "agent:completed"; agentId: string; record: AgentRecord; timestamp: number; } | { type: "agent:heartbeat"; agentId: string; timestamp: number; } | { type: "agent:failed"; agentId: string; error?: string; timestamp: number; } | { type: "delivery"; records: AgentRecord[]; partial: boolean; meta: SwarmDeliveryMeta; timestamp: number; } | { type: "leader:elected"; leaderId: string; timestamp: number; } | { type: "leader:lost"; leaderId: string; timestamp: number; } | { type: "quorum:met"; count: number; required: number; timestamp: number; } | { type: "timeout"; pendingAgents: string[]; timestamp: number; } | { type: "swarm:created"; timestamp: number; } | { type: "swarm:disposed"; timestamp: number; }; interface SwarmInternal { config: SwarmConfig; agents: Map; completedRecords: Map; deliveredRecordIds: Set; timeoutHandle?: ReturnType; heartbeatHandle?: ReturnType; delivered: boolean; isStraggler: boolean; epoch: number; leaderId?: string; /** Rate limiter: timestamps of recent deliveries. */ deliveryTimestamps: number[]; /** In-flight messages waiting for routing. */ messageQueue: Array<{ from: string; to: string | "*"; payload: unknown; ts: number; }>; } export declare class SwarmCoordinator { private swarms; private agentToSwarm; private deliverCb; private messageCb?; private defaultSwarmTimeout; /** Global metrics aggregator. */ private metrics; constructor(deliverCb: SwarmDeliveryCallback, messageCbOrTimeout?: SwarmMessageCallback | number); /** * Create a new swarm with full configuration. * Returns the swarmId. */ createSwarm(name?: string): string; createSwarm(config: Omit & { swarmId?: string; }): string; /** * Legacy compatibility: register swarm with initial members. */ registerSwarm(swarmId: string, initialAgentIds?: string[], name?: string): string; /** * Dispose a swarm and clean up all resources. */ disposeSwarm(swarmId: string): boolean; /** * Add an agent to a swarm at runtime. Core primitive for dynamic collaboration. * Supports priority-based leader election if enabled. */ addAgentToSwarm(swarmId: string, agentId: string, priority?: number): boolean; /** * Remove an agent from its swarm. Supports graceful leave. */ removeAgentFromSwarm(agentId: string): boolean; /** * Agent heartbeat — call periodically to maintain healthy status. */ heartbeat(agentId: string): boolean; /** * Update agent metadata (e.g., progress, partial results). */ updateAgentMeta(agentId: string, meta: Record): boolean; /** * Called when a swarm agent completes its task. * * Strategies: * - "live": Deliver immediately (streaming collaboration) * - "quorum": Deliver when quorum reached * - "vote": Deliver aggregated votes (requires all to complete) * - "merge": Merge all results into single delivery when complete * - "batch": Traditional batch, hold until all complete or timeout */ onAgentComplete(record: AgentRecord): "delivered" | "held" | "pass"; /** * Mark an agent as failed (e.g., crash, error, or missed heartbeats). */ onAgentFailed(agentId: string, error?: string): boolean; /** * Send a message to another agent in the same swarm (or broadcast with "*"). */ sendMessage(fromAgentId: string, toAgentId: string | "*", payload: unknown): boolean; /** * Poll messages for a specific agent (call from agent runner). */ pollMessages(agentId: string, since?: number): Array<{ from: string; payload: unknown; ts: number; }>; private electLeader; private checkHealth; private onTimeout; private deliverBatch; private checkRateLimit; private cleanupRateLimit; private computeQuorumRequired; private emit; listSwarms(): Array<{ swarmId: string; name: string; agentCount: number; strategy: SwarmStrategy; leaderId?: string; }>; getSwarmMembers(swarmId: string): SwarmAgentState[]; getSwarmState(swarmId: string): SwarmInternal | undefined; isSwarmMember(agentId: string): boolean; getSwarmIdForAgent(agentId: string): string | undefined; getSwarmMetrics(swarmId?: string): SwarmMetrics; dispose(): void; } export interface SwarmMetrics { totalDeliveries: number; totalRecordsDelivered: number; partialDeliveries: number; timedOutDeliveries: number; averageLatencyMs?: number; bySwarm: Record; } export declare function setActiveSwarmCoordinator(coordinator: SwarmCoordinator | null): void; export declare function getSwarmCoordinator(): SwarmCoordinator | null; /** * UI convenience: create a swarm from selected agents with full config. */ export declare function uiCreateSwarm(agentIds: string[], config?: Partial>): string | null; /** * UI convenience: join existing swarm. */ export declare function uiJoinSwarm(swarmId: string, agentId: string): boolean; /** * Backwards-compat convenience: create or join a swarm from the UI layer. */ export declare function uiCreateOrJoinSwarm(agentIds: string[], suggestedName?: string): string | null; export {};