zoey_core/
multi_agent.rs

1//! Multi-Agent Coordination
2//!
3//! Enables agents to communicate, collaborate, and help each other
4
5use crate::types::*;
6use crate::Result;
7use async_trait::async_trait;
8use std::collections::HashMap;
9use std::sync::{Arc, RwLock};
10use tracing::{debug, info};
11
12/// Agent coordination message
13#[derive(Debug, Clone)]
14pub struct CoordinationMessage {
15    /// Message ID
16    pub id: uuid::Uuid,
17
18    /// Source agent ID
19    pub from_agent_id: uuid::Uuid,
20
21    /// Target agent ID
22    pub to_agent_id: uuid::Uuid,
23
24    /// Message type
25    pub message_type: CoordinationMessageType,
26
27    /// Message content
28    pub content: serde_json::Value,
29
30    /// Priority (higher = more urgent)
31    pub priority: i32,
32
33    /// Timestamp
34    pub timestamp: i64,
35
36    /// Optional response required
37    pub requires_response: bool,
38}
39
40/// Types of coordination messages
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum CoordinationMessageType {
43    /// Request help from another agent
44    HelpRequest,
45
46    /// Offer help to another agent
47    HelpOffer,
48
49    /// Share information
50    InformationShare,
51
52    /// Delegate task
53    TaskDelegation,
54
55    /// Query capabilities
56    CapabilityQuery,
57
58    /// Status update
59    StatusUpdate,
60
61    /// Generic message
62    Generic,
63}
64
65/// Agent capability description
66#[derive(Debug, Clone)]
67pub struct AgentCapability {
68    /// Agent ID
69    pub agent_id: uuid::Uuid,
70
71    /// Capability name
72    pub name: String,
73
74    /// Capability description
75    pub description: String,
76
77    /// Proficiency level (0.0 - 1.0)
78    pub proficiency: f32,
79
80    /// Availability (0.0 - 1.0, where 1.0 = fully available)
81    pub availability: f32,
82}
83
84/// Multi-agent coordinator
85pub struct MultiAgentCoordinator {
86    /// Registered agents
87    agents: Arc<RwLock<HashMap<uuid::Uuid, AgentInfo>>>,
88
89    /// Message queue
90    messages: Arc<RwLock<Vec<CoordinationMessage>>>,
91
92    /// Agent capabilities
93    capabilities: Arc<RwLock<HashMap<uuid::Uuid, Vec<AgentCapability>>>>,
94}
95
96/// Agent information
97#[derive(Debug, Clone)]
98pub struct AgentInfo {
99    /// Agent ID
100    pub id: uuid::Uuid,
101
102    /// Agent name
103    pub name: String,
104
105    /// Agent status
106    pub status: AgentStatus,
107
108    /// Current load (0.0 - 1.0)
109    pub load: f32,
110
111    /// Last heartbeat
112    pub last_heartbeat: i64,
113}
114
115/// Agent status
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117pub enum AgentStatus {
118    /// Agent is online and available
119    Online,
120
121    /// Agent is busy
122    Busy,
123
124    /// Agent is idle
125    Idle,
126
127    /// Agent is offline
128    Offline,
129}
130
131impl MultiAgentCoordinator {
132    /// Create a new multi-agent coordinator
133    pub fn new() -> Self {
134        Self {
135            agents: Arc::new(RwLock::new(HashMap::new())),
136            messages: Arc::new(RwLock::new(Vec::new())),
137            capabilities: Arc::new(RwLock::new(HashMap::new())),
138        }
139    }
140
141    /// Register an agent
142    pub fn register_agent(&self, agent_id: uuid::Uuid, name: String) -> Result<()> {
143        info!("Registering agent {} ({})", name, agent_id);
144
145        let info = AgentInfo {
146            id: agent_id,
147            name,
148            status: AgentStatus::Online,
149            load: 0.0,
150            last_heartbeat: chrono::Utc::now().timestamp(),
151        };
152
153        self.agents.write().unwrap().insert(agent_id, info);
154
155        Ok(())
156    }
157
158    /// Unregister an agent
159    pub fn unregister_agent(&self, agent_id: uuid::Uuid) -> Result<()> {
160        info!("Unregistering agent {}", agent_id);
161        self.agents.write().unwrap().remove(&agent_id);
162        Ok(())
163    }
164
165    /// Send message to another agent
166    pub fn send_message(&self, message: CoordinationMessage) -> Result<()> {
167        debug!(
168            "Sending coordination message: {} -> {}",
169            message.from_agent_id, message.to_agent_id
170        );
171
172        self.messages.write().unwrap().push(message);
173
174        Ok(())
175    }
176
177    /// Get messages for an agent
178    pub fn get_messages(&self, agent_id: uuid::Uuid) -> Vec<CoordinationMessage> {
179        let mut messages = self.messages.write().unwrap();
180
181        // Extract messages for this agent
182        let agent_messages: Vec<_> = messages
183            .iter()
184            .filter(|m| m.to_agent_id == agent_id)
185            .cloned()
186            .collect();
187
188        // Remove from queue
189        messages.retain(|m| m.to_agent_id != agent_id);
190
191        agent_messages
192    }
193
194    /// Register agent capability
195    pub fn register_capability(&self, capability: AgentCapability) -> Result<()> {
196        debug!(
197            "Registering capability {} for agent {}",
198            capability.name, capability.agent_id
199        );
200
201        self.capabilities
202            .write()
203            .unwrap()
204            .entry(capability.agent_id)
205            .or_insert_with(Vec::new)
206            .push(capability);
207
208        Ok(())
209    }
210
211    /// Find agents with specific capability
212    pub fn find_agents_with_capability(&self, capability_name: &str) -> Vec<(uuid::Uuid, f32)> {
213        let capabilities = self.capabilities.read().unwrap();
214        let agents = self.agents.read().unwrap();
215
216        let mut matches = Vec::new();
217
218        for (agent_id, caps) in capabilities.iter() {
219            if let Some(agent_info) = agents.get(agent_id) {
220                // Only consider online agents
221                if agent_info.status == AgentStatus::Online
222                    || agent_info.status == AgentStatus::Idle
223                {
224                    for cap in caps {
225                        if cap.name == capability_name {
226                            let score =
227                                cap.proficiency * cap.availability * (1.0 - agent_info.load);
228                            matches.push((*agent_id, score));
229                        }
230                    }
231                }
232            }
233        }
234
235        // Sort by score (highest first)
236        matches.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
237
238        matches
239    }
240
241    /// Request help from another agent
242    pub async fn request_help(
243        &self,
244        from_agent_id: uuid::Uuid,
245        capability_needed: &str,
246        request_data: serde_json::Value,
247    ) -> Result<Option<uuid::Uuid>> {
248        // Find best agent for the task
249        let candidates = self.find_agents_with_capability(capability_needed);
250
251        if let Some((best_agent_id, score)) = candidates.first() {
252            info!(
253                "Found agent {} for capability {} (score: {})",
254                best_agent_id, capability_needed, score
255            );
256
257            // Send help request
258            let message = CoordinationMessage {
259                id: uuid::Uuid::new_v4(),
260                from_agent_id,
261                to_agent_id: *best_agent_id,
262                message_type: CoordinationMessageType::HelpRequest,
263                content: request_data,
264                priority: 5,
265                timestamp: chrono::Utc::now().timestamp(),
266                requires_response: true,
267            };
268
269            self.send_message(message)?;
270
271            Ok(Some(*best_agent_id))
272        } else {
273            debug!("No agents found with capability {}", capability_needed);
274            Ok(None)
275        }
276    }
277
278    /// Update agent status
279    pub fn update_agent_status(
280        &self,
281        agent_id: uuid::Uuid,
282        status: AgentStatus,
283        load: f32,
284    ) -> Result<()> {
285        if let Some(agent) = self.agents.write().unwrap().get_mut(&agent_id) {
286            agent.status = status;
287            agent.load = load;
288            agent.last_heartbeat = chrono::Utc::now().timestamp();
289        }
290        Ok(())
291    }
292
293    /// Get all active agents
294    pub fn get_active_agents(&self) -> Vec<AgentInfo> {
295        self.agents
296            .read()
297            .unwrap()
298            .values()
299            .filter(|a| a.status != AgentStatus::Offline)
300            .cloned()
301            .collect()
302    }
303
304    /// Broadcast message to all agents
305    pub fn broadcast(
306        &self,
307        from_agent_id: uuid::Uuid,
308        content: serde_json::Value,
309    ) -> Result<usize> {
310        let agents = self.get_active_agents();
311        let mut sent = 0;
312
313        for agent in agents {
314            if agent.id != from_agent_id {
315                let message = CoordinationMessage {
316                    id: uuid::Uuid::new_v4(),
317                    from_agent_id,
318                    to_agent_id: agent.id,
319                    message_type: CoordinationMessageType::InformationShare,
320                    content: content.clone(),
321                    priority: 3,
322                    timestamp: chrono::Utc::now().timestamp(),
323                    requires_response: false,
324                };
325
326                self.send_message(message)?;
327                sent += 1;
328            }
329        }
330
331        Ok(sent)
332    }
333}
334
335impl Default for MultiAgentCoordinator {
336    fn default() -> Self {
337        Self::new()
338    }
339}
340
341/// Multi-agent coordination service
342pub struct MultiAgentService {
343    coordinator: Arc<MultiAgentCoordinator>,
344    agent_id: uuid::Uuid,
345}
346
347impl MultiAgentService {
348    /// Create a new multi-agent service
349    pub fn new(coordinator: Arc<MultiAgentCoordinator>, agent_id: uuid::Uuid) -> Self {
350        Self {
351            coordinator,
352            agent_id,
353        }
354    }
355
356    /// Request help from another agent
357    pub async fn request_help(
358        &self,
359        capability: &str,
360        data: serde_json::Value,
361    ) -> Result<Option<uuid::Uuid>> {
362        self.coordinator
363            .request_help(self.agent_id, capability, data)
364            .await
365    }
366
367    /// Offer help for a capability
368    pub fn offer_capability(
369        &self,
370        name: String,
371        description: String,
372        proficiency: f32,
373    ) -> Result<()> {
374        let capability = AgentCapability {
375            agent_id: self.agent_id,
376            name,
377            description,
378            proficiency,
379            availability: 1.0,
380        };
381
382        self.coordinator.register_capability(capability)
383    }
384
385    /// Get pending messages
386    pub fn get_messages(&self) -> Vec<CoordinationMessage> {
387        self.coordinator.get_messages(self.agent_id)
388    }
389
390    /// Update status
391    pub fn update_status(&self, status: AgentStatus, load: f32) -> Result<()> {
392        self.coordinator
393            .update_agent_status(self.agent_id, status, load)
394    }
395
396    /// Find agents offering a capability and return (agent_id, score)
397    pub fn find_agents(&self, capability: &str) -> Vec<(uuid::Uuid, f32)> {
398        self.coordinator.find_agents_with_capability(capability)
399    }
400}
401
402#[async_trait]
403impl Service for MultiAgentService {
404    fn service_type(&self) -> &str {
405        "multi-agent-coordination"
406    }
407
408    async fn initialize(&mut self, _runtime: Arc<dyn std::any::Any + Send + Sync>) -> Result<()> {
409        info!("Multi-agent coordination service initialized");
410        Ok(())
411    }
412
413    async fn start(&mut self) -> Result<()> {
414        info!("Multi-agent coordination service started");
415        Ok(())
416    }
417
418    fn query_agents(&self, capability: &str) -> Option<Vec<(uuid::Uuid, f32)>> {
419        Some(self.coordinator.find_agents_with_capability(capability))
420    }
421}
422
423#[cfg(test)]
424mod tests {
425    use super::*;
426
427    #[test]
428    fn test_coordinator_creation() {
429        let coordinator = MultiAgentCoordinator::new();
430        assert_eq!(coordinator.get_active_agents().len(), 0);
431    }
432
433    #[test]
434    fn test_agent_registration() {
435        let coordinator = MultiAgentCoordinator::new();
436
437        let agent_id = uuid::Uuid::new_v4();
438        coordinator
439            .register_agent(agent_id, "TestAgent".to_string())
440            .unwrap();
441
442        assert_eq!(coordinator.get_active_agents().len(), 1);
443    }
444
445    #[test]
446    fn test_capability_registration() {
447        let coordinator = MultiAgentCoordinator::new();
448
449        let agent_id = uuid::Uuid::new_v4();
450        coordinator
451            .register_agent(agent_id, "Agent1".to_string())
452            .unwrap();
453
454        let capability = AgentCapability {
455            agent_id,
456            name: "code_generation".to_string(),
457            description: "Can generate code".to_string(),
458            proficiency: 0.9,
459            availability: 1.0,
460        };
461
462        coordinator.register_capability(capability).unwrap();
463
464        let matches = coordinator.find_agents_with_capability("code_generation");
465        assert_eq!(matches.len(), 1);
466        assert_eq!(matches[0].0, agent_id);
467    }
468
469    #[tokio::test]
470    async fn test_help_request() {
471        let coordinator = MultiAgentCoordinator::new();
472
473        let agent1 = uuid::Uuid::new_v4();
474        let agent2 = uuid::Uuid::new_v4();
475
476        coordinator
477            .register_agent(agent1, "Agent1".to_string())
478            .unwrap();
479        coordinator
480            .register_agent(agent2, "Agent2".to_string())
481            .unwrap();
482
483        // Agent2 has a capability
484        let capability = AgentCapability {
485            agent_id: agent2,
486            name: "translation".to_string(),
487            description: "Can translate text".to_string(),
488            proficiency: 0.95,
489            availability: 1.0,
490        };
491        coordinator.register_capability(capability).unwrap();
492
493        // Agent1 requests help
494        let result = coordinator
495            .request_help(
496                agent1,
497                "translation",
498                serde_json::json!({"text": "Hello", "to_lang": "Spanish"}),
499            )
500            .await
501            .unwrap();
502
503        assert!(result.is_some());
504        assert_eq!(result.unwrap(), agent2);
505
506        // Agent2 should have received the message
507        let messages = coordinator.get_messages(agent2);
508        assert_eq!(messages.len(), 1);
509        assert_eq!(
510            messages[0].message_type,
511            CoordinationMessageType::HelpRequest
512        );
513    }
514
515    #[test]
516    fn test_broadcast() {
517        let coordinator = MultiAgentCoordinator::new();
518
519        let agent1 = uuid::Uuid::new_v4();
520        let agent2 = uuid::Uuid::new_v4();
521        let agent3 = uuid::Uuid::new_v4();
522
523        coordinator
524            .register_agent(agent1, "Agent1".to_string())
525            .unwrap();
526        coordinator
527            .register_agent(agent2, "Agent2".to_string())
528            .unwrap();
529        coordinator
530            .register_agent(agent3, "Agent3".to_string())
531            .unwrap();
532
533        let sent = coordinator
534            .broadcast(agent1, serde_json::json!({"info": "test"}))
535            .unwrap();
536
537        assert_eq!(sent, 2); // Sent to agent2 and agent3 (not agent1)
538
539        assert_eq!(coordinator.get_messages(agent2).len(), 1);
540        assert_eq!(coordinator.get_messages(agent3).len(), 1);
541        assert_eq!(coordinator.get_messages(agent1).len(), 0);
542    }
543}