1use crate::types::{Layer2Result, TaskId};
8use crate::workflow_engine::{Dag, Node};
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
13pub enum DecompositionStrategy {
14 Sequential,
16 Parallel,
18 #[default]
20 Hybrid,
21 Hierarchical,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct SubTask {
28 pub id: String,
30 pub name: String,
32 pub description: String,
34 pub priority: u32,
36 pub dependencies: Vec<String>,
38 pub estimated_complexity: u32,
40 pub tool: Option<String>,
42 pub tool_args: Option<serde_json::Value>,
44 pub validation_criteria: Vec<String>,
46 pub fallback: Option<Box<SubTask>>,
48}
49
50impl SubTask {
51 pub fn new(
53 id: impl Into<String>,
54 name: impl Into<String>,
55 description: impl Into<String>,
56 ) -> Self {
57 Self {
58 id: id.into(),
59 name: name.into(),
60 description: description.into(),
61 priority: 0,
62 dependencies: Vec::new(),
63 estimated_complexity: 5,
64 tool: None,
65 tool_args: None,
66 validation_criteria: Vec::new(),
67 fallback: None,
68 }
69 }
70
71 pub fn with_priority(mut self, priority: u32) -> Self {
73 self.priority = priority;
74 self
75 }
76
77 pub fn with_dependency(mut self, dep_id: impl Into<String>) -> Self {
79 self.dependencies.push(dep_id.into());
80 self
81 }
82
83 pub fn with_tool(mut self, tool: impl Into<String>, args: serde_json::Value) -> Self {
85 self.tool = Some(tool.into());
86 self.tool_args = Some(args);
87 self
88 }
89
90 pub fn with_validation(mut self, criteria: impl Into<String>) -> Self {
92 self.validation_criteria.push(criteria.into());
93 self
94 }
95
96 pub fn with_fallback(mut self, fallback: SubTask) -> Self {
98 self.fallback = Some(Box::new(fallback));
99 self
100 }
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct ExecutionPlan {
106 pub id: String,
108 pub original_task: String,
110 pub strategy: DecompositionStrategy,
112 pub subtasks: Vec<SubTask>,
114 pub execution_order: Vec<String>,
116 pub estimated_steps: u32,
118 pub risk_level: RiskLevel,
120 pub created_at: chrono::DateTime<chrono::Utc>,
122}
123
124#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
126pub enum RiskLevel {
127 Low,
129 #[default]
131 Medium,
132 High,
134 Critical,
136}
137
138impl ExecutionPlan {
139 pub fn new(original_task: impl Into<String>) -> Self {
141 Self {
142 id: TaskId::new().to_string(),
143 original_task: original_task.into(),
144 strategy: DecompositionStrategy::default(),
145 subtasks: Vec::new(),
146 execution_order: Vec::new(),
147 estimated_steps: 1,
148 risk_level: RiskLevel::default(),
149 created_at: chrono::Utc::now(),
150 }
151 }
152
153 pub fn add_subtask(&mut self, subtask: SubTask) -> &mut Self {
155 self.subtasks.push(subtask);
156 self
157 }
158
159 pub fn compute_execution_order(&mut self) -> Layer2Result<()> {
161 let mut dag = Dag::new();
162
163 for subtask in &self.subtasks {
165 let node = Node::new(&subtask.id, &subtask.name);
166 dag.add_node(node)?;
167 }
168
169 for subtask in &self.subtasks {
171 for dep in &subtask.dependencies {
172 dag.add_edge(dep, &subtask.id)?;
173 }
174 }
175
176 if dag.has_cycle() {
178 return Err(anyhow::anyhow!(
179 "Circular dependency detected in execution plan"
180 ));
181 }
182
183 self.execution_order = dag.topological_sort()?;
185
186 self.estimated_steps = self.subtasks.len() as u32;
188
189 Ok(())
190 }
191
192 pub fn to_dag(&self) -> Layer2Result<Dag> {
194 let mut dag = Dag::new();
195
196 for subtask in &self.subtasks {
197 let mut node = Node::new(&subtask.id, &subtask.name);
198 node.config = serde_json::json!({
200 "description": subtask.description,
201 "tool": subtask.tool,
202 "tool_args": subtask.tool_args,
203 });
204 dag.add_node(node)?;
205 }
206
207 for subtask in &self.subtasks {
208 for dep in &subtask.dependencies {
209 dag.add_edge(dep, &subtask.id)?;
210 }
211 }
212
213 Ok(dag)
214 }
215}
216
217pub struct TaskDecomposer {
219 strategy: DecompositionStrategy,
221 max_depth: u32,
223 #[allow(dead_code)]
225 min_granularity: u32,
226}
227
228impl Default for TaskDecomposer {
229 fn default() -> Self {
230 Self {
231 strategy: DecompositionStrategy::Hybrid,
232 max_depth: 3,
233 min_granularity: 1,
234 }
235 }
236}
237
238impl TaskDecomposer {
239 pub fn new() -> Self {
241 Self::default()
242 }
243
244 pub fn with_strategy(mut self, strategy: DecompositionStrategy) -> Self {
246 self.strategy = strategy;
247 self
248 }
249
250 pub fn with_max_depth(mut self, depth: u32) -> Self {
252 self.max_depth = depth;
253 self
254 }
255
256 pub fn decompose(&self, task: &str) -> Layer2Result<ExecutionPlan> {
258 let mut plan = ExecutionPlan::new(task);
259 plan.strategy = self.strategy;
260
261 let complexity = self.analyze_complexity(task);
263 plan.risk_level = self.estimate_risk(task, complexity);
264
265 let subtasks = match self.strategy {
267 DecompositionStrategy::Sequential => self.decompose_sequential(task),
268 DecompositionStrategy::Parallel => self.decompose_parallel(task),
269 DecompositionStrategy::Hierarchical => self.decompose_hierarchical(task, 0),
270 DecompositionStrategy::Hybrid => self.decompose_hybrid(task),
271 };
272
273 for subtask in subtasks {
274 plan.add_subtask(subtask);
275 }
276
277 plan.compute_execution_order()?;
278
279 Ok(plan)
280 }
281
282 fn analyze_complexity(&self, task: &str) -> u32 {
284 let mut complexity = 1u32;
285
286 let task_lower = task.to_lowercase();
288
289 if task_lower.contains("implement") || task_lower.contains("create") {
291 complexity += 2;
292 }
293 if task_lower.contains("refactor") || task_lower.contains("rewrite") {
294 complexity += 2;
295 }
296 if task_lower.contains("integrate") || task_lower.contains("connect") {
297 complexity += 1;
298 }
299 if task_lower.contains("test") || task_lower.contains("verify") {
300 complexity += 1;
301 }
302 if task_lower.contains("and") || task_lower.contains("then") {
303 complexity += 1;
304 }
305 if task_lower.contains("multiple") || task_lower.contains("several") {
306 complexity += 1;
307 }
308
309 let word_count = task.split_whitespace().count();
311 if word_count > 20 {
312 complexity += 1;
313 }
314 if word_count > 50 {
315 complexity += 1;
316 }
317
318 complexity.min(10)
319 }
320
321 fn estimate_risk(&self, task: &str, complexity: u32) -> RiskLevel {
323 let task_lower = task.to_lowercase();
324
325 if task_lower.contains("delete")
327 || task_lower.contains("remove")
328 || task_lower.contains("drop")
329 {
330 return RiskLevel::Critical;
331 }
332 if task_lower.contains("production")
333 || task_lower.contains("live")
334 || task_lower.contains("deploy")
335 {
336 return RiskLevel::High;
337 }
338 if task_lower.contains("database") || task_lower.contains("migration") {
339 return RiskLevel::High;
340 }
341
342 match complexity {
344 1..=3 => RiskLevel::Low,
345 4..=6 => RiskLevel::Medium,
346 7..=8 => RiskLevel::High,
347 _ => RiskLevel::Critical,
348 }
349 }
350
351 fn decompose_sequential(&self, task: &str) -> Vec<SubTask> {
353 let steps = self.extract_steps(task);
354 let mut subtasks = Vec::new();
355 let mut prev_id: Option<String> = None;
356
357 for (i, step) in steps.into_iter().enumerate() {
358 let id = format!("step_{}", i + 1);
359 let mut subtask = SubTask::new(&id, format!("Step {}", i + 1), step);
360 subtask.priority = i as u32;
361
362 if let Some(prev) = prev_id {
363 subtask = subtask.with_dependency(prev);
364 }
365
366 prev_id = Some(id);
367 subtasks.push(subtask);
368 }
369
370 if subtasks.is_empty() {
371 subtasks.push(SubTask::new("step_1", "Execute task", task));
372 }
373
374 subtasks
375 }
376
377 fn decompose_parallel(&self, task: &str) -> Vec<SubTask> {
379 let parts = self.extract_parallel_parts(task);
380 let mut subtasks = Vec::new();
381
382 for (i, part) in parts.into_iter().enumerate() {
383 let id = format!("parallel_{}", i + 1);
384 let subtask = SubTask::new(&id, format!("Task {}", i + 1), part);
385 subtasks.push(subtask);
386 }
387
388 if subtasks.is_empty() {
389 subtasks.push(SubTask::new("parallel_1", "Execute task", task));
390 }
391
392 subtasks
393 }
394
395 fn decompose_hierarchical(&self, task: &str, depth: u32) -> Vec<SubTask> {
397 if depth >= self.max_depth {
398 return vec![SubTask::new(format!("leaf_{}", depth), "Execute", task)];
399 }
400
401 let main_steps = self.extract_steps(task);
402 let mut subtasks = Vec::new();
403
404 for (i, step) in main_steps.into_iter().enumerate() {
405 let id = format!("h{}_{}", depth, i + 1);
406 let mut subtask = SubTask::new(&id, format!("Phase {}", i + 1), step.clone());
407 subtask.estimated_complexity = self.analyze_complexity(&step);
408
409 if subtask.estimated_complexity > 5 && depth < self.max_depth - 1 {
411 let sub_subtasks = self.decompose_hierarchical(&step, depth + 1);
412 for (j, sub_sub) in sub_subtasks.into_iter().enumerate() {
413 let mut sub_sub_id = sub_sub;
414 sub_sub_id.id = format!("{}_{}", id, j + 1);
415 sub_sub_id.dependencies.push(id.clone());
416 subtasks.push(sub_sub_id);
417 }
418 }
419
420 subtasks.push(subtask);
421 }
422
423 subtasks
424 }
425
426 fn decompose_hybrid(&self, task: &str) -> Vec<SubTask> {
428 let complexity = self.analyze_complexity(task);
429
430 if complexity <= 3 {
431 vec![SubTask::new("execute", "Execute task", task)]
433 } else if complexity <= 6 {
434 self.decompose_sequential(task)
436 } else {
437 self.decompose_hierarchical(task, 0)
439 }
440 }
441
442 fn extract_steps(&self, task: &str) -> Vec<String> {
444 let mut steps = Vec::new();
445
446 let sentences: Vec<&str> = task
448 .split(&['.', ';', '\n'][..])
449 .map(|s| s.trim())
450 .filter(|s| !s.is_empty())
451 .collect();
452
453 if sentences.len() > 1 {
454 steps = sentences.into_iter().map(|s| s.to_string()).collect();
455 } else {
456 let mut then_parts: Vec<&str> = task.split("and then").collect();
458 if then_parts.len() == 1 {
459 then_parts = task.split("then").collect();
460 }
461 if then_parts.len() == 1 {
462 then_parts = task.split("after that").collect();
463 }
464
465 if then_parts.len() > 1 {
466 steps = then_parts
467 .into_iter()
468 .map(|s| s.trim().to_string())
469 .collect();
470 } else {
471 steps.push(task.to_string());
473 }
474 }
475
476 steps
477 }
478
479 fn extract_parallel_parts(&self, task: &str) -> Vec<String> {
481 let mut parts: Vec<&str> = task.split(", and ").collect();
483 if parts.len() == 1 {
484 parts = task.split(" and ").collect();
485 }
486 if parts.len() == 1 {
487 parts = task.split(", ").collect();
488 }
489
490 let parts: Vec<&str> = parts
491 .into_iter()
492 .map(|s| s.trim())
493 .filter(|s| !s.is_empty() && s.len() > 3)
494 .collect();
495
496 if parts.len() > 1 {
497 parts.into_iter().map(|s| s.to_string()).collect()
498 } else {
499 vec![task.to_string()]
500 }
501 }
502}
503
504#[derive(Debug, Clone)]
506pub struct PlanResult {
507 pub plan: ExecutionPlan,
509 pub quality_score: u32,
511 pub suggestions: Vec<String>,
513}
514
515impl PlanResult {
516 pub fn new(plan: ExecutionPlan) -> Self {
518 let quality_score = Self::calculate_quality(&plan);
519 let suggestions = Self::generate_suggestions(&plan);
520
521 Self {
522 plan,
523 quality_score,
524 suggestions,
525 }
526 }
527
528 fn calculate_quality(plan: &ExecutionPlan) -> u32 {
530 let mut score = 100u32;
531
532 if plan.subtasks.is_empty() {
534 score = 0;
535 } else if plan.subtasks.len() == 1 {
536 score -= 20; }
538
539 if plan.execution_order.len() != plan.subtasks.len() {
541 score -= 30;
542 }
543
544 let has_validation = plan
546 .subtasks
547 .iter()
548 .any(|s| !s.validation_criteria.is_empty());
549 if !has_validation {
550 score -= 10;
551 }
552
553 let has_fallback = plan.subtasks.iter().any(|s| s.fallback.is_some());
555 if !has_fallback && plan.risk_level >= RiskLevel::High {
556 score -= 15;
557 }
558
559 score
560 }
561
562 fn generate_suggestions(plan: &ExecutionPlan) -> Vec<String> {
564 let mut suggestions = Vec::new();
565
566 if plan.subtasks.len() == 1 {
567 suggestions.push("Consider breaking down the task into smaller subtasks".to_string());
568 }
569
570 if plan.risk_level >= RiskLevel::High {
571 suggestions.push("High-risk task: consider adding validation steps".to_string());
572 }
573
574 let has_fallback = plan.subtasks.iter().any(|s| s.fallback.is_some());
575 if !has_fallback && !plan.subtasks.is_empty() {
576 suggestions
577 .push("Consider adding fallback strategies for critical subtasks".to_string());
578 }
579
580 suggestions
581 }
582}
583
584#[cfg(test)]
585mod tests {
586 use super::{
587 DecompositionStrategy, ExecutionPlan, PlanResult, RiskLevel, SubTask, TaskDecomposer,
588 };
589
590 #[test]
591 fn test_subtask_creation() {
592 let subtask = SubTask::new("test_1", "Test", "Test subtask");
593 assert_eq!(subtask.id, "test_1");
594 assert_eq!(subtask.name, "Test");
595 }
596
597 #[test]
598 fn test_subtask_with_dependencies() {
599 let subtask = SubTask::new("test_2", "Test", "Test").with_dependency("test_1");
600 assert_eq!(subtask.dependencies.len(), 1);
601 }
602
603 #[test]
604 fn test_execution_plan_creation() {
605 let plan = ExecutionPlan::new("Test task");
606 assert!(!plan.original_task.is_empty());
607 assert!(plan.subtasks.is_empty());
608 }
609
610 #[test]
611 fn test_task_decomposer() {
612 let decomposer = TaskDecomposer::new();
613 let plan = decomposer
614 .decompose("Create a file and write some content")
615 .unwrap();
616
617 assert!(!plan.subtasks.is_empty());
618 assert!(!plan.execution_order.is_empty());
619 }
620
621 #[test]
622 fn test_complexity_analysis() {
623 let decomposer = TaskDecomposer::new();
624
625 let simple = decomposer.analyze_complexity("Read a file");
626 assert!(simple <= 3);
627
628 let complex = decomposer.analyze_complexity(
629 "Implement a complete authentication system with OAuth2 integration",
630 );
631 assert!(complex > 3);
632 }
633
634 #[test]
635 fn test_risk_estimation() {
636 let decomposer = TaskDecomposer::new();
637
638 let low = decomposer.estimate_risk("Read a file", 2);
639 assert_eq!(low, RiskLevel::Low);
640
641 let critical = decomposer.estimate_risk("Delete the production database", 5);
642 assert_eq!(critical, RiskLevel::Critical);
643 }
644
645 #[test]
646 fn test_sequential_decomposition() {
647 let decomposer = TaskDecomposer::new().with_strategy(DecompositionStrategy::Sequential);
648
649 let plan = decomposer
650 .decompose("First step. Second step. Third step.")
651 .unwrap();
652
653 assert!(plan.subtasks.len() >= 3);
654 for i in 1..plan.subtasks.len() {
656 assert!(plan.subtasks[i]
657 .dependencies
658 .contains(&plan.subtasks[i - 1].id));
659 }
660 }
661
662 #[test]
663 fn test_parallel_decomposition() {
664 let decomposer = TaskDecomposer::new().with_strategy(DecompositionStrategy::Parallel);
665
666 let plan = decomposer
667 .decompose("Task A and Task B and Task C")
668 .unwrap();
669
670 assert!(plan.subtasks.len() >= 2);
671 let has_deps: bool = plan.subtasks.iter().any(|s| !s.dependencies.is_empty());
673 assert!(!has_deps);
674 }
675
676 #[test]
677 fn test_plan_result_quality() {
678 let mut plan = ExecutionPlan::new("Test task");
679 plan.add_subtask(SubTask::new("s1", "Step 1", "First step"));
680 plan.add_subtask(SubTask::new("s2", "Step 2", "Second step").with_dependency("s1"));
681 plan.compute_execution_order().unwrap();
682
683 let result = PlanResult::new(plan);
684 assert!(result.quality_score > 0);
685 }
686
687 #[test]
688 fn test_dag_conversion() {
689 let mut plan = ExecutionPlan::new("Test task");
690 plan.add_subtask(SubTask::new("s1", "Step 1", "First step"));
691 plan.add_subtask(SubTask::new("s2", "Step 2", "Second step").with_dependency("s1"));
692 plan.compute_execution_order().unwrap();
693
694 let dag_result = plan.to_dag();
695 assert!(dag_result.is_ok());
696 }
697}