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 handle the task
345        let initiator_agent = self.agents.get_mut(initiator).unwrap();
346        let breakdown_prompt = format!(
347            "Task: {}\n\n\
348            Available team members: {}\n\n\
349            If this task is simple, complete it yourself. If it's complex and requires \
350            specialized expertise, you can use the 'delegate_task' tool to assign work to \
351            the appropriate team members. Provide a complete final answer.",
352            task_description,
353            involved_agents.join(", ")
354        );
355
356        let result = initiator_agent.chat(breakdown_prompt).await?;
357
358        // Process any messages that were generated and trigger agent responses
359        self.process_messages_and_trigger_responses(&involved_agents)
360            .await?;
361
362        // Mark task as completed
363        {
364            let mut context = self.shared_context.write().await;
365            context.set(
366                "task_status".to_string(),
367                Value::String("completed".to_string()),
368            );
369        }
370
371        Ok(result)
372    }
373
374    /// Processes pending messages and triggers responses from agents.
375    ///
376    /// This method iterates through pending messages, delivers them to recipient agents,
377    /// and triggers their responses. It continues until no more messages are generated
378    /// or max_iterations is reached.
379    async fn process_messages_and_trigger_responses(
380        &mut self,
381        involved_agents: &[AgentId],
382    ) -> Result<()> {
383        let mut iteration = 0;
384
385        while iteration < self.max_iterations {
386            // First, deliver all pending messages
387            self.process_messages().await?;
388
389            // Track agents that received new messages and need to respond
390            let mut agents_to_respond = Vec::new();
391
392            for agent_id in involved_agents {
393                if let Some(agent) = self.agents.get(agent_id) {
394                    let messages = &agent.chat_session().messages;
395                    if !messages.is_empty() {
396                        let last_message = messages.last().unwrap();
397                        // If the last message is from a user (another agent), this agent should respond
398                        if last_message.role == crate::chat::Role::User {
399                            agents_to_respond.push(agent_id.clone());
400                        }
401                    }
402                }
403            }
404
405            // If no agents need to respond, we're done
406            if agents_to_respond.is_empty() {
407                break;
408            }
409
410            // Have each agent respond to their messages
411            for agent_id in agents_to_respond {
412                if let Some(agent) = self.agents.get_mut(&agent_id) {
413                    // Agent processes the message and may use tools to delegate or send messages
414                    let _response = agent.chat("").await?;
415                }
416            }
417
418            iteration += 1;
419        }
420
421        Ok(())
422    }
423
424    /// Gets the shared context.
425    pub async fn get_shared_context(&self) -> SharedContext {
426        self.shared_context.read().await.clone()
427    }
428
429    /// Sets a value in the shared context.
430    pub async fn set_shared_context(&self, key: String, value: Value) {
431        let mut context = self.shared_context.write().await;
432        context.set(key, value);
433    }
434}
435
436impl Default for ForestOfAgents {
437    fn default() -> Self {
438        Self::new()
439    }
440}
441
442/// A tool that allows agents to send messages to other agents.
443pub struct SendMessageTool {
444    agent_id: AgentId,
445    message_queue: Arc<RwLock<Vec<ForestMessage>>>,
446    shared_context: Arc<RwLock<SharedContext>>,
447}
448
449impl SendMessageTool {
450    /// Creates a new SendMessageTool.
451    pub fn new(
452        agent_id: AgentId,
453        message_queue: Arc<RwLock<Vec<ForestMessage>>>,
454        shared_context: Arc<RwLock<SharedContext>>,
455    ) -> Self {
456        Self {
457            agent_id,
458            message_queue,
459            shared_context,
460        }
461    }
462}
463
464#[async_trait::async_trait]
465impl Tool for SendMessageTool {
466    fn name(&self) -> &str {
467        "send_message"
468    }
469
470    fn description(&self) -> &str {
471        "Send a message to another agent or broadcast to all agents in the forest."
472    }
473
474    fn parameters(&self) -> HashMap<String, ToolParameter> {
475        let mut params = HashMap::new();
476        params.insert(
477            "to".to_string(),
478            ToolParameter {
479                param_type: "string".to_string(),
480                description: "ID of the recipient agent (leave empty for broadcast)".to_string(),
481                required: Some(false),
482            },
483        );
484        params.insert(
485            "message".to_string(),
486            ToolParameter {
487                param_type: "string".to_string(),
488                description: "The message content to send".to_string(),
489                required: Some(true),
490            },
491        );
492        params
493    }
494
495    async fn execute(&self, args: Value) -> Result<ToolResult> {
496        let message = args
497            .get("message")
498            .and_then(|v| v.as_str())
499            .ok_or_else(|| HeliosError::ToolError("Missing 'message' parameter".to_string()))?
500            .to_string();
501
502        let to = args
503            .get("to")
504            .and_then(|v| v.as_str())
505            .map(|s| s.to_string());
506
507        let forest_message = if let Some(to_id) = &to {
508            ForestMessage::new(self.agent_id.clone(), Some(to_id.clone()), message)
509        } else {
510            ForestMessage::broadcast(self.agent_id.clone(), message)
511        };
512
513        {
514            let mut queue = self.message_queue.write().await;
515            queue.push(forest_message.clone());
516        }
517
518        {
519            let mut context = self.shared_context.write().await;
520            context.add_message(forest_message);
521        }
522
523        Ok(ToolResult::success("Message sent successfully"))
524    }
525}
526
527/// A tool that allows agents to delegate tasks to other agents.
528pub struct DelegateTaskTool {
529    agent_id: AgentId,
530    message_queue: Arc<RwLock<Vec<ForestMessage>>>,
531    shared_context: Arc<RwLock<SharedContext>>,
532}
533
534impl DelegateTaskTool {
535    /// Creates a new DelegateTaskTool.
536    pub fn new(
537        agent_id: AgentId,
538        message_queue: Arc<RwLock<Vec<ForestMessage>>>,
539        shared_context: Arc<RwLock<SharedContext>>,
540    ) -> Self {
541        Self {
542            agent_id,
543            message_queue,
544            shared_context,
545        }
546    }
547}
548
549#[async_trait::async_trait]
550impl Tool for DelegateTaskTool {
551    fn name(&self) -> &str {
552        "delegate_task"
553    }
554
555    fn description(&self) -> &str {
556        "Delegate a specific task to another agent for execution."
557    }
558
559    fn parameters(&self) -> HashMap<String, ToolParameter> {
560        let mut params = HashMap::new();
561        params.insert(
562            "to".to_string(),
563            ToolParameter {
564                param_type: "string".to_string(),
565                description: "ID of the agent to delegate the task to".to_string(),
566                required: Some(true),
567            },
568        );
569        params.insert(
570            "task".to_string(),
571            ToolParameter {
572                param_type: "string".to_string(),
573                description: "Description of the task to delegate".to_string(),
574                required: Some(true),
575            },
576        );
577        params.insert(
578            "context".to_string(),
579            ToolParameter {
580                param_type: "string".to_string(),
581                description: "Additional context or requirements for the task".to_string(),
582                required: Some(false),
583            },
584        );
585        params
586    }
587
588    async fn execute(&self, args: Value) -> Result<ToolResult> {
589        let to = args
590            .get("to")
591            .and_then(|v| v.as_str())
592            .ok_or_else(|| HeliosError::ToolError("Missing 'to' parameter".to_string()))?;
593
594        let task = args
595            .get("task")
596            .and_then(|v| v.as_str())
597            .ok_or_else(|| HeliosError::ToolError("Missing 'task' parameter".to_string()))?;
598
599        let context = args.get("context").and_then(|v| v.as_str()).unwrap_or("");
600
601        let message = if context.is_empty() {
602            format!("Task delegated: {}", task)
603        } else {
604            format!("Task delegated: {}\nContext: {}", task, context)
605        };
606
607        let forest_message =
608            ForestMessage::new(self.agent_id.clone(), Some(to.to_string()), message)
609                .with_metadata("type".to_string(), "task_delegation".to_string())
610                .with_metadata("task".to_string(), task.to_string());
611
612        {
613            let mut queue = self.message_queue.write().await;
614            queue.push(forest_message.clone());
615        }
616
617        {
618            let mut context_lock = self.shared_context.write().await;
619            context_lock.add_message(forest_message);
620        }
621
622        Ok(ToolResult::success(format!(
623            "Task delegated to agent '{}'",
624            to
625        )))
626    }
627}
628
629/// A tool that allows agents to share information in the shared context.
630pub struct ShareContextTool {
631    agent_id: AgentId,
632    shared_context: Arc<RwLock<SharedContext>>,
633}
634
635impl ShareContextTool {
636    /// Creates a new ShareContextTool.
637    pub fn new(agent_id: AgentId, shared_context: Arc<RwLock<SharedContext>>) -> Self {
638        Self {
639            agent_id,
640            shared_context,
641        }
642    }
643}
644
645#[async_trait::async_trait]
646impl Tool for ShareContextTool {
647    fn name(&self) -> &str {
648        "share_context"
649    }
650
651    fn description(&self) -> &str {
652        "Share information in the shared context that all agents can access."
653    }
654
655    fn parameters(&self) -> HashMap<String, ToolParameter> {
656        let mut params = HashMap::new();
657        params.insert(
658            "key".to_string(),
659            ToolParameter {
660                param_type: "string".to_string(),
661                description: "Key for the shared information".to_string(),
662                required: Some(true),
663            },
664        );
665        params.insert(
666            "value".to_string(),
667            ToolParameter {
668                param_type: "string".to_string(),
669                description: "Value to share".to_string(),
670                required: Some(true),
671            },
672        );
673        params.insert(
674            "description".to_string(),
675            ToolParameter {
676                param_type: "string".to_string(),
677                description: "Description of what this information represents".to_string(),
678                required: Some(false),
679            },
680        );
681        params
682    }
683
684    async fn execute(&self, args: Value) -> Result<ToolResult> {
685        let key = args
686            .get("key")
687            .and_then(|v| v.as_str())
688            .ok_or_else(|| HeliosError::ToolError("Missing 'key' parameter".to_string()))?;
689
690        let value = args
691            .get("value")
692            .and_then(|v| v.as_str())
693            .ok_or_else(|| HeliosError::ToolError("Missing 'value' parameter".to_string()))?;
694
695        let description = args
696            .get("description")
697            .and_then(|v| v.as_str())
698            .unwrap_or("");
699
700        let mut context = self.shared_context.write().await;
701
702        // Store the value with its metadata in a nested object
703        let metadata = serde_json::json!({
704            "shared_by": self.agent_id,
705            "timestamp": chrono::Utc::now().to_rfc3339(),
706            "description": description
707        });
708
709        let value_with_meta = serde_json::json!({
710            "value": value,
711            "metadata": metadata
712        });
713
714        context.set(key.to_string(), value_with_meta);
715
716        Ok(ToolResult::success(format!(
717            "Information shared with key '{}'",
718            key
719        )))
720    }
721}
722
723/// Builder for creating a Forest of Agents with multiple agents.
724pub struct ForestBuilder {
725    config: Option<Config>,
726    agents: Vec<(AgentId, AgentBuilder)>,
727    max_iterations: usize,
728}
729
730impl ForestBuilder {
731    /// Creates a new ForestBuilder.
732    pub fn new() -> Self {
733        Self {
734            config: None,
735            agents: Vec::new(),
736            max_iterations: 10,
737        }
738    }
739
740    /// Sets the configuration for all agents in the forest.
741    pub fn config(mut self, config: Config) -> Self {
742        self.config = Some(config);
743        self
744    }
745
746    /// Adds an agent to the forest with a builder.
747    pub fn agent(mut self, id: AgentId, builder: AgentBuilder) -> Self {
748        self.agents.push((id, builder));
749        self
750    }
751
752    /// Sets the maximum iterations for agent interactions.
753    pub fn max_iterations(mut self, max: usize) -> Self {
754        self.max_iterations = max;
755        self
756    }
757
758    /// Builds the Forest of Agents.
759    pub async fn build(self) -> Result<ForestOfAgents> {
760        let config = self
761            .config
762            .ok_or_else(|| HeliosError::AgentError("Config is required".to_string()))?;
763
764        let mut forest = ForestOfAgents::with_max_iterations(self.max_iterations);
765
766        for (id, builder) in self.agents {
767            let agent = builder.config(config.clone()).build().await?;
768            forest.add_agent(id, agent)?;
769        }
770
771        Ok(forest)
772    }
773}
774
775impl Default for ForestBuilder {
776    fn default() -> Self {
777        Self::new()
778    }
779}
780
781#[cfg(test)]
782mod tests {
783    use super::*;
784    use crate::config::Config;
785    use crate::tools::Tool;
786    use serde_json::Value;
787
788    /// Tests basic ForestOfAgents creation and agent management.
789    #[tokio::test]
790    async fn test_forest_creation_and_agent_management() {
791        let mut forest = ForestOfAgents::new();
792        let config = Config::new_default();
793
794        // Create and add agents
795        let agent1 = Agent::builder("agent1")
796            .config(config.clone())
797            .system_prompt("You are agent 1")
798            .build()
799            .await
800            .unwrap();
801
802        let agent2 = Agent::builder("agent2")
803            .config(config)
804            .system_prompt("You are agent 2")
805            .build()
806            .await
807            .unwrap();
808
809        // Add agents to forest
810        forest.add_agent("agent1".to_string(), agent1).unwrap();
811        forest.add_agent("agent2".to_string(), agent2).unwrap();
812
813        // Test agent listing
814        let agents = forest.list_agents();
815        assert_eq!(agents.len(), 2);
816        assert!(agents.contains(&"agent1".to_string()));
817        assert!(agents.contains(&"agent2".to_string()));
818
819        // Test agent retrieval
820        assert!(forest.get_agent(&"agent1".to_string()).is_some());
821        assert!(forest.get_agent(&"agent3".to_string()).is_none());
822
823        // Test duplicate agent addition
824        let agent3 = Agent::builder("agent3")
825            .config(Config::new_default())
826            .build()
827            .await
828            .unwrap();
829        let result = forest.add_agent("agent1".to_string(), agent3);
830        assert!(result.is_err());
831
832        // Test agent removal
833        let removed = forest.remove_agent(&"agent1".to_string());
834        assert!(removed.is_some());
835        assert_eq!(forest.list_agents().len(), 1);
836        assert!(forest.get_agent(&"agent1".to_string()).is_none());
837    }
838
839    /// Tests message sending between agents.
840    #[tokio::test]
841    async fn test_message_sending() {
842        let mut forest = ForestOfAgents::new();
843        let config = Config::new_default();
844
845        // Create and add agents
846        let agent1 = Agent::builder("alice")
847            .config(config.clone())
848            .build()
849            .await
850            .unwrap();
851
852        let agent2 = Agent::builder("bob").config(config).build().await.unwrap();
853
854        forest.add_agent("alice".to_string(), agent1).unwrap();
855        forest.add_agent("bob".to_string(), agent2).unwrap();
856
857        // Test direct message
858        forest
859            .send_message(
860                &"alice".to_string(),
861                Some(&"bob".to_string()),
862                "Hello Bob!".to_string(),
863            )
864            .await
865            .unwrap();
866
867        // Process messages
868        forest.process_messages().await.unwrap();
869
870        // Check that Bob received the message
871        let bob = forest.get_agent(&"bob".to_string()).unwrap();
872        let messages = bob.chat_session().messages.clone();
873        assert!(messages.len() >= 1);
874        let last_message = messages.last().unwrap();
875        assert_eq!(last_message.role, crate::chat::Role::User);
876        assert!(last_message
877            .content
878            .contains("Message from alice: Hello Bob!"));
879
880        // Test broadcast message
881        let alice_message_count_before = forest
882            .get_agent(&"alice".to_string())
883            .unwrap()
884            .chat_session()
885            .messages
886            .len();
887        forest
888            .send_message(&"alice".to_string(), None, "Hello everyone!".to_string())
889            .await
890            .unwrap();
891        forest.process_messages().await.unwrap();
892
893        // Check that Bob received the broadcast, but Alice did not
894        let alice = forest.get_agent(&"alice".to_string()).unwrap();
895        assert_eq!(
896            alice.chat_session().messages.len(),
897            alice_message_count_before
898        );
899
900        let bob = forest.get_agent(&"bob".to_string()).unwrap();
901        let bob_messages = bob.chat_session().messages.clone();
902        let bob_last = bob_messages.last().unwrap();
903        assert!(bob_last
904            .content
905            .contains("Broadcast from alice: Hello everyone!"));
906    }
907
908    /// Tests the SendMessageTool functionality.
909    #[tokio::test]
910    async fn test_send_message_tool() {
911        let message_queue = Arc::new(RwLock::new(Vec::<ForestMessage>::new()));
912        let shared_context = Arc::new(RwLock::new(SharedContext::new()));
913
914        let tool = SendMessageTool::new(
915            "alice".to_string(),
916            message_queue.clone(),
917            shared_context.clone(),
918        );
919
920        // Test sending a direct message
921        let args = serde_json::json!({
922            "to": "bob",
923            "message": "Test message"
924        });
925
926        let result = tool.execute(args).await.unwrap();
927        assert!(result.success);
928        assert_eq!(result.output, "Message sent successfully");
929
930        // Check message queue
931        let queue = message_queue.read().await;
932        assert_eq!(queue.len(), 1);
933        let message = &queue[0];
934        assert_eq!(message.from, "alice");
935        assert_eq!(message.to, Some("bob".to_string()));
936        assert_eq!(message.content, "Test message");
937
938        // Check shared context
939        let context = shared_context.read().await;
940        let messages = context.get_recent_messages(10);
941        assert_eq!(messages.len(), 1);
942        assert_eq!(messages[0].from, "alice");
943
944        // TODO: Test broadcast message - currently causes hang
945        // The direct message functionality works correctly
946    }
947
948    /// Tests the DelegateTaskTool functionality.
949    #[tokio::test]
950    async fn test_delegate_task_tool() {
951        let message_queue = Arc::new(RwLock::new(Vec::new()));
952        let shared_context = Arc::new(RwLock::new(SharedContext::new()));
953
954        let tool = DelegateTaskTool::new(
955            "manager".to_string(),
956            Arc::clone(&message_queue),
957            Arc::clone(&shared_context),
958        );
959
960        // Test task delegation
961        let args = serde_json::json!({
962            "to": "worker",
963            "task": "Analyze the data",
964            "context": "Use statistical methods"
965        });
966
967        let result = tool.execute(args).await.unwrap();
968        assert!(result.success);
969        assert_eq!(result.output, "Task delegated to agent 'worker'");
970
971        // Check message queue
972        let queue = message_queue.read().await;
973        assert_eq!(queue.len(), 1);
974        let message = &queue[0];
975        assert_eq!(message.from, "manager");
976        assert_eq!(message.to, Some("worker".to_string()));
977        assert!(message.content.contains("Task delegated: Analyze the data"));
978        assert!(message.content.contains("Context: Use statistical methods"));
979
980        // Check metadata
981        assert_eq!(
982            message.metadata.get("type"),
983            Some(&"task_delegation".to_string())
984        );
985        assert_eq!(
986            message.metadata.get("task"),
987            Some(&"Analyze the data".to_string())
988        );
989    }
990
991    /// Tests the ShareContextTool functionality.
992    #[tokio::test]
993    async fn test_share_context_tool() {
994        let shared_context = Arc::new(RwLock::new(SharedContext::new()));
995
996        let tool = ShareContextTool::new("researcher".to_string(), Arc::clone(&shared_context));
997
998        // Test sharing context
999        let args = serde_json::json!({
1000            "key": "findings",
1001            "value": "Temperature affects reaction rate",
1002            "description": "Key experimental finding"
1003        });
1004
1005        let result = tool.execute(args).await.unwrap();
1006        assert!(result.success);
1007        assert_eq!(result.output, "Information shared with key 'findings'");
1008
1009        // Check shared context
1010        let context = shared_context.read().await;
1011        let findings_data = context.get("findings").unwrap();
1012        let findings_obj = findings_data.as_object().unwrap();
1013
1014        // Check the value
1015        assert_eq!(
1016            findings_obj.get("value").unwrap(),
1017            &Value::String("Temperature affects reaction rate".to_string())
1018        );
1019
1020        // Check metadata
1021        let metadata = findings_obj.get("metadata").unwrap();
1022        let metadata_obj = metadata.as_object().unwrap();
1023        assert_eq!(
1024            metadata_obj.get("shared_by").unwrap(),
1025            &Value::String("researcher".to_string())
1026        );
1027        assert_eq!(
1028            metadata_obj.get("description").unwrap(),
1029            &Value::String("Key experimental finding".to_string())
1030        );
1031        assert!(metadata_obj.contains_key("timestamp"));
1032    }
1033
1034    /// Tests the SharedContext functionality.
1035    #[tokio::test]
1036    async fn test_shared_context() {
1037        let mut context = SharedContext::new();
1038
1039        // Test setting and getting values
1040        context.set("key1".to_string(), Value::String("value1".to_string()));
1041        context.set("key2".to_string(), Value::Number(42.into()));
1042
1043        assert_eq!(
1044            context.get("key1"),
1045            Some(&Value::String("value1".to_string()))
1046        );
1047        assert_eq!(context.get("key2"), Some(&Value::Number(42.into())));
1048        assert_eq!(context.get("key3"), None);
1049
1050        // Test message history
1051        let msg1 = ForestMessage::new(
1052            "alice".to_string(),
1053            Some("bob".to_string()),
1054            "Hello".to_string(),
1055        );
1056        let msg2 = ForestMessage::broadcast("bob".to_string(), "Hi everyone".to_string());
1057
1058        context.add_message(msg1);
1059        context.add_message(msg2);
1060
1061        let messages = context.get_recent_messages(10);
1062        assert_eq!(messages.len(), 2);
1063        assert_eq!(messages[0].from, "alice");
1064        assert_eq!(messages[1].from, "bob");
1065
1066        // Test removing values
1067        let removed = context.remove("key1");
1068        assert_eq!(removed, Some(Value::String("value1".to_string())));
1069        assert_eq!(context.get("key1"), None);
1070    }
1071
1072    /// Tests collaborative task execution.
1073    #[tokio::test]
1074    async fn test_collaborative_task() {
1075        let mut forest = ForestOfAgents::new();
1076        let config = Config::new_default();
1077
1078        // Create agents with different roles
1079        let coordinator = Agent::builder("coordinator")
1080            .config(config.clone())
1081            .system_prompt(
1082                "You are a task coordinator. Break down tasks and delegate to specialists.",
1083            )
1084            .build()
1085            .await
1086            .unwrap();
1087
1088        let researcher = Agent::builder("researcher")
1089            .config(config.clone())
1090            .system_prompt("You are a researcher. Gather and analyze information.")
1091            .build()
1092            .await
1093            .unwrap();
1094
1095        let writer = Agent::builder("writer")
1096            .config(config)
1097            .system_prompt("You are a writer. Create clear, well-structured content.")
1098            .build()
1099            .await
1100            .unwrap();
1101
1102        forest
1103            .add_agent("coordinator".to_string(), coordinator)
1104            .unwrap();
1105        forest
1106            .add_agent("researcher".to_string(), researcher)
1107            .unwrap();
1108        forest.add_agent("writer".to_string(), writer).unwrap();
1109
1110        // Test that collaborative task setup works (without actually executing LLM calls)
1111        // We can't run the full collaborative task in unit tests due to LLM dependencies,
1112        // but we can test the setup and basic validation
1113
1114        // Test that agents exist validation works
1115        // (The actual task execution would require valid LLM API keys)
1116
1117        // Check that the forest has the expected agents
1118        assert_eq!(forest.list_agents().len(), 3);
1119        assert!(forest.get_agent(&"coordinator".to_string()).is_some());
1120        assert!(forest.get_agent(&"researcher".to_string()).is_some());
1121        assert!(forest.get_agent(&"writer".to_string()).is_some());
1122
1123        // Test that the method would set up shared context correctly by calling a minimal version
1124        // We'll test the context setup by manually calling the initial setup part
1125
1126        // Simulate the initial context setup that happens in execute_collaborative_task
1127        forest
1128            .set_shared_context(
1129                "current_task".to_string(),
1130                Value::String("Create a report on climate change impacts".to_string()),
1131            )
1132            .await;
1133        forest
1134            .set_shared_context(
1135                "involved_agents".to_string(),
1136                Value::Array(vec![
1137                    Value::String("researcher".to_string()),
1138                    Value::String("writer".to_string()),
1139                ]),
1140            )
1141            .await;
1142        forest
1143            .set_shared_context(
1144                "task_status".to_string(),
1145                Value::String("in_progress".to_string()),
1146            )
1147            .await;
1148
1149        // Check shared context was updated
1150        let context = forest.get_shared_context().await;
1151        assert_eq!(
1152            context.get("task_status"),
1153            Some(&Value::String("in_progress".to_string()))
1154        );
1155        assert!(context.get("current_task").is_some());
1156        assert!(context.get("involved_agents").is_some());
1157    }
1158
1159    /// Tests the ForestBuilder functionality.
1160    #[tokio::test]
1161    async fn test_forest_builder() {
1162        let config = Config::new_default();
1163
1164        let forest = ForestBuilder::new()
1165            .config(config)
1166            .agent(
1167                "agent1".to_string(),
1168                Agent::builder("agent1").system_prompt("Agent 1 prompt"),
1169            )
1170            .agent(
1171                "agent2".to_string(),
1172                Agent::builder("agent2").system_prompt("Agent 2 prompt"),
1173            )
1174            .max_iterations(5)
1175            .build()
1176            .await
1177            .unwrap();
1178
1179        assert_eq!(forest.list_agents().len(), 2);
1180        assert!(forest.get_agent(&"agent1".to_string()).is_some());
1181        assert!(forest.get_agent(&"agent2".to_string()).is_some());
1182        assert_eq!(forest.max_iterations, 5);
1183    }
1184
1185    /// Tests error handling in ForestOfAgents.
1186    #[tokio::test]
1187    async fn test_forest_error_handling() {
1188        let mut forest = ForestOfAgents::new();
1189
1190        // Test sending message from non-existent agent
1191        let result = forest
1192            .send_message(
1193                &"nonexistent".to_string(),
1194                Some(&"target".to_string()),
1195                "test".to_string(),
1196            )
1197            .await;
1198        assert!(result.is_err());
1199
1200        // Test collaborative task with non-existent initiator
1201        let result = forest
1202            .execute_collaborative_task(&"nonexistent".to_string(), "test task".to_string(), vec![])
1203            .await;
1204        assert!(result.is_err());
1205
1206        // Test collaborative task with non-existent involved agent
1207        let config = Config::new_default();
1208        let agent = Agent::builder("real_agent")
1209            .config(config)
1210            .build()
1211            .await
1212            .unwrap();
1213        forest.add_agent("real_agent".to_string(), agent).unwrap();
1214
1215        let result = forest
1216            .execute_collaborative_task(
1217                &"real_agent".to_string(),
1218                "test task".to_string(),
1219                vec!["nonexistent".to_string()],
1220            )
1221            .await;
1222        assert!(result.is_err());
1223    }
1224
1225    /// Tests ForestMessage creation and properties.
1226    #[tokio::test]
1227    async fn test_forest_message() {
1228        // Test direct message
1229        let msg = ForestMessage::new(
1230            "alice".to_string(),
1231            Some("bob".to_string()),
1232            "Hello".to_string(),
1233        );
1234        assert_eq!(msg.from, "alice");
1235        assert_eq!(msg.to, Some("bob".to_string()));
1236        assert_eq!(msg.content, "Hello");
1237
1238        // Test broadcast message
1239        let broadcast = ForestMessage::broadcast("alice".to_string(), "Announcement".to_string());
1240        assert_eq!(broadcast.from, "alice");
1241        assert!(broadcast.to.is_none());
1242        assert_eq!(broadcast.content, "Announcement");
1243
1244        // Test metadata
1245        let msg_with_meta = msg.with_metadata("priority".to_string(), "high".to_string());
1246        assert_eq!(
1247            msg_with_meta.metadata.get("priority"),
1248            Some(&"high".to_string())
1249        );
1250    }
1251}