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, PartialEq)]
62pub enum TaskStatus {
63 Pending,
64 InProgress,
65 Completed,
66 Failed,
67}
68
69impl TaskStatus {
70 pub fn as_str(&self) -> &str {
71 match self {
72 TaskStatus::Pending => "pending",
73 TaskStatus::InProgress => "in_progress",
74 TaskStatus::Completed => "completed",
75 TaskStatus::Failed => "failed",
76 }
77 }
78}
79
80#[derive(Debug, Clone)]
82pub struct TaskItem {
83 pub id: String,
85 pub description: String,
87 pub assigned_to: AgentId,
89 pub status: TaskStatus,
91 pub result: Option<String>,
93 pub dependencies: Vec<String>,
95 pub metadata: HashMap<String, String>,
97}
98
99impl TaskItem {
100 pub fn new(id: String, description: String, assigned_to: AgentId) -> Self {
101 Self {
102 id,
103 description,
104 assigned_to,
105 status: TaskStatus::Pending,
106 result: None,
107 dependencies: Vec::new(),
108 metadata: HashMap::new(),
109 }
110 }
111
112 pub fn with_dependencies(mut self, deps: Vec<String>) -> Self {
113 self.dependencies = deps;
114 self
115 }
116}
117
118#[derive(Debug, Clone)]
120pub struct TaskPlan {
121 pub plan_id: String,
123 pub objective: String,
125 pub tasks: HashMap<String, TaskItem>,
127 pub task_order: Vec<String>,
129 pub created_at: chrono::DateTime<chrono::Utc>,
131}
132
133impl TaskPlan {
134 pub fn new(plan_id: String, objective: String) -> Self {
135 Self {
136 plan_id,
137 objective,
138 tasks: HashMap::new(),
139 task_order: Vec::new(),
140 created_at: chrono::Utc::now(),
141 }
142 }
143
144 pub fn add_task(&mut self, task: TaskItem) {
145 self.task_order.push(task.id.clone());
146 self.tasks.insert(task.id.clone(), task);
147 }
148
149 pub fn get_task_mut(&mut self, task_id: &str) -> Option<&mut TaskItem> {
150 self.tasks.get_mut(task_id)
151 }
152
153 pub fn get_task(&self, task_id: &str) -> Option<&TaskItem> {
154 self.tasks.get(task_id)
155 }
156
157 pub fn get_next_ready_tasks(&self) -> Vec<&TaskItem> {
160 self.task_order
161 .iter()
162 .filter_map(|task_id| self.tasks.get(task_id))
163 .filter(|t| {
164 t.status == TaskStatus::Pending
165 && t.dependencies.iter().all(|dep_id| {
166 self.tasks
168 .get(dep_id)
169 .map(|dt| dt.status == TaskStatus::Completed)
170 .unwrap_or(false)
171 })
172 })
173 .collect()
174 }
175
176 pub fn is_complete(&self) -> bool {
177 self.tasks
178 .values()
179 .all(|t| t.status == TaskStatus::Completed || t.status == TaskStatus::Failed)
180 }
181
182 pub fn get_progress(&self) -> (usize, usize) {
183 let completed = self
184 .tasks
185 .values()
186 .filter(|t| t.status == TaskStatus::Completed)
187 .count();
188 (completed, self.tasks.len())
189 }
190
191 pub fn tasks_in_order(&self) -> Vec<&TaskItem> {
193 self.task_order
194 .iter()
195 .filter_map(|id| self.tasks.get(id))
196 .collect()
197 }
198}
199
200#[derive(Debug, Clone)]
202pub struct SharedContext {
203 pub data: HashMap<String, Value>,
205 pub message_history: Vec<ForestMessage>,
207 pub metadata: HashMap<String, String>,
209 pub current_plan: Option<TaskPlan>,
211}
212
213impl SharedContext {
214 pub fn new() -> Self {
216 Self {
217 data: HashMap::new(),
218 message_history: Vec::new(),
219 metadata: HashMap::new(),
220 current_plan: None,
221 }
222 }
223
224 pub fn set(&mut self, key: String, value: Value) {
226 self.data.insert(key, value);
227 }
228
229 pub fn get(&self, key: &str) -> Option<&Value> {
231 self.data.get(key)
232 }
233
234 pub fn remove(&mut self, key: &str) -> Option<Value> {
236 self.data.remove(key)
237 }
238
239 pub fn add_message(&mut self, message: ForestMessage) {
241 self.message_history.push(message);
242 }
243
244 pub fn get_recent_messages(&self, limit: usize) -> &[ForestMessage] {
246 let len = self.message_history.len();
247 let start = len.saturating_sub(limit);
248 &self.message_history[start..]
249 }
250
251 pub fn set_plan(&mut self, plan: TaskPlan) {
253 self.current_plan = Some(plan);
254 }
255
256 pub fn get_plan(&self) -> Option<&TaskPlan> {
258 self.current_plan.as_ref()
259 }
260
261 pub fn get_plan_mut(&mut self) -> Option<&mut TaskPlan> {
263 self.current_plan.as_mut()
264 }
265
266 pub fn clear_plan(&mut self) {
268 self.current_plan = None;
269 }
270}
271
272impl Default for SharedContext {
273 fn default() -> Self {
274 Self::new()
275 }
276}
277
278pub struct ForestOfAgents {
280 agents: HashMap<AgentId, Agent>,
282 shared_context: Arc<RwLock<SharedContext>>,
284 message_queue: Arc<RwLock<Vec<ForestMessage>>>,
286 max_iterations: usize,
288}
289
290impl ForestOfAgents {
291 pub fn new() -> Self {
293 Self {
294 agents: HashMap::new(),
295 shared_context: Arc::new(RwLock::new(SharedContext::new())),
296 message_queue: Arc::new(RwLock::new(Vec::new())),
297 max_iterations: 10,
298 }
299 }
300
301 pub fn with_max_iterations(max_iterations: usize) -> Self {
303 Self {
304 max_iterations,
305 ..Self::new()
306 }
307 }
308
309 pub fn add_agent(&mut self, id: AgentId, mut agent: Agent) -> Result<()> {
320 if self.agents.contains_key(&id) {
321 return Err(HeliosError::AgentError(format!(
322 "Agent with ID '{}' already exists",
323 id
324 )));
325 }
326
327 let send_message_tool = Box::new(SendMessageTool::new(
329 id.clone(),
330 Arc::clone(&self.message_queue),
331 Arc::clone(&self.shared_context),
332 ));
333 agent.register_tool(send_message_tool);
334
335 let delegate_task_tool = Box::new(DelegateTaskTool::new(
336 id.clone(),
337 Arc::clone(&self.message_queue),
338 Arc::clone(&self.shared_context),
339 ));
340 agent.register_tool(delegate_task_tool);
341
342 let share_context_tool = Box::new(ShareContextTool::new(
343 id.clone(),
344 Arc::clone(&self.shared_context),
345 ));
346 agent.register_tool(share_context_tool);
347
348 let update_task_memory_tool = Box::new(UpdateTaskMemoryTool::new(
349 id.clone(),
350 Arc::clone(&self.shared_context),
351 ));
352 agent.register_tool(update_task_memory_tool);
353
354 let create_plan_tool = Box::new(CreatePlanTool::new(
355 id.clone(),
356 Arc::clone(&self.shared_context),
357 ));
358 agent.register_tool(create_plan_tool);
359
360 self.agents.insert(id, agent);
361 Ok(())
362 }
363
364 pub fn remove_agent(&mut self, id: &AgentId) -> Option<Agent> {
374 self.agents.remove(id)
375 }
376
377 pub fn get_agent(&self, id: &AgentId) -> Option<&Agent> {
379 self.agents.get(id)
380 }
381
382 pub fn get_agent_mut(&mut self, id: &AgentId) -> Option<&mut Agent> {
384 self.agents.get_mut(id)
385 }
386
387 pub fn list_agents(&self) -> Vec<AgentId> {
389 self.agents.keys().cloned().collect()
390 }
391
392 pub async fn send_message(
404 &self,
405 from: &AgentId,
406 to: Option<&AgentId>,
407 content: String,
408 ) -> Result<()> {
409 if !self.agents.contains_key(from) {
410 return Err(HeliosError::AgentError(format!(
411 "Agent '{}' not found",
412 from
413 )));
414 }
415
416 let message = if let Some(to_id) = to {
417 ForestMessage::new(from.clone(), Some(to_id.clone()), content)
418 } else {
419 ForestMessage::broadcast(from.clone(), content)
420 };
421
422 let mut queue = self.message_queue.write().await;
423 queue.push(message.clone());
424
425 let mut context = self.shared_context.write().await;
427 context.add_message(message);
428
429 Ok(())
430 }
431
432 pub async fn process_messages(&mut self) -> Result<()> {
434 let messages: Vec<ForestMessage> = {
435 let mut queue = self.message_queue.write().await;
436 queue.drain(..).collect()
437 };
438
439 for message in messages {
440 if let Some(to_id) = &message.to {
441 if let Some(agent) = self.agents.get_mut(to_id) {
443 let formatted_message =
445 format!("Message from {}: {}", message.from, message.content);
446 agent.chat_session_mut().add_user_message(formatted_message);
447 }
448 } else {
449 for (agent_id, agent) in &mut self.agents {
451 if agent_id != &message.from {
452 let formatted_message =
453 format!("Broadcast from {}: {}", message.from, message.content);
454 agent.chat_session_mut().add_user_message(formatted_message);
455 }
456 }
457 }
458 }
459
460 Ok(())
461 }
462
463 pub async fn execute_collaborative_task(
475 &mut self,
476 initiator: &AgentId,
477 task_description: String,
478 involved_agents: Vec<AgentId>,
479 ) -> Result<String> {
480 for agent_id in &involved_agents {
482 if !self.agents.contains_key(agent_id) {
483 return Err(HeliosError::AgentError(format!(
484 "Agent '{}' not found",
485 agent_id
486 )));
487 }
488 }
489
490 if !self.agents.contains_key(initiator) {
491 return Err(HeliosError::AgentError(format!(
492 "Initiator agent '{}' not found",
493 initiator
494 )));
495 }
496 {
498 let mut context = self.shared_context.write().await;
499 context.set(
500 "current_task".to_string(),
501 Value::String(task_description.clone()),
502 );
503 context.set(
504 "involved_agents".to_string(),
505 Value::Array(
506 involved_agents
507 .iter()
508 .map(|id| Value::String(id.clone()))
509 .collect(),
510 ),
511 );
512 context.set(
513 "task_status".to_string(),
514 Value::String("planning".to_string()),
515 );
516 }
517
518 let coordinator = self.agents.get_mut(initiator).unwrap();
519 let planning_prompt = format!(
520 "You are coordinating a collaborative task. Create a detailed plan using the 'create_plan' tool.\n\n\
521 Task: {}\n\n\
522 Available team members and their expertise:\n{}\n\n\
523 Break this task into subtasks and assign each to the most appropriate agent. \
524 Use the create_plan tool with a JSON array of tasks. Each task should have:\n\
525 - id: unique identifier (e.g., 'task_1')\n\
526 - description: what needs to be done\n\
527 - assigned_to: agent name\n\
528 - dependencies: array of task IDs that must complete first (use [] if none)\n\n\
529 IMPORTANT: You MUST use the create_plan tool to create a plan before doing anything else. \
530 Do not try to complete the task yourself - just create the plan using the tool.",
531 task_description,
532 involved_agents.join(", ")
533 );
534
535 let _planning_result = coordinator.chat(planning_prompt).await?;
536
537 let plan_exists = {
539 let context = self.shared_context.read().await;
540 context.get_plan().is_some()
541 };
542
543 if !plan_exists {
544 return Ok(_planning_result);
546 }
547
548 let mut iteration = 0;
550 let max_task_iterations = self.max_iterations * 3; while iteration < max_task_iterations {
553 let ready_tasks: Vec<(String, String, AgentId)> = {
555 let context = self.shared_context.read().await;
556 if let Some(plan) = context.get_plan() {
557 if plan.is_complete() {
558 break;
559 }
560 plan.get_next_ready_tasks()
561 .iter()
562 .map(|t| (t.id.clone(), t.description.clone(), t.assigned_to.clone()))
563 .collect()
564 } else {
565 let initiator_agent = self.agents.get_mut(initiator).unwrap();
567 let result = initiator_agent
568 .chat(format!(
569 "Complete this task: {}\nYou can delegate to: {}",
570 task_description,
571 involved_agents.join(", ")
572 ))
573 .await?;
574 return Ok(result);
575 }
576 };
577
578 if ready_tasks.is_empty() {
579 let has_in_progress = {
581 let context = self.shared_context.read().await;
582 context
583 .get_plan()
584 .map(|p| p.tasks.values().any(|t| t.status == TaskStatus::InProgress))
585 .unwrap_or(false)
586 };
587
588 if !has_in_progress {
589 break; }
591
592 if iteration > 5 {
594 break;
595 }
596
597 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
598 iteration += 1;
599 continue;
600 }
601
602 for (task_id, task_desc, agent_id) in ready_tasks {
604 {
606 let mut context = self.shared_context.write().await;
607 if let Some(plan) = context.get_plan_mut() {
608 if let Some(task) = plan.get_task_mut(&task_id) {
609 task.status = TaskStatus::InProgress;
610 }
611 }
612 }
613
614 let shared_memory_info = {
616 let context = self.shared_context.read().await;
617 let mut info = String::from("\n=== SHARED TASK MEMORY ===\n");
618
619 if let Some(plan) = context.get_plan() {
620 info.push_str(&format!("Overall Objective: {}\n", plan.objective));
621 info.push_str(&format!(
622 "Progress: {}/{} tasks completed\n\n",
623 plan.get_progress().0,
624 plan.get_progress().1
625 ));
626
627 info.push_str("Completed Tasks:\n");
628 for task in plan.tasks_in_order() {
629 if task.status == TaskStatus::Completed {
630 info.push_str(&format!(
631 " ✓ [{}] {}: {}\n",
632 task.assigned_to,
633 task.description,
634 task.result.as_ref().unwrap_or(&"No result".to_string())
635 ));
636 }
637 }
638 }
639
640 info.push_str("\nShared Data:\n");
641 for (key, value) in &context.data {
642 if !key.starts_with("current_task")
643 && !key.starts_with("involved_agents")
644 && !key.starts_with("task_status")
645 {
646 info.push_str(&format!(" • {}: {}\n", key, value));
647 }
648 }
649 info.push_str("=========================\n\n");
650 info
651 };
652
653 if let Some(agent) = self.agents.get_mut(&agent_id) {
655 let task_prompt = format!(
656 "{}Your assigned task: {}\n\n\
657 Complete this task and use the 'update_task_memory' tool to save your results to the shared memory. \
658 The task_id is '{}'. Include key findings and data that other agents might need.\n\n\
659 Provide a complete response with your results.",
660 shared_memory_info, task_desc, task_id
661 );
662
663 let result = agent.chat(task_prompt).await?;
664
665 {
667 let mut context = self.shared_context.write().await;
668 if let Some(plan) = context.get_plan_mut() {
669 if let Some(task) = plan.get_task_mut(&task_id) {
670 if task.status == TaskStatus::InProgress {
671 task.status = TaskStatus::Completed;
672 task.result = Some(result.clone());
673 }
674 }
675 }
676 }
677 }
678 }
679
680 iteration += 1;
681 }
682
683 let final_summary = {
686 let context = self.shared_context.read().await;
687 let mut summary = String::from("=== TASK COMPLETION SUMMARY ===\n\n");
688
689 if let Some(plan) = context.get_plan() {
690 summary.push_str(&format!("Objective: {}\n", plan.objective));
691 summary.push_str(&format!(
692 "Status: All tasks completed ({}/{} tasks)\n\n",
693 plan.get_progress().0,
694 plan.get_progress().1
695 ));
696
697 summary.push_str("Task Results:\n");
698 for task in plan.tasks_in_order() {
699 summary.push_str(&format!("\n[{}] {}\n", task.assigned_to, task.description));
700 if let Some(result) = &task.result {
701 summary.push_str(&format!("Result: {}\n", result));
702 }
703 }
704 }
705 summary
706 };
707
708 let coordinator = self.agents.get_mut(initiator).unwrap();
709 let synthesis_prompt = format!(
710 "Based on the completed tasks, provide a comprehensive final answer to the original request.\n\n\
711 Original Task: {}\n\n\
712 {}\n\n\
713 Synthesize all the information into a cohesive, complete response.",
714 task_description, final_summary
715 );
716
717 let final_result = coordinator.chat(synthesis_prompt).await?;
718
719 {
721 let mut context = self.shared_context.write().await;
722 context.set(
723 "task_status".to_string(),
724 Value::String("completed".to_string()),
725 );
726 }
727
728 Ok(final_result)
729 }
730
731 #[allow(dead_code)]
737 async fn process_messages_and_trigger_responses(
738 &mut self,
739 involved_agents: &[AgentId],
740 ) -> Result<()> {
741 let mut iteration = 0;
742
743 while iteration < self.max_iterations {
744 self.process_messages().await?;
746
747 let mut agents_to_respond = Vec::new();
749
750 for agent_id in involved_agents {
751 if let Some(agent) = self.agents.get(agent_id) {
752 let messages = &agent.chat_session().messages;
753 if !messages.is_empty() {
754 let last_message = messages.last().unwrap();
755 if last_message.role == crate::chat::Role::User {
757 agents_to_respond.push(agent_id.clone());
758 }
759 }
760 }
761 }
762
763 if agents_to_respond.is_empty() {
765 break;
766 }
767
768 for agent_id in agents_to_respond {
770 if let Some(agent) = self.agents.get_mut(&agent_id) {
771 let _response = agent.chat("").await?;
773 }
774 }
775
776 iteration += 1;
777 }
778
779 Ok(())
780 }
781
782 pub async fn get_shared_context(&self) -> SharedContext {
784 self.shared_context.read().await.clone()
785 }
786
787 pub async fn set_shared_context(&self, key: String, value: Value) {
789 let mut context = self.shared_context.write().await;
790 context.set(key, value);
791 }
792}
793
794impl Default for ForestOfAgents {
795 fn default() -> Self {
796 Self::new()
797 }
798}
799
800pub struct SendMessageTool {
802 agent_id: AgentId,
803 message_queue: Arc<RwLock<Vec<ForestMessage>>>,
804 shared_context: Arc<RwLock<SharedContext>>,
805}
806
807impl SendMessageTool {
808 pub fn new(
810 agent_id: AgentId,
811 message_queue: Arc<RwLock<Vec<ForestMessage>>>,
812 shared_context: Arc<RwLock<SharedContext>>,
813 ) -> Self {
814 Self {
815 agent_id,
816 message_queue,
817 shared_context,
818 }
819 }
820}
821
822#[async_trait::async_trait]
823impl Tool for SendMessageTool {
824 fn name(&self) -> &str {
825 "send_message"
826 }
827
828 fn description(&self) -> &str {
829 "Send a message to another agent or broadcast to all agents in the forest."
830 }
831
832 fn parameters(&self) -> HashMap<String, ToolParameter> {
833 let mut params = HashMap::new();
834 params.insert(
835 "to".to_string(),
836 ToolParameter {
837 param_type: "string".to_string(),
838 description: "ID of the recipient agent (leave empty for broadcast)".to_string(),
839 required: Some(false),
840 },
841 );
842 params.insert(
843 "message".to_string(),
844 ToolParameter {
845 param_type: "string".to_string(),
846 description: "The message content to send".to_string(),
847 required: Some(true),
848 },
849 );
850 params
851 }
852
853 async fn execute(&self, args: Value) -> Result<ToolResult> {
854 let message = args
855 .get("message")
856 .and_then(|v| v.as_str())
857 .ok_or_else(|| HeliosError::ToolError("Missing 'message' parameter".to_string()))?
858 .to_string();
859
860 let to = args
861 .get("to")
862 .and_then(|v| v.as_str())
863 .map(|s| s.to_string());
864
865 let forest_message = if let Some(to_id) = &to {
866 ForestMessage::new(self.agent_id.clone(), Some(to_id.clone()), message)
867 } else {
868 ForestMessage::broadcast(self.agent_id.clone(), message)
869 };
870
871 {
872 let mut queue = self.message_queue.write().await;
873 queue.push(forest_message.clone());
874 }
875
876 {
877 let mut context = self.shared_context.write().await;
878 context.add_message(forest_message);
879 }
880
881 Ok(ToolResult::success("Message sent successfully"))
882 }
883}
884
885pub struct DelegateTaskTool {
887 agent_id: AgentId,
888 message_queue: Arc<RwLock<Vec<ForestMessage>>>,
889 shared_context: Arc<RwLock<SharedContext>>,
890}
891
892impl DelegateTaskTool {
893 pub fn new(
895 agent_id: AgentId,
896 message_queue: Arc<RwLock<Vec<ForestMessage>>>,
897 shared_context: Arc<RwLock<SharedContext>>,
898 ) -> Self {
899 Self {
900 agent_id,
901 message_queue,
902 shared_context,
903 }
904 }
905}
906
907#[async_trait::async_trait]
908impl Tool for DelegateTaskTool {
909 fn name(&self) -> &str {
910 "delegate_task"
911 }
912
913 fn description(&self) -> &str {
914 "Delegate a specific task to another agent for execution."
915 }
916
917 fn parameters(&self) -> HashMap<String, ToolParameter> {
918 let mut params = HashMap::new();
919 params.insert(
920 "to".to_string(),
921 ToolParameter {
922 param_type: "string".to_string(),
923 description: "ID of the agent to delegate the task to".to_string(),
924 required: Some(true),
925 },
926 );
927 params.insert(
928 "task".to_string(),
929 ToolParameter {
930 param_type: "string".to_string(),
931 description: "Description of the task to delegate".to_string(),
932 required: Some(true),
933 },
934 );
935 params.insert(
936 "context".to_string(),
937 ToolParameter {
938 param_type: "string".to_string(),
939 description: "Additional context or requirements for the task".to_string(),
940 required: Some(false),
941 },
942 );
943 params
944 }
945
946 async fn execute(&self, args: Value) -> Result<ToolResult> {
947 let to = args
948 .get("to")
949 .and_then(|v| v.as_str())
950 .ok_or_else(|| HeliosError::ToolError("Missing 'to' parameter".to_string()))?;
951
952 let task = args
953 .get("task")
954 .and_then(|v| v.as_str())
955 .ok_or_else(|| HeliosError::ToolError("Missing 'task' parameter".to_string()))?;
956
957 let context = args.get("context").and_then(|v| v.as_str()).unwrap_or("");
958
959 let message = if context.is_empty() {
960 format!("Task delegated: {}", task)
961 } else {
962 format!("Task delegated: {}\nContext: {}", task, context)
963 };
964
965 let forest_message =
966 ForestMessage::new(self.agent_id.clone(), Some(to.to_string()), message)
967 .with_metadata("type".to_string(), "task_delegation".to_string())
968 .with_metadata("task".to_string(), task.to_string());
969
970 {
971 let mut queue = self.message_queue.write().await;
972 queue.push(forest_message.clone());
973 }
974
975 {
976 let mut context_lock = self.shared_context.write().await;
977 context_lock.add_message(forest_message);
978 }
979
980 Ok(ToolResult::success(format!(
981 "Task delegated to agent '{}'",
982 to
983 )))
984 }
985}
986
987pub struct ShareContextTool {
989 agent_id: AgentId,
990 shared_context: Arc<RwLock<SharedContext>>,
991}
992
993impl ShareContextTool {
994 pub fn new(agent_id: AgentId, shared_context: Arc<RwLock<SharedContext>>) -> Self {
996 Self {
997 agent_id,
998 shared_context,
999 }
1000 }
1001}
1002
1003#[async_trait::async_trait]
1004impl Tool for ShareContextTool {
1005 fn name(&self) -> &str {
1006 "share_context"
1007 }
1008
1009 fn description(&self) -> &str {
1010 "Share information in the shared context that all agents can access."
1011 }
1012
1013 fn parameters(&self) -> HashMap<String, ToolParameter> {
1014 let mut params = HashMap::new();
1015 params.insert(
1016 "key".to_string(),
1017 ToolParameter {
1018 param_type: "string".to_string(),
1019 description: "Key for the shared information".to_string(),
1020 required: Some(true),
1021 },
1022 );
1023 params.insert(
1024 "value".to_string(),
1025 ToolParameter {
1026 param_type: "string".to_string(),
1027 description: "Value to share".to_string(),
1028 required: Some(true),
1029 },
1030 );
1031 params.insert(
1032 "description".to_string(),
1033 ToolParameter {
1034 param_type: "string".to_string(),
1035 description: "Description of what this information represents".to_string(),
1036 required: Some(false),
1037 },
1038 );
1039 params
1040 }
1041
1042 async fn execute(&self, args: Value) -> Result<ToolResult> {
1043 let key = args
1044 .get("key")
1045 .and_then(|v| v.as_str())
1046 .ok_or_else(|| HeliosError::ToolError("Missing 'key' parameter".to_string()))?;
1047
1048 let value = args
1049 .get("value")
1050 .and_then(|v| v.as_str())
1051 .ok_or_else(|| HeliosError::ToolError("Missing 'value' parameter".to_string()))?;
1052
1053 let description = args
1054 .get("description")
1055 .and_then(|v| v.as_str())
1056 .unwrap_or("");
1057
1058 let mut context = self.shared_context.write().await;
1059
1060 let metadata = serde_json::json!({
1062 "shared_by": self.agent_id,
1063 "timestamp": chrono::Utc::now().to_rfc3339(),
1064 "description": description
1065 });
1066
1067 let value_with_meta = serde_json::json!({
1068 "value": value,
1069 "metadata": metadata
1070 });
1071
1072 context.set(key.to_string(), value_with_meta);
1073
1074 Ok(ToolResult::success(format!(
1075 "Information shared with key '{}'",
1076 key
1077 )))
1078 }
1079}
1080
1081pub struct UpdateTaskMemoryTool {
1083 agent_id: AgentId,
1084 shared_context: Arc<RwLock<SharedContext>>,
1085}
1086
1087impl UpdateTaskMemoryTool {
1088 pub fn new(agent_id: AgentId, shared_context: Arc<RwLock<SharedContext>>) -> Self {
1089 Self {
1090 agent_id,
1091 shared_context,
1092 }
1093 }
1094}
1095
1096#[async_trait::async_trait]
1097impl Tool for UpdateTaskMemoryTool {
1098 fn name(&self) -> &str {
1099 "update_task_memory"
1100 }
1101
1102 fn description(&self) -> &str {
1103 "Update the shared task memory with your results, findings, and data. This allows other agents to see your progress and use your outputs."
1104 }
1105
1106 fn parameters(&self) -> HashMap<String, ToolParameter> {
1107 let mut params = HashMap::new();
1108 params.insert(
1109 "task_id".to_string(),
1110 ToolParameter {
1111 param_type: "string".to_string(),
1112 description: "The ID of the task you're updating (e.g., 'task_1')".to_string(),
1113 required: Some(true),
1114 },
1115 );
1116 params.insert(
1117 "result".to_string(),
1118 ToolParameter {
1119 param_type: "string".to_string(),
1120 description: "Your results, findings, or output from completing the task"
1121 .to_string(),
1122 required: Some(true),
1123 },
1124 );
1125 params.insert(
1126 "data".to_string(),
1127 ToolParameter {
1128 param_type: "string".to_string(),
1129 description: "Additional data or information to share (e.g., key findings, metrics, recommendations)".to_string(),
1130 required: Some(false),
1131 },
1132 );
1133 params
1134 }
1135
1136 async fn execute(&self, args: Value) -> Result<ToolResult> {
1137 let task_id = args
1138 .get("task_id")
1139 .and_then(|v| v.as_str())
1140 .ok_or_else(|| HeliosError::ToolError("Missing 'task_id' parameter".to_string()))?;
1141
1142 let result = args
1143 .get("result")
1144 .and_then(|v| v.as_str())
1145 .ok_or_else(|| HeliosError::ToolError("Missing 'result' parameter".to_string()))?;
1146
1147 let additional_data = args.get("data").and_then(|v| v.as_str()).unwrap_or("");
1148
1149 let mut context = self.shared_context.write().await;
1150
1151 if let Some(plan) = context.get_plan_mut() {
1153 if let Some(task) = plan.get_task_mut(task_id) {
1154 task.status = TaskStatus::Completed;
1155 task.result = Some(result.to_string());
1156 let task_description = task.description.clone();
1157
1158 if !additional_data.is_empty() {
1160 let data_key = format!("task_data_{}", task_id);
1161 context.set(
1162 data_key,
1163 serde_json::json!({
1164 "agent": self.agent_id,
1165 "task": task_description,
1166 "data": additional_data,
1167 "timestamp": chrono::Utc::now().to_rfc3339()
1168 }),
1169 );
1170 }
1171
1172 return Ok(ToolResult::success(format!(
1173 "Task '{}' marked as completed. Results saved to shared memory.",
1174 task_id
1175 )));
1176 } else {
1177 return Err(HeliosError::ToolError(format!(
1178 "Task '{}' not found in current plan",
1179 task_id
1180 )));
1181 }
1182 }
1183
1184 Err(HeliosError::ToolError(
1185 "No active task plan found".to_string(),
1186 ))
1187 }
1188}
1189
1190pub struct CreatePlanTool {
1192 #[allow(dead_code)]
1193 agent_id: AgentId,
1194 shared_context: Arc<RwLock<SharedContext>>,
1195}
1196
1197impl CreatePlanTool {
1198 pub fn new(agent_id: AgentId, shared_context: Arc<RwLock<SharedContext>>) -> Self {
1199 Self {
1200 agent_id,
1201 shared_context,
1202 }
1203 }
1204}
1205
1206#[async_trait::async_trait]
1207impl Tool for CreatePlanTool {
1208 fn name(&self) -> &str {
1209 "create_plan"
1210 }
1211
1212 fn description(&self) -> &str {
1213 "Create a detailed task plan for collaborative work. Break down the overall objective into specific tasks and assign them to team members."
1214 }
1215
1216 fn parameters(&self) -> HashMap<String, ToolParameter> {
1217 let mut params = HashMap::new();
1218 params.insert(
1219 "objective".to_string(),
1220 ToolParameter {
1221 param_type: "string".to_string(),
1222 description: "The overall objective or goal of the plan".to_string(),
1223 required: Some(true),
1224 },
1225 );
1226 params.insert(
1227 "tasks".to_string(),
1228 ToolParameter {
1229 param_type: "string".to_string(),
1230 description: "JSON array of tasks. Each task must have: id (string), description (string), assigned_to (string), dependencies (array of task IDs)".to_string(),
1231 required: Some(true),
1232 },
1233 );
1234 params
1235 }
1236
1237 async fn execute(&self, args: Value) -> Result<ToolResult> {
1238 let objective = args
1239 .get("objective")
1240 .and_then(|v| v.as_str())
1241 .ok_or_else(|| HeliosError::ToolError("Missing 'objective' parameter".to_string()))?;
1242
1243 let tasks_json = args
1244 .get("tasks")
1245 .and_then(|v| v.as_str())
1246 .ok_or_else(|| HeliosError::ToolError("Missing 'tasks' parameter".to_string()))?;
1247
1248 let tasks_array: Vec<Value> = serde_json::from_str(tasks_json)
1250 .map_err(|e| HeliosError::ToolError(format!("Invalid JSON for tasks: {}", e)))?;
1251
1252 let plan_id = format!("plan_{}", chrono::Utc::now().timestamp());
1253 let mut plan = TaskPlan::new(plan_id.clone(), objective.to_string());
1254
1255 for task_value in tasks_array {
1256 let task_obj = task_value.as_object().ok_or_else(|| {
1257 HeliosError::ToolError("Each task must be a JSON object".to_string())
1258 })?;
1259
1260 let id = task_obj
1261 .get("id")
1262 .and_then(|v| v.as_str())
1263 .ok_or_else(|| HeliosError::ToolError("Task missing 'id' field".to_string()))?
1264 .to_string();
1265
1266 let description = task_obj
1267 .get("description")
1268 .and_then(|v| v.as_str())
1269 .ok_or_else(|| {
1270 HeliosError::ToolError("Task missing 'description' field".to_string())
1271 })?
1272 .to_string();
1273
1274 let assigned_to = task_obj
1275 .get("assigned_to")
1276 .and_then(|v| v.as_str())
1277 .ok_or_else(|| {
1278 HeliosError::ToolError("Task missing 'assigned_to' field".to_string())
1279 })?
1280 .to_string();
1281
1282 let dependencies = task_obj
1283 .get("dependencies")
1284 .and_then(|v| v.as_array())
1285 .map(|arr| {
1286 arr.iter()
1287 .filter_map(|v| v.as_str().map(|s| s.to_string()))
1288 .collect::<Vec<String>>()
1289 })
1290 .unwrap_or_else(Vec::new);
1291
1292 let task = TaskItem::new(id, description, assigned_to).with_dependencies(dependencies);
1293 plan.add_task(task);
1294 }
1295
1296 let mut context = self.shared_context.write().await;
1297 context.set_plan(plan.clone());
1298
1299 let task_summary = plan
1300 .tasks_in_order()
1301 .iter()
1302 .map(|t| {
1303 format!(
1304 " • [{}] {} (assigned to: {})",
1305 t.id, t.description, t.assigned_to
1306 )
1307 })
1308 .collect::<Vec<_>>()
1309 .join("\n");
1310
1311 Ok(ToolResult::success(format!(
1312 "Plan created with {} tasks:\n{}",
1313 plan.tasks.len(),
1314 task_summary
1315 )))
1316 }
1317}
1318
1319pub struct ForestBuilder {
1321 config: Option<Config>,
1322 agents: Vec<(AgentId, AgentBuilder)>,
1323 max_iterations: usize,
1324}
1325
1326impl ForestBuilder {
1327 pub fn new() -> Self {
1329 Self {
1330 config: None,
1331 agents: Vec::new(),
1332 max_iterations: 10,
1333 }
1334 }
1335
1336 pub fn config(mut self, config: Config) -> Self {
1338 self.config = Some(config);
1339 self
1340 }
1341
1342 pub fn agent(mut self, id: AgentId, builder: AgentBuilder) -> Self {
1344 self.agents.push((id, builder));
1345 self
1346 }
1347
1348 pub fn agents(mut self, agents: Vec<(AgentId, AgentBuilder)>) -> Self {
1368 self.agents.extend(agents);
1369 self
1370 }
1371
1372 pub fn max_iterations(mut self, max: usize) -> Self {
1374 self.max_iterations = max;
1375 self
1376 }
1377
1378 pub async fn build(self) -> Result<ForestOfAgents> {
1380 let config = self
1381 .config
1382 .ok_or_else(|| HeliosError::AgentError("Config is required".to_string()))?;
1383
1384 let mut forest = ForestOfAgents::with_max_iterations(self.max_iterations);
1385
1386 for (id, builder) in self.agents {
1387 let agent = builder.config(config.clone()).build().await?;
1388 forest.add_agent(id, agent)?;
1389 }
1390
1391 Ok(forest)
1392 }
1393}
1394
1395impl Default for ForestBuilder {
1396 fn default() -> Self {
1397 Self::new()
1398 }
1399}
1400
1401#[cfg(test)]
1402mod tests {
1403 use super::*;
1404 use crate::config::Config;
1405 use crate::tools::Tool;
1406 use serde_json::Value;
1407
1408 #[tokio::test]
1410 async fn test_forest_creation_and_agent_management() {
1411 let mut forest = ForestOfAgents::new();
1412 let config = Config::new_default();
1413
1414 let agent1 = Agent::builder("agent1")
1416 .config(config.clone())
1417 .system_prompt("You are agent 1")
1418 .build()
1419 .await
1420 .unwrap();
1421
1422 let agent2 = Agent::builder("agent2")
1423 .config(config)
1424 .system_prompt("You are agent 2")
1425 .build()
1426 .await
1427 .unwrap();
1428
1429 forest.add_agent("agent1".to_string(), agent1).unwrap();
1431 forest.add_agent("agent2".to_string(), agent2).unwrap();
1432
1433 let agents = forest.list_agents();
1435 assert_eq!(agents.len(), 2);
1436 assert!(agents.contains(&"agent1".to_string()));
1437 assert!(agents.contains(&"agent2".to_string()));
1438
1439 assert!(forest.get_agent(&"agent1".to_string()).is_some());
1441 assert!(forest.get_agent(&"agent3".to_string()).is_none());
1442
1443 let agent3 = Agent::builder("agent3")
1445 .config(Config::new_default())
1446 .build()
1447 .await
1448 .unwrap();
1449 let result = forest.add_agent("agent1".to_string(), agent3);
1450 assert!(result.is_err());
1451
1452 let removed = forest.remove_agent(&"agent1".to_string());
1454 assert!(removed.is_some());
1455 assert_eq!(forest.list_agents().len(), 1);
1456 assert!(forest.get_agent(&"agent1".to_string()).is_none());
1457 }
1458
1459 #[tokio::test]
1461 async fn test_message_sending() {
1462 let mut forest = ForestOfAgents::new();
1463 let config = Config::new_default();
1464
1465 let agent1 = Agent::builder("alice")
1467 .config(config.clone())
1468 .build()
1469 .await
1470 .unwrap();
1471
1472 let agent2 = Agent::builder("bob").config(config).build().await.unwrap();
1473
1474 forest.add_agent("alice".to_string(), agent1).unwrap();
1475 forest.add_agent("bob".to_string(), agent2).unwrap();
1476
1477 forest
1479 .send_message(
1480 &"alice".to_string(),
1481 Some(&"bob".to_string()),
1482 "Hello Bob!".to_string(),
1483 )
1484 .await
1485 .unwrap();
1486
1487 forest.process_messages().await.unwrap();
1489
1490 let bob = forest.get_agent(&"bob".to_string()).unwrap();
1492 let messages = bob.chat_session().messages.clone();
1493 assert!(!messages.is_empty());
1494 let last_message = messages.last().unwrap();
1495 assert_eq!(last_message.role, crate::chat::Role::User);
1496 assert!(last_message
1497 .content
1498 .contains("Message from alice: Hello Bob!"));
1499
1500 let alice_message_count_before = forest
1502 .get_agent(&"alice".to_string())
1503 .unwrap()
1504 .chat_session()
1505 .messages
1506 .len();
1507 forest
1508 .send_message(&"alice".to_string(), None, "Hello everyone!".to_string())
1509 .await
1510 .unwrap();
1511 forest.process_messages().await.unwrap();
1512
1513 let alice = forest.get_agent(&"alice".to_string()).unwrap();
1515 assert_eq!(
1516 alice.chat_session().messages.len(),
1517 alice_message_count_before
1518 );
1519
1520 let bob = forest.get_agent(&"bob".to_string()).unwrap();
1521 let bob_messages = bob.chat_session().messages.clone();
1522 let bob_last = bob_messages.last().unwrap();
1523 assert!(bob_last
1524 .content
1525 .contains("Broadcast from alice: Hello everyone!"));
1526 }
1527
1528 #[tokio::test]
1530 async fn test_send_message_tool() {
1531 let message_queue = Arc::new(RwLock::new(Vec::<ForestMessage>::new()));
1532 let shared_context = Arc::new(RwLock::new(SharedContext::new()));
1533
1534 let tool = SendMessageTool::new(
1535 "alice".to_string(),
1536 message_queue.clone(),
1537 shared_context.clone(),
1538 );
1539
1540 let args = serde_json::json!({
1542 "to": "bob",
1543 "message": "Test message"
1544 });
1545
1546 let result = tool.execute(args).await.unwrap();
1547 assert!(result.success);
1548 assert_eq!(result.output, "Message sent successfully");
1549
1550 let queue = message_queue.read().await;
1552 assert_eq!(queue.len(), 1);
1553 let message = &queue[0];
1554 assert_eq!(message.from, "alice");
1555 assert_eq!(message.to, Some("bob".to_string()));
1556 assert_eq!(message.content, "Test message");
1557
1558 let context = shared_context.read().await;
1560 let messages = context.get_recent_messages(10);
1561 assert_eq!(messages.len(), 1);
1562 assert_eq!(messages[0].from, "alice");
1563
1564 }
1567
1568 #[tokio::test]
1570 async fn test_delegate_task_tool() {
1571 let message_queue = Arc::new(RwLock::new(Vec::new()));
1572 let shared_context = Arc::new(RwLock::new(SharedContext::new()));
1573
1574 let tool = DelegateTaskTool::new(
1575 "manager".to_string(),
1576 Arc::clone(&message_queue),
1577 Arc::clone(&shared_context),
1578 );
1579
1580 let args = serde_json::json!({
1582 "to": "worker",
1583 "task": "Analyze the data",
1584 "context": "Use statistical methods"
1585 });
1586
1587 let result = tool.execute(args).await.unwrap();
1588 assert!(result.success);
1589 assert_eq!(result.output, "Task delegated to agent 'worker'");
1590
1591 let queue = message_queue.read().await;
1593 assert_eq!(queue.len(), 1);
1594 let message = &queue[0];
1595 assert_eq!(message.from, "manager");
1596 assert_eq!(message.to, Some("worker".to_string()));
1597 assert!(message.content.contains("Task delegated: Analyze the data"));
1598 assert!(message.content.contains("Context: Use statistical methods"));
1599
1600 assert_eq!(
1602 message.metadata.get("type"),
1603 Some(&"task_delegation".to_string())
1604 );
1605 assert_eq!(
1606 message.metadata.get("task"),
1607 Some(&"Analyze the data".to_string())
1608 );
1609 }
1610
1611 #[tokio::test]
1613 async fn test_share_context_tool() {
1614 let shared_context = Arc::new(RwLock::new(SharedContext::new()));
1615
1616 let tool = ShareContextTool::new("researcher".to_string(), Arc::clone(&shared_context));
1617
1618 let args = serde_json::json!({
1620 "key": "findings",
1621 "value": "Temperature affects reaction rate",
1622 "description": "Key experimental finding"
1623 });
1624
1625 let result = tool.execute(args).await.unwrap();
1626 assert!(result.success);
1627 assert_eq!(result.output, "Information shared with key 'findings'");
1628
1629 let context = shared_context.read().await;
1631 let findings_data = context.get("findings").unwrap();
1632 let findings_obj = findings_data.as_object().unwrap();
1633
1634 assert_eq!(
1636 findings_obj.get("value").unwrap(),
1637 &Value::String("Temperature affects reaction rate".to_string())
1638 );
1639
1640 let metadata = findings_obj.get("metadata").unwrap();
1642 let metadata_obj = metadata.as_object().unwrap();
1643 assert_eq!(
1644 metadata_obj.get("shared_by").unwrap(),
1645 &Value::String("researcher".to_string())
1646 );
1647 assert_eq!(
1648 metadata_obj.get("description").unwrap(),
1649 &Value::String("Key experimental finding".to_string())
1650 );
1651 assert!(metadata_obj.contains_key("timestamp"));
1652 }
1653
1654 #[tokio::test]
1656 async fn test_shared_context() {
1657 let mut context = SharedContext::new();
1658
1659 context.set("key1".to_string(), Value::String("value1".to_string()));
1661 context.set("key2".to_string(), Value::Number(42.into()));
1662
1663 assert_eq!(
1664 context.get("key1"),
1665 Some(&Value::String("value1".to_string()))
1666 );
1667 assert_eq!(context.get("key2"), Some(&Value::Number(42.into())));
1668 assert_eq!(context.get("key3"), None);
1669
1670 let msg1 = ForestMessage::new(
1672 "alice".to_string(),
1673 Some("bob".to_string()),
1674 "Hello".to_string(),
1675 );
1676 let msg2 = ForestMessage::broadcast("bob".to_string(), "Hi everyone".to_string());
1677
1678 context.add_message(msg1);
1679 context.add_message(msg2);
1680
1681 let messages = context.get_recent_messages(10);
1682 assert_eq!(messages.len(), 2);
1683 assert_eq!(messages[0].from, "alice");
1684 assert_eq!(messages[1].from, "bob");
1685
1686 let removed = context.remove("key1");
1688 assert_eq!(removed, Some(Value::String("value1".to_string())));
1689 assert_eq!(context.get("key1"), None);
1690 }
1691
1692 #[tokio::test]
1694 async fn test_collaborative_task() {
1695 let mut forest = ForestOfAgents::new();
1696 let config = Config::new_default();
1697
1698 let coordinator = Agent::builder("coordinator")
1700 .config(config.clone())
1701 .system_prompt(
1702 "You are a task coordinator. Break down tasks and delegate to specialists.",
1703 )
1704 .build()
1705 .await
1706 .unwrap();
1707
1708 let researcher = Agent::builder("researcher")
1709 .config(config.clone())
1710 .system_prompt("You are a researcher. Gather and analyze information.")
1711 .build()
1712 .await
1713 .unwrap();
1714
1715 let writer = Agent::builder("writer")
1716 .config(config)
1717 .system_prompt("You are a writer. Create clear, well-structured content.")
1718 .build()
1719 .await
1720 .unwrap();
1721
1722 forest
1723 .add_agent("coordinator".to_string(), coordinator)
1724 .unwrap();
1725 forest
1726 .add_agent("researcher".to_string(), researcher)
1727 .unwrap();
1728 forest.add_agent("writer".to_string(), writer).unwrap();
1729
1730 assert_eq!(forest.list_agents().len(), 3);
1739 assert!(forest.get_agent(&"coordinator".to_string()).is_some());
1740 assert!(forest.get_agent(&"researcher".to_string()).is_some());
1741 assert!(forest.get_agent(&"writer".to_string()).is_some());
1742
1743 forest
1748 .set_shared_context(
1749 "current_task".to_string(),
1750 Value::String("Create a report on climate change impacts".to_string()),
1751 )
1752 .await;
1753 forest
1754 .set_shared_context(
1755 "involved_agents".to_string(),
1756 Value::Array(vec![
1757 Value::String("researcher".to_string()),
1758 Value::String("writer".to_string()),
1759 ]),
1760 )
1761 .await;
1762 forest
1763 .set_shared_context(
1764 "task_status".to_string(),
1765 Value::String("in_progress".to_string()),
1766 )
1767 .await;
1768
1769 let context = forest.get_shared_context().await;
1771 assert_eq!(
1772 context.get("task_status"),
1773 Some(&Value::String("in_progress".to_string()))
1774 );
1775 assert!(context.get("current_task").is_some());
1776 assert!(context.get("involved_agents").is_some());
1777 }
1778
1779 #[tokio::test]
1781 async fn test_forest_builder() {
1782 let config = Config::new_default();
1783
1784 let forest = ForestBuilder::new()
1785 .config(config)
1786 .agent(
1787 "agent1".to_string(),
1788 Agent::builder("agent1").system_prompt("Agent 1 prompt"),
1789 )
1790 .agent(
1791 "agent2".to_string(),
1792 Agent::builder("agent2").system_prompt("Agent 2 prompt"),
1793 )
1794 .max_iterations(5)
1795 .build()
1796 .await
1797 .unwrap();
1798
1799 assert_eq!(forest.list_agents().len(), 2);
1800 assert!(forest.get_agent(&"agent1".to_string()).is_some());
1801 assert!(forest.get_agent(&"agent2".to_string()).is_some());
1802 assert_eq!(forest.max_iterations, 5);
1803 }
1804
1805 #[tokio::test]
1807 async fn test_forest_error_handling() {
1808 let mut forest = ForestOfAgents::new();
1809
1810 let result = forest
1812 .send_message(
1813 &"nonexistent".to_string(),
1814 Some(&"target".to_string()),
1815 "test".to_string(),
1816 )
1817 .await;
1818 assert!(result.is_err());
1819
1820 let result = forest
1822 .execute_collaborative_task(&"nonexistent".to_string(), "test task".to_string(), vec![])
1823 .await;
1824 assert!(result.is_err());
1825
1826 let config = Config::new_default();
1828 let agent = Agent::builder("real_agent")
1829 .config(config)
1830 .build()
1831 .await
1832 .unwrap();
1833 forest.add_agent("real_agent".to_string(), agent).unwrap();
1834
1835 let result = forest
1836 .execute_collaborative_task(
1837 &"real_agent".to_string(),
1838 "test task".to_string(),
1839 vec!["nonexistent".to_string()],
1840 )
1841 .await;
1842 assert!(result.is_err());
1843 }
1844
1845 #[tokio::test]
1847 async fn test_forest_message() {
1848 let msg = ForestMessage::new(
1850 "alice".to_string(),
1851 Some("bob".to_string()),
1852 "Hello".to_string(),
1853 );
1854 assert_eq!(msg.from, "alice");
1855 assert_eq!(msg.to, Some("bob".to_string()));
1856 assert_eq!(msg.content, "Hello");
1857
1858 let broadcast = ForestMessage::broadcast("alice".to_string(), "Announcement".to_string());
1860 assert_eq!(broadcast.from, "alice");
1861 assert!(broadcast.to.is_none());
1862 assert_eq!(broadcast.content, "Announcement");
1863
1864 let msg_with_meta = msg.with_metadata("priority".to_string(), "high".to_string());
1866 assert_eq!(
1867 msg_with_meta.metadata.get("priority"),
1868 Some(&"high".to_string())
1869 );
1870 }
1871}