Coverage for agentos/comm/layer.py: 34%

122 statements  

« prev     ^ index     » next       coverage.py v7.14.3, created at 2026-07-06 12:29 +0800

1""" 

2Communication Layer for NexusAgent. 

3 

4Provides multiple communication patterns for multi-agent systems: 

5- Blackboard: Shared memory space 

6- EventBus: Publish-subscribe events 

7- Mailbox: Direct point-to-point messaging 

8""" 

9 

10from __future__ import annotations 

11 

12import asyncio 

13import time 

14import uuid 

15from collections.abc import Callable 

16from dataclasses import dataclass, field 

17from typing import Any 

18 

19 

20@dataclass 

21class Message: 

22 """ 

23 Communication message. 

24 

25 Attributes: 

26 id: Unique identifier 

27 sender: Sender name 

28 receiver: Receiver name 

29 content: Message content 

30 metadata: Additional metadata 

31 timestamp: Message timestamp 

32 """ 

33 

34 id: str = field(default_factory=lambda: uuid.uuid4().hex[:12]) 

35 sender: str = "" 

36 receiver: str | None = None 

37 content: Any = None 

38 metadata: dict[str, Any] = field(default_factory=dict) 

39 timestamp: float = field(default_factory=time.time) 

40 

41 def to_dict(self) -> dict[str, Any]: 

42 """Convert to dict.""" 

43 return { 

44 "id": self.id, 

45 "sender": self.sender, 

46 "receiver": self.receiver, 

47 "content": self.content, 

48 "metadata": self.metadata, 

49 "timestamp": self.timestamp, 

50 } 

51 

52 

53class Blackboard: 

54 """ 

55 Shared memory space for agents. 

56 

57 Agents can read/write to a shared blackboard. 

58 Useful for collaborative problem solving. 

59 

60 Usage: 

61 blackboard = Blackboard() 

62 blackboard.write("agent1", "status", "working") 

63 status = blackboard.read("agent1", "status") 

64 """ 

65 

66 def __init__(self): 

67 """Initialize blackboard.""" 

68 self._data: dict[str, dict[str, Any]] = {} 

69 self._history: list[dict[str, Any]] = [] 

70 

71 def write( 

72 self, 

73 agent_name: str, 

74 key: str, 

75 value: Any, 

76 ) -> None: 

77 """ 

78 Write to blackboard. 

79 

80 Args: 

81 agent_name: Agent name 

82 key: Data key 

83 value: Data value 

84 """ 

85 if agent_name not in self._data: 

86 self._data[agent_name] = {} 

87 

88 self._data[agent_name][key] = value 

89 

90 # Record in history 

91 self._history.append( 

92 { 

93 "agent": agent_name, 

94 "key": key, 

95 "value": value, 

96 "timestamp": time.time(), 

97 } 

98 ) 

99 

100 def read( 

101 self, 

102 agent_name: str, 

103 key: str, 

104 default: Any = None, 

105 ) -> Any: 

106 """ 

107 Read from blackboard. 

108 

109 Args: 

110 agent_name: Agent name 

111 key: Data key 

112 default: Default value if not found 

113 

114 Returns: 

115 Data value 

116 """ 

117 if agent_name not in self._data: 

118 return default 

119 

120 return self._data[agent_name].get(key, default) 

121 

122 def read_all(self, key: str) -> dict[str, Any]: 

123 """ 

124 Read key from all agents. 

125 

126 Args: 

127 key: Data key 

128 

129 Returns: 

130 Dict of agent_name -> value 

131 """ 

132 return {agent: data.get(key) for agent, data in self._data.items() if key in data} 

133 

134 def get_agent_data(self, agent_name: str) -> dict[str, Any]: 

135 """ 

136 Get all data for an agent. 

137 

138 Args: 

139 agent_name: Agent name 

140 

141 Returns: 

142 Dict of key -> value 

143 """ 

144 return self._data.get(agent_name, {}).copy() 

145 

146 def get_history( 

147 self, 

148 agent_name: str | None = None, 

149 limit: int = 100, 

150 ) -> list[dict[str, Any]]: 

151 """ 

152 Get write history. 

153 

154 Args: 

155 agent_name: Filter by agent (None = all) 

156 limit: Max results 

157 

158 Returns: 

159 List of history entries 

160 """ 

161 history = self._history 

162 

