Execute an execution plan using wave-based dependency-aware scheduling. Steps with no unmet dependencies are grouped into "waves". A wave with a single step executes sequentially (preserving the history chain). A wave with multiple independent steps executes them in parallel via `JoinSet`, then merges their results back into the shared history.
(
&self,
history: &[Message],
plan: &ExecutionPlan,
session_id: Option<&str>,
event_tx: Option<mpsc::Sender<AgentEvent>>,
)
| 202 | /// wave with multiple independent steps executes them in parallel via |
| 203 | /// `JoinSet`, then merges their results back into the shared history. |
| 204 | pub(super) async fn execute_plan( |
| 205 | &self, |
| 206 | history: &[Message], |
| 207 | plan: &ExecutionPlan, |
| 208 | session_id: Option<&str>, |
| 209 | event_tx: Option<mpsc::Sender<AgentEvent>>, |
| 210 | ) -> Result<AgentResult> { |
| 211 | let mut plan = plan.clone(); |
| 212 | let task_session_id = session_id.unwrap_or("").to_string(); |
| 213 | let mut current_history = history.to_vec(); |
| 214 | let mut total_usage = TokenUsage::default(); |
| 215 | let mut tool_calls_count = 0; |
| 216 | let total_steps = plan.steps.len(); |
| 217 | |
| 218 | // Add initial user message with the goal |
| 219 | let steps_text = plan |
| 220 | .steps |
| 221 | .iter() |
| 222 | .enumerate() |
| 223 | .map(|(i, step)| format!("{}. {}", i + 1, step.content)) |
| 224 | .collect::<Vec<_>>() |
| 225 | .join("\n"); |
| 226 | current_history.push(Message::user(&crate::prompts::render( |
| 227 | crate::prompts::PLAN_EXECUTE_GOAL, |
| 228 | &[("goal", &plan.goal), ("steps", &steps_text)], |
| 229 | ))); |
| 230 | self.emit_task_updated(&event_tx, &task_session_id, &plan) |
| 231 | .await; |
| 232 | |
| 233 | loop { |
| 234 | let ready: Vec<String> = plan |
| 235 | .get_ready_steps() |
| 236 | .iter() |
| 237 | .map(|s| s.id.clone()) |
| 238 | .collect(); |
| 239 | |
| 240 | if ready.is_empty() { |
| 241 | // All done or deadlock |
| 242 | if plan.has_deadlock() { |
| 243 | tracing::warn!( |
| 244 | "Plan deadlock detected: {} pending steps with unresolvable dependencies", |
| 245 | plan.pending_count() |
| 246 | ); |
| 247 | } |
| 248 | break; |
| 249 | } |
| 250 | |
| 251 | if ready.len() == 1 { |
| 252 | // === Single step: sequential execution (preserves history chain) === |
| 253 | let step_id = &ready[0]; |
| 254 | let step = plan |
| 255 | .steps |
| 256 | .iter() |
| 257 | .find(|s| s.id == *step_id) |
| 258 | .ok_or_else(|| anyhow::anyhow!("step '{}' not found in plan", step_id))? |
| 259 | .clone(); |
| 260 | let step_number = plan |
| 261 | .steps |