agentic-flow 1.6.3 → 1.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +175 -0
- package/dist/cli-proxy.js +75 -11
- package/dist/mcp/fastmcp/tools/swarm/init.js +18 -5
- package/dist/swarm/index.js +133 -0
- package/dist/swarm/quic-coordinator.js +460 -0
- package/dist/swarm/transport-router.js +374 -0
- package/dist/transport/quic-handshake.js +198 -0
- package/dist/transport/quic.js +581 -58
- package/dist/utils/cli.js +27 -12
- package/docs/architecture/QUIC-IMPLEMENTATION-SUMMARY.md +490 -0
- package/docs/architecture/QUIC-SWARM-INTEGRATION.md +593 -0
- package/docs/guides/QUIC-SWARM-QUICKSTART.md +543 -0
- package/docs/integration-docs/QUIC-WASM-INTEGRATION.md +537 -0
- package/docs/plans/QUIC/quic-tutorial.md +457 -0
- package/docs/quic/FINAL-VALIDATION.md +336 -0
- package/docs/quic/IMPLEMENTATION-COMPLETE-SUMMARY.md +349 -0
- package/docs/quic/PERFORMANCE-VALIDATION.md +282 -0
- package/docs/quic/QUIC-STATUS-OLD.md +513 -0
- package/docs/quic/QUIC-STATUS.md +451 -0
- package/docs/quic/QUIC-VALIDATION-REPORT.md +370 -0
- package/docs/quic/WASM-INTEGRATION-COMPLETE.md +382 -0
- package/package.json +1 -1
- package/wasm/reasoningbank/reasoningbank_wasm_bg.js +2 -2
- package/wasm/reasoningbank/reasoningbank_wasm_bg.wasm +0 -0
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
// QUIC-enabled Swarm Coordinator
|
|
2
|
+
// Manages agent-to-agent communication over QUIC transport with fallback to HTTP/2
|
|
3
|
+
import { logger } from '../utils/logger.js';
|
|
4
|
+
/**
|
|
5
|
+
* QuicCoordinator - Manages multi-agent swarm coordination over QUIC
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Agent-to-agent communication via QUIC streams
|
|
9
|
+
* - Topology-aware message routing (mesh, hierarchical, ring, star)
|
|
10
|
+
* - Connection pooling for efficient resource usage
|
|
11
|
+
* - Real-time state synchronization
|
|
12
|
+
* - Per-agent statistics tracking
|
|
13
|
+
*/
|
|
14
|
+
export class QuicCoordinator {
|
|
15
|
+
config;
|
|
16
|
+
state;
|
|
17
|
+
messageQueue;
|
|
18
|
+
heartbeatTimer;
|
|
19
|
+
syncTimer;
|
|
20
|
+
messageStats;
|
|
21
|
+
constructor(config) {
|
|
22
|
+
this.config = {
|
|
23
|
+
...config,
|
|
24
|
+
heartbeatInterval: config.heartbeatInterval || 10000,
|
|
25
|
+
statesSyncInterval: config.statesSyncInterval || 5000,
|
|
26
|
+
enableCompression: config.enableCompression ?? true
|
|
27
|
+
};
|
|
28
|
+
this.state = {
|
|
29
|
+
swarmId: config.swarmId,
|
|
30
|
+
topology: config.topology,
|
|
31
|
+
agents: new Map(),
|
|
32
|
+
connections: new Map(),
|
|
33
|
+
stats: {
|
|
34
|
+
totalAgents: 0,
|
|
35
|
+
activeAgents: 0,
|
|
36
|
+
totalMessages: 0,
|
|
37
|
+
messagesPerSecond: 0,
|
|
38
|
+
averageLatency: 0,
|
|
39
|
+
quicStats: {
|
|
40
|
+
totalConnections: 0,
|
|
41
|
+
activeConnections: 0,
|
|
42
|
+
totalStreams: 0,
|
|
43
|
+
activeStreams: 0,
|
|
44
|
+
bytesReceived: 0,
|
|
45
|
+
bytesSent: 0,
|
|
46
|
+
packetsLost: 0,
|
|
47
|
+
rttMs: 0
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
this.messageQueue = [];
|
|
52
|
+
this.messageStats = new Map();
|
|
53
|
+
logger.info('QUIC Coordinator initialized', {
|
|
54
|
+
swarmId: config.swarmId,
|
|
55
|
+
topology: config.topology,
|
|
56
|
+
maxAgents: config.maxAgents
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Start the coordinator (heartbeat and state sync)
|
|
61
|
+
*/
|
|
62
|
+
async start() {
|
|
63
|
+
logger.info('Starting QUIC Coordinator', { swarmId: this.config.swarmId });
|
|
64
|
+
// Initialize QUIC client
|
|
65
|
+
await this.config.quicClient.initialize();
|
|
66
|
+
// Start heartbeat
|
|
67
|
+
this.startHeartbeat();
|
|
68
|
+
// Start state synchronization
|
|
69
|
+
this.startStateSync();
|
|
70
|
+
logger.info('QUIC Coordinator started successfully');
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Stop the coordinator
|
|
74
|
+
*/
|
|
75
|
+
async stop() {
|
|
76
|
+
logger.info('Stopping QUIC Coordinator', { swarmId: this.config.swarmId });
|
|
77
|
+
// Stop timers
|
|
78
|
+
if (this.heartbeatTimer) {
|
|
79
|
+
clearInterval(this.heartbeatTimer);
|
|
80
|
+
}
|
|
81
|
+
if (this.syncTimer) {
|
|
82
|
+
clearInterval(this.syncTimer);
|
|
83
|
+
}
|
|
84
|
+
// Close all connections
|
|
85
|
+
for (const [agentId, connection] of this.state.connections.entries()) {
|
|
86
|
+
await this.config.quicClient.closeConnection(connection.id);
|
|
87
|
+
logger.debug('Closed connection', { agentId, connectionId: connection.id });
|
|
88
|
+
}
|
|
89
|
+
// Shutdown QUIC client
|
|
90
|
+
await this.config.quicClient.shutdown();
|
|
91
|
+
logger.info('QUIC Coordinator stopped');
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Register an agent in the swarm
|
|
95
|
+
*/
|
|
96
|
+
async registerAgent(agent) {
|
|
97
|
+
if (this.state.agents.size >= this.config.maxAgents) {
|
|
98
|
+
throw new Error(`Maximum agents (${this.config.maxAgents}) reached`);
|
|
99
|
+
}
|
|
100
|
+
logger.info('Registering agent', {
|
|
101
|
+
agentId: agent.id,
|
|
102
|
+
role: agent.role,
|
|
103
|
+
host: agent.host,
|
|
104
|
+
port: agent.port
|
|
105
|
+
});
|
|
106
|
+
// Establish QUIC connection to agent
|
|
107
|
+
const connection = await this.config.connectionPool.getConnection(agent.host, agent.port);
|
|
108
|
+
// Store agent and connection
|
|
109
|
+
this.state.agents.set(agent.id, agent);
|
|
110
|
+
this.state.connections.set(agent.id, connection);
|
|
111
|
+
this.messageStats.set(agent.id, { sent: 0, received: 0, latency: [] });
|
|
112
|
+
// Update stats
|
|
113
|
+
this.state.stats.totalAgents = this.state.agents.size;
|
|
114
|
+
this.state.stats.activeAgents = this.state.agents.size;
|
|
115
|
+
// Establish topology-specific connections
|
|
116
|
+
await this.establishTopologyConnections(agent);
|
|
117
|
+
logger.info('Agent registered successfully', { agentId: agent.id });
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Unregister an agent from the swarm
|
|
121
|
+
*/
|
|
122
|
+
async unregisterAgent(agentId) {
|
|
123
|
+
const agent = this.state.agents.get(agentId);
|
|
124
|
+
if (!agent) {
|
|
125
|
+
logger.warn('Agent not found', { agentId });
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
logger.info('Unregistering agent', { agentId });
|
|
129
|
+
// Close connection
|
|
130
|
+
const connection = this.state.connections.get(agentId);
|
|
131
|
+
if (connection) {
|
|
132
|
+
await this.config.quicClient.closeConnection(connection.id);
|
|
133
|
+
}
|
|
134
|
+
// Remove from state
|
|
135
|
+
this.state.agents.delete(agentId);
|
|
136
|
+
this.state.connections.delete(agentId);
|
|
137
|
+
this.messageStats.delete(agentId);
|
|
138
|
+
// Update stats
|
|
139
|
+
this.state.stats.totalAgents = this.state.agents.size;
|
|
140
|
+
this.state.stats.activeAgents = this.state.agents.size;
|
|
141
|
+
logger.info('Agent unregistered successfully', { agentId });
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Send message to one or more agents
|
|
145
|
+
*/
|
|
146
|
+
async sendMessage(message) {
|
|
147
|
+
const startTime = Date.now();
|
|
148
|
+
logger.debug('Sending message', {
|
|
149
|
+
messageId: message.id,
|
|
150
|
+
from: message.from,
|
|
151
|
+
to: message.to,
|
|
152
|
+
type: message.type
|
|
153
|
+
});
|
|
154
|
+
// Determine recipients based on topology
|
|
155
|
+
const recipients = this.resolveRecipients(message);
|
|
156
|
+
// Send message to each recipient
|
|
157
|
+
for (const recipientId of recipients) {
|
|
158
|
+
await this.sendToAgent(recipientId, message);
|
|
159
|
+
}
|
|
160
|
+
// Update stats
|
|
161
|
+
const latency = Date.now() - startTime;
|
|
162
|
+
this.updateMessageStats(message.from, 'sent', latency);
|
|
163
|
+
this.state.stats.totalMessages++;
|
|
164
|
+
logger.debug('Message sent successfully', {
|
|
165
|
+
messageId: message.id,
|
|
166
|
+
recipients: recipients.length,
|
|
167
|
+
latency
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Broadcast message to all agents (except sender)
|
|
172
|
+
*/
|
|
173
|
+
async broadcast(message) {
|
|
174
|
+
const broadcastMessage = {
|
|
175
|
+
...message,
|
|
176
|
+
to: '*'
|
|
177
|
+
};
|
|
178
|
+
await this.sendMessage(broadcastMessage);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Get current swarm state
|
|
182
|
+
*/
|
|
183
|
+
async getState() {
|
|
184
|
+
return {
|
|
185
|
+
...this.state,
|
|
186
|
+
stats: await this.calculateStats()
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Get agent statistics
|
|
191
|
+
*/
|
|
192
|
+
getAgentStats(agentId) {
|
|
193
|
+
const stats = this.messageStats.get(agentId);
|
|
194
|
+
if (!stats) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
sent: stats.sent,
|
|
199
|
+
received: stats.received,
|
|
200
|
+
avgLatency: stats.latency.length > 0
|
|
201
|
+
? stats.latency.reduce((sum, l) => sum + l, 0) / stats.latency.length
|
|
202
|
+
: 0
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Get all agent statistics
|
|
207
|
+
*/
|
|
208
|
+
getAllAgentStats() {
|
|
209
|
+
const allStats = new Map();
|
|
210
|
+
for (const [agentId, stats] of this.messageStats.entries()) {
|
|
211
|
+
allStats.set(agentId, {
|
|
212
|
+
sent: stats.sent,
|
|
213
|
+
received: stats.received,
|
|
214
|
+
avgLatency: stats.latency.length > 0
|
|
215
|
+
? stats.latency.reduce((sum, l) => sum + l, 0) / stats.latency.length
|
|
216
|
+
: 0
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
return allStats;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Synchronize state across all agents
|
|
223
|
+
*/
|
|
224
|
+
async syncState() {
|
|
225
|
+
logger.debug('Synchronizing swarm state', { swarmId: this.config.swarmId });
|
|
226
|
+
const stateMessage = {
|
|
227
|
+
id: `sync-${Date.now()}`,
|
|
228
|
+
from: 'coordinator',
|
|
229
|
+
to: '*',
|
|
230
|
+
type: 'sync',
|
|
231
|
+
payload: {
|
|
232
|
+
swarmId: this.state.swarmId,
|
|
233
|
+
topology: this.state.topology,
|
|
234
|
+
agents: Array.from(this.state.agents.values()),
|
|
235
|
+
stats: this.calculateStats()
|
|
236
|
+
},
|
|
237
|
+
timestamp: Date.now()
|
|
238
|
+
};
|
|
239
|
+
await this.broadcast(stateMessage);
|
|
240
|
+
}
|
|
241
|
+
// ========== Private Methods ==========
|
|
242
|
+
/**
|
|
243
|
+
* Establish topology-specific connections
|
|
244
|
+
*/
|
|
245
|
+
async establishTopologyConnections(agent) {
|
|
246
|
+
switch (this.config.topology) {
|
|
247
|
+
case 'mesh':
|
|
248
|
+
// In mesh, each agent connects to all others (handled by caller)
|
|
249
|
+
logger.debug('Mesh topology: agent connects to all', { agentId: agent.id });
|
|
250
|
+
break;
|
|
251
|
+
case 'hierarchical':
|
|
252
|
+
// In hierarchical, workers connect to coordinators
|
|
253
|
+
if (agent.role === 'worker') {
|
|
254
|
+
const coordinators = Array.from(this.state.agents.values())
|
|
255
|
+
.filter(a => a.role === 'coordinator');
|
|
256
|
+
logger.debug('Hierarchical topology: connecting worker to coordinators', {
|
|
257
|
+
agentId: agent.id,
|
|
258
|
+
coordinators: coordinators.length
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
break;
|
|
262
|
+
case 'ring':
|
|
263
|
+
// In ring, each agent connects to next agent in circular order
|
|
264
|
+
const agents = Array.from(this.state.agents.values());
|
|
265
|
+
if (agents.length > 1) {
|
|
266
|
+
logger.debug('Ring topology: establishing ring connections', {
|
|
267
|
+
agentId: agent.id,
|
|
268
|
+
totalAgents: agents.length
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
break;
|
|
272
|
+
case 'star':
|
|
273
|
+
// In star, all agents connect to central coordinator
|
|
274
|
+
if (agent.role === 'coordinator') {
|
|
275
|
+
logger.debug('Star topology: coordinator established', { agentId: agent.id });
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
const coordinator = Array.from(this.state.agents.values())
|
|
279
|
+
.find(a => a.role === 'coordinator');
|
|
280
|
+
if (coordinator) {
|
|
281
|
+
logger.debug('Star topology: connecting to coordinator', {
|
|
282
|
+
agentId: agent.id,
|
|
283
|
+
coordinator: coordinator.id
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Resolve message recipients based on topology
|
|
292
|
+
*/
|
|
293
|
+
resolveRecipients(message) {
|
|
294
|
+
const recipients = [];
|
|
295
|
+
const to = Array.isArray(message.to) ? message.to : [message.to];
|
|
296
|
+
for (const target of to) {
|
|
297
|
+
if (target === '*') {
|
|
298
|
+
// Broadcast to all (except sender)
|
|
299
|
+
recipients.push(...Array.from(this.state.agents.keys())
|
|
300
|
+
.filter(id => id !== message.from));
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
recipients.push(target);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
// Apply topology-specific routing
|
|
307
|
+
return this.applyTopologyRouting(message.from, recipients);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Apply topology-specific routing rules
|
|
311
|
+
*/
|
|
312
|
+
applyTopologyRouting(senderId, recipients) {
|
|
313
|
+
switch (this.config.topology) {
|
|
314
|
+
case 'mesh':
|
|
315
|
+
// Direct routing in mesh topology
|
|
316
|
+
return recipients;
|
|
317
|
+
case 'hierarchical':
|
|
318
|
+
// Route through coordinator in hierarchical
|
|
319
|
+
const sender = this.state.agents.get(senderId);
|
|
320
|
+
if (sender?.role === 'worker') {
|
|
321
|
+
// Worker messages go through coordinator
|
|
322
|
+
const coordinators = Array.from(this.state.agents.values())
|
|
323
|
+
.filter(a => a.role === 'coordinator')
|
|
324
|
+
.map(a => a.id);
|
|
325
|
+
return coordinators.length > 0 ? coordinators : recipients;
|
|
326
|
+
}
|
|
327
|
+
return recipients;
|
|
328
|
+
case 'ring':
|
|
329
|
+
// Forward to next agent in ring
|
|
330
|
+
const agents = Array.from(this.state.agents.keys());
|
|
331
|
+
const currentIndex = agents.indexOf(senderId);
|
|
332
|
+
const nextIndex = (currentIndex + 1) % agents.length;
|
|
333
|
+
return [agents[nextIndex]];
|
|
334
|
+
case 'star':
|
|
335
|
+
// Route through central coordinator
|
|
336
|
+
const coordinator = Array.from(this.state.agents.values())
|
|
337
|
+
.find(a => a.role === 'coordinator');
|
|
338
|
+
if (coordinator && senderId !== coordinator.id) {
|
|
339
|
+
return [coordinator.id];
|
|
340
|
+
}
|
|
341
|
+
return recipients;
|
|
342
|
+
default:
|
|
343
|
+
return recipients;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Send message to specific agent
|
|
348
|
+
*/
|
|
349
|
+
async sendToAgent(agentId, message) {
|
|
350
|
+
const connection = this.state.connections.get(agentId);
|
|
351
|
+
if (!connection) {
|
|
352
|
+
logger.warn('Connection not found for agent', { agentId });
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
try {
|
|
356
|
+
// Create QUIC stream
|
|
357
|
+
const stream = await this.config.quicClient.createStream(connection.id);
|
|
358
|
+
// Serialize message
|
|
359
|
+
const messageBytes = this.serializeMessage(message);
|
|
360
|
+
// Send message
|
|
361
|
+
await stream.send(messageBytes);
|
|
362
|
+
// Close stream
|
|
363
|
+
await stream.close();
|
|
364
|
+
logger.debug('Message sent to agent', { agentId, messageId: message.id });
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
logger.error('Failed to send message to agent', { agentId, error });
|
|
368
|
+
throw error;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Serialize message to bytes
|
|
373
|
+
*/
|
|
374
|
+
serializeMessage(message) {
|
|
375
|
+
const json = JSON.stringify(message);
|
|
376
|
+
const encoder = new TextEncoder();
|
|
377
|
+
return encoder.encode(json);
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Start heartbeat timer
|
|
381
|
+
*/
|
|
382
|
+
startHeartbeat() {
|
|
383
|
+
this.heartbeatTimer = setInterval(async () => {
|
|
384
|
+
logger.debug('Sending heartbeat', { swarmId: this.config.swarmId });
|
|
385
|
+
const heartbeat = {
|
|
386
|
+
id: `heartbeat-${Date.now()}`,
|
|
387
|
+
from: 'coordinator',
|
|
388
|
+
to: '*',
|
|
389
|
+
type: 'heartbeat',
|
|
390
|
+
payload: { timestamp: Date.now() },
|
|
391
|
+
timestamp: Date.now()
|
|
392
|
+
};
|
|
393
|
+
try {
|
|
394
|
+
await this.broadcast(heartbeat);
|
|
395
|
+
}
|
|
396
|
+
catch (error) {
|
|
397
|
+
logger.error('Heartbeat failed', { error });
|
|
398
|
+
}
|
|
399
|
+
}, this.config.heartbeatInterval);
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Start state sync timer
|
|
403
|
+
*/
|
|
404
|
+
startStateSync() {
|
|
405
|
+
this.syncTimer = setInterval(async () => {
|
|
406
|
+
try {
|
|
407
|
+
await this.syncState();
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
logger.error('State sync failed', { error });
|
|
411
|
+
}
|
|
412
|
+
}, this.config.statesSyncInterval);
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Update message statistics
|
|
416
|
+
*/
|
|
417
|
+
updateMessageStats(agentId, type, latency) {
|
|
418
|
+
const stats = this.messageStats.get(agentId);
|
|
419
|
+
if (!stats) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
if (type === 'sent') {
|
|
423
|
+
stats.sent++;
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
stats.received++;
|
|
427
|
+
}
|
|
428
|
+
stats.latency.push(latency);
|
|
429
|
+
// Keep only last 100 latency measurements
|
|
430
|
+
if (stats.latency.length > 100) {
|
|
431
|
+
stats.latency.shift();
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Calculate current swarm statistics
|
|
436
|
+
*/
|
|
437
|
+
async calculateStats() {
|
|
438
|
+
const quicStats = await this.config.quicClient.getStats();
|
|
439
|
+
// Calculate messages per second (last minute average)
|
|
440
|
+
const messagesPerSecond = this.state.stats.totalMessages / 60;
|
|
441
|
+
// Calculate average latency across all agents
|
|
442
|
+
let totalLatency = 0;
|
|
443
|
+
let latencyCount = 0;
|
|
444
|
+
for (const stats of this.messageStats.values()) {
|
|
445
|
+
if (stats.latency.length > 0) {
|
|
446
|
+
totalLatency += stats.latency.reduce((sum, l) => sum + l, 0);
|
|
447
|
+
latencyCount += stats.latency.length;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
const averageLatency = latencyCount > 0 ? totalLatency / latencyCount : 0;
|
|
451
|
+
return {
|
|
452
|
+
totalAgents: this.state.agents.size,
|
|
453
|
+
activeAgents: this.state.agents.size,
|
|
454
|
+
totalMessages: this.state.stats.totalMessages,
|
|
455
|
+
messagesPerSecond,
|
|
456
|
+
averageLatency,
|
|
457
|
+
quicStats
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
}
|