Skip to main content

ravenclaws/
patterns.rs

1//! Multi-agent pattern primitives
2//!
3//! Built-in multi-agent collaboration patterns that extend beyond simple
4//! swarm/supervisor modes. Each pattern implements a distinct collaboration
5//! strategy:
6//!
7//! - **Debate** — Multiple agents argue different positions, then converge
8//! - **Review-Loop** — One agent produces, another reviews, iterating to quality
9//! - **Research-Synthesize** — Parallel research agents feed a synthesizer
10//! - **Voting** — Multiple agents vote on options, majority decides
11//! - **Tree-of-Thought** — Multiple parallel reasoning paths with evaluation and pruning
12//! - **Self-Reflection** — Agent generates output, reflects, and improves iteratively
13//!
14//! These patterns are first-class modes accessible via `--mode debate`,
15//! `--mode review-loop`, `--mode research-synthesize`, `--mode voting`,
16//! `--mode tree-of-thought`, or `--mode self-reflection`.
17
18use std::sync::{Arc, Mutex};
19use tracing::{info, warn};
20
21use crate::agent::ConversationMemory;
22use crate::config::Config;
23use crate::error::RavenClawsError;
24use crate::healing::SelfHealingEngine;
25use crate::llm::{LLMProviderTrait, MultiModelManager};
26use crate::ravenfabric::RavenFabricClient;
27
28// ── Pattern Configuration ──────────────────────────────────────────────────
29
30/// Configuration for multi-agent patterns
31#[derive(Debug, Clone)]
32pub struct PatternConfig {
33    /// Maximum debate rounds (default: 3)
34    pub max_rounds: usize,
35    /// Maximum review iterations (default: 3)
36    pub max_review_iterations: usize,
37    /// Number of research agents (default: 3)
38    pub research_agent_count: usize,
39    /// Number of voters (default: 3)
40    pub voter_count: usize,
41    /// Whether to show intermediate results
42    pub verbose: bool,
43    /// Tree-of-thought: number of branches per step (default: 3)
44    pub tot_branches: usize,
45    /// Tree-of-thought: maximum depth (default: 3)
46    pub tot_depth: usize,
47    /// Tree-of-thought: top-k branches to keep after pruning (default: 2)
48    pub tot_top_k: usize,
49    /// Self-reflection: number of reflection rounds (default: 2)
50    pub reflection_rounds: usize,
51}
52
53impl Default for PatternConfig {
54    fn default() -> Self {
55        Self {
56            max_rounds: 3,
57            max_review_iterations: 3,
58            research_agent_count: 3,
59            voter_count: 3,
60            verbose: false,
61            tot_branches: 3,
62            tot_depth: 3,
63            tot_top_k: 2,
64            reflection_rounds: 2,
65        }
66    }
67}
68
69// ── Pattern 1: Debate ──────────────────────────────────────────────────────
70
71/// Run a debate between multiple agents with different positions.
72///
73/// Each agent argues a distinct position, then a judge synthesizes the
74/// final conclusion after configurable rounds of debate.
75pub async fn run_debate(
76    llm: Arc<dyn LLMProviderTrait>,
77    config: Config,
78    ravenfabric: Option<RavenFabricClient>,
79    pattern_config: PatternConfig,
80    healing_engine: Option<Arc<Mutex<SelfHealingEngine>>>,
81) -> crate::error::Result<()> {
82    info!(
83        "Starting debate mode with {} max rounds",
84        pattern_config.max_rounds
85    );
86
87    let system_prompt = &config.llm.system_prompt;
88    let task = "Analyze the given task and provide your solution.";
89
90    // Define debate positions
91    let positions = [
92        ("Proponent", "You argue FOR the proposition. Focus on benefits, opportunities, and strengths. Be persuasive and evidence-based."),
93        ("Opponent", "You argue AGAINST the proposition. Focus on risks, drawbacks, and weaknesses. Be critical and thorough."),
94        ("Synthesizer", "You are the neutral judge. Listen to both sides, identify common ground, and synthesize a balanced conclusion."),
95    ];
96
97    let mut debate_history = ConversationMemory::new(system_prompt, 50);
98    debate_history.add_user_message(&format!(
99        "Debate topic: {}\n\nProponent, present your opening argument.",
100        task
101    ));
102
103    // Debate rounds
104    for round in 0..pattern_config.max_rounds {
105        info!(round = round + 1, "Debate round starting");
106
107        for (role, persona) in &positions {
108            if *role == "Synthesizer" && round < pattern_config.max_rounds - 1 {
109                continue; // Synthesizer only speaks in final round
110            }
111
112            let mut agent_memory = ConversationMemory::new(persona, 20);
113            // Feed debate history
114            for msg in debate_history.history() {
115                if msg.role == "system" {
116                    continue;
117                }
118                if msg.role == "user" {
119                    agent_memory.add_user_message(&msg.content);
120                } else {
121                    agent_memory.add_assistant_message(&msg.content);
122                }
123            }
124
125            // Check self-healing circuit breaker
126            if let Some(ref healing) = healing_engine {
127                let healthy = {
128                    let mut engine = healing.lock().unwrap();
129                    engine.is_healthy(&format!("debate-{}", role))
130                };
131                if !healthy {
132                    warn!(role = %role, round = round + 1, "Debate agent blocked by circuit breaker");
133                    continue;
134                }
135            }
136
137            let messages = agent_memory.history().to_vec();
138            match llm.chat(messages).await {
139                Ok(response) => {
140                    // Record success
141                    if let Some(ref healing) = healing_engine {
142                        let mut engine = healing.lock().unwrap();
143                        engine.record_success(&format!("debate-{}", role));
144                    }
145
146                    if let Some(choice) = response.choices.first() {
147                        let content = &choice.message.content;
148                        info!(role = %role, round = round + 1, "Debate contribution received");
149
150                        if pattern_config.verbose {
151                            println!("\n── {} (Round {}) ──\n{}", role, round + 1, content);
152                        }
153
154                        debate_history.add_assistant_message(&format!("{}: {}", role, content));
155                    }
156                }
157                Err(e) => {
158                    // Record failure
159                    if let Some(ref healing) = healing_engine {
160                        let mut engine = healing.lock().unwrap();
161                        engine.record_failure(&format!("debate-{}", role), &e.to_string());
162                    }
163                    warn!(error = %e, role = %role, round = round + 1, "Debate LLM request failed");
164                }
165            }
166        }
167    }
168
169    // Final synthesis
170    let synthesizer_persona = "You are a neutral synthesizer. Produce a final balanced conclusion that incorporates the best arguments from both sides.";
171    let mut final_memory = ConversationMemory::new(synthesizer_persona, 30);
172    for msg in debate_history.history() {
173        if msg.role == "system" {
174            continue;
175        }
176        if msg.role == "user" {
177            final_memory.add_user_message(&msg.content);
178        } else {
179            final_memory.add_assistant_message(&msg.content);
180        }
181    }
182    final_memory
183        .add_user_message("Now produce your FINAL synthesis that balances all perspectives:");
184
185    // Check self-healing circuit breaker before synthesis
186    if let Some(ref healing) = healing_engine {
187        let healthy = {
188            let mut engine = healing.lock().unwrap();
189            engine.is_healthy("debate-synthesis")
190        };
191        if !healthy {
192            warn!("Debate synthesis blocked by circuit breaker");
193            return Err(RavenClawsError::HealingError(
194                "Debate synthesis blocked by circuit breaker".to_string(),
195            ));
196        }
197    }
198
199    let messages = final_memory.history().to_vec();
200    match llm.chat(messages).await {
201        Ok(response) => {
202            // Record success
203            if let Some(ref healing) = healing_engine {
204                let mut engine = healing.lock().unwrap();
205                engine.record_success("debate-synthesis");
206            }
207
208            if let Some(choice) = response.choices.first() {
209                let result = &choice.message.content;
210                println!("\n🐦‍⬛ Debate Synthesis:\n{}", result);
211
212                if let Some(ref rf) = ravenfabric {
213                    if rf.is_enabled() {
214                        let preview = result.chars().take(500).collect::<String>();
215                        let _ = rf.broadcast(&preview, 30).await;
216                    }
217                }
218            }
219        }
220        Err(e) => {
221            // Record failure
222            if let Some(ref healing) = healing_engine {
223                let mut engine = healing.lock().unwrap();
224                engine.record_failure("debate-synthesis", &e.to_string());
225            }
226            warn!(error = %e, "Debate synthesis failed");
227            return Err(RavenClawsError::CommandExecution(format!(
228                "Debate synthesis failed: {}",
229                e
230            )));
231        }
232    }
233
234    Ok(())
235}
236
237// ── Pattern 2: Review-Loop ─────────────────────────────────────────────────
238
239/// Run a producer-reviewer loop that iterates until quality is met.
240///
241/// A producer agent creates content, a reviewer agent critiques it,
242/// and the producer revises based on feedback. Repeats until the
243/// reviewer approves or max iterations reached.
244pub async fn run_review_loop(
245    llm: Arc<dyn LLMProviderTrait>,
246    config: Config,
247    ravenfabric: Option<RavenFabricClient>,
248    pattern_config: PatternConfig,
249    healing_engine: Option<Arc<Mutex<SelfHealingEngine>>>,
250) -> crate::error::Result<()> {
251    info!(
252        "Starting review-loop mode with max {} iterations",
253        pattern_config.max_review_iterations
254    );
255
256    let _system_prompt = &config.llm.system_prompt;
257    let task = "Analyze the given task and provide your solution.";
258
259    let producer_persona = "You are a producer. Create high-quality content based on the requirements. Be thorough and detailed.";
260    let reviewer_persona = "You are a reviewer. Critically evaluate the content. Identify specific issues, gaps, and improvements needed. Be constructive and precise. If the content meets quality standards, respond with APPROVED: followed by your final sign-off.";
261
262    let mut current_content = String::new();
263    let mut approved = false;
264
265    for iteration in 0..pattern_config.max_review_iterations {
266        info!(iteration = iteration + 1, "Review-loop iteration");
267
268        if iteration == 0 {
269            // Producer creates initial content
270            let mut producer_memory = ConversationMemory::new(producer_persona, 10);
271            producer_memory.add_user_message(&format!(
272                "Create content for the following task:\n\n{}",
273                task
274            ));
275
276            // Check self-healing circuit breaker
277            if let Some(ref healing) = healing_engine {
278                let healthy = {
279                    let mut engine = healing.lock().unwrap();
280                    engine.is_healthy("review-producer")
281                };
282                if !healthy {
283                    warn!("Review producer blocked by circuit breaker");
284                    return Err(RavenClawsError::HealingError(
285                        "Review producer blocked by circuit breaker".to_string(),
286                    ));
287                }
288            }
289
290            let messages = producer_memory.history().to_vec();
291            match llm.chat(messages).await {
292                Ok(response) => {
293                    // Record success
294                    if let Some(ref healing) = healing_engine {
295                        let mut engine = healing.lock().unwrap();
296                        engine.record_success("review-producer");
297                    }
298
299                    if let Some(choice) = response.choices.first() {
300                        current_content = choice.message.content.clone();
301                        info!("Initial content produced: {} chars", current_content.len());
302
303                        if pattern_config.verbose {
304                            println!("\n── Initial Content ──\n{}", current_content);
305                        }
306                    }
307                }
308                Err(e) => {
309                    // Record failure
310                    if let Some(ref healing) = healing_engine {
311                        let mut engine = healing.lock().unwrap();
312                        engine.record_failure("review-producer", &e.to_string());
313                    }
314                    warn!(error = %e, "Producer LLM request failed");
315                    return Err(RavenClawsError::CommandExecution(format!(
316                        "Producer failed: {}",
317                        e
318                    )));
319                }
320            }
321        } else {
322            // Reviewer critiques
323            let mut reviewer_memory = ConversationMemory::new(reviewer_persona, 10);
324            reviewer_memory.add_user_message(&format!(
325                "Review the following content and provide constructive feedback:\n\n{}",
326                current_content
327            ));
328
329            // Check self-healing circuit breaker
330            if let Some(ref healing) = healing_engine {
331                let healthy = {
332                    let mut engine = healing.lock().unwrap();
333                    engine.is_healthy("review-reviewer")
334                };
335                if !healthy {
336                    warn!("Reviewer blocked by circuit breaker");
337                    continue;
338                }
339            }
340
341            let messages = reviewer_memory.history().to_vec();
342            let review = match llm.chat(messages).await {
343                Ok(response) => {
344                    // Record success
345                    if let Some(ref healing) = healing_engine {
346                        let mut engine = healing.lock().unwrap();
347                        engine.record_success("review-reviewer");
348                    }
349                    response
350                        .choices
351                        .first()
352                        .map(|c| c.message.content.clone())
353                        .unwrap_or_default()
354                }
355                Err(e) => {
356                    // Record failure
357                    if let Some(ref healing) = healing_engine {
358                        let mut engine = healing.lock().unwrap();
359                        engine.record_failure("review-reviewer", &e.to_string());
360                    }
361                    warn!(error = %e, "Reviewer LLM request failed");
362                    continue;
363                }
364            };
365
366            if pattern_config.verbose {
367                println!("\n── Review (Iteration {}) ──\n{}", iteration + 1, review);
368            }
369
370            // Check if approved
371            if review.contains("APPROVED:") {
372                info!("Content approved after {} iterations", iteration + 1);
373                let final_content = review.split("APPROVED:").nth(1).unwrap_or(&current_content);
374                current_content = final_content.trim().to_string();
375                approved = true;
376                break;
377            }
378
379            // Producer revises based on feedback
380            let mut producer_memory = ConversationMemory::new(producer_persona, 10);
381            producer_memory.add_user_message(&format!(
382                "Your previous content:\n\n{}\n\nReviewer feedback:\n\n{}\n\nPlease revise the content addressing all feedback.",
383                current_content, review
384            ));
385
386            // Check self-healing circuit breaker
387            if let Some(ref healing) = healing_engine {
388                let healthy = {
389                    let mut engine = healing.lock().unwrap();
390                    engine.is_healthy("review-producer")
391                };
392                if !healthy {
393                    warn!("Producer revision blocked by circuit breaker");
394                    continue;
395                }
396            }
397
398            let messages = producer_memory.history().to_vec();
399            match llm.chat(messages).await {
400                Ok(response) => {
401                    // Record success
402                    if let Some(ref healing) = healing_engine {
403                        let mut engine = healing.lock().unwrap();
404                        engine.record_success("review-producer");
405                    }
406
407                    if let Some(choice) = response.choices.first() {
408                        current_content = choice.message.content.clone();
409                        info!("Content revised: {} chars", current_content.len());
410
411                        if pattern_config.verbose {
412                            println!(
413                                "\n── Revised Content (Iteration {}) ──\n{}",
414                                iteration + 1,
415                                current_content
416                            );
417                        }
418                    }
419                }
420                Err(e) => {
421                    // Record failure
422                    if let Some(ref healing) = healing_engine {
423                        let mut engine = healing.lock().unwrap();
424                        engine.record_failure("review-producer", &e.to_string());
425                    }
426                    warn!(error = %e, "Producer revision failed");
427                    continue;
428                }
429            }
430        }
431    }
432
433    if !approved {
434        warn!("Review-loop reached max iterations without approval");
435    }
436
437    println!("\n🐦‍⬛ Review-Loop Final Content:\n{}", current_content);
438
439    if let Some(ref rf) = ravenfabric {
440        if rf.is_enabled() {
441            let preview = current_content.chars().take(500).collect::<String>();
442            let _ = rf.broadcast(&preview, 30).await;
443        }
444    }
445
446    Ok(())
447}
448
449// ── Pattern 3: Research-Synthesize ─────────────────────────────────────────
450
451/// Run parallel research agents followed by a synthesizer.
452///
453/// Multiple research agents explore different aspects of a topic in parallel.
454/// A synthesizer agent then combines their findings into a coherent report.
455pub async fn run_research_synthesize(
456    llm: Arc<dyn LLMProviderTrait>,
457    config: Config,
458    ravenfabric: Option<RavenFabricClient>,
459    pattern_config: PatternConfig,
460    healing_engine: Option<Arc<Mutex<SelfHealingEngine>>>,
461) -> crate::error::Result<()> {
462    info!(
463        "Starting research-synthesize mode with {} research agents",
464        pattern_config.research_agent_count
465    );
466
467    let _system_prompt = &config.llm.system_prompt;
468    let task = "Analyze the given task and provide your solution.";
469
470    // Define research perspectives
471    let perspectives = [
472        ("Fact-Finder", "You are a fact-finding researcher. Focus on verifiable facts, data, statistics, and concrete evidence. Cite specific sources and numbers."),
473        ("Analyst", "You are an analytical researcher. Focus on patterns, trends, cause-and-effect relationships, and strategic implications."),
474        ("Innovator", "You are an innovative researcher. Focus on novel approaches, emerging trends, creative solutions, and future possibilities."),
475    ];
476
477    let agent_count = pattern_config.research_agent_count.min(perspectives.len());
478    let mut research_results: Vec<(String, String)> = Vec::new();
479
480    // Phase 1: Parallel research
481    for (role, persona) in perspectives.iter().take(agent_count) {
482        info!(role = %role, "Research agent starting");
483
484        let mut memory = ConversationMemory::new(persona, 10);
485        memory.add_user_message(&format!(
486            "Research the following topic from your perspective:\n\n{}",
487            task
488        ));
489
490        // Check self-healing circuit breaker
491        if let Some(ref healing) = healing_engine {
492            let healthy = {
493                let mut engine = healing.lock().unwrap();
494                engine.is_healthy(&format!("research-{}", role))
495            };
496            if !healthy {
497                warn!(role = %role, "Research agent blocked by circuit breaker");
498                research_results.push((
499                    role.to_string(),
500                    "[Research blocked by circuit breaker]".to_string(),
501                ));
502                continue;
503            }
504        }
505
506        let messages = memory.history().to_vec();
507        match llm.chat(messages).await {
508            Ok(response) => {
509                // Record success
510                if let Some(ref healing) = healing_engine {
511                    let mut engine = healing.lock().unwrap();
512                    engine.record_success(&format!("research-{}", role));
513                }
514
515                if let Some(choice) = response.choices.first() {
516                    let content = choice.message.content.clone();
517                    info!(role = %role, "Research completed: {} chars", content.len());
518                    research_results.push((role.to_string(), content));
519                }
520            }
521            Err(e) => {
522                // Record failure
523                if let Some(ref healing) = healing_engine {
524                    let mut engine = healing.lock().unwrap();
525                    engine.record_failure(&format!("research-{}", role), &e.to_string());
526                }
527                warn!(error = %e, role = %role, "Research agent failed");
528                research_results.push((role.to_string(), format!("[Research failed: {}]", e)));
529            }
530        }
531    }
532
533    // Print intermediate research if verbose
534    if pattern_config.verbose {
535        println!("\n── Research Findings ──");
536        for (role, content) in &research_results {
537            println!("\n--- {} ---\n{}", role, content);
538        }
539    }
540
541    // Phase 2: Synthesis
542    let synthesizer_persona = "You are a synthesis specialist. Combine multiple research perspectives into a coherent, well-structured report. Identify common themes, resolve contradictions, and present a unified analysis.";
543    let mut synth_memory = ConversationMemory::new(synthesizer_persona, 20);
544
545    let mut synthesis_input =
546        String::from("Synthesize the following research findings into a comprehensive report:\n\n");
547    for (role, content) in &research_results {
548        synthesis_input.push_str(&format!("\n=== {} ===\n{}\n", role, content));
549    }
550    synth_memory.add_user_message(&synthesis_input);
551
552    // Check self-healing circuit breaker before synthesis
553    if let Some(ref healing) = healing_engine {
554        let healthy = {
555            let mut engine = healing.lock().unwrap();
556            engine.is_healthy("research-synthesis")
557        };
558        if !healthy {
559            warn!("Research synthesis blocked by circuit breaker");
560            return Err(RavenClawsError::HealingError(
561                "Research synthesis blocked by circuit breaker".to_string(),
562            ));
563        }
564    }
565
566    let messages = synth_memory.history().to_vec();
567    match llm.chat(messages).await {
568        Ok(response) => {
569            // Record success
570            if let Some(ref healing) = healing_engine {
571                let mut engine = healing.lock().unwrap();
572                engine.record_success("research-synthesis");
573            }
574
575            if let Some(choice) = response.choices.first() {
576                let result = &choice.message.content;
577                println!("\n🐦‍⬛ Research Synthesis:\n{}", result);
578
579                if let Some(ref rf) = ravenfabric {
580                    if rf.is_enabled() {
581                        let preview = result.chars().take(500).collect::<String>();
582                        let _ = rf.broadcast(&preview, 30).await;
583                    }
584                }
585            }
586        }
587        Err(e) => {
588            // Record failure
589            if let Some(ref healing) = healing_engine {
590                let mut engine = healing.lock().unwrap();
591                engine.record_failure("research-synthesis", &e.to_string());
592            }
593            warn!(error = %e, "Synthesis failed");
594            return Err(RavenClawsError::CommandExecution(format!(
595                "Synthesis failed: {}",
596                e
597            )));
598        }
599    }
600
601    Ok(())
602}
603
604// ── Pattern 4: Voting ──────────────────────────────────────────────────────
605
606/// Run a voting process where multiple agents evaluate options.
607///
608/// Each voter agent independently evaluates the options and provides
609/// their choice with reasoning. Results are tallied and the majority
610/// decision is reported.
611pub async fn run_voting(
612    llm: Arc<dyn LLMProviderTrait>,
613    config: Config,
614    ravenfabric: Option<RavenFabricClient>,
615    pattern_config: PatternConfig,
616    healing_engine: Option<Arc<Mutex<SelfHealingEngine>>>,
617) -> crate::error::Result<()> {
618    info!(
619        "Starting voting mode with {} voters",
620        pattern_config.voter_count
621    );
622
623    let system_prompt = &config.llm.system_prompt;
624    let task = "Analyze the given task and provide your solution.";
625
626    // Define voter personas for diversity
627    let voter_personas = [
628        "You are a conservative voter. You prefer safe, proven approaches. Prioritize stability and risk mitigation. Respond with: VOTE: <your choice> REASONING: <your reasoning>",
629        "You are an aggressive voter. You prefer bold, ambitious approaches. Prioritize maximum impact and innovation. Respond with: VOTE: <your choice> REASONING: <your reasoning>",
630        "You are a balanced voter. You weigh pros and cons carefully. Prioritize pragmatic, well-rounded solutions. Respond with: VOTE: <your choice> REASONING: <your reasoning>",
631        "You are a detail-oriented voter. You focus on implementation feasibility and technical soundness. Respond with: VOTE: <your choice> REASONING: <your reasoning>",
632        "You are a user-centric voter. You prioritize user experience, accessibility, and usability. Respond with: VOTE: <your choice> REASONING: <your reasoning>",
633    ];
634
635    let voter_count = pattern_config.voter_count.min(voter_personas.len());
636    let mut votes: Vec<(String, String, String)> = Vec::new(); // (persona_name, vote, reasoning)
637
638    // Phase 1: Independent voting
639    for (i, persona) in voter_personas.iter().enumerate().take(voter_count) {
640        let persona_name = persona
641            .split('.')
642            .next()
643            .unwrap_or(&format!("Voter {}", i + 1))
644            .to_string();
645
646        let mut memory = ConversationMemory::new(&format!("{}\n\n{}", system_prompt, persona), 10);
647        memory.add_user_message(&format!(
648            "Evaluate the following and cast your vote:\n\n{}",
649            task
650        ));
651
652        // Check self-healing circuit breaker
653        if let Some(ref healing) = healing_engine {
654            let healthy = {
655                let mut engine = healing.lock().unwrap();
656                engine.is_healthy(&format!("voter-{}", i))
657            };
658            if !healthy {
659                warn!(voter = %persona_name, "Voter blocked by circuit breaker");
660                votes.push((
661                    persona_name,
662                    "Error".to_string(),
663                    "Vote blocked by circuit breaker".to_string(),
664                ));
665                continue;
666            }
667        }
668
669        let messages = memory.history().to_vec();
670        match llm.chat(messages).await {
671            Ok(response) => {
672                // Record success
673                if let Some(ref healing) = healing_engine {
674                    let mut engine = healing.lock().unwrap();
675                    engine.record_success(&format!("voter-{}", i));
676                }
677
678                if let Some(choice) = response.choices.first() {
679                    let content = choice.message.content.clone();
680                    info!(voter = %persona_name, "Vote cast");
681
682                    // Extract vote and reasoning
683                    let vote = content
684                        .split("VOTE:")
685                        .nth(1)
686                        .and_then(|s| s.split("REASONING:").next())
687                        .map(|s| s.trim().to_string())
688                        .unwrap_or_else(|| "Unknown".to_string());
689
690                    let reasoning = content
691                        .split("REASONING:")
692                        .nth(1)
693                        .map(|s| s.trim().to_string())
694                        .unwrap_or_default();
695
696                    if pattern_config.verbose {
697                        println!(
698                            "\n── {} ──\nVOTE: {}\nREASONING: {}",
699                            persona_name, vote, reasoning
700                        );
701                    }
702
703                    votes.push((persona_name, vote, reasoning));
704                }
705            }
706            Err(e) => {
707                // Record failure
708                if let Some(ref healing) = healing_engine {
709                    let mut engine = healing.lock().unwrap();
710                    engine.record_failure(&format!("voter-{}", i), &e.to_string());
711                }
712                warn!(error = %e, voter = %persona_name, "Voter LLM request failed");
713                votes.push((
714                    persona_name,
715                    "Error".to_string(),
716                    format!("Vote failed: {}", e),
717                ));
718            }
719        }
720    }
721
722    // Phase 2: Tally results
723    let mut tally: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
724    for (_, vote, _) in &votes {
725        *tally.entry(vote.clone()).or_insert(0) += 1;
726    }
727
728    // Find winner(s)
729    let max_votes = tally.values().cloned().max().unwrap_or(0);
730    let winners: Vec<String> = tally
731        .iter()
732        .filter(|(_, count)| **count == max_votes)
733        .map(|(vote, _)| vote.clone())
734        .collect();
735
736    println!("\n🐦‍⬛ Voting Results:");
737    println!("───");
738    for (persona, vote, reasoning) in &votes {
739        println!("{}: VOTE = {} | {}", persona, vote, reasoning);
740    }
741    println!("───");
742    println!("Tally: {:?}", tally);
743    println!("\n🏆 Decision: {}", winners.join(", "));
744
745    if let Some(ref rf) = ravenfabric {
746        if rf.is_enabled() {
747            let summary = format!(
748                "Voting completed: {} voters, decision: {}",
749                votes.len(),
750                winners.join(", ")
751            );
752            let _ = rf.broadcast(&summary, 30).await;
753        }
754    }
755
756    Ok(())
757}
758
759// ── Multi-model variants ───────────────────────────────────────────────────
760
761/// Run debate mode with multiple LLM providers (round-robin)
762pub async fn run_debate_multi(
763    multi_llm: MultiModelManager,
764    config: Config,
765    _ravenfabric: Option<RavenFabricClient>,
766    pattern_config: PatternConfig,
767    healing_engine: Option<Arc<Mutex<SelfHealingEngine>>>,
768) -> crate::error::Result<()> {
769    info!(
770        "Starting debate mode (multi-model) with {} providers",
771        multi_llm.client_count()
772    );
773
774    let system_prompt = &config.llm.system_prompt;
775    let task = "Analyze the given task and provide your solution.";
776
777    let positions = [
778        (
779            "Proponent",
780            "You argue FOR the proposition. Focus on benefits, opportunities, and strengths.",
781        ),
782        (
783            "Opponent",
784            "You argue AGAINST the proposition. Focus on risks, drawbacks, and weaknesses.",
785        ),
786        (
787            "Synthesizer",
788            "You are the neutral judge. Synthesize a balanced conclusion.",
789        ),
790    ];
791
792    let mut debate_history = ConversationMemory::new(system_prompt, 50);
793    debate_history.add_user_message(&format!("Debate topic: {}", task));
794
795    for round in 0..pattern_config.max_rounds {
796        info!(round = round + 1, "Multi-model debate round");
797
798        for (role, persona) in &positions {
799            if *role == "Synthesizer" && round < pattern_config.max_rounds - 1 {
800                continue;
801            }
802
803            let provider_idx = round % multi_llm.client_count();
804            let client = multi_llm.get_client(provider_idx);
805
806            if let Some(client) = client {
807                let mut agent_memory = ConversationMemory::new(persona, 20);
808                for msg in debate_history.history() {
809                    if msg.role == "system" {
810                        continue;
811                    }
812                    if msg.role == "user" {
813                        agent_memory.add_user_message(&msg.content);
814                    } else {
815                        agent_memory.add_assistant_message(&msg.content);
816                    }
817                }
818
819                // Check self-healing circuit breaker
820                if let Some(ref healing) = healing_engine {
821                    let healthy = {
822                        let mut engine = healing.lock().unwrap();
823                        engine.is_healthy(&format!("debate-multi-{}", role))
824                    };
825                    if !healthy {
826                        warn!(role = %role, "Multi-model debate agent blocked by circuit breaker");
827                        continue;
828                    }
829                }
830
831                let messages = agent_memory.history().to_vec();
832                match client.chat(messages).await {
833                    Ok(response) => {
834                        // Record success
835                        if let Some(ref healing) = healing_engine {
836                            let mut engine = healing.lock().unwrap();
837                            engine.record_success(&format!("debate-multi-{}", role));
838                        }
839
840                        if let Some(choice) = response.choices.first() {
841                            let content = &choice.message.content;
842                            info!(role = %role, provider = client.provider_name(), round = round + 1, "Debate contribution");
843                            if pattern_config.verbose {
844                                println!(
845                                    "\n── {} ({} via {}, Round {}) ──\n{}",
846                                    role,
847                                    client.provider_name(),
848                                    client.model(),
849                                    round + 1,
850                                    content
851                                );
852                            }
853                            debate_history.add_assistant_message(&format!(
854                                "{} ({}): {}",
855                                role,
856                                client.provider_name(),
857                                content
858                            ));
859                        }
860                    }
861                    Err(e) => {
862                        // Record failure
863                        if let Some(ref healing) = healing_engine {
864                            let mut engine = healing.lock().unwrap();
865                            engine
866                                .record_failure(&format!("debate-multi-{}", role), &e.to_string());
867                        }
868                        warn!(error = %e, role = %role, "Multi-model debate failed");
869                    }
870                }
871            }
872        }
873    }
874
875    // Final synthesis using first provider
876    if let Some(client) = multi_llm.get_client(0) {
877        // Check self-healing circuit breaker
878        if let Some(ref healing) = healing_engine {
879            let healthy = {
880                let mut engine = healing.lock().unwrap();
881                engine.is_healthy("debate-multi-synthesis")
882            };
883            if !healthy {
884                warn!("Multi-model debate synthesis blocked by circuit breaker");
885                return Err(RavenClawsError::HealingError(
886                    "Multi-model debate synthesis blocked by circuit breaker".to_string(),
887                ));
888            }
889        }
890
891        let synthesizer_persona =
892            "You are a neutral synthesizer. Produce a final balanced conclusion.";
893        let mut final_memory = ConversationMemory::new(synthesizer_persona, 30);
894        for msg in debate_history.history() {
895            if msg.role == "system" {
896                continue;
897            }
898            if msg.role == "user" {
899                final_memory.add_user_message(&msg.content);
900            } else {
901                final_memory.add_assistant_message(&msg.content);
902            }
903        }
904        final_memory.add_user_message("Now produce your FINAL synthesis:");
905
906        let messages = final_memory.history().to_vec();
907        match client.chat(messages).await {
908            Ok(response) => {
909                // Record success
910                if let Some(ref healing) = healing_engine {
911                    let mut engine = healing.lock().unwrap();
912                    engine.record_success("debate-multi-synthesis");
913                }
914
915                if let Some(choice) = response.choices.first() {
916                    println!(
917                        "\n🐦‍⬛ Multi-Model Debate Synthesis:\n{}",
918                        choice.message.content
919                    );
920                }
921            }
922            Err(e) => {
923                // Record failure
924                if let Some(ref healing) = healing_engine {
925                    let mut engine = healing.lock().unwrap();
926                    engine.record_failure("debate-multi-synthesis", &e.to_string());
927                }
928                warn!(error = %e, "Multi-model synthesis failed");
929            }
930        }
931    }
932
933    Ok(())
934}
935
936/// Run review-loop mode with multiple LLM providers
937pub async fn run_review_loop_multi(
938    multi_llm: MultiModelManager,
939    _config: Config,
940    _ravenfabric: Option<RavenFabricClient>,
941    pattern_config: PatternConfig,
942    healing_engine: Option<Arc<Mutex<SelfHealingEngine>>>,
943) -> crate::error::Result<()> {
944    info!("Starting review-loop mode (multi-model)");
945
946    let _system_prompt = &_config.llm.system_prompt;
947    let _ = &_system_prompt;
948    let task = "Analyze the given task and provide your solution.";
949
950    let producer_persona = "You are a producer. Create high-quality content.";
951    let reviewer_persona = "You are a reviewer. Critically evaluate content. Respond with APPROVED: when quality is met.";
952
953    let mut current_content = String::new();
954    let mut approved = false;
955
956    for iteration in 0..pattern_config.max_review_iterations {
957        info!(iteration = iteration + 1, "Multi-model review-loop");
958
959        let provider_idx = iteration % multi_llm.client_count();
960        let client = multi_llm.get_client(provider_idx);
961
962        if let Some(client) = client {
963            // Check self-healing circuit breaker
964            if let Some(ref healing) = healing_engine {
965                let agent_id = if iteration == 0 {
966                    "review-multi-producer"
967                } else {
968                    "review-multi-reviewer"
969                };
970                let healthy = {
971                    let mut engine = healing.lock().unwrap();
972                    engine.is_healthy(agent_id)
973                };
974                if !healthy {
975                    warn!(
976                        iteration = iteration + 1,
977                        "Multi-model review blocked by circuit breaker"
978                    );
979                    continue;
980                }
981            }
982
983            if iteration == 0 {
984                let mut memory = ConversationMemory::new(producer_persona, 10);
985                memory.add_user_message(&format!("Create content for: {}", task));
986                let messages = memory.history().to_vec();
987                match client.chat(messages).await {
988                    Ok(response) => {
989                        // Record success
990                        if let Some(ref healing) = healing_engine {
991                            let mut engine = healing.lock().unwrap();
992                            engine.record_success("review-multi-producer");
993                        }
994
995                        if let Some(choice) = response.choices.first() {
996                            current_content = choice.message.content.clone();
997                            info!(
998                                "Initial content: {} chars via {}",
999                                current_content.len(),
1000                                client.provider_name()
1001                            );
1002                        }
1003                    }
1004                    Err(e) => {
1005                        // Record failure
1006                        if let Some(ref healing) = healing_engine {
1007                            let mut engine = healing.lock().unwrap();
1008                            engine.record_failure("review-multi-producer", &e.to_string());
1009                        }
1010                        warn!(error = %e, "Producer failed");
1011                    }
1012                }
1013            } else {
1014                // Review
1015                let mut rev_memory = ConversationMemory::new(reviewer_persona, 10);
1016                rev_memory.add_user_message(&format!("Review:\n\n{}", current_content));
1017                let messages = rev_memory.history().to_vec();
1018                match client.chat(messages).await {
1019                    Ok(response) => {
1020                        // Record success
1021                        if let Some(ref healing) = healing_engine {
1022                            let mut engine = healing.lock().unwrap();
1023                            engine.record_success("review-multi-reviewer");
1024                        }
1025
1026                        if let Some(choice) = response.choices.first() {
1027                            let review = &choice.message.content;
1028                            if review.contains("APPROVED:") {
1029                                info!(
1030                                    "Approved after {} iterations via {}",
1031                                    iteration + 1,
1032                                    client.provider_name()
1033                                );
1034                                current_content = review
1035                                    .split("APPROVED:")
1036                                    .nth(1)
1037                                    .unwrap_or(&current_content)
1038                                    .trim()
1039                                    .to_string();
1040                                approved = true;
1041                                break;
1042                            }
1043                            // Revise
1044                            let mut prod_memory = ConversationMemory::new(producer_persona, 10);
1045                            prod_memory.add_user_message(&format!(
1046                                "Content:\n{}\n\nFeedback:\n{}\n\nRevise:",
1047                                current_content, review
1048                            ));
1049                            let msgs = prod_memory.history().to_vec();
1050                            if let Some(next_client) =
1051                                multi_llm.get_client((iteration + 1) % multi_llm.client_count())
1052                            {
1053                                // Check circuit breaker for producer revision
1054                                if let Some(ref healing) = healing_engine {
1055                                    let healthy = {
1056                                        let mut engine = healing.lock().unwrap();
1057                                        engine.is_healthy("review-multi-producer")
1058                                    };
1059                                    if !healthy {
1060                                        warn!("Multi-model producer revision blocked by circuit breaker");
1061                                        continue;
1062                                    }
1063                                }
1064
1065                                if let Ok(rev_resp) = next_client.chat(msgs).await {
1066                                    // Record success
1067                                    if let Some(ref healing) = healing_engine {
1068                                        let mut engine = healing.lock().unwrap();
1069                                        engine.record_success("review-multi-producer");
1070                                    }
1071
1072                                    if let Some(rev_choice) = rev_resp.choices.first() {
1073                                        current_content = rev_choice.message.content.clone();
1074                                        info!(
1075                                            "Revised: {} chars via {}",
1076                                            current_content.len(),
1077                                            next_client.provider_name()
1078                                        );
1079                                    }
1080                                } else {
1081                                    // Record failure
1082                                    if let Some(ref healing) = healing_engine {
1083                                        let mut engine = healing.lock().unwrap();
1084                                        engine.record_failure(
1085                                            "review-multi-producer",
1086                                            "revision failed",
1087                                        );
1088                                    }
1089                                }
1090                            }
1091                        }
1092                    }
1093                    Err(e) => {
1094                        // Record failure
1095                        if let Some(ref healing) = healing_engine {
1096                            let mut engine = healing.lock().unwrap();
1097                            engine.record_failure("review-multi-reviewer", &e.to_string());
1098                        }
1099                        warn!(error = %e, "Review failed");
1100                    }
1101                }
1102            }
1103        }
1104    }
1105
1106    if !approved {
1107        warn!("Review-loop reached max iterations without approval");
1108    }
1109
1110    println!("\n🐦‍⬛ Multi-Model Review-Loop Final:\n{}", current_content);
1111    Ok(())
1112}
1113
1114/// Run research-synthesize mode with multiple LLM providers
1115pub async fn run_research_synthesize_multi(
1116    multi_llm: MultiModelManager,
1117    config: Config,
1118    _ravenfabric: Option<RavenFabricClient>,
1119    pattern_config: PatternConfig,
1120    healing_engine: Option<Arc<Mutex<SelfHealingEngine>>>,
1121) -> crate::error::Result<()> {
1122    info!("Starting research-synthesize mode (multi-model)");
1123
1124    let system_prompt = &config.llm.system_prompt;
1125    let task = "Analyze the given task and provide your solution.";
1126
1127    let perspectives = [
1128        (
1129            "Fact-Finder",
1130            "Focus on verifiable facts, data, statistics.",
1131        ),
1132        (
1133            "Analyst",
1134            "Focus on patterns, trends, strategic implications.",
1135        ),
1136        (
1137            "Innovator",
1138            "Focus on novel approaches and future possibilities.",
1139        ),
1140    ];
1141
1142    let agent_count = pattern_config.research_agent_count.min(perspectives.len());
1143    let mut results: Vec<(String, String, String)> = Vec::new(); // (role, provider, content)
1144
1145    for (i, (role, persona)) in perspectives.iter().enumerate().take(agent_count) {
1146        let client = multi_llm.get_client(i % multi_llm.client_count());
1147
1148        if let Some(client) = client {
1149            let mut memory =
1150                ConversationMemory::new(&format!("{}\n\n{}", system_prompt, persona), 10);
1151            memory.add_user_message(&format!("Research: {}", task));
1152            let messages = memory.history().to_vec();
1153
1154            // Check self-healing circuit breaker
1155            if let Some(ref healing) = healing_engine {
1156                let healthy = {
1157                    let mut engine = healing.lock().unwrap();
1158                    engine.is_healthy(&format!("research-multi-{}", role))
1159                };
1160                if !healthy {
1161                    warn!(role = %role, "Multi-model research agent blocked by circuit breaker");
1162                    results.push((
1163                        role.to_string(),
1164                        "blocked".to_string(),
1165                        "[Research blocked by circuit breaker]".to_string(),
1166                    ));
1167                    continue;
1168                }
1169            }
1170
1171            match client.chat(messages).await {
1172                Ok(response) => {
1173                    // Record success
1174                    if let Some(ref healing) = healing_engine {
1175                        let mut engine = healing.lock().unwrap();
1176                        engine.record_success(&format!("research-multi-{}", role));
1177                    }
1178
1179                    if let Some(choice) = response.choices.first() {
1180                        let content = choice.message.content.clone();
1181                        info!(role = %role, provider = client.provider_name(), "Research completed");
1182                        results.push((
1183                            role.to_string(),
1184                            client.provider_name().to_string(),
1185                            content,
1186                        ));
1187                    }
1188                }
1189                Err(e) => {
1190                    // Record failure
1191                    if let Some(ref healing) = healing_engine {
1192                        let mut engine = healing.lock().unwrap();
1193                        engine.record_failure(&format!("research-multi-{}", role), &e.to_string());
1194                    }
1195                    warn!(error = %e, role = %role, "Research failed");
1196                }
1197            }
1198        }
1199    }
1200
1201    // Synthesize
1202    if let Some(client) = multi_llm.get_client(0) {
1203        // Check self-healing circuit breaker
1204        if let Some(ref healing) = healing_engine {
1205            let healthy = {
1206                let mut engine = healing.lock().unwrap();
1207                engine.is_healthy("research-multi-synthesis")
1208            };
1209            if !healthy {
1210                warn!("Multi-model research synthesis blocked by circuit breaker");
1211                return Err(RavenClawsError::HealingError(
1212                    "Multi-model research synthesis blocked by circuit breaker".to_string(),
1213                ));
1214            }
1215        }
1216
1217        let mut synth_memory = ConversationMemory::new("You are a synthesis specialist.", 20);
1218        let mut input = String::from("Synthesize these findings:\n\n");
1219        for (role, provider, content) in &results {
1220            input.push_str(&format!("=== {} ({}) ===\n{}\n", role, provider, content));
1221        }
1222        synth_memory.add_user_message(&input);
1223        let messages = synth_memory.history().to_vec();
1224
1225        match client.chat(messages).await {
1226            Ok(response) => {
1227                // Record success
1228                if let Some(ref healing) = healing_engine {
1229                    let mut engine = healing.lock().unwrap();
1230                    engine.record_success("research-multi-synthesis");
1231                }
1232
1233                if let Some(choice) = response.choices.first() {
1234                    println!(
1235                        "\n🐦‍⬛ Multi-Model Research Synthesis:\n{}",
1236                        choice.message.content
1237                    );
1238                }
1239            }
1240            Err(e) => {
1241                // Record failure
1242                if let Some(ref healing) = healing_engine {
1243                    let mut engine = healing.lock().unwrap();
1244                    engine.record_failure("research-multi-synthesis", &e.to_string());
1245                }
1246                warn!(error = %e, "Synthesis failed");
1247            }
1248        }
1249    }
1250
1251    Ok(())
1252}
1253
1254/// Run voting mode with multiple LLM providers
1255pub async fn run_voting_multi(
1256    multi_llm: MultiModelManager,
1257    config: Config,
1258    _ravenfabric: Option<RavenFabricClient>,
1259    pattern_config: PatternConfig,
1260    healing_engine: Option<Arc<Mutex<SelfHealingEngine>>>,
1261) -> crate::error::Result<()> {
1262    info!(
1263        "Starting voting mode (multi-model) with {} providers",
1264        multi_llm.client_count()
1265    );
1266
1267    let system_prompt = &config.llm.system_prompt;
1268    let task = "Analyze the given task and provide your solution.";
1269
1270    let voter_personas = [
1271        "Conservative: Prefer safe, proven approaches. VOTE: <choice> REASONING: <reasoning>",
1272        "Aggressive: Prefer bold, ambitious approaches. VOTE: <choice> REASONING: <reasoning>",
1273        "Balanced: Weigh pros and cons. VOTE: <choice> REASONING: <reasoning>",
1274        "Detail-oriented: Focus on feasibility. VOTE: <choice> REASONING: <reasoning>",
1275        "User-centric: Prioritize UX. VOTE: <choice> REASONING: <reasoning>",
1276    ];
1277
1278    let voter_count = pattern_config
1279        .voter_count
1280        .min(voter_personas.len())
1281        .min(multi_llm.client_count());
1282    let mut votes: Vec<(String, String, String, String)> = Vec::new(); // (persona, provider, vote, reasoning)
1283
1284    for (i, persona) in voter_personas.iter().enumerate().take(voter_count) {
1285        let client = multi_llm.get_client(i % multi_llm.client_count());
1286
1287        if let Some(client) = client {
1288            let mut memory =
1289                ConversationMemory::new(&format!("{}\n\n{}", system_prompt, persona), 10);
1290            memory.add_user_message(&format!("Cast your vote on: {}", task));
1291            let messages = memory.history().to_vec();
1292
1293            // Check self-healing circuit breaker
1294            if let Some(ref healing) = healing_engine {
1295                let healthy = {
1296                    let mut engine = healing.lock().unwrap();
1297                    engine.is_healthy(&format!("voter-multi-{}", i))
1298                };
1299                if !healthy {
1300                    warn!(
1301                        voter = i + 1,
1302                        "Multi-model voter blocked by circuit breaker"
1303                    );
1304                    votes.push((
1305                        format!("Voter {}", i + 1),
1306                        "blocked".to_string(),
1307                        "Error".to_string(),
1308                        "Vote blocked by circuit breaker".to_string(),
1309                    ));
1310                    continue;
1311                }
1312            }
1313
1314            match client.chat(messages).await {
1315                Ok(response) => {
1316                    // Record success
1317                    if let Some(ref healing) = healing_engine {
1318                        let mut engine = healing.lock().unwrap();
1319                        engine.record_success(&format!("voter-multi-{}", i));
1320                    }
1321
1322                    if let Some(choice) = response.choices.first() {
1323                        let content = choice.message.content.clone();
1324                        let vote = content
1325                            .split("VOTE:")
1326                            .nth(1)
1327                            .and_then(|s| s.split("REASONING:").next())
1328                            .map(|s| s.trim().to_string())
1329                            .unwrap_or_else(|| "Unknown".to_string());
1330                        let reasoning = content
1331                            .split("REASONING:")
1332                            .nth(1)
1333                            .map(|s| s.trim().to_string())
1334                            .unwrap_or_default();
1335                        votes.push((
1336                            format!("Voter {}", i + 1),
1337                            client.provider_name().to_string(),
1338                            vote,
1339                            reasoning,
1340                        ));
1341                    }
1342                }
1343                Err(e) => {
1344                    // Record failure
1345                    if let Some(ref healing) = healing_engine {
1346                        let mut engine = healing.lock().unwrap();
1347                        engine.record_failure(&format!("voter-multi-{}", i), &e.to_string());
1348                    }
1349                    warn!(error = %e, "Voter {} failed", i + 1);
1350                }
1351            }
1352        }
1353    }
1354
1355    // Tally
1356    let mut tally: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
1357    for (_, _, vote, _) in &votes {
1358        *tally.entry(vote.clone()).or_insert(0) += 1;
1359    }
1360
1361    println!("\n🐦‍⬛ Multi-Model Voting Results:");
1362    for (persona, provider, vote, reasoning) in &votes {
1363        println!(
1364            "{} ({}): VOTE = {} | {}",
1365            persona, provider, vote, reasoning
1366        );
1367    }
1368    println!("Tally: {:?}", tally);
1369    println!(
1370        "🏆 Decision: {}",
1371        tally
1372            .iter()
1373            .max_by_key(|(_, c)| *c)
1374            .map(|(v, _)| v.clone())
1375            .unwrap_or_else(|| "No decision".to_string())
1376    );
1377
1378    Ok(())
1379}
1380
1381// ── Pattern 5: Tree-of-Thought Reasoning ────────────────────────────────────
1382
1383/// Run tree-of-thought reasoning with multiple parallel branches.
1384///
1385/// At each step, the agent generates N candidate thoughts, evaluates each
1386/// one's promise, prunes to the top-K, and continues exploring the most
1387/// promising branches. Returns the best complete reasoning path.
1388pub async fn run_tree_of_thought(
1389    llm: Arc<dyn LLMProviderTrait>,
1390    config: Config,
1391    ravenfabric: Option<RavenFabricClient>,
1392    pattern_config: PatternConfig,
1393    healing_engine: Option<Arc<Mutex<SelfHealingEngine>>>,
1394) -> crate::error::Result<()> {
1395    info!(
1396        "Starting tree-of-thought mode: {} branches, depth {}, top-k {}",
1397        pattern_config.tot_branches, pattern_config.tot_depth, pattern_config.tot_top_k
1398    );
1399
1400    let system_prompt = &config.llm.system_prompt;
1401    let task = "Analyze the given task and provide your solution.";
1402
1403    // Each branch is a vector of thought strings (the reasoning path so far)
1404    let mut branches: Vec<Vec<String>> = vec![Vec::new()];
1405
1406    for depth in 0..pattern_config.tot_depth {
1407        info!(depth = depth + 1, branches = branches.len(), "ToT step");
1408
1409        // Check self-healing circuit breaker
1410        if let Some(ref healing) = healing_engine {
1411            let healthy = {
1412                let mut engine = healing.lock().unwrap();
1413                engine.is_healthy("tree-of-thought")
1414            };
1415            if !healthy {
1416                warn!("Tree-of-thought blocked by circuit breaker");
1417                return Err(RavenClawsError::HealingError(
1418                    "Tree-of-thought blocked by circuit breaker".to_string(),
1419                ));
1420            }
1421        }
1422
1423        let mut all_candidates: Vec<(Vec<String>, String, f64)> = Vec::new();
1424
1425        for (branch_idx, branch) in branches.iter().enumerate() {
1426            let mut memory = ConversationMemory::new(
1427                &format!("{}\n\nYou are a reasoning agent. Generate diverse candidate thoughts and evaluate them.", system_prompt),
1428                30,
1429            );
1430            memory.add_user_message(&format!(
1431                "Task: {}\n\nCurrent reasoning path (step {}/{}):\n{}\n\nGenerate {} distinct next-step thoughts. \
1432                 For each thought, provide:\n1. The thought itself\n2. A confidence score (0.0 to 1.0)\n\n\
1433                 Format each as:\nTHOUGHT: <your thought>\nCONFIDENCE: <0.0-1.0>",
1434                task,
1435                depth + 1,
1436                pattern_config.tot_depth,
1437                if branch.is_empty() {
1438                    "No steps yet — start from scratch.".to_string()
1439                } else {
1440                    branch.join("\n")
1441                },
1442                pattern_config.tot_branches,
1443            ));
1444
1445            let messages = memory.history().to_vec();
1446            match llm.chat(messages).await {
1447                Ok(response) => {
1448                    if let Some(ref healing) = healing_engine {
1449                        let mut engine = healing.lock().unwrap();
1450                        engine.record_success("tree-of-thought");
1451                    }
1452
1453                    if let Some(choice) = response.choices.first() {
1454                        let content = &choice.message.content;
1455                        // Parse thoughts and confidences
1456                        let mut current_thought = String::new();
1457                        let mut current_confidence = 0.5;
1458                        let mut parsing_thought = false;
1459
1460                        for line in content.lines() {
1461                            let trimmed = line.trim();
1462                            if trimmed.starts_with("THOUGHT:") {
1463                                // Save previous thought if any
1464                                if parsing_thought && !current_thought.is_empty() {
1465                                    let mut new_branch = branch.clone();
1466                                    new_branch.push(current_thought.clone());
1467                                    all_candidates.push((
1468                                        new_branch,
1469                                        format!("Confidence: {:.2}", current_confidence),
1470                                        current_confidence,
1471                                    ));
1472                                }
1473                                current_thought = trimmed
1474                                    .strip_prefix("THOUGHT:")
1475                                    .unwrap_or("")
1476                                    .trim()
1477                                    .to_string();
1478                                parsing_thought = true;
1479                                current_confidence = 0.5;
1480                            } else if trimmed.starts_with("CONFIDENCE:") {
1481                                let val_str =
1482                                    trimmed.strip_prefix("CONFIDENCE:").unwrap_or("0.5").trim();
1483                                if let Ok(val) = val_str.parse::<f64>() {
1484                                    current_confidence = val.clamp(0.0, 1.0);
1485                                }
1486                            } else if parsing_thought && !trimmed.is_empty() {
1487                                current_thought.push(' ');
1488                                current_thought.push_str(trimmed);
1489                            }
1490                        }
1491                        // Save last thought
1492                        if parsing_thought && !current_thought.is_empty() {
1493                            let mut new_branch = branch.clone();
1494                            new_branch.push(current_thought);
1495                            all_candidates.push((
1496                                new_branch,
1497                                format!("Confidence: {:.2}", current_confidence),
1498                                current_confidence,
1499                            ));
1500                        }
1501                    }
1502                }
1503                Err(e) => {
1504                    if let Some(ref healing) = healing_engine {
1505                        let mut engine = healing.lock().unwrap();
1506                        engine.record_failure("tree-of-thought", &e.to_string());
1507                    }
1508                    warn!(error = %e, "Tree-of-thought branch {} failed", branch_idx);
1509                }
1510            }
1511        }
1512
1513        if all_candidates.is_empty() {
1514            warn!("No candidates generated at depth {}", depth + 1);
1515            break;
1516        }
1517
1518        // Sort by confidence descending and keep top-k
1519        all_candidates.sort_by(|a, b| b.2.partial_cmp(&a.2).unwrap_or(std::cmp::Ordering::Equal));
1520        let top_k = pattern_config.tot_top_k.min(all_candidates.len());
1521        branches = all_candidates
1522            .into_iter()
1523            .take(top_k)
1524            .map(|(branch, _, _)| branch)
1525            .collect();
1526
1527        if pattern_config.verbose {
1528            println!(
1529                "\n── ToT Depth {}/{} ──",
1530                depth + 1,
1531                pattern_config.tot_depth
1532            );
1533            for (i, branch) in branches.iter().enumerate() {
1534                println!("Branch {} ({} steps):", i + 1, branch.len());
1535                for (j, thought) in branch.iter().enumerate() {
1536                    println!("  Step {}: {}", j + 1, thought);
1537                }
1538                println!();
1539            }
1540        }
1541    }
1542
1543    // Final synthesis: pick the best branch and produce a final answer
1544    let best_branch = branches.into_iter().next().unwrap_or_default();
1545    let mut final_memory = ConversationMemory::new(
1546        &format!(
1547            "{}\n\nYou are a synthesizer. Produce a final answer based on the best reasoning path.",
1548            system_prompt
1549        ),
1550        20,
1551    );
1552    final_memory.add_user_message(&format!(
1553        "Task: {}\n\nBest reasoning path:\n{}\n\nProduce a final, well-reasoned answer:",
1554        task,
1555        best_branch.join("\n→ "),
1556    ));
1557
1558    let messages = final_memory.history().to_vec();
1559    match llm.chat(messages).await {
1560        Ok(response) => {
1561            if let Some(ref healing) = healing_engine {
1562                let mut engine = healing.lock().unwrap();
1563                engine.record_success("tree-of-thought-synthesis");
1564            }
1565
1566            if let Some(choice) = response.choices.first() {
1567                println!(
1568                    "\n🐦‍⬛ Tree-of-Thought Final Answer:\n{}",
1569                    choice.message.content
1570                );
1571
1572                if let Some(ref rf) = ravenfabric {
1573                    if rf.is_enabled() {
1574                        let summary = format!(
1575                            "Tree-of-Thought completed: depth {}, branches explored",
1576                            pattern_config.tot_depth
1577                        );
1578                        let _ = rf.broadcast(&summary, 30).await;
1579                    }
1580                }
1581            }
1582        }
1583        Err(e) => {
1584            if let Some(ref healing) = healing_engine {
1585                let mut engine = healing.lock().unwrap();
1586                engine.record_failure("tree-of-thought-synthesis", &e.to_string());
1587            }
1588            warn!(error = %e, "Tree-of-thought synthesis failed");
1589        }
1590    }
1591
1592    Ok(())
1593}
1594
1595/// Run tree-of-thought with multiple LLM providers (round-robin per depth level)
1596pub async fn run_tree_of_thought_multi(
1597    multi_llm: MultiModelManager,
1598    config: Config,
1599    ravenfabric: Option<RavenFabricClient>,
1600    pattern_config: PatternConfig,
1601    healing_engine: Option<Arc<Mutex<SelfHealingEngine>>>,
1602) -> crate::error::Result<()> {
1603    info!(
1604        "Starting tree-of-thought mode (multi-model): {} branches, depth {}, top-k {}",
1605        pattern_config.tot_branches, pattern_config.tot_depth, pattern_config.tot_top_k
1606    );
1607
1608    let system_prompt = &config.llm.system_prompt;
1609    let task = "Analyze the given task and provide your solution.";
1610
1611    let mut branches: Vec<Vec<String>> = vec![Vec::new()];
1612
1613    for depth in 0..pattern_config.tot_depth {
1614        info!(
1615            depth = depth + 1,
1616            branches = branches.len(),
1617            "ToT multi step"
1618        );
1619
1620        // Check self-healing circuit breaker
1621        if let Some(ref healing) = healing_engine {
1622            let healthy = {
1623                let mut engine = healing.lock().unwrap();
1624                engine.is_healthy("tree-of-thought-multi")
1625            };
1626            if !healthy {
1627                warn!("Tree-of-thought (multi) blocked by circuit breaker");
1628                return Err(RavenClawsError::HealingError(
1629                    "Tree-of-thought (multi) blocked by circuit breaker".to_string(),
1630                ));
1631            }
1632        }
1633
1634        let mut all_candidates: Vec<(Vec<String>, String, f64)> = Vec::new();
1635
1636        for (branch_idx, branch) in branches.iter().enumerate() {
1637            let provider_idx = (depth * branches.len() + branch_idx) % multi_llm.client_count();
1638            let client = multi_llm.get_client(provider_idx);
1639
1640            if let Some(client) = client {
1641                let mut memory = ConversationMemory::new(
1642                    &format!("{}\n\nYou are a reasoning agent. Generate diverse candidate thoughts and evaluate them.", system_prompt),
1643                    30,
1644                );
1645                memory.add_user_message(&format!(
1646                    "Task: {}\n\nCurrent reasoning path (step {}/{}):\n{}\n\nGenerate {} distinct next-step thoughts. \
1647                     For each thought, provide:\n1. The thought itself\n2. A confidence score (0.0 to 1.0)\n\n\
1648                     Format each as:\nTHOUGHT: <your thought>\nCONFIDENCE: <0.0-1.0>",
1649                    task,
1650                    depth + 1,
1651                    pattern_config.tot_depth,
1652                    if branch.is_empty() {
1653                        "No steps yet — start from scratch.".to_string()
1654                    } else {
1655                        branch.join("\n")
1656                    },
1657                    pattern_config.tot_branches,
1658                ));
1659
1660                let messages = memory.history().to_vec();
1661                match client.chat(messages).await {
1662                    Ok(response) => {
1663                        if let Some(ref healing) = healing_engine {
1664                            let mut engine = healing.lock().unwrap();
1665                            engine.record_success("tree-of-thought-multi");
1666                        }
1667
1668                        if let Some(choice) = response.choices.first() {
1669                            let content = &choice.message.content;
1670                            let mut current_thought = String::new();
1671                            let mut current_confidence = 0.5;
1672                            let mut parsing_thought = false;
1673
1674                            for line in content.lines() {
1675                                let trimmed = line.trim();
1676                                if trimmed.starts_with("THOUGHT:") {
1677                                    if parsing_thought && !current_thought.is_empty() {
1678                                        let mut new_branch = branch.clone();
1679                                        new_branch.push(current_thought.clone());
1680                                        all_candidates.push((
1681                                            new_branch,
1682                                            format!("Confidence: {:.2}", current_confidence),
1683                                            current_confidence,
1684                                        ));
1685                                    }
1686                                    current_thought = trimmed
1687                                        .strip_prefix("THOUGHT:")
1688                                        .unwrap_or("")
1689                                        .trim()
1690                                        .to_string();
1691                                    parsing_thought = true;
1692                                    current_confidence = 0.5;
1693                                } else if trimmed.starts_with("CONFIDENCE:") {
1694                                    let val_str =
1695                                        trimmed.strip_prefix("CONFIDENCE:").unwrap_or("0.5").trim();
1696                                    if let Ok(val) = val_str.parse::<f64>() {
1697                                        current_confidence = val.clamp(0.0, 1.0);
1698                                    }
1699                                } else if parsing_thought && !trimmed.is_empty() {
1700                                    current_thought.push(' ');
1701                                    current_thought.push_str(trimmed);
1702                                }
1703                            }
1704                            if parsing_thought && !current_thought.is_empty() {
1705                                let mut new_branch = branch.clone();
1706                                new_branch.push(current_thought);
1707                                all_candidates.push((
1708                                    new_branch,
1709                                    format!("Confidence: {:.2}", current_confidence),
1710                                    current_confidence,
1711                                ));
1712                            }
1713                        }
1714                    }
1715                    Err(e) => {
1716                        if let Some(ref healing) = healing_engine {
1717                            let mut engine = healing.lock().unwrap();
1718                            engine.record_failure("tree-of-thought-multi", &e.to_string());
1719                        }
1720                        warn!(error = %e, "ToT multi branch {} failed", branch_idx);
1721                    }
1722                }
1723            }
1724        }
1725
1726        if all_candidates.is_empty() {
1727            warn!("No candidates generated at depth {}", depth + 1);
1728            break;
1729        }
1730
1731        all_candidates.sort_by(|a, b| b.2.partial_cmp(&a.2).unwrap_or(std::cmp::Ordering::Equal));
1732        let top_k = pattern_config.tot_top_k.min(all_candidates.len());
1733        branches = all_candidates
1734            .into_iter()
1735            .take(top_k)
1736            .map(|(branch, _, _)| branch)
1737            .collect();
1738
1739        if pattern_config.verbose {
1740            println!(
1741                "\n── ToT Multi Depth {}/{} ──",
1742                depth + 1,
1743                pattern_config.tot_depth
1744            );
1745            for (i, branch) in branches.iter().enumerate() {
1746                println!("Branch {} ({} steps):", i + 1, branch.len());
1747                for (j, thought) in branch.iter().enumerate() {
1748                    println!("  Step {}: {}", j + 1, thought);
1749                }
1750                println!();
1751            }
1752        }
1753    }
1754
1755    // Final synthesis using first provider
1756    let best_branch = branches.into_iter().next().unwrap_or_default();
1757    if let Some(client) = multi_llm.get_client(0) {
1758        if let Some(ref healing) = healing_engine {
1759            let healthy = {
1760                let mut engine = healing.lock().unwrap();
1761                engine.is_healthy("tree-of-thought-multi-synthesis")
1762            };
1763            if !healthy {
1764                warn!("ToT multi synthesis blocked by circuit breaker");
1765                return Err(RavenClawsError::HealingError(
1766                    "ToT multi synthesis blocked by circuit breaker".to_string(),
1767                ));
1768            }
1769        }
1770
1771        let mut final_memory = ConversationMemory::new(
1772            &format!("{}\n\nYou are a synthesizer. Produce a final answer based on the best reasoning path.", system_prompt),
1773            20,
1774        );
1775        final_memory.add_user_message(&format!(
1776            "Task: {}\n\nBest reasoning path:\n{}\n\nProduce a final, well-reasoned answer:",
1777            task,
1778            best_branch.join("\n→ "),
1779        ));
1780
1781        let messages = final_memory.history().to_vec();
1782        match client.chat(messages).await {
1783            Ok(response) => {
1784                if let Some(ref healing) = healing_engine {
1785                    let mut engine = healing.lock().unwrap();
1786                    engine.record_success("tree-of-thought-multi-synthesis");
1787                }
1788
1789                if let Some(choice) = response.choices.first() {
1790                    println!(
1791                        "\n🐦‍⬛ Tree-of-Thought (Multi) Final Answer:\n{}",
1792                        choice.message.content
1793                    );
1794
1795                    if let Some(ref rf) = ravenfabric {
1796                        if rf.is_enabled() {
1797                            let summary = format!(
1798                                "Tree-of-Thought (multi) completed: depth {}, branches explored",
1799                                pattern_config.tot_depth
1800                            );
1801                            let _ = rf.broadcast(&summary, 30).await;
1802                        }
1803                    }
1804                }
1805            }
1806            Err(e) => {
1807                if let Some(ref healing) = healing_engine {
1808                    let mut engine = healing.lock().unwrap();
1809                    engine.record_failure("tree-of-thought-multi-synthesis", &e.to_string());
1810                }
1811                warn!(error = %e, "ToT multi synthesis failed");
1812            }
1813        }
1814    }
1815
1816    Ok(())
1817}
1818
1819// ── Pattern 6: Self-Reflection ─────────────────────────────────────────────
1820
1821/// Run self-reflection reasoning.
1822///
1823/// The agent generates an initial solution, then reflects on it to identify
1824/// gaps, errors, or weaknesses, and produces an improved version. This
1825/// iterates for configurable rounds of reflection.
1826pub async fn run_self_reflection(
1827    llm: Arc<dyn LLMProviderTrait>,
1828    config: Config,
1829    ravenfabric: Option<RavenFabricClient>,
1830    pattern_config: PatternConfig,
1831    healing_engine: Option<Arc<Mutex<SelfHealingEngine>>>,
1832) -> crate::error::Result<()> {
1833    info!(
1834        "Starting self-reflection mode with {} reflection rounds",
1835        pattern_config.reflection_rounds
1836    );
1837
1838    let system_prompt = &config.llm.system_prompt;
1839    let task = "Analyze the given task and provide your solution.";
1840
1841    // Phase 1: Generate initial solution
1842    let mut current_solution = String::new();
1843
1844    {
1845        let mut memory = ConversationMemory::new(
1846            &format!(
1847                "{}\n\nYou are a problem solver. Generate a thorough solution.",
1848                system_prompt
1849            ),
1850            10,
1851        );
1852        memory.add_user_message(&format!("Task: {}\n\nProvide your solution:", task));
1853        let messages = memory.history().to_vec();
1854
1855        // Check self-healing circuit breaker
1856        if let Some(ref healing) = healing_engine {
1857            let healthy = {
1858                let mut engine = healing.lock().unwrap();
1859                engine.is_healthy("self-reflection-generate")
1860            };
1861            if !healthy {
1862                warn!("Self-reflection generation blocked by circuit breaker");
1863                return Err(RavenClawsError::HealingError(
1864                    "Self-reflection generation blocked by circuit breaker".to_string(),
1865                ));
1866            }
1867        }
1868
1869        match llm.chat(messages).await {
1870            Ok(response) => {
1871                if let Some(ref healing) = healing_engine {
1872                    let mut engine = healing.lock().unwrap();
1873                    engine.record_success("self-reflection-generate");
1874                }
1875
1876                if let Some(choice) = response.choices.first() {
1877                    current_solution = choice.message.content.clone();
1878                    info!("Initial solution: {} chars", current_solution.len());
1879                    if pattern_config.verbose {
1880                        println!(
1881                            "\n── Self-Reflection: Initial Solution ──\n{}",
1882                            current_solution
1883                        );
1884                    }
1885                }
1886            }
1887            Err(e) => {
1888                if let Some(ref healing) = healing_engine {
1889                    let mut engine = healing.lock().unwrap();
1890                    engine.record_failure("self-reflection-generate", &e.to_string());
1891                }
1892                warn!(error = %e, "Initial solution generation failed");
1893                return Err(RavenClawsError::Llm(crate::llm::LLMError::RequestFailed(
1894                    format!("Initial solution generation failed: {}", e),
1895                )));
1896            }
1897        }
1898    }
1899
1900    // Phase 2: Reflect and improve
1901    for round in 0..pattern_config.reflection_rounds {
1902        info!(round = round + 1, "Self-reflection round");
1903
1904        // Check self-healing circuit breaker
1905        if let Some(ref healing) = healing_engine {
1906            let healthy = {
1907                let mut engine = healing.lock().unwrap();
1908                engine.is_healthy(&format!("self-reflection-round-{}", round))
1909            };
1910            if !healthy {
1911                warn!(
1912                    round = round + 1,
1913                    "Self-reflection round blocked by circuit breaker"
1914                );
1915                break;
1916            }
1917        }
1918
1919        // Step A: Reflect on the current solution
1920        let mut reflection = String::new();
1921        {
1922            let mut reflect_memory = ConversationMemory::new(
1923                "You are a critical reviewer. Analyze solutions for gaps, errors, logical flaws, \
1924                 missing edge cases, and opportunities for improvement. Be thorough and constructive.",
1925                10,
1926            );
1927            reflect_memory.add_user_message(&format!(
1928                "Review this solution critically:\n\n{}\n\nIdentify:\n1. Gaps or missing elements\n\
1929                 2. Logical flaws or errors\n3. Edge cases not handled\n4. Opportunities for improvement\n\
1930                 5. Overall quality assessment",
1931                current_solution
1932            ));
1933            let messages = reflect_memory.history().to_vec();
1934
1935            match llm.chat(messages).await {
1936                Ok(response) => {
1937                    if let Some(ref healing) = healing_engine {
1938                        let mut engine = healing.lock().unwrap();
1939                        engine.record_success(&format!("self-reflection-round-{}", round));
1940                    }
1941
1942                    if let Some(choice) = response.choices.first() {
1943                        reflection = choice.message.content.clone();
1944                        if pattern_config.verbose {
1945                            println!(
1946                                "\n── Self-Reflection Round {}/{}: Reflection ──\n{}",
1947                                round + 1,
1948                                pattern_config.reflection_rounds,
1949                                reflection
1950                            );
1951                        }
1952                    }
1953                }
1954                Err(e) => {
1955                    if let Some(ref healing) = healing_engine {
1956                        let mut engine = healing.lock().unwrap();
1957                        engine.record_failure(
1958                            &format!("self-reflection-round-{}", round),
1959                            &e.to_string(),
1960                        );
1961                    }
1962                    warn!(error = %e, round = round + 1, "Reflection failed");
1963                    continue;
1964                }
1965            }
1966        }
1967
1968        // Step B: Improve based on reflection
1969        if !reflection.is_empty() {
1970            let mut improve_memory = ConversationMemory::new(
1971                &format!(
1972                    "{}\n\nYou are an improver. Take feedback and produce a better version.",
1973                    system_prompt
1974                ),
1975                10,
1976            );
1977            improve_memory.add_user_message(&format!(
1978                "Original solution:\n{}\n\nFeedback received:\n{}\n\n\
1979                 Produce an improved solution that addresses all feedback. \
1980                 Mark your response with IMPROVED: at the start.",
1981                current_solution, reflection
1982            ));
1983            let messages = improve_memory.history().to_vec();
1984
1985            match llm.chat(messages).await {
1986                Ok(response) => {
1987                    if let Some(choice) = response.choices.first() {
1988                        let improved = choice.message.content.clone();
1989                        let improved_clean = improved
1990                            .strip_prefix("IMPROVED:")
1991                            .unwrap_or(&improved)
1992                            .trim()
1993                            .to_string();
1994                        current_solution = improved_clean;
1995                        info!(
1996                            round = round + 1,
1997                            "Solution improved: {} chars",
1998                            current_solution.len()
1999                        );
2000                    }
2001                }
2002                Err(e) => {
2003                    warn!(error = %e, round = round + 1, "Improvement failed");
2004                }
2005            }
2006        }
2007    }
2008
2009    println!(
2010        "\n🐦‍⬛ Self-Reflection Final Solution (after {} rounds):\n{}",
2011        pattern_config.reflection_rounds, current_solution
2012    );
2013
2014    if let Some(ref rf) = ravenfabric {
2015        if rf.is_enabled() {
2016            let summary = format!(
2017                "Self-reflection completed: {} rounds, final solution {} chars",
2018                pattern_config.reflection_rounds,
2019                current_solution.len()
2020            );
2021            let _ = rf.broadcast(&summary, 30).await;
2022        }
2023    }
2024
2025    Ok(())
2026}
2027
2028/// Run self-reflection with multiple LLM providers (alternating roles)
2029pub async fn run_self_reflection_multi(
2030    multi_llm: MultiModelManager,
2031    config: Config,
2032    ravenfabric: Option<RavenFabricClient>,
2033    pattern_config: PatternConfig,
2034    healing_engine: Option<Arc<Mutex<SelfHealingEngine>>>,
2035) -> crate::error::Result<()> {
2036    info!(
2037        "Starting self-reflection mode (multi-model) with {} rounds",
2038        pattern_config.reflection_rounds
2039    );
2040
2041    let system_prompt = &config.llm.system_prompt;
2042    let task = "Analyze the given task and provide your solution.";
2043
2044    // Phase 1: Generate initial solution using first provider
2045    let mut current_solution = String::new();
2046
2047    if let Some(client) = multi_llm.get_client(0) {
2048        if let Some(ref healing) = healing_engine {
2049            let healthy = {
2050                let mut engine = healing.lock().unwrap();
2051                engine.is_healthy("self-reflection-multi-generate")
2052            };
2053            if !healthy {
2054                warn!("Self-reflection multi generation blocked by circuit breaker");
2055                return Err(RavenClawsError::HealingError(
2056                    "Self-reflection multi generation blocked by circuit breaker".to_string(),
2057                ));
2058            }
2059        }
2060
2061        let mut memory = ConversationMemory::new(
2062            &format!(
2063                "{}\n\nYou are a problem solver. Generate a thorough solution.",
2064                system_prompt
2065            ),
2066            10,
2067        );
2068        memory.add_user_message(&format!("Task: {}\n\nProvide your solution:", task));
2069        let messages = memory.history().to_vec();
2070
2071        match client.chat(messages).await {
2072            Ok(response) => {
2073                if let Some(ref healing) = healing_engine {
2074                    let mut engine = healing.lock().unwrap();
2075                    engine.record_success("self-reflection-multi-generate");
2076                }
2077
2078                if let Some(choice) = response.choices.first() {
2079                    current_solution = choice.message.content.clone();
2080                    info!(
2081                        "Initial solution: {} chars via {}",
2082                        current_solution.len(),
2083                        client.provider_name()
2084                    );
2085                    if pattern_config.verbose {
2086                        println!(
2087                            "\n── Self-Reflection Multi: Initial Solution ──\n{}",
2088                            current_solution
2089                        );
2090                    }
2091                }
2092            }
2093            Err(e) => {
2094                if let Some(ref healing) = healing_engine {
2095                    let mut engine = healing.lock().unwrap();
2096                    engine.record_failure("self-reflection-multi-generate", &e.to_string());
2097                }
2098                warn!(error = %e, "Multi initial solution generation failed");
2099                return Err(RavenClawsError::Llm(crate::llm::LLMError::RequestFailed(
2100                    format!("Multi initial solution generation failed: {}", e),
2101                )));
2102            }
2103        }
2104    }
2105
2106    // Phase 2: Reflect and improve using alternating providers
2107    for round in 0..pattern_config.reflection_rounds {
2108        info!(round = round + 1, "Self-reflection multi round");
2109
2110        // Use different providers for reflection vs improvement
2111        let reflector_idx = (round * 2 + 1) % multi_llm.client_count();
2112        let improver_idx = (round * 2 + 2) % multi_llm.client_count();
2113
2114        // Check self-healing circuit breaker
2115        if let Some(ref healing) = healing_engine {
2116            let healthy = {
2117                let mut engine = healing.lock().unwrap();
2118                engine.is_healthy(&format!("self-reflection-multi-round-{}", round))
2119            };
2120            if !healthy {
2121                warn!(
2122                    round = round + 1,
2123                    "Self-reflection multi round blocked by circuit breaker"
2124                );
2125                break;
2126            }
2127        }
2128
2129        // Step A: Reflect using one provider
2130        let mut reflection = String::new();
2131        if let Some(reflector) = multi_llm.get_client(reflector_idx) {
2132            let mut reflect_memory = ConversationMemory::new(
2133                "You are a critical reviewer. Analyze solutions for gaps, errors, logical flaws, \
2134                 missing edge cases, and opportunities for improvement. Be thorough and constructive.",
2135                10,
2136            );
2137            reflect_memory.add_user_message(&format!(
2138                "Review this solution critically:\n\n{}\n\nIdentify:\n1. Gaps or missing elements\n\
2139                 2. Logical flaws or errors\n3. Edge cases not handled\n4. Opportunities for improvement\n\
2140                 5. Overall quality assessment",
2141                current_solution
2142            ));
2143            let messages = reflect_memory.history().to_vec();
2144
2145            match reflector.chat(messages).await {
2146                Ok(response) => {
2147                    if let Some(ref healing) = healing_engine {
2148                        let mut engine = healing.lock().unwrap();
2149                        engine.record_success(&format!("self-reflection-multi-round-{}", round));
2150                    }
2151
2152                    if let Some(choice) = response.choices.first() {
2153                        reflection = choice.message.content.clone();
2154                        if pattern_config.verbose {
2155                            println!(
2156                                "\n── Self-Reflection Multi Round {}/{}: Reflection ({} via {}) ──\n{}",
2157                                round + 1,
2158                                pattern_config.reflection_rounds,
2159                                reflector.provider_name(),
2160                                reflector.model(),
2161                                reflection
2162                            );
2163                        }
2164                    }
2165                }
2166                Err(e) => {
2167                    if let Some(ref healing) = healing_engine {
2168                        let mut engine = healing.lock().unwrap();
2169                        engine.record_failure(
2170                            &format!("self-reflection-multi-round-{}", round),
2171                            &e.to_string(),
2172                        );
2173                    }
2174                    warn!(error = %e, round = round + 1, "Multi reflection failed");
2175                    continue;
2176                }
2177            }
2178        }
2179
2180        // Step B: Improve using a different provider
2181        if !reflection.is_empty() {
2182            if let Some(improver) = multi_llm.get_client(improver_idx) {
2183                let mut improve_memory = ConversationMemory::new(
2184                    &format!(
2185                        "{}\n\nYou are an improver. Take feedback and produce a better version.",
2186                        system_prompt
2187                    ),
2188                    10,
2189                );
2190                improve_memory.add_user_message(&format!(
2191                    "Original solution:\n{}\n\nFeedback received:\n{}\n\n\
2192                     Produce an improved solution that addresses all feedback. \
2193                     Mark your response with IMPROVED: at the start.",
2194                    current_solution, reflection
2195                ));
2196                let messages = improve_memory.history().to_vec();
2197
2198                match improver.chat(messages).await {
2199                    Ok(response) => {
2200                        if let Some(choice) = response.choices.first() {
2201                            let improved = choice.message.content.clone();
2202                            let improved_clean = improved
2203                                .strip_prefix("IMPROVED:")
2204                                .unwrap_or(&improved)
2205                                .trim()
2206                                .to_string();
2207                            current_solution = improved_clean;
2208                            info!(
2209                                round = round + 1,
2210                                "Solution improved via {}: {} chars",
2211                                improver.provider_name(),
2212                                current_solution.len()
2213                            );
2214                        }
2215                    }
2216                    Err(e) => {
2217                        warn!(error = %e, round = round + 1, "Multi improvement failed");
2218                    }
2219                }
2220            }
2221        }
2222    }
2223
2224    println!(
2225        "\n🐦‍⬛ Self-Reflection (Multi) Final Solution (after {} rounds):\n{}",
2226        pattern_config.reflection_rounds, current_solution
2227    );
2228
2229    if let Some(ref rf) = ravenfabric {
2230        if rf.is_enabled() {
2231            let summary = format!(
2232                "Self-reflection (multi) completed: {} rounds, final solution {} chars",
2233                pattern_config.reflection_rounds,
2234                current_solution.len()
2235            );
2236            let _ = rf.broadcast(&summary, 30).await;
2237        }
2238    }
2239
2240    Ok(())
2241}
2242
2243// ── Tests ──────────────────────────────────────────────────────────────────
2244
2245#[cfg(test)]
2246mod tests {
2247    use super::*;
2248
2249    #[test]
2250    fn test_pattern_config_defaults() {
2251        let cfg = PatternConfig::default();
2252        assert_eq!(cfg.max_rounds, 3);
2253        assert_eq!(cfg.max_review_iterations, 3);
2254        assert_eq!(cfg.research_agent_count, 3);
2255        assert_eq!(cfg.voter_count, 3);
2256        assert!(!cfg.verbose);
2257        assert_eq!(cfg.tot_branches, 3);
2258        assert_eq!(cfg.tot_depth, 3);
2259        assert_eq!(cfg.tot_top_k, 2);
2260        assert_eq!(cfg.reflection_rounds, 2);
2261    }
2262
2263    #[test]
2264    fn test_pattern_config_custom() {
2265        let cfg = PatternConfig {
2266            max_rounds: 5,
2267            max_review_iterations: 5,
2268            research_agent_count: 5,
2269            voter_count: 7,
2270            verbose: true,
2271            tot_branches: 4,
2272            tot_depth: 5,
2273            tot_top_k: 3,
2274            reflection_rounds: 4,
2275        };
2276        assert_eq!(cfg.max_rounds, 5);
2277        assert_eq!(cfg.voter_count, 7);
2278        assert!(cfg.verbose);
2279        assert_eq!(cfg.tot_branches, 4);
2280        assert_eq!(cfg.tot_depth, 5);
2281        assert_eq!(cfg.tot_top_k, 3);
2282        assert_eq!(cfg.reflection_rounds, 4);
2283    }
2284
2285    #[test]
2286    fn test_debate_function_exists() {
2287        // Compile-time check that debate function signature is valid
2288        let _ = std::mem::size_of::<PatternConfig>();
2289    }
2290
2291    #[test]
2292    fn test_review_loop_function_exists() {
2293        let cfg = PatternConfig::default();
2294        assert_eq!(cfg.max_review_iterations, 3);
2295    }
2296
2297    #[test]
2298    fn test_research_synthesize_function_exists() {
2299        let cfg = PatternConfig::default();
2300        assert_eq!(cfg.research_agent_count, 3);
2301    }
2302
2303    #[test]
2304    fn test_voting_function_exists() {
2305        let cfg = PatternConfig::default();
2306        assert_eq!(cfg.voter_count, 3);
2307    }
2308
2309    #[test]
2310    fn test_tree_of_thought_config() {
2311        let cfg = PatternConfig::default();
2312        assert_eq!(cfg.tot_branches, 3);
2313        assert_eq!(cfg.tot_depth, 3);
2314        assert_eq!(cfg.tot_top_k, 2);
2315    }
2316
2317    #[test]
2318    fn test_self_reflection_config() {
2319        let cfg = PatternConfig::default();
2320        assert_eq!(cfg.reflection_rounds, 2);
2321    }
2322}