1use std::fmt;
25
26use serde::{Deserialize, Serialize};
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
32pub enum StrategyStep {
33 Thought(String),
35 Action {
37 tool: String,
39 args: serde_json::Value,
41 },
42 Observation(String),
44 Reflection {
46 critique: String,
48 revised_plan: String,
50 },
51 Branch {
53 branch_id: usize,
55 thought: String,
57 score: f64,
59 },
60 FinalAnswer(String),
62}
63
64impl fmt::Display for StrategyStep {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 match self {
67 StrategyStep::Thought(t) => write!(f, "Thought: {}", t),
68 StrategyStep::Action { tool, .. } => write!(f, "Action: {}", tool),
69 StrategyStep::Observation(o) => write!(f, "Observation: {}", o),
70 StrategyStep::Reflection { critique, .. } => write!(f, "Reflection: {}", critique),
71 StrategyStep::Branch {
72 branch_id,
73 thought,
74 score,
75 } => {
76 write!(f, "Branch[{}] (score={:.2}): {}", branch_id, score, thought)
77 }
78 StrategyStep::FinalAnswer(a) => write!(f, "Final: {}", a),
79 }
80 }
81}
82
83pub trait ReasoningStrategy: Send + Sync {
90 fn name(&self) -> &str;
92
93 fn description(&self) -> &str;
95
96 fn system_prompt(&self, agent_id: &str, working_directory: &str) -> String;
102
103 fn is_complete(&self, steps: &[StrategyStep]) -> bool;
108
109 fn max_steps(&self) -> usize;
111}
112
113#[derive(Debug, Clone)]
120pub struct ReActStrategy {
121 max_steps: usize,
122}
123
124impl ReActStrategy {
125 pub fn new(max_steps: usize) -> Self {
127 Self { max_steps }
128 }
129}
130
131impl Default for ReActStrategy {
132 fn default() -> Self {
133 Self { max_steps: 15 }
134 }
135}
136
137impl ReasoningStrategy for ReActStrategy {
138 fn name(&self) -> &str {
139 "ReAct"
140 }
141
142 fn description(&self) -> &str {
143 "Reasoning + Acting: alternate between Thought, Action, and Observation steps"
144 }
145
146 fn system_prompt(&self, agent_id: &str, working_directory: &str) -> String {
147 format!(
148 r#"You are a task agent (ID: {agent_id}) using the ReAct reasoning framework.
149
150Working Directory: {working_directory}
151
152# ReAct FRAMEWORK
153
154You MUST follow this strict loop for every step:
155
1561. **Thought**: Reason about what you need to do next. What information do
157 you have? What do you still need? What is the best next action?
158
1592. **Action**: Execute exactly ONE tool call based on your thought.
160
1613. **Observation**: Analyze the result. Did it succeed? What did you learn?
162 Does the result change your plan?
163
164Then repeat: Thought → Action → Observation → Thought → ...
165
166## Format
167
168Always structure your response as:
169
170Thought: <your reasoning about what to do next>
171Action: <use a tool>
172Observation: <analyze the result after receiving it>
173
174## Rules
175
176- Each cycle must have exactly ONE action (tool call)
177- Never skip the Thought step — always reason before acting
178- If a tool call fails, reflect in your next Thought and adjust
179- When the task is complete, state your final answer clearly
180- Maximum {max_steps} reasoning cycles allowed
181
182## Completion
183
184When the task is fully complete, provide your final summary.
185Do NOT continue cycling after the task is done."#,
186 agent_id = agent_id,
187 working_directory = working_directory,
188 max_steps = self.max_steps,
189 )
190 }
191
192 fn is_complete(&self, steps: &[StrategyStep]) -> bool {
193 if steps.len() >= self.max_steps {
194 return true;
195 }
196 steps
197 .iter()
198 .any(|s| matches!(s, StrategyStep::FinalAnswer(_)))
199 }
200
201 fn max_steps(&self) -> usize {
202 self.max_steps
203 }
204}
205
206#[derive(Debug, Clone)]
213pub struct ReflexionStrategy {
214 max_steps: usize,
215 max_reflections: usize,
217}
218
219impl ReflexionStrategy {
220 pub fn new(max_steps: usize, max_reflections: usize) -> Self {
222 Self {
223 max_steps,
224 max_reflections,
225 }
226 }
227}
228
229impl Default for ReflexionStrategy {
230 fn default() -> Self {
231 Self {
232 max_steps: 20,
233 max_reflections: 5,
234 }
235 }
236}
237
238impl ReasoningStrategy for ReflexionStrategy {
239 fn name(&self) -> &str {
240 "Reflexion"
241 }
242
243 fn description(&self) -> &str {
244 "Self-critique after each action cycle with iterative plan refinement"
245 }
246
247 fn system_prompt(&self, agent_id: &str, working_directory: &str) -> String {
248 format!(
249 r#"You are a task agent (ID: {agent_id}) using the Reflexion reasoning framework.
250
251Working Directory: {working_directory}
252
253# REFLEXION FRAMEWORK
254
255You follow a 4-phase loop:
256
2571. **Plan**: State your current plan and what you intend to do
2582. **Act**: Execute your planned action using a tool
2593. **Observe**: Analyze the result
2604. **Reflect**: Critically evaluate your approach:
261 - What went well?
262 - What could be improved?
263 - Should I change my plan?
264 - Am I making progress toward the goal?
265
266Then update your plan and repeat.
267
268## Format
269
270Structure each cycle as:
271
272Plan: <current plan and next intended action>
273Act: <use a tool>
274Observe: <what happened>
275Reflect: <self-critique — what worked, what didn't, revised plan>
276
277## Rules
278
279- Every action MUST be followed by reflection
280- Be honest in your self-critique — identify mistakes early
281- Update your plan after each reflection if needed
282- If you've made the same mistake twice, try a completely different approach
283- Maximum {max_reflections} reflection cycles before you must finalize
284- Maximum {max_steps} total steps allowed
285
286## Completion
287
288After your final reflection confirms the task is complete, provide
289your summary."#,
290 agent_id = agent_id,
291 working_directory = working_directory,
292 max_reflections = self.max_reflections,
293 max_steps = self.max_steps,
294 )
295 }
296
297 fn is_complete(&self, steps: &[StrategyStep]) -> bool {
298 if steps.len() >= self.max_steps {
299 return true;
300 }
301 let reflection_count = steps
302 .iter()
303 .filter(|s| matches!(s, StrategyStep::Reflection { .. }))
304 .count();
305 if reflection_count >= self.max_reflections {
306 return true;
307 }
308 steps
309 .iter()
310 .any(|s| matches!(s, StrategyStep::FinalAnswer(_)))
311 }
312
313 fn max_steps(&self) -> usize {
314 self.max_steps
315 }
316}
317
318#[derive(Debug, Clone)]
325pub struct ChainOfThoughtStrategy {
326 max_steps: usize,
327}
328
329impl ChainOfThoughtStrategy {
330 pub fn new(max_steps: usize) -> Self {
332 Self { max_steps }
333 }
334}
335
336impl Default for ChainOfThoughtStrategy {
337 fn default() -> Self {
338 Self { max_steps: 15 }
339 }
340}
341
342impl ReasoningStrategy for ChainOfThoughtStrategy {
343 fn name(&self) -> &str {
344 "Chain-of-Thought"
345 }
346
347 fn description(&self) -> &str {
348 "Step-by-step reasoning before taking action"
349 }
350
351 fn system_prompt(&self, agent_id: &str, working_directory: &str) -> String {
352 format!(
353 r#"You are a task agent (ID: {agent_id}) using Chain-of-Thought reasoning.
354
355Working Directory: {working_directory}
356
357# CHAIN OF THOUGHT FRAMEWORK
358
359Before taking ANY action, you MUST reason through the problem step by step.
360
361## Process
362
3631. **Decompose**: Break the task into numbered steps
3642. **Reason**: For each step, explain your logic:
365 - What do I know?
366 - What do I need to find out?
367 - What is the logical next step?
3683. **Act**: After reasoning, execute the steps using tools
3694. **Verify**: Confirm each step's result before proceeding
370
371## Format
372
373Let's think step by step:
374
375Step 1: <describe what you need to do and why>
376Step 2: <next logical step>
377Step 3: ...
378
379Then execute each step, verifying results between actions.
380
381## Rules
382
383- Always number your reasoning steps
384- Show your work — explain WHY, not just WHAT
385- If a step's result is unexpected, re-reason from that point
386- Do not skip steps — complete each one before moving on
387- Maximum {max_steps} steps allowed
388
389## Completion
390
391After all steps are verified, provide your final answer."#,
392 agent_id = agent_id,
393 working_directory = working_directory,
394 max_steps = self.max_steps,
395 )
396 }
397
398 fn is_complete(&self, steps: &[StrategyStep]) -> bool {
399 if steps.len() >= self.max_steps {
400 return true;
401 }
402 steps
403 .iter()
404 .any(|s| matches!(s, StrategyStep::FinalAnswer(_)))
405 }
406
407 fn max_steps(&self) -> usize {
408 self.max_steps
409 }
410}
411
412#[derive(Debug, Clone)]
420pub struct TreeOfThoughtsStrategy {
421 max_steps: usize,
422 branching_factor: usize,
424 pruning_threshold: f64,
426}
427
428impl TreeOfThoughtsStrategy {
429 pub fn new(max_steps: usize, branching_factor: usize, pruning_threshold: f64) -> Self {
431 Self {
432 max_steps,
433 branching_factor,
434 pruning_threshold,
435 }
436 }
437}
438
439impl Default for TreeOfThoughtsStrategy {
440 fn default() -> Self {
441 Self {
442 max_steps: 25,
443 branching_factor: 3,
444 pruning_threshold: 0.4,
445 }
446 }
447}
448
449impl ReasoningStrategy for TreeOfThoughtsStrategy {
450 fn name(&self) -> &str {
451 "Tree-of-Thoughts"
452 }
453
454 fn description(&self) -> &str {
455 "Multi-branch exploration with scoring and pruning"
456 }
457
458 fn system_prompt(&self, agent_id: &str, working_directory: &str) -> String {
459 format!(
460 r#"You are a task agent (ID: {agent_id}) using Tree-of-Thoughts reasoning.
461
462Working Directory: {working_directory}
463
464# TREE OF THOUGHTS FRAMEWORK
465
466At each decision point, generate {branching_factor} candidate approaches,
467evaluate them, and pursue the best one.
468
469## Process
470
4711. **Generate**: Propose {branching_factor} different approaches to the
472 current sub-problem
4732. **Evaluate**: Score each approach (0.0 to 1.0) based on:
474 - Likelihood of success
475 - Efficiency (fewer steps = higher score)
476 - Risk of side effects
477 - Alignment with the overall goal
4783. **Select**: Choose the approach with the highest score
479 (must be above {pruning_threshold:.1} to proceed)
4804. **Execute**: Implement the selected approach
4815. **Backtrack**: If the selected approach fails, try the next-best candidate
482
483## Format
484
485=== Decision Point ===
486Candidate 1: <approach description> → Score: X.X
487Candidate 2: <approach description> → Score: X.X
488Candidate 3: <approach description> → Score: X.X
489
490Selected: Candidate N (score: X.X)
491Reason: <why this is the best approach>
492
493Then execute the selected approach.
494
495## Rules
496
497- Generate exactly {branching_factor} candidates at each major decision
498- Score honestly — don't inflate scores to justify a preference
499- If all candidates score below {pruning_threshold:.1}, step back and reconsider
500 the problem framing
501- Keep a mental note of unexplored branches in case backtracking is needed
502- Maximum {max_steps} total steps allowed
503
504## Completion
505
506When the task is complete, summarize which branches were explored and why
507the final approach was chosen."#,
508 agent_id = agent_id,
509 working_directory = working_directory,
510 branching_factor = self.branching_factor,
511 pruning_threshold = self.pruning_threshold,
512 max_steps = self.max_steps,
513 )
514 }
515
516 fn is_complete(&self, steps: &[StrategyStep]) -> bool {
517 if steps.len() >= self.max_steps {
518 return true;
519 }
520 steps
521 .iter()
522 .any(|s| matches!(s, StrategyStep::FinalAnswer(_)))
523 }
524
525 fn max_steps(&self) -> usize {
526 self.max_steps
527 }
528}
529
530#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
534pub enum StrategyPreset {
535 ReAct,
537 Reflexion,
539 ChainOfThought,
541 TreeOfThoughts,
543}
544
545impl StrategyPreset {
546 pub fn create(&self) -> Box<dyn ReasoningStrategy> {
548 match self {
549 StrategyPreset::ReAct => Box::new(ReActStrategy::default()),
550 StrategyPreset::Reflexion => Box::new(ReflexionStrategy::default()),
551 StrategyPreset::ChainOfThought => Box::new(ChainOfThoughtStrategy::default()),
552 StrategyPreset::TreeOfThoughts => Box::new(TreeOfThoughtsStrategy::default()),
553 }
554 }
555
556 pub fn all() -> &'static [StrategyPreset] {
558 &[
559 StrategyPreset::ReAct,
560 StrategyPreset::Reflexion,
561 StrategyPreset::ChainOfThought,
562 StrategyPreset::TreeOfThoughts,
563 ]
564 }
565}
566
567impl fmt::Display for StrategyPreset {
568 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
569 match self {
570 StrategyPreset::ReAct => write!(f, "ReAct"),
571 StrategyPreset::Reflexion => write!(f, "Reflexion"),
572 StrategyPreset::ChainOfThought => write!(f, "Chain-of-Thought"),
573 StrategyPreset::TreeOfThoughts => write!(f, "Tree-of-Thoughts"),
574 }
575 }
576}
577
578#[cfg(test)]
581mod tests {
582 use super::*;
583
584 #[test]
585 fn test_react_system_prompt() {
586 let strategy = ReActStrategy::new(10);
587 let prompt = strategy.system_prompt("agent-1", "/project");
588 assert!(prompt.contains("ReAct"));
589 assert!(prompt.contains("Thought"));
590 assert!(prompt.contains("Action"));
591 assert!(prompt.contains("Observation"));
592 assert!(prompt.contains("agent-1"));
593 }
594
595 #[test]
596 fn test_react_completion() {
597 let strategy = ReActStrategy::new(3);
598 let steps = vec![
599 StrategyStep::Thought("thinking".to_string()),
600 StrategyStep::Action {
601 tool: "read".to_string(),
602 args: serde_json::json!({}),
603 },
604 ];
605 assert!(!strategy.is_complete(&steps));
606
607 let steps_with_answer = vec![
608 StrategyStep::Thought("thinking".to_string()),
609 StrategyStep::FinalAnswer("done".to_string()),
610 ];
611 assert!(strategy.is_complete(&steps_with_answer));
612 }
613
614 #[test]
615 fn test_react_max_steps() {
616 let strategy = ReActStrategy::new(2);
617 let steps = vec![
618 StrategyStep::Thought("a".to_string()),
619 StrategyStep::Thought("b".to_string()),
620 ];
621 assert!(strategy.is_complete(&steps));
622 }
623
624 #[test]
625 fn test_reflexion_system_prompt() {
626 let strategy = ReflexionStrategy::new(15, 3);
627 let prompt = strategy.system_prompt("agent-2", "/work");
628 assert!(prompt.contains("Reflexion"));
629 assert!(prompt.contains("Reflect"));
630 assert!(prompt.contains("self-critique"));
631 }
632
633 #[test]
634 fn test_reflexion_max_reflections() {
635 let strategy = ReflexionStrategy::new(20, 2);
636 let steps = vec![
637 StrategyStep::Reflection {
638 critique: "a".into(),
639 revised_plan: "b".into(),
640 },
641 StrategyStep::Reflection {
642 critique: "c".into(),
643 revised_plan: "d".into(),
644 },
645 ];
646 assert!(strategy.is_complete(&steps));
647 }
648
649 #[test]
650 fn test_cot_system_prompt() {
651 let strategy = ChainOfThoughtStrategy::new(10);
652 let prompt = strategy.system_prompt("agent-3", "/code");
653 assert!(prompt.contains("Chain-of-Thought") || prompt.contains("Chain of Thought"));
654 assert!(prompt.contains("step by step"));
655 }
656
657 #[test]
658 fn test_tot_system_prompt() {
659 let strategy = TreeOfThoughtsStrategy::new(20, 3, 0.4);
660 let prompt = strategy.system_prompt("agent-4", "/proj");
661 assert!(prompt.contains("Tree-of-Thoughts") || prompt.contains("Tree of Thoughts"));
662 assert!(prompt.contains("3")); }
664
665 #[test]
666 fn test_strategy_preset_create() {
667 for preset in StrategyPreset::all() {
668 let strategy = preset.create();
669 assert!(!strategy.name().is_empty());
670 assert!(!strategy.description().is_empty());
671 assert!(strategy.max_steps() > 0);
672 }
673 }
674
675 #[test]
676 fn test_strategy_step_display() {
677 let step = StrategyStep::Thought("testing".to_string());
678 assert_eq!(format!("{}", step), "Thought: testing");
679
680 let step = StrategyStep::Branch {
681 branch_id: 1,
682 thought: "try X".to_string(),
683 score: 0.85,
684 };
685 assert!(format!("{}", step).contains("0.85"));
686 }
687}