prodigy/cook/execution/mapreduce/pure/
parallel_execution.rs1use crate::cook::execution::errors::MapReduceError;
13use crate::cook::execution::mapreduce::effects::commands::{
14 execute_commands_effect, CommandResult,
15};
16use crate::cook::execution::mapreduce::effects::merge::{merge_to_parent_effect, MergeResult};
17use crate::cook::execution::mapreduce::effects::worktree::{create_worktree_effect, Worktree};
18use crate::cook::execution::mapreduce::environment::MapEnv;
19use serde_json::Value;
20use stillwater::{from_async, par_all_limit, BoxedEffect, Effect, EffectExt};
21
22#[derive(Debug, Clone)]
24pub struct AgentExecutionResult {
25 pub worktree: Worktree,
26 pub command_result: CommandResult,
27 pub merge_result: MergeResult,
28}
29
30pub fn execute_agent_effect(
45 item: &Value,
46 agent_name: &str,
47 parent_branch: &str,
48) -> BoxedEffect<AgentExecutionResult, MapReduceError, MapEnv> {
49 let item = item.clone();
50 let agent_name = agent_name.to_string();
51 let parent_branch = parent_branch.to_string();
52
53 create_worktree_effect(&agent_name, &parent_branch)
55 .and_then(move |worktree| {
56 let item = item.clone();
57 let parent_branch = parent_branch.clone();
58 let worktree_clone = worktree.clone();
59
60 execute_commands_effect(&item, &worktree).and_then(move |command_result| {
61 let worktree = worktree_clone;
62 let worktree_clone2 = worktree.clone();
63
64 merge_to_parent_effect(&worktree, &parent_branch).map(move |merge_result| {
65 AgentExecutionResult {
66 worktree: worktree_clone2,
67 command_result,
68 merge_result,
69 }
70 })
71 })
72 })
73 .boxed()
74}
75
76pub fn execute_agents_parallel(
98 items: &[Value],
99 parent_branch: &str,
100 max_parallel: usize,
101) -> impl Effect<Output = Vec<AgentExecutionResult>, Error = Vec<MapReduceError>, Env = MapEnv> {
102 let parent_branch = parent_branch.to_string();
103 let items: Vec<Value> = items.to_vec();
104
105 from_async(move |env: &MapEnv| {
107 let parent_branch = parent_branch.clone();
108 let items = items.clone();
109 let env = env.clone(); async move {
112 let agent_effects: Vec<BoxedEffect<AgentExecutionResult, MapReduceError, MapEnv>> =
114 items
115 .iter()
116 .enumerate()
117 .map(|(index, item)| {
118 let agent_name = format!("agent-{}", index);
119 execute_agent_effect(item, &agent_name, &parent_branch)
120 })
121 .collect();
122
123 par_all_limit(agent_effects, max_parallel, &env).await
126 }
127 })
128}
129
130pub fn plan_parallel_batches(item_count: usize, max_parallel: usize) -> Vec<Vec<usize>> {
135 let mut batches = Vec::new();
136 let mut current_batch = Vec::new();
137
138 for i in 0..item_count {
139 current_batch.push(i);
140 if current_batch.len() >= max_parallel {
141 batches.push(current_batch.clone());
142 current_batch.clear();
143 }
144 }
145
146 if !current_batch.is_empty() {
147 batches.push(current_batch);
148 }
149
150 batches
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn test_plan_parallel_batches() {
159 let batches = plan_parallel_batches(6, 2);
161 assert_eq!(batches.len(), 3);
162 assert_eq!(batches[0], vec![0, 1]);
163 assert_eq!(batches[1], vec![2, 3]);
164 assert_eq!(batches[2], vec![4, 5]);
165
166 let batches = plan_parallel_batches(5, 2);
168 assert_eq!(batches.len(), 3);
169 assert_eq!(batches[0], vec![0, 1]);
170 assert_eq!(batches[1], vec![2, 3]);
171 assert_eq!(batches[2], vec![4]);
172
173 let batches = plan_parallel_batches(3, 10);
175 assert_eq!(batches.len(), 1);
176 assert_eq!(batches[0], vec![0, 1, 2]);
177 }
178
179 #[test]
180 fn test_plan_parallel_batches_edge_cases() {
181 let batches = plan_parallel_batches(0, 2);
183 assert_eq!(batches.len(), 0);
184
185 let batches = plan_parallel_batches(1, 2);
187 assert_eq!(batches.len(), 1);
188 assert_eq!(batches[0], vec![0]);
189
190 let batches = plan_parallel_batches(5, 1);
192 assert_eq!(batches.len(), 5);
193 for (i, batch) in batches.iter().enumerate() {
194 assert_eq!(batch, &vec![i]);
195 }
196 }
197}