1use std::collections::HashMap;
14use std::sync::Arc;
15use std::time::Duration;
16
17use tokio::sync::{Mutex, mpsc};
18use tracing::{debug, warn};
19
20use clawft_types::agent_bus::{AgentBusError, InterAgentMessage};
21
22const DEFAULT_INBOX_CAPACITY: usize = 256;
24
25pub struct AgentInbox {
30 agent_id: String,
31 rx: mpsc::Receiver<InterAgentMessage>,
32}
33
34impl AgentInbox {
35 pub fn agent_id(&self) -> &str {
37 &self.agent_id
38 }
39
40 pub async fn recv(&mut self) -> Option<InterAgentMessage> {
45 loop {
46 match self.rx.recv().await {
47 Some(msg) => {
48 if msg.is_expired() {
49 warn!(
50 msg_id = %msg.id,
51 from = %msg.from_agent,
52 to = %msg.to_agent,
53 "dropping expired inter-agent message"
54 );
55 continue;
56 }
57 return Some(msg);
58 }
59 None => return None,
60 }
61 }
62 }
63
64 pub fn try_recv(&mut self) -> Option<InterAgentMessage> {
68 loop {
69 match self.rx.try_recv() {
70 Ok(msg) => {
71 if msg.is_expired() {
72 warn!(
73 msg_id = %msg.id,
74 from = %msg.from_agent,
75 to = %msg.to_agent,
76 "dropping expired inter-agent message"
77 );
78 continue;
79 }
80 return Some(msg);
81 }
82 Err(_) => return None,
83 }
84 }
85 }
86}
87
88pub struct AgentBus {
99 inboxes: Arc<Mutex<HashMap<String, mpsc::Sender<InterAgentMessage>>>>,
101 inbox_capacity: usize,
103}
104
105impl AgentBus {
106 pub fn new() -> Self {
108 Self {
109 inboxes: Arc::new(Mutex::new(HashMap::new())),
110 inbox_capacity: DEFAULT_INBOX_CAPACITY,
111 }
112 }
113
114 pub fn with_capacity(capacity: usize) -> Self {
116 Self {
117 inboxes: Arc::new(Mutex::new(HashMap::new())),
118 inbox_capacity: capacity,
119 }
120 }
121
122 pub async fn register_agent(&self, agent_id: &str) -> AgentInbox {
127 let (tx, rx) = mpsc::channel(self.inbox_capacity);
128 let mut inboxes = self.inboxes.lock().await;
129 inboxes.insert(agent_id.to_string(), tx);
130
131 debug!(agent_id = %agent_id, "agent registered on bus");
132
133 AgentInbox {
134 agent_id: agent_id.to_string(),
135 rx,
136 }
137 }
138
139 pub async fn unregister_agent(&self, agent_id: &str) {
144 let mut inboxes = self.inboxes.lock().await;
145 if inboxes.remove(agent_id).is_some() {
146 debug!(agent_id = %agent_id, "agent unregistered from bus");
147 }
148 }
149
150 pub async fn send(&self, msg: InterAgentMessage) -> Result<(), AgentBusError> {
161 if msg.is_expired() {
163 warn!(
164 msg_id = %msg.id,
165 from = %msg.from_agent,
166 to = %msg.to_agent,
167 "dropping expired message before delivery"
168 );
169 return Err(AgentBusError::MessageExpired { ttl: msg.ttl });
170 }
171
172 let inboxes = self.inboxes.lock().await;
173 let tx = inboxes
174 .get(&msg.to_agent)
175 .ok_or_else(|| AgentBusError::AgentNotFound(msg.to_agent.clone()))?;
176
177 tx.try_send(msg).map_err(|e| match e {
178 mpsc::error::TrySendError::Full(msg) => {
179 AgentBusError::InboxFull(msg.to_agent.clone())
180 }
181 mpsc::error::TrySendError::Closed(msg) => {
182 AgentBusError::AgentNotFound(msg.to_agent.clone())
183 }
184 })
185 }
186
187 pub async fn is_registered(&self, agent_id: &str) -> bool {
189 let inboxes = self.inboxes.lock().await;
190 inboxes.contains_key(agent_id)
191 }
192
193 pub async fn registered_agents(&self) -> Vec<String> {
195 let inboxes = self.inboxes.lock().await;
196 inboxes.keys().cloned().collect()
197 }
198}
199
200impl Default for AgentBus {
201 fn default() -> Self {
202 Self::new()
203 }
204}
205
206pub struct SwarmCoordinator {
212 bus: Arc<AgentBus>,
214 coordinator_id: String,
216 worker_agents: Vec<String>,
218}
219
220impl SwarmCoordinator {
221 pub fn new(
223 bus: Arc<AgentBus>,
224 coordinator_id: impl Into<String>,
225 worker_agents: Vec<String>,
226 ) -> Self {
227 Self {
228 bus,
229 coordinator_id: coordinator_id.into(),
230 worker_agents,
231 }
232 }
233
234 pub async fn dispatch_subtask(
239 &self,
240 task: &str,
241 worker: &str,
242 payload: serde_json::Value,
243 ttl: Duration,
244 ) -> Result<uuid::Uuid, AgentBusError> {
245 let msg = InterAgentMessage::new(
246 &self.coordinator_id,
247 worker,
248 task,
249 payload,
250 ttl,
251 );
252 let msg_id = msg.id;
253 self.bus.send(msg).await?;
254 debug!(
255 coordinator = %self.coordinator_id,
256 worker = %worker,
257 task = %task,
258 msg_id = %msg_id,
259 "dispatched subtask"
260 );
261 Ok(msg_id)
262 }
263
264 pub async fn broadcast_task(
268 &self,
269 task: &str,
270 payload: serde_json::Value,
271 ttl: Duration,
272 ) -> Vec<(String, Result<uuid::Uuid, AgentBusError>)> {
273 let mut results = Vec::new();
274 for worker in &self.worker_agents {
275 let result = self
276 .dispatch_subtask(task, worker, payload.clone(), ttl)
277 .await;
278 results.push((worker.clone(), result));
279 }
280 results
281 }
282
283 pub fn coordinator_id(&self) -> &str {
285 &self.coordinator_id
286 }
287
288 pub fn workers(&self) -> &[String] {
290 &self.worker_agents
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297 use serde_json::json;
298
299 #[tokio::test]
300 async fn register_and_send() {
301 let bus = AgentBus::new();
302 let mut inbox = bus.register_agent("agent-a").await;
303
304 let msg = InterAgentMessage::new(
305 "agent-b",
306 "agent-a",
307 "do work",
308 json!({}),
309 Duration::from_secs(60),
310 );
311 bus.send(msg).await.unwrap();
312
313 let received = inbox.recv().await.unwrap();
314 assert_eq!(received.from_agent, "agent-b");
315 assert_eq!(received.to_agent, "agent-a");
316 assert_eq!(received.task, "do work");
317 }
318
319 #[tokio::test]
320 async fn send_to_unregistered_agent_fails() {
321 let bus = AgentBus::new();
322 let msg = InterAgentMessage::new(
323 "sender",
324 "nonexistent",
325 "task",
326 json!({}),
327 Duration::from_secs(60),
328 );
329 let result = bus.send(msg).await;
330 assert!(result.is_err());
331 assert!(matches!(
332 result.unwrap_err(),
333 AgentBusError::AgentNotFound(_)
334 ));
335 }
336
337 #[tokio::test]
338 async fn agent_isolation() {
339 let bus = AgentBus::new();
340 let mut inbox_a = bus.register_agent("agent-a").await;
341 let mut inbox_b = bus.register_agent("agent-b").await;
342
343 let msg = InterAgentMessage::new(
345 "sender",
346 "agent-a",
347 "for a only",
348 json!({}),
349 Duration::from_secs(60),
350 );
351 bus.send(msg).await.unwrap();
352
353 let received = inbox_a.try_recv();
355 assert!(received.is_some());
356 assert_eq!(received.unwrap().task, "for a only");
357
358 let received_b = inbox_b.try_recv();
360 assert!(received_b.is_none());
361 }
362
363 #[tokio::test]
364 async fn unregister_agent() {
365 let bus = AgentBus::new();
366 let _inbox = bus.register_agent("agent-a").await;
367
368 assert!(bus.is_registered("agent-a").await);
369 bus.unregister_agent("agent-a").await;
370 assert!(!bus.is_registered("agent-a").await);
371 }
372
373 #[tokio::test]
374 async fn inbox_full_backpressure() {
375 let bus = AgentBus::with_capacity(2);
376 let _inbox = bus.register_agent("agent-a").await;
377
378 for i in 0..2 {
380 let msg = InterAgentMessage::new(
381 "sender",
382 "agent-a",
383 &format!("task-{i}"),
384 json!({}),
385 Duration::from_secs(60),
386 );
387 bus.send(msg).await.unwrap();
388 }
389
390 let msg = InterAgentMessage::new(
392 "sender",
393 "agent-a",
394 "overflow",
395 json!({}),
396 Duration::from_secs(60),
397 );
398 let result = bus.send(msg).await;
399 assert!(result.is_err());
400 assert!(matches!(result.unwrap_err(), AgentBusError::InboxFull(_)));
401 }
402
403 #[tokio::test]
404 async fn registered_agents_list() {
405 let bus = AgentBus::new();
406 bus.register_agent("a").await;
407 bus.register_agent("b").await;
408
409 let agents = bus.registered_agents().await;
410 assert_eq!(agents.len(), 2);
411 assert!(agents.contains(&"a".to_string()));
412 assert!(agents.contains(&"b".to_string()));
413 }
414
415 #[tokio::test]
416 async fn coordinator_dispatch() {
417 let bus = Arc::new(AgentBus::new());
418 let _inbox = bus.register_agent("worker-1").await;
419
420 let coord = SwarmCoordinator::new(
421 bus.clone(),
422 "coordinator",
423 vec!["worker-1".into()],
424 );
425
426 let msg_id = coord
427 .dispatch_subtask("subtask-1", "worker-1", json!({"data": 42}), Duration::from_secs(60))
428 .await
429 .unwrap();
430
431 assert!(!msg_id.is_nil());
433 }
434
435 #[tokio::test]
436 async fn coordinator_broadcast() {
437 let bus = Arc::new(AgentBus::new());
438 let _inbox1 = bus.register_agent("w1").await;
439 let _inbox2 = bus.register_agent("w2").await;
440
441 let coord = SwarmCoordinator::new(
442 bus.clone(),
443 "coord",
444 vec!["w1".into(), "w2".into()],
445 );
446
447 let results = coord
448 .broadcast_task("broadcast-task", json!({}), Duration::from_secs(60))
449 .await;
450
451 assert_eq!(results.len(), 2);
452 for (_, result) in &results {
453 assert!(result.is_ok());
454 }
455 }
456
457 #[test]
458 fn agent_bus_is_send_sync() {
459 fn assert_send_sync<T: Send + Sync>() {}
460 assert_send_sync::<AgentBus>();
461 }
462}