Split ~/.arc/logs/ into logs/ and runs/

Per-run data now lives in ~/.arc/runs/ while daily CLI log files
stay in ~/.arc/logs/. Renames: logs_root → run_dir (RunConfig field,
Handler trait param, all handlers), logs_dir → run_dir (CLI arg,
local variables), default_logs_base → default_runs_base (path fn).
Adds DB migration 002 to rename workflow_runs.logs_dir → run_dir.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-03-10 09:40:03 -04:00
parent 54ecae9586
commit 1dcad0529a
39 changed files with 541 additions and 539 deletions

View file

@ -7,7 +7,7 @@ When an agent or prompt node finishes, Arc captures its response text and produc
## Response capture
After an agent or prompt node completes, Arc captures the full response text and writes it to the run logs at `{logs_root}/nodes/{node_id}/response.md`. It also writes the final outcome (status, context updates, routing directives) to `{logs_root}/nodes/{node_id}/status.json`.
After an agent or prompt node completes, Arc captures the full response text and writes it to the run logs at `{run_dir}/nodes/{node_id}/response.md`. It also writes the final outcome (status, context updates, routing directives) to `{run_dir}/nodes/{node_id}/status.json`.
## Context updates
@ -92,7 +92,7 @@ review -> approve [label="Approve"]
## Output logging
Arc writes several files per stage to `{logs_root}/nodes/{node_id}/`:
Arc writes several files per stage to `{run_dir}/nodes/{node_id}/`:
| File | Contents |
|---|---|
@ -147,10 +147,10 @@ Values under 100KB remain in the context as-is.
### Artifact storage layout
Offloaded artifacts are written to the run's logs directory:
Offloaded artifacts are written to the run's directory:
```
~/.arc/logs/{run_id}/
~/.arc/runs/{run_id}/
artifacts/
values/
response.plan.json
@ -206,7 +206,7 @@ For each pointer in the context updates:
```
# Before sync (host path)
file:///home/user/.arc/logs/01JK.../artifacts/values/response.plan.json
file:///home/user/.arc/runs/01JK.../artifacts/values/response.plan.json
# After sync (sandbox path)
file:///workspace/.arc/artifacts/response.plan.json
@ -220,7 +220,7 @@ For local sandboxes, syncing is a no-op since the agent can already access the h
## Automatic asset capture
After each node executes a command, Arc automatically scans the sandbox for test artifacts — screenshots, videos, reports, and traces — and copies any new or changed files to the run's logs directory. This happens without any agent or workflow configuration.
After each node executes a command, Arc automatically scans the sandbox for test artifacts — screenshots, videos, reports, and traces — and copies any new or changed files to the run's directory. This happens without any agent or workflow configuration.
### How asset capture works
@ -252,10 +252,10 @@ Tool caches and dependency directories (`node_modules`, `.cache/ms-playwright`,
### Asset storage layout
Collected assets are written to the run's logs directory, organized by node and retry attempt:
Collected assets are written to the run's directory, organized by node and retry attempt:
```
~/.arc/logs/{run_id}/
~/.arc/runs/{run_id}/
assets/
{node_slug}/
retry_1/
@ -291,4 +291,4 @@ Outputs and artifacts appear in several observability surfaces:
| `WorkflowRunCompleted` event | `artifact_count` -- total number of offloaded artifacts across the run |
| [Retros](/execution/retros) | Per-stage `files_touched` and aggregate `files_touched` across all stages |
| [Preambles](/execution/context#preamble-construction) | File list and artifact pointer references for completed stages |
| Stage logs | `status.json` in each stage's logs directory contains the full outcome including `files_touched` |
| Stage logs | `status.json` in each stage's run directory contains the full outcome including `files_touched` |

View file

@ -292,4 +292,4 @@ Use prompt nodes for analysis, classification, and summarization tasks where too
## Prompt logging
Arc writes the assembled prompt to `{logs_root}/nodes/{node_id}/prompt.md` for every agent and prompt stage. This includes the preamble (if any) and the expanded prompt text. Use these files for debugging when an agent behaves unexpectedly.
Arc writes the assembled prompt to `{run_dir}/nodes/{node_id}/prompt.md` for every agent and prompt stage. This includes the preamble (if any) and the expanded prompt text. Use these files for debugging when an agent behaves unexpectedly.

View file

@ -69,7 +69,7 @@ The `checkpoint.json` captures everything needed to resume a run:
| `loop_failure_signatures` | Failure signature counts for loop detection |
| `restart_failure_signatures` | Failure signature counts across loop-restart edges |
The checkpoint is also saved to `checkpoint.json` in the logs directory for quick local access.
The checkpoint is also saved to `checkpoint.json` in the run directory for quick local access.
## Worktrees
@ -77,7 +77,7 @@ Arc uses Git worktrees to isolate workflow runs from your working directory. Whe
1. Arc records the current HEAD as the **base SHA**
2. Creates a new branch `arc/run/{run_id}` at that SHA
3. Adds a worktree at `{logs_dir}/worktree` on that branch
3. Adds a worktree at `{run_dir}/worktree` on that branch
4. Changes into the worktree directory for the duration of the run
This means your original working directory stays untouched while the agent makes changes in the worktree. When the run completes, Arc removes the worktree and restores your original directory.
@ -94,7 +94,7 @@ There are two ways to resume an interrupted run:
### From a checkpoint file
Resume from a `checkpoint.json` saved in the logs directory:
Resume from a `checkpoint.json` saved in the run directory:
```bash
arc run workflow.dot --resume path/to/logs/checkpoint.json
@ -125,7 +125,7 @@ This reads the checkpoint, manifest, and graph DOT from the metadata branch (`re
Here's the full sequence that runs after every node completes:
1. **Save checkpoint to disk** — Write `checkpoint.json` to the logs directory
1. **Save checkpoint to disk** — Write `checkpoint.json` to the run directory
2. **Write metadata branch** — Serialize the checkpoint and any new artifacts to the metadata branch (shadow commit)
3. **Commit to run branch** — Stage all file changes, commit with structured trailers linking to the shadow commit SHA
4. **Update checkpoint** — Re-save `checkpoint.json` with the `git_commit_sha` field set

View file

@ -9,7 +9,7 @@ Arc captures a structured event for every significant action during a workflow r
The event stream is the foundation of Arc's observability. Every workflow run emits a sequence of `WorkflowRunEvent` records that are:
- **Written to `progress.jsonl`** in the run's logs directory (one JSON object per line)
- **Written to `progress.jsonl`** in the run's directory (one JSON object per line)
- **Broadcast via SSE** to connected API clients in real time
- **Logged via `tracing`** to the daily log file at `~/.arc/logs/`
@ -114,17 +114,17 @@ Arc writes two kinds of logs:
### Run logs (`progress.jsonl`)
Every run writes its event stream to `{logs_dir}/progress.jsonl`. This is the primary data source for post-run analysis — [retros](/execution/retros) read it, and you can query it directly with standard tools:
Every run writes its event stream to `{run_dir}/progress.jsonl`. This is the primary data source for post-run analysis — [retros](/execution/retros) read it, and you can query it directly with standard tools:
```bash
# Count tool calls in a run
grep "ToolCallStarted" ~/.arc/logs/01JKXYZ.../progress.jsonl | wc -l
grep "ToolCallStarted" ~/.arc/runs/01JKXYZ.../progress.jsonl | wc -l
# Find all failures
grep -E "StageFailed|WorkflowRunFailed" ~/.arc/logs/01JKXYZ.../progress.jsonl | jq .
grep -E "StageFailed|WorkflowRunFailed" ~/.arc/runs/01JKXYZ.../progress.jsonl | jq .
# See which edges were taken
grep "EdgeSelected" ~/.arc/logs/01JKXYZ.../progress.jsonl | jq '{from: .from_node, to: .to_node}'
grep "EdgeSelected" ~/.arc/runs/01JKXYZ.../progress.jsonl | jq '{from: .from_node, to: .to_node}'
```
### Live snapshot (`live.json`)
@ -132,7 +132,7 @@ grep "EdgeSelected" ~/.arc/logs/01JKXYZ.../progress.jsonl | jq '{from: .from_nod
During execution, Arc also writes `live.json` — a pretty-printed copy of the most recent event. This is useful for quick status checks while a run is in progress:
```bash
cat ~/.arc/logs/01JKXYZ.../live.json
cat ~/.arc/runs/01JKXYZ.../live.json
```
### Application logs
@ -178,11 +178,11 @@ arc ps --label team=platform
arc ps --json
```
This scans `~/.arc/logs/` and displays each run's ID, workflow name, status, and start time. Use `--json` for machine-readable output.
This scans `~/.arc/runs/` and displays each run's ID, workflow name, status, and start time. Use `--json` for machine-readable output.
### Run artifacts
Each run's logs directory contains a standard set of files:
Each run's directory contains a standard set of files:
| File | Description |
|---|---|

View file

@ -91,7 +91,7 @@ Open items capture follow-up work identified during the run:
Retro generation happens in two phases after a run completes:
1. **Derive** — Arc extracts stage durations from `progress.jsonl` and builds a retro from the checkpoint data. This is deterministic, fast, and produces the quantitative layer. The retro is saved immediately as `retro.json` in the run's logs directory.
1. **Derive** — Arc extracts stage durations from `progress.jsonl` and builds a retro from the checkpoint data. This is deterministic, fast, and produces the quantitative layer. The retro is saved immediately as `retro.json` in the run's directory.
2. **Narrate** — An LLM agent session analyzes the run data. The agent has read access to `progress.jsonl`, `checkpoint.json`, and `manifest.json`. It uses grep and read tools to find interesting signals — failures, retries, errors, approach changes — then calls a `submit_retro` tool with its structured analysis. The narrative fields are merged into the existing retro and saved.
@ -101,7 +101,7 @@ Both phases run automatically at the end of every CLI run. The API server derive
### CLI
Retros are saved to `{logs_dir}/retro.json` after every run. The path is printed at the end of the run output:
Retros are saved to `{run_dir}/retro.json` after every run. The path is printed at the end of the run output:
```
Retro: smooth — Successfully implemented the feature
@ -129,4 +129,4 @@ Retros are also available via the REST API. See the [Retros API reference](/api-
## Storage
Retros are stored as `retro.json` in the run's logs directory alongside `checkpoint.json` and `progress.jsonl`. They are plain JSON files — easy to parse, query, or pipe into other tools.
Retros are stored as `retro.json` in the run's directory alongside `checkpoint.json` and `progress.jsonl`. They are plain JSON files — easy to parse, query, or pipe into other tools.

View file

@ -48,7 +48,7 @@ arc run --run-branch arc/run/abc123
| Argument / Flag | Description |
|---|---|
| `<WORKFLOW>` | Path to a `.dot` workflow file or `.toml` task config. Not required when using `--run-branch`. |
| `--logs-dir <DIR>` | Log and artifact directory |
| `--run-dir <DIR>` | Run output directory |
| `--dry-run` | Execute with a simulated LLM backend |
| `--preflight` | Validate run configuration without executing |
| `--auto-approve` | Auto-approve all human gates |
@ -71,7 +71,7 @@ arc run --run-branch arc/run/abc123
## `arc ps`
List workflow runs stored in `~/.arc/logs`.
List workflow runs stored in `~/.arc/runs`.
```bash
arc ps

View file

@ -1,9 +1,12 @@
---
title: "Logs Directory"
description: "Structure of Arc's local logs directory"
title: "Logs & Runs Directories"
description: "Structure of Arc's local logs and runs directories"
---
Arc writes all local run data to `~/.arc/logs/`. This directory contains daily CLI log files and a subdirectory for each workflow run.
Arc stores data in two directories under `~/.arc/`:
- **`~/.arc/runs/`** — Per-run data. Each workflow run gets its own subdirectory containing event streams, checkpoints, artifacts, and worktrees.
- **`~/.arc/logs/`** — Daily CLI log files. One `.log` file per day with aggregated tracing output.
## Daily log files
@ -20,10 +23,10 @@ The log level defaults to `info`. Set `ARC_LOG=debug` or pass `--debug` for verb
Each `arc run` invocation creates a timestamped directory:
```
~/.arc/logs/20260307-01JQXYZ123ABC456DEF789/
~/.arc/runs/20260307-01JQXYZ123ABC456DEF789/
```
The naming format is `YYYYMMDD-{run_id}`, where `run_id` is the ULID assigned to the run. You can override the location with `--logs-dir`.
The naming format is `YYYYMMDD-{run_id}`, where `run_id` is the ULID assigned to the run. You can override the location with `--run-dir`.
### Root-level files
@ -81,7 +84,7 @@ Manager nodes that run sub-workflows write a nested `child/` directory containin
## Browsing runs
Use `arc ps` to scan the logs directory and display a table of all runs with their status, workflow name, and timestamps. Pass `--json` for machine-readable output.
Use `arc ps` to scan the runs directory and display a table of all runs with their status, workflow name, and timestamps. Pass `--json` for machine-readable output.
```bash
arc ps
@ -94,6 +97,8 @@ arc ps --filter workflow=my-workflow
```
~/.arc/logs/
├── 2026-03-07.log # Daily CLI log
~/.arc/runs/
├── 20260307-01JQXYZ123ABC456DEF789/ # One directory per run
│ ├── manifest.json
│ ├── graph.dot

View file

@ -76,7 +76,7 @@ struct ManagedRun {
checkpoint: Option<Checkpoint>,
cancel_tx: Option<tokio::sync::oneshot::Sender<()>>,
cancel_token: Option<Arc<AtomicBool>>,
logs_root: Option<std::path::PathBuf>,
run_dir: Option<std::path::PathBuf>,
}
/// Per-model usage totals.
@ -491,7 +491,7 @@ async fn start_run(
checkpoint: None,
cancel_tx: None,
cancel_token: None,
logs_root: None,
run_dir: None,
},
);
}
@ -591,10 +591,10 @@ async fn execute_run(state: Arc<AppState>, run_id: String) {
}
}
let logs_root = std::env::temp_dir().join(format!("arc-{}", uuid::Uuid::new_v4()));
std::fs::create_dir_all(&logs_root).expect("failed to create logs directory");
let run_dir = std::env::temp_dir().join(format!("arc-{}", uuid::Uuid::new_v4()));
std::fs::create_dir_all(&run_dir).expect("failed to create run directory");
let config = RunConfig {
logs_root,
run_dir,
cancel_token: Some(cancel_token),
dry_run: state.dry_run,
run_id: run_id.clone(),
@ -626,7 +626,7 @@ async fn execute_run(state: Arc<AppState>, run_id: String) {
};
// Save final checkpoint
let checkpoint = Checkpoint::load(&config.logs_root.join("checkpoint.json")).ok();
let checkpoint = Checkpoint::load(&config.run_dir.join("checkpoint.json")).ok();
// Auto-derive retro and accumulate aggregate usage
if let Some(ref cp) = checkpoint {
@ -634,7 +634,7 @@ async fn execute_run(state: Arc<AppState>, run_id: String) {
Ok(_) => (false, None),
Err(e) => (true, Some(e.to_string())),
};
let stage_durations = arc_workflows::retro::extract_stage_durations(&config.logs_root);
let stage_durations = arc_workflows::retro::extract_stage_durations(&config.run_dir);
let retro = arc_workflows::retro::derive_retro(
&run_id,
"workflow",
@ -645,7 +645,7 @@ async fn execute_run(state: Arc<AppState>, run_id: String) {
0,
&stage_durations,
);
let _ = retro.save(&config.logs_root);
let _ = retro.save(&config.run_dir);
// Accumulate aggregate usage
let mut agg = state
@ -686,7 +686,7 @@ async fn execute_run(state: Arc<AppState>, run_id: String) {
}
}
managed_run.checkpoint = checkpoint;
managed_run.logs_root = Some(config.logs_root.clone());
managed_run.run_dir = Some(config.run_dir.clone());
managed_run.event_tx = None;
}
drop(runs);
@ -1388,19 +1388,19 @@ async fn get_retro(
State(state): State<Arc<AppState>>,
Path(id): Path<String>,
) -> Response {
let logs_root = {
let run_dir = {
let runs = state.runs.lock().expect("runs lock poisoned");
match runs.get(&id) {
Some(managed_run) => managed_run.logs_root.clone(),
Some(managed_run) => managed_run.run_dir.clone(),
None => return ApiError::not_found("Run not found.").into_response(),
}
};
let Some(logs_root) = logs_root else {
let Some(run_dir) = run_dir else {
return (StatusCode::OK, Json(serde_json::json!(null))).into_response();
};
match arc_workflows::retro::Retro::load(&logs_root) {
match arc_workflows::retro::Retro::load(&run_dir) {
Ok(retro) => (StatusCode::OK, Json(retro)).into_response(),
Err(_) => (StatusCode::OK, Json(serde_json::json!(null))).into_response(),
}

View file

@ -692,22 +692,22 @@ fn doctor_live_flag_accepted() {
#[test]
fn dry_run_writes_jsonl_and_live_json() {
let tmp = tempfile::tempdir().unwrap();
let logs_dir = tmp.path().join("logs");
let run_dir = tmp.path().join("run");
arc()
.args([
"run",
"--dry-run",
"--auto-approve",
"--logs-dir",
logs_dir.to_str().unwrap(),
"--run-dir",
run_dir.to_str().unwrap(),
"../../../test/simple.dot",
])
.assert()
.success();
// progress.jsonl must exist and contain valid JSON lines
let jsonl_path = logs_dir.join("progress.jsonl");
let jsonl_path = run_dir.join("progress.jsonl");
assert!(jsonl_path.exists(), "progress.jsonl should exist");
let jsonl_content = std::fs::read_to_string(&jsonl_path).unwrap();
let lines: Vec<&str> = jsonl_content.lines().collect();
@ -738,7 +738,7 @@ fn dry_run_writes_jsonl_and_live_json() {
assert!(!run_id.is_empty(), "run_id should be non-empty");
// live.json must exist and contain valid JSON matching the last JSONL line
let live_path = logs_dir.join("live.json");
let live_path = run_dir.join("live.json");
assert!(live_path.exists(), "live.json should exist");
let live_content: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(&live_path).unwrap()).unwrap();

View file

@ -0,0 +1 @@
ALTER TABLE workflow_runs RENAME COLUMN logs_dir TO run_dir;

View file

@ -73,7 +73,7 @@ mod tests {
.fetch_one(&pool)
.await
.unwrap();
assert_eq!(row.0, 1);
assert_eq!(row.0, 2);
}
#[tokio::test]
@ -86,7 +86,7 @@ mod tests {
.fetch_one(&pool)
.await
.unwrap();
assert_eq!(row.0, 1);
assert_eq!(row.0, 2);
}
#[tokio::test]
@ -98,7 +98,7 @@ mod tests {
let now_str = now.format("%Y-%m-%d %H:%M:%S").to_string();
sqlx::query(
"INSERT INTO workflow_runs (id, title, logs_dir, work_dir, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
"INSERT INTO workflow_runs (id, title, run_dir, work_dir, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
)
.bind("run-1")
.bind("My Run")
@ -118,7 +118,7 @@ mod tests {
assert_eq!(run.id, "run-1");
assert_eq!(run.title, "My Run");
assert_eq!(run.logs_dir, "/tmp/logs");
assert_eq!(run.run_dir, "/tmp/logs");
assert_eq!(run.work_dir, "/tmp/work");
}
}

View file

@ -1,9 +1,10 @@
use sqlx::SqlitePool;
use tracing::{debug, info};
const CURRENT_VERSION: i64 = 1;
const CURRENT_VERSION: i64 = 2;
const MIGRATION_001: &str = include_str!("../migrations/001_create_workflow_runs.sql");
const MIGRATION_002: &str = include_str!("../migrations/002_rename_logs_dir_to_run_dir.sql");
/// Apply all pending migrations to the database.
///
@ -26,6 +27,10 @@ pub async fn initialize_db(pool: &SqlitePool) -> Result<(), sqlx::Error> {
sqlx::query(MIGRATION_001).execute(&mut *tx).await?;
}
if from_version < 2 {
sqlx::query(MIGRATION_002).execute(&mut *tx).await?;
}
sqlx::query(&format!("PRAGMA user_version = {CURRENT_VERSION}"))
.execute(&mut *tx)
.await?;

View file

@ -4,7 +4,7 @@ use chrono::{DateTime, Utc};
pub struct WorkflowRun {
pub id: String,
pub title: String,
pub logs_dir: String,
pub run_dir: String,
pub work_dir: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,

View file

@ -79,7 +79,7 @@ registry.register("agent", Box::new(AgentHandler::new(None)));
let engine = PipelineEngine::new(registry, EventEmitter::new());
let config = RunConfig {
logs_root: "/tmp/pipeline-run".into(),
run_dir: "/tmp/pipeline-run".into(),
};
// engine.run(&graph, &config).await
@ -107,7 +107,7 @@ impl Handler for MyHandler {
node: &Node,
context: &Context,
graph: &Graph,
logs_root: &Path,
run_dir: &Path,
) -> Result<Outcome, ArcError> {
// Custom logic here
Ok(Outcome::success())

View file

@ -304,11 +304,11 @@ pub async fn collect_assets(
Ok(summary)
}
/// Collect all asset paths from manifest files under `{logs_dir}/artifacts/assets/*/retry_*/manifest.json`.
/// Collect all asset paths from manifest files under `{run_dir}/artifacts/assets/*/retry_*/manifest.json`.
///
/// Returns the full on-disk paths to the downloaded asset files.
pub fn collect_asset_paths(logs_dir: &Path) -> Vec<String> {
let assets_dir = logs_dir.join("artifacts/assets");
pub fn collect_asset_paths(run_dir: &Path) -> Vec<String> {
let assets_dir = run_dir.join("artifacts/assets");
let Ok(nodes) = std::fs::read_dir(&assets_dir) else {
return Vec::new();
};

View file

@ -4,7 +4,7 @@ use anyhow::{bail, Context, Result};
use clap::Args;
use tracing::{debug, info};
use crate::cli::runs::{default_logs_base, find_run_by_prefix};
use crate::cli::runs::{default_runs_base, find_run_by_prefix};
use crate::sandbox_record::SandboxRecord;
#[derive(Args)]
@ -172,7 +172,7 @@ async fn load_sandbox(
pub async fn cp_command(args: CpArgs) -> Result<()> {
let direction = parse_direction(&args.src, &args.dst)?;
let base = default_logs_base();
let base = default_runs_base();
match direction {
CopyDirection::Download {

View file

@ -99,9 +99,9 @@ pub struct RunArgs {
#[arg(required_unless_present = "run_branch")]
pub workflow: Option<PathBuf>,
/// Log/artifact directory
/// Run output directory
#[arg(long)]
pub logs_dir: Option<PathBuf>,
pub run_dir: Option<PathBuf>,
/// Execute with simulated LLM backend
#[arg(long)]

View file

@ -4,7 +4,7 @@ use anyhow::{bail, Context, Result};
use clap::Args;
use tracing::info;
use crate::cli::runs::{default_logs_base, find_run_by_prefix};
use crate::cli::runs::{default_runs_base, find_run_by_prefix};
use crate::conclusion::Conclusion;
use crate::manifest::Manifest;
use crate::outcome::StageStatus;
@ -22,7 +22,7 @@ pub async fn pr_create_command(
args: PrCreateArgs,
github_app: Option<arc_github::GitHubAppCredentials>,
) -> Result<()> {
let base = default_logs_base();
let base = default_runs_base();
pr_create_from(&base, args, github_app).await
}

View file

@ -641,16 +641,16 @@ impl ProgressUI {
// ── Logs dir (called externally) ────────────────────────────────────
pub fn show_logs_dir(&mut self, logs_dir: &Path) {
let path_str = super::tilde_path(logs_dir);
pub fn show_run_dir(&mut self, run_dir: &Path) {
let path_str = super::tilde_path(run_dir);
match &self.renderer {
ProgressRenderer::Tty(tty) => {
let bar = tty.multi.add(ProgressBar::new_spinner());
bar.set_style(style_static_dim());
bar.finish_with_message(format!("Logs: {path_str}"));
bar.finish_with_message(format!("Run: {path_str}"));
}
ProgressRenderer::Plain => {
eprintln!(" Logs: {path_str}");
eprintln!(" Run: {path_str}");
}
}
}

View file

@ -392,21 +392,21 @@ pub async fn run_command(
// 3. Create logs directory
let run_id = ulid::Ulid::new().to_string();
let logs_dir = args.logs_dir.unwrap_or_else(|| {
let run_dir = args.run_dir.unwrap_or_else(|| {
let base = dirs::home_dir()
.expect("could not determine home directory")
.join(".arc")
.join("logs");
.join("runs");
base.join(format!("{}-{}", Local::now().format("%Y%m%d"), run_id))
});
tokio::fs::create_dir_all(&logs_dir).await?;
arc_util::run_log::activate(&logs_dir.join("cli.log"))
tokio::fs::create_dir_all(&run_dir).await?;
arc_util::run_log::activate(&run_dir.join("cli.log"))
.context("Failed to activate per-run log")?;
tokio::fs::write(logs_dir.join("graph.dot"), &source).await?;
tokio::fs::write(logs_dir.join("run.pid"), std::process::id().to_string()).await?;
tokio::fs::write(run_dir.join("graph.dot"), &source).await?;
tokio::fs::write(run_dir.join("run.pid"), std::process::id().to_string()).await?;
if workflow_path.extension().is_some_and(|ext| ext == "toml") {
if let Ok(toml_contents) = tokio::fs::read(workflow_path).await {
tokio::fs::write(logs_dir.join("run.toml"), toml_contents).await?;
tokio::fs::write(run_dir.join("run.toml"), toml_contents).await?;
}
}
@ -418,7 +418,7 @@ pub async fn run_command(
ui.show_version();
ui.show_run_id(&run_id);
ui.show_time(&Local::now().format("%Y-%m-%d %H:%M:%S").to_string());
ui.show_logs_dir(&logs_dir);
ui.show_run_dir(&run_dir);
}
// 3. Build event emitter
@ -455,8 +455,8 @@ pub async fn run_command(
// JSONL progress log + live.json snapshot
{
let jsonl_path = logs_dir.join("progress.jsonl");
let live_path = logs_dir.join("live.json");
let jsonl_path = run_dir.join("progress.jsonl");
let live_path = run_dir.join("live.json");
let run_id = Arc::new(Mutex::new(String::new()));
let run_id_clone = Arc::clone(&run_id);
emitter.on_event(move |event| {
@ -575,7 +575,7 @@ pub async fn run_command(
let (worktree_work_dir, worktree_path, worktree_branch, worktree_base_sha) =
if should_create_worktree {
match setup_worktree(&original_cwd, &logs_dir, &run_id) {
match setup_worktree(&original_cwd, &run_dir, &run_id) {
Ok((wd, wt, branch, base)) => (Some(wd), Some(wt), Some(branch), Some(base)),
Err(e) => {
eprintln!(
@ -733,7 +733,7 @@ pub async fn run_command(
}
}
};
if let Err(e) = record.save(&logs_dir.join("sandbox.json")) {
if let Err(e) = record.save(&run_dir.join("sandbox.json")) {
tracing::warn!(error = %e, "Failed to save sandbox record");
}
}
@ -957,7 +957,7 @@ pub async fn run_command(
.unwrap_or_default();
let pr_cfg = run_cfg.as_ref().and_then(|c| c.pull_request.as_ref());
let config = RunConfig {
logs_root: logs_dir.clone(),
run_dir: run_dir.clone(),
cancel_token: None,
dry_run: dry_run_mode,
run_id: run_id.clone(),
@ -1016,7 +1016,7 @@ pub async fn run_command(
failure_reason,
final_git_commit_sha: last_git_sha.lock().unwrap().clone(),
};
let _ = conclusion.save(&logs_dir.join("conclusion.json"));
let _ = conclusion.save(&run_dir.join("conclusion.json"));
}
// Finish progress bars before printing summary
@ -1035,7 +1035,7 @@ pub async fn run_command(
&config.run_id,
&graph.name,
graph.goal(),
&logs_dir,
&run_dir,
failed,
failure_reason.as_deref(),
run_duration_ms,
@ -1058,7 +1058,7 @@ pub async fn run_command(
outcome.status,
StageStatus::Success | StageStatus::PartialSuccess
) {
let diff = tokio::fs::read_to_string(logs_dir.join("final.patch"))
let diff = tokio::fs::read_to_string(run_dir.join("final.patch"))
.await
.unwrap_or_default();
if let (
@ -1119,7 +1119,7 @@ pub async fn run_command(
&diff,
&model,
config.pull_request_draft,
&logs_dir,
&run_dir,
)
.await
{
@ -1130,7 +1130,7 @@ pub async fn run_command(
draft: config.pull_request_draft,
});
pr_url = Some(record.html_url.clone());
if let Err(e) = record.save(&logs_dir.join("pull_request.json")) {
if let Err(e) = record.save(&run_dir.join("pull_request.json")) {
tracing::warn!(error = %e, "Failed to save pull_request.json");
}
}
@ -1214,7 +1214,7 @@ pub async fn run_command(
"{}",
styles
.dim
.apply_to(format!("Logs: {}", tilde_path(&logs_dir)))
.apply_to(format!("Run: {}", tilde_path(&run_dir)))
);
if let Some(failure) = outcome.failure_reason() {
@ -1231,8 +1231,8 @@ pub async fn run_command(
}
}
print_final_output(&logs_dir, styles);
print_assets(&logs_dir, styles);
print_final_output(&run_dir, styles);
print_assets(&run_dir, styles);
// 9. Cleanup sandbox (defuse the scopeguard so we await properly)
scopeguard::ScopeGuard::into_inner(cleanup_guard);
@ -1269,14 +1269,14 @@ pub async fn run_command(
/// Returns (work_dir, worktree_path, branch_name, base_sha) on success.
fn setup_worktree(
original_cwd: &std::path::Path,
logs_dir: &std::path::Path,
run_dir: &std::path::Path,
run_id: &str,
) -> anyhow::Result<(PathBuf, PathBuf, String, String)> {
let base_sha = crate::git::head_sha(original_cwd).map_err(|e| anyhow::anyhow!("{e}"))?;
let branch_name = format!("arc/run/{run_id}");
crate::git::create_branch(original_cwd, &branch_name).map_err(|e| anyhow::anyhow!("{e}"))?;
let worktree_path = logs_dir.join("worktree");
let worktree_path = run_dir.join("worktree");
crate::git::replace_worktree(original_cwd, &worktree_path, &branch_name)
.map_err(|e| anyhow::anyhow!("{e}"))?;
@ -1401,21 +1401,21 @@ async fn run_from_branch(
}
// Set up logs directory
let logs_dir = args.logs_dir.unwrap_or_else(|| {
let run_dir = args.run_dir.unwrap_or_else(|| {
let base = dirs::home_dir()
.expect("could not determine home directory")
.join(".arc")
.join("logs");
.join("runs");
base.join(format!(
"{}-{}",
chrono::Local::now().format("%Y%m%d"),
run_id
))
});
tokio::fs::create_dir_all(&logs_dir).await?;
arc_util::run_log::activate(&logs_dir.join("cli.log"))
tokio::fs::create_dir_all(&run_dir).await?;
arc_util::run_log::activate(&run_dir.join("cli.log"))
.context("Failed to activate per-run log")?;
tokio::fs::write(logs_dir.join("graph.dot"), &source).await?;
tokio::fs::write(run_dir.join("graph.dot"), &source).await?;
let base_sha =
crate::git::MetadataStore::read_manifest(&original_cwd, &run_id)?.and_then(|m| m.base_sha);
@ -1432,7 +1432,7 @@ async fn run_from_branch(
match sandbox_provider {
SandboxProvider::Local | SandboxProvider::Docker => {
// Re-attach worktree to the existing run branch
let wt = logs_dir.join("worktree");
let wt = run_dir.join("worktree");
crate::git::replace_worktree(&original_cwd, &wt, run_branch).map_err(|e| {
anyhow::anyhow!("failed to attach worktree to {run_branch}: {e}")
})?;
@ -1540,7 +1540,7 @@ async fn run_from_branch(
let meta_branch = Some(crate::git::MetadataStore::branch_name(&run_id));
let config = RunConfig {
logs_root: logs_dir.clone(),
run_dir: run_dir.clone(),
cancel_token: None,
dry_run: dry_run_mode,
run_id: run_id.clone(),
@ -1594,7 +1594,7 @@ async fn run_from_branch(
&config.run_id,
&graph.name,
graph.goal(),
&logs_dir,
&run_dir,
failed,
failure_reason.as_deref(),
run_duration_ms,
@ -1626,11 +1626,11 @@ async fn run_from_branch(
"{}",
styles
.dim
.apply_to(format!("Logs: {}", tilde_path(&logs_dir)))
.apply_to(format!("Run: {}", tilde_path(&run_dir)))
);
print_final_output(&logs_dir, styles);
print_assets(&logs_dir, styles);
print_final_output(&run_dir, styles);
print_assets(&run_dir, styles);
arc_util::run_log::deactivate();
match outcome.status {
@ -1640,8 +1640,8 @@ async fn run_from_branch(
}
/// Print the final stage output from the checkpoint, if available.
fn print_final_output(logs_dir: &std::path::Path, styles: &Styles) {
let Ok(checkpoint) = Checkpoint::load(&logs_dir.join("checkpoint.json")) else {
fn print_final_output(run_dir: &std::path::Path, styles: &Styles) {
let Ok(checkpoint) = Checkpoint::load(&run_dir.join("checkpoint.json")) else {
return;
};
@ -1661,8 +1661,8 @@ fn print_final_output(logs_dir: &std::path::Path, styles: &Styles) {
}
/// Print collected asset paths, if any.
fn print_assets(logs_dir: &std::path::Path, styles: &Styles) {
let paths = crate::asset_snapshot::collect_asset_paths(logs_dir);
fn print_assets(run_dir: &std::path::Path, styles: &Styles) {
let paths = crate::asset_snapshot::collect_asset_paths(run_dir);
if paths.is_empty() {
return;
}
@ -1934,7 +1934,7 @@ async fn generate_retro(
run_id: &str,
workflow_name: &str,
goal: &str,
logs_dir: &std::path::Path,
run_dir: &std::path::Path,
failed: bool,
failure_reason: Option<&str>,
run_duration_ms: u64,
@ -1945,7 +1945,7 @@ async fn generate_retro(
model: &str,
styles: &'static Styles,
) {
let cp = match Checkpoint::load(&logs_dir.join("checkpoint.json")) {
let cp = match Checkpoint::load(&run_dir.join("checkpoint.json")) {
Ok(cp) => cp,
Err(e) => {
eprintln!(
@ -1956,7 +1956,7 @@ async fn generate_retro(
}
};
let stage_durations = crate::retro::extract_stage_durations(logs_dir);
let stage_durations = crate::retro::extract_stage_durations(run_dir);
let mut retro = crate::retro::derive_retro(
run_id,
workflow_name,
@ -1968,7 +1968,7 @@ async fn generate_retro(
&stage_durations,
);
match retro.save(logs_dir) {
match retro.save(run_dir) {
Ok(()) => {}
Err(e) => {
eprintln!(
@ -1988,7 +1988,7 @@ async fn generate_retro(
let narrative_result = if dry_run_mode {
Ok(crate::retro_agent::dry_run_narrative())
} else if let Some(client) = llm_client {
crate::retro_agent::run_retro_agent(sandbox, logs_dir, client, provider_enum, model).await
crate::retro_agent::run_retro_agent(sandbox, run_dir, client, provider_enum, model).await
} else {
Err(anyhow::anyhow!("No LLM client available"))
};
@ -1997,7 +1997,7 @@ async fn generate_retro(
match narrative_result {
Ok(narrative) => {
retro.apply_narrative(narrative);
match retro.save(logs_dir) {
match retro.save(run_dir) {
Ok(()) => {
// Line 1: smoothness + outcome with right-aligned duration
let smoothness_str = retro
@ -2046,7 +2046,7 @@ async fn generate_retro(
}
// Line 3: file path
let retro_path = format!("{}/retro.json", super::tilde_path(logs_dir));
let retro_path = format!("{}/retro.json", super::tilde_path(run_dir));
eprintln!(
" {} {}",
styles.dim.apply_to("Retro saved to"),

View file

@ -239,6 +239,10 @@ fn default_data_dir() -> PathBuf {
.join(".arc")
}
pub(crate) fn default_runs_base() -> PathBuf {
default_data_dir().join("runs")
}
pub(crate) fn default_logs_base() -> PathBuf {
default_data_dir().join("logs")
}
@ -272,7 +276,7 @@ pub fn find_run_by_prefix(base: &Path, prefix: &str) -> Result<PathBuf> {
}
pub fn list_command(args: &RunsListArgs) -> Result<()> {
let base = default_logs_base();
let base = default_runs_base();
let runs = scan_runs(&base)?;
let label_filters = parse_label_filters(&args.filter.label);
let filtered = filter_runs(
@ -362,13 +366,14 @@ fn format_size(bytes: u64) -> String {
pub fn df_command(args: &DfArgs) -> Result<()> {
let data_dir = default_data_dir();
let logs_base = data_dir.join("logs");
df_from(args, &data_dir, &logs_base)
let runs_base = default_runs_base();
let logs_base = default_logs_base();
df_from(args, &data_dir, &runs_base, &logs_base)
}
pub fn df_from(args: &DfArgs, data_dir: &Path, logs_base: &Path) -> Result<()> {
pub fn df_from(args: &DfArgs, data_dir: &Path, runs_base: &Path, logs_base: &Path) -> Result<()> {
// --- Runs ---
let runs = scan_runs(logs_base)?;
let runs = scan_runs(runs_base)?;
let mut active_count = 0u64;
let mut total_run_size = 0u64;
let mut reclaimable_run_size = 0u64;
@ -535,7 +540,7 @@ pub fn df_from(args: &DfArgs, data_dir: &Path, logs_base: &Path) -> Result<()> {
}
pub fn prune_command(args: &RunsPruneArgs) -> Result<()> {
let base = default_logs_base();
let base = default_runs_base();
prune_from(args, &base)
}
@ -1043,12 +1048,14 @@ mod tests {
fn df_reports_run_sizes() {
let tmp = tempfile::tempdir().unwrap();
let data_dir = tmp.path();
let runs_base = data_dir.join("runs");
fs::create_dir(&runs_base).unwrap();
let logs_base = data_dir.join("logs");
fs::create_dir(&logs_base).unwrap();
// Running run
make_run_dir(
&logs_base,
&runs_base,
"20260308-RUNNING",
Some(serde_json::json!({
"run_id": "running-1",
@ -1063,14 +1070,14 @@ mod tests {
);
// Add a file to give it size
fs::write(
logs_base.join("20260308-RUNNING").join("data.bin"),
runs_base.join("20260308-RUNNING").join("data.bin"),
vec![0u8; 100],
)
.unwrap();
// Completed run
make_run_dir(
&logs_base,
&runs_base,
"20260307-DONE",
Some(serde_json::json!({
"run_id": "done-1",
@ -1088,20 +1095,22 @@ mod tests {
false,
);
fs::write(
logs_base.join("20260307-DONE").join("data.bin"),
runs_base.join("20260307-DONE").join("data.bin"),
vec![0u8; 200],
)
.unwrap();
let args = DfArgs { verbose: false };
// Should not panic
df_from(&args, data_dir, &logs_base).unwrap();
df_from(&args, data_dir, &runs_base, &logs_base).unwrap();
}
#[test]
fn df_reports_log_files() {
let tmp = tempfile::tempdir().unwrap();
let data_dir = tmp.path();
let runs_base = data_dir.join("runs");
fs::create_dir(&runs_base).unwrap();
let logs_base = data_dir.join("logs");
fs::create_dir(&logs_base).unwrap();
@ -1109,13 +1118,15 @@ mod tests {
fs::write(logs_base.join("serve-2026-03-08.log"), vec![0u8; 300]).unwrap();
let args = DfArgs { verbose: false };
df_from(&args, data_dir, &logs_base).unwrap();
df_from(&args, data_dir, &runs_base, &logs_base).unwrap();
}
#[test]
fn df_reports_database_files() {
let tmp = tempfile::tempdir().unwrap();
let data_dir = tmp.path();
let runs_base = data_dir.join("runs");
fs::create_dir(&runs_base).unwrap();
let logs_base = data_dir.join("logs");
fs::create_dir(&logs_base).unwrap();
@ -1124,7 +1135,7 @@ mod tests {
fs::write(data_dir.join("arc.db-shm"), vec![0u8; 32]).unwrap();
let args = DfArgs { verbose: false };
df_from(&args, data_dir, &logs_base).unwrap();
df_from(&args, data_dir, &runs_base, &logs_base).unwrap();
}
#[test]

View file

@ -283,11 +283,7 @@ pub fn resolve_thread_id(
// --- Run directory helpers (spec 5.6) ---
/// Write manifest.json at the start of a workflow run. Returns the manifest.
fn write_manifest(
logs_root: &Path,
graph: &Graph,
config: &RunConfig,
) -> crate::manifest::Manifest {
fn write_manifest(run_dir: &Path, graph: &Graph, config: &RunConfig) -> crate::manifest::Manifest {
let workflow_name = if graph.name.is_empty() {
"unnamed".to_string()
} else {
@ -305,20 +301,20 @@ fn write_manifest(
labels: config.labels.clone(),
base_branch: config.base_branch.clone(),
};
let _ = std::fs::create_dir_all(logs_root);
let _ = manifest.save(&logs_root.join("manifest.json"));
let _ = std::fs::create_dir_all(run_dir);
let _ = manifest.save(&run_dir.join("manifest.json"));
manifest
}
/// Return the directory for a node's logs.
///
/// First visit (`visit <= 1`): `{logs_root}/nodes/{node_id}`
/// Subsequent visits: `{logs_root}/nodes/{node_id}-visit_{visit}`
pub fn node_dir(logs_root: &Path, node_id: &str, visit: usize) -> PathBuf {
/// First visit (`visit <= 1`): `{run_dir}/nodes/{node_id}`
/// Subsequent visits: `{run_dir}/nodes/{node_id}-visit_{visit}`
pub fn node_dir(run_dir: &Path, node_id: &str, visit: usize) -> PathBuf {
if visit <= 1 {
logs_root.join("nodes").join(node_id)
run_dir.join("nodes").join(node_id)
} else {
logs_root
run_dir
.join("nodes")
.join(format!("{node_id}-visit_{visit}"))
}
@ -329,9 +325,9 @@ pub fn visit_from_context(context: &Context) -> usize {
context.node_visit_count()
}
/// Write status.json for a completed node into {`logs_root}/nodes/{node_id}/status.json`.
fn write_node_status(logs_root: &Path, node_id: &str, visit: usize, outcome: &Outcome) {
let node_dir = node_dir(logs_root, node_id, visit);
/// Write status.json for a completed node into {`run_dir}/nodes/{node_id}/status.json`.
fn write_node_status(run_dir: &Path, node_id: &str, visit: usize, outcome: &Outcome) {
let node_dir = node_dir(run_dir, node_id, visit);
let _ = std::fs::create_dir_all(&node_dir);
let status = serde_json::json!({
"status": outcome.status.to_string(),
@ -811,7 +807,7 @@ pub async fn git_replace_worktree_remote(sandbox: &dyn Sandbox, path: &str, bran
/// Configuration for a workflow run.
pub struct RunConfig {
pub logs_root: PathBuf,
pub run_dir: PathBuf,
pub cancel_token: Option<Arc<AtomicBool>>,
pub dry_run: bool,
/// Unique identifier for this workflow run.
@ -965,7 +961,7 @@ impl WorkflowRunEngine {
node: &Node,
context: &Context,
graph: &Graph,
logs_root: &Path,
run_dir: &Path,
policy: &RetryPolicy,
stage_index: usize,
visit: usize,
@ -985,7 +981,7 @@ impl WorkflowRunEngine {
// Gap #11: Panic safety -- catch panics from handler execution
let result = {
let future = handler.execute(node, context, graph, logs_root, &self.services);
let future = handler.execute(node, context, graph, run_dir, &self.services);
let panic_safe = AssertUnwindSafe(future).catch_unwind();
// Gap #2: Timeout enforcement -- wrap with tokio::time::timeout
let timed_result = if let Some(duration) = node_timeout {
@ -1009,7 +1005,7 @@ impl WorkflowRunEngine {
} else {
"handler panicked".to_string()
};
let panic_dir = node_dir(logs_root, &node.id, visit);
let panic_dir = node_dir(run_dir, &node.id, visit);
let _ = std::fs::create_dir_all(&panic_dir);
let _ = std::fs::write(panic_dir.join("panic.txt"), &msg);
Err(ArcError::handler(msg))
@ -1024,7 +1020,7 @@ impl WorkflowRunEngine {
} else {
format!("{}-visit_{visit}", node.id)
};
let assets_dir = logs_root
let assets_dir = run_dir
.join("artifacts")
.join("assets")
.join(&node_slug)
@ -1205,7 +1201,7 @@ impl WorkflowRunEngine {
) -> Result<(Outcome, Context)> {
let run_start = Instant::now();
let run_id = config.run_id.clone();
let artifact_store = ArtifactStore::new(Some(config.logs_root.clone()));
let artifact_store = ArtifactStore::new(Some(config.run_dir.clone()));
// Populate git_state for handlers (parallel, fan_in) when checkpointing is active
let git_state = match (&config.git_checkpoint, &config.base_sha) {
@ -1253,7 +1249,7 @@ impl WorkflowRunEngine {
}
// Write manifest.json (spec 5.6)
let manifest = write_manifest(&config.logs_root, graph, config);
let manifest = write_manifest(&config.run_dir, graph, config);
// Initialize metadata branch for git-native checkpoint storage (best-effort)
if config.meta_branch.is_some() {
@ -1267,7 +1263,7 @@ impl WorkflowRunEngine {
let store = crate::git::MetadataStore::new(repo_path, &config.git_author);
let manifest_bytes = serde_json::to_vec_pretty(&manifest).unwrap_or_default();
let dot_source =
std::fs::read(config.logs_root.join("graph.dot")).unwrap_or_default();
std::fs::read(config.run_dir.join("graph.dot")).unwrap_or_default();
if let Err(e) = store.init_run(&config.run_id, &manifest_bytes, &dot_source) {
tracing::warn!(run_id = %config.run_id, error = %e, "Metadata branch init failed");
}
@ -1615,7 +1611,7 @@ impl WorkflowRunEngine {
let (mut outcome, attempts_used) = if let Some((ref token, _)) = stall_token {
tokio::select! {
result = self.execute_with_retry(
node, &context, graph, &config.logs_root, &retry_policy, stage_index, visit, &config.asset_globs,
node, &context, graph, &config.run_dir, &retry_policy, stage_index, visit, &config.asset_globs,
) => result?,
() = token.cancelled() => {
let idle_secs = graph.stall_timeout().map_or(0, |d| d.as_secs());
@ -1634,7 +1630,7 @@ impl WorkflowRunEngine {
node,
&context,
graph,
&config.logs_root,
&config.run_dir,
&retry_policy,
stage_index,
visit,
@ -1751,7 +1747,7 @@ impl WorkflowRunEngine {
}
// Write per-node status.json (spec 5.6)
write_node_status(&config.logs_root, &node.id, visit, &outcome);
write_node_status(&config.run_dir, &node.id, visit, &outcome);
// Offload large context values to artifact store before recording
if let Err(e) = offload_large_values(&mut outcome.context_updates, &artifact_store) {
@ -1869,7 +1865,7 @@ impl WorkflowRunEngine {
loop_state.restart_failure_signatures.clone(),
loop_state.node_visits.clone(),
);
let checkpoint_path = config.logs_root.join("checkpoint.json");
let checkpoint_path = config.run_dir.join("checkpoint.json");
if let Err(e) = checkpoint.save(&checkpoint_path) {
context.append_log(format!("checkpoint save failed: {e}"));
} else {
@ -2010,7 +2006,7 @@ impl WorkflowRunEngine {
.unwrap_or(&sha);
let diff_base = prev.to_string();
let diff_dest =
node_dir(&config.logs_root, &node.id, visit).join("diff.patch");
node_dir(&config.run_dir, &node.id, visit).join("diff.patch");
let diff_result = match mode {
GitCheckpointMode::Host(work_dir) => {
@ -2182,7 +2178,7 @@ impl WorkflowRunEngine {
};
if let Some(patch) = patch {
if !patch.is_empty() {
let _ = std::fs::write(config.logs_root.join("final.patch"), patch);
let _ = std::fs::write(config.run_dir.join("final.patch"), patch);
}
}
}
@ -2223,7 +2219,7 @@ mod tests {
_node: &Node,
_context: &Context,
_graph: &Graph,
_logs_root: &Path,
_run_dir: &Path,
_services: &crate::handler::EngineServices,
) -> std::result::Result<Outcome, ArcError> {
Ok(Outcome::fail_classify("always fails"))
@ -2242,7 +2238,7 @@ mod tests {
_node: &Node,
_context: &Context,
_graph: &Graph,
_logs_root: &Path,
_run_dir: &Path,
_services: &crate::handler::EngineServices,
) -> std::result::Result<Outcome, ArcError> {
tokio::time::sleep(Duration::from_millis(self.sleep_ms)).await;
@ -2844,7 +2840,7 @@ mod tests {
let engine =
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -2872,7 +2868,7 @@ mod tests {
let engine =
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -2908,7 +2904,7 @@ mod tests {
let engine = WorkflowRunEngine::new(make_registry(), Arc::new(emitter), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -2940,7 +2936,7 @@ mod tests {
let engine =
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -2968,7 +2964,7 @@ mod tests {
let engine =
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -3009,7 +3005,7 @@ mod tests {
let engine =
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -3074,7 +3070,7 @@ mod tests {
let engine =
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -3166,7 +3162,7 @@ mod tests {
let engine =
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -3201,7 +3197,7 @@ mod tests {
let engine =
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "labels-run".into(),
@ -3231,7 +3227,7 @@ mod tests {
let engine =
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "no-labels-run".into(),
@ -3261,7 +3257,7 @@ mod tests {
let engine =
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -3295,7 +3291,7 @@ mod tests {
let engine =
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -3457,7 +3453,7 @@ mod tests {
let engine =
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -3502,7 +3498,7 @@ mod tests {
let engine =
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -3565,7 +3561,7 @@ mod tests {
registry.register("always_fail", Box::new(AlwaysFailHandler));
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -3631,7 +3627,7 @@ mod tests {
registry.register("always_fail", Box::new(AlwaysFailHandler));
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -3701,7 +3697,7 @@ mod tests {
registry.register("slow", Box::new(SlowHandler { sleep_ms: 500 }));
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -3760,7 +3756,7 @@ mod tests {
registry.register("slow", Box::new(SlowHandler { sleep_ms: 10 }));
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -3820,7 +3816,7 @@ mod tests {
registry.register("slow", Box::new(SlowHandler { sleep_ms: 500 }));
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -3855,7 +3851,7 @@ mod tests {
let engine =
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -3886,7 +3882,7 @@ mod tests {
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
let cancel_token = Arc::new(AtomicBool::new(true));
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: Some(cancel_token),
dry_run: false,
run_id: "test-run".into(),
@ -3916,7 +3912,7 @@ mod tests {
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
let cancel_token = Arc::new(AtomicBool::new(false));
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: Some(cancel_token),
dry_run: false,
run_id: "test-run".into(),
@ -3959,7 +3955,7 @@ mod tests {
registry.register("slow", Box::new(SlowHandler { sleep_ms: 200 }));
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: Some(cancel_token),
dry_run: false,
run_id: "test-run".into(),
@ -4039,7 +4035,7 @@ mod tests {
let engine =
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -4072,7 +4068,7 @@ mod tests {
let engine =
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: true,
run_id: "test-run".into(),
@ -4107,7 +4103,7 @@ mod tests {
let engine =
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: true,
run_id: "test-run".into(),
@ -4147,7 +4143,7 @@ mod tests {
let engine =
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -4185,7 +4181,7 @@ mod tests {
let engine =
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: true,
run_id: "test-run".into(),
@ -4220,7 +4216,7 @@ mod tests {
let engine =
WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -4284,7 +4280,7 @@ mod tests {
_node: &Node,
_context: &Context,
_graph: &Graph,
_logs_root: &Path,
_run_dir: &Path,
_services: &crate::handler::EngineServices,
) -> std::result::Result<Outcome, ArcError> {
panic!("test panic message");
@ -4316,7 +4312,7 @@ mod tests {
registry.register("panicker", Box::new(PanickingHandler));
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -4481,7 +4477,7 @@ mod tests {
_node: &Node,
_context: &Context,
_graph: &Graph,
_logs_root: &Path,
_run_dir: &Path,
_services: &crate::handler::EngineServices,
) -> std::result::Result<Outcome, ArcError> {
Ok(Outcome::fail_classify("connection refused"))
@ -4509,7 +4505,7 @@ mod tests {
_node: &Node,
_context: &Context,
_graph: &Graph,
_logs_root: &Path,
_run_dir: &Path,
_services: &crate::handler::EngineServices,
) -> std::result::Result<Outcome, ArcError> {
let n = self.counter.fetch_add(1, Ordering::Relaxed);
@ -4527,7 +4523,7 @@ mod tests {
registry.register("always_fail", Box::new(AlwaysFailHandler));
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -4565,7 +4561,7 @@ mod tests {
registry.register("always_fail", Box::new(TransientFailHandler));
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -4610,7 +4606,7 @@ mod tests {
);
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -4695,7 +4691,7 @@ mod tests {
registry.register("always_fail", Box::new(AlwaysFailHandler));
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -4737,7 +4733,7 @@ mod tests {
node: &Node,
_context: &Context,
_graph: &Graph,
_logs_root: &Path,
_run_dir: &Path,
services: &crate::handler::EngineServices,
) -> std::result::Result<Outcome, ArcError> {
let start = Instant::now();
@ -4791,7 +4787,7 @@ mod tests {
registry.register("slow", Box::new(SlowHandler { sleep_ms: 60_000 }));
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -4864,7 +4860,7 @@ mod tests {
);
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -4924,7 +4920,7 @@ mod tests {
registry.register("slow", Box::new(SlowHandler { sleep_ms: 50 }));
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -4985,7 +4981,7 @@ mod tests {
registry.register("always_fail", Box::new(AlwaysFailHandler));
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -5054,7 +5050,7 @@ mod tests {
.trim()
.to_string();
let logs_dir = tempfile::tempdir().unwrap();
let run_tmp = tempfile::tempdir().unwrap();
// Build start -> work -> exit graph so work node produces a git checkpoint
let mut g = simple_graph();
@ -5073,7 +5069,7 @@ mod tests {
let engine = WorkflowRunEngine::new(make_registry(), Arc::new(emitter), local_env());
let config = RunConfig {
logs_root: logs_dir.path().to_path_buf(),
run_dir: run_tmp.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "git-cp-test".into(),

View file

@ -202,7 +202,7 @@ impl Handler for AgentHandler {
node: &Node,
context: &Context,
graph: &Graph,
logs_root: &Path,
run_dir: &Path,
services: &EngineServices,
) -> Result<Outcome, ArcError> {
// 1. Build prompt (prepend fidelity preamble if present)
@ -220,7 +220,7 @@ impl Handler for AgentHandler {
// 2. Write prompt to logs
let visit = crate::engine::visit_from_context(context);
let stage_dir = crate::engine::node_dir(logs_root, &node.id, visit);
let stage_dir = crate::engine::node_dir(run_dir, &node.id, visit);
tokio::fs::create_dir_all(&stage_dir).await?;
tokio::fs::write(stage_dir.join("prompt.md"), &prompt).await?;

View file

@ -32,7 +32,7 @@ impl Handler for CommandHandler {
node: &Node,
context: &Context,
_graph: &Graph,
logs_root: &Path,
run_dir: &Path,
services: &EngineServices,
) -> Result<Outcome, ArcError> {
let script = node
@ -59,7 +59,7 @@ impl Handler for CommandHandler {
}
let visit = crate::engine::visit_from_context(context);
let stage_dir = crate::engine::node_dir(logs_root, &node.id, visit);
let stage_dir = crate::engine::node_dir(run_dir, &node.id, visit);
tokio::fs::create_dir_all(&stage_dir).await?;
let invocation = serde_json::json!({
@ -177,10 +177,10 @@ mod tests {
let node = Node::new("script_node");
let context = Context::new();
let graph = Graph::new("test");
let logs_root = tempfile::tempdir().unwrap();
let run_dir = tempfile::tempdir().unwrap();
let outcome = handler
.execute(&node, &context, &graph, logs_root.path(), &make_services())
.execute(&node, &context, &graph, run_dir.path(), &make_services())
.await
.unwrap();
assert_eq!(outcome.status, StageStatus::Fail);
@ -197,10 +197,10 @@ mod tests {
);
let context = Context::new();
let graph = Graph::new("test");
let logs_root = tempfile::tempdir().unwrap();
let run_dir = tempfile::tempdir().unwrap();
let outcome = handler
.execute(&node, &context, &graph, logs_root.path(), &make_services())
.execute(&node, &context, &graph, run_dir.path(), &make_services())
.await
.unwrap();
assert_eq!(outcome.status, StageStatus::Success);
@ -219,10 +219,10 @@ mod tests {
.insert("script".to_string(), AttrValue::String("false".to_string()));
let context = Context::new();
let graph = Graph::new("test");
let logs_root = tempfile::tempdir().unwrap();
let run_dir = tempfile::tempdir().unwrap();
let outcome = handler
.execute(&node, &context, &graph, logs_root.path(), &make_services())
.execute(&node, &context, &graph, run_dir.path(), &make_services())
.await
.unwrap();
assert_eq!(outcome.status, StageStatus::Fail);
@ -242,10 +242,10 @@ mod tests {
);
let context = Context::new();
let graph = Graph::new("test");
let logs_root = tempfile::tempdir().unwrap();
let run_dir = tempfile::tempdir().unwrap();
let err = handler
.execute(&node, &context, &graph, logs_root.path(), &make_services())
.execute(&node, &context, &graph, run_dir.path(), &make_services())
.await
.unwrap_err();
let msg = err.to_string();
@ -265,14 +265,14 @@ mod tests {
);
let context = Context::new();
let graph = Graph::new("test");
let logs_root = tempfile::tempdir().unwrap();
let run_dir = tempfile::tempdir().unwrap();
handler
.execute(&node, &context, &graph, logs_root.path(), &make_services())
.execute(&node, &context, &graph, run_dir.path(), &make_services())
.await
.unwrap();
let invocation_path = logs_root
let invocation_path = run_dir
.path()
.join("nodes")
.join("script_node")
@ -298,14 +298,14 @@ mod tests {
);
let context = Context::new();
let graph = Graph::new("test");
let logs_root = tempfile::tempdir().unwrap();
let run_dir = tempfile::tempdir().unwrap();
handler
.execute(&node, &context, &graph, logs_root.path(), &make_services())
.execute(&node, &context, &graph, run_dir.path(), &make_services())
.await
.unwrap();
let invocation_path = logs_root
let invocation_path = run_dir
.path()
.join("nodes")
.join("script_node")
@ -327,14 +327,14 @@ mod tests {
);
let context = Context::new();
let graph = Graph::new("test");
let logs_root = tempfile::tempdir().unwrap();
let run_dir = tempfile::tempdir().unwrap();
handler
.execute(&node, &context, &graph, logs_root.path(), &make_services())
.execute(&node, &context, &graph, run_dir.path(), &make_services())
.await
.unwrap();
let stage_dir = logs_root.path().join("nodes").join("script_node");
let stage_dir = run_dir.path().join("nodes").join("script_node");
let stdout = std::fs::read_to_string(stage_dir.join("stdout.log")).unwrap();
assert_eq!(stdout.trim(), "hello");
let stderr = std::fs::read_to_string(stage_dir.join("stderr.log")).unwrap();
@ -351,14 +351,14 @@ mod tests {
);
let context = Context::new();
let graph = Graph::new("test");
let logs_root = tempfile::tempdir().unwrap();
let run_dir = tempfile::tempdir().unwrap();
handler
.execute(&node, &context, &graph, logs_root.path(), &make_services())
.execute(&node, &context, &graph, run_dir.path(), &make_services())
.await
.unwrap();
let stage_dir = logs_root.path().join("nodes").join("script_node");
let stage_dir = run_dir.path().join("nodes").join("script_node");
let stderr = std::fs::read_to_string(stage_dir.join("stderr.log")).unwrap();
assert_eq!(stderr.trim(), "oops");
}
@ -373,14 +373,14 @@ mod tests {
);
let context = Context::new();
let graph = Graph::new("test");
let logs_root = tempfile::tempdir().unwrap();
let run_dir = tempfile::tempdir().unwrap();
handler
.execute(&node, &context, &graph, logs_root.path(), &make_services())
.execute(&node, &context, &graph, run_dir.path(), &make_services())
.await
.unwrap();
let timing_path = logs_root
let timing_path = run_dir
.path()
.join("nodes")
.join("script_node")
@ -400,14 +400,14 @@ mod tests {
.insert("script".to_string(), AttrValue::String("false".to_string()));
let context = Context::new();
let graph = Graph::new("test");
let logs_root = tempfile::tempdir().unwrap();
let run_dir = tempfile::tempdir().unwrap();
handler
.execute(&node, &context, &graph, logs_root.path(), &make_services())
.execute(&node, &context, &graph, run_dir.path(), &make_services())
.await
.unwrap();
let timing_path = logs_root
let timing_path = run_dir
.path()
.join("nodes")
.join("script_node")
@ -432,14 +432,14 @@ mod tests {
);
let context = Context::new();
let graph = Graph::new("test");
let logs_root = tempfile::tempdir().unwrap();
let run_dir = tempfile::tempdir().unwrap();
let _err = handler
.execute(&node, &context, &graph, logs_root.path(), &make_services())
.execute(&node, &context, &graph, run_dir.path(), &make_services())
.await
.unwrap_err();
let timing_path = logs_root
let timing_path = run_dir
.path()
.join("nodes")
.join("script_node")
@ -465,10 +465,10 @@ mod tests {
);
let context = Context::new();
let graph = Graph::new("test");
let logs_root = tempfile::tempdir().unwrap();
let run_dir = tempfile::tempdir().unwrap();
let outcome = handler
.execute(&node, &context, &graph, logs_root.path(), &make_services())
.execute(&node, &context, &graph, run_dir.path(), &make_services())
.await
.unwrap();
assert_eq!(outcome.status, StageStatus::Success);
@ -493,10 +493,10 @@ mod tests {
);
let context = Context::new();
let graph = Graph::new("test");
let logs_root = tempfile::tempdir().unwrap();
let run_dir = tempfile::tempdir().unwrap();
let outcome = handler
.execute(&node, &context, &graph, logs_root.path(), &make_services())
.execute(&node, &context, &graph, run_dir.path(), &make_services())
.await
.unwrap();
assert_eq!(outcome.status, StageStatus::Fail);
@ -516,10 +516,10 @@ mod tests {
);
let context = Context::new();
let graph = Graph::new("test");
let logs_root = tempfile::tempdir().unwrap();
let run_dir = tempfile::tempdir().unwrap();
let outcome = handler
.execute(&node, &context, &graph, logs_root.path(), &make_services())
.execute(&node, &context, &graph, run_dir.path(), &make_services())
.await
.unwrap();
assert_eq!(outcome.status, StageStatus::Fail);
@ -539,10 +539,10 @@ mod tests {
);
let context = Context::new();
let graph = Graph::new("test");
let logs_root = tempfile::tempdir().unwrap();
let run_dir = tempfile::tempdir().unwrap();
let outcome = handler
.execute(&node, &context, &graph, logs_root.path(), &make_services())
.execute(&node, &context, &graph, run_dir.path(), &make_services())
.await
.unwrap();
assert_eq!(outcome.status, StageStatus::Success);
@ -560,10 +560,10 @@ mod tests {
);
let context = Context::new();
let graph = Graph::new("test");
let logs_root = tempfile::tempdir().unwrap();
let run_dir = tempfile::tempdir().unwrap();
let outcome = handler
.execute(&node, &context, &graph, logs_root.path(), &make_services())
.execute(&node, &context, &graph, run_dir.path(), &make_services())
.await
.unwrap();
assert_eq!(outcome.status, StageStatus::Success);
@ -699,14 +699,14 @@ mod tests {
);
let context = Context::new();
let graph = Graph::new("test");
let logs_root = tempfile::tempdir().unwrap();
let run_dir = tempfile::tempdir().unwrap();
let outcome = handler
.execute(
&node,
&context,
&graph,
logs_root.path(),
run_dir.path(),
&make_spy_services(spy.clone()),
)
.await
@ -748,14 +748,14 @@ mod tests {
);
let context = Context::new();
let graph = Graph::new("test");
let logs_root = tempfile::tempdir().unwrap();
let run_dir = tempfile::tempdir().unwrap();
let outcome = handler
.execute(
&node,
&context,
&graph,
logs_root.path(),
run_dir.path(),
&make_spy_services(spy.clone()),
)
.await
@ -785,7 +785,7 @@ mod tests {
.insert("script".to_string(), AttrValue::String("true".to_string()));
let context = Context::new();
let graph = Graph::new("test");
let logs_root = tempfile::tempdir().unwrap();
let run_dir = tempfile::tempdir().unwrap();
let mut services = make_spy_services(spy.clone());
services
@ -793,7 +793,7 @@ mod tests {
.insert("MY_VAR".to_string(), "my_value".to_string());
handler
.execute(&node, &context, &graph, logs_root.path(), &services)
.execute(&node, &context, &graph, run_dir.path(), &services)
.await
.unwrap();
@ -814,10 +814,10 @@ mod tests {
);
let context = Context::new();
let graph = Graph::new("test");
let logs_root = tempfile::tempdir().unwrap();
let run_dir = tempfile::tempdir().unwrap();
let outcome = handler
.execute(&node, &context, &graph, logs_root.path(), &make_services())
.execute(&node, &context, &graph, run_dir.path(), &make_services())
.await
.unwrap();
assert_eq!(outcome.status, StageStatus::Success);
@ -838,10 +838,10 @@ mod tests {
);
let context = Context::new();
let graph = Graph::new("test");
let logs_root = tempfile::tempdir().unwrap();
let run_dir = tempfile::tempdir().unwrap();
let outcome = handler
.execute(&node, &context, &graph, logs_root.path(), &make_services())
.execute(&node, &context, &graph, run_dir.path(), &make_services())
.await
.unwrap();
assert_eq!(outcome.status, StageStatus::Fail);
@ -885,10 +885,10 @@ mod tests {
);
let context = Context::new();
let graph = Graph::new("test");
let logs_root = tempfile::tempdir().unwrap();
let run_dir = tempfile::tempdir().unwrap();
let outcome = handler
.execute(&node, &context, &graph, logs_root.path(), &make_services())
.execute(&node, &context, &graph, run_dir.path(), &make_services())
.await
.unwrap();
assert_eq!(outcome.status, StageStatus::Fail);

View file

@ -20,7 +20,7 @@ impl Handler for ConditionalHandler {
node: &Node,
_context: &Context,
_graph: &Graph,
_logs_root: &Path,
_run_dir: &Path,
_services: &EngineServices,
) -> Result<Outcome, ArcError> {
let mut outcome = Outcome::success();
@ -55,9 +55,9 @@ mod tests {
let node = Node::new("gate");
let context = Context::new();
let graph = Graph::new("test");
let logs_root = Path::new("/tmp/test");
let run_dir = Path::new("/tmp/test");
let outcome = handler
.execute(&node, &context, &graph, logs_root, &make_services())
.execute(&node, &context, &graph, run_dir, &make_services())
.await
.unwrap();
assert_eq!(outcome.status, crate::outcome::StageStatus::Success);

View file

@ -19,7 +19,7 @@ impl Handler for ExitHandler {
_node: &Node,
_context: &Context,
_graph: &Graph,
_logs_root: &Path,
_run_dir: &Path,
_services: &EngineServices,
) -> Result<Outcome, ArcError> {
Ok(Outcome::success())
@ -52,9 +52,9 @@ mod tests {
let node = Node::new("exit");
let context = Context::new();
let graph = Graph::new("test");
let logs_root = Path::new("/tmp/test");
let run_dir = Path::new("/tmp/test");
let outcome = handler
.execute(&node, &context, &graph, logs_root, &make_services())
.execute(&node, &context, &graph, run_dir, &make_services())
.await
.unwrap();
assert_eq!(outcome.status, crate::outcome::StageStatus::Success);

View file

@ -33,7 +33,7 @@ impl Handler for FanInHandler {
node: &Node,
context: &Context,
_graph: &Graph,
logs_root: &Path,
run_dir: &Path,
services: &EngineServices,
) -> Result<Outcome, ArcError> {
let results = context.get(keys::PARALLEL_RESULTS);
@ -51,7 +51,7 @@ impl Handler for FanInHandler {
prompt_text,
&results,
context,
logs_root,
run_dir,
&node.id,
&services.emitter,
&services.sandbox,
@ -196,7 +196,7 @@ async fn llm_evaluate(
prompt: &str,
results: &serde_json::Value,
context: &Context,
logs_root: &Path,
run_dir: &Path,
node_id: &str,
emitter: &Arc<EventEmitter>,
sandbox: &Arc<dyn Sandbox>,
@ -211,7 +211,7 @@ async fn llm_evaluate(
// Write prompt to logs
let visit = crate::engine::visit_from_context(context);
let stage_dir = crate::engine::node_dir(logs_root, node_id, visit);
let stage_dir = crate::engine::node_dir(run_dir, node_id, visit);
tokio::fs::create_dir_all(&stage_dir).await?;
tokio::fs::write(stage_dir.join("prompt.md"), &full_prompt).await?;
@ -318,10 +318,10 @@ mod tests {
let node = Node::new("fan_in");
let context = Context::new();
let graph = Graph::new("test");
let logs_root = Path::new("/tmp/test");
let run_dir = Path::new("/tmp/test");
let outcome = handler
.execute(&node, &context, &graph, logs_root, &make_services())
.execute(&node, &context, &graph, run_dir, &make_services())
.await
.unwrap();
assert_eq!(outcome.status, StageStatus::Fail);
@ -340,10 +340,10 @@ mod tests {
]),
);
let graph = Graph::new("test");
let logs_root = Path::new("/tmp/test");
let run_dir = Path::new("/tmp/test");
let outcome = handler
.execute(&node, &context, &graph, logs_root, &make_services())
.execute(&node, &context, &graph, run_dir, &make_services())
.await
.unwrap();
assert_eq!(outcome.status, StageStatus::Success);
@ -367,10 +367,10 @@ mod tests {
]),
);
let graph = Graph::new("test");
let logs_root = Path::new("/tmp/test");
let run_dir = Path::new("/tmp/test");
let outcome = handler
.execute(&node, &context, &graph, logs_root, &make_services())
.execute(&node, &context, &graph, run_dir, &make_services())
.await
.unwrap();
assert_eq!(
@ -404,10 +404,10 @@ mod tests {
]),
);
let graph = Graph::new("test");
let logs_root = Path::new("/tmp/test");
let run_dir = Path::new("/tmp/test");
let outcome = handler
.execute(&node, &context, &graph, logs_root, &make_services())
.execute(&node, &context, &graph, run_dir, &make_services())
.await
.unwrap();
assert_eq!(outcome.status, StageStatus::Success);
@ -501,10 +501,10 @@ mod tests {
]),
);
let graph = Graph::new("test");
let logs_root = Path::new("/tmp/test");
let run_dir = Path::new("/tmp/test");
let outcome = handler
.execute(&node, &context, &graph, logs_root, &make_services())
.execute(&node, &context, &graph, run_dir, &make_services())
.await
.unwrap();
assert_eq!(outcome.status, StageStatus::Fail);
@ -528,10 +528,10 @@ mod tests {
]),
);
let graph = Graph::new("test");
let logs_root = Path::new("/tmp/test");
let run_dir = Path::new("/tmp/test");
let outcome = handler
.execute(&node, &context, &graph, logs_root, &make_services())
.execute(&node, &context, &graph, run_dir, &make_services())
.await
.unwrap();
assert_eq!(outcome.status, StageStatus::Success);

View file

@ -101,7 +101,7 @@ impl Handler for HumanHandler {
node: &Node,
_context: &Context,
graph: &Graph,
_logs_root: &Path,
_run_dir: &Path,
_services: &EngineServices,
) -> Result<Outcome, ArcError> {
// 1. Derive choices from outgoing edges
@ -353,10 +353,10 @@ mod tests {
let graph = build_graph_with_human_gate();
let node = graph.nodes.get("gate").unwrap();
let context = Context::new();
let logs_root = Path::new("/tmp/test");
let run_dir = Path::new("/tmp/test");
let outcome = handler
.execute(node, &context, &graph, logs_root, &make_services())
.execute(node, &context, &graph, run_dir, &make_services())
.await
.unwrap();
assert_eq!(outcome.status, crate::outcome::StageStatus::Success);
@ -377,10 +377,10 @@ mod tests {
graph.nodes.insert("gate".to_string(), gate);
let node = graph.nodes.get("gate").unwrap();
let context = Context::new();
let logs_root = Path::new("/tmp/test");
let run_dir = Path::new("/tmp/test");
let outcome = handler
.execute(node, &context, &graph, logs_root, &make_services())
.execute(node, &context, &graph, run_dir, &make_services())
.await
.unwrap();
assert_eq!(outcome.status, crate::outcome::StageStatus::Fail);
@ -409,10 +409,10 @@ mod tests {
let node = graph.nodes.get("gate").unwrap();
let context = Context::new();
let logs_root = Path::new("/tmp/test");
let run_dir = Path::new("/tmp/test");
let outcome = handler
.execute(node, &context, &graph, logs_root, &make_services())
.execute(node, &context, &graph, run_dir, &make_services())
.await
.unwrap();
assert_eq!(outcome.status, crate::outcome::StageStatus::Success);

View file

@ -86,7 +86,7 @@ impl Handler for SubWorkflowHandler {
node: &Node,
context: &Context,
_graph: &Graph,
logs_root: &Path,
run_dir: &Path,
services: &EngineServices,
) -> Result<Outcome, ArcError> {
let poll_interval = node
@ -127,7 +127,7 @@ impl Handler for SubWorkflowHandler {
// Build child RunConfig
let visit = context.node_visit_count() as u64;
let child_logs = logs_root.join(format!("nodes/{}_{visit}/child", node.id));
let child_logs = run_dir.join(format!("nodes/{}_{visit}/child", node.id));
let _ = std::fs::create_dir_all(&child_logs);
let parent_run_id = context.run_id();
@ -136,7 +136,7 @@ impl Handler for SubWorkflowHandler {
let git_state = services.git_state();
let child_config = RunConfig {
logs_root: child_logs,
run_dir: child_logs,
cancel_token: Some(cancel_token),
dry_run: false,
run_id: format!("{parent_run_id}_child_{}", node.id),
@ -375,7 +375,7 @@ mod tests {
_node: &Node,
context: &Context,
_graph: &Graph,
_logs_root: &Path,
_run_dir: &Path,
_services: &EngineServices,
) -> Result<Outcome, ArcError> {
let target = context.get_string("review.target", "");
@ -491,7 +491,7 @@ mod tests {
_node: &Node,
_context: &Context,
_graph: &Graph,
_logs_root: &Path,
_run_dir: &Path,
_services: &EngineServices,
) -> Result<Outcome, ArcError> {
tokio::time::sleep(Duration::from_secs(10)).await;
@ -552,7 +552,7 @@ mod tests {
_node: &Node,
_context: &Context,
_graph: &Graph,
_logs_root: &Path,
_run_dir: &Path,
_services: &EngineServices,
) -> Result<Outcome, ArcError> {
tokio::time::sleep(Duration::from_secs(10)).await;
@ -712,7 +712,7 @@ mod tests {
_node: &Node,
context: &Context,
_graph: &Graph,
_logs_root: &Path,
_run_dir: &Path,
_services: &EngineServices,
) -> Result<Outcome, ArcError> {
let target = context.get_string("review.target", "");
@ -798,7 +798,7 @@ mod tests {
_node: &Node,
context: &Context,
_graph: &Graph,
_logs_root: &Path,
_run_dir: &Path,
_services: &EngineServices,
) -> Result<Outcome, ArcError> {
let parent_preamble = context.get_string(keys::INTERNAL_PARENT_PREAMBLE, "");

View file

@ -60,7 +60,7 @@ pub trait Handler: Send + Sync {
node: &Node,
context: &Context,
graph: &Graph,
logs_root: &Path,
run_dir: &Path,
services: &EngineServices,
) -> Result<Outcome, ArcError>;
@ -174,7 +174,7 @@ mod tests {
_node: &Node,
_context: &Context,
_graph: &Graph,
_logs_root: &Path,
_run_dir: &Path,
_services: &EngineServices,
) -> Result<Outcome, ArcError> {
Ok(Outcome::success())
@ -251,7 +251,7 @@ mod tests {
_node: &Node,
_context: &Context,
_graph: &Graph,
_logs_root: &Path,
_run_dir: &Path,
_services: &EngineServices,
) -> Result<Outcome, ArcError> {
Ok(Outcome::success())

View file

@ -210,7 +210,7 @@ impl Handler for ParallelHandler {
node: &Node,
context: &Context,
graph: &Graph,
logs_root: &Path,
run_dir: &Path,
services: &EngineServices,
) -> Result<Outcome, ArcError> {
let parallel_start = Instant::now();
@ -323,7 +323,7 @@ impl Handler for ParallelHandler {
match &gs.mode {
GitCheckpointMode::Host(work_dir) => {
let wt_path = logs_root
let wt_path = run_dir
.join("parallel")
.join(&node.id)
.join(branch_key)
@ -352,7 +352,7 @@ impl Handler for ParallelHandler {
}
GitCheckpointMode::Remote(_) => {
let wt_path_str = format!(
"{}/.arc/logs/{}/parallel/{}/{}",
"{}/.arc/runs/{}/parallel/{}/{}",
services.sandbox.working_directory(),
gs.run_id,
node.id,
@ -422,7 +422,7 @@ impl Handler for ParallelHandler {
let hook_runner = services.hook_runner.clone();
let env = services.env.clone();
let graph = graph.clone();
let logs_root = logs_root.to_path_buf();
let run_dir = run_dir.to_path_buf();
let sem = Arc::clone(&semaphore);
let has_git = git_state.is_some();
let run_id = git_state.as_ref().map(|gs| gs.run_id.clone());
@ -476,7 +476,7 @@ impl Handler for ParallelHandler {
target_node,
&setup.branch_context,
&graph,
&logs_root,
&run_dir,
&branch_services,
)
.await?;
@ -676,7 +676,7 @@ impl Handler for ParallelHandler {
context.set(keys::PARALLEL_BRANCH_COUNT, serde_json::json!(total));
let visit = crate::engine::visit_from_context(context);
let node_dir = crate::engine::node_dir(logs_root, &node.id, visit);
let node_dir = crate::engine::node_dir(run_dir, &node.id, visit);
let _ = tokio::fs::create_dir_all(&node_dir).await;
if let Ok(json) = serde_json::to_string_pretty(&results_json) {
let _ = tokio::fs::write(node_dir.join("parallel_results.json"), json).await;
@ -812,10 +812,10 @@ mod tests {
let node = Node::new("par");
let context = Context::new();
let graph = Graph::new("test");
let logs_root = Path::new("/tmp/test");
let run_dir = Path::new("/tmp/test");
let outcome = ParallelHandler
.execute(&node, &context, &graph, logs_root, &services)
.execute(&node, &context, &graph, run_dir, &services)
.await
.unwrap();
assert_eq!(outcome.status, StageStatus::Fail);
@ -889,9 +889,9 @@ mod tests {
.insert("branch_a".to_string(), Node::new("branch_a"));
graph.edges.push(Edge::new("par", "branch_a"));
let logs_root = Path::new("/tmp/test");
let run_dir = Path::new("/tmp/test");
let outcome = ParallelHandler
.execute(&node, &context, &graph, logs_root, &services)
.execute(&node, &context, &graph, run_dir, &services)
.await
.unwrap();
@ -922,9 +922,9 @@ mod tests {
graph.edges.push(Edge::new("par", "branch_b"));
graph.edges.push(Edge::new("par", "branch_c"));
let logs_root = Path::new("/tmp/test");
let run_dir = Path::new("/tmp/test");
let outcome = ParallelHandler
.execute(&node, &context, &graph, logs_root, &services)
.execute(&node, &context, &graph, run_dir, &services)
.await
.unwrap();

View file

@ -34,7 +34,7 @@ impl Handler for PromptHandler {
node: &Node,
context: &Context,
graph: &Graph,
logs_root: &Path,
run_dir: &Path,
services: &EngineServices,
) -> Result<Outcome, ArcError> {
// 1. Build prompt (prepend fidelity preamble if present)
@ -76,7 +76,7 @@ impl Handler for PromptHandler {
// 2. Write prompt to logs
let visit = crate::engine::visit_from_context(context);
let stage_dir = crate::engine::node_dir(logs_root, &node.id, visit);
let stage_dir = crate::engine::node_dir(run_dir, &node.id, visit);
tokio::fs::create_dir_all(&stage_dir).await?;
tokio::fs::write(stage_dir.join("prompt.md"), &prompt).await?;

View file

@ -19,7 +19,7 @@ impl Handler for StartHandler {
_node: &Node,
_context: &Context,
_graph: &Graph,
_logs_root: &Path,
_run_dir: &Path,
_services: &EngineServices,
) -> Result<Outcome, ArcError> {
Ok(Outcome::success())
@ -51,9 +51,9 @@ mod tests {
let node = Node::new("start");
let context = Context::new();
let graph = Graph::new("test");
let logs_root = Path::new("/tmp/test");
let run_dir = Path::new("/tmp/test");
let outcome = handler
.execute(&node, &context, &graph, logs_root, &make_services())
.execute(&node, &context, &graph, run_dir, &make_services())
.await
.unwrap();
assert_eq!(outcome.status, crate::outcome::StageStatus::Success);

View file

@ -19,7 +19,7 @@ impl Handler for WaitHandler {
node: &Node,
_context: &Context,
_graph: &Graph,
_logs_root: &Path,
_run_dir: &Path,
_services: &EngineServices,
) -> Result<Outcome, ArcError> {
let duration = node
@ -70,9 +70,9 @@ mod tests {
);
let context = Context::new();
let graph = Graph::new("test");
let logs_root = Path::new("/tmp/test");
let run_dir = Path::new("/tmp/test");
let outcome = handler
.execute(&node, &context, &graph, logs_root, &make_services())
.execute(&node, &context, &graph, run_dir, &make_services())
.await
.unwrap();
assert_eq!(outcome.status, crate::outcome::StageStatus::Success);
@ -84,9 +84,9 @@ mod tests {
let node = Node::new("wait_no_dur");
let context = Context::new();
let graph = Graph::new("test");
let logs_root = Path::new("/tmp/test");
let run_dir = Path::new("/tmp/test");
let result = handler
.execute(&node, &context, &graph, logs_root, &make_services())
.execute(&node, &context, &graph, run_dir, &make_services())
.await;
assert!(result.is_err());
}

View file

@ -189,9 +189,9 @@ fn parse_dot_summary(dot: &str) -> (String, usize, usize) {
}
}
/// Read the DOT graph source from `logs_dir/graph.dot`.
fn read_dot_source(logs_dir: &Path) -> Option<String> {
let path = logs_dir.join("graph.dot");
/// Read the DOT graph source from `run_dir/graph.dot`.
fn read_dot_source(run_dir: &Path) -> Option<String> {
let path = run_dir.join("graph.dot");
match std::fs::read_to_string(&path) {
Ok(content) => {
debug!(path = %path.display(), "Read DOT graph for PR body");
@ -201,11 +201,11 @@ fn read_dot_source(logs_dir: &Path) -> Option<String> {
}
}
/// Read plan text from the first `nodes/plan*/response.md` found in logs_dir.
/// Read plan text from the first `nodes/plan*/response.md` found in run_dir.
///
/// Entries are sorted alphabetically so `plan` is preferred over `planning`.
fn read_plan_text(logs_dir: &Path) -> Option<String> {
let nodes_dir = logs_dir.join("nodes");
fn read_plan_text(run_dir: &Path) -> Option<String> {
let nodes_dir = run_dir.join("nodes");
let mut entries: Vec<_> = std::fs::read_dir(&nodes_dir).ok()?.flatten().collect();
entries.sort_by_key(|e| e.file_name());
for entry in entries {
@ -264,13 +264,13 @@ pub async fn build_pr_body(
diff: &str,
goal: &str,
model: &str,
logs_dir: &Path,
run_dir: &Path,
) -> Result<String, String> {
debug!("Building PR body");
let plan_text = read_plan_text(logs_dir);
let retro = Retro::load(logs_dir).ok();
let dot_source = read_dot_source(logs_dir);
let plan_text = read_plan_text(run_dir);
let retro = Retro::load(run_dir).ok();
let dot_source = read_dot_source(run_dir);
// Build LLM prompt
let system = if plan_text.is_some() {
@ -342,7 +342,7 @@ pub async fn maybe_open_pull_request(
diff: &str,
model: &str,
draft: bool,
logs_dir: &Path,
run_dir: &Path,
) -> Result<Option<PullRequestRecord>, String> {
if diff.is_empty() {
debug!("Empty diff, skipping pull request creation");
@ -352,7 +352,7 @@ pub async fn maybe_open_pull_request(
let https_url = ssh_url_to_https(origin_url);
let (owner, repo) = github_app::parse_github_owner_repo(&https_url)?;
let body = build_pr_body(diff, goal, model, logs_dir).await?;
let body = build_pr_body(diff, goal, model, run_dir).await?;
let body = truncate_pr_body(&body);
let title = pr_title_from_goal(goal);

View file

@ -168,21 +168,21 @@ impl Retro {
};
}
/// Save the retro as JSON to `logs_root/retro.json`.
pub fn save(&self, logs_root: &Path) -> Result<()> {
crate::save_json(self, &logs_root.join("retro.json"), "retro")
/// Save the retro as JSON to `run_dir/retro.json`.
pub fn save(&self, run_dir: &Path) -> Result<()> {
crate::save_json(self, &run_dir.join("retro.json"), "retro")
}
/// Load a retro from `logs_root/retro.json`.
pub fn load(logs_root: &Path) -> Result<Self> {
crate::load_json(&logs_root.join("retro.json"), "retro")
/// Load a retro from `run_dir/retro.json`.
pub fn load(run_dir: &Path) -> Result<Self> {
crate::load_json(&run_dir.join("retro.json"), "retro")
}
}
/// Extract stage durations from `progress.jsonl` by reading `StageCompleted` events.
pub fn extract_stage_durations(logs_root: &Path) -> HashMap<String, u64> {
pub fn extract_stage_durations(run_dir: &Path) -> HashMap<String, u64> {
let mut durations = HashMap::new();
let jsonl_path = logs_root.join("progress.jsonl");
let jsonl_path = run_dir.join("progress.jsonl");
let Ok(data) = std::fs::read_to_string(&jsonl_path) else {
return durations;
};

View file

@ -114,7 +114,7 @@ const SUBMIT_RETRO_SCHEMA: &str = r#"{
/// files via tool access, then calls `submit_retro` with its analysis.
pub async fn run_retro_agent(
sandbox: &Arc<dyn Sandbox>,
logs_root: &Path,
run_dir: &Path,
llm_client: &Client,
provider: Provider,
model: &str,
@ -122,7 +122,7 @@ pub async fn run_retro_agent(
// Upload data files into sandbox (needed for Daytona; no-op effect for local
// since the agent can also read from the original paths via tools).
let retro_data_dir = "/tmp/retro_data";
upload_data_files(sandbox, logs_root, retro_data_dir).await?;
upload_data_files(sandbox, run_dir, retro_data_dir).await?;
// Build provider profile with the submit_retro tool
let captured: Arc<Mutex<Option<RetroNarrative>>> = Arc::new(Mutex::new(None));
@ -165,7 +165,7 @@ pub async fn run_retro_agent(
let mut session = Session::new(llm_client.clone(), profile, Arc::clone(sandbox), config);
// Set up event writer before initialize (which emits SessionStarted)
let retro_dir = logs_root.join("retro");
let retro_dir = run_dir.join("retro");
std::fs::create_dir_all(&retro_dir)?;
let rx = session.subscribe();
let event_writer_handle = spawn_retro_event_writer(rx, retro_dir.join("retro_session.jsonl"));
@ -316,7 +316,7 @@ fn build_profile(provider: Provider, model: &str) -> Box<dyn ProviderProfile> {
async fn upload_data_files(
sandbox: &Arc<dyn Sandbox>,
logs_root: &Path,
run_dir: &Path,
target_dir: &str,
) -> anyhow::Result<()> {
// Create target directory
@ -327,7 +327,7 @@ async fn upload_data_files(
let files = ["progress.jsonl", "checkpoint.json", "manifest.json"];
for filename in &files {
let source = logs_root.join(filename);
let source = run_dir.join(filename);
if source.exists() {
let content = std::fs::read_to_string(&source)?;
sandbox

View file

@ -331,7 +331,7 @@ impl Handler for LargeOutputHandler {
node: &Node,
_context: &Context,
_graph: &Graph,
_logs_root: &Path,
_run_dir: &Path,
_services: &arc_workflows::handler::EngineServices,
) -> Result<Outcome, ArcError> {
let mut outcome = Outcome::success();
@ -389,7 +389,7 @@ async fn daytona_pipeline_artifact_offload_and_sync() {
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), env.clone());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "test-run".into(),
@ -464,7 +464,7 @@ impl Handler for FileWriterHandler {
node: &Node,
_context: &Context,
_graph: &Graph,
_logs_root: &Path,
_run_dir: &Path,
services: &arc_workflows::handler::EngineServices,
) -> Result<Outcome, ArcError> {
let content = format!("output from {}", node.id);
@ -587,7 +587,7 @@ async fn daytona_git_checkpoint_remote_emits_events() {
let engine = WorkflowRunEngine::new(registry, Arc::new(emitter), env.clone());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id,
@ -659,10 +659,7 @@ async fn daytona_git_checkpoint_remote_emits_events() {
// Assert final.patch exists and contains changes from the run
let final_patch = dir.path().join("final.patch");
assert!(
final_patch.exists(),
"final.patch should exist in logs_root"
);
assert!(final_patch.exists(), "final.patch should exist in run_dir");
let patch_content = std::fs::read_to_string(&final_patch).unwrap();
assert!(!patch_content.is_empty(), "final.patch should not be empty");
@ -758,7 +755,7 @@ async fn daytona_parallel_git_branching_e2e() {
graph.edges.push(Edge::new("branch_b", "fan_in"));
graph.edges.push(Edge::new("fan_in", "exit"));
let logs_dir = tempfile::tempdir().unwrap();
let run_tmp = tempfile::tempdir().unwrap();
let mut emitter = EventEmitter::new();
let events = Arc::new(std::sync::Mutex::new(Vec::new()));
{
@ -777,11 +774,11 @@ async fn daytona_parallel_git_branching_e2e() {
let engine = WorkflowRunEngine::new(registry, Arc::new(emitter), Arc::clone(&env));
let config = RunConfig {
logs_root: logs_dir.path().to_path_buf(),
run_dir: run_tmp.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: run_id.clone(),
git_checkpoint: Some(GitCheckpointMode::Remote(logs_dir.path().to_path_buf())),
git_checkpoint: Some(GitCheckpointMode::Remote(run_tmp.path().to_path_buf())),
base_sha: Some(base_sha),
run_branch: Some(branch_name),
meta_branch: None,
@ -808,7 +805,7 @@ async fn daytona_parallel_git_branching_e2e() {
// Verify parallel.results has head_sha for each branch
let checkpoint =
Checkpoint::load(&logs_dir.path().join("checkpoint.json")).expect("checkpoint should load");
Checkpoint::load(&run_tmp.path().join("checkpoint.json")).expect("checkpoint should load");
let parallel_results = checkpoint
.context_values
.get("parallel.results")
@ -1155,7 +1152,7 @@ async fn daytona_git_checkpoint_with_shadow_branch() {
let meta_branch = MetadataStore::branch_name(&run_id);
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), env.clone());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: run_id.clone(),
@ -1210,10 +1207,7 @@ async fn daytona_git_checkpoint_with_shadow_branch() {
// Assert final.patch exists
let final_patch = dir.path().join("final.patch");
assert!(
final_patch.exists(),
"final.patch should exist in logs_root"
);
assert!(final_patch.exists(), "final.patch should exist in run_dir");
env.cleanup().await.unwrap();
}
@ -1232,7 +1226,7 @@ impl Handler for AssetCreatorHandler {
_node: &Node,
_context: &Context,
_graph: &Graph,
_logs_root: &Path,
_run_dir: &Path,
services: &arc_workflows::handler::EngineServices,
) -> Result<Outcome, ArcError> {
let script = concat!(
@ -1299,7 +1293,7 @@ async fn daytona_asset_collection() {
graph.edges.push(Edge::new("create_assets", "exit"));
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: "asset-test-daytona".into(),
@ -1555,7 +1549,7 @@ async fn daytona_git_push_run_branch_to_origin() {
let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), env.clone());
let config = RunConfig {
logs_root: dir.path().to_path_buf(),
run_dir: dir.path().to_path_buf(),
cancel_token: None,
dry_run: false,
run_id: run_id.clone(),

File diff suppressed because it is too large Load diff