Rename TaskConfig → WorkflowRunConfig, task → goal

Better reflects domain semantics: the config describes a workflow run,
and the free-text field is the run's goal, not a generic "task".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-03-03 00:44:24 -05:00
parent 62b4e9df74
commit c888ab8be5
7 changed files with 67 additions and 67 deletions

View file

@ -97,7 +97,7 @@ export default function RunConfiguration({ loaderData }: Route.ComponentProps) {
<div className="min-w-0 flex-1">
{configText ? (
<CollapsibleFile
file={{ name: "task.toml", contents: configText, lang: "toml" }}
file={{ name: "run.toml", contents: configText, lang: "toml" }}
/>
) : (
<p className="text-sm text-fg-muted">No configuration found.</p>

View file

@ -27,7 +27,7 @@ export default function WorkflowDefinition() {
return (
<div className="flex flex-col gap-6">
<CollapsibleFile
file={{ name: "task.toml", contents: workflow.config, lang: "toml" }}
file={{ name: "run.toml", contents: workflow.config, lang: "toml" }}
defaultOpen={false}
/>
{dotReady && (

View file

@ -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]

View file

@ -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;

View file

@ -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<String>) {
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<String> = task_cfg
let setup_commands: Vec<String> = 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<task_config::TaskConfig>,
run_cfg: &Option<run_config::WorkflowRunConfig>,
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()),
}),

View file

@ -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<String>,
pub llm: Option<LlmConfig>,
@ -42,14 +42,14 @@ pub struct SandboxConfig {
pub daytona: Option<DaytonaConfig>,
}
/// 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<TaskConfig> {
pub fn load_run_config(path: &Path) -> anyhow::Result<WorkflowRunConfig> {
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<TaskConfig> {
let config: TaskConfig =
toml::from_str(contents).context("Failed to parse task config TOML")?;
fn parse_run_config(contents: &str) -> anyhow::Result<WorkflowRunConfig> {
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]

View file

@ -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]