use super::super::App;
use tokio::sync::mpsc;
use crate::agent::r#loop::AgentEvent;
impl App {
pub(crate) fn start_evolution_loop(
&mut self,
event_tx: mpsc::UnboundedSender<AgentEvent>,
extra_args: &[String],
) {
use crate::evolution::{
adapter::ColletEvolvable,
benchmarks::{FileBenchmark, NullBenchmark, SingleTaskBenchmark, SweBenchAdapter},
config::EvolveConfig,
engines::SkillforgeEngine,
r#loop::EvolutionLoop,
trial::{BenchmarkAdapter, TrialRunner},
};
use std::path::PathBuf;
use tokio::sync::mpsc as tpsc;
use tokio_util::sync::CancellationToken;
let mut cycles: Option<u32> = None;
let mut benchmark_name: Option<String> = None;
let mut tasks_path: Option<PathBuf> = None;
let mut batch = 5usize;
let mut docker = false;
let mut score_cmd: Option<String> = None;
let mut task_words: Vec<String> = Vec::new();
let mut i = 0;
while i < extra_args.len() {
match extra_args[i].as_str() {
"--cycles" => {
i += 1;
cycles = extra_args.get(i).and_then(|s| s.parse().ok());
}
"--benchmark" => {
i += 1;
benchmark_name = extra_args.get(i).cloned();
}
"--tasks" => {
i += 1;
tasks_path = extra_args.get(i).map(PathBuf::from);
}
"--batch" => {
i += 1;
batch = extra_args.get(i).and_then(|s| s.parse().ok()).unwrap_or(5);
}
"--docker" => docker = true,
"--score-cmd" => {
i += 1;
score_cmd = extra_args.get(i).cloned();
}
other if other.starts_with("--") => {} word => task_words.push(word.to_string()), }
i += 1;
}
let task_text: Option<String> = if task_words.is_empty() {
None
} else {
Some(task_words.join(" "))
};
let workspace_root = PathBuf::from(&self.working_dir)
.join(".collet")
.join("workspace");
let eval_dir = PathBuf::from(&self.working_dir)
.join(".collet")
.join("eval");
let _ = std::fs::create_dir_all(&workspace_root);
let (benchmark, benchmark_label, cycles_default): (Box<dyn BenchmarkAdapter>, String, u32) =
if let Some(ref text) = task_text {
(
Box::new(SingleTaskBenchmark::from_text(text.clone())),
"inline".to_string(),
1,
)
} else {
match benchmark_name.as_deref().unwrap_or("null") {
"file" => {
let path = tasks_path
.clone()
.unwrap_or_else(|| PathBuf::from("tasks.jsonl"));
let mut fb = FileBenchmark::new(path);
if let Some(cmd) = score_cmd {
fb = fb.with_score_cmd(cmd);
}
(Box::new(fb), "file".to_string(), 10)
}
"swebench" | "swe-bench" => {
let path = tasks_path
.clone()
.unwrap_or_else(|| PathBuf::from("swe-bench.jsonl"));
let _ = std::fs::create_dir_all(&eval_dir);
(
Box::new(SweBenchAdapter::new(path, eval_dir).with_docker(docker)),
"swebench".to_string(),
10,
)
}
_ => (Box::new(NullBenchmark), "null".to_string(), 10),
}
};
let max_cycles = cycles.unwrap_or(cycles_default);
let evolve_config = EvolveConfig {
max_cycles,
batch_size: batch,
evolver_model: self.config.model.clone(),
..Default::default()
};
let evolvable = ColletEvolvable::new(
self.client.clone(),
self.config.clone(),
workspace_root.clone(),
self.working_dir.clone(),
);
let trial = TrialRunner::new(Box::new(evolvable), benchmark);
let (evo_tx, mut evo_rx) = tpsc::unbounded_channel::<crate::evolution::EvolutionEvent>();
let agent_tx_clone = event_tx.clone();
tokio::spawn(async move {
while let Some(evt) = evo_rx.recv().await {
let _ = agent_tx_clone.send(AgentEvent::Evolution(evt));
}
});
let client = self.client.clone();
let cancel = CancellationToken::new();
if let Some(ref old_cancel) = self.cancel_token {
old_cancel.cancel();
}
self.cancel_token = Some(cancel.clone());
self.state.agent_busy = true;
self.agent_busy_since = Some(std::time::Instant::now());
let start_msg = if let Some(ref text) = task_text {
format!("🧬 Evolving: \"{text}\" ({max_cycles} cycle(s))...")
} else {
format!(
"🧬 Starting evolution loop ({max_cycles} cycles, benchmark: {benchmark_label})..."
)
};
self.state
.messages
.push(crate::tui::state::ChatMessage::text(
crate::tui::state::MessageRole::System,
start_msg,
));
tokio::spawn(async move {
let Ok(mut evo_loop) =
EvolutionLoop::new(workspace_root, trial, evolve_config.clone(), evo_tx)
else {
return;
};
let mut engine = SkillforgeEngine::new(evolve_config, client);
let _ = evo_loop.run(&mut engine, cancel).await;
});
}
}