Add [execution] section to TOML task config for execution environment

Allow specifying execution environment (local/docker/daytona) in TOML
task configs so pipelines are self-contained. CLI flag takes precedence
over TOML, which takes precedence over the default (local).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 600b7720bbda
This commit is contained in:
Bryan Helmkamp 2026-02-26 18:53:51 -05:00
parent 8d593ff68e
commit 2253259ec5
4 changed files with 39 additions and 8 deletions

View file

@ -105,8 +105,8 @@ pub struct RunArgs {
pub verbose: u8,
/// Execution environment for agent tools
#[arg(long, value_enum, default_value_t)]
pub execution_env: ExecutionEnvKind,
#[arg(long, value_enum)]
pub execution_env: Option<ExecutionEnvKind>,
}
#[derive(Args)]
@ -139,8 +139,8 @@ pub struct ServeArgs {
pub dry_run: bool,
/// Execution environment for agent tools
#[arg(long, value_enum, default_value_t)]
pub execution_env: ExecutionEnvKind,
#[arg(long, value_enum)]
pub execution_env: Option<ExecutionEnvKind>,
}
/// Read a .dot file from disk.

View file

@ -18,7 +18,7 @@ use crate::validation::Severity;
use super::backend::AgentBackend;
use super::task_config;
use super::{compute_stage_cost, format_cost, format_duration_human, format_event_detail, format_event_summary, format_tokens_human, print_diagnostics, read_dot_file, RunArgs};
use super::{compute_stage_cost, format_cost, format_duration_human, format_event_detail, format_event_summary, format_tokens_human, print_diagnostics, read_dot_file, ExecutionEnvKind, RunArgs};
/// Accumulates token usage and cost across all pipeline stages.
#[derive(Default)]
@ -218,7 +218,17 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu
Arc::new(ConsoleInterviewer::new(styles))
};
// 5. Resolve backend, model, and provider
// 5. Resolve execution environment: CLI flag > TOML > default
let toml_execution_env = task_cfg
.as_ref()
.and_then(|c| c.execution.as_ref())
.and_then(|e| e.environment.as_deref())
.map(|s| s.parse::<ExecutionEnvKind>())
.transpose()
.map_err(|e| anyhow::anyhow!("Invalid execution environment in TOML: {e}"))?;
let execution_env = args.execution_env.or(toml_execution_env).unwrap_or_default();
// 6. Resolve backend, model, and provider
let dry_run_mode = if args.dry_run {
true
} else {
@ -294,7 +304,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu
provider.clone(),
args.verbose,
styles,
args.execution_env,
execution_env,
setup_commands.clone(),
)))
}

View file

@ -66,7 +66,7 @@ pub async fn serve_command(args: ServeArgs, styles: &'static Styles) -> anyhow::
provider.clone(),
0,
styles,
args.execution_env,
args.execution_env.unwrap_or_default(),
Vec::new(),
)))
}

View file

@ -14,6 +14,7 @@ pub struct TaskConfig {
pub directory: Option<String>,
pub llm: Option<LlmConfig>,
pub setup: Option<SetupConfig>,
pub execution: Option<ExecutionConfig>,
pub vars: Option<HashMap<String, String>>,
}
@ -29,6 +30,11 @@ pub struct SetupConfig {
pub timeout_ms: Option<u64>,
}
#[derive(Debug, Deserialize)]
pub struct ExecutionConfig {
pub environment: Option<String>,
}
/// Load and validate a task config from a TOML file.
///
/// The `graph` path in the returned config is resolved relative to the
@ -201,6 +207,21 @@ language = "python"
assert_eq!(expand_vars("costs $5", &vars).unwrap(), "costs $5");
}
#[test]
fn parse_toml_with_execution() {
let toml = r#"
version = 1
task = "Run tests"
graph = "pipeline.dot"
[execution]
environment = "daytona"
"#;
let config = parse_task_config(toml).unwrap();
let execution = config.execution.unwrap();
assert_eq!(execution.environment.as_deref(), Some("daytona"));
}
#[test]
fn parse_minimal_toml() {
let toml = r#"