diff --git a/apps/arc-web/app/routes/run-configuration.tsx b/apps/arc-web/app/routes/run-configuration.tsx index f765e668b..1813b1603 100644 --- a/apps/arc-web/app/routes/run-configuration.tsx +++ b/apps/arc-web/app/routes/run-configuration.tsx @@ -97,7 +97,7 @@ export default function RunConfiguration({ loaderData }: Route.ComponentProps) {
{configText ? ( ) : (

No configuration found.

diff --git a/apps/arc-web/app/routes/workflow-definition.tsx b/apps/arc-web/app/routes/workflow-definition.tsx index d5f885ec4..927b3a299 100644 --- a/apps/arc-web/app/routes/workflow-definition.tsx +++ b/apps/arc-web/app/routes/workflow-definition.tsx @@ -27,7 +27,7 @@ export default function WorkflowDefinition() { return (
{dotReady && ( diff --git a/crates/arc-api/src/demo/mod.rs b/crates/arc-api/src/demo/mod.rs index 29fba1156..469fc6c71 100644 --- a/crates/arc-api/src/demo/mod.rs +++ b/crates/arc-api/src/demo/mod.rs @@ -867,7 +867,7 @@ mod workflows { title: "Fix Build".into(), slug: "fix_build".into(), filename: "fix_build.dot".into(), description: "Automatically diagnoses and fixes CI build failures by analyzing error logs, identifying root causes, and applying targeted code changes.".into(), config: r#"version = 1 -task = "Diagnose and fix CI build failures" +goal = "Diagnose and fix CI build failures" graph = "fix_build.dot" [llm] @@ -917,7 +917,7 @@ disk = 10 title: "Implement Feature".into(), slug: "implement".into(), filename: "implement.dot".into(), description: "Generates production-ready code from a technical blueprint, including tests, documentation, and a pull request ready for review.".into(), config: r#"version = 1 -task = "Implement feature from technical blueprint" +goal = "Implement feature from technical blueprint" graph = "implement.dot" [llm] @@ -986,7 +986,7 @@ disk = 20 title: "Sync Drift".into(), slug: "sync_drift".into(), filename: "sync_drift.dot".into(), description: "Detects configuration and code drift between environments, then generates reconciliation patches to bring everything back in sync.".into(), config: r#"version = 1 -task = "Detect and reconcile configuration drift across environments" +goal = "Detect and reconcile configuration drift across environments" graph = "sync_drift.dot" [llm] @@ -1042,7 +1042,7 @@ disk = 10 title: "Expand Product".into(), slug: "expand".into(), filename: "expand.dot".into(), description: "Evolves the product by analyzing usage patterns and specifications to propose and implement incremental improvements.".into(), config: r#"version = 1 -task = "Propose and implement incremental product improvements" +goal = "Propose and implement incremental product improvements" graph = "expand.dot" [llm] diff --git a/crates/arc-workflows/src/cli/mod.rs b/crates/arc-workflows/src/cli/mod.rs index 55edce9e3..50991e336 100644 --- a/crates/arc-workflows/src/cli/mod.rs +++ b/crates/arc-workflows/src/cli/mod.rs @@ -2,7 +2,7 @@ pub mod backend; pub mod cli_backend; pub mod run; pub mod runs; -pub mod task_config; +pub mod run_config; pub mod validate; use std::path::Path; diff --git a/crates/arc-workflows/src/cli/run.rs b/crates/arc-workflows/src/cli/run.rs index b0d7b7fc7..0c4fc2451 100644 --- a/crates/arc-workflows/src/cli/run.rs +++ b/crates/arc-workflows/src/cli/run.rs @@ -25,8 +25,8 @@ use arc_llm::provider::Provider; use super::backend::AgentApiBackend; use super::cli_backend::{BackendRouter, AgentCliBackend}; -use super::task_config; -use super::task_config::TaskConfig; +use super::run_config; +use super::run_config::WorkflowRunConfig; use super::{ compute_stage_cost, format_cost, format_duration_human, format_event_summary, format_tokens_human, print_diagnostics, read_dot_file, @@ -52,13 +52,13 @@ fn default_model_for_provider(provider: Provider) -> &'static str { fn resolve_model_provider( cli_model: Option<&str>, cli_provider: Option<&str>, - task_cfg: Option<&TaskConfig>, + run_cfg: Option<&WorkflowRunConfig>, graph: &crate::graph::types::Graph, ) -> (String, Option) { - let toml_model = task_cfg + let toml_model = run_cfg .and_then(|c| c.llm.as_ref()) .and_then(|l| l.model.as_deref()); - let toml_provider = task_cfg + let toml_provider = run_cfg .and_then(|c| c.llm.as_ref()) .and_then(|l| l.provider.as_deref()); @@ -125,16 +125,16 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu .as_ref() .ok_or_else(|| anyhow::anyhow!("--workflow is required unless --run-branch is provided"))?; - // 0. Load task config if TOML, resolve DOT path, run setup - let (dot_path, task_cfg) = if workflow_path.extension().is_some_and(|ext| ext == "toml") { - let cfg = task_config::load_task_config(workflow_path)?; - let dot = task_config::resolve_graph_path(workflow_path, &cfg.graph); + // 0. Load run config if TOML, resolve DOT path, run setup + let (dot_path, run_cfg) = if workflow_path.extension().is_some_and(|ext| ext == "toml") { + let cfg = run_config::load_run_config(workflow_path)?; + let dot = run_config::resolve_graph_path(workflow_path, &cfg.graph); (dot, Some(cfg)) } else { (workflow_path.clone(), None) }; - if let Some(ref cfg) = task_cfg { + if let Some(ref cfg) = run_cfg { if let Some(ref dir) = cfg.directory { std::env::set_current_dir(dir) .map_err(|e| anyhow::anyhow!("Failed to set working directory to {dir}: {e}"))?; @@ -142,7 +142,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu } // Collect setup commands — they'll be run inside the sandbox - let setup_commands: Vec = task_cfg + let setup_commands: Vec = run_cfg .as_ref() .and_then(|c| c.setup.as_ref()) .map(|s| s.commands.clone()) @@ -150,8 +150,8 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu // 1. Parse and validate workflow let source = read_dot_file(&dot_path)?; - let source = match task_cfg.as_ref().and_then(|c| c.vars.as_ref()) { - Some(vars) => task_config::expand_vars(&source, vars)?, + let source = match run_cfg.as_ref().and_then(|c| c.vars.as_ref()) { + Some(vars) => run_config::expand_vars(&source, vars)?, None => source, }; let (graph, diagnostics) = WorkflowBuilder::new().prepare(&source)?; @@ -184,7 +184,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu // 2. Pre-flight: check git cleanliness before creating any files // (must happen before logs dir is created, which may be inside the repo) let sandbox_provider_preview = { - let toml_exec = task_cfg + let toml_exec = run_cfg .as_ref() .and_then(|c| c.sandbox.as_ref()) .and_then(|e| e.provider.as_deref()) @@ -203,7 +203,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu }; if args.preflight { - return run_preflight(&graph, &task_cfg, &args, git_clean, sandbox_provider_preview, styles).await; + return run_preflight(&graph, &run_cfg, &args, git_clean, sandbox_provider_preview, styles).await; } // 3. Create logs directory @@ -219,7 +219,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu tokio::fs::write(logs_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("task.toml"), toml_contents).await?; + tokio::fs::write(logs_dir.join("run.toml"), toml_contents).await?; } } @@ -368,7 +368,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu }; // 5. Resolve sandbox: CLI flag > TOML > default - let toml_sandbox = task_cfg + let toml_sandbox = run_cfg .as_ref() .and_then(|c| c.sandbox.as_ref()) .and_then(|e| e.provider.as_deref()) @@ -400,7 +400,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu }; let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); - let daytona_config = task_cfg + let daytona_config = run_cfg .as_ref() .and_then(|c| c.sandbox.as_ref()) .and_then(|e| e.daytona.clone()); @@ -554,7 +554,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu let (model, provider) = resolve_model_provider( args.model.as_deref(), args.provider.as_deref(), - task_cfg.as_ref(), + run_cfg.as_ref(), &graph, ); @@ -1045,7 +1045,7 @@ async fn run_from_branch( /// a structured report. async fn run_preflight( graph: &crate::graph::types::Graph, - task_cfg: &Option, + run_cfg: &Option, args: &RunArgs, git_clean: bool, sandbox_provider: SandboxProvider, @@ -1055,7 +1055,7 @@ async fn run_preflight( // 1. Sandbox boot check let original_cwd = std::env::current_dir()?; - let daytona_config = task_cfg + let daytona_config = run_cfg .as_ref() .and_then(|c| c.sandbox.as_ref()) .and_then(|e| e.daytona.clone()); @@ -1124,7 +1124,7 @@ async fn run_preflight( let (model, provider) = resolve_model_provider( args.model.as_deref(), args.provider.as_deref(), - task_cfg.as_ref(), + run_cfg.as_ref(), graph, ); @@ -1142,7 +1142,7 @@ async fn run_preflight( }; // 5. Count setup commands for display - let setup_command_count = task_cfg + let setup_command_count = run_cfg .as_ref() .and_then(|c| c.setup.as_ref()) .map_or(0, |s| s.commands.len()); @@ -1337,12 +1337,12 @@ mod tests { #[test] fn resolve_model_provider_cli_overrides_toml() { let graph = crate::graph::types::Graph::new("test"); - let cfg = task_config::TaskConfig { + let cfg = run_config::WorkflowRunConfig { version: 1, - task: "test".to_string(), + goal: "test".to_string(), graph: "test.dot".to_string(), directory: None, - llm: Some(task_config::LlmConfig { + llm: Some(run_config::LlmConfig { model: Some("toml-model".to_string()), provider: Some("openai".to_string()), }), @@ -1367,12 +1367,12 @@ mod tests { graph.attrs.insert("default_model".to_string(), AttrValue::String("graph-model".to_string())); graph.attrs.insert("default_provider".to_string(), AttrValue::String("gemini".to_string())); - let cfg = task_config::TaskConfig { + let cfg = run_config::WorkflowRunConfig { version: 1, - task: "test".to_string(), + goal: "test".to_string(), graph: "test.dot".to_string(), directory: None, - llm: Some(task_config::LlmConfig { + llm: Some(run_config::LlmConfig { model: Some("toml-model".to_string()), provider: Some("openai".to_string()), }), diff --git a/crates/arc-workflows/src/cli/task_config.rs b/crates/arc-workflows/src/cli/run_config.rs similarity index 88% rename from crates/arc-workflows/src/cli/task_config.rs rename to crates/arc-workflows/src/cli/run_config.rs index b24901695..4d793ba69 100644 --- a/crates/arc-workflows/src/cli/task_config.rs +++ b/crates/arc-workflows/src/cli/run_config.rs @@ -10,9 +10,9 @@ const SUPPORTED_VERSION: u32 = 1; #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] -pub struct TaskConfig { +pub struct WorkflowRunConfig { pub version: u32, - pub task: String, + pub goal: String, pub graph: String, pub directory: Option, pub llm: Option, @@ -42,14 +42,14 @@ pub struct SandboxConfig { pub daytona: Option, } -/// Load and validate a task config from a TOML file. +/// Load and validate a run config from a TOML file. /// /// The `graph` path in the returned config is resolved relative to the /// TOML file's parent directory. -pub fn load_task_config(path: &Path) -> anyhow::Result { +pub fn load_run_config(path: &Path) -> anyhow::Result { let contents = std::fs::read_to_string(path) .with_context(|| format!("Failed to read {}", path.display()))?; - let config = parse_task_config(&contents)?; + let config = parse_run_config(&contents)?; Ok(config) } @@ -67,13 +67,13 @@ pub fn resolve_graph_path(toml_path: &Path, graph: &str) -> PathBuf { } } -fn parse_task_config(contents: &str) -> anyhow::Result { - let config: TaskConfig = - toml::from_str(contents).context("Failed to parse task config TOML")?; +fn parse_run_config(contents: &str) -> anyhow::Result { + let config: WorkflowRunConfig = + toml::from_str(contents).context("Failed to parse run config TOML")?; if config.version != SUPPORTED_VERSION { bail!( - "Unsupported task config version {}. Only version {SUPPORTED_VERSION} is supported.", + "Unsupported run config version {}. Only version {SUPPORTED_VERSION} is supported.", config.version ); } @@ -163,14 +163,14 @@ mod tests { fn parse_toml_with_vars() { let toml = r#" version = 1 -task = "Run tests" +goal = "Run tests" graph = "workflow.dot" [vars] repo_url = "https://github.com/org/repo" language = "python" "#; - let config = parse_task_config(toml).unwrap(); + let config = parse_run_config(toml).unwrap(); let vars = config.vars.unwrap(); assert_eq!(vars["repo_url"], "https://github.com/org/repo"); assert_eq!(vars["language"], "python"); @@ -223,13 +223,13 @@ language = "python" fn parse_toml_with_sandbox() { let toml = r#" version = 1 -task = "Run tests" +goal = "Run tests" graph = "workflow.dot" [sandbox] provider = "daytona" "#; - let config = parse_task_config(toml).unwrap(); + let config = parse_run_config(toml).unwrap(); let sandbox = config.sandbox.unwrap(); assert_eq!(sandbox.provider.as_deref(), Some("daytona")); assert!(sandbox.daytona.is_none()); @@ -239,7 +239,7 @@ provider = "daytona" fn parse_toml_with_daytona_config() { let toml = r#" version = 1 -task = "Run tests" +goal = "Run tests" graph = "workflow.dot" [sandbox] @@ -258,7 +258,7 @@ memory = 8 disk = 10 dockerfile = "FROM rust:1.85-slim-bookworm\nRUN apt-get update" "#; - let config = parse_task_config(toml).unwrap(); + let config = parse_run_config(toml).unwrap(); let sandbox = config.sandbox.unwrap(); assert_eq!(sandbox.provider.as_deref(), Some("daytona")); @@ -282,7 +282,7 @@ dockerfile = "FROM rust:1.85-slim-bookworm\nRUN apt-get update" fn parse_toml_with_daytona_no_snapshot() { let toml = r#" version = 1 -task = "Run tests" +goal = "Run tests" graph = "workflow.dot" [sandbox] @@ -291,7 +291,7 @@ provider = "daytona" [sandbox.daytona] auto_stop_interval = 30 "#; - let config = parse_task_config(toml).unwrap(); + let config = parse_run_config(toml).unwrap(); let daytona = config.sandbox.unwrap().daytona.unwrap(); assert_eq!(daytona.auto_stop_interval, Some(30)); assert!(daytona.snapshot.is_none()); @@ -301,12 +301,12 @@ auto_stop_interval = 30 fn parse_minimal_toml() { let toml = r#" version = 1 -task = "Run tests" +goal = "Run tests" graph = "workflow.dot" "#; - let config = parse_task_config(toml).unwrap(); + let config = parse_run_config(toml).unwrap(); assert_eq!(config.version, 1); - assert_eq!(config.task, "Run tests"); + assert_eq!(config.goal, "Run tests"); assert_eq!(config.graph, "workflow.dot"); assert!(config.directory.is_none()); assert!(config.llm.is_none()); @@ -317,7 +317,7 @@ graph = "workflow.dot" fn parse_full_toml() { let toml = r#" version = 1 -task = "Full workflow" +goal = "Full workflow" graph = "workflow.dot" directory = "/tmp/repo" @@ -329,8 +329,8 @@ provider = "anthropic" commands = ["pip install -r requirements.txt", "npm install"] timeout_ms = 60000 "#; - let config = parse_task_config(toml).unwrap(); - assert_eq!(config.task, "Full workflow"); + let config = parse_run_config(toml).unwrap(); + assert_eq!(config.goal, "Full workflow"); assert_eq!(config.directory.as_deref(), Some("/tmp/repo")); let llm = config.llm.unwrap(); @@ -346,13 +346,13 @@ timeout_ms = 60000 fn unsupported_version_rejected() { let toml = r#" version = 2 -task = "x" +goal = "x" graph = "p.dot" "#; - let err = parse_task_config(toml).unwrap_err(); + let err = parse_run_config(toml).unwrap_err(); assert!( err.to_string() - .contains("Unsupported task config version 2"), + .contains("Unsupported run config version 2"), "unexpected error: {err}" ); } @@ -373,17 +373,17 @@ graph = "p.dot" #[test] fn missing_required_fields() { - let no_task = r#" + let no_goal = r#" version = 1 graph = "p.dot" "#; - assert!(parse_task_config(no_task).is_err()); + assert!(parse_run_config(no_goal).is_err()); let no_graph = r#" version = 1 -task = "x" +goal = "x" "#; - assert!(parse_task_config(no_graph).is_err()); + assert!(parse_run_config(no_graph).is_err()); } #[tokio::test] diff --git a/pipelines/daytona-check/task.toml b/pipelines/daytona-check/run.toml similarity index 92% rename from pipelines/daytona-check/task.toml rename to pipelines/daytona-check/run.toml index 824c38b36..4eb017cfb 100644 --- a/pipelines/daytona-check/task.toml +++ b/pipelines/daytona-check/run.toml @@ -1,5 +1,5 @@ version = 1 -task = "Run cargo check in a Daytona cloud sandbox" +goal = "Run cargo check in a Daytona cloud sandbox" graph = "check.dot" [llm]