Skip to content

Commit 3ac1023

Browse files
committed
feat: extract full agent project instead of concatenating files
- Add agent_archive field to ExtractedArchive (raw ZIP bytes) - run_agent extracts full archive into repo/_agent/agent_code/ - Installs requirements.txt if present - Runs agent.py with --instruction from agent_code/ directory - Falls back to legacy single-file mode when no archive available - Fixes baseagent execution (was SyntaxError from concatenated non-Python files)
1 parent 16b2e00 commit 3ac1023

File tree

3 files changed

+90
-13
lines changed

3 files changed

+90
-13
lines changed

src/executor.rs

Lines changed: 86 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ async fn run_batch(
183183
let total_tasks = archive.tasks.len();
184184
let agent_code = Arc::new(archive.agent_code);
185185
let agent_language = Arc::new(archive.agent_language);
186+
let agent_archive = Arc::new(archive.agent_archive);
186187
let agent_env = Arc::new(agent_env);
187188

188189
{
@@ -214,6 +215,7 @@ async fn run_batch(
214215
let events_tx = batch.events_tx.clone();
215216
let agent_code = agent_code.clone();
216217
let agent_language = agent_language.clone();
218+
let agent_archive = agent_archive.clone();
217219
let agent_env = agent_env.clone();
218220
let semaphore = semaphore.clone();
219221
let task_results = task_results.clone();
@@ -245,6 +247,7 @@ async fn run_batch(
245247
&task,
246248
&agent_code,
247249
&agent_language,
250+
agent_archive.as_deref(),
248251
&agent_env,
249252
cancel_rx,
250253
)
@@ -303,6 +306,7 @@ async fn run_single_task(
303306
task: &SweForgeTask,
304307
agent_code: &str,
305308
agent_language: &str,
309+
agent_archive: Option<&[u8]>,
306310
agent_env: &HashMap<String, String>,
307311
cancel_rx: tokio::sync::watch::Receiver<bool>,
308312
) -> TaskResult {
@@ -321,6 +325,7 @@ async fn run_single_task(
321325
task,
322326
agent_code,
323327
agent_language,
328+
agent_archive,
324329
agent_env,
325330
&work_dir,
326331
&cancel_rx,
@@ -350,6 +355,7 @@ async fn run_task_pipeline(
350355
task: &SweForgeTask,
351356
agent_code: &str,
352357
agent_language: &str,
358+
agent_archive: Option<&[u8]>,
353359
agent_env: &HashMap<String, String>,
354360
work_dir: &Path,
355361
cancel_rx: &tokio::sync::watch::Receiver<bool>,
@@ -411,6 +417,7 @@ async fn run_task_pipeline(
411417
let agent_output = run_agent(
412418
agent_code,
413419
agent_language,
420+
agent_archive,
414421
&task.prompt,
415422
&repo_dir,
416423
config.agent_timeout_secs,
@@ -548,29 +555,95 @@ fn agent_runner(language: &str, script_path: &str) -> Vec<String> {
548555
async fn run_agent(
549556
agent_code: &str,
550557
agent_language: &str,
558+
agent_archive: Option<&[u8]>,
551559
prompt: &str,
552560
repo_dir: &Path,
553561
timeout_secs: u64,
554562
agent_env: &HashMap<String, String>,
555563
) -> Result<String> {
556-
let ext = agent_extension(agent_language);
557-
let script_name = format!("_agent_code{}", ext);
558-
let script_path = repo_dir.join(&script_name);
559-
tokio::fs::write(&script_path, agent_code).await?;
560-
561564
let prompt_path = repo_dir.join("_task_prompt.md");
562565
tokio::fs::write(&prompt_path, prompt).await?;
563566

564-
let mut argv_owned = agent_runner(agent_language, &script_name);
565-
// Pass --instruction for Python agents (baseagent convention)
566-
if matches!(agent_language.to_lowercase().as_str(), "python" | "py") {
567-
argv_owned.push("--instruction".into());
568-
argv_owned.push(prompt.into());
569-
}
567+
// If we have the full archive, extract it into the repo so the agent project
568+
// structure (agent_code/agent.py, requirements.txt, src/, etc.) is preserved.
569+
let (argv_owned, run_dir) = if let Some(archive_bytes) = agent_archive {
570+
let agent_base = repo_dir.join("_agent");
571+
let _ = tokio::fs::create_dir_all(&agent_base).await;
572+
let base = agent_base.clone();
573+
let data = archive_bytes.to_vec();
574+
tokio::task::spawn_blocking(move || crate::task::extract_archive_bytes(&data, &base))
575+
.await
576+
.context("extract agent archive")??;
577+
578+
// Find agent_code/ dir inside extracted archive
579+
let agent_dir = if agent_base.join("agent_code").exists() {
580+
agent_base.join("agent_code")
581+
} else {
582+
// Look one level deeper
583+
let mut found = agent_base.clone();
584+
if let Ok(mut entries) = tokio::fs::read_dir(&agent_base).await {
585+
while let Ok(Some(entry)) = entries.next_entry().await {
586+
if entry.path().join("agent_code").exists() {
587+
found = entry.path().join("agent_code");
588+
break;
589+
}
590+
}
591+
}
592+
found
593+
};
594+
595+
// Install Python dependencies if requirements.txt exists
596+
if agent_dir.join("requirements.txt").exists() {
597+
info!("Installing agent requirements.txt");
598+
let (_, stderr, exit) = run_shell(
599+
"pip install -q -r requirements.txt 2>&1 || pip3 install -q -r requirements.txt 2>&1 || true",
600+
&agent_dir,
601+
Duration::from_secs(120),
602+
None,
603+
)
604+
.await?;
605+
if exit != 0 {
606+
warn!(
607+
"Agent pip install failed (exit {}): {}",
608+
exit,
609+
&stderr[..stderr.len().min(500)]
610+
);
611+
}
612+
}
613+
614+
// Determine entry point
615+
let entry = if agent_dir.join("agent.py").exists() {
616+
"agent.py"
617+
} else if agent_dir.join("main.py").exists() {
618+
"main.py"
619+
} else {
620+
"agent.py"
621+
};
622+
623+
let mut argv = vec!["python3".to_string(), entry.to_string()];
624+
argv.push("--instruction".into());
625+
argv.push(prompt.into());
626+
(argv, agent_dir)
627+
} else {
628+
// Legacy path: single-file agent code written to _agent_code.py
629+
let ext = agent_extension(agent_language);
630+
let script_name = format!("_agent_code{}", ext);
631+
let script_path = repo_dir.join(&script_name);
632+
tokio::fs::write(&script_path, agent_code).await?;
633+
634+
let mut argv = agent_runner(agent_language, &script_name);
635+
if matches!(agent_language.to_lowercase().as_str(), "python" | "py") {
636+
argv.push("--instruction".into());
637+
argv.push(prompt.into());
638+
}
639+
(argv, repo_dir.to_path_buf())
640+
};
641+
570642
let argv: Vec<&str> = argv_owned.iter().map(|s| s.as_str()).collect();
571643
info!(
572-
"Running agent: {:?} with {} env vars",
644+
"Running agent: {:?} in {} with {} env vars",
573645
argv,
646+
run_dir.display(),
574647
agent_env.len()
575648
);
576649

@@ -591,7 +664,7 @@ async fn run_agent(
591664

592665
let (stdout, stderr, exit) = run_cmd(
593666
&argv,
594-
repo_dir,
667+
&run_dir,
595668
Duration::from_secs(timeout_secs),
596669
Some(&env_refs),
597670
)

src/handlers.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1218,6 +1218,7 @@ async fn submit_tasks(
12181218
tasks: hf_tasks,
12191219
agent_code: extracted.agent_code,
12201220
agent_language: extracted.agent_language,
1221+
agent_archive: extracted.agent_archive,
12211222
};
12221223

12231224
if state.sessions.has_active_batch() {
@@ -1392,6 +1393,7 @@ async fn evaluate_with_stored_agent(
13921393
tasks: hf_tasks,
13931394
agent_code,
13941395
agent_language,
1396+
agent_archive: Some(archive_bytes),
13951397
};
13961398

13971399
if state.sessions.has_active_batch() {

src/task/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ pub struct ExtractedArchive {
5858
pub tasks: Vec<SweForgeTask>,
5959
pub agent_code: String,
6060
pub agent_language: String,
61+
pub agent_archive: Option<Vec<u8>>,
6162
}
6263

6364
pub fn extract_archive_bytes(data: &[u8], dest: &Path) -> Result<()> {
@@ -141,6 +142,7 @@ pub async fn extract_uploaded_archive(data: &[u8], dest: &Path) -> Result<Extrac
141142
tasks,
142143
agent_code,
143144
agent_language,
145+
agent_archive: None,
144146
})
145147
}
146148

0 commit comments

Comments
 (0)