Run rounds until the predicate says [`Stop`](LoopDecision::Stop), a round is asked to run no specs, or `max_iterations` is reached — whichever comes first. Each round is a barrier ([`execute_steps_parallel`]); `next` receives the just-completed round's outcomes and decides whether (and with what) to continue. Returns the last round's outcomes (empty if `initial` was empty). `max_iterations` is **
(
executor: Arc<dyn AgentExecutor>,
initial: Vec<AgentStepSpec>,
max_iterations: usize,
event_tx: Option<broadcast::Sender<AgentEvent>>,
mut next: F,
)
| 238 | /// combinators it is written purely against the [`AgentExecutor`] seam and adds |
| 239 | /// no scheduling of its own. |
| 240 | pub async fn execute_loop<F>( |
| 241 | executor: Arc<dyn AgentExecutor>, |
| 242 | initial: Vec<AgentStepSpec>, |
| 243 | max_iterations: usize, |
| 244 | event_tx: Option<broadcast::Sender<AgentEvent>>, |
| 245 | mut next: F, |
| 246 | ) -> Vec<StepOutcome> |
| 247 | where |
| 248 | F: FnMut(&[StepOutcome]) -> LoopDecision + Send, |
| 249 | { |
| 250 | let cap = max_iterations.max(1); |
| 251 | let mut specs = initial; |
| 252 | let mut last = Vec::new(); |
| 253 | let mut iterations = 0; |
| 254 | |
| 255 | while !specs.is_empty() { |
| 256 | let round = execute_steps_parallel( |
| 257 | Arc::clone(&executor), |
| 258 | std::mem::take(&mut specs), |
| 259 | event_tx.clone(), |
| 260 | ) |
| 261 | .await; |
| 262 | iterations += 1; |
| 263 | let decision = next(&round); |
| 264 | last = round; |
| 265 | match decision { |
| 266 | LoopDecision::Continue(more) if iterations < cap => specs = more, |
| 267 | _ => break, |
| 268 | } |
| 269 | } |
| 270 | |
| 271 | last |
| 272 | } |
| 273 | |
| 274 | #[cfg(test)] |
| 275 | mod tests { |