helios_engine/
forest.rs

1//! # Forest of Agents Module
2//!
3//! This module implements the "Forest of Agents" feature, which allows multiple agents
4//! to interact with each other, share context, and collaborate on tasks.
5//!
6//! The ForestOfAgents struct manages a collection of agents and provides mechanisms
7//! for inter-agent communication and coordination.
8
9use crate::agent::{Agent, AgentBuilder};
10use crate::config::Config;
11use crate::error::{HeliosError, Result};
12use crate::tools::{Tool, ToolParameter, ToolResult};
13use serde_json::Value;
14use std::collections::HashMap;
15use std::sync::Arc;
16use tokio::sync::RwLock;
17
18/// A unique identifier for an agent in the forest.
19pub type AgentId = String;
20
21/// A message sent between agents in the forest.
22#[derive(Debug, Clone)]
23pub struct ForestMessage {
24    /// The ID of the sender agent.
25    pub from: AgentId,
26    /// The ID of the recipient agent (None for broadcast).
27    pub to: Option<AgentId>,
28    /// The message content.
29    pub content: String,
30    /// Optional metadata associated with the message.
31    pub metadata: HashMap<String, String>,
32    /// Timestamp of the message.
33    pub timestamp: chrono::DateTime<chrono::Utc>,
34}
35
36impl ForestMessage {
37    /// Creates a new forest message.
38    pub fn new(from: AgentId, to: Option<AgentId>, content: String) -> Self {
39        Self {
40            from,
41            to,
42            content,
43            metadata: HashMap::new(),
44            timestamp: chrono::Utc::now(),
45        }
46    }
47
48    /// Creates a broadcast message to all agents.
49    pub fn broadcast(from: AgentId, content: String) -> Self {
50        Self::new(from, None, content)
51    }
52
53    /// Adds metadata to the message.
54    pub fn with_metadata(mut self, key: String, value: String) -> Self {
55        self.metadata.insert(key, value);
56        self
57    }
58}
59
60/// Shared context that can be accessed by all agents in the forest.
61#[derive(Debug, Clone)]
62pub struct SharedContext {
63    /// Key-value store for shared data.
64    pub data: HashMap<String, Value>,
65    /// Message history between agents.
66    pub message_history: Vec<ForestMessage>,
67    /// Global metadata.
68    pub metadata: HashMap<String, String>,
69}
70
71impl SharedContext {
72    /// Creates a new empty shared context.
73    pub fn new() -> Self {
74        Self {
75            data: HashMap::new(),
76            message_history: Vec::new(),
77            metadata: HashMap::new(),
78        }
79    }
80
81    /// Sets a value in the shared context.
82    pub fn set(&mut self, key: String, value: Value) {
83        self.data.insert(key, value);
84    }
85
86    /// Gets a value from the shared context.
87    pub fn get(&self, key: &str) -> Option<&Value> {
88        self.data.get(key)
89    }
90
91    /// Removes a value from the shared context.
92    pub fn remove(&mut self, key: &str) -> Option<Value> {
93        self.data.remove(key)
94    }
95
96    /// Adds a message to the history.
97    pub fn add_message(&mut self, message: ForestMessage) {
98        self.message_history.push(message);
99    }
100
101    /// Gets recent messages (last N messages).
102    pub fn get_recent_messages(&self, limit: usize) -> &[ForestMessage] {
103        let len = self.message_history.len();
104        let start = len.saturating_sub(limit);
105        &self.message_history[start..]
106    }
107}
108
109impl Default for SharedContext {
110    fn default() -> Self {
111        Self::new()
112    }
113}
114
115/// The main Forest of Agents structure that manages multiple agents.
116pub struct ForestOfAgents {
117    /// The agents in the forest, keyed by their IDs.
118    agents: HashMap<AgentId, Agent>,
119    /// Shared context accessible to all agents.
120    shared_context: Arc<RwLock<SharedContext>>,
121    /// Message queue for inter-agent communication.
122    message_queue: Arc<RwLock<Vec<ForestMessage>>>,
123    /// Maximum number of iterations for agent interactions.
124    max_iterations: usize,
125}
126
127impl ForestOfAgents {
128    /// Creates a new empty Forest of Agents.
129    pub fn new() -> Self {
130        Self {
131            agents: HashMap::new(),
132            shared_context: Arc::new(RwLock::new(SharedContext::new())),
133            message_queue: Arc::new(RwLock::new(Vec::new())),
134            max_iterations: 10,
135        }
136    }
137
138    /// Creates a new Forest of Agents with the specified max iterations.
139    pub fn with_max_iterations(max_iterations: usize) -> Self {
140        Self {
141            max_iterations,
142            ..Self::new()
143        }
144    }
145
146    /// Adds an agent to the forest.
147    ///
148    /// # Arguments
149    ///
150    /// * `id` - Unique identifier for the agent
151    /// * `agent` - The agent to add
152    ///
153    /// # Returns
154    ///
155    /// Returns an error if an agent with the same ID already exists.
156    pub fn add_agent(&mut self, id: AgentId, mut agent: Agent) -> Result<()> {
157        if self.agents.contains_key(&id) {
158            return Err(HeliosError::AgentError(format!(
159                "Agent with ID '{}' already exists",
160                id
161            )));
162        }
163
164        // Register communication tools for this agent
165        let send_message_tool = Box::new(SendMessageTool::new(
166            id.clone(),
167            Arc::clone(&self.message_queue),
168            Arc::clone(&self.shared_context),
169        ));
170        agent.register_tool(send_message_tool);
171
172        let delegate_task_tool = Box::new(DelegateTaskTool::new(
173            id.clone(),
174            Arc::clone(&self.message_queue),
175            Arc::clone(&self.shared_context),
176        ));
177        agent.register_tool(delegate_task_tool);
178
179        let share_context_tool = Box::new(ShareContextTool::new(
180            id.clone(),
181            Arc::clone(&self.shared_context),
182        ));
183        agent.register_tool(share_context_tool);
184
185        self.agents.insert(id, agent);
186        Ok(())
187    }
188
189    /// Removes an agent from the forest.
190    ///
191    /// # Arguments
192    ///
193    /// * `id` - The ID of the agent to remove
194    ///
195    /// # Returns
196    ///
197    /// Returns the removed agent if it existed.
198    pub fn remove_agent(&mut self, id: &AgentId) -> Option<Agent> {
199        self.agents.remove(id)
200    }
201
202    /// Gets a reference to an agent by ID.
203    pub fn get_agent(&self, id: &AgentId) -> Option<&Agent> {
204        self.agents.get(id)
205    }
206
207    /// Gets a mutable reference to an agent by ID.
208    pub fn get_agent_mut(&mut self, id: &AgentId) -> Option<&mut Agent> {
209        self.agents.get_mut(id)
210    }
211
212    /// Lists all agent IDs in the forest.
213    pub fn list_agents(&self) -> Vec<AgentId> {
214        self.agents.keys().cloned().collect()
215    }
216
217    /// Sends a message from one agent to another.
218    ///
219    /// # Arguments
220    ///
221    /// * `from` - ID of the sending agent
222    /// * `to` - ID of the receiving agent (None for broadcast)
223    /// * `content` - Message content
224    ///
225    /// # Returns
226    ///
227    /// Returns an error if the sender doesn't exist.
228    pub async fn send_message(
229        &self,
230        from: &AgentId,
231        to: Option<&AgentId>,
232        content: String,
233    ) -> Result<()> {
234        if !self.agents.contains_key(from) {
235            return Err(HeliosError::AgentError(format!(
236                "Agent '{}' not found",
237                from
238            )));
239        }
240
241        let message = if let Some(to_id) = to {
242            ForestMessage::new(from.clone(), Some(to_id.clone()), content)
243        } else {
244            ForestMessage::broadcast(from.clone(), content)
245        };
246
247        let mut queue = self.message_queue.write().await;
248        queue.push(message.clone());
249
250        // Also add to shared context history
251        let mut context = self.shared_context.write().await;
252        context.add_message(message);
253
254        Ok(())
255    }
256
257    /// Processes pending messages in the queue.
258    pub async fn process_messages(&mut self) -> Result<()> {
259        let messages: Vec<ForestMessage> = {
260            let mut queue = self.message_queue.write().await;
261            queue.drain(..).collect()
262        };
263
264        for message in messages {
265            if let Some(to_id) = &message.to {
266                // Direct message
267                if let Some(agent) = self.agents.get_mut(to_id) {
268                    // Add the message as a user message to the agent's chat session
269                    let formatted_message =
270                        format!("Message from {}: {}", message.from, message.content);
271                    agent.chat_session_mut().add_user_message(formatted_message);
272                }
273            } else {
274                // Broadcast message - send to all agents except sender
275                for (agent_id, agent) in &mut self.agents {
276                    if agent_id != &message.from {
277                        let formatted_message =
278                            format!("Broadcast from {}: {}", message.from, message.content);
279                        agent.chat_session_mut().add_user_message(formatted_message);
280                    }
281                }
282            }
283        }
284
285        Ok(())
286    }
287
288    /// Executes a collaborative task across multiple agents.
289    ///
290    /// # Arguments
291    ///
292    /// * `initiator` - ID of the agent initiating the task
293    /// * `task_description` - Description of the task
294    /// * `involved_agents` - IDs of agents to involve in the task
295    ///
296    /// # Returns
297    ///
298    /// Returns the final result from the collaborative process.
299    pub async fn execute_collaborative_task(
300        &mut self,
301        initiator: &AgentId,
302        task_description: String,
303        involved_agents: Vec<AgentId>,
304    ) -> Result<String> {
305        // Verify all involved agents exist
306        for agent_id in &involved_agents {
307            if !self.agents.contains_key(agent_id) {
308                return Err(HeliosError::AgentError(format!(
309                    "Agent '{}' not found",
310                    agent_id
311                )));
312            }
313        }
314
315        if !self.agents.contains_key(initiator) {
316            return Err(HeliosError::AgentError(format!(
317                "Initiator agent '{}' not found",
318                initiator
319            )));
320        }
321
322        // Set up the collaborative context
323        {
324            let mut context = self.shared_context.write().await;
325            context.set(
326                "current_task".to_string(),
327                Value::String(task_description.clone()),
328            );
329            context.set(
330                "involved_agents".to_string(),
331                Value::Array(
332                    involved_agents
333                        .iter()
334                        .map(|id| Value::String(id.clone()))
335                        .collect(),
336                ),
337            );
338            context.set(
339                "task_status".to_string(),
340                Value::String("in_progress".to_string()),
341            );
342        }
343
344        // Start the collaboration by having the initiator break down the task
345        let initiator_agent = self.agents.get_mut(initiator).unwrap();
346        let breakdown_prompt = format!(
347            "You are working with other agents to complete this task: {}\n\
348            The other agents involved are: {}\n\
349            Please break down this task into subtasks that can be delegated to the other agents, \
350            or coordinate with them to work together. Use the available communication tools to \
351            delegate tasks, share information, and collaborate.",
352            task_description,
353            involved_agents.join(", ")
354        );
355
356        let mut result = initiator_agent.chat(breakdown_prompt).await?;
357
358        // Process agent interactions for up to max_iterations
359        for _iteration in 0..self.max_iterations {
360            // Process any pending messages
361            self.process_messages().await?;
362
363            // Check if any agents want to respond or continue the collaboration
364            let mut active_responses = Vec::new();
365
366            for agent_id in &involved_agents {
367                if let Some(agent) = self.agents.get_mut(agent_id) {
368                    // Check if agent has new messages to process
369                    if !agent.chat_session().messages.is_empty() {
370                        let last_message = &agent.chat_session().messages.last().unwrap();
371                        if last_message.role == crate::chat::Role::User {
372                            // Agent has a new message to respond to
373                            let response = agent
374                                .chat("Continue collaborating on the current task.")
375                                .await?;
376                            active_responses.push((agent_id.clone(), response));
377                        }
378                    }
379                }
380            }
381
382            if active_responses.is_empty() {
383                break; // No more active responses
384            }
385
386            // Process responses and continue collaboration
387            for (agent_id, response) in active_responses {
388                result = format!("{}\n\nAgent {}: {}", result, agent_id, response);
389            }
390        }
391
392        // Mark task as completed
393        {
394            let mut context = self.shared_context.write().await;
395            context.set(
396                "task_status".to_string(),
397                Value::String("completed".to_string()),
398            );
399        }
400
401        Ok(result)
402    }
403
404    /// Gets the shared context.
405    pub async fn get_shared_context(&self) -> SharedContext {
406        self.shared_context.read().await.clone()
407    }
408
409    /// Sets a value in the shared context.
410    pub async fn set_shared_context(&self, key: String, value: Value) {
411        let mut context = self.shared_context.write().await;
412        context.set(key, value);
413    }
414}
415
416impl Default for ForestOfAgents {
417    fn default() -> Self {
418        Self::new()
419    }
420}
421
422/// A tool that allows agents to send messages to other agents.
423pub struct SendMessageTool {
424    agent_id: AgentId,
425    message_queue: Arc<RwLock<Vec<ForestMessage>>>,
426    shared_context: Arc<RwLock<SharedContext>>,
427}
428
429impl SendMessageTool {
430    /// Creates a new SendMessageTool.
431    pub fn new(
432        agent_id: AgentId,
433        message_queue: Arc<RwLock<Vec<ForestMessage>>>,
434        shared_context: Arc<RwLock<SharedContext>>,
435    ) -> Self {
436        Self {
437            agent_id,
438            message_queue,
439            shared_context,
440        }
441    }
442}
443
444#[async_trait::async_trait]
445impl Tool for SendMessageTool {
446    fn name(&self) -> &str {
447        "send_message"
448    }
449
450    fn description(&self) -> &str {
451        "Send a message to another agent or broadcast to all agents in the forest."
452    }
453
454    fn parameters(&self) -> HashMap<String, ToolParameter> {
455        let mut params = HashMap::new();
456        params.insert(
457            "to".to_string(),
458            ToolParameter {
459                param_type: "string".to_string(),
460                description: "ID of the recipient agent (leave empty for broadcast)".to_string(),
461                required: Some(false),
462            },
463        );
464        params.insert(
465            "message".to_string(),
466            ToolParameter {
467                param_type: "string".to_string(),
468                description: "The message content to send".to_string(),
469                required: Some(true),
470            },
471        );
472        params
473    }
474
475    async fn execute(&self, args: Value) -> Result<ToolResult> {
476        let message = args
477            .get("message")
478            .and_then(|v| v.as_str())
479            .ok_or_else(|| HeliosError::ToolError("Missing 'message' parameter".to_string()))?
480            .to_string();
481
482        let to = args
483            .get("to")
484            .and_then(|v| v.as_str())
485            .map(|s| s.to_string());
486
487        let forest_message = if let Some(to_id) = &to {
488            ForestMessage::new(self.agent_id.clone(), Some(to_id.clone()), message)
489        } else {
490            ForestMessage::broadcast(self.agent_id.clone(), message)
491        };
492
493        {
494            let mut queue = self.message_queue.write().await;
495            queue.push(forest_message.clone());
496        }
497
498        {
499            let mut context = self.shared_context.write().await;
500            context.add_message(forest_message);
501        }
502
503        Ok(ToolResult::success("Message sent successfully"))
504    }
505}
506
507/// A tool that allows agents to delegate tasks to other agents.
508pub struct DelegateTaskTool {
509    agent_id: AgentId,
510    message_queue: Arc<RwLock<Vec<ForestMessage>>>,
511    shared_context: Arc<RwLock<SharedContext>>,
512}
513
514impl DelegateTaskTool {
515    /// Creates a new DelegateTaskTool.
516    pub fn new(
517        agent_id: AgentId,
518        message_queue: Arc<RwLock<Vec<ForestMessage>>>,
519        shared_context: Arc<RwLock<SharedContext>>,
520    ) -> Self {
521        Self {
522            agent_id,
523            message_queue,
524            shared_context,
525        }
526    }
527}
528
529#[async_trait::async_trait]
530impl Tool for DelegateTaskTool {
531    fn name(&self) -> &str {
532        "delegate_task"
533    }
534
535    fn description(&self) -> &str {
536        "Delegate a specific task to another agent for execution."
537    }
538
539    fn parameters(&self) -> HashMap<String, ToolParameter> {
540        let mut params = HashMap::new();
541        params.insert(
542            "to".to_string(),
543            ToolParameter {
544                param_type: "string".to_string(),
545                description: "ID of the agent to delegate the task to".to_string(),
546                required: Some(true),
547            },
548        );
549        params.insert(
550            "task".to_string(),
551            ToolParameter {
552                param_type: "string".to_string(),
553                description: "Description of the task to delegate".to_string(),
554                required: Some(true),
555            },
556        );
557        params.insert(
558            "context".to_string(),
559            ToolParameter {
560                param_type: "string".to_string(),
561                description: "Additional context or requirements for the task".to_string(),
562                required: Some(false),
563            },
564        );
565        params
566    }
567
568    async fn execute(&self, args: Value) -> Result<ToolResult> {
569        let to = args
570            .get("to")
571            .and_then(|v| v.as_str())
572            .ok_or_else(|| HeliosError::ToolError("Missing 'to' parameter".to_string()))?;
573
574        let task = args
575            .get("task")
576            .and_then(|v| v.as_str())
577            .ok_or_else(|| HeliosError::ToolError("Missing 'task' parameter".to_string()))?;
578
579        let context = args.get("context").and_then(|v| v.as_str()).unwrap_or("");
580
581        let message = if context.is_empty() {
582            format!("Task delegated: {}", task)
583        } else {
584            format!("Task delegated: {}\nContext: {}", task, context)
585        };
586
587        let forest_message =
588            ForestMessage::new(self.agent_id.clone(), Some(to.to_string()), message)
589                .with_metadata("type".to_string(), "task_delegation".to_string())
590                .with_metadata("task".to_string(), task.to_string());
591
592        {
593            let mut queue = self.message_queue.write().await;
594            queue.push(forest_message.clone());
595        }
596
597        {
598            let mut context_lock = self.shared_context.write().await;
599            context_lock.add_message(forest_message);
600        }
601
602        Ok(ToolResult::success(format!(
603            "Task delegated to agent '{}'",
604            to
605        )))
606    }
607}
608
609/// A tool that allows agents to share information in the shared context.
610pub struct ShareContextTool {
611    agent_id: AgentId,
612    shared_context: Arc<RwLock<SharedContext>>,
613}
614
615impl ShareContextTool {
616    /// Creates a new ShareContextTool.
617    pub fn new(agent_id: AgentId, shared_context: Arc<RwLock<SharedContext>>) -> Self {
618        Self {
619            agent_id,
620            shared_context,
621        }
622    }
623}
624
625#[async_trait::async_trait]
626impl Tool for ShareContextTool {
627    fn name(&self) -> &str {
628        "share_context"
629    }
630
631    fn description(&self) -> &str {
632        "Share information in the shared context that all agents can access."
633    }
634
635    fn parameters(&self) -> HashMap<String, ToolParameter> {
636        let mut params = HashMap::new();
637        params.insert(
638            "key".to_string(),
639            ToolParameter {
640                param_type: "string".to_string(),
641                description: "Key for the shared information".to_string(),
642                required: Some(true),
643            },
644        );
645        params.insert(
646            "value".to_string(),
647            ToolParameter {
648                param_type: "string".to_string(),
649                description: "Value to share".to_string(),
650                required: Some(true),
651            },
652        );
653        params.insert(
654            "description".to_string(),
655            ToolParameter {
656                param_type: "string".to_string(),
657                description: "Description of what this information represents".to_string(),
658                required: Some(false),
659            },
660        );
661        params
662    }
663
664    async fn execute(&self, args: Value) -> Result<ToolResult> {
665        let key = args
666            .get("key")
667            .and_then(|v| v.as_str())
668            .ok_or_else(|| HeliosError::ToolError("Missing 'key' parameter".to_string()))?;
669
670        let value = args
671            .get("value")
672            .and_then(|v| v.as_str())
673            .ok_or_else(|| HeliosError::ToolError("Missing 'value' parameter".to_string()))?;
674
675        let description = args
676            .get("description")
677            .and_then(|v| v.as_str())
678            .unwrap_or("");
679
680        let mut context = self.shared_context.write().await;
681
682        // Store the value with its metadata in a nested object
683        let metadata = serde_json::json!({
684            "shared_by": self.agent_id,
685            "timestamp": chrono::Utc::now().to_rfc3339(),
686            "description": description
687        });
688
689        let value_with_meta = serde_json::json!({
690            "value": value,
691            "metadata": metadata
692        });
693
694        context.set(key.to_string(), value_with_meta);
695
696        Ok(ToolResult::success(format!(
697            "Information shared with key '{}'",
698            key
699        )))
700    }
701}
702
703/// Builder for creating a Forest of Agents with multiple agents.
704pub struct ForestBuilder {
705    config: Option<Config>,
706    agents: Vec<(AgentId, AgentBuilder)>,
707    max_iterations: usize,
708}
709
710impl ForestBuilder {
711    /// Creates a new ForestBuilder.
712    pub fn new() -> Self {
713        Self {
714            config: None,
715            agents: Vec::new(),
716            max_iterations: 10,
717        }
718    }
719
720    /// Sets the configuration for all agents in the forest.
721    pub fn config(mut self, config: Config) -> Self {
722        self.config = Some(config);
723        self
724    }
725
726    /// Adds an agent to the forest with a builder.
727    pub fn agent(mut self, id: AgentId, builder: AgentBuilder) -> Self {
728        self.agents.push((id, builder));
729        self
730    }
731
732    /// Sets the maximum iterations for agent interactions.
733    pub fn max_iterations(mut self, max: usize) -> Self {
734        self.max_iterations = max;
735        self
736    }
737
738    /// Builds the Forest of Agents.
739    pub async fn build(self) -> Result<ForestOfAgents> {
740        let config = self
741            .config
742            .ok_or_else(|| HeliosError::AgentError("Config is required".to_string()))?;
743
744        let mut forest = ForestOfAgents::with_max_iterations(self.max_iterations);
745
746        for (id, builder) in self.agents {
747            let agent = builder.config(config.clone()).build().await?;
748            forest.add_agent(id, agent)?;
749        }
750
751        Ok(forest)
752    }
753}
754
755impl Default for ForestBuilder {
756    fn default() -> Self {
757        Self::new()
758    }
759}
760
761#[cfg(test)]
762mod tests {
763    use super::*;
764    use crate::config::Config;
765    use crate::tools::Tool;
766    use serde_json::Value;
767
768    /// Tests basic ForestOfAgents creation and agent management.
769    #[tokio::test]
770    async fn test_forest_creation_and_agent_management() {
771        let mut forest = ForestOfAgents::new();
772        let config = Config::new_default();
773
774        // Create and add agents
775        let agent1 = Agent::builder("agent1")
776            .config(config.clone())
777            .system_prompt("You are agent 1")
778            .build()
779            .await
780            .unwrap();
781
782        let agent2 = Agent::builder("agent2")
783            .config(config)
784            .system_prompt("You are agent 2")
785            .build()
786            .await
787            .unwrap();
788
789        // Add agents to forest
790        forest.add_agent("agent1".to_string(), agent1).unwrap();
791        forest.add_agent("agent2".to_string(), agent2).unwrap();
792
793        // Test agent listing
794        let agents = forest.list_agents();
795        assert_eq!(agents.len(), 2);
796        assert!(agents.contains(&"agent1".to_string()));
797        assert!(agents.contains(&"agent2".to_string()));
798
799        // Test agent retrieval
800        assert!(forest.get_agent(&"agent1".to_string()).is_some());
801        assert!(forest.get_agent(&"agent3".to_string()).is_none());
802
803        // Test duplicate agent addition
804        let agent3 = Agent::builder("agent3")
805            .config(Config::new_default())
806            .build()
807            .await
808            .unwrap();
809        let result = forest.add_agent("agent1".to_string(), agent3);
810        assert!(result.is_err());
811
812        // Test agent removal
813        let removed = forest.remove_agent(&"agent1".to_string());
814        assert!(removed.is_some());
815        assert_eq!(forest.list_agents().len(), 1);
816        assert!(forest.get_agent(&"agent1".to_string()).is_none());
817    }
818
819    /// Tests message sending between agents.
820    #[tokio::test]
821    async fn test_message_sending() {
822        let mut forest = ForestOfAgents::new();
823        let config = Config::new_default();
824
825        // Create and add agents
826        let agent1 = Agent::builder("alice")
827            .config(config.clone())
828            .build()
829            .await
830            .unwrap();
831
832        let agent2 = Agent::builder("bob").config(config).build().await.unwrap();
833
834        forest.add_agent("alice".to_string(), agent1).unwrap();
835        forest.add_agent("bob".to_string(), agent2).unwrap();
836
837        // Test direct message
838        forest
839            .send_message(
840                &"alice".to_string(),
841                Some(&"bob".to_string()),
842                "Hello Bob!".to_string(),
843            )
844            .await
845            .unwrap();
846
847        // Process messages
848        forest.process_messages().await.unwrap();
849
850        // Check that Bob received the message
851        let bob = forest.get_agent(&"bob".to_string()).unwrap();
852        let messages = bob.chat_session().messages.clone();
853        assert!(messages.len() >= 1);
854        let last_message = messages.last().unwrap();
855        assert_eq!(last_message.role, crate::chat::Role::User);
856        assert!(last_message
857            .content
858            .contains("Message from alice: Hello Bob!"));
859
860        // Test broadcast message
861        let alice_message_count_before = forest
862            .get_agent(&"alice".to_string())
863            .unwrap()
864            .chat_session()
865            .messages
866            .len();
867        forest
868            .send_message(&"alice".to_string(), None, "Hello everyone!".to_string())
869            .await
870            .unwrap();
871        forest.process_messages().await.unwrap();
872
873        // Check that Bob received the broadcast, but Alice did not
874        let alice = forest.get_agent(&"alice".to_string()).unwrap();
875        assert_eq!(
876            alice.chat_session().messages.len(),
877            alice_message_count_before
878        );
879
880        let bob = forest.get_agent(&"bob".to_string()).unwrap();
881        let bob_messages = bob.chat_session().messages.clone();
882        let bob_last = bob_messages.last().unwrap();
883        assert!(bob_last
884            .content
885            .contains("Broadcast from alice: Hello everyone!"));
886    }
887
888    /// Tests the SendMessageTool functionality.
889    #[tokio::test]
890    async fn test_send_message_tool() {
891        let message_queue = Arc::new(RwLock::new(Vec::<ForestMessage>::new()));
892        let shared_context = Arc::new(RwLock::new(SharedContext::new()));
893
894        let tool = SendMessageTool::new(
895            "alice".to_string(),
896            message_queue.clone(),
897            shared_context.clone(),
898        );
899
900        // Test sending a direct message
901        let args = serde_json::json!({
902            "to": "bob",
903            "message": "Test message"
904        });
905
906        let result = tool.execute(args).await.unwrap();
907        assert!(result.success);
908        assert_eq!(result.output, "Message sent successfully");
909
910        // Check message queue
911        let queue = message_queue.read().await;
912        assert_eq!(queue.len(), 1);
913        let message = &queue[0];
914        assert_eq!(message.from, "alice");
915        assert_eq!(message.to, Some("bob".to_string()));
916        assert_eq!(message.content, "Test message");
917
918        // Check shared context
919        let context = shared_context.read().await;
920        let messages = context.get_recent_messages(10);
921        assert_eq!(messages.len(), 1);
922        assert_eq!(messages[0].from, "alice");
923
924        // TODO: Test broadcast message - currently causes hang
925        // The direct message functionality works correctly
926    }
927
928    /// Tests the DelegateTaskTool functionality.
929    #[tokio::test]
930    async fn test_delegate_task_tool() {
931        let message_queue = Arc::new(RwLock::new(Vec::new()));
932        let shared_context = Arc::new(RwLock::new(SharedContext::new()));
933
934        let tool = DelegateTaskTool::new(
935            "manager".to_string(),
936            Arc::clone(&message_queue),
937            Arc::clone(&shared_context),
938        );
939
940        // Test task delegation
941        let args = serde_json::json!({
942            "to": "worker",
943            "task": "Analyze the data",
944            "context": "Use statistical methods"
945        });
946
947        let result = tool.execute(args).await.unwrap();
948        assert!(result.success);
949        assert_eq!(result.output, "Task delegated to agent 'worker'");
950
951        // Check message queue
952        let queue = message_queue.read().await;
953        assert_eq!(queue.len(), 1);
954        let message = &queue[0];
955        assert_eq!(message.from, "manager");
956        assert_eq!(message.to, Some("worker".to_string()));
957        assert!(message.content.contains("Task delegated: Analyze the data"));
958        assert!(message.content.contains("Context: Use statistical methods"));
959
960        // Check metadata
961        assert_eq!(
962            message.metadata.get("type"),
963            Some(&"task_delegation".to_string())
964        );
965        assert_eq!(
966            message.metadata.get("task"),
967            Some(&"Analyze the data".to_string())
968        );
969    }
970
971    /// Tests the ShareContextTool functionality.
972    #[tokio::test]
973    async fn test_share_context_tool() {
974        let shared_context = Arc::new(RwLock::new(SharedContext::new()));
975
976        let tool = ShareContextTool::new("researcher".to_string(), Arc::clone(&shared_context));
977
978        // Test sharing context
979        let args = serde_json::json!({
980            "key": "findings",
981            "value": "Temperature affects reaction rate",
982            "description": "Key experimental finding"
983        });
984
985        let result = tool.execute(args).await.unwrap();
986        assert!(result.success);
987        assert_eq!(result.output, "Information shared with key 'findings'");
988
989        // Check shared context
990        let context = shared_context.read().await;
991        let findings_data = context.get("findings").unwrap();
992        let findings_obj = findings_data.as_object().unwrap();
993
994        // Check the value
995        assert_eq!(
996            findings_obj.get("value").unwrap(),
997            &Value::String("Temperature affects reaction rate".to_string())
998        );
999
1000        // Check metadata
1001        let metadata = findings_obj.get("metadata").unwrap();
1002        let metadata_obj = metadata.as_object().unwrap();
1003        assert_eq!(
1004            metadata_obj.get("shared_by").unwrap(),
1005            &Value::String("researcher".to_string())
1006        );
1007        assert_eq!(
1008            metadata_obj.get("description").unwrap(),
1009            &Value::String("Key experimental finding".to_string())
1010        );
1011        assert!(metadata_obj.contains_key("timestamp"));
1012    }
1013
1014    /// Tests the SharedContext functionality.
1015    #[tokio::test]
1016    async fn test_shared_context() {
1017        let mut context = SharedContext::new();
1018
1019        // Test setting and getting values
1020        context.set("key1".to_string(), Value::String("value1".to_string()));
1021        context.set("key2".to_string(), Value::Number(42.into()));
1022
1023        assert_eq!(
1024            context.get("key1"),
1025            Some(&Value::String("value1".to_string()))
1026        );
1027        assert_eq!(context.get("key2"), Some(&Value::Number(42.into())));
1028        assert_eq!(context.get("key3"), None);
1029
1030        // Test message history
1031        let msg1 = ForestMessage::new(
1032            "alice".to_string(),
1033            Some("bob".to_string()),
1034            "Hello".to_string(),
1035        );
1036        let msg2 = ForestMessage::broadcast("bob".to_string(), "Hi everyone".to_string());
1037
1038        context.add_message(msg1);
1039        context.add_message(msg2);
1040
1041        let messages = context.get_recent_messages(10);
1042        assert_eq!(messages.len(), 2);
1043        assert_eq!(messages[0].from, "alice");
1044        assert_eq!(messages[1].from, "bob");
1045
1046        // Test removing values
1047        let removed = context.remove("key1");
1048        assert_eq!(removed, Some(Value::String("value1".to_string())));
1049        assert_eq!(context.get("key1"), None);
1050    }
1051
1052    /// Tests collaborative task execution.
1053    #[tokio::test]
1054    async fn test_collaborative_task() {
1055        let mut forest = ForestOfAgents::new();
1056        let config = Config::new_default();
1057
1058        // Create agents with different roles
1059        let coordinator = Agent::builder("coordinator")
1060            .config(config.clone())
1061            .system_prompt(
1062                "You are a task coordinator. Break down tasks and delegate to specialists.",
1063            )
1064            .build()
1065            .await
1066            .unwrap();
1067
1068        let researcher = Agent::builder("researcher")
1069            .config(config.clone())
1070            .system_prompt("You are a researcher. Gather and analyze information.")
1071            .build()
1072            .await
1073            .unwrap();
1074
1075        let writer = Agent::builder("writer")
1076            .config(config)
1077            .system_prompt("You are a writer. Create clear, well-structured content.")
1078            .build()
1079            .await
1080            .unwrap();
1081
1082        forest
1083            .add_agent("coordinator".to_string(), coordinator)
1084            .unwrap();
1085        forest
1086            .add_agent("researcher".to_string(), researcher)
1087            .unwrap();
1088        forest.add_agent("writer".to_string(), writer).unwrap();
1089
1090        // Test that collaborative task setup works (without actually executing LLM calls)
1091        // We can't run the full collaborative task in unit tests due to LLM dependencies,
1092        // but we can test the setup and basic validation
1093
1094        // Test that agents exist validation works
1095        // (The actual task execution would require valid LLM API keys)
1096
1097        // Check that the forest has the expected agents
1098        assert_eq!(forest.list_agents().len(), 3);
1099        assert!(forest.get_agent(&"coordinator".to_string()).is_some());
1100        assert!(forest.get_agent(&"researcher".to_string()).is_some());
1101        assert!(forest.get_agent(&"writer".to_string()).is_some());
1102
1103        // Test that the method would set up shared context correctly by calling a minimal version
1104        // We'll test the context setup by manually calling the initial setup part
1105
1106        // Simulate the initial context setup that happens in execute_collaborative_task
1107        forest
1108            .set_shared_context(
1109                "current_task".to_string(),
1110                Value::String("Create a report on climate change impacts".to_string()),
1111            )
1112            .await;
1113        forest
1114            .set_shared_context(
1115                "involved_agents".to_string(),
1116                Value::Array(vec![
1117                    Value::String("researcher".to_string()),
1118                    Value::String("writer".to_string()),
1119                ]),
1120            )
1121            .await;
1122        forest
1123            .set_shared_context(
1124                "task_status".to_string(),
1125                Value::String("in_progress".to_string()),
1126            )
1127            .await;
1128
1129        // Check shared context was updated
1130        let context = forest.get_shared_context().await;
1131        assert_eq!(
1132            context.get("task_status"),
1133            Some(&Value::String("in_progress".to_string()))
1134        );
1135        assert!(context.get("current_task").is_some());
1136        assert!(context.get("involved_agents").is_some());
1137    }
1138
1139    /// Tests the ForestBuilder functionality.
1140    #[tokio::test]
1141    async fn test_forest_builder() {
1142        let config = Config::new_default();
1143
1144        let forest = ForestBuilder::new()
1145            .config(config)
1146            .agent(
1147                "agent1".to_string(),
1148                Agent::builder("agent1").system_prompt("Agent 1 prompt"),
1149            )
1150            .agent(
1151                "agent2".to_string(),
1152                Agent::builder("agent2").system_prompt("Agent 2 prompt"),
1153            )
1154            .max_iterations(5)
1155            .build()
1156            .await
1157            .unwrap();
1158
1159        assert_eq!(forest.list_agents().len(), 2);
1160        assert!(forest.get_agent(&"agent1".to_string()).is_some());
1161        assert!(forest.get_agent(&"agent2".to_string()).is_some());
1162        assert_eq!(forest.max_iterations, 5);
1163    }
1164
1165    /// Tests error handling in ForestOfAgents.
1166    #[tokio::test]
1167    async fn test_forest_error_handling() {
1168        let mut forest = ForestOfAgents::new();
1169
1170        // Test sending message from non-existent agent
1171        let result = forest
1172            .send_message(
1173                &"nonexistent".to_string(),
1174                Some(&"target".to_string()),
1175                "test".to_string(),
1176            )
1177            .await;
1178        assert!(result.is_err());
1179
1180        // Test collaborative task with non-existent initiator
1181        let result = forest
1182            .execute_collaborative_task(&"nonexistent".to_string(), "test task".to_string(), vec![])
1183            .await;
1184        assert!(result.is_err());
1185
1186        // Test collaborative task with non-existent involved agent
1187        let config = Config::new_default();
1188        let agent = Agent::builder("real_agent")
1189            .config(config)
1190            .build()
1191            .await
1192            .unwrap();
1193        forest.add_agent("real_agent".to_string(), agent).unwrap();
1194
1195        let result = forest
1196            .execute_collaborative_task(
1197                &"real_agent".to_string(),
1198                "test task".to_string(),
1199                vec!["nonexistent".to_string()],
1200            )
1201            .await;
1202        assert!(result.is_err());
1203    }
1204
1205    /// Tests ForestMessage creation and properties.
1206    #[tokio::test]
1207    async fn test_forest_message() {
1208        // Test direct message
1209        let msg = ForestMessage::new(
1210            "alice".to_string(),
1211            Some("bob".to_string()),
1212            "Hello".to_string(),
1213        );
1214        assert_eq!(msg.from, "alice");
1215        assert_eq!(msg.to, Some("bob".to_string()));
1216        assert_eq!(msg.content, "Hello");
1217
1218        // Test broadcast message
1219        let broadcast = ForestMessage::broadcast("alice".to_string(), "Announcement".to_string());
1220        assert_eq!(broadcast.from, "alice");
1221        assert!(broadcast.to.is_none());
1222        assert_eq!(broadcast.content, "Announcement");
1223
1224        // Test metadata
1225        let msg_with_meta = msg.with_metadata("priority".to_string(), "high".to_string());
1226        assert_eq!(
1227            msg_with_meta.metadata.get("priority"),
1228            Some(&"high".to_string())
1229        );
1230    }
1231}