163 if agent_name: 

164 history = [h for h in history if h["agent"] == agent_name] 

165 

166 return history[-limit:] 

167 

168 def clear(self, agent_name: str | None = None) -> None: 

169 """ 

170 Clear blackboard. 

171 

172 Args: 

173 agent_name: Clear specific agent (None = all) 

174 """ 

175 if agent_name: 

176 self._data.pop(agent_name, None) 

177 else: 

178 self._data.clear() 

179 

180 

181class EventBus: 

182 """ 

183 Publish-subscribe event system. 

184 

185 Agents can subscribe to events and publish events. 

186 Useful for event-driven architectures. 

187 

188 Usage: 

189 bus = EventBus() 

190 

191 # Subscribe 

192 bus.subscribe("task_completed", callback) 

193 

194 # Publish 

195 bus.publish("task_completed", {"task_id": "123"}) 

196 """ 

197 

198 def __init__(self): 

199 """Initialize event bus.""" 

200 self._subscribers: dict[str, list[Callable[[Any], None]]] = {} 

201 self._history: list[dict[str, Any]] = [] 

202 

203 def subscribe( 

204 self, 

205 event_type: str, 

206 callback: Callable[[Any], None], 

207 ) -> None: 

208 """ 

209 Subscribe to an event. 

210 

211 Args: 

212 event_type: Event type 

213 callback: Callback function 

214 """ 

215 if event_type not in self._subscribers: 

216 self._subscribers[event_type] = [] 

217 

218 self._subscribers[event_type].append(callback) 

219 

220 def unsubscribe( 

221 self, 

222 event_type: str, 

223 callback: Callable[[Any], None], 

224 ) -> bool: 

225 """ 

226 Unsubscribe from an event. 

227 

228 Args: 

229 event_type: Event type 

230 callback: Callback function 

231 

232 Returns: 

233 True if unsubscribed, False if not found 

234 """ 

235 if event_type not in self._subscribers: 

236 return False 

237 

238 if callback in self._subscribers[event_type]: 

239 self._subscribers[event_type].remove(callback) 

240 return True 

241 

242 return False 

243 

244 def publish( 

245 self, 

246 event_type: str, 

247 data: Any = None, 

248 sender: str = "", 

249 ) -> int: 

250 """ 

251 Publish an event. 

252 

253 Args: 

254 event_type: Event type 

255 data: Event data 

256 sender: Sender name 

257 

258 Returns: 

259 Number of subscribers notified 

260 """ 

261 # Record in history 

262 self._history.append( 

263 { 

264 "event_type": event_type, 

265 "data": data, 

266 "sender": sender, 

267 "timestamp": time.time(), 

268 } 

269 ) 

270 

271 # Notify subscribers 

272 subscribers = self._subscribers.get(event_type, []) 

273 for callback in subscribers: 

274 try: 

275 callback(data) 

276 except Exception: 

277 pass # Don't let one callback break others 

278 

279 return len(subscribers) 

280 

281 async def publish_async( 

282 self, 

283 event_type: str, 

284 data: Any = None, 

285 sender: str = "", 

286 ) -> int: 

287 """ 

288 Publish an event asynchronously. 

289 

290 Args: 

291 event_type: Event type 

292 data: Event data 

293 sender: Sender name 

294 

295 Returns: 

296 Number of subscribers notified 

297 """ 

298 # Record in history 

299 self._history.append( 

300 { 

301 "event_type": event_type, 

302 "data": data, 

303 "sender": sender, 

304 "timestamp": time.time(), 

305 } 

306 ) 

307 

308 # Notify subscribers 

309 subscribers = self._subscribers.get(event_type, []) 

310 tasks = [] 

311 for callback in subscribers: 

312 if asyncio.iscoroutinefunction(callback): 

313 tasks.append(callback(data)) 

314 else: 

315 callback(data) 

316 

317 if tasks: 

318 await asyncio.gather(*tasks, return_exceptions=True) 

319 

320 return len(subscribers) 

321 

322 def get_history( 

323 self, 

324 event_type: str | None = None, 

325 limit: int = 100, 

326 ) -> list[dict[str, Any]]: 

327 """ 

328 Get event history. 

329 

330 Args: 

331 event_type: Filter by event type (None = all) 

332 limit: Max results 

333 

334 Returns: 

335 List of history entries 

336 """ 

