From 1dcad0529abbc95aae0da287d9e2209e23bca49c Mon Sep 17 00:00:00 2001 From: Bryan Helmkamp Date: Tue, 10 Mar 2026 09:40:03 -0400 Subject: [PATCH] Split ~/.arc/logs/ into logs/ and runs/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- docs/agents/outputs.mdx | 18 +- docs/agents/prompts.mdx | 2 +- docs/execution/checkpoints.mdx | 8 +- docs/execution/observability.mdx | 16 +- docs/execution/retros.mdx | 6 +- docs/reference/cli.mdx | 4 +- docs/reference/logs-directory.mdx | 17 +- lib/crates/arc-api/src/server.rs | 26 +- lib/crates/arc-cli/tests/cli.rs | 10 +- .../002_rename_logs_dir_to_run_dir.sql | 1 + lib/crates/arc-db/src/lib.rs | 8 +- lib/crates/arc-db/src/migrate.rs | 7 +- lib/crates/arc-db/src/workflow_run.rs | 2 +- lib/crates/arc-workflows/README.md | 4 +- .../arc-workflows/src/asset_snapshot.rs | 6 +- lib/crates/arc-workflows/src/cli/cp.rs | 4 +- lib/crates/arc-workflows/src/cli/mod.rs | 4 +- lib/crates/arc-workflows/src/cli/pr.rs | 4 +- lib/crates/arc-workflows/src/cli/progress.rs | 8 +- lib/crates/arc-workflows/src/cli/run.rs | 90 ++--- lib/crates/arc-workflows/src/cli/runs.rs | 37 +- lib/crates/arc-workflows/src/engine.rs | 146 ++++---- lib/crates/arc-workflows/src/handler/agent.rs | 4 +- .../arc-workflows/src/handler/command.rs | 106 +++--- .../arc-workflows/src/handler/conditional.rs | 6 +- lib/crates/arc-workflows/src/handler/exit.rs | 6 +- .../arc-workflows/src/handler/fan_in.rs | 32 +- lib/crates/arc-workflows/src/handler/human.rs | 14 +- .../arc-workflows/src/handler/manager_loop.rs | 16 +- lib/crates/arc-workflows/src/handler/mod.rs | 6 +- .../arc-workflows/src/handler/parallel.rs | 24 +- .../arc-workflows/src/handler/prompt.rs | 4 +- lib/crates/arc-workflows/src/handler/start.rs | 6 +- lib/crates/arc-workflows/src/handler/wait.rs | 10 +- lib/crates/arc-workflows/src/pull_request.rs | 24 +- lib/crates/arc-workflows/src/retro.rs | 16 +- lib/crates/arc-workflows/src/retro_agent.rs | 10 +- .../tests/daytona_integration.rs | 34 +- lib/crates/arc-workflows/tests/integration.rs | 334 +++++++++--------- 39 files changed, 541 insertions(+), 539 deletions(-) create mode 100644 lib/crates/arc-db/migrations/002_rename_logs_dir_to_run_dir.sql diff --git a/docs/agents/outputs.mdx b/docs/agents/outputs.mdx index da7aacfb1..4304844ba 100644 --- a/docs/agents/outputs.mdx +++ b/docs/agents/outputs.mdx @@ -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` | diff --git a/docs/agents/prompts.mdx b/docs/agents/prompts.mdx index 872c0747b..ef7734a59 100644 --- a/docs/agents/prompts.mdx +++ b/docs/agents/prompts.mdx @@ -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. diff --git a/docs/execution/checkpoints.mdx b/docs/execution/checkpoints.mdx index 9d555ec5a..ee7cd8716 100644 --- a/docs/execution/checkpoints.mdx +++ b/docs/execution/checkpoints.mdx @@ -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 diff --git a/docs/execution/observability.mdx b/docs/execution/observability.mdx index 1aa431d12..173543e80 100644 --- a/docs/execution/observability.mdx +++ b/docs/execution/observability.mdx @@ -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 | |---|---| diff --git a/docs/execution/retros.mdx b/docs/execution/retros.mdx index c31629a3a..4803e928f 100644 --- a/docs/execution/retros.mdx +++ b/docs/execution/retros.mdx @@ -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. diff --git a/docs/reference/cli.mdx b/docs/reference/cli.mdx index 63ef2fd83..a13796b4d 100644 --- a/docs/reference/cli.mdx +++ b/docs/reference/cli.mdx @@ -48,7 +48,7 @@ arc run --run-branch arc/run/abc123 | Argument / Flag | Description | |---|---| | `` | Path to a `.dot` workflow file or `.toml` task config. Not required when using `--run-branch`. | -| `--logs-dir ` | Log and artifact directory | +| `--run-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 diff --git a/docs/reference/logs-directory.mdx b/docs/reference/logs-directory.mdx index bd1142ea7..a10c6153a 100644 --- a/docs/reference/logs-directory.mdx +++ b/docs/reference/logs-directory.mdx @@ -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 diff --git a/lib/crates/arc-api/src/server.rs b/lib/crates/arc-api/src/server.rs index 62ef45ad9..1fba47294 100644 --- a/lib/crates/arc-api/src/server.rs +++ b/lib/crates/arc-api/src/server.rs @@ -76,7 +76,7 @@ struct ManagedRun { checkpoint: Option, cancel_tx: Option>, cancel_token: Option>, - logs_root: Option, + run_dir: Option, } /// 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, 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, 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, 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, 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, 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>, Path(id): Path, ) -> 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(), } diff --git a/lib/crates/arc-cli/tests/cli.rs b/lib/crates/arc-cli/tests/cli.rs index 29c5b7698..c9b45f561 100644 --- a/lib/crates/arc-cli/tests/cli.rs +++ b/lib/crates/arc-cli/tests/cli.rs @@ -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(); diff --git a/lib/crates/arc-db/migrations/002_rename_logs_dir_to_run_dir.sql b/lib/crates/arc-db/migrations/002_rename_logs_dir_to_run_dir.sql new file mode 100644 index 000000000..29c7c7e03 --- /dev/null +++ b/lib/crates/arc-db/migrations/002_rename_logs_dir_to_run_dir.sql @@ -0,0 +1 @@ +ALTER TABLE workflow_runs RENAME COLUMN logs_dir TO run_dir; diff --git a/lib/crates/arc-db/src/lib.rs b/lib/crates/arc-db/src/lib.rs index ed60358d3..382dfbaf7 100644 --- a/lib/crates/arc-db/src/lib.rs +++ b/lib/crates/arc-db/src/lib.rs @@ -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"); } } diff --git a/lib/crates/arc-db/src/migrate.rs b/lib/crates/arc-db/src/migrate.rs index ee392edf5..d7420e326 100644 --- a/lib/crates/arc-db/src/migrate.rs +++ b/lib/crates/arc-db/src/migrate.rs @@ -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?; diff --git a/lib/crates/arc-db/src/workflow_run.rs b/lib/crates/arc-db/src/workflow_run.rs index 349f2dffd..712ed9497 100644 --- a/lib/crates/arc-db/src/workflow_run.rs +++ b/lib/crates/arc-db/src/workflow_run.rs @@ -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, pub updated_at: DateTime, diff --git a/lib/crates/arc-workflows/README.md b/lib/crates/arc-workflows/README.md index cceb846f1..9e4d83b59 100644 --- a/lib/crates/arc-workflows/README.md +++ b/lib/crates/arc-workflows/README.md @@ -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 { // Custom logic here Ok(Outcome::success()) diff --git a/lib/crates/arc-workflows/src/asset_snapshot.rs b/lib/crates/arc-workflows/src/asset_snapshot.rs index 50d915ad2..622fa8983 100644 --- a/lib/crates/arc-workflows/src/asset_snapshot.rs +++ b/lib/crates/arc-workflows/src/asset_snapshot.rs @@ -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 { - let assets_dir = logs_dir.join("artifacts/assets"); +pub fn collect_asset_paths(run_dir: &Path) -> Vec { + let assets_dir = run_dir.join("artifacts/assets"); let Ok(nodes) = std::fs::read_dir(&assets_dir) else { return Vec::new(); }; diff --git a/lib/crates/arc-workflows/src/cli/cp.rs b/lib/crates/arc-workflows/src/cli/cp.rs index 360547782..30ae5b37d 100644 --- a/lib/crates/arc-workflows/src/cli/cp.rs +++ b/lib/crates/arc-workflows/src/cli/cp.rs @@ -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 { diff --git a/lib/crates/arc-workflows/src/cli/mod.rs b/lib/crates/arc-workflows/src/cli/mod.rs index c493fda94..489c525e3 100644 --- a/lib/crates/arc-workflows/src/cli/mod.rs +++ b/lib/crates/arc-workflows/src/cli/mod.rs @@ -99,9 +99,9 @@ pub struct RunArgs { #[arg(required_unless_present = "run_branch")] pub workflow: Option, - /// Log/artifact directory + /// Run output directory #[arg(long)] - pub logs_dir: Option, + pub run_dir: Option, /// Execute with simulated LLM backend #[arg(long)] diff --git a/lib/crates/arc-workflows/src/cli/pr.rs b/lib/crates/arc-workflows/src/cli/pr.rs index ff37a5d64..b125ccdf1 100644 --- a/lib/crates/arc-workflows/src/cli/pr.rs +++ b/lib/crates/arc-workflows/src/cli/pr.rs @@ -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, ) -> Result<()> { - let base = default_logs_base(); + let base = default_runs_base(); pr_create_from(&base, args, github_app).await } diff --git a/lib/crates/arc-workflows/src/cli/progress.rs b/lib/crates/arc-workflows/src/cli/progress.rs index 8c2582c9b..7ae80940f 100644 --- a/lib/crates/arc-workflows/src/cli/progress.rs +++ b/lib/crates/arc-workflows/src/cli/progress.rs @@ -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}"); } } } diff --git a/lib/crates/arc-workflows/src/cli/run.rs b/lib/crates/arc-workflows/src/cli/run.rs index f5dcb36d6..3cd3b4c2e 100644 --- a/lib/crates/arc-workflows/src/cli/run.rs +++ b/lib/crates/arc-workflows/src/cli/run.rs @@ -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"), diff --git a/lib/crates/arc-workflows/src/cli/runs.rs b/lib/crates/arc-workflows/src/cli/runs.rs index efa8c01a7..8cb16657a 100644 --- a/lib/crates/arc-workflows/src/cli/runs.rs +++ b/lib/crates/arc-workflows/src/cli/runs.rs @@ -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 { } 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] diff --git a/lib/crates/arc-workflows/src/engine.rs b/lib/crates/arc-workflows/src/engine.rs index a95a7cd28..78a600297 100644 --- a/lib/crates/arc-workflows/src/engine.rs +++ b/lib/crates/arc-workflows/src/engine.rs @@ -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>, 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 { 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 { 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 { 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 { 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 { 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 { 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(), diff --git a/lib/crates/arc-workflows/src/handler/agent.rs b/lib/crates/arc-workflows/src/handler/agent.rs index a219edd6d..0c0344567 100644 --- a/lib/crates/arc-workflows/src/handler/agent.rs +++ b/lib/crates/arc-workflows/src/handler/agent.rs @@ -202,7 +202,7 @@ impl Handler for AgentHandler { node: &Node, context: &Context, graph: &Graph, - logs_root: &Path, + run_dir: &Path, services: &EngineServices, ) -> Result { // 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?; diff --git a/lib/crates/arc-workflows/src/handler/command.rs b/lib/crates/arc-workflows/src/handler/command.rs index c31e03ef0..66e43b028 100644 --- a/lib/crates/arc-workflows/src/handler/command.rs +++ b/lib/crates/arc-workflows/src/handler/command.rs @@ -32,7 +32,7 @@ impl Handler for CommandHandler { node: &Node, context: &Context, _graph: &Graph, - logs_root: &Path, + run_dir: &Path, services: &EngineServices, ) -> Result { 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); diff --git a/lib/crates/arc-workflows/src/handler/conditional.rs b/lib/crates/arc-workflows/src/handler/conditional.rs index 7610651d6..c1a7b7380 100644 --- a/lib/crates/arc-workflows/src/handler/conditional.rs +++ b/lib/crates/arc-workflows/src/handler/conditional.rs @@ -20,7 +20,7 @@ impl Handler for ConditionalHandler { node: &Node, _context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &EngineServices, ) -> Result { 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); diff --git a/lib/crates/arc-workflows/src/handler/exit.rs b/lib/crates/arc-workflows/src/handler/exit.rs index ab36f4365..ef6b050f7 100644 --- a/lib/crates/arc-workflows/src/handler/exit.rs +++ b/lib/crates/arc-workflows/src/handler/exit.rs @@ -19,7 +19,7 @@ impl Handler for ExitHandler { _node: &Node, _context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &EngineServices, ) -> Result { 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); diff --git a/lib/crates/arc-workflows/src/handler/fan_in.rs b/lib/crates/arc-workflows/src/handler/fan_in.rs index 1708e4e1f..26da1132a 100644 --- a/lib/crates/arc-workflows/src/handler/fan_in.rs +++ b/lib/crates/arc-workflows/src/handler/fan_in.rs @@ -33,7 +33,7 @@ impl Handler for FanInHandler { node: &Node, context: &Context, _graph: &Graph, - logs_root: &Path, + run_dir: &Path, services: &EngineServices, ) -> Result { 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, sandbox: &Arc, @@ -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); diff --git a/lib/crates/arc-workflows/src/handler/human.rs b/lib/crates/arc-workflows/src/handler/human.rs index c6915174a..21d4e11bd 100644 --- a/lib/crates/arc-workflows/src/handler/human.rs +++ b/lib/crates/arc-workflows/src/handler/human.rs @@ -101,7 +101,7 @@ impl Handler for HumanHandler { node: &Node, _context: &Context, graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &EngineServices, ) -> Result { // 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); diff --git a/lib/crates/arc-workflows/src/handler/manager_loop.rs b/lib/crates/arc-workflows/src/handler/manager_loop.rs index c1917bf52..5f843b466 100644 --- a/lib/crates/arc-workflows/src/handler/manager_loop.rs +++ b/lib/crates/arc-workflows/src/handler/manager_loop.rs @@ -86,7 +86,7 @@ impl Handler for SubWorkflowHandler { node: &Node, context: &Context, _graph: &Graph, - logs_root: &Path, + run_dir: &Path, services: &EngineServices, ) -> Result { 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 { 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 { 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 { 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 { 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 { let parent_preamble = context.get_string(keys::INTERNAL_PARENT_PREAMBLE, ""); diff --git a/lib/crates/arc-workflows/src/handler/mod.rs b/lib/crates/arc-workflows/src/handler/mod.rs index 2d2d647fe..6bea0b062 100644 --- a/lib/crates/arc-workflows/src/handler/mod.rs +++ b/lib/crates/arc-workflows/src/handler/mod.rs @@ -60,7 +60,7 @@ pub trait Handler: Send + Sync { node: &Node, context: &Context, graph: &Graph, - logs_root: &Path, + run_dir: &Path, services: &EngineServices, ) -> Result; @@ -174,7 +174,7 @@ mod tests { _node: &Node, _context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &EngineServices, ) -> Result { Ok(Outcome::success()) @@ -251,7 +251,7 @@ mod tests { _node: &Node, _context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &EngineServices, ) -> Result { Ok(Outcome::success()) diff --git a/lib/crates/arc-workflows/src/handler/parallel.rs b/lib/crates/arc-workflows/src/handler/parallel.rs index 23191d34c..366948d56 100644 --- a/lib/crates/arc-workflows/src/handler/parallel.rs +++ b/lib/crates/arc-workflows/src/handler/parallel.rs @@ -210,7 +210,7 @@ impl Handler for ParallelHandler { node: &Node, context: &Context, graph: &Graph, - logs_root: &Path, + run_dir: &Path, services: &EngineServices, ) -> Result { 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(); diff --git a/lib/crates/arc-workflows/src/handler/prompt.rs b/lib/crates/arc-workflows/src/handler/prompt.rs index 7131b03d4..5e5a0adf3 100644 --- a/lib/crates/arc-workflows/src/handler/prompt.rs +++ b/lib/crates/arc-workflows/src/handler/prompt.rs @@ -34,7 +34,7 @@ impl Handler for PromptHandler { node: &Node, context: &Context, graph: &Graph, - logs_root: &Path, + run_dir: &Path, services: &EngineServices, ) -> Result { // 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?; diff --git a/lib/crates/arc-workflows/src/handler/start.rs b/lib/crates/arc-workflows/src/handler/start.rs index fc17235e5..81e37e788 100644 --- a/lib/crates/arc-workflows/src/handler/start.rs +++ b/lib/crates/arc-workflows/src/handler/start.rs @@ -19,7 +19,7 @@ impl Handler for StartHandler { _node: &Node, _context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &EngineServices, ) -> Result { 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); diff --git a/lib/crates/arc-workflows/src/handler/wait.rs b/lib/crates/arc-workflows/src/handler/wait.rs index f5bdf0cfa..db03e9a06 100644 --- a/lib/crates/arc-workflows/src/handler/wait.rs +++ b/lib/crates/arc-workflows/src/handler/wait.rs @@ -19,7 +19,7 @@ impl Handler for WaitHandler { node: &Node, _context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &EngineServices, ) -> Result { 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()); } diff --git a/lib/crates/arc-workflows/src/pull_request.rs b/lib/crates/arc-workflows/src/pull_request.rs index 027650e8c..298f1535f 100644 --- a/lib/crates/arc-workflows/src/pull_request.rs +++ b/lib/crates/arc-workflows/src/pull_request.rs @@ -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 { - 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 { + 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 { } } -/// 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 { - let nodes_dir = logs_dir.join("nodes"); +fn read_plan_text(run_dir: &Path) -> Option { + 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 { 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, 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); diff --git a/lib/crates/arc-workflows/src/retro.rs b/lib/crates/arc-workflows/src/retro.rs index 906a8437c..1e618406a 100644 --- a/lib/crates/arc-workflows/src/retro.rs +++ b/lib/crates/arc-workflows/src/retro.rs @@ -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 { - crate::load_json(&logs_root.join("retro.json"), "retro") + /// Load a retro from `run_dir/retro.json`. + pub fn load(run_dir: &Path) -> Result { + 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 { +pub fn extract_stage_durations(run_dir: &Path) -> HashMap { 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; }; diff --git a/lib/crates/arc-workflows/src/retro_agent.rs b/lib/crates/arc-workflows/src/retro_agent.rs index e70306470..92874b460 100644 --- a/lib/crates/arc-workflows/src/retro_agent.rs +++ b/lib/crates/arc-workflows/src/retro_agent.rs @@ -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, - 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>> = 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 { async fn upload_data_files( sandbox: &Arc, - 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 diff --git a/lib/crates/arc-workflows/tests/daytona_integration.rs b/lib/crates/arc-workflows/tests/daytona_integration.rs index f7c237f6c..cac7e9f94 100644 --- a/lib/crates/arc-workflows/tests/daytona_integration.rs +++ b/lib/crates/arc-workflows/tests/daytona_integration.rs @@ -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 { 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 { 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 { 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(), diff --git a/lib/crates/arc-workflows/tests/integration.rs b/lib/crates/arc-workflows/tests/integration.rs index 02c0f3e64..81481eac9 100644 --- a/lib/crates/arc-workflows/tests/integration.rs +++ b/lib/crates/arc-workflows/tests/integration.rs @@ -190,7 +190,7 @@ async fn end_to_end_linear_pipeline() { 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(), @@ -334,7 +334,7 @@ async fn end_to_end_branching_pipeline() { 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(), @@ -460,7 +460,7 @@ async fn end_to_end_human_gate_pipeline() { 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(), @@ -509,7 +509,7 @@ impl Handler for AlwaysFailHandler { node: &Node, _context: &arc_workflows::context::Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &arc_workflows::handler::EngineServices, ) -> Result { Ok(Outcome::fail_classify(format!( @@ -576,7 +576,7 @@ async fn goal_gate_routes_to_retry_target_on_failure() { 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(), @@ -639,7 +639,7 @@ async fn goal_gate_routes_to_retry_target_when_present() { _node: &Node, _context: &arc_workflows::context::Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &arc_workflows::handler::EngineServices, ) -> Result { let count = self @@ -702,7 +702,7 @@ async fn goal_gate_routes_to_retry_target_when_present() { 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(), @@ -959,7 +959,7 @@ async fn retry_on_failure_then_succeed() { _node: &Node, _context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &arc_workflows::handler::EngineServices, ) -> Result { let count = self @@ -1015,7 +1015,7 @@ async fn retry_on_failure_then_succeed() { 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(), @@ -1095,7 +1095,7 @@ async fn pipeline_with_many_nodes() { 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(), @@ -1225,7 +1225,7 @@ impl Handler for CounterHandler { _node: &Node, _context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &arc_workflows::handler::EngineServices, ) -> Result { let count = self @@ -1250,7 +1250,7 @@ impl Handler for LargeOutputHandler { node: &Node, _context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &arc_workflows::handler::EngineServices, ) -> Result { let mut outcome = Outcome::success(); @@ -1274,7 +1274,7 @@ impl Handler for ContextSetterHandler { _node: &Node, _context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &arc_workflows::handler::EngineServices, ) -> Result { let mut outcome = Outcome::success(); @@ -1425,7 +1425,7 @@ async fn smoke_test_with_mock_codergen_backend() { 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(), @@ -1531,7 +1531,7 @@ async fn end_to_end_parallel_fan_out_fan_in() { 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(), @@ -1649,7 +1649,7 @@ async fn resume_from_checkpoint_completes_pipeline() { 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(), @@ -1753,7 +1753,7 @@ async fn resume_from_checkpoint_preserves_goal_gate_outcomes() { 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(), @@ -1801,7 +1801,7 @@ async fn graph_goal_in_context() { 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(), @@ -1841,7 +1841,7 @@ async fn event_streaming_lifecycle() { let events = collect_events(&mut emitter); let engine = WorkflowRunEngine::new(make_linear_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(), @@ -1925,7 +1925,7 @@ async fn context_flow_between_stages() { 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(), @@ -1982,7 +1982,7 @@ async fn tool_handler_e2e() { 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(), @@ -2056,7 +2056,7 @@ async fn auto_approve_interviewer_e2e() { 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(), @@ -2097,7 +2097,7 @@ async fn codergen_without_backend_simulated() { 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(), @@ -2147,7 +2147,7 @@ async fn branching_loop_back_on_failure() { _node: &Node, _context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &arc_workflows::handler::EngineServices, ) -> Result { let count = self @@ -2206,7 +2206,7 @@ async fn branching_loop_back_on_failure() { ); 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(), @@ -2295,7 +2295,7 @@ async fn human_gate_loops_back() { registry.register("human", Box::new(HumanHandler::new(interviewer))); 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(), @@ -2357,7 +2357,7 @@ async fn scenario_ship_a_feature() { 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(), @@ -2447,7 +2447,7 @@ async fn scenario_parallel_expert_review() { 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(), @@ -2492,7 +2492,7 @@ async fn scenario_node_retries_on_retry_status() { _node: &Node, _context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &arc_workflows::handler::EngineServices, ) -> Result { let count = self @@ -2531,7 +2531,7 @@ async fn scenario_node_retries_on_retry_status() { ); 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(), @@ -2597,7 +2597,7 @@ async fn scenario_loop_restart_resets_context() { ); 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(), @@ -2669,7 +2669,7 @@ async fn scenario_bug_triage_router() { registry.register("conditional", Box::new(ConditionalHandler)); 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(), @@ -2732,7 +2732,7 @@ async fn scenario_crash_recovery() { registry.register("exit", Box::new(ExitHandler)); 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(), @@ -2774,7 +2774,7 @@ async fn manager_loop_stop_condition_satisfied_e2e() { _node: &Node, _context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &arc_workflows::handler::EngineServices, ) -> Result { let mut outcome = Outcome::success(); @@ -2794,7 +2794,7 @@ async fn manager_loop_stop_condition_satisfied_e2e() { _node: &Node, _context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &arc_workflows::handler::EngineServices, ) -> Result { tokio::time::sleep(std::time::Duration::from_secs(10)).await; @@ -2845,7 +2845,7 @@ async fn manager_loop_stop_condition_satisfied_e2e() { registry.register("stack.manager_loop", Box::new(SubWorkflowHandler)); 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(), @@ -2887,7 +2887,7 @@ async fn manager_loop_max_cycles_exceeded_e2e() { _node: &Node, _context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &arc_workflows::handler::EngineServices, ) -> Result { tokio::time::sleep(std::time::Duration::from_secs(10)).await; @@ -2926,7 +2926,7 @@ async fn manager_loop_max_cycles_exceeded_e2e() { registry.register("stack.manager_loop", Box::new(SubWorkflowHandler)); 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(), @@ -3066,7 +3066,7 @@ async fn conditional_branching_success_fail_paths() { 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(), @@ -3123,7 +3123,7 @@ async fn edge_selection_condition_match_wins_over_weight() { registry.register("exit", Box::new(ExitHandler)); 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(), @@ -3174,7 +3174,7 @@ async fn edge_selection_weight_breaks_ties() { registry.register("exit", Box::new(ExitHandler)); 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(), @@ -3217,7 +3217,7 @@ async fn edge_selection_lexical_tiebreak() { registry.register("exit", Box::new(ExitHandler)); 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(), @@ -3279,7 +3279,7 @@ async fn context_updates_visible_across_nodes() { registry.register("context_setter", Box::new(ContextSetterHandler)); 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(), @@ -3327,7 +3327,7 @@ async fn stylesheet_applies_model_override() { 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(), @@ -3359,7 +3359,7 @@ async fn custom_handler_registration_and_execution() { _node: &Node, _context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &arc_workflows::handler::EngineServices, ) -> Result { let mut outcome = Outcome::success(); @@ -3387,7 +3387,7 @@ async fn custom_handler_registration_and_execution() { registry.register("my_custom", Box::new(CustomHandler)); 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(), @@ -3462,7 +3462,7 @@ async fn integration_smoke_plan_implement_review_done() { 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(), @@ -3570,7 +3570,7 @@ async fn manager_loop_runs_child_engine_e2e() { 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(), @@ -3630,7 +3630,7 @@ async fn manager_loop_context_flows_e2e() { _node: &Node, context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &arc_workflows::handler::EngineServices, ) -> Result { let target = context.get_string("review.target", ""); @@ -3656,7 +3656,7 @@ async fn manager_loop_context_flows_e2e() { _node: &Node, _context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &arc_workflows::handler::EngineServices, ) -> Result { let mut outcome = Outcome::success(); @@ -3709,7 +3709,7 @@ async fn manager_loop_context_flows_e2e() { 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(), @@ -3787,7 +3787,7 @@ async fn manager_loop_child_dotfile_e2e() { 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(), @@ -3905,7 +3905,7 @@ async fn graph_merge_e2e_through_engine() { 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(), @@ -4004,7 +4004,7 @@ impl Handler for FidelityCapturingHandler { node: &Node, context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &arc_workflows::handler::EngineServices, ) -> Result { let fidelity = context.get_string("internal.fidelity", "none"); @@ -4060,7 +4060,7 @@ async fn fidelity_default_is_compact() { 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(), @@ -4121,7 +4121,7 @@ async fn fidelity_graph_default_applied() { 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(), @@ -4178,7 +4178,7 @@ async fn fidelity_node_overrides_graph_default() { 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(), @@ -4241,7 +4241,7 @@ async fn fidelity_edge_overrides_node_and_graph() { 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(), @@ -4294,7 +4294,7 @@ async fn fidelity_full_produces_empty_preamble() { 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(), @@ -4357,7 +4357,7 @@ async fn fidelity_truncate_preamble_minimal() { 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(), @@ -4433,7 +4433,7 @@ async fn fidelity_summary_low_mode() { 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(), @@ -4504,7 +4504,7 @@ async fn fidelity_summary_medium_mode() { 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(), @@ -4575,7 +4575,7 @@ async fn fidelity_summary_high_mode() { 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(), @@ -4639,7 +4639,7 @@ async fn fidelity_full_sets_thread_id_in_context() { 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(), @@ -4714,7 +4714,7 @@ async fn fidelity_full_nodes_share_thread_id() { 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(), @@ -4799,7 +4799,7 @@ async fn fidelity_resume_degrades_full_to_summary_high() { 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(), @@ -4900,7 +4900,7 @@ async fn fidelity_resume_degrade_only_affects_first_hop() { 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(), @@ -4988,7 +4988,7 @@ async fn fidelity_resume_no_degrade_when_not_full() { 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(), @@ -5034,7 +5034,7 @@ async fn fidelity_stored_in_checkpoint_context() { 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(), @@ -5124,7 +5124,7 @@ async fn fidelity_precedence_multi_node_pipeline() { 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(), @@ -5196,7 +5196,7 @@ async fn fidelity_compact_preamble_includes_completed_stages_and_context() { 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(), @@ -5276,7 +5276,7 @@ async fn fidelity_summary_low_excludes_context_values_in_pipeline() { let engine_low = WorkflowRunEngine::new(registry_low, Arc::new(EventEmitter::new()), local_env()); let config_low = RunConfig { - logs_root: dir_low.path().to_path_buf(), + run_dir: dir_low.path().to_path_buf(), cancel_token: None, dry_run: false, run_id: "test-run".into(), @@ -5348,7 +5348,7 @@ async fn fidelity_summary_low_excludes_context_values_in_pipeline() { let engine_med = WorkflowRunEngine::new(registry_med, Arc::new(EventEmitter::new()), local_env()); let config_med = RunConfig { - logs_root: dir_med.path().to_path_buf(), + run_dir: dir_med.path().to_path_buf(), cancel_token: None, dry_run: false, run_id: "test-run".into(), @@ -5423,7 +5423,7 @@ async fn fidelity_thread_id_fallback_to_previous_node_in_pipeline() { 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(), @@ -5481,7 +5481,7 @@ async fn fidelity_thread_id_from_node_class_in_pipeline() { 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(), @@ -5542,7 +5542,7 @@ async fn fidelity_edge_thread_id_override_in_pipeline() { 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(), @@ -5604,7 +5604,7 @@ async fn fidelity_full_without_explicit_thread_id_uses_previous_node() { 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(), @@ -5676,7 +5676,7 @@ async fn fidelity_from_parsed_dot_pipeline() { 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(), @@ -5728,7 +5728,7 @@ async fn fidelity_checkpoint_roundtrip_preserves_fidelity() { 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(), @@ -5802,7 +5802,7 @@ async fn fidelity_node_thread_id_overrides_edge_thread_id_in_pipeline() { 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(), @@ -5893,7 +5893,7 @@ async fn fidelity_resume_preserves_context_values_across_checkpoint() { 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(), @@ -6108,7 +6108,7 @@ mod real_llm { 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(), @@ -6227,7 +6227,7 @@ mod real_llm { 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(), @@ -6371,7 +6371,7 @@ mod real_llm { 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(), @@ -6483,7 +6483,7 @@ mod real_llm { 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(), @@ -6584,7 +6584,7 @@ async fn human_gate_freeform_only_routes_text() { 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(), @@ -6720,7 +6720,7 @@ async fn human_gate_freeform_with_fixed_choice_match() { 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(), @@ -6840,7 +6840,7 @@ async fn human_gate_freeform_fallback_on_unmatched_text() { 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(), @@ -6974,7 +6974,7 @@ async fn human_gate_freeform_sets_allow_freeform_on_question() { 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(), @@ -7088,7 +7088,7 @@ async fn human_gate_without_freeform_sets_allow_freeform_false() { 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(), @@ -7353,7 +7353,7 @@ fn engine_with_hooks_and_events( fn make_run_config(dir: &std::path::Path) -> RunConfig { RunConfig { - logs_root: dir.to_path_buf(), + run_dir: dir.to_path_buf(), cancel_token: None, dry_run: false, run_id: "hook-test-run".into(), @@ -8491,10 +8491,10 @@ async fn arc_e2e_with_real_llm() { as Box) }); - let logs_dir = tempfile::tempdir().unwrap(); + let run_dir = tempfile::tempdir().unwrap(); let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { - logs_root: logs_dir.path().to_path_buf(), + run_dir: run_dir.path().to_path_buf(), cancel_token: None, dry_run: false, run_id: "test-run".into(), @@ -8521,7 +8521,7 @@ async fn arc_e2e_with_real_llm() { assert_eq!(outcome.status, StageStatus::Success); // 2. Artifacts exist - let work_dir = logs_dir.path().join("nodes").join("work"); + let work_dir = run_dir.path().join("nodes").join("work"); assert!( work_dir.join("prompt.md").exists(), "prompt.md should exist" @@ -8537,7 +8537,7 @@ async fn arc_e2e_with_real_llm() { // 3. Goal gate: check checkpoint node outcomes let checkpoint = - Checkpoint::load(&logs_dir.path().join("checkpoint.json")).expect("checkpoint should load"); + Checkpoint::load(&run_dir.path().join("checkpoint.json")).expect("checkpoint should load"); let work_outcome = checkpoint .node_outcomes .get("work") @@ -8627,7 +8627,7 @@ async fn run_fidelity_prompt_pipeline(fidelity: &str) -> String { 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(), @@ -8831,7 +8831,7 @@ async fn large_context_values_are_offloaded_to_artifact_store() { let events = collect_events(&mut emitter); let engine = WorkflowRunEngine::new(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(), @@ -9056,7 +9056,7 @@ async fn artifact_pointers_rewritten_for_remote_sandbox() { let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), remote_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(), @@ -9124,7 +9124,7 @@ async fn node_dir_uses_visit_count_on_revisit() { _node: &Node, _context: &arc_workflows::context::Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &arc_workflows::handler::EngineServices, ) -> Result { let n = self @@ -9191,7 +9191,7 @@ async fn node_dir_uses_visit_count_on_revisit() { 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(), @@ -10156,7 +10156,7 @@ async fn full_pipeline_with_cli_backend_node() { let dir = tempfile::tempdir().unwrap(); let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), 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(), @@ -10290,7 +10290,7 @@ async fn stylesheet_backend_property_routes_to_cli() { let dir = tempfile::tempdir().unwrap(); let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), 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(), @@ -10473,7 +10473,7 @@ impl Handler for FileWriterHandler { node: &Node, _context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, services: &arc_workflows::handler::EngineServices, ) -> Result { let work_dir = services.sandbox.working_directory().to_string(); @@ -10565,7 +10565,7 @@ async fn git_checkpoint_host_emits_events_and_diff_patch() { graph.edges.push(Edge::new("work", "exit")); // 4. Set up event collection and engine - let logs_dir = tempfile::tempdir().unwrap(); + let run_dir = tempfile::tempdir().unwrap(); let mut emitter = EventEmitter::new(); let events = collect_events(&mut emitter); @@ -10577,7 +10577,7 @@ async fn git_checkpoint_host_emits_events_and_diff_patch() { let engine = WorkflowRunEngine::new(registry, Arc::new(emitter), env); let config = RunConfig { - logs_root: logs_dir.path().to_path_buf(), + run_dir: run_dir.path().to_path_buf(), cancel_token: None, dry_run: false, run_id: "test-docker".into(), @@ -10638,7 +10638,7 @@ async fn git_checkpoint_host_emits_events_and_diff_patch() { ); // 7. diff.patch is NOT written for the start node (git checkpoint skipped) - let start_diff = logs_dir + let start_diff = run_dir .path() .join("nodes") .join("start") @@ -10650,18 +10650,15 @@ async fn git_checkpoint_host_emits_events_and_diff_patch() { // 8. Verify checkpoint.json has git_commit_sha let checkpoint = - Checkpoint::load(&logs_dir.path().join("checkpoint.json")).expect("checkpoint should load"); + Checkpoint::load(&run_dir.path().join("checkpoint.json")).expect("checkpoint should load"); assert!( checkpoint.git_commit_sha.is_some(), "checkpoint should have git_commit_sha" ); // 9. Assert final.patch exists and contains the changes - let final_patch = logs_dir.path().join("final.patch"); - assert!( - final_patch.exists(), - "final.patch should exist in logs_root" - ); + let final_patch = run_dir.path().join("final.patch"); + 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.contains("hello.txt"), @@ -10757,9 +10754,9 @@ async fn git_checkpoint_host_writes_shadow_branch() { graph.edges.push(Edge::new("work", "exit")); // 4. Set up engine with meta_branch - let logs_dir = tempfile::tempdir().unwrap(); + let run_dir = tempfile::tempdir().unwrap(); // Write graph.dot so init_run can read it - std::fs::write(logs_dir.path().join("graph.dot"), "digraph {}").unwrap(); + std::fs::write(run_dir.path().join("graph.dot"), "digraph {}").unwrap(); let emitter = EventEmitter::new(); let env: Arc = @@ -10771,7 +10768,7 @@ async fn git_checkpoint_host_writes_shadow_branch() { let meta_branch = MetadataStore::branch_name(run_id); let config = RunConfig { - logs_root: logs_dir.path().to_path_buf(), + run_dir: run_dir.path().to_path_buf(), cancel_token: None, dry_run: false, run_id: run_id.into(), @@ -10951,7 +10948,7 @@ async fn parallel_git_branching_host_e2e() { graph.edges.push(Edge::new("fan_in", "exit")); // 4. Set up engine with FileWriterHandler for branches - let logs_dir = tempfile::tempdir().unwrap(); + let run_dir = tempfile::tempdir().unwrap(); let mut emitter = EventEmitter::new(); let events = collect_events(&mut emitter); @@ -10970,7 +10967,7 @@ async fn parallel_git_branching_host_e2e() { let engine = WorkflowRunEngine::new(registry, Arc::new(emitter), env); let config = RunConfig { - logs_root: logs_dir.path().to_path_buf(), + run_dir: run_dir.path().to_path_buf(), cancel_token: None, dry_run: false, run_id: run_id.into(), @@ -11002,7 +10999,7 @@ async fn parallel_git_branching_host_e2e() { // 6. 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_dir.path().join("checkpoint.json")).expect("checkpoint should load"); let parallel_results = checkpoint .context_values .get("parallel.results") @@ -11108,11 +11105,8 @@ async fn parallel_git_branching_host_e2e() { ); // 11. Verify final.patch contains the winner's changes - let final_patch = logs_dir.path().join("final.patch"); - assert!( - final_patch.exists(), - "final.patch should exist in logs_root" - ); + let final_patch = run_dir.path().join("final.patch"); + 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.contains(&format!("{best_id}.txt")), @@ -11225,7 +11219,7 @@ async fn git_checkpoint_host_skips_empty_diff_patch() { graph.edges.push(Edge::new("start", "work")); graph.edges.push(Edge::new("work", "exit")); - let logs_dir = tempfile::tempdir().unwrap(); + let run_dir = tempfile::tempdir().unwrap(); let mut emitter = EventEmitter::new(); let _events = collect_events(&mut emitter); @@ -11237,7 +11231,7 @@ async fn git_checkpoint_host_skips_empty_diff_patch() { let engine = WorkflowRunEngine::new(registry, Arc::new(emitter), env); let config = RunConfig { - logs_root: logs_dir.path().to_path_buf(), + run_dir: run_dir.path().to_path_buf(), cancel_token: None, dry_run: false, run_id: "empty-diff".into(), @@ -11262,18 +11256,14 @@ async fn git_checkpoint_host_skips_empty_diff_patch() { assert_eq!(outcome.status, StageStatus::Success); // diff.patch should NOT exist for the "work" node (no file changes) - let work_diff = logs_dir - .path() - .join("nodes") - .join("work") - .join("diff.patch"); + let work_diff = run_dir.path().join("nodes").join("work").join("diff.patch"); assert!( !work_diff.exists(), "diff.patch should not exist when there are no changes" ); // final.patch should NOT exist either - let final_patch = logs_dir.path().join("final.patch"); + let final_patch = run_dir.path().join("final.patch"); assert!( !final_patch.exists(), "final.patch should not exist when there are no changes" @@ -11311,7 +11301,7 @@ impl Handler for DeterministicFailHandler { _node: &Node, _context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &arc_workflows::handler::EngineServices, ) -> Result { Ok(Outcome::fail_classify(&self.reason)) @@ -11328,7 +11318,7 @@ impl Handler for TransientInfraFailHandler { _node: &Node, _context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &arc_workflows::handler::EngineServices, ) -> Result { Ok(Outcome::fail_classify("connection refused")) @@ -11345,7 +11335,7 @@ impl Handler for SignatureHintHandler { _node: &Node, _context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &arc_workflows::handler::EngineServices, ) -> Result { Ok( @@ -11380,7 +11370,7 @@ impl Handler for VaryingReasonFailHandler { _node: &Node, _context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &arc_workflows::handler::EngineServices, ) -> Result { let n = self @@ -11405,7 +11395,7 @@ impl Handler for SucceedOnNthHandler { _node: &Node, _context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &arc_workflows::handler::EngineServices, ) -> Result { let n = self @@ -11624,7 +11614,7 @@ async fn e2e_circuit_breaker_deterministic_self_loop() { 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: "e2e-circuit-breaker".into(), @@ -11676,7 +11666,7 @@ async fn e2e_circuit_breaker_custom_limit() { 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: "e2e-custom-limit".into(), @@ -11721,7 +11711,7 @@ async fn e2e_circuit_breaker_ignores_transient_failures() { 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: "e2e-transient-no-breaker".into(), @@ -11773,7 +11763,7 @@ async fn e2e_circuit_breaker_different_reasons_separate_counters() { 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: "e2e-varying-reasons".into(), @@ -11818,7 +11808,7 @@ async fn e2e_circuit_breaker_loop_restart() { 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: "e2e-restart-breaker".into(), @@ -11885,7 +11875,7 @@ async fn e2e_failure_signature_persisted_in_context() { 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: "e2e-sig-context".into(), @@ -11954,7 +11944,7 @@ async fn e2e_failure_signature_hint_overrides_reason_in_context() { 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: "e2e-sig-hint".into(), @@ -12015,7 +12005,7 @@ async fn e2e_signature_maps_persist_in_checkpoint() { 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: "e2e-sig-persist".into(), @@ -12147,7 +12137,7 @@ async fn e2e_circuit_breaker_emits_events_before_abort() { let engine = WorkflowRunEngine::new(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: "e2e-events".into(), @@ -12219,7 +12209,7 @@ async fn e2e_circuit_breaker_does_not_fire_below_limit() { 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: "e2e-below-limit".into(), @@ -12320,7 +12310,7 @@ async fn e2e_circuit_breaker_multi_stage_impl_verify_cycle() { 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: "e2e-impl-verify-cycle".into(), @@ -12388,7 +12378,7 @@ impl Handler for ClassifiedFailHandler { _node: &Node, _context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &arc_workflows::handler::EngineServices, ) -> Result { let n = self @@ -12421,7 +12411,7 @@ async fn e2e_loop_restart_blocked_for_deterministic_failure() { 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: "e2e-restart-blocked-det".into(), @@ -12466,7 +12456,7 @@ async fn e2e_loop_restart_blocked_for_structural_failure() { 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: "e2e-restart-blocked-struct".into(), @@ -12511,7 +12501,7 @@ async fn e2e_loop_restart_blocked_for_budget_exhausted_failure() { 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: "e2e-restart-blocked-budget".into(), @@ -12556,7 +12546,7 @@ async fn e2e_loop_restart_blocked_for_canceled_failure() { 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: "e2e-restart-blocked-canceled".into(), @@ -12598,7 +12588,7 @@ async fn e2e_loop_restart_blocked_for_compilation_loop_failure() { 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: "e2e-restart-blocked-comploop".into(), @@ -12644,7 +12634,7 @@ async fn e2e_loop_restart_allowed_for_transient_infra() { 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: "e2e-restart-allowed-transient".into(), @@ -12684,7 +12674,7 @@ impl Handler for HangingHandler { _node: &Node, _context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &arc_workflows::handler::EngineServices, ) -> Result { tokio::time::sleep(std::time::Duration::from_secs(60)).await; @@ -12705,7 +12695,7 @@ impl Handler for KeepaliveHandler { node: &Node, _context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, services: &arc_workflows::handler::EngineServices, ) -> Result { let start = std::time::Instant::now(); @@ -12753,7 +12743,7 @@ async fn e2e_stall_watchdog_triggers_from_dot_parsed_pipeline() { let engine = WorkflowRunEngine::new(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: "stall-e2e".into(), @@ -12814,7 +12804,7 @@ async fn e2e_stall_watchdog_kept_alive_by_handler_events() { 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: "stall-alive-e2e".into(), @@ -12865,7 +12855,7 @@ async fn e2e_stall_watchdog_disabled_with_zero_timeout() { 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: "stall-disabled-e2e".into(), @@ -12902,7 +12892,7 @@ impl Handler for SlowTestHandler { _node: &Node, _context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, _services: &arc_workflows::handler::EngineServices, ) -> Result { tokio::time::sleep(std::time::Duration::from_millis(self.sleep_ms)).await; @@ -12935,7 +12925,7 @@ async fn e2e_stall_watchdog_with_explicit_timeout_override() { 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: "stall-override-e2e".into(), @@ -12995,7 +12985,7 @@ impl Handler for AssetCreatorHandler { _node: &Node, _context: &Context, _graph: &Graph, - _logs_root: &Path, + _run_dir: &Path, services: &arc_workflows::handler::EngineServices, ) -> Result { // Create asset files via the sandbox's exec_command @@ -13022,7 +13012,7 @@ impl Handler for AssetCreatorHandler { #[tokio::test] async fn asset_collection_local_sandbox_success() { let work_dir = tempfile::tempdir().unwrap(); - let logs_dir = tempfile::tempdir().unwrap(); + let run_dir = tempfile::tempdir().unwrap(); let sandbox: Arc = Arc::new(arc_agent::LocalSandbox::new(work_dir.path().to_path_buf())); @@ -13070,7 +13060,7 @@ async fn asset_collection_local_sandbox_success() { graph.edges.push(Edge::new("create_assets", "exit")); let config = RunConfig { - logs_root: logs_dir.path().to_path_buf(), + run_dir: run_dir.path().to_path_buf(), cancel_token: None, dry_run: false, run_id: "asset-test-local".into(), @@ -13095,7 +13085,7 @@ async fn asset_collection_local_sandbox_success() { assert_eq!(outcome.status, StageStatus::Success); // Check that asset files were collected into the stage directory - let assets_dir = logs_dir + let assets_dir = run_dir .path() .join("artifacts") .join("assets") @@ -13138,7 +13128,7 @@ async fn asset_collection_local_sandbox_success() { #[tokio::test] async fn asset_collection_local_sandbox_on_failure() { let work_dir = tempfile::tempdir().unwrap(); - let logs_dir = tempfile::tempdir().unwrap(); + let run_dir = tempfile::tempdir().unwrap(); let sandbox: Arc = Arc::new(arc_agent::LocalSandbox::new(work_dir.path().to_path_buf())); @@ -13183,7 +13173,7 @@ async fn asset_collection_local_sandbox_on_failure() { graph.edges.push(Edge::new("create_assets", "exit")); let config = RunConfig { - logs_root: logs_dir.path().to_path_buf(), + run_dir: run_dir.path().to_path_buf(), cancel_token: None, dry_run: false, run_id: "asset-test-fail".into(), @@ -13208,7 +13198,7 @@ async fn asset_collection_local_sandbox_on_failure() { // The pipeline completes (handler returned Fail, not an error), but assets should still be collected assert_eq!(outcome.status, StageStatus::Fail); - let assets_dir = logs_dir + let assets_dir = run_dir .path() .join("artifacts") .join("assets") @@ -13229,7 +13219,7 @@ async fn asset_collection_local_sandbox_on_failure() { #[ignore] async fn asset_collection_docker_sandbox() { let host_dir = tempfile::tempdir().unwrap(); - let logs_dir = tempfile::tempdir().unwrap(); + let run_dir = tempfile::tempdir().unwrap(); let config = arc_agent::DockerSandboxConfig { host_working_directory: host_dir.path().to_str().unwrap().to_string(), @@ -13279,7 +13269,7 @@ async fn asset_collection_docker_sandbox() { graph.edges.push(Edge::new("create_assets", "exit")); let run_config = RunConfig { - logs_root: logs_dir.path().to_path_buf(), + run_dir: run_dir.path().to_path_buf(), cancel_token: None, dry_run: false, run_id: "asset-test-docker".into(), @@ -13303,7 +13293,7 @@ async fn asset_collection_docker_sandbox() { .expect("pipeline should succeed"); assert_eq!(outcome.status, StageStatus::Success); - let assets_dir = logs_dir + let assets_dir = run_dir .path() .join("artifacts") .join("assets") @@ -13353,7 +13343,7 @@ async fn wait_timer_e2e() { 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(),