1use crate::types::AgentRole;
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
16#[serde(tag = "type", rename_all = "snake_case")]
17pub enum CollaborationPattern {
18 Pipeline {
21 stages: Vec<PipelineStage>,
23 },
24
25 MapReduce {
28 mapper_role: AgentRole,
30 reducer_role: AgentRole,
32 chunk_count: usize,
34 },
35
36 Debate {
39 proponent: AgentRole,
41 opponent: AgentRole,
43 judge: AgentRole,
45 max_rounds: u32,
47 },
48
49 Ensemble {
52 agents: Vec<AgentRole>,
54 aggregation: AggregationStrategy,
56 },
57
58 Supervisor {
61 supervisor: AgentRole,
63 workers: Vec<AgentRole>,
65 review_policy: ReviewPolicy,
67 },
68
69 Swarm {
71 roles: Vec<AgentRole>,
73 max_iterations: u32,
75 convergence_threshold: f64,
77 },
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct PipelineStage {
87 pub role: AgentRole,
89 pub description: String,
91 pub transform: Option<String>,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
97#[serde(tag = "strategy", rename_all = "snake_case")]
98pub enum AggregationStrategy {
99 MajorityVote,
101 BestOfN {
103 metric: String,
105 },
106 Concatenate,
108 LlmSynthesize,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
114#[serde(tag = "policy", rename_all = "snake_case")]
115pub enum ReviewPolicy {
116 AlwaysReview,
118 SamplePercent(f64),
120 OnError,
122 Never,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct PatternConfig {
129 pub pattern: CollaborationPattern,
131 pub timeout_secs: u64,
133 pub max_retries: u32,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct PatternResult {
140 pub pattern_name: String,
142 pub stages_completed: u32,
144 pub total_stages: u32,
146 pub artifacts: Vec<String>,
148 pub consensus_reached: bool,
150 pub final_output: String,
152}
153
154#[derive(Debug, Clone)]
160pub struct PatternConfigBuilder {
161 pattern: CollaborationPattern,
162 timeout_secs: u64,
163 max_retries: u32,
164}
165
166impl PatternConfig {
167 pub fn pipeline() -> PatternConfigBuilder {
169 PatternConfigBuilder {
170 pattern: CollaborationPattern::Pipeline { stages: Vec::new() },
171 timeout_secs: 300,
172 max_retries: 1,
173 }
174 }
175
176 pub fn debate() -> PatternConfigBuilder {
178 PatternConfigBuilder {
179 pattern: CollaborationPattern::Debate {
180 proponent: AgentRole::Coder,
181 opponent: AgentRole::Reviewer,
182 judge: AgentRole::Architect,
183 max_rounds: 3,
184 },
185 timeout_secs: 300,
186 max_retries: 1,
187 }
188 }
189
190 pub fn ensemble() -> PatternConfigBuilder {
192 PatternConfigBuilder {
193 pattern: CollaborationPattern::Ensemble {
194 agents: Vec::new(),
195 aggregation: AggregationStrategy::MajorityVote,
196 },
197 timeout_secs: 300,
198 max_retries: 1,
199 }
200 }
201
202 pub fn map_reduce() -> PatternConfigBuilder {
204 PatternConfigBuilder {
205 pattern: CollaborationPattern::MapReduce {
206 mapper_role: AgentRole::Coder,
207 reducer_role: AgentRole::Orchestrator,
208 chunk_count: 4,
209 },
210 timeout_secs: 600,
211 max_retries: 1,
212 }
213 }
214
215 pub fn supervisor() -> PatternConfigBuilder {
217 PatternConfigBuilder {
218 pattern: CollaborationPattern::Supervisor {
219 supervisor: AgentRole::Reviewer,
220 workers: Vec::new(),
221 review_policy: ReviewPolicy::AlwaysReview,
222 },
223 timeout_secs: 300,
224 max_retries: 1,
225 }
226 }
227
228 pub fn swarm() -> PatternConfigBuilder {
230 PatternConfigBuilder {
231 pattern: CollaborationPattern::Swarm {
232 roles: Vec::new(),
233 max_iterations: 10,
234 convergence_threshold: 0.9,
235 },
236 timeout_secs: 600,
237 max_retries: 1,
238 }
239 }
240}
241
242impl PatternConfigBuilder {
243 pub fn with_timeout(mut self, secs: u64) -> Self {
245 self.timeout_secs = secs;
246 self
247 }
248
249 pub fn with_retries(mut self, n: u32) -> Self {
251 self.max_retries = n;
252 self
253 }
254
255 pub fn add_stage(mut self, stage: PipelineStage) -> Self {
257 if let CollaborationPattern::Pipeline { ref mut stages } = self.pattern {
258 stages.push(stage);
259 }
260 self
261 }
262
263 pub fn add_agent(mut self, role: AgentRole) -> Self {
265 if let CollaborationPattern::Ensemble { ref mut agents, .. } = self.pattern {
266 agents.push(role);
267 }
268 self
269 }
270
271 pub fn with_aggregation(mut self, strategy: AggregationStrategy) -> Self {
273 if let CollaborationPattern::Ensemble {
274 ref mut aggregation,
275 ..
276 } = self.pattern
277 {
278 *aggregation = strategy;
279 }
280 self
281 }
282
283 pub fn with_debate_roles(
285 mut self,
286 proponent: AgentRole,
287 opponent: AgentRole,
288 judge: AgentRole,
289 ) -> Self {
290 if let CollaborationPattern::Debate {
291 proponent: ref mut p,
292 opponent: ref mut o,
293 judge: ref mut j,
294 ..
295 } = self.pattern
296 {
297 *p = proponent;
298 *o = opponent;
299 *j = judge;
300 }
301 self
302 }
303
304 pub fn with_max_rounds(mut self, rounds: u32) -> Self {
306 if let CollaborationPattern::Debate {
307 ref mut max_rounds, ..
308 } = self.pattern
309 {
310 *max_rounds = rounds;
311 }
312 self
313 }
314
315 pub fn add_worker(mut self, role: AgentRole) -> Self {
317 if let CollaborationPattern::Supervisor {
318 ref mut workers, ..
319 } = self.pattern
320 {
321 workers.push(role);
322 }
323 self
324 }
325
326 pub fn with_review_policy(mut self, policy: ReviewPolicy) -> Self {
328 if let CollaborationPattern::Supervisor {
329 ref mut review_policy,
330 ..
331 } = self.pattern
332 {
333 *review_policy = policy;
334 }
335 self
336 }
337
338 pub fn with_mapper_reducer(
340 mut self,
341 mapper: AgentRole,
342 reducer: AgentRole,
343 chunks: usize,
344 ) -> Self {
345 if let CollaborationPattern::MapReduce {
346 ref mut mapper_role,
347 ref mut reducer_role,
348 ref mut chunk_count,
349 } = self.pattern
350 {
351 *mapper_role = mapper;
352 *reducer_role = reducer;
353 *chunk_count = chunks;
354 }
355 self
356 }
357
358 pub fn add_swarm_role(mut self, role: AgentRole) -> Self {
360 if let CollaborationPattern::Swarm { ref mut roles, .. } = self.pattern {
361 roles.push(role);
362 }
363 self
364 }
365
366 pub fn with_convergence(mut self, max_iterations: u32, threshold: f64) -> Self {
368 if let CollaborationPattern::Swarm {
369 max_iterations: ref mut mi,
370 convergence_threshold: ref mut ct,
371 ..
372 } = self.pattern
373 {
374 *mi = max_iterations;
375 *ct = threshold;
376 }
377 self
378 }
379
380 pub fn build(self) -> PatternConfig {
382 PatternConfig {
383 pattern: self.pattern,
384 timeout_secs: self.timeout_secs,
385 max_retries: self.max_retries,
386 }
387 }
388}
389
390pub fn describe_pattern(pattern: &CollaborationPattern) -> String {
396 match pattern {
397 CollaborationPattern::Pipeline { stages } => {
398 let names: Vec<String> = stages.iter().map(|s| s.role.to_string()).collect();
399 format!(
400 "Pipeline with {} stages: {}",
401 stages.len(),
402 names.join(" -> ")
403 )
404 }
405 CollaborationPattern::MapReduce {
406 mapper_role,
407 reducer_role,
408 chunk_count,
409 } => {
410 format!("MapReduce: {chunk_count} x {mapper_role} mappers -> {reducer_role} reducer")
411 }
412 CollaborationPattern::Debate {
413 proponent,
414 opponent,
415 judge,
416 max_rounds,
417 } => {
418 format!(
419 "Debate: {proponent} vs {opponent}, judged by {judge} (max {max_rounds} rounds)"
420 )
421 }
422 CollaborationPattern::Ensemble {
423 agents,
424 aggregation,
425 } => {
426 let names: Vec<String> = agents
427 .iter()
428 .map(std::string::ToString::to_string)
429 .collect();
430 let strat = match aggregation {
431 AggregationStrategy::MajorityVote => "majority vote",
432 AggregationStrategy::BestOfN { metric } => {
433 return format!(
434 "Ensemble of {} agents [{}], aggregated by best-of-N (metric: {metric})",
435 agents.len(),
436 names.join(", ")
437 );
438 }
439 AggregationStrategy::Concatenate => "concatenation",
440 AggregationStrategy::LlmSynthesize => "LLM synthesis",
441 };
442 format!(
443 "Ensemble of {} agents [{}], aggregated by {strat}",
444 agents.len(),
445 names.join(", ")
446 )
447 }
448 CollaborationPattern::Supervisor {
449 supervisor,
450 workers,
451 review_policy,
452 } => {
453 let worker_names: Vec<String> = workers
454 .iter()
455 .map(std::string::ToString::to_string)
456 .collect();
457 let policy = match review_policy {
458 ReviewPolicy::AlwaysReview => "always review",
459 ReviewPolicy::SamplePercent(p) => {
460 return format!(
461 "Supervisor {supervisor} overseeing [{}], reviewing {:.0}% of outputs",
462 worker_names.join(", "),
463 p * 100.0
464 );
465 }
466 ReviewPolicy::OnError => "review on error",
467 ReviewPolicy::Never => "no review",
468 };
469 format!(
470 "Supervisor {supervisor} overseeing [{}], policy: {policy}",
471 worker_names.join(", ")
472 )
473 }
474 CollaborationPattern::Swarm {
475 roles,
476 max_iterations,
477 convergence_threshold,
478 } => {
479 let names: Vec<String> = roles.iter().map(std::string::ToString::to_string).collect();
480 format!(
481 "Swarm of {} agents [{}], max {max_iterations} iterations, convergence >= {convergence_threshold}",
482 roles.len(),
483 names.join(", ")
484 )
485 }
486 }
487}
488
489pub fn validate_pattern(pattern: &CollaborationPattern) -> Result<(), String> {
493 match pattern {
494 CollaborationPattern::Pipeline { stages } => {
495 if stages.is_empty() {
496 return Err("Pipeline must have at least one stage".into());
497 }
498 Ok(())
499 }
500 CollaborationPattern::MapReduce { chunk_count, .. } => {
501 if *chunk_count == 0 {
502 return Err("MapReduce chunk_count must be > 0".into());
503 }
504 Ok(())
505 }
506 CollaborationPattern::Debate {
507 max_rounds,
508 proponent,
509 opponent,
510 ..
511 } => {
512 if *max_rounds == 0 {
513 return Err("Debate must have at least 1 round".into());
514 }
515 if proponent == opponent {
516 return Err("Debate proponent and opponent must be different roles".into());
517 }
518 Ok(())
519 }
520 CollaborationPattern::Ensemble { agents, .. } => {
521 if agents.len() < 2 {
522 return Err("Ensemble requires at least 2 agents".into());
523 }
524 Ok(())
525 }
526 CollaborationPattern::Supervisor { workers, .. } => {
527 if workers.is_empty() {
528 return Err("Supervisor pattern requires at least one worker".into());
529 }
530 Ok(())
531 }
532 CollaborationPattern::Swarm {
533 roles,
534 max_iterations,
535 convergence_threshold,
536 } => {
537 if roles.len() < 2 {
538 return Err("Swarm requires at least 2 agents".into());
539 }
540 if *max_iterations == 0 {
541 return Err("Swarm must have at least 1 iteration".into());
542 }
543 if *convergence_threshold <= 0.0 || *convergence_threshold > 1.0 {
544 return Err("Swarm convergence_threshold must be in (0.0, 1.0]".into());
545 }
546 Ok(())
547 }
548 }
549}
550
551pub fn estimate_cost(pattern: &CollaborationPattern, tokens_per_agent: u64) -> u64 {
561 match pattern {
562 CollaborationPattern::Pipeline { stages } => stages.len() as u64 * tokens_per_agent,
563 CollaborationPattern::MapReduce { chunk_count, .. } => {
564 (*chunk_count as u64 + 1) * tokens_per_agent
565 }
566 CollaborationPattern::Debate { max_rounds, .. } => {
567 (2 * *max_rounds as u64 + 1) * tokens_per_agent
569 }
570 CollaborationPattern::Ensemble {
571 agents,
572 aggregation,
573 } => {
574 let base = agents.len() as u64 * tokens_per_agent;
575 match aggregation {
576 AggregationStrategy::LlmSynthesize => base + tokens_per_agent,
577 _ => base,
578 }
579 }
580 CollaborationPattern::Supervisor {
581 workers,
582 review_policy,
583 ..
584 } => {
585 let worker_cost = workers.len() as u64 * tokens_per_agent;
586 let review_cost = match review_policy {
587 ReviewPolicy::AlwaysReview => workers.len() as u64 * tokens_per_agent,
588 ReviewPolicy::SamplePercent(p) => {
589 (workers.len() as f64 * p * tokens_per_agent as f64) as u64
590 }
591 ReviewPolicy::OnError => tokens_per_agent, ReviewPolicy::Never => 0,
593 };
594 worker_cost + review_cost
595 }
596 CollaborationPattern::Swarm {
597 roles,
598 max_iterations,
599 ..
600 } => roles.len() as u64 * *max_iterations as u64 * tokens_per_agent,
601 }
602}
603
604#[cfg(test)]
609#[allow(clippy::unwrap_used, clippy::expect_used)]
610mod tests {
611 use super::*;
612
613 #[test]
616 fn test_pipeline_describe() {
617 let pattern = CollaborationPattern::Pipeline {
618 stages: vec![
619 PipelineStage {
620 role: AgentRole::Spec,
621 description: "Generate spec".into(),
622 transform: None,
623 },
624 PipelineStage {
625 role: AgentRole::Coder,
626 description: "Implement".into(),
627 transform: Some("extract_code".into()),
628 },
629 ],
630 };
631 let desc = describe_pattern(&pattern);
632 assert!(desc.contains("Pipeline"));
633 assert!(desc.contains("2 stages"));
634 assert!(desc.contains("spec -> coder"));
635 }
636
637 #[test]
638 fn test_pipeline_validate_empty() {
639 let pattern = CollaborationPattern::Pipeline { stages: Vec::new() };
640 assert!(validate_pattern(&pattern).is_err());
641 }
642
643 #[test]
644 fn test_pipeline_validate_ok() {
645 let pattern = CollaborationPattern::Pipeline {
646 stages: vec![PipelineStage {
647 role: AgentRole::Coder,
648 description: "Code it".into(),
649 transform: None,
650 }],
651 };
652 assert!(validate_pattern(&pattern).is_ok());
653 }
654
655 #[test]
656 fn test_pipeline_estimate_cost() {
657 let pattern = CollaborationPattern::Pipeline {
658 stages: vec![
659 PipelineStage {
660 role: AgentRole::Spec,
661 description: "Spec".into(),
662 transform: None,
663 },
664 PipelineStage {
665 role: AgentRole::Coder,
666 description: "Code".into(),
667 transform: None,
668 },
669 PipelineStage {
670 role: AgentRole::Tester,
671 description: "Test".into(),
672 transform: None,
673 },
674 ],
675 };
676 assert_eq!(estimate_cost(&pattern, 1000), 3000);
677 }
678
679 #[test]
682 fn test_map_reduce_validate_zero_chunks() {
683 let pattern = CollaborationPattern::MapReduce {
684 mapper_role: AgentRole::Coder,
685 reducer_role: AgentRole::Orchestrator,
686 chunk_count: 0,
687 };
688 assert!(validate_pattern(&pattern).is_err());
689 }
690
691 #[test]
692 fn test_map_reduce_estimate() {
693 let pattern = CollaborationPattern::MapReduce {
694 mapper_role: AgentRole::Coder,
695 reducer_role: AgentRole::Orchestrator,
696 chunk_count: 4,
697 };
698 assert_eq!(estimate_cost(&pattern, 500), 2500);
700 }
701
702 #[test]
705 fn test_debate_validate_same_roles() {
706 let pattern = CollaborationPattern::Debate {
707 proponent: AgentRole::Coder,
708 opponent: AgentRole::Coder,
709 judge: AgentRole::Architect,
710 max_rounds: 3,
711 };
712 assert!(validate_pattern(&pattern).is_err());
713 }
714
715 #[test]
716 fn test_debate_validate_zero_rounds() {
717 let pattern = CollaborationPattern::Debate {
718 proponent: AgentRole::Coder,
719 opponent: AgentRole::Reviewer,
720 judge: AgentRole::Architect,
721 max_rounds: 0,
722 };
723 assert!(validate_pattern(&pattern).is_err());
724 }
725
726 #[test]
727 fn test_debate_estimate() {
728 let pattern = CollaborationPattern::Debate {
729 proponent: AgentRole::Coder,
730 opponent: AgentRole::Reviewer,
731 judge: AgentRole::Architect,
732 max_rounds: 3,
733 };
734 assert_eq!(estimate_cost(&pattern, 1000), 7000);
736 }
737
738 #[test]
741 fn test_ensemble_validate_too_few() {
742 let pattern = CollaborationPattern::Ensemble {
743 agents: vec![AgentRole::Coder],
744 aggregation: AggregationStrategy::MajorityVote,
745 };
746 assert!(validate_pattern(&pattern).is_err());
747 }
748
749 #[test]
750 fn test_ensemble_llm_synthesize_extra_cost() {
751 let pattern = CollaborationPattern::Ensemble {
752 agents: vec![AgentRole::Coder, AgentRole::Reviewer, AgentRole::Architect],
753 aggregation: AggregationStrategy::LlmSynthesize,
754 };
755 assert_eq!(estimate_cost(&pattern, 1000), 4000);
757 }
758
759 #[test]
762 fn test_supervisor_validate_no_workers() {
763 let pattern = CollaborationPattern::Supervisor {
764 supervisor: AgentRole::Reviewer,
765 workers: Vec::new(),
766 review_policy: ReviewPolicy::AlwaysReview,
767 };
768 assert!(validate_pattern(&pattern).is_err());
769 }
770
771 #[test]
772 fn test_supervisor_estimate_always_review() {
773 let pattern = CollaborationPattern::Supervisor {
774 supervisor: AgentRole::Reviewer,
775 workers: vec![AgentRole::Coder, AgentRole::Tester],
776 review_policy: ReviewPolicy::AlwaysReview,
777 };
778 assert_eq!(estimate_cost(&pattern, 1000), 4000);
780 }
781
782 #[test]
785 fn test_swarm_validate_threshold_out_of_range() {
786 let pattern = CollaborationPattern::Swarm {
787 roles: vec![AgentRole::Coder, AgentRole::Reviewer],
788 max_iterations: 5,
789 convergence_threshold: 1.5,
790 };
791 assert!(validate_pattern(&pattern).is_err());
792 }
793
794 #[test]
795 fn test_swarm_validate_zero_threshold() {
796 let pattern = CollaborationPattern::Swarm {
797 roles: vec![AgentRole::Coder, AgentRole::Reviewer],
798 max_iterations: 5,
799 convergence_threshold: 0.0,
800 };
801 assert!(validate_pattern(&pattern).is_err());
802 }
803
804 #[test]
805 fn test_swarm_estimate() {
806 let pattern = CollaborationPattern::Swarm {
807 roles: vec![AgentRole::Coder, AgentRole::Reviewer, AgentRole::Tester],
808 max_iterations: 10,
809 convergence_threshold: 0.9,
810 };
811 assert_eq!(estimate_cost(&pattern, 500), 15000);
813 }
814
815 #[test]
818 fn test_builder_pipeline() {
819 let config = PatternConfig::pipeline()
820 .add_stage(PipelineStage {
821 role: AgentRole::Spec,
822 description: "Requirements".into(),
823 transform: None,
824 })
825 .add_stage(PipelineStage {
826 role: AgentRole::Coder,
827 description: "Implementation".into(),
828 transform: Some("extract_rust".into()),
829 })
830 .with_timeout(120)
831 .with_retries(3)
832 .build();
833
834 assert_eq!(config.timeout_secs, 120);
835 assert_eq!(config.max_retries, 3);
836 if let CollaborationPattern::Pipeline { stages } = &config.pattern {
837 assert_eq!(stages.len(), 2);
838 assert_eq!(stages[0].role, AgentRole::Spec);
839 assert_eq!(stages[1].transform.as_deref(), Some("extract_rust"));
840 } else {
841 panic!("Expected Pipeline pattern");
842 }
843 }
844
845 #[test]
846 fn test_builder_debate() {
847 let config = PatternConfig::debate()
848 .with_debate_roles(
849 AgentRole::Architect,
850 AgentRole::SecurityAuditor,
851 AgentRole::Reviewer,
852 )
853 .with_max_rounds(5)
854 .with_timeout(600)
855 .build();
856
857 if let CollaborationPattern::Debate {
858 proponent,
859 opponent,
860 judge,
861 max_rounds,
862 } = &config.pattern
863 {
864 assert_eq!(*proponent, AgentRole::Architect);
865 assert_eq!(*opponent, AgentRole::SecurityAuditor);
866 assert_eq!(*judge, AgentRole::Reviewer);
867 assert_eq!(*max_rounds, 5);
868 } else {
869 panic!("Expected Debate pattern");
870 }
871 }
872
873 #[test]
874 fn test_builder_ensemble() {
875 let config = PatternConfig::ensemble()
876 .add_agent(AgentRole::Coder)
877 .add_agent(AgentRole::Reviewer)
878 .add_agent(AgentRole::Architect)
879 .with_aggregation(AggregationStrategy::BestOfN {
880 metric: "accuracy".into(),
881 })
882 .build();
883
884 if let CollaborationPattern::Ensemble {
885 agents,
886 aggregation,
887 } = &config.pattern
888 {
889 assert_eq!(agents.len(), 3);
890 if let AggregationStrategy::BestOfN { metric } = aggregation {
891 assert_eq!(metric, "accuracy");
892 } else {
893 panic!("Expected BestOfN strategy");
894 }
895 } else {
896 panic!("Expected Ensemble pattern");
897 }
898 }
899
900 #[test]
903 fn test_pattern_config_roundtrip() {
904 let config = PatternConfig::debate()
905 .with_debate_roles(AgentRole::Coder, AgentRole::Reviewer, AgentRole::Architect)
906 .with_max_rounds(3)
907 .with_timeout(180)
908 .with_retries(2)
909 .build();
910
911 let json = serde_json::to_string(&config).unwrap();
912 let parsed: PatternConfig = serde_json::from_str(&json).unwrap();
913
914 assert_eq!(parsed.timeout_secs, 180);
915 assert_eq!(parsed.max_retries, 2);
916 if let CollaborationPattern::Debate { max_rounds, .. } = &parsed.pattern {
917 assert_eq!(*max_rounds, 3);
918 } else {
919 panic!("Expected Debate after deserialization");
920 }
921 }
922
923 #[test]
924 fn test_pattern_result_construction() {
925 let result = PatternResult {
926 pattern_name: "debate".into(),
927 stages_completed: 6,
928 total_stages: 7,
929 artifacts: vec!["spec.md".into(), "review.md".into()],
930 consensus_reached: true,
931 final_output: "Use approach A".into(),
932 };
933 assert!(result.consensus_reached);
934 assert_eq!(result.artifacts.len(), 2);
935 assert_eq!(result.stages_completed, 6);
936 }
937
938 #[test]
939 fn test_describe_all_patterns() {
940 let patterns = vec![
942 CollaborationPattern::Pipeline {
943 stages: vec![PipelineStage {
944 role: AgentRole::Coder,
945 description: "code".into(),
946 transform: None,
947 }],
948 },
949 CollaborationPattern::MapReduce {
950 mapper_role: AgentRole::Coder,
951 reducer_role: AgentRole::Orchestrator,
952 chunk_count: 3,
953 },
954 CollaborationPattern::Debate {
955 proponent: AgentRole::Coder,
956 opponent: AgentRole::Reviewer,
957 judge: AgentRole::Architect,
958 max_rounds: 2,
959 },
960 CollaborationPattern::Ensemble {
961 agents: vec![AgentRole::Coder, AgentRole::Reviewer],
962 aggregation: AggregationStrategy::Concatenate,
963 },
964 CollaborationPattern::Supervisor {
965 supervisor: AgentRole::Reviewer,
966 workers: vec![AgentRole::Coder],
967 review_policy: ReviewPolicy::SamplePercent(0.5),
968 },
969 CollaborationPattern::Swarm {
970 roles: vec![AgentRole::Coder, AgentRole::Tester],
971 max_iterations: 5,
972 convergence_threshold: 0.8,
973 },
974 ];
975
976 for p in &patterns {
977 let desc = describe_pattern(p);
978 assert!(!desc.is_empty(), "Description should not be empty");
979 }
980 }
981}