337 history = self._history 

338 

339 if event_type: 

340 history = [h for h in history if h["event_type"] == event_type] 

341 

342 return history[-limit:] 

343 

344 def clear(self) -> None: 

345 """Clear event bus.""" 

346 self._subscribers.clear() 

347 self._history.clear() 

348 

349 

350class Mailbox: 

351 """ 

352 Point-to-point messaging system. 

353 

354 Agents have mailboxes and can send/receive messages. 

355 Useful for direct communication. 

356 

357 Usage: 

358 mailbox = Mailbox() 

359 mailbox.send("agent1", "agent2", "Hello") 

360 messages = mailbox.receive("agent2") 

361 """ 

362 

363 def __init__(self): 

364 """Initialize mailbox system.""" 

365 self._mailboxes: dict[str, list[Message]] = {} 

366 self._sent: list[Message] = [] 

367 

368 def send(self, sender: str, receiver: str, content: Any, **metadata) -> Message: 

369 """ 

370 Send a message. 

371 

372 Args: 

373 sender: Sender name 

374 receiver: Receiver name 

375 content: Message content 

376 **metadata: Additional metadata 

377 

378 Returns: 

379 Created Message 

380 """ 

381 message = Message( 

382 sender=sender, 

383 receiver=receiver, 

384 content=content, 

385 metadata=metadata, 

386 ) 

387 

388 # Add to receiver's mailbox 

389 if receiver not in self._mailboxes: 

390 self._mailboxes[receiver] = [] 

391 

392 self._mailboxes[receiver].append(message) 

393 self._sent.append(message) 

394 

395 return message 

396 

397 def receive( 

398 self, 

399 receiver: str, 

400 limit: int = 100, 

401 ) -> list[Message]: 

402 """ 

403 Receive messages. 

404 

405 Args: 

406 receiver: Receiver name 

407 limit: Max messages 

408 

409 Returns: 

410 List of messages 

411 """ 

412 messages = self._mailboxes.get(receiver, []) 

413 return messages[:limit] 

414 

415 def receive_and_clear( 

416 self, 

417 receiver: str, 

418 limit: int = 100, 

419 ) -> list[Message]: 

420 """ 

421 Receive and clear messages. 

422 

423 Args: 

424 receiver: Receiver name 

425 limit: Max messages 

426 

427 Returns: 

428 List of messages 

429 """ 

430 messages = self._mailboxes.get(receiver, [])[:limit] 

431 self._mailboxes[receiver] = self._mailboxes.get(receiver, [])[limit:] 

432 return messages 

433 

434 def get_sent( 

435 self, 

436 sender: str | None = None, 

437 limit: int = 100, 

438 ) -> list[Message]: 

439 """ 

440 Get sent messages. 

441 

442 Args: 

443 sender: Filter by sender (None = all) 

444 limit: Max results 

445 

446 Returns: 

447 List of messages 

448 """ 

449 sent = self._sent 

450 

451 if sender: 

452 sent = [m for m in sent if m.sender == sender] 

453 

454 return sent[-limit:] 

455 

456 def clear(self, receiver: str | None = None) -> None: 

457 """ 

458 Clear mailboxes. 

459 

460 Args: 

461 receiver: Clear specific receiver (None = all) 

462 """ 

463 if receiver: 

464 self._mailboxes.pop(receiver, None) 

465 else: 

466 self._mailboxes.clear() 

467 

468 

469class CommunicationLayer: 

470 """ 

471 Unified communication layer. 

472 

473 Combines Blackboard, EventBus, and Mailbox into 

474 a single interface. 

475 

476 Usage: 

477 comm = CommunicationLayer() 

478 

479 # Use blackboard 

480 comm.blackboard.write("agent1", "status", "working") 

481 

482 # Use event bus 

483 comm.event_bus.subscribe("task_completed", callback) 

484 

485 # Use mailbox 

486 comm.mailbox.send("agent1", "agent2", "Hello") 

487 """ 

488 

489 def __init__(self): 

490 """Initialize communication layer.""" 

491 self.blackboard = Blackboard() 

492 self.event_bus = EventBus() 

493 self.mailbox = Mailbox() 

494 

495 def clear(self) -> None: 

496 """Clear all communication channels.""" 

497 self.blackboard.clear() 

498 self.event_bus.clear() 

499 self.mailbox.clear()