1use 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
18pub type AgentId = String;
20
21#[derive(Debug, Clone)]
23pub struct ForestMessage {
24 pub from: AgentId,
26 pub to: Option<AgentId>,
28 pub content: String,
30 pub metadata: HashMap<String, String>,
32 pub timestamp: chrono::DateTime<chrono::Utc>,
34}
35
36impl ForestMessage {
37 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 pub fn broadcast(from: AgentId, content: String) -> Self {
50 Self::new(from, None, content)
51 }
52
53 pub fn with_metadata(mut self, key: String, value: String) -> Self {
55 self.metadata.insert(key, value);
56 self
57 }
58}
59
60#[derive(Debug, Clone)]
62pub struct SharedContext {
63 pub data: HashMap<String, Value>,
65 pub message_history: Vec<ForestMessage>,
67 pub metadata: HashMap<String, String>,
69}
70
71impl SharedContext {
72 pub fn new() -> Self {
74 Self {
75 data: HashMap::new(),
76 message_history: Vec::new(),
77 metadata: HashMap::new(),
78 }
79 }
80
81 pub fn set(&mut self, key: String, value: Value) {
83 self.data.insert(key, value);
84 }
85
86 pub fn get(&self, key: &str) -> Option<&Value> {
88 self.data.get(key)
89 }
90
91 pub fn remove(&mut self, key: &str) -> Option<Value> {
93 self.data.remove(key)
94 }
95
96 pub fn add_message(&mut self, message: ForestMessage) {
98 self.message_history.push(message);
99 }
100
101 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
115pub struct ForestOfAgents {
117 agents: HashMap<AgentId, Agent>,
119 shared_context: Arc<RwLock<SharedContext>>,
121 message_queue: Arc<RwLock<Vec<ForestMessage>>>,
123 max_iterations: usize,
125}
126
127impl ForestOfAgents {
128 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 pub fn with_max_iterations(max_iterations: usize) -> Self {
140 Self {
141 max_iterations,
142 ..Self::new()
143 }
144 }
145
146 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 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 pub fn remove_agent(&mut self, id: &AgentId) -> Option<Agent> {
199 self.agents.remove(id)
200 }
201
202 pub fn get_agent(&self, id: &AgentId) -> Option<&Agent> {
204 self.agents.get(id)
205 }
206
207 pub fn get_agent_mut(&mut self, id: &AgentId) -> Option<&mut Agent> {
209 self.agents.get_mut(id)
210 }
211
212 pub fn list_agents(&self) -> Vec<AgentId> {
214 self.agents.keys().cloned().collect()
215 }
216
217 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 let mut context = self.shared_context.write().await;
252 context.add_message(message);
253
254 Ok(())
255 }
256
257 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 if let Some(agent) = self.agents.get_mut(to_id) {
268 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 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 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 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 {
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 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 self.process_messages_and_trigger_responses(&involved_agents)
360 .await?;
361
362 {
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 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 self.process_messages().await?;
388
389 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 last_message.role == crate::chat::Role::User {
399 agents_to_respond.push(agent_id.clone());
400 }
401 }
402 }
403 }
404
405 if agents_to_respond.is_empty() {
407 break;
408 }
409
410 for agent_id in agents_to_respond {
412 if let Some(agent) = self.agents.get_mut(&agent_id) {
413 let _response = agent.chat("").await?;
415 }
416 }
417
418 iteration += 1;
419 }
420
421 Ok(())
422 }
423
424 pub async fn get_shared_context(&self) -> SharedContext {
426 self.shared_context.read().await.clone()
427 }
428
429 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
442pub struct SendMessageTool {
444 agent_id: AgentId,
445 message_queue: Arc<RwLock<Vec<ForestMessage>>>,
446 shared_context: Arc<RwLock<SharedContext>>,
447}
448
449impl SendMessageTool {
450 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
527pub struct DelegateTaskTool {
529 agent_id: AgentId,
530 message_queue: Arc<RwLock<Vec<ForestMessage>>>,
531 shared_context: Arc<RwLock<SharedContext>>,
532}
533
534impl DelegateTaskTool {
535 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
629pub struct ShareContextTool {
631 agent_id: AgentId,
632 shared_context: Arc<RwLock<SharedContext>>,
633}
634
635impl ShareContextTool {
636 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 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
723pub struct ForestBuilder {
725 config: Option<Config>,
726 agents: Vec<(AgentId, AgentBuilder)>,
727 max_iterations: usize,
728}
729
730impl ForestBuilder {
731 pub fn new() -> Self {
733 Self {
734 config: None,
735 agents: Vec::new(),
736 max_iterations: 10,
737 }
738 }
739
740 pub fn config(mut self, config: Config) -> Self {
742 self.config = Some(config);
743 self
744 }
745
746 pub fn agent(mut self, id: AgentId, builder: AgentBuilder) -> Self {
748 self.agents.push((id, builder));
749 self
750 }
751
752 pub fn max_iterations(mut self, max: usize) -> Self {
754 self.max_iterations = max;
755 self
756 }
757
758 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 #[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 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 forest.add_agent("agent1".to_string(), agent1).unwrap();
811 forest.add_agent("agent2".to_string(), agent2).unwrap();
812
813 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 assert!(forest.get_agent(&"agent1".to_string()).is_some());
821 assert!(forest.get_agent(&"agent3".to_string()).is_none());
822
823 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 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 #[tokio::test]
841 async fn test_message_sending() {
842 let mut forest = ForestOfAgents::new();
843 let config = Config::new_default();
844
845 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 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 forest.process_messages().await.unwrap();
869
870 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 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 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 #[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 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 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 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 }
947
948 #[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 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 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 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 #[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 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 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 assert_eq!(
1016 findings_obj.get("value").unwrap(),
1017 &Value::String("Temperature affects reaction rate".to_string())
1018 );
1019
1020 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 #[tokio::test]
1036 async fn test_shared_context() {
1037 let mut context = SharedContext::new();
1038
1039 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 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 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 #[tokio::test]
1074 async fn test_collaborative_task() {
1075 let mut forest = ForestOfAgents::new();
1076 let config = Config::new_default();
1077
1078 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 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 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 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 #[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 #[tokio::test]
1187 async fn test_forest_error_handling() {
1188 let mut forest = ForestOfAgents::new();
1189
1190 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 let result = forest
1202 .execute_collaborative_task(&"nonexistent".to_string(), "test task".to_string(), vec![])
1203 .await;
1204 assert!(result.is_err());
1205
1206 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 #[tokio::test]
1227 async fn test_forest_message() {
1228 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 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 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}