diff --git a/crates/arc-workflows/src/cli/backend.rs b/crates/arc-workflows/src/cli/backend.rs index b5d716c5e..78be7c34a 100644 --- a/crates/arc-workflows/src/cli/backend.rs +++ b/crates/arc-workflows/src/cli/backend.rs @@ -231,7 +231,7 @@ impl CodergenBackend for AgentApiBackend { let pipeline_emitter = Arc::clone(emitter); let mut rx = session.subscribe(); tokio::spawn(async move { - use crate::event::PipelineEvent; + use crate::event::WorkflowRunEvent; while let Ok(event) = rx.recv().await { // Track file changes from tool calls match &event.event { @@ -276,7 +276,7 @@ impl CodergenBackend for AgentApiBackend { | AgentEvent::ToolCallOutputDelta { .. } | AgentEvent::SkillExpanded { .. } ) { - pipeline_emitter.emit(&PipelineEvent::Agent { + pipeline_emitter.emit(&WorkflowRunEvent::Agent { stage: node_id.clone(), event: event.event.clone(), }); @@ -329,7 +329,7 @@ impl CodergenBackend for AgentApiBackend { }); // Emit Prompt event before processing - emitter.emit(&crate::event::PipelineEvent::Prompt { + emitter.emit(&crate::event::WorkflowRunEvent::Prompt { stage: node.id.clone(), text: prompt.to_string(), }); diff --git a/crates/arc-workflows/src/cli/mod.rs b/crates/arc-workflows/src/cli/mod.rs index 599bb0934..2dc555481 100644 --- a/crates/arc-workflows/src/cli/mod.rs +++ b/crates/arc-workflows/src/cli/mod.rs @@ -13,7 +13,7 @@ use std::fmt; use std::path::PathBuf; use std::str::FromStr; -use crate::event::PipelineEvent; +use crate::event::WorkflowRunEvent; use crate::outcome::StageUsage; use crate::validation::{Diagnostic, Severity}; use arc_agent::AgentEvent; @@ -57,7 +57,7 @@ impl FromStr for ExecutionEnvKind { #[command( name = "arc-workflows", version, - about = "DOT-based pipeline runner for AI workflows" + about = "DOT-based workflow runner for AI workflows" )] pub struct Cli { #[command(subcommand)] @@ -66,17 +66,17 @@ pub struct Cli { #[derive(Subcommand)] pub enum Command { - /// Launch a pipeline from a .dot or .toml task file + /// Launch a workflow from a .dot or .toml task file Run(RunArgs), - /// Parse and validate a pipeline without executing + /// Parse and validate a workflow without executing Validate(ValidateArgs), } #[derive(Args)] pub struct RunArgs { - /// Path to a .dot pipeline file or .toml task config (not required with --run-branch) + /// Path to a .dot workflow file or .toml task config (not required with --run-branch) #[arg(required_unless_present = "run_branch")] - pub pipeline: Option, + pub workflow: Option, /// Log/artifact directory #[arg(long)] @@ -121,8 +121,8 @@ pub struct RunArgs { #[derive(Args)] pub struct ValidateArgs { - /// Path to the .dot pipeline file - pub pipeline: PathBuf, + /// Path to the .dot workflow file + pub workflow: PathBuf, } /// Read a .dot file from disk. @@ -191,32 +191,32 @@ pub fn format_duration_human(ms: u64) -> String { } } -/// One-line summary of a pipeline event for `-v` output (dimmed). +/// One-line summary of a workflow run event for `-v` output (dimmed). #[must_use] -pub fn format_event_summary(event: &PipelineEvent, styles: &Styles) -> String { +pub fn format_event_summary(event: &WorkflowRunEvent, styles: &Styles) -> String { let body = match event { - PipelineEvent::PipelineStarted { name, run_id, .. } => { - format!("[PIPELINE_STARTED] name={name} id={run_id}") + WorkflowRunEvent::WorkflowRunStarted { name, run_id, .. } => { + format!("[WORKFLOW_RUN_STARTED] name={name} id={run_id}") } - PipelineEvent::PipelineCompleted { + WorkflowRunEvent::WorkflowRunCompleted { duration_ms, artifact_count, total_cost, .. } => { let mut s = - format!("[PIPELINE_COMPLETED] duration={duration_ms}ms artifacts={artifact_count}"); + format!("[WORKFLOW_RUN_COMPLETED] duration={duration_ms}ms artifacts={artifact_count}"); if let Some(cost) = total_cost { s.push_str(&format!(" total_cost={}", format_cost(*cost))); } s } - PipelineEvent::PipelineFailed { + WorkflowRunEvent::WorkflowRunFailed { error, duration_ms, .. } => { - format!("[PIPELINE_FAILED] error=\"{error}\" duration={duration_ms}ms") + format!("[WORKFLOW_RUN_FAILED] error=\"{error}\" duration={duration_ms}ms") } - PipelineEvent::StageStarted { + WorkflowRunEvent::StageStarted { name, index, handler_type, @@ -230,7 +230,7 @@ pub fn format_event_summary(event: &PipelineEvent, styles: &Styles) -> String { s.push_str(&format!(" attempt={attempt}/{max_attempts}")); s } - PipelineEvent::StageCompleted { + WorkflowRunEvent::StageCompleted { name, index, duration_ms, @@ -279,7 +279,7 @@ pub fn format_event_summary(event: &PipelineEvent, styles: &Styles) -> String { } s } - PipelineEvent::StageFailed { + WorkflowRunEvent::StageFailed { name, index, error, @@ -298,7 +298,7 @@ pub fn format_event_summary(event: &PipelineEvent, styles: &Styles) -> String { } s } - PipelineEvent::StageRetrying { + WorkflowRunEvent::StageRetrying { name, index, attempt, @@ -309,17 +309,17 @@ pub fn format_event_summary(event: &PipelineEvent, styles: &Styles) -> String { "[STAGE_RETRYING] name={name} index={index} attempt={attempt}/{max_attempts} delay={delay_ms}ms" ) } - PipelineEvent::ParallelStarted { + WorkflowRunEvent::ParallelStarted { branch_count, join_policy, error_policy, } => { format!("[PARALLEL_STARTED] branches={branch_count} join_policy={join_policy} error_policy={error_policy}") } - PipelineEvent::ParallelBranchStarted { branch, index } => { + WorkflowRunEvent::ParallelBranchStarted { branch, index } => { format!("[PARALLEL_BRANCH_STARTED] branch={branch} index={index}") } - PipelineEvent::ParallelBranchCompleted { + WorkflowRunEvent::ParallelBranchCompleted { branch, index, duration_ms, @@ -327,21 +327,21 @@ pub fn format_event_summary(event: &PipelineEvent, styles: &Styles) -> String { } => { format!("[PARALLEL_BRANCH_COMPLETED] branch={branch} index={index} duration={duration_ms}ms status={status}") } - PipelineEvent::ParallelCompleted { + WorkflowRunEvent::ParallelCompleted { duration_ms, success_count, failure_count, } => { format!("[PARALLEL_COMPLETED] duration={duration_ms}ms succeeded={success_count} failed={failure_count}") } - PipelineEvent::InterviewStarted { + WorkflowRunEvent::InterviewStarted { question, stage, question_type, } => { format!("[INTERVIEW_STARTED] stage={stage} question=\"{question}\" question_type={question_type}") } - PipelineEvent::InterviewCompleted { + WorkflowRunEvent::InterviewCompleted { question, answer, duration_ms, @@ -350,15 +350,15 @@ pub fn format_event_summary(event: &PipelineEvent, styles: &Styles) -> String { "[INTERVIEW_COMPLETED] question=\"{question}\" answer=\"{answer}\" duration={duration_ms}ms" ) } - PipelineEvent::InterviewTimeout { + WorkflowRunEvent::InterviewTimeout { stage, duration_ms, .. } => { format!("[INTERVIEW_TIMEOUT] stage={stage} duration={duration_ms}ms") } - PipelineEvent::CheckpointSaved { node_id } => { + WorkflowRunEvent::CheckpointSaved { node_id } => { format!("[CHECKPOINT_SAVED] node={node_id}") } - PipelineEvent::GitCheckpoint { + WorkflowRunEvent::GitCheckpoint { node_id, git_commit_sha, status, @@ -366,7 +366,7 @@ pub fn format_event_summary(event: &PipelineEvent, styles: &Styles) -> String { } => { format!("[GIT_CHECKPOINT] node={node_id} sha={git_commit_sha} status={status}") } - PipelineEvent::EdgeSelected { + WorkflowRunEvent::EdgeSelected { from_node, to_node, label, @@ -381,14 +381,14 @@ pub fn format_event_summary(event: &PipelineEvent, styles: &Styles) -> String { } s } - PipelineEvent::LoopRestart { from_node, to_node } => { + WorkflowRunEvent::LoopRestart { from_node, to_node } => { format!("[LOOP_RESTART] from={from_node} to={to_node}") } - PipelineEvent::Prompt { stage, text } => { + WorkflowRunEvent::Prompt { stage, text } => { let truncated = if text.len() > 80 { &text[..80] } else { text }; format!("[PROMPT] stage={stage} text=\"{truncated}\"") } - PipelineEvent::Agent { stage, event } => match event { + WorkflowRunEvent::Agent { stage, event } => match event { AgentEvent::AssistantMessage { model, usage, @@ -490,20 +490,20 @@ pub fn format_event_summary(event: &PipelineEvent, styles: &Styles) -> String { } other => format!("[AGENT] stage={stage} event={other:?}"), }, - PipelineEvent::ParallelEarlyTermination { + WorkflowRunEvent::ParallelEarlyTermination { reason, completed_count, pending_count, } => { format!("[PARALLEL_EARLY_TERMINATION] reason={reason} completed={completed_count} pending={pending_count}") } - PipelineEvent::SubgraphStarted { + WorkflowRunEvent::SubgraphStarted { node_id, start_node, } => { format!("[SUBGRAPH_STARTED] node={node_id} start_node={start_node}") } - PipelineEvent::SubgraphCompleted { + WorkflowRunEvent::SubgraphCompleted { node_id, steps_executed, status, @@ -511,7 +511,7 @@ pub fn format_event_summary(event: &PipelineEvent, styles: &Styles) -> String { } => { format!("[SUBGRAPH_COMPLETED] node={node_id} steps={steps_executed} status={status} duration={duration_ms}ms") } - PipelineEvent::ExecutionEnv { event } => { + WorkflowRunEvent::ExecutionEnv { event } => { use arc_agent::ExecutionEnvEvent; match event { ExecutionEnvEvent::Initializing { env_type } => format!("[EXEC_ENV_INITIALIZING] env_type={env_type}"), @@ -534,13 +534,13 @@ pub fn format_event_summary(event: &PipelineEvent, styles: &Styles) -> String { ExecutionEnvEvent::GitCloneFailed { url, error } => format!("[EXEC_ENV_GIT_CLONE_FAILED] url={url} error=\"{error}\""), } } - PipelineEvent::SetupStarted { command_count } => { + WorkflowRunEvent::SetupStarted { command_count } => { format!("[SETUP_STARTED] command_count={command_count}") } - PipelineEvent::SetupCommandStarted { command, index } => { + WorkflowRunEvent::SetupCommandStarted { command, index } => { format!("[SETUP_COMMAND_STARTED] index={index} command=\"{command}\"") } - PipelineEvent::SetupCommandCompleted { + WorkflowRunEvent::SetupCommandCompleted { command, index, exit_code, @@ -548,10 +548,10 @@ pub fn format_event_summary(event: &PipelineEvent, styles: &Styles) -> String { } => { format!("[SETUP_COMMAND_COMPLETED] index={index} command=\"{command}\" exit_code={exit_code} duration={duration_ms}ms") } - PipelineEvent::SetupCompleted { duration_ms } => { + WorkflowRunEvent::SetupCompleted { duration_ms } => { format!("[SETUP_COMPLETED] duration={duration_ms}ms") } - PipelineEvent::SetupFailed { + WorkflowRunEvent::SetupFailed { command, index, exit_code, @@ -564,42 +564,42 @@ pub fn format_event_summary(event: &PipelineEvent, styles: &Styles) -> String { }; format!("[SETUP_FAILED] index={index} command=\"{command}\" exit_code={exit_code} stderr=\"{truncated}\"") } - PipelineEvent::StallWatchdogTimeout { node, idle_seconds } => { + WorkflowRunEvent::StallWatchdogTimeout { node, idle_seconds } => { format!("[STALL_WATCHDOG_TIMEOUT] node={node} idle_seconds={idle_seconds}") } }; format!("{dim}{body}{reset}", dim = styles.dim, reset = styles.reset) } -/// Multi-line detail view of a pipeline event for `-vv` output. +/// Multi-line detail view of a workflow run event for `-vv` output. /// Box-drawing is dimmed; values are normal. #[must_use] -pub fn format_event_detail(event: &PipelineEvent, styles: &Styles) -> String { +pub fn format_event_detail(event: &WorkflowRunEvent, styles: &Styles) -> String { let d = styles.dim; let r = styles.reset; match event { - PipelineEvent::PipelineStarted { name, run_id, .. } => { + WorkflowRunEvent::WorkflowRunStarted { name, run_id, .. } => { format!( - "{d}── PIPELINE_STARTED ─────────────────────────{r}\n {d}name:{r} {name}\n {d}id:{r} {run_id}\n" + "{d}── WORKFLOW_RUN_STARTED ─────────────────────────{r}\n {d}name:{r} {name}\n {d}id:{r} {run_id}\n" ) } - PipelineEvent::PipelineCompleted { + WorkflowRunEvent::WorkflowRunCompleted { duration_ms, artifact_count, total_cost, .. } => { - let mut s = format!("{d}── PIPELINE_COMPLETED ───────────────────────{r}\n {d}duration_ms:{r} {duration_ms}\n {d}artifact_count:{r} {artifact_count}\n"); + let mut s = format!("{d}── WORKFLOW_RUN_COMPLETED ───────────────────────{r}\n {d}duration_ms:{r} {duration_ms}\n {d}artifact_count:{r} {artifact_count}\n"); if let Some(cost) = total_cost { s.push_str(&format!(" {d}total_cost:{r} {}\n", format_cost(*cost))); } s } - PipelineEvent::PipelineFailed { error, duration_ms, .. } => { - format!("{d}── PIPELINE_FAILED ──────────────────────────{r}\n {d}error:{r} {error}\n {d}duration_ms:{r} {duration_ms}\n") + WorkflowRunEvent::WorkflowRunFailed { error, duration_ms, .. } => { + format!("{d}── WORKFLOW_RUN_FAILED ──────────────────────────{r}\n {d}error:{r} {error}\n {d}duration_ms:{r} {duration_ms}\n") } - PipelineEvent::StageStarted { name, index, handler_type, attempt, max_attempts } => { + WorkflowRunEvent::StageStarted { name, index, handler_type, attempt, max_attempts } => { let mut s = format!( "{d}── STAGE_STARTED ────────────────────────────{r}\n {d}name:{r} {name}\n {d}index:{r} {index}\n" ); @@ -609,7 +609,7 @@ pub fn format_event_detail(event: &PipelineEvent, styles: &Styles) -> String { s.push_str(&format!(" {d}attempt:{r} {attempt}/{max_attempts}\n")); s } - PipelineEvent::StageCompleted { + WorkflowRunEvent::StageCompleted { name, index, duration_ms, @@ -667,7 +667,7 @@ pub fn format_event_detail(event: &PipelineEvent, styles: &Styles) -> String { } s } - PipelineEvent::StageFailed { + WorkflowRunEvent::StageFailed { name, index, error, @@ -684,7 +684,7 @@ pub fn format_event_detail(event: &PipelineEvent, styles: &Styles) -> String { } s } - PipelineEvent::StageRetrying { + WorkflowRunEvent::StageRetrying { name, index, attempt, @@ -693,13 +693,13 @@ pub fn format_event_detail(event: &PipelineEvent, styles: &Styles) -> String { } => { format!("{d}── STAGE_RETRYING ───────────────────────────{r}\n {d}name:{r} {name}\n {d}index:{r} {index}\n {d}attempt:{r} {attempt}/{max_attempts}\n {d}delay_ms:{r} {delay_ms}\n") } - PipelineEvent::ParallelStarted { branch_count, join_policy, error_policy } => { + WorkflowRunEvent::ParallelStarted { branch_count, join_policy, error_policy } => { format!("{d}── PARALLEL_STARTED ─────────────────────────{r}\n {d}branch_count:{r} {branch_count}\n {d}join_policy:{r} {join_policy}\n {d}error_policy:{r} {error_policy}\n") } - PipelineEvent::ParallelBranchStarted { branch, index } => { + WorkflowRunEvent::ParallelBranchStarted { branch, index } => { format!("{d}── PARALLEL_BRANCH_STARTED ──────────────────{r}\n {d}branch:{r} {branch}\n {d}index:{r} {index}\n") } - PipelineEvent::ParallelBranchCompleted { + WorkflowRunEvent::ParallelBranchCompleted { branch, index, duration_ms, @@ -707,41 +707,41 @@ pub fn format_event_detail(event: &PipelineEvent, styles: &Styles) -> String { } => { format!("{d}── PARALLEL_BRANCH_COMPLETED ────────────────{r}\n {d}branch:{r} {branch}\n {d}index:{r} {index}\n {d}duration_ms:{r} {duration_ms}\n {d}status:{r} {status}\n") } - PipelineEvent::ParallelCompleted { + WorkflowRunEvent::ParallelCompleted { duration_ms, success_count, failure_count, } => { format!("{d}── PARALLEL_COMPLETED ───────────────────────{r}\n {d}duration_ms:{r} {duration_ms}\n {d}success_count:{r} {success_count}\n {d}failure_count:{r} {failure_count}\n") } - PipelineEvent::InterviewStarted { question, stage, question_type } => { + WorkflowRunEvent::InterviewStarted { question, stage, question_type } => { format!("{d}── INTERVIEW_STARTED ────────────────────────{r}\n {d}stage:{r} {stage}\n {d}question:{r} {question}\n {d}question_type:{r} {question_type}\n") } - PipelineEvent::InterviewCompleted { + WorkflowRunEvent::InterviewCompleted { question, answer, duration_ms, } => { format!("{d}── INTERVIEW_COMPLETED ──────────────────────{r}\n {d}question:{r} {question}\n {d}answer:{r} {answer}\n {d}duration_ms:{r} {duration_ms}\n") } - PipelineEvent::InterviewTimeout { + WorkflowRunEvent::InterviewTimeout { question, stage, duration_ms, } => { format!("{d}── INTERVIEW_TIMEOUT ────────────────────────{r}\n {d}question:{r} {question}\n {d}stage:{r} {stage}\n {d}duration_ms:{r} {duration_ms}\n") } - PipelineEvent::CheckpointSaved { node_id } => { + WorkflowRunEvent::CheckpointSaved { node_id } => { format!( "{d}── CHECKPOINT_SAVED ─────────────────────────{r}\n {d}node_id:{r} {node_id}\n" ) } - PipelineEvent::GitCheckpoint { run_id, node_id, status, git_commit_sha } => { + WorkflowRunEvent::GitCheckpoint { run_id, node_id, status, git_commit_sha } => { format!( "{d}── GIT_CHECKPOINT ───────────────────────────{r}\n {d}run_id:{r} {run_id}\n {d}node_id:{r} {node_id}\n {d}status:{r} {status}\n {d}sha:{r} {git_commit_sha}\n" ) } - PipelineEvent::EdgeSelected { from_node, to_node, label, condition } => { + WorkflowRunEvent::EdgeSelected { from_node, to_node, label, condition } => { let mut s = format!("{d}── EDGE_SELECTED ────────────────────────────{r}\n {d}from:{r} {from_node}\n {d}to:{r} {to_node}\n"); if let Some(l) = label { s.push_str(&format!(" {d}label:{r} {l}\n")); @@ -751,13 +751,13 @@ pub fn format_event_detail(event: &PipelineEvent, styles: &Styles) -> String { } s } - PipelineEvent::LoopRestart { from_node, to_node } => { + WorkflowRunEvent::LoopRestart { from_node, to_node } => { format!("{d}── LOOP_RESTART ─────────────────────────────{r}\n {d}from:{r} {from_node}\n {d}to:{r} {to_node}\n") } - PipelineEvent::Prompt { stage, text } => { + WorkflowRunEvent::Prompt { stage, text } => { format!("{d}── PROMPT ───────────────────────────────────{r}\n {d}stage:{r} {stage}\n {d}text:{r}\n{text}\n") } - PipelineEvent::Agent { stage, event } => match event { + WorkflowRunEvent::Agent { stage, event } => match event { AgentEvent::AssistantMessage { text, model, usage, tool_call_count } => { let total = usage.input_tokens + usage.output_tokens; let truncated = if text.len() > 200 { &text[..200] } else { text.as_str() }; @@ -828,17 +828,17 @@ pub fn format_event_detail(event: &PipelineEvent, styles: &Styles) -> String { } other => format!("{d}── AGENT ────────────────────────────────────{r}\n {d}stage:{r} {stage}\n {d}event:{r} {other:?}\n"), } - PipelineEvent::ParallelEarlyTermination { + WorkflowRunEvent::ParallelEarlyTermination { reason, completed_count, pending_count, } => { format!("{d}── PARALLEL_EARLY_TERMINATION ───────────────{r}\n {d}reason:{r} {reason}\n {d}completed_count:{r} {completed_count}\n {d}pending_count:{r} {pending_count}\n") } - PipelineEvent::SubgraphStarted { node_id, start_node } => { + WorkflowRunEvent::SubgraphStarted { node_id, start_node } => { format!("{d}── SUBGRAPH_STARTED ─────────────────────────{r}\n {d}node_id:{r} {node_id}\n {d}start_node:{r} {start_node}\n") } - PipelineEvent::SubgraphCompleted { + WorkflowRunEvent::SubgraphCompleted { node_id, steps_executed, status, @@ -846,7 +846,7 @@ pub fn format_event_detail(event: &PipelineEvent, styles: &Styles) -> String { } => { format!("{d}── SUBGRAPH_COMPLETED ───────────────────────{r}\n {d}node_id:{r} {node_id}\n {d}steps_executed:{r} {steps_executed}\n {d}status:{r} {status}\n {d}duration_ms:{r} {duration_ms}\n") } - PipelineEvent::ExecutionEnv { event } => { + WorkflowRunEvent::ExecutionEnv { event } => { use arc_agent::ExecutionEnvEvent; match event { ExecutionEnvEvent::Initializing { env_type } => { @@ -897,22 +897,22 @@ pub fn format_event_detail(event: &PipelineEvent, styles: &Styles) -> String { } } } - PipelineEvent::SetupStarted { command_count } => { + WorkflowRunEvent::SetupStarted { command_count } => { format!("{d}── SETUP_STARTED ────────────────────────────{r}\n {d}command_count:{r} {command_count}\n") } - PipelineEvent::SetupCommandStarted { command, index } => { + WorkflowRunEvent::SetupCommandStarted { command, index } => { format!("{d}── SETUP_COMMAND_STARTED ────────────────────{r}\n {d}index:{r} {index}\n {d}command:{r} {command}\n") } - PipelineEvent::SetupCommandCompleted { command, index, exit_code, duration_ms } => { + WorkflowRunEvent::SetupCommandCompleted { command, index, exit_code, duration_ms } => { format!("{d}── SETUP_COMMAND_COMPLETED ──────────────────{r}\n {d}index:{r} {index}\n {d}command:{r} {command}\n {d}exit_code:{r} {exit_code}\n {d}duration_ms:{r} {duration_ms}\n") } - PipelineEvent::SetupCompleted { duration_ms } => { + WorkflowRunEvent::SetupCompleted { duration_ms } => { format!("{d}── SETUP_COMPLETED ──────────────────────────{r}\n {d}duration_ms:{r} {duration_ms}\n") } - PipelineEvent::SetupFailed { command, index, exit_code, stderr } => { + WorkflowRunEvent::SetupFailed { command, index, exit_code, stderr } => { format!("{d}── SETUP_FAILED ─────────────────────────────{r}\n {d}index:{r} {index}\n {d}command:{r} {command}\n {d}exit_code:{r} {exit_code}\n {d}stderr:{r} {stderr}\n") } - PipelineEvent::StallWatchdogTimeout { node, idle_seconds } => { + WorkflowRunEvent::StallWatchdogTimeout { node, idle_seconds } => { format!("{d}── STALL_WATCHDOG_TIMEOUT ────────────────────{r}\n {d}node:{r} {node}\n {d}idle_seconds:{r} {idle_seconds}\n") } } @@ -989,7 +989,7 @@ mod tests { #[test] fn format_summary_execution_env_initializing() { - let event = PipelineEvent::ExecutionEnv { + let event = WorkflowRunEvent::ExecutionEnv { event: arc_agent::ExecutionEnvEvent::Initializing { env_type: "docker".into(), }, @@ -1001,7 +1001,7 @@ mod tests { #[test] fn format_summary_setup_started() { - let event = PipelineEvent::SetupStarted { command_count: 3 }; + let event = WorkflowRunEvent::SetupStarted { command_count: 3 }; let s = format_event_summary(&event, test_styles()); assert!(s.contains("[SETUP_STARTED]")); assert!(s.contains("3")); @@ -1009,7 +1009,7 @@ mod tests { #[test] fn format_detail_execution_env_ready() { - let event = PipelineEvent::ExecutionEnv { + let event = WorkflowRunEvent::ExecutionEnv { event: arc_agent::ExecutionEnvEvent::Ready { env_type: "local".into(), duration_ms: 42, @@ -1023,7 +1023,7 @@ mod tests { #[test] fn format_summary_subagent_spawned() { - let event = PipelineEvent::Agent { + let event = WorkflowRunEvent::Agent { stage: "code".into(), event: AgentEvent::SubAgentSpawned { agent_id: "abcdef12-3456-7890-abcd-ef1234567890".into(), @@ -1039,7 +1039,7 @@ mod tests { #[test] fn format_summary_subagent_completed() { - let event = PipelineEvent::Agent { + let event = WorkflowRunEvent::Agent { stage: "code".into(), event: AgentEvent::SubAgentCompleted { agent_id: "abcdef12-xxxx".into(), @@ -1056,7 +1056,7 @@ mod tests { #[test] fn format_detail_subagent_failed() { - let event = PipelineEvent::Agent { + let event = WorkflowRunEvent::Agent { stage: "code".into(), event: AgentEvent::SubAgentFailed { agent_id: "abcdef12-xxxx".into(), @@ -1072,7 +1072,7 @@ mod tests { #[test] fn format_detail_subagent_closed() { - let event = PipelineEvent::Agent { + let event = WorkflowRunEvent::Agent { stage: "code".into(), event: AgentEvent::SubAgentClosed { agent_id: "abcdef12-xxxx".into(), @@ -1086,7 +1086,7 @@ mod tests { #[test] fn format_summary_subagent_event() { - let event = PipelineEvent::Agent { + let event = WorkflowRunEvent::Agent { stage: "code".into(), event: AgentEvent::SubAgentEvent { agent_id: "abcdef12-xxxx".into(), @@ -1101,7 +1101,7 @@ mod tests { #[test] fn format_detail_setup_command_completed() { - let event = PipelineEvent::SetupCommandCompleted { + let event = WorkflowRunEvent::SetupCommandCompleted { command: "npm install".into(), index: 0, exit_code: 0, diff --git a/crates/arc-workflows/src/cli/run.rs b/crates/arc-workflows/src/cli/run.rs index 783a94cc7..9fa2796ee 100644 --- a/crates/arc-workflows/src/cli/run.rs +++ b/crates/arc-workflows/src/cli/run.rs @@ -11,14 +11,14 @@ use arc_util::terminal::Styles; use chrono::{Local, Utc}; use crate::checkpoint::Checkpoint; -use crate::engine::{GitCheckpointMode, PipelineEngine, RunConfig}; +use crate::engine::{GitCheckpointMode, WorkflowRunEngine, RunConfig}; use crate::event::EventEmitter; use crate::handler::default_registry; use crate::interviewer::auto_approve::AutoApproveInterviewer; use crate::interviewer::console::ConsoleInterviewer; use crate::interviewer::Interviewer; use crate::outcome::StageStatus; -use crate::pipeline::PipelineBuilder; +use crate::workflow::WorkflowBuilder; use crate::validation::Severity; use arc_llm::provider::Provider; @@ -32,7 +32,7 @@ use super::{ RunArgs, }; -/// Accumulates token usage and cost across all pipeline stages. +/// Accumulates token usage and cost across all workflow stages. #[derive(Default)] struct CostAccumulator { total_input_tokens: i64, @@ -44,29 +44,29 @@ struct CostAccumulator { has_pricing: bool, } -/// Execute a full pipeline run. +/// Execute a full workflow run. /// /// # Errors /// -/// Returns an error if the pipeline cannot be read, parsed, validated, or executed. +/// Returns an error if the workflow cannot be read, parsed, validated, or executed. pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Result<()> { // Handle --run-branch resume: read everything from git metadata if let Some(branch) = args.run_branch.clone() { return run_from_branch(args, &branch, styles).await; } - let pipeline_path = args - .pipeline + let workflow_path = args + .workflow .as_ref() - .ok_or_else(|| anyhow::anyhow!("--pipeline is required unless --run-branch is provided"))?; + .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 pipeline_path.extension().is_some_and(|ext| ext == "toml") { - let cfg = task_config::load_task_config(pipeline_path)?; - let dot = task_config::resolve_graph_path(pipeline_path, &cfg.graph); + 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); (dot, Some(cfg)) } else { - (pipeline_path.clone(), None) + (workflow_path.clone(), None) }; if let Some(ref cfg) = task_cfg { @@ -83,16 +83,16 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu .map(|s| s.commands.clone()) .unwrap_or_default(); - // 1. Parse and validate pipeline + // 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)?, None => source, }; - let (graph, diagnostics) = PipelineBuilder::new().prepare(&source)?; + let (graph, diagnostics) = WorkflowBuilder::new().prepare(&source)?; eprintln!( - "{bold}Parsed pipeline:{reset} {} ({dim}{} nodes, {} edges{reset})", + "{bold}Parsed workflow:{reset} {} ({dim}{} nodes, {} edges{reset})", graph.name, graph.nodes.len(), graph.edges.len(), @@ -148,8 +148,8 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu tokio::fs::create_dir_all(&logs_dir).await?; tokio::fs::write(logs_dir.join("graph.dot"), &source).await?; tokio::fs::write(logs_dir.join("run.pid"), std::process::id().to_string()).await?; - if pipeline_path.extension().is_some_and(|ext| ext == "toml") { - if let Ok(toml_contents) = tokio::fs::read(pipeline_path).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?; } } @@ -171,7 +171,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu { let sha_clone = Arc::clone(&last_git_sha); emitter.on_event(move |event| { - if let crate::event::PipelineEvent::GitCheckpoint { git_commit_sha, .. } = event { + if let crate::event::WorkflowRunEvent::GitCheckpoint { git_commit_sha, .. } = event { *sha_clone.lock().unwrap() = Some(git_commit_sha.clone()); } }); @@ -181,7 +181,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu let accumulator = Arc::new(Mutex::new(CostAccumulator::default())); let acc_clone = Arc::clone(&accumulator); emitter.on_event(move |event| { - if let crate::event::PipelineEvent::StageCompleted { usage: Some(u), .. } = event { + if let crate::event::WorkflowRunEvent::StageCompleted { usage: Some(u), .. } = event { let mut acc = acc_clone.lock().unwrap(); acc.total_input_tokens += u.input_tokens; acc.total_output_tokens += u.output_tokens; @@ -202,7 +202,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu let run_id = Arc::new(Mutex::new(String::new())); let run_id_clone = Arc::clone(&run_id); emitter.on_event(move |event| { - if let crate::event::PipelineEvent::PipelineStarted { run_id, .. } = event { + if let crate::event::WorkflowRunEvent::WorkflowRunStarted { run_id, .. } = event { *run_id_clone.lock().unwrap() = run_id.clone(); } let envelope = serde_json::json!({ @@ -240,7 +240,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu }); } else { emitter.on_event(move |event| match event { - crate::event::PipelineEvent::StageCompleted { + crate::event::WorkflowRunEvent::StageCompleted { name, duration_ms, status, @@ -266,7 +266,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu } eprintln!("{line}{reset}", reset = styles.reset); } - crate::event::PipelineEvent::StageFailed { name, .. } => { + crate::event::WorkflowRunEvent::StageFailed { name, .. } => { eprintln!( "{dim}Stage \"{name}\" failed{reset}", dim = styles.dim, @@ -335,7 +335,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu .map_err(|e| anyhow::anyhow!("Failed to create Docker environment: {e}"))?; let emitter_cb = Arc::clone(&emitter); env.set_event_callback(Arc::new(move |event| { - emitter_cb.emit(&crate::event::PipelineEvent::ExecutionEnv { event }); + emitter_cb.emit(&crate::event::WorkflowRunEvent::ExecutionEnv { event }); })); Arc::new(env) } @@ -348,7 +348,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu crate::daytona_env::DaytonaExecutionEnvironment::new(daytona_client, config); let emitter_cb = Arc::clone(&emitter); env.set_event_callback(Arc::new(move |event| { - emitter_cb.emit(&crate::event::PipelineEvent::ExecutionEnv { event }); + emitter_cb.emit(&crate::event::WorkflowRunEvent::ExecutionEnv { event }); })); Arc::new(env) } @@ -356,13 +356,13 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu let mut env = LocalExecutionEnvironment::new(cwd); let emitter_cb = Arc::clone(&emitter); env.set_event_callback(Arc::new(move |event| { - emitter_cb.emit(&crate::event::PipelineEvent::ExecutionEnv { event }); + emitter_cb.emit(&crate::event::WorkflowRunEvent::ExecutionEnv { event }); })); Arc::new(env) } }; - // Initialize execution environment (creates sandbox/container once for the whole pipeline) + // Initialize execution environment (creates sandbox/container once for the whole run) execution_env .initialize() .await @@ -403,12 +403,12 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu // Run setup commands inside the execution environment (once, not per-stage) if !setup_commands.is_empty() { - emitter.emit(&crate::event::PipelineEvent::SetupStarted { + emitter.emit(&crate::event::WorkflowRunEvent::SetupStarted { command_count: setup_commands.len(), }); let setup_start = Instant::now(); for (index, cmd) in setup_commands.iter().enumerate() { - emitter.emit(&crate::event::PipelineEvent::SetupCommandStarted { + emitter.emit(&crate::event::WorkflowRunEvent::SetupCommandStarted { command: cmd.clone(), index, }); @@ -419,7 +419,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu .map_err(|e| anyhow::anyhow!("Setup command failed: {e}"))?; let cmd_duration = u64::try_from(cmd_start.elapsed().as_millis()).unwrap_or(u64::MAX); if result.exit_code != 0 { - emitter.emit(&crate::event::PipelineEvent::SetupFailed { + emitter.emit(&crate::event::WorkflowRunEvent::SetupFailed { command: cmd.clone(), index, exit_code: result.exit_code, @@ -431,7 +431,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu result.stderr, ); } - emitter.emit(&crate::event::PipelineEvent::SetupCommandCompleted { + emitter.emit(&crate::event::WorkflowRunEvent::SetupCommandCompleted { command: cmd.clone(), index, exit_code: result.exit_code, @@ -439,7 +439,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu }); } let setup_duration = u64::try_from(setup_start.elapsed().as_millis()).unwrap_or(u64::MAX); - emitter.emit(&crate::event::PipelineEvent::SetupCompleted { + emitter.emit(&crate::event::WorkflowRunEvent::SetupCompleted { duration_ms: setup_duration, }); } @@ -529,7 +529,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu Some(Box::new(BackendRouter::new(Box::new(api), cli))) } }); - let engine = PipelineEngine::with_interviewer( + let engine = WorkflowRunEngine::with_interviewer( registry, Arc::clone(&emitter), interviewer, @@ -634,7 +634,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu // 8. Print result eprintln!( - "\n{bold}=== Pipeline Result ==={reset}", + "\n{bold}=== Run Result ==={reset}", bold = styles.bold, reset = styles.reset, ); @@ -708,7 +708,7 @@ pub async fn run_command(args: RunArgs, styles: &'static Styles) -> anyhow::Resu } } -/// Set up a git worktree for an isolated pipeline run. +/// Set up a git worktree for an isolated workflow run. /// Caller must have already verified the repo is clean via `git::ensure_clean`. /// Returns (run_id, work_dir, worktree_path, branch_name, base_sha) on success. fn setup_worktree( @@ -774,7 +774,7 @@ async fn setup_daytona_git( Ok((run_id, base_sha, branch_name)) } -/// Resume a pipeline run from a git run branch. +/// Resume a workflow run from a git run branch. /// /// Reads the checkpoint, manifest, and graph DOT from the metadata branch /// (`refs/arc/{run_id}`), re-attaches a worktree to the existing run branch, @@ -807,16 +807,16 @@ async fn run_from_branch( .ok_or_else(|| anyhow::anyhow!("no graph.dot found on metadata branch for run {run_id}"))?; // If --pipeline was also provided, use it instead (allows overriding) - let source = if let Some(ref pipeline_path) = args.pipeline { - super::read_dot_file(pipeline_path)? + let source = if let Some(ref workflow_path) = args.workflow { + super::read_dot_file(workflow_path)? } else { source }; - let (graph, diagnostics) = crate::pipeline::PipelineBuilder::new().prepare(&source)?; + let (graph, diagnostics) = crate::workflow::WorkflowBuilder::new().prepare(&source)?; eprintln!( - "{bold}Resuming pipeline:{reset} {} from branch {dim}{run_branch}{reset}", + "{bold}Resuming workflow:{reset} {} from branch {dim}{run_branch}{reset}", graph.name, bold = styles.bold, dim = styles.dim, @@ -860,7 +860,7 @@ async fn run_from_branch( let mut env = arc_agent::LocalExecutionEnvironment::new(worktree_path.clone()); let emitter_cb = Arc::clone(&emitter); env.set_event_callback(Arc::new(move |event| { - emitter_cb.emit(&crate::event::PipelineEvent::ExecutionEnv { event }); + emitter_cb.emit(&crate::event::WorkflowRunEvent::ExecutionEnv { event }); })); Arc::new(env) }; @@ -897,7 +897,7 @@ async fn run_from_branch( Some(Box::new(BackendRouter::new(Box::new(api), cli))) } }); - let engine = crate::engine::PipelineEngine::with_interviewer( + let engine = crate::engine::WorkflowRunEngine::with_interviewer( registry, Arc::clone(&emitter), interviewer, @@ -961,7 +961,7 @@ async fn run_from_branch( let outcome = engine_result?; eprintln!( - "\n{bold}=== Pipeline Result ==={reset}", + "\n{bold}=== Run Result ==={reset}", bold = styles.bold, reset = styles.reset, ); @@ -991,14 +991,14 @@ async fn run_from_branch( } } -/// Generate a retro report for a completed pipeline run. +/// Generate a retro report for a completed workflow run. /// /// Derives a basic retro from the checkpoint, then optionally runs the retro agent /// for a richer narrative. Errors are logged as warnings rather than propagated. #[allow(clippy::too_many_arguments)] async fn generate_retro( run_id: &str, - pipeline_name: &str, + workflow_name: &str, goal: &str, logs_dir: &std::path::Path, failed: bool, @@ -1026,7 +1026,7 @@ async fn generate_retro( let stage_durations = crate::retro::extract_stage_durations(logs_dir); let mut retro = crate::retro::derive_retro( run_id, - pipeline_name, + workflow_name, goal, &cp, failed, diff --git a/crates/arc-workflows/src/cli/runs.rs b/crates/arc-workflows/src/cli/runs.rs index e1d43bd1d..a6f8fd65e 100644 --- a/crates/arc-workflows/src/cli/runs.rs +++ b/crates/arc-workflows/src/cli/runs.rs @@ -12,9 +12,9 @@ pub struct RunsListArgs { #[arg(long)] pub before: Option, - /// Filter by pipeline name (substring match) + /// Filter by workflow name (substring match) #[arg(long)] - pub pipeline: Option, + pub workflow: Option, /// Filter by label (KEY=VALUE, repeatable, AND semantics) #[arg(long = "label", value_name = "KEY=VALUE")] @@ -35,9 +35,9 @@ pub struct RunsPruneArgs { #[arg(long)] pub before: Option, - /// Filter by pipeline name (substring match) + /// Filter by workflow name (substring match) #[arg(long)] - pub pipeline: Option, + pub workflow: Option, /// Filter by label (KEY=VALUE, repeatable, AND semantics) #[arg(long = "label", value_name = "KEY=VALUE")] @@ -56,7 +56,7 @@ pub struct RunsPruneArgs { pub struct RunInfo { pub run_id: String, pub dir_name: String, - pub pipeline_name: String, + pub workflow_name: String, pub status: String, pub start_time: String, pub labels: HashMap, @@ -99,7 +99,7 @@ pub fn scan_runs(base: &Path) -> Result> { .as_str() .unwrap_or(&dir_name) .to_string(); - let pipeline_name = manifest["pipeline_name"] + let workflow_name = manifest["workflow_name"] .as_str() .unwrap_or("unknown") .to_string(); @@ -117,7 +117,7 @@ pub fn scan_runs(base: &Path) -> Result> { runs.push(RunInfo { run_id, dir_name, - pipeline_name, + workflow_name, status, start_time, labels, @@ -139,7 +139,7 @@ pub fn scan_runs(base: &Path) -> Result> { runs.push(RunInfo { run_id: dir_name.clone(), dir_name, - pipeline_name: "[no manifest]".to_string(), + workflow_name: "[no manifest]".to_string(), status: "unknown".to_string(), start_time: mtime, labels: HashMap::new(), @@ -176,7 +176,7 @@ fn read_status(run_dir: &Path) -> String { pub fn filter_runs( runs: &[RunInfo], before: Option<&str>, - pipeline: Option<&str>, + workflow: Option<&str>, labels: &[(String, String)], include_orphans: bool, ) -> Vec { @@ -190,8 +190,8 @@ pub fn filter_runs( return false; } } - if let Some(pat) = pipeline { - if !r.pipeline_name.contains(pat) { + if let Some(pat) = workflow { + if !r.workflow_name.contains(pat) { return false; } } @@ -229,7 +229,7 @@ pub fn list_command(args: &RunsListArgs) -> Result<()> { let filtered = filter_runs( &runs, args.before.as_deref(), - args.pipeline.as_deref(), + args.workflow.as_deref(), &label_filters, args.orphans, ); @@ -247,7 +247,7 @@ pub fn list_command(args: &RunsListArgs) -> Result<()> { // Print table header let header = format!( "{:<30} {:<25} {:<10} {:<25} LABELS", - "RUN ID", "PIPELINE", "STATUS", "STARTED" + "RUN ID", "WORKFLOW", "STATUS", "STARTED" ); println!("{header}"); println!("{}", "-".repeat(100)); @@ -271,7 +271,7 @@ pub fn list_command(args: &RunsListArgs) -> Result<()> { }; println!( "{:<30} {:<25} {:<10} {:<25} {}", - run_id_display, run.pipeline_name, run.status, start_display, labels_str + run_id_display, run.workflow_name, run.status, start_display, labels_str ); } eprintln!("\n{} run(s) listed.", filtered.len()); @@ -289,7 +289,7 @@ pub fn prune_from(args: &RunsPruneArgs, base: &Path) -> Result<()> { let filtered = filter_runs( &runs, args.before.as_deref(), - args.pipeline.as_deref(), + args.workflow.as_deref(), &label_filters, args.orphans, ); @@ -310,7 +310,7 @@ pub fn prune_from(args: &RunsPruneArgs, base: &Path) -> Result<()> { debug!(run_id = %run.run_id, "would delete run (dry-run)"); println!( "would delete: {} ({})", - run.dir_name, run.pipeline_name + run.dir_name, run.workflow_name ); } eprintln!( @@ -359,7 +359,7 @@ mod tests { "arc-run-20260101-120000", Some(serde_json::json!({ "run_id": "abc123", - "pipeline_name": "my-pipeline", + "workflow_name": "my-pipeline", "start_time": "2026-01-01T12:00:00Z", "labels": { "env": "prod" } })), @@ -373,13 +373,13 @@ mod tests { assert_eq!(runs.len(), 2); let completed = runs.iter().find(|r| r.run_id == "abc123").unwrap(); - assert_eq!(completed.pipeline_name, "my-pipeline"); + assert_eq!(completed.workflow_name, "my-pipeline"); assert_eq!(completed.status, "success"); assert_eq!(completed.labels.get("env").unwrap(), "prod"); assert!(!completed.is_orphan); let orphan = runs.iter().find(|r| r.is_orphan).unwrap(); - assert_eq!(orphan.pipeline_name, "[no manifest]"); + assert_eq!(orphan.workflow_name, "[no manifest]"); assert_eq!(orphan.status, "unknown"); } @@ -393,7 +393,7 @@ mod tests { "arc-run-running", Some(serde_json::json!({ "run_id": "running-1", - "pipeline_name": "pipeline-a", + "workflow_name": "pipeline-a", "start_time": "2026-01-15T10:00:00Z" })), None, @@ -424,7 +424,7 @@ mod tests { RunInfo { run_id: "old".into(), dir_name: "d1".into(), - pipeline_name: "p".into(), + workflow_name: "p".into(), status: "success".into(), start_time: "2025-06-01T00:00:00Z".into(), labels: HashMap::new(), @@ -434,7 +434,7 @@ mod tests { RunInfo { run_id: "new".into(), dir_name: "d2".into(), - pipeline_name: "p".into(), + workflow_name: "p".into(), status: "success".into(), start_time: "2026-03-01T00:00:00Z".into(), labels: HashMap::new(), @@ -448,12 +448,12 @@ mod tests { } #[test] - fn filter_runs_pipeline() { + fn filter_runs_workflow() { let runs = vec![ RunInfo { run_id: "a".into(), dir_name: "d1".into(), - pipeline_name: "deploy-prod".into(), + workflow_name: "deploy-prod".into(), status: "success".into(), start_time: "2026-01-01T00:00:00Z".into(), labels: HashMap::new(), @@ -463,7 +463,7 @@ mod tests { RunInfo { run_id: "b".into(), dir_name: "d2".into(), - pipeline_name: "test-suite".into(), + workflow_name: "test-suite".into(), status: "success".into(), start_time: "2026-01-01T00:00:00Z".into(), labels: HashMap::new(), @@ -482,7 +482,7 @@ mod tests { RunInfo { run_id: "a".into(), dir_name: "d1".into(), - pipeline_name: "p".into(), + workflow_name: "p".into(), status: "success".into(), start_time: "2026-01-01T00:00:00Z".into(), labels: HashMap::from([("env".into(), "prod".into())]), @@ -492,7 +492,7 @@ mod tests { RunInfo { run_id: "b".into(), dir_name: "d2".into(), - pipeline_name: "p".into(), + workflow_name: "p".into(), status: "success".into(), start_time: "2026-01-01T00:00:00Z".into(), labels: HashMap::from([("env".into(), "staging".into())]), @@ -516,7 +516,7 @@ mod tests { let runs = vec![RunInfo { run_id: "orphan".into(), dir_name: "d1".into(), - pipeline_name: "[no manifest]".into(), + workflow_name: "[no manifest]".into(), status: "unknown".into(), start_time: "".into(), labels: HashMap::new(), @@ -540,7 +540,7 @@ mod tests { "arc-run-20250101-120000", Some(serde_json::json!({ "run_id": "to-prune", - "pipeline_name": "old-pipeline", + "workflow_name": "old-pipeline", "start_time": "2025-01-01T12:00:00Z" })), Some(serde_json::json!({ "status": "success" })), @@ -549,7 +549,7 @@ mod tests { let args = RunsPruneArgs { before: Some("2026-01-01".into()), - pipeline: None, + workflow: None, label: Vec::new(), orphans: false, yes: false, @@ -569,7 +569,7 @@ mod tests { "arc-run-20250101-120000", Some(serde_json::json!({ "run_id": "to-prune", - "pipeline_name": "old-pipeline", + "workflow_name": "old-pipeline", "start_time": "2025-01-01T12:00:00Z" })), Some(serde_json::json!({ "status": "success" })), @@ -582,7 +582,7 @@ mod tests { "arc-run-20260301-120000", Some(serde_json::json!({ "run_id": "keep-this", - "pipeline_name": "new-pipeline", + "workflow_name": "new-pipeline", "start_time": "2026-03-01T12:00:00Z" })), Some(serde_json::json!({ "status": "success" })), @@ -591,7 +591,7 @@ mod tests { let args = RunsPruneArgs { before: Some("2026-01-01".into()), - pipeline: None, + workflow: None, label: Vec::new(), orphans: false, yes: true, @@ -611,7 +611,7 @@ mod tests { let args = RunsPruneArgs { before: None, - pipeline: None, + workflow: None, label: Vec::new(), orphans: true, yes: true, diff --git a/crates/arc-workflows/src/cli/task_config.rs b/crates/arc-workflows/src/cli/task_config.rs index d4afb31ef..3f371a73c 100644 --- a/crates/arc-workflows/src/cli/task_config.rs +++ b/crates/arc-workflows/src/cli/task_config.rs @@ -164,7 +164,7 @@ mod tests { let toml = r#" version = 1 task = "Run tests" -graph = "pipeline.dot" +graph = "workflow.dot" [vars] repo_url = "https://github.com/org/repo" @@ -224,7 +224,7 @@ language = "python" let toml = r#" version = 1 task = "Run tests" -graph = "pipeline.dot" +graph = "workflow.dot" [execution] environment = "daytona" @@ -240,7 +240,7 @@ environment = "daytona" let toml = r#" version = 1 task = "Run tests" -graph = "pipeline.dot" +graph = "workflow.dot" [execution] environment = "daytona" @@ -283,7 +283,7 @@ dockerfile = "FROM rust:1.85-slim-bookworm\nRUN apt-get update" let toml = r#" version = 1 task = "Run tests" -graph = "pipeline.dot" +graph = "workflow.dot" [execution] environment = "daytona" @@ -302,12 +302,12 @@ auto_stop_interval = 30 let toml = r#" version = 1 task = "Run tests" -graph = "pipeline.dot" +graph = "workflow.dot" "#; let config = parse_task_config(toml).unwrap(); assert_eq!(config.version, 1); assert_eq!(config.task, "Run tests"); - assert_eq!(config.graph, "pipeline.dot"); + assert_eq!(config.graph, "workflow.dot"); assert!(config.directory.is_none()); assert!(config.llm.is_none()); assert!(config.setup.is_none()); @@ -318,7 +318,7 @@ graph = "pipeline.dot" let toml = r#" version = 1 task = "Full workflow" -graph = "pipeline.dot" +graph = "workflow.dot" directory = "/tmp/repo" [llm] @@ -367,8 +367,8 @@ graph = "p.dot" #[test] fn graph_path_absolute_unchanged() { let toml_path = Path::new("/tmp/sub/task.toml"); - let resolved = resolve_graph_path(toml_path, "/other/pipeline.dot"); - assert_eq!(resolved, PathBuf::from("/other/pipeline.dot")); + let resolved = resolve_graph_path(toml_path, "/other/workflow.dot"); + assert_eq!(resolved, PathBuf::from("/other/workflow.dot")); } #[test] diff --git a/crates/arc-workflows/src/cli/validate.rs b/crates/arc-workflows/src/cli/validate.rs index 6676f1d95..046c850ff 100644 --- a/crates/arc-workflows/src/cli/validate.rs +++ b/crates/arc-workflows/src/cli/validate.rs @@ -1,22 +1,22 @@ use anyhow::bail; use arc_util::terminal::Styles; -use crate::pipeline::PipelineBuilder; +use crate::workflow::WorkflowBuilder; use crate::validation::Severity; use super::{print_diagnostics, read_dot_file, ValidateArgs}; -/// Parse and validate a pipeline file without executing it. +/// Parse and validate a workflow file without executing it. /// /// # Errors /// /// Returns an error if the file cannot be read, parsed, or has validation errors. pub fn validate_command(args: &ValidateArgs, styles: &Styles) -> anyhow::Result<()> { - let source = read_dot_file(&args.pipeline)?; - let (graph, diagnostics) = PipelineBuilder::new().prepare(&source)?; + let source = read_dot_file(&args.workflow)?; + let (graph, diagnostics) = WorkflowBuilder::new().prepare(&source)?; eprintln!( - "{bold}Parsed pipeline:{reset} {} ({dim}{} nodes, {} edges{reset})", + "{bold}Parsed workflow:{reset} {} ({dim}{} nodes, {} edges{reset})", graph.name, graph.nodes.len(), graph.edges.len(), diff --git a/crates/arc-workflows/src/engine.rs b/crates/arc-workflows/src/engine.rs index 3e1629648..c68cc943c 100644 --- a/crates/arc-workflows/src/engine.rs +++ b/crates/arc-workflows/src/engine.rs @@ -18,7 +18,7 @@ use crate::checkpoint::Checkpoint; use crate::condition::evaluate_condition; use crate::context::Context; use crate::error::{classify_failure_reason, ArcError, FailureClass, FailureSignature, Result}; -use crate::event::{EventEmitter, PipelineEvent}; +use crate::event::{EventEmitter, WorkflowRunEvent}; use crate::graph::{Edge, Graph, Node}; use crate::handler::{EngineServices, HandlerRegistry}; use crate::interviewer::Interviewer; @@ -283,16 +283,16 @@ pub fn resolve_thread_id( // --- Run directory helpers (spec 5.6) --- -/// Write manifest.json at the start of a pipeline run. Returns the manifest value. +/// Write manifest.json at the start of a workflow run. Returns the manifest value. fn write_manifest(logs_root: &Path, graph: &Graph, config: &RunConfig) -> serde_json::Value { - let pipeline_name = if graph.name.is_empty() { + let workflow_name = if graph.name.is_empty() { "unnamed" } else { &graph.name }; let mut manifest = serde_json::json!({ "run_id": config.run_id, - "pipeline_name": pipeline_name, + "workflow_name": workflow_name, "goal": graph.goal(), "start_time": Utc::now().to_rfc3339(), "node_count": graph.nodes.len(), @@ -514,9 +514,9 @@ fn is_terminal(node: &Node) -> bool { node.shape() == "Msquare" || node.handler_type() == Some("exit") } -// --- Pipeline engine --- +// --- Workflow run engine --- -/// Captured git state for a pipeline run, shared with handlers. +/// Captured git state for a workflow run, shared with handlers. #[derive(Debug, Clone)] pub struct GitState { pub mode: GitCheckpointMode, @@ -526,7 +526,7 @@ pub struct GitState { pub meta_branch: Option, } -/// How git checkpointing should be performed for a pipeline run. +/// How git checkpointing should be performed for a workflow run. #[derive(Debug, Clone)] pub enum GitCheckpointMode { /// Run git commands on the host filesystem (local & Docker bind-mount). @@ -724,12 +724,12 @@ pub async fn git_replace_worktree_remote( git_add_worktree_remote(exec_env, path, branch).await } -/// Configuration for a pipeline run. +/// Configuration for a workflow run. pub struct RunConfig { pub logs_root: PathBuf, pub cancel_token: Option>, pub dry_run: bool, - /// Unique identifier for this pipeline run. + /// Unique identifier for this workflow run. pub run_id: String, /// Git checkpoint mode (None = no checkpointing). pub git_checkpoint: Option, @@ -743,13 +743,13 @@ pub struct RunConfig { pub labels: HashMap, } -/// The pipeline execution engine. -pub struct PipelineEngine { +/// The workflow run execution engine. +pub struct WorkflowRunEngine { services: EngineServices, pub interviewer: Option>, } -impl PipelineEngine { +impl WorkflowRunEngine { #[must_use] pub fn new( registry: HandlerRegistry, @@ -884,7 +884,7 @@ impl PipelineEngine { // Gap #7: Check should_retry predicate before retrying if attempt < policy.max_attempts && handler.should_retry(&e) { let delay = policy.backoff.delay_for_attempt(attempt); - self.services.emitter.emit(&PipelineEvent::StageFailed { + self.services.emitter.emit(&WorkflowRunEvent::StageFailed { name: node.label().to_string(), index: stage_index, error: e.to_string(), @@ -892,7 +892,7 @@ impl PipelineEngine { failure_reason: None, failure_class: Some(e.failure_class().to_string()), }); - self.services.emitter.emit(&PipelineEvent::StageRetrying { + self.services.emitter.emit(&WorkflowRunEvent::StageRetrying { name: node.label().to_string(), index: stage_index, attempt: usize::try_from(attempt).unwrap_or(usize::MAX), @@ -917,7 +917,7 @@ impl PipelineEngine { StageStatus::Retry => { if attempt < policy.max_attempts { let delay = policy.backoff.delay_for_attempt(attempt); - self.services.emitter.emit(&PipelineEvent::StageRetrying { + self.services.emitter.emit(&WorkflowRunEvent::StageRetrying { name: node.label().to_string(), index: stage_index, attempt: usize::try_from(attempt).unwrap_or(usize::MAX), @@ -946,7 +946,7 @@ impl PipelineEngine { Ok((Outcome::fail("max retries exceeded"), policy.max_attempts)) } - /// Run the pipeline. Returns the final outcome. + /// Run the workflow. Returns the final outcome. /// /// # Errors /// @@ -959,7 +959,7 @@ impl PipelineEngine { Ok(outcome) } - /// Run a pipeline seeded with an existing context. Returns both the outcome + /// Run a workflow seeded with an existing context. Returns both the outcome /// and the final context so the caller can diff changes. pub async fn run_with_context( &self, @@ -1028,7 +1028,7 @@ impl PipelineEngine { }; self.services.set_git_state(git_state); - self.services.emitter.emit(&PipelineEvent::PipelineStarted { + self.services.emitter.emit(&WorkflowRunEvent::WorkflowRunStarted { name: graph.name.clone(), run_id: run_id.clone(), base_sha: config.base_sha.clone(), @@ -1038,7 +1038,7 @@ impl PipelineEngine { _ => None, }, }); - self.inform(&format!("Pipeline started: {}", graph.name), "pipeline"); + self.inform(&format!("Run started: {}", graph.name), "run"); // Write manifest.json (spec 5.6) let manifest = write_manifest(&config.logs_root, graph, config); @@ -1205,9 +1205,9 @@ impl PipelineEngine { .or_insert(0); *count += 1; if max_node_visits > 0 && *count >= max_node_visits { - tracing::warn!(node = %current_node_id, visits = *count, limit = max_node_visits, "Node visit limit exceeded, pipeline stuck in cycle"); + tracing::warn!(node = %current_node_id, visits = *count, limit = max_node_visits, "Node visit limit exceeded, run stuck in cycle"); return Err(ArcError::Engine(format!( - "node \"{}\" visited {count} times (limit {max_node_visits}); pipeline is stuck in a cycle", + "node \"{}\" visited {count} times (limit {max_node_visits}); run is stuck in a cycle", current_node_id ))); } @@ -1225,7 +1225,7 @@ impl PipelineEngine { let error_msg = format!( "goal gate unsatisfied for node {failed_node_id} and no retry target" ); - self.services.emitter.emit(&PipelineEvent::PipelineFailed { + self.services.emitter.emit(&WorkflowRunEvent::WorkflowRunFailed { error: error_msg.clone(), duration_ms, git_commit_sha: last_git_sha.clone(), @@ -1273,7 +1273,7 @@ impl PipelineEngine { context.set("current_node", serde_json::json!(&node.id)); let retry_policy = build_retry_policy(node, graph); - self.services.emitter.emit(&PipelineEvent::StageStarted { + self.services.emitter.emit(&WorkflowRunEvent::StageStarted { name: node.label().to_string(), index: stage_index, handler_type: node.handler_type().map(String::from), @@ -1292,7 +1292,7 @@ impl PipelineEngine { ) => result?, () = token.cancelled() => { let idle_secs = graph.stall_timeout().map_or(0, |d| d.as_secs()); - self.services.emitter.emit(&PipelineEvent::StallWatchdogTimeout { + self.services.emitter.emit(&WorkflowRunEvent::StallWatchdogTimeout { node: node.id.clone(), idle_seconds: idle_secs, }); @@ -1361,7 +1361,7 @@ impl PipelineEngine { }; if outcome.status == StageStatus::Fail { - self.services.emitter.emit(&PipelineEvent::StageFailed { + self.services.emitter.emit(&WorkflowRunEvent::StageFailed { name: node.label().to_string(), index: stage_index, error: outcome @@ -1374,7 +1374,7 @@ impl PipelineEngine { failure_class: outcome_failure_class.map(|fc| fc.to_string()), }); } else { - self.services.emitter.emit(&PipelineEvent::StageCompleted { + self.services.emitter.emit(&WorkflowRunEvent::StageCompleted { name: node.label().to_string(), index: stage_index, duration_ms: stage_duration_ms, @@ -1434,7 +1434,7 @@ impl PipelineEngine { // Step 5: Select next edge (done before checkpoint so we can store next_node_id) let next_edge = select_edge(&node.id, &outcome, &context, graph); if let Some(edge) = next_edge { - self.services.emitter.emit(&PipelineEvent::EdgeSelected { + self.services.emitter.emit(&WorkflowRunEvent::EdgeSelected { from_node: node.id.clone(), to_node: edge.to.clone(), label: edge.label().map(String::from), @@ -1458,7 +1458,7 @@ impl PipelineEngine { if let Err(e) = checkpoint.save(&checkpoint_path) { context.append_log(format!("checkpoint save failed: {e}")); } else { - self.services.emitter.emit(&PipelineEvent::CheckpointSaved { + self.services.emitter.emit(&WorkflowRunEvent::CheckpointSaved { node_id: node.id.clone(), }); } @@ -1539,7 +1539,7 @@ impl PipelineEngine { if let Err(e) = checkpoint.save(&checkpoint_path) { context.append_log(format!("checkpoint re-save with SHA failed: {e}")); } - self.services.emitter.emit(&PipelineEvent::GitCheckpoint { + self.services.emitter.emit(&WorkflowRunEvent::GitCheckpoint { run_id: run_id.clone(), node_id: node.id.clone(), status: outcome.status.to_string(), @@ -1587,7 +1587,7 @@ impl PipelineEngine { let duration_ms = millis_u64(run_start.elapsed()); let error_msg = format!("stage {} failed with no outgoing fail edge", node.id); - self.services.emitter.emit(&PipelineEvent::PipelineFailed { + self.services.emitter.emit(&WorkflowRunEvent::WorkflowRunFailed { error: error_msg.clone(), duration_ms, git_commit_sha: last_git_sha.clone(), @@ -1625,7 +1625,7 @@ impl PipelineEngine { ))); } } - self.services.emitter.emit(&PipelineEvent::LoopRestart { + self.services.emitter.emit(&WorkflowRunEvent::LoopRestart { from_node: node.id.clone(), to_node: edge.to.clone(), }); @@ -1663,7 +1663,7 @@ impl PipelineEngine { }; self.services .emitter - .emit(&PipelineEvent::PipelineCompleted { + .emit(&WorkflowRunEvent::WorkflowRunCompleted { duration_ms, artifact_count: artifact_store.list().len(), total_cost, @@ -2300,7 +2300,7 @@ mod tests { assert!(!is_terminal(&n)); } - // --- PipelineEngine integration tests --- + // --- WorkflowRunEngine integration tests --- fn simple_graph() -> Graph { let mut g = Graph::new("test_pipeline"); @@ -2336,11 +2336,11 @@ mod tests { } #[tokio::test] - async fn engine_runs_simple_pipeline() { + async fn engine_runs_simple_workflow() { let dir = tempfile::tempdir().unwrap(); let g = simple_graph(); let engine = - PipelineEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); + WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -2361,7 +2361,7 @@ mod tests { let dir = tempfile::tempdir().unwrap(); let g = simple_graph(); let engine = - PipelineEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); + WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -2390,7 +2390,7 @@ mod tests { events_clone.lock().unwrap().push(format!("{event:?}")); }); - let engine = PipelineEngine::new(make_registry(), Arc::new(emitter), local_env()); + let engine = WorkflowRunEngine::new(make_registry(), Arc::new(emitter), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -2405,8 +2405,8 @@ mod tests { engine.run(&g, &config).await.unwrap(); let collected = events.lock().unwrap(); - // Should have: PipelineStarted, StageStarted (start), StageCompleted (start), - // CheckpointSaved, PipelineCompleted + // Should have: RunStarted, StageStarted (start), StageCompleted (start), + // CheckpointSaved, RunCompleted assert!(collected.len() >= 4); } @@ -2415,7 +2415,7 @@ mod tests { let dir = tempfile::tempdir().unwrap(); let g = Graph::new("empty"); let engine = - PipelineEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); + WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -2436,7 +2436,7 @@ mod tests { let dir = tempfile::tempdir().unwrap(); let g = simple_graph(); let engine = - PipelineEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); + WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -2459,7 +2459,7 @@ mod tests { } #[tokio::test] - async fn engine_multi_node_pipeline() { + async fn engine_multi_node_workflow() { let dir = tempfile::tempdir().unwrap(); let mut g = simple_graph(); // Insert a work node between start and exit @@ -2470,7 +2470,7 @@ mod tests { g.edges.push(Edge::new("work", "exit")); let engine = - PipelineEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); + WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -2528,7 +2528,7 @@ mod tests { g.edges.push(Edge::new("path_b", "exit")); let engine = - PipelineEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); + WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -2606,7 +2606,7 @@ mod tests { let dir = tempfile::tempdir().unwrap(); let g = simple_graph(); let engine = - PipelineEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); + WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -2624,7 +2624,7 @@ mod tests { assert!(manifest_path.exists()); let manifest: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(&manifest_path).unwrap()).unwrap(); - assert_eq!(manifest["pipeline_name"], "test_pipeline"); + assert_eq!(manifest["workflow_name"], "test_pipeline"); assert_eq!(manifest["goal"], "Run tests"); assert!(manifest["start_time"].is_string()); assert!(manifest["node_count"].is_number()); @@ -2636,7 +2636,7 @@ mod tests { let dir = tempfile::tempdir().unwrap(); let g = simple_graph(); let engine = - PipelineEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); + WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -2661,7 +2661,7 @@ mod tests { let dir = tempfile::tempdir().unwrap(); let g = simple_graph(); let engine = - PipelineEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); + WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -2686,7 +2686,7 @@ mod tests { let dir = tempfile::tempdir().unwrap(); let g = simple_graph(); let engine = - PipelineEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); + WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -2713,7 +2713,7 @@ mod tests { let dir = tempfile::tempdir().unwrap(); let g = simple_graph(); let engine = - PipelineEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); + WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -2868,7 +2868,7 @@ mod tests { let dir = tempfile::tempdir().unwrap(); let g = simple_graph(); let engine = - PipelineEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); + WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -2907,7 +2907,7 @@ mod tests { g.edges.push(Edge::new("start", "exit")); let engine = - PipelineEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); + WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -2964,7 +2964,7 @@ mod tests { let mut registry = make_registry(); registry.register("always_fail", Box::new(AlwaysFailHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -3023,7 +3023,7 @@ mod tests { let mut registry = make_registry(); registry.register("always_fail", Box::new(AlwaysFailHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -3086,7 +3086,7 @@ mod tests { let mut registry = make_registry(); registry.register("slow", Box::new(SlowHandler { sleep_ms: 500 })); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -3138,7 +3138,7 @@ mod tests { let mut registry = make_registry(); registry.register("slow", Box::new(SlowHandler { sleep_ms: 10 })); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -3191,7 +3191,7 @@ mod tests { let mut registry = make_registry(); registry.register("slow", Box::new(SlowHandler { sleep_ms: 500 })); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -3242,11 +3242,11 @@ mod tests { } #[tokio::test] - async fn engine_calls_inform_on_pipeline_start() { + async fn engine_calls_inform_on_run_start() { let dir = tempfile::tempdir().unwrap(); let g = simple_graph(); let informer = Arc::new(RecordingInformer::new()); - let engine = PipelineEngine::with_interviewer( + let engine = WorkflowRunEngine::with_interviewer( make_registry(), Arc::new(EventEmitter::new()), Arc::clone(&informer) as Arc, @@ -3271,8 +3271,8 @@ mod tests { assert!( messages .iter() - .any(|(msg, stage)| msg.contains("Pipeline started") && stage == "pipeline"), - "expected 'Pipeline started' inform call, got: {messages:?}" + .any(|(msg, stage)| msg.contains("Run started") && stage == "run"), + "expected 'Run started' inform call, got: {messages:?}" ); } @@ -3281,7 +3281,7 @@ mod tests { let dir = tempfile::tempdir().unwrap(); let g = simple_graph(); let informer = Arc::new(RecordingInformer::new()); - let engine = PipelineEngine::with_interviewer( + let engine = WorkflowRunEngine::with_interviewer( make_registry(), Arc::new(EventEmitter::new()), Arc::clone(&informer) as Arc, @@ -3322,7 +3322,7 @@ mod tests { let dir = tempfile::tempdir().unwrap(); let g = simple_graph(); let engine = - PipelineEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); + WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -3345,7 +3345,7 @@ mod tests { let dir = tempfile::tempdir().unwrap(); let g = simple_graph(); let engine = - PipelineEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); + 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(), @@ -3368,7 +3368,7 @@ mod tests { let dir = tempfile::tempdir().unwrap(); let g = simple_graph(); let engine = - PipelineEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); + 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(), @@ -3386,7 +3386,7 @@ mod tests { } #[tokio::test] - async fn engine_cancelled_mid_pipeline() { + async fn engine_cancelled_mid_run() { let dir = tempfile::tempdir().unwrap(); let mut g = simple_graph(); // Insert a work node between start and exit @@ -3405,7 +3405,7 @@ mod tests { let mut registry = make_registry(); registry.register("slow", Box::new(SlowHandler { sleep_ms: 200 })); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: Some(cancel_token), @@ -3478,7 +3478,7 @@ mod tests { g.attrs .insert("max_node_visits".to_string(), AttrValue::Integer(3)); let engine = - PipelineEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); + WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -3504,7 +3504,7 @@ mod tests { let dir = tempfile::tempdir().unwrap(); let g = cyclic_graph(); let engine = - PipelineEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); + WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -3532,7 +3532,7 @@ mod tests { g.attrs .insert("max_node_visits".to_string(), AttrValue::Integer(2)); let engine = - PipelineEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); + WorkflowRunEngine::new(make_registry(), Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -3621,7 +3621,7 @@ mod tests { let mut registry = make_registry(); registry.register("panicker", Box::new(PanickingHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -3846,7 +3846,7 @@ mod tests { let mut registry = make_registry(); registry.register("always_fail", Box::new(AlwaysFailHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -3877,7 +3877,7 @@ mod tests { let mut registry = make_registry(); registry.register("always_fail", Box::new(TransientFailHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -3915,7 +3915,7 @@ mod tests { counter: std::sync::atomic::AtomicUsize::new(0), }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -3938,7 +3938,7 @@ mod tests { #[tokio::test] async fn restart_circuit_breaker_aborts_on_repeated_failure() { - // In a pipeline with loop_restart edges, a repeating deterministic failure + // In a workflow with loop_restart edges, a repeating deterministic failure // triggers a circuit breaker (either loop or restart, depending on topology). let dir = tempfile::tempdir().unwrap(); let mut g = Graph::new("restart_test"); @@ -3993,7 +3993,7 @@ mod tests { let mut registry = make_registry(); registry.register("always_fail", Box::new(AlwaysFailHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -4036,7 +4036,7 @@ mod tests { let start = Instant::now(); while start.elapsed() < Duration::from_millis(self.total_ms) { tokio::time::sleep(Duration::from_millis(self.interval_ms)).await; - services.emitter.emit(&PipelineEvent::Prompt { + services.emitter.emit(&WorkflowRunEvent::Prompt { stage: node.id.clone(), text: "keepalive".to_string(), }); @@ -4084,7 +4084,7 @@ mod tests { let mut registry = make_registry(); registry.register("slow", Box::new(SlowHandler { sleep_ms: 60_000 })); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -4150,7 +4150,7 @@ mod tests { total_ms: 500, }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -4205,7 +4205,7 @@ mod tests { let mut registry = make_registry(); registry.register("slow", Box::new(SlowHandler { sleep_ms: 200 })); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -4224,7 +4224,7 @@ mod tests { #[tokio::test] async fn failure_signature_stored_in_context() { let dir = tempfile::tempdir().unwrap(); - // Simple pipeline: start -> work (fails) -> exit (via fail edge) + // Simple workflow: start -> work (fails) -> exit (via fail edge) let mut g = Graph::new("sig_context_test"); g.attrs .insert("goal".to_string(), AttrValue::String("test".to_string())); @@ -4259,7 +4259,7 @@ mod tests { let mut registry = make_registry(); registry.register("always_fail", Box::new(AlwaysFailHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, diff --git a/crates/arc-workflows/src/event.rs b/crates/arc-workflows/src/event.rs index 9029077b1..a0d246417 100644 --- a/crates/arc-workflows/src/event.rs +++ b/crates/arc-workflows/src/event.rs @@ -5,10 +5,10 @@ use serde::{Deserialize, Serialize}; use crate::outcome::StageUsage; use arc_agent::{AgentEvent, ExecutionEnvEvent}; -/// Events emitted during pipeline execution for observability. +/// Events emitted during workflow run execution for observability. #[derive(Debug, Clone, Serialize, Deserialize)] -pub enum PipelineEvent { - PipelineStarted { +pub enum WorkflowRunEvent { + WorkflowRunStarted { name: String, run_id: String, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -18,7 +18,7 @@ pub enum PipelineEvent { #[serde(default, skip_serializing_if = "Option::is_none")] worktree_dir: Option, }, - PipelineCompleted { + WorkflowRunCompleted { duration_ms: u64, artifact_count: usize, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -26,7 +26,7 @@ pub enum PipelineEvent { #[serde(default, skip_serializing_if = "Option::is_none")] final_git_commit_sha: Option, }, - PipelineFailed { + WorkflowRunFailed { error: String, duration_ms: u64, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -127,7 +127,7 @@ pub enum PipelineEvent { stage: String, text: String, }, - /// Forwarded from an agent session, tagged with the pipeline stage. + /// Forwarded from an agent session, tagged with the workflow stage. Agent { stage: String, event: AgentEvent, @@ -179,24 +179,24 @@ pub enum PipelineEvent { }, } -impl PipelineEvent { +impl WorkflowRunEvent { pub fn trace(&self) { use tracing::{debug, error, info, warn}; match self { - Self::PipelineStarted { name, run_id, .. } => { - info!(pipeline = name.as_str(), run_id, "Pipeline started"); + Self::WorkflowRunStarted { name, run_id, .. } => { + info!(workflow = name.as_str(), run_id, "Workflow run started"); } - Self::PipelineCompleted { + Self::WorkflowRunCompleted { duration_ms, artifact_count, .. } => { - info!(duration_ms, artifact_count, "Pipeline completed"); + info!(duration_ms, artifact_count, "Workflow run completed"); } - Self::PipelineFailed { + Self::WorkflowRunFailed { error, duration_ms, .. } => { - error!(error, duration_ms, "Pipeline failed"); + error!(error, duration_ms, "Workflow run failed"); } Self::StageStarted { name, @@ -446,10 +446,10 @@ fn epoch_millis() -> i64 { .as_millis() as i64 } -/// Listener callback type for pipeline events. -type EventListener = Box; +/// Listener callback type for workflow run events. +type EventListener = Box; -/// Callback-based event emitter for pipeline events. +/// Callback-based event emitter for workflow run events. pub struct EventEmitter { listeners: Vec, /// Epoch milliseconds of the last `emit()` or `touch()` call. 0 until first event. @@ -480,11 +480,11 @@ impl EventEmitter { } } - pub fn on_event(&mut self, listener: impl Fn(&PipelineEvent) + Send + Sync + 'static) { + pub fn on_event(&mut self, listener: impl Fn(&WorkflowRunEvent) + Send + Sync + 'static) { self.listeners.push(Box::new(listener)); } - pub fn emit(&self, event: &PipelineEvent) { + pub fn emit(&self, event: &WorkflowRunEvent) { self.last_event_at.store(epoch_millis(), Ordering::Relaxed); event.trace(); for listener in &self.listeners { @@ -498,7 +498,7 @@ impl EventEmitter { self.last_event_at.load(Ordering::Relaxed) } - /// Manually update the last-event timestamp (e.g. to seed the watchdog at pipeline start). + /// Manually update the last-event timestamp (e.g. to seed the watchdog at workflow run start). pub fn touch(&self) { self.last_event_at.store(epoch_millis(), Ordering::Relaxed); } @@ -523,12 +523,12 @@ mod tests { let received_clone = Arc::clone(&received); emitter.on_event(move |event| { let name = match event { - PipelineEvent::PipelineStarted { name, .. } => name.clone(), + WorkflowRunEvent::WorkflowRunStarted { name, .. } => name.clone(), _ => "other".to_string(), }; received_clone.lock().unwrap().push(name); }); - emitter.emit(&PipelineEvent::PipelineStarted { + emitter.emit(&WorkflowRunEvent::WorkflowRunStarted { name: "test".to_string(), run_id: "1".to_string(), base_sha: None, @@ -541,8 +541,8 @@ mod tests { } #[test] - fn pipeline_event_serialization() { - let event = PipelineEvent::StageStarted { + fn workflow_run_event_serialization() { + let event = WorkflowRunEvent::StageStarted { name: "plan".to_string(), index: 0, handler_type: Some("codergen".to_string()), @@ -557,7 +557,7 @@ mod tests { assert!(json.contains("\"max_attempts\":3")); // None handler_type serializes as null - let event_none = PipelineEvent::StageStarted { + let event_none = WorkflowRunEvent::StageStarted { name: "plan".to_string(), index: 0, handler_type: None, @@ -576,7 +576,7 @@ mod tests { #[test] fn agent_event_wrapper_serialization() { - let event = PipelineEvent::Agent { + let event = WorkflowRunEvent::Agent { stage: "plan".to_string(), event: AgentEvent::ToolCallStarted { tool_name: "read_file".to_string(), @@ -591,13 +591,13 @@ mod tests { assert!(json.contains("plan")); // Verify round-trip - let deserialized: PipelineEvent = serde_json::from_str(&json).unwrap(); - assert!(matches!(deserialized, PipelineEvent::Agent { stage, .. } if stage == "plan")); + let deserialized: WorkflowRunEvent = serde_json::from_str(&json).unwrap(); + assert!(matches!(deserialized, WorkflowRunEvent::Agent { stage, .. } if stage == "plan")); } #[test] fn agent_assistant_message_serialization() { - let event = PipelineEvent::Agent { + let event = WorkflowRunEvent::Agent { stage: "code".to_string(), event: AgentEvent::AssistantMessage { text: "Here is the implementation".to_string(), @@ -621,9 +621,9 @@ mod tests { assert!(json.contains("\"reasoning_tokens\":100")); // Round-trip - let deserialized: PipelineEvent = serde_json::from_str(&json).unwrap(); + let deserialized: WorkflowRunEvent = serde_json::from_str(&json).unwrap(); match deserialized { - PipelineEvent::Agent { + WorkflowRunEvent::Agent { event: AgentEvent::AssistantMessage { usage, .. }, .. } => { @@ -636,7 +636,7 @@ mod tests { #[test] fn agent_assistant_message_without_cache_tokens_omits_them() { - let event = PipelineEvent::Agent { + let event = WorkflowRunEvent::Agent { stage: "code".to_string(), event: AgentEvent::AssistantMessage { text: "response".to_string(), @@ -657,7 +657,7 @@ mod tests { #[test] fn stage_completed_event_serialization_with_new_fields() { - let event = PipelineEvent::StageCompleted { + let event = WorkflowRunEvent::StageCompleted { name: "plan".to_string(), index: 0, duration_ms: 1500, @@ -680,7 +680,7 @@ mod tests { assert!(json.contains("\"max_attempts\":3")); assert!(json.contains("\"failure_class\":null")); - let event_none = PipelineEvent::StageCompleted { + let event_none = WorkflowRunEvent::StageCompleted { name: "plan".to_string(), index: 0, duration_ms: 1500, @@ -702,7 +702,7 @@ mod tests { #[test] fn stage_failed_event_serialization() { - let event = PipelineEvent::StageFailed { + let event = WorkflowRunEvent::StageFailed { name: "plan".to_string(), index: 0, error: "timeout".to_string(), @@ -714,12 +714,12 @@ mod tests { assert!(json.contains("\"failure_reason\":\"LLM request timed out\"")); assert!(json.contains("\"failure_class\":\"transient\"")); - let deserialized: PipelineEvent = serde_json::from_str(&json).unwrap(); + let deserialized: WorkflowRunEvent = serde_json::from_str(&json).unwrap(); assert!( - matches!(deserialized, PipelineEvent::StageFailed { failure_class: Some(fc), .. } if fc == "transient") + matches!(deserialized, WorkflowRunEvent::StageFailed { failure_class: Some(fc), .. } if fc == "transient") ); - let event_none = PipelineEvent::StageFailed { + let event_none = WorkflowRunEvent::StageFailed { name: "plan".to_string(), index: 0, error: "timeout".to_string(), @@ -734,7 +734,7 @@ mod tests { #[test] fn parallel_branch_completed_event_serialization() { - let event = PipelineEvent::ParallelBranchCompleted { + let event = WorkflowRunEvent::ParallelBranchCompleted { branch: "branch_a".to_string(), index: 0, duration_ms: 1500, @@ -744,15 +744,15 @@ mod tests { assert!(json.contains("\"status\":\"success\"")); assert!(!json.contains("\"success\":")); - let deserialized: PipelineEvent = serde_json::from_str(&json).unwrap(); + let deserialized: WorkflowRunEvent = serde_json::from_str(&json).unwrap(); assert!( - matches!(deserialized, PipelineEvent::ParallelBranchCompleted { status, .. } if status == "success") + matches!(deserialized, WorkflowRunEvent::ParallelBranchCompleted { status, .. } if status == "success") ); } #[test] fn parallel_started_event_serialization() { - let event = PipelineEvent::ParallelStarted { + let event = WorkflowRunEvent::ParallelStarted { branch_count: 3, join_policy: "wait_all".to_string(), error_policy: "continue".to_string(), @@ -761,15 +761,15 @@ mod tests { assert!(json.contains("\"join_policy\":\"wait_all\"")); assert!(json.contains("\"error_policy\":\"continue\"")); - let deserialized: PipelineEvent = serde_json::from_str(&json).unwrap(); + let deserialized: WorkflowRunEvent = serde_json::from_str(&json).unwrap(); assert!( - matches!(deserialized, PipelineEvent::ParallelStarted { join_policy, error_policy, .. } if join_policy == "wait_all" && error_policy == "continue") + matches!(deserialized, WorkflowRunEvent::ParallelStarted { join_policy, error_policy, .. } if join_policy == "wait_all" && error_policy == "continue") ); } #[test] fn interview_started_event_serialization() { - let event = PipelineEvent::InterviewStarted { + let event = WorkflowRunEvent::InterviewStarted { question: "Review changes?".to_string(), stage: "gate".to_string(), question_type: "multiple_choice".to_string(), @@ -777,15 +777,15 @@ mod tests { let json = serde_json::to_string(&event).unwrap(); assert!(json.contains("\"question_type\":\"multiple_choice\"")); - let deserialized: PipelineEvent = serde_json::from_str(&json).unwrap(); + let deserialized: WorkflowRunEvent = serde_json::from_str(&json).unwrap(); assert!( - matches!(deserialized, PipelineEvent::InterviewStarted { question_type, .. } if question_type == "multiple_choice") + matches!(deserialized, WorkflowRunEvent::InterviewStarted { question_type, .. } if question_type == "multiple_choice") ); } #[test] fn agent_compaction_event_serialization() { - let started = PipelineEvent::Agent { + let started = WorkflowRunEvent::Agent { stage: "code".to_string(), event: AgentEvent::CompactionStarted { estimated_tokens: 5000, @@ -794,10 +794,10 @@ mod tests { }; let json = serde_json::to_string(&started).unwrap(); assert!(json.contains("CompactionStarted")); - let deserialized: PipelineEvent = serde_json::from_str(&json).unwrap(); - assert!(matches!(deserialized, PipelineEvent::Agent { stage, .. } if stage == "code")); + let deserialized: WorkflowRunEvent = serde_json::from_str(&json).unwrap(); + assert!(matches!(deserialized, WorkflowRunEvent::Agent { stage, .. } if stage == "code")); - let completed = PipelineEvent::Agent { + let completed = WorkflowRunEvent::Agent { stage: "code".to_string(), event: AgentEvent::CompactionCompleted { original_turn_count: 20, @@ -808,13 +808,13 @@ mod tests { }; let json = serde_json::to_string(&completed).unwrap(); assert!(json.contains("CompactionCompleted")); - let deserialized: PipelineEvent = serde_json::from_str(&json).unwrap(); - assert!(matches!(deserialized, PipelineEvent::Agent { stage, .. } if stage == "code")); + let deserialized: WorkflowRunEvent = serde_json::from_str(&json).unwrap(); + assert!(matches!(deserialized, WorkflowRunEvent::Agent { stage, .. } if stage == "code")); } #[test] fn edge_selected_event_serialization() { - let event = PipelineEvent::EdgeSelected { + let event = WorkflowRunEvent::EdgeSelected { from_node: "plan".to_string(), to_node: "code".to_string(), label: Some("success".to_string()), @@ -827,13 +827,13 @@ mod tests { assert!(json.contains("\"label\":\"success\"")); assert!(json.contains("\"condition\":\"outcome == 'success'\"")); - let deserialized: PipelineEvent = serde_json::from_str(&json).unwrap(); + let deserialized: WorkflowRunEvent = serde_json::from_str(&json).unwrap(); assert!( - matches!(deserialized, PipelineEvent::EdgeSelected { from_node, to_node, .. } if from_node == "plan" && to_node == "code") + matches!(deserialized, WorkflowRunEvent::EdgeSelected { from_node, to_node, .. } if from_node == "plan" && to_node == "code") ); // None label/condition - let event_none = PipelineEvent::EdgeSelected { + let event_none = WorkflowRunEvent::EdgeSelected { from_node: "a".to_string(), to_node: "b".to_string(), label: None, @@ -846,7 +846,7 @@ mod tests { #[test] fn loop_restart_event_serialization() { - let event = PipelineEvent::LoopRestart { + let event = WorkflowRunEvent::LoopRestart { from_node: "review".to_string(), to_node: "code".to_string(), }; @@ -855,15 +855,15 @@ mod tests { assert!(json.contains("\"from_node\":\"review\"")); assert!(json.contains("\"to_node\":\"code\"")); - let deserialized: PipelineEvent = serde_json::from_str(&json).unwrap(); + let deserialized: WorkflowRunEvent = serde_json::from_str(&json).unwrap(); assert!( - matches!(deserialized, PipelineEvent::LoopRestart { from_node, to_node } if from_node == "review" && to_node == "code") + matches!(deserialized, WorkflowRunEvent::LoopRestart { from_node, to_node } if from_node == "review" && to_node == "code") ); } #[test] fn stage_retrying_event_serialization() { - let event = PipelineEvent::StageRetrying { + let event = WorkflowRunEvent::StageRetrying { name: "lint".to_string(), index: 2, attempt: 3, @@ -876,10 +876,10 @@ mod tests { assert!(json.contains("\"max_attempts\":5")); assert!(json.contains("\"delay_ms\":400")); - let deserialized: PipelineEvent = serde_json::from_str(&json).unwrap(); + let deserialized: WorkflowRunEvent = serde_json::from_str(&json).unwrap(); assert!(matches!( deserialized, - PipelineEvent::StageRetrying { + WorkflowRunEvent::StageRetrying { max_attempts: 5, .. } @@ -888,7 +888,7 @@ mod tests { #[test] fn agent_llm_retry_event_serialization() { - let event = PipelineEvent::Agent { + let event = WorkflowRunEvent::Agent { stage: "code".to_string(), event: AgentEvent::LlmRetry { provider: "anthropic".to_string(), @@ -903,13 +903,13 @@ mod tests { assert!(json.contains("\"provider\":\"anthropic\"")); assert!(json.contains("\"delay_secs\":1.5")); - let deserialized: PipelineEvent = serde_json::from_str(&json).unwrap(); - assert!(matches!(deserialized, PipelineEvent::Agent { stage, .. } if stage == "code")); + let deserialized: WorkflowRunEvent = serde_json::from_str(&json).unwrap(); + assert!(matches!(deserialized, WorkflowRunEvent::Agent { stage, .. } if stage == "code")); } #[test] fn parallel_early_termination_event_serialization() { - let event = PipelineEvent::ParallelEarlyTermination { + let event = WorkflowRunEvent::ParallelEarlyTermination { reason: "fail_fast_branch_failed".to_string(), completed_count: 2, pending_count: 3, @@ -919,10 +919,10 @@ mod tests { assert!(json.contains("\"completed_count\":2")); assert!(json.contains("\"pending_count\":3")); - let deserialized: PipelineEvent = serde_json::from_str(&json).unwrap(); + let deserialized: WorkflowRunEvent = serde_json::from_str(&json).unwrap(); assert!(matches!( deserialized, - PipelineEvent::ParallelEarlyTermination { + WorkflowRunEvent::ParallelEarlyTermination { completed_count: 2, .. } @@ -931,7 +931,7 @@ mod tests { #[test] fn subgraph_started_event_serialization() { - let event = PipelineEvent::SubgraphStarted { + let event = WorkflowRunEvent::SubgraphStarted { node_id: "sub_1".to_string(), start_node: "start".to_string(), }; @@ -940,15 +940,15 @@ mod tests { assert!(json.contains("\"node_id\":\"sub_1\"")); assert!(json.contains("\"start_node\":\"start\"")); - let deserialized: PipelineEvent = serde_json::from_str(&json).unwrap(); + let deserialized: WorkflowRunEvent = serde_json::from_str(&json).unwrap(); assert!( - matches!(deserialized, PipelineEvent::SubgraphStarted { node_id, .. } if node_id == "sub_1") + matches!(deserialized, WorkflowRunEvent::SubgraphStarted { node_id, .. } if node_id == "sub_1") ); } #[test] fn subgraph_completed_event_serialization() { - let event = PipelineEvent::SubgraphCompleted { + let event = WorkflowRunEvent::SubgraphCompleted { node_id: "sub_1".to_string(), steps_executed: 5, status: "success".to_string(), @@ -959,10 +959,10 @@ mod tests { assert!(json.contains("\"steps_executed\":5")); assert!(json.contains("\"duration_ms\":3200")); - let deserialized: PipelineEvent = serde_json::from_str(&json).unwrap(); + let deserialized: WorkflowRunEvent = serde_json::from_str(&json).unwrap(); assert!(matches!( deserialized, - PipelineEvent::SubgraphCompleted { + WorkflowRunEvent::SubgraphCompleted { steps_executed: 5, .. } @@ -973,7 +973,7 @@ mod tests { fn execution_env_event_wrapper_serialization() { use arc_agent::ExecutionEnvEvent; - let event = PipelineEvent::ExecutionEnv { + let event = WorkflowRunEvent::ExecutionEnv { event: ExecutionEnvEvent::Initializing { env_type: "docker".into(), }, @@ -983,8 +983,8 @@ mod tests { assert!(json.contains("Initializing")); assert!(json.contains("docker")); - let deserialized: PipelineEvent = serde_json::from_str(&json).unwrap(); - assert!(matches!(deserialized, PipelineEvent::ExecutionEnv { .. })); + let deserialized: WorkflowRunEvent = serde_json::from_str(&json).unwrap(); + assert!(matches!(deserialized, WorkflowRunEvent::ExecutionEnv { .. })); } #[test] @@ -997,7 +997,7 @@ mod tests { fn emitter_last_event_at_updates_after_emit() { let emitter = EventEmitter::new(); assert_eq!(emitter.last_event_at(), 0); - emitter.emit(&PipelineEvent::PipelineStarted { + emitter.emit(&WorkflowRunEvent::WorkflowRunStarted { name: "test".to_string(), run_id: "1".to_string(), base_sha: None, @@ -1017,7 +1017,7 @@ mod tests { #[test] fn stall_watchdog_timeout_serialization() { - let event = PipelineEvent::StallWatchdogTimeout { + let event = WorkflowRunEvent::StallWatchdogTimeout { node: "work".to_string(), idle_seconds: 600, }; @@ -1026,28 +1026,28 @@ mod tests { assert!(json.contains("\"node\":\"work\"")); assert!(json.contains("\"idle_seconds\":600")); - let deserialized: PipelineEvent = serde_json::from_str(&json).unwrap(); + let deserialized: WorkflowRunEvent = serde_json::from_str(&json).unwrap(); assert!( - matches!(deserialized, PipelineEvent::StallWatchdogTimeout { node, idle_seconds } if node == "work" && idle_seconds == 600) + matches!(deserialized, WorkflowRunEvent::StallWatchdogTimeout { node, idle_seconds } if node == "work" && idle_seconds == 600) ); } #[test] fn setup_events_serialization() { let events = vec![ - PipelineEvent::SetupStarted { command_count: 3 }, - PipelineEvent::SetupCommandStarted { + WorkflowRunEvent::SetupStarted { command_count: 3 }, + WorkflowRunEvent::SetupCommandStarted { command: "npm install".into(), index: 0, }, - PipelineEvent::SetupCommandCompleted { + WorkflowRunEvent::SetupCommandCompleted { command: "npm install".into(), index: 0, exit_code: 0, duration_ms: 5000, }, - PipelineEvent::SetupCompleted { duration_ms: 8000 }, - PipelineEvent::SetupFailed { + WorkflowRunEvent::SetupCompleted { duration_ms: 8000 }, + WorkflowRunEvent::SetupFailed { command: "npm test".into(), index: 1, exit_code: 1, @@ -1057,7 +1057,7 @@ mod tests { for event in &events { let json = serde_json::to_string(event).unwrap(); - let deserialized: PipelineEvent = serde_json::from_str(&json).unwrap(); + let deserialized: WorkflowRunEvent = serde_json::from_str(&json).unwrap(); let json2 = serde_json::to_string(&deserialized).unwrap(); assert_eq!(json, json2); } diff --git a/crates/arc-workflows/src/handler/manager_loop.rs b/crates/arc-workflows/src/handler/manager_loop.rs index c3e0e2ffd..697c4667c 100644 --- a/crates/arc-workflows/src/handler/manager_loop.rs +++ b/crates/arc-workflows/src/handler/manager_loop.rs @@ -8,15 +8,15 @@ use async_trait::async_trait; use crate::condition::evaluate_condition; use crate::context::Context; -use crate::engine::{PipelineEngine, RunConfig}; +use crate::engine::{WorkflowRunEngine, RunConfig}; use crate::error::ArcError; use crate::graph::{Graph, Node}; use crate::outcome::{Outcome, StageStatus}; -use crate::pipeline::prepare_pipeline; +use crate::workflow::prepare_workflow; use super::{EngineServices, Handler}; -/// Orchestrates a child pipeline engine, polling for completion or stop conditions. +/// Orchestrates a child workflow engine, polling for completion or stop conditions. pub struct SubWorkflowHandler; /// Parse a duration string like "45s", "200ms", "5m" into a Duration. @@ -110,7 +110,7 @@ impl Handler for SubWorkflowHandler { Err(e) => return Ok(Outcome::fail(e.to_string())), }; - let child_graph = match prepare_pipeline(&dot_source) { + let child_graph = match prepare_workflow(&dot_source) { Ok(g) => g, Err(e) => { return Ok(Outcome::fail(format!( @@ -151,7 +151,7 @@ impl Handler for SubWorkflowHandler { let before_snapshot = context.snapshot(); // Spawn child engine - let engine = PipelineEngine::from_services(services); + let engine = WorkflowRunEngine::from_services(services); let mut child_handle = tokio::spawn( async move { engine.run_with_context(&child_graph, &child_config, child_context).await }, diff --git a/crates/arc-workflows/src/handler/parallel.rs b/crates/arc-workflows/src/handler/parallel.rs index 0b12d8bfe..622504ec9 100644 --- a/crates/arc-workflows/src/handler/parallel.rs +++ b/crates/arc-workflows/src/handler/parallel.rs @@ -9,7 +9,7 @@ use tokio::sync::Semaphore; use crate::context::Context; use crate::engine::GitCheckpointMode; use crate::error::ArcError; -use crate::event::PipelineEvent; +use crate::event::WorkflowRunEvent; use crate::graph::{Graph, Node}; use crate::outcome::{Outcome, StageStatus}; @@ -208,7 +208,7 @@ impl Handler for ParallelHandler { .unwrap_or("continue"), ); - services.emitter.emit(&PipelineEvent::ParallelStarted { + services.emitter.emit(&WorkflowRunEvent::ParallelStarted { branch_count: branches.len(), join_policy: join_policy.to_string(), error_policy: error_policy.to_string(), @@ -391,7 +391,7 @@ impl Handler for ParallelHandler { .await .map_err(|e| ArcError::Handler(format!("semaphore error: {e}")))?; - emitter.emit(&PipelineEvent::ParallelBranchStarted { + emitter.emit(&WorkflowRunEvent::ParallelBranchStarted { branch: setup.target_id.clone(), index: setup.branch_index, }); @@ -400,7 +400,7 @@ impl Handler for ParallelHandler { let Some(target_node) = graph.nodes.get(&setup.target_id) else { let outcome = Outcome::fail(format!("branch target node not found: {}", setup.target_id)); - emitter.emit(&PipelineEvent::ParallelBranchCompleted { + emitter.emit(&WorkflowRunEvent::ParallelBranchCompleted { branch: setup.target_id.clone(), index: setup.branch_index, duration_ms: millis_u64(branch_start.elapsed()), @@ -466,7 +466,7 @@ impl Handler for ParallelHandler { None }; - emitter.emit(&PipelineEvent::ParallelBranchCompleted { + emitter.emit(&WorkflowRunEvent::ParallelBranchCompleted { branch: setup.target_id.clone(), index: setup.branch_index, duration_ms: millis_u64(branch_start.elapsed()), @@ -495,7 +495,7 @@ impl Handler for ParallelHandler { results.push(result); services .emitter - .emit(&PipelineEvent::ParallelEarlyTermination { + .emit(&WorkflowRunEvent::ParallelEarlyTermination { reason: "fail_fast_branch_failed".to_string(), completed_count: results.len(), pending_count: total_branches - handle_index - 1, @@ -515,7 +515,7 @@ impl Handler for ParallelHandler { results.push(result); services .emitter - .emit(&PipelineEvent::ParallelEarlyTermination { + .emit(&WorkflowRunEvent::ParallelEarlyTermination { reason: "fail_fast_handler_error".to_string(), completed_count: results.len(), pending_count: total_branches - handle_index - 1, @@ -535,7 +535,7 @@ impl Handler for ParallelHandler { results.push(result); services .emitter - .emit(&PipelineEvent::ParallelEarlyTermination { + .emit(&WorkflowRunEvent::ParallelEarlyTermination { reason: "fail_fast_join_error".to_string(), completed_count: results.len(), pending_count: total_branches - handle_index - 1, @@ -634,7 +634,7 @@ impl Handler for ParallelHandler { let _ = tokio::fs::write(node_dir.join("parallel_results.json"), json).await; } - services.emitter.emit(&PipelineEvent::ParallelCompleted { + services.emitter.emit(&WorkflowRunEvent::ParallelCompleted { duration_ms: millis_u64(parallel_start.elapsed()), success_count, failure_count: fail_count, diff --git a/crates/arc-workflows/src/handler/wait_human.rs b/crates/arc-workflows/src/handler/wait_human.rs index 8a8e54714..d8cb1909d 100644 --- a/crates/arc-workflows/src/handler/wait_human.rs +++ b/crates/arc-workflows/src/handler/wait_human.rs @@ -6,7 +6,7 @@ use async_trait::async_trait; use crate::context::Context; use crate::error::ArcError; -use crate::event::{EventEmitter, PipelineEvent}; +use crate::event::{EventEmitter, WorkflowRunEvent}; use crate::graph::{Graph, Node}; use crate::interviewer::{ Answer, AnswerValue, Interviewer, Question, QuestionOption, QuestionType, @@ -90,7 +90,7 @@ impl WaitHumanHandler { self } - fn emit(&self, event: &PipelineEvent) { + fn emit(&self, event: &WorkflowRunEvent) { if let Some(emitter) = &self.emitter { emitter.emit(event); } @@ -146,7 +146,7 @@ impl Handler for WaitHumanHandler { // 3. Present to interviewer let question_text = node.label().to_string(); - self.emit(&PipelineEvent::InterviewStarted { + self.emit(&WorkflowRunEvent::InterviewStarted { question: question_text.clone(), stage: node.id.clone(), question_type: question.question_type.to_string(), @@ -156,7 +156,7 @@ impl Handler for WaitHumanHandler { // 4. Handle timeout if answer.value == AnswerValue::Timeout { - self.emit(&PipelineEvent::InterviewTimeout { + self.emit(&WorkflowRunEvent::InterviewTimeout { question: question_text, stage: node.id.clone(), duration_ms: millis_u64(interview_start.elapsed()), @@ -181,7 +181,7 @@ impl Handler for WaitHumanHandler { } // Emit interview completed for successful interactions - self.emit(&PipelineEvent::InterviewCompleted { + self.emit(&WorkflowRunEvent::InterviewCompleted { question: question_text, answer: answer_text(&answer), duration_ms: millis_u64(interview_start.elapsed()), diff --git a/crates/arc-workflows/src/lib.rs b/crates/arc-workflows/src/lib.rs index 73b17d9ad..ba0525b01 100644 --- a/crates/arc-workflows/src/lib.rs +++ b/crates/arc-workflows/src/lib.rs @@ -13,7 +13,7 @@ pub mod handler; pub mod interviewer; pub mod outcome; pub mod parser; -pub mod pipeline; +pub mod workflow; pub mod preamble; pub mod retro; pub mod retro_agent; diff --git a/crates/arc-workflows/src/retro.rs b/crates/arc-workflows/src/retro.rs index 9ce18eddb..66ae4de6d 100644 --- a/crates/arc-workflows/src/retro.rs +++ b/crates/arc-workflows/src/retro.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; use crate::checkpoint::Checkpoint; use crate::error::{ArcError, Result}; -use crate::event::PipelineEvent; +use crate::event::WorkflowRunEvent; use crate::outcome::StageStatus; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -113,7 +113,7 @@ pub struct RetroNarrative { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Retro { pub run_id: String, - pub pipeline_name: String, + pub workflow_name: String, pub goal: String, pub timestamp: DateTime, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -186,10 +186,10 @@ pub fn extract_stage_durations(logs_root: &Path) -> HashMap { let Some(event_value) = envelope.get("event") else { continue; }; - let Ok(event) = serde_json::from_value::(event_value.clone()) else { + let Ok(event) = serde_json::from_value::(event_value.clone()) else { continue; }; - if let PipelineEvent::StageCompleted { + if let WorkflowRunEvent::StageCompleted { name, duration_ms, .. } = event { @@ -205,11 +205,11 @@ pub fn extract_stage_durations(logs_root: &Path) -> HashMap { #[allow(clippy::too_many_arguments)] pub fn derive_retro( run_id: &str, - pipeline_name: &str, + workflow_name: &str, goal: &str, checkpoint: &Checkpoint, - pipeline_failed: bool, - _pipeline_error: Option<&str>, + run_failed: bool, + _run_error: Option<&str>, duration_ms: u64, stage_durations: &HashMap, ) -> Retro { @@ -262,8 +262,8 @@ pub fn derive_retro( }); } - // If pipeline failed with an error not captured in stages, record it - if pipeline_failed && stages_failed == 0 { + // If run failed with an error not captured in stages, record it + if run_failed && stages_failed == 0 { stages_failed = 1; } @@ -281,7 +281,7 @@ pub fn derive_retro( Retro { run_id: run_id.to_string(), - pipeline_name: pipeline_name.to_string(), + workflow_name: workflow_name.to_string(), goal: goal.to_string(), timestamp: Utc::now(), smoothness: None, @@ -368,7 +368,7 @@ mod tests { ); assert_eq!(retro.run_id, "run-1"); - assert_eq!(retro.pipeline_name, "my_pipeline"); + assert_eq!(retro.workflow_name, "my_pipeline"); assert_eq!(retro.goal, "Fix the bug"); assert_eq!(retro.stages.len(), 2); assert_eq!(retro.stages[0].stage_id, "plan"); @@ -388,7 +388,7 @@ mod tests { } #[test] - fn derive_retro_handles_failed_pipeline() { + fn derive_retro_handles_failed_run() { let cp = Checkpoint { timestamp: Utc::now(), current_node: "start".to_string(), diff --git a/crates/arc-workflows/src/retro_agent.rs b/crates/arc-workflows/src/retro_agent.rs index 737e26112..a5dcf86fb 100644 --- a/crates/arc-workflows/src/retro_agent.rs +++ b/crates/arc-workflows/src/retro_agent.rs @@ -12,9 +12,9 @@ use arc_llm::types::ToolDefinition; use crate::retro::RetroNarrative; -const RETRO_SYSTEM_PROMPT: &str = r#"You are a pipeline retrospective analyst. Your job is to analyze a completed pipeline run and generate a structured retrospective. +const RETRO_SYSTEM_PROMPT: &str = r#"You are a workflow run retrospective analyst. Your job is to analyze a completed workflow run and generate a structured retrospective. -You have access to the pipeline's data files: +You have access to the run's data files: - `progress.ndjson` — the full event stream (stage starts/completions, agent tool calls, errors, retries) - `checkpoint.json` — final execution state with node outcomes - `manifest.json` — run metadata (if available) @@ -33,17 +33,17 @@ You have access to the pipeline's data files: Grade the run on a 5-point scale: -- **effortless** — Pipeline achieved its goal on the first try with no retries, no wrong approaches. Agent moved efficiently from start to finish. +- **effortless** — Run achieved its goal on the first try with no retries, no wrong approaches. Agent moved efficiently from start to finish. - **smooth** — Goal achieved with minor hiccups (1-2 retries or a brief wrong approach quickly corrected). No human intervention needed. Overall clean execution. - **bumpy** — Goal achieved but with notable friction: multiple retries, at least one significant wrong approach, or substantial time spent on dead ends. - **struggled** — Goal achieved only with difficulty: many retries, major approach changes, human intervention, or partial failures requiring recovery. -- **failed** — Pipeline did not achieve its stated goal. May have completed some stages but the overall intent was not fulfilled. +- **failed** — Run did not achieve its stated goal. May have completed some stages but the overall intent was not fulfilled. Consider the full context: not just stage pass/fail, but the quality of the journey visible in the agent events (tool call patterns, error recovery, approach pivots). ## Guidelines for qualitative fields -- **intent**: What was the pipeline trying to accomplish? Summarize the goal in a sentence. +- **intent**: What was the workflow run trying to accomplish? Summarize the goal in a sentence. - **outcome**: What actually happened? Did it succeed? What was produced? - **learnings**: What was discovered about the repo, code, workflow, or tools? - **friction_points**: Where did things get stuck? What caused slowdowns? @@ -57,11 +57,11 @@ const SUBMIT_RETRO_SCHEMA: &str = r#"{ "smoothness": { "type": "string", "enum": ["effortless", "smooth", "bumpy", "struggled", "failed"], - "description": "Overall smoothness rating for the pipeline run" + "description": "Overall smoothness rating for the workflow run" }, "intent": { "type": "string", - "description": "What was the pipeline trying to accomplish?" + "description": "What was the workflow run trying to accomplish?" }, "outcome": { "type": "string", @@ -108,7 +108,7 @@ const SUBMIT_RETRO_SCHEMA: &str = r#"{ "required": ["smoothness", "intent", "outcome"] }"#; -/// Run a retro agent session that analyzes pipeline run data and produces +/// Run a retro agent session that analyzes workflow run data and produces /// a structured narrative. The agent explores `progress.ndjson` and other /// files via tool access, then calls `submit_retro` with its analysis. pub async fn run_retro_agent( @@ -133,7 +133,7 @@ pub async fn run_retro_agent( let submit_tool = arc_agent::tool_registry::RegisteredTool { definition: ToolDefinition { name: "submit_retro".to_string(), - description: "Submit the structured retrospective analysis. Call this once you have analyzed the pipeline run data.".to_string(), + description: "Submit the structured retrospective analysis. Call this once you have analyzed the workflow run data.".to_string(), parameters: serde_json::from_str(SUBMIT_RETRO_SCHEMA) .expect("submit_retro schema should be valid JSON"), }, @@ -171,7 +171,7 @@ pub async fn run_retro_agent( session.initialize().await; let prompt = format!( - "Analyze the pipeline run data at `{retro_data_dir}/` and generate a retrospective. \ + "Analyze the workflow run data at `{retro_data_dir}/` and generate a retrospective. \ The key file is `{retro_data_dir}/progress.ndjson` which contains the full event stream. \ Also check `{retro_data_dir}/checkpoint.json` for stage outcomes. \ Use grep to search for interesting signals (failures, retries, errors, approach changes) \ @@ -199,7 +199,7 @@ pub fn dry_run_narrative() -> RetroNarrative { RetroNarrative { smoothness: crate::retro::SmoothnessRating::Smooth, intent: "[dry-run] No LLM analysis performed".to_string(), - outcome: "[dry-run] Pipeline completed in simulated mode".to_string(), + outcome: "[dry-run] Run completed in simulated mode".to_string(), learnings: vec![], friction_points: vec![], open_items: vec![], diff --git a/crates/arc-workflows/src/pipeline.rs b/crates/arc-workflows/src/workflow.rs similarity index 82% rename from crates/arc-workflows/src/pipeline.rs rename to crates/arc-workflows/src/workflow.rs index 4890c8495..fa526ff4c 100644 --- a/crates/arc-workflows/src/pipeline.rs +++ b/crates/arc-workflows/src/workflow.rs @@ -3,13 +3,13 @@ use crate::graph::Graph; use crate::transform::{StylesheetApplicationTransform, Transform, VariableExpansionTransform}; use crate::validation::{self, Diagnostic}; -/// Builder for configuring and executing a pipeline preparation. +/// Builder for configuring and executing a workflow preparation. /// Collects custom transforms that run after the built-in ones. -pub struct PipelineBuilder { +pub struct WorkflowBuilder { transforms: Vec>, } -impl PipelineBuilder { +impl WorkflowBuilder { #[must_use] pub fn new() -> Self { Self { @@ -23,7 +23,7 @@ impl PipelineBuilder { self.transforms.push(transform); } - /// Prepare a pipeline: parse DOT, apply built-in and custom transforms, validate. + /// Prepare a workflow: parse DOT, apply built-in and custom transforms, validate. /// /// # Errors /// @@ -45,7 +45,7 @@ impl PipelineBuilder { } } -impl Default for PipelineBuilder { +impl Default for WorkflowBuilder { fn default() -> Self { Self::new() } @@ -56,8 +56,8 @@ impl Default for PipelineBuilder { /// # Errors /// /// Returns an error if parsing fails or if validation produces Error-severity diagnostics. -pub fn prepare_pipeline(dot_source: &str) -> Result { - let builder = PipelineBuilder::new(); +pub fn prepare_workflow(dot_source: &str) -> Result { + let builder = WorkflowBuilder::new(); let (graph, diagnostics) = builder.prepare(dot_source)?; let errors: Vec<&Diagnostic> = diagnostics @@ -85,15 +85,15 @@ mod tests { }"#; #[test] - fn prepare_pipeline_minimal() { - let graph = prepare_pipeline(MINIMAL_DOT).unwrap(); + fn prepare_workflow_minimal() { + let graph = prepare_workflow(MINIMAL_DOT).unwrap(); assert_eq!(graph.name, "Test"); assert!(graph.find_start_node().is_some()); assert!(graph.find_exit_node().is_some()); } #[test] - fn prepare_pipeline_applies_variable_expansion() { + fn prepare_workflow_applies_variable_expansion() { let dot = r#"digraph Test { graph [goal="Fix bugs"] start [shape=Mdiamond] @@ -101,7 +101,7 @@ mod tests { exit [shape=Msquare] start -> work -> exit }"#; - let graph = prepare_pipeline(dot).unwrap(); + let graph = prepare_workflow(dot).unwrap(); let prompt = graph.nodes["work"] .attrs .get("prompt") @@ -111,7 +111,7 @@ mod tests { } #[test] - fn prepare_pipeline_applies_stylesheet() { + fn prepare_workflow_applies_stylesheet() { let dot = r#"digraph Test { graph [goal="Test", model_stylesheet="* { llm_model: sonnet; }"] start [shape=Mdiamond] @@ -119,7 +119,7 @@ mod tests { exit [shape=Msquare] start -> work -> exit }"#; - let graph = prepare_pipeline(dot).unwrap(); + let graph = prepare_workflow(dot).unwrap(); assert_eq!( graph.nodes["work"].attrs.get("llm_model"), Some(&AttrValue::String("sonnet".into())) @@ -127,18 +127,18 @@ mod tests { } #[test] - fn prepare_pipeline_returns_error_on_invalid_dot() { - let result = prepare_pipeline("not a graph"); + fn prepare_workflow_returns_error_on_invalid_dot() { + let result = prepare_workflow("not a graph"); assert!(result.is_err()); } #[test] - fn prepare_pipeline_returns_error_on_validation_failure() { + fn prepare_workflow_returns_error_on_validation_failure() { let dot = r#"digraph Test { graph [goal="Test"] work [label="Work"] }"#; - let result = prepare_pipeline(dot); + let result = prepare_workflow(dot); assert!(result.is_err()); } @@ -154,7 +154,7 @@ mod tests { } } - let mut builder = PipelineBuilder::new(); + let mut builder = WorkflowBuilder::new(); builder.register_transform(Box::new(TagTransform)); let (graph, _) = builder.prepare(MINIMAL_DOT).unwrap(); assert_eq!( @@ -165,7 +165,7 @@ mod tests { #[test] fn pipeline_builder_default() { - let builder = PipelineBuilder::default(); + let builder = WorkflowBuilder::default(); let (graph, _) = builder.prepare(MINIMAL_DOT).unwrap(); assert_eq!(graph.name, "Test"); } diff --git a/crates/arc-workflows/tests/daytona_integration.rs b/crates/arc-workflows/tests/daytona_integration.rs index d4e7d6b33..cefbbf0b0 100644 --- a/crates/arc-workflows/tests/daytona_integration.rs +++ b/crates/arc-workflows/tests/daytona_integration.rs @@ -13,7 +13,7 @@ use arc_workflows::artifact::sync_artifacts_to_env; use arc_workflows::checkpoint::Checkpoint; use arc_workflows::context::Context; use arc_workflows::daytona_env::{DaytonaConfig, DaytonaExecutionEnvironment}; -use arc_workflows::engine::{PipelineEngine, RunConfig}; +use arc_workflows::engine::{WorkflowRunEngine, RunConfig}; use arc_workflows::error::ArcError; use arc_workflows::event::EventEmitter; use arc_workflows::graph::{AttrValue, Edge, Graph, Node}; @@ -274,7 +274,7 @@ async fn daytona_pipeline_artifact_offload_and_sync() { registry.register("start", Box::new(StartHandler)); registry.register("exit", Box::new(ExitHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), env.clone()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), env.clone()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -465,7 +465,7 @@ async fn daytona_git_checkpoint_remote_emits_events() { registry.register("start", Box::new(StartHandler)); registry.register("exit", Box::new(ExitHandler)); - let engine = PipelineEngine::new(registry, Arc::new(emitter), env.clone()); + let engine = WorkflowRunEngine::new(registry, Arc::new(emitter), env.clone()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -490,7 +490,7 @@ async fn daytona_git_checkpoint_remote_emits_events() { let git_events: Vec<_> = events .iter() .filter_map(|e| { - if let arc_workflows::event::PipelineEvent::GitCheckpoint { + if let arc_workflows::event::WorkflowRunEvent::GitCheckpoint { node_id, git_commit_sha, .. @@ -644,7 +644,7 @@ async fn daytona_parallel_git_branching_e2e() { registry.register("parallel", Box::new(ParallelHandler)); registry.register("parallel.fan_in", Box::new(FanInHandler::new(None))); - let engine = PipelineEngine::new(registry, Arc::new(emitter), Arc::clone(&env)); + let engine = WorkflowRunEngine::new(registry, Arc::new(emitter), Arc::clone(&env)); let config = RunConfig { logs_root: logs_dir.path().to_path_buf(), @@ -740,7 +740,7 @@ async fn daytona_parallel_git_branching_e2e() { .filter(|e| { matches!( e, - arc_workflows::event::PipelineEvent::ParallelStarted { .. } + arc_workflows::event::WorkflowRunEvent::ParallelStarted { .. } ) }) .collect(); @@ -754,7 +754,7 @@ async fn daytona_parallel_git_branching_e2e() { .filter(|e| { matches!( e, - arc_workflows::event::PipelineEvent::ParallelCompleted { .. } + arc_workflows::event::WorkflowRunEvent::ParallelCompleted { .. } ) }) .collect(); @@ -970,7 +970,7 @@ async fn daytona_git_checkpoint_with_shadow_branch() { registry.register("exit", Box::new(ExitHandler)); let meta_branch = MetadataStore::branch_name(&run_id); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), env.clone()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), env.clone()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, diff --git a/crates/arc-workflows/tests/integration.rs b/crates/arc-workflows/tests/integration.rs index e2baab1e9..3bfa5b6f3 100644 --- a/crates/arc-workflows/tests/integration.rs +++ b/crates/arc-workflows/tests/integration.rs @@ -7,9 +7,9 @@ use arc_util::terminal::Styles; use arc_workflows::checkpoint::Checkpoint; use arc_workflows::cli::backend::AgentApiBackend; use arc_workflows::context::Context; -use arc_workflows::engine::{PipelineEngine, RunConfig}; +use arc_workflows::engine::{WorkflowRunEngine, RunConfig}; use arc_workflows::error::ArcError; -use arc_workflows::event::{EventEmitter, PipelineEvent}; +use arc_workflows::event::{EventEmitter, WorkflowRunEvent}; use arc_workflows::graph::{AttrValue, Edge, Graph, Node}; use arc_workflows::handler::codergen::{CodergenBackend, CodergenHandler, CodergenResult}; use arc_workflows::handler::conditional::ConditionalHandler; @@ -184,7 +184,7 @@ async fn end_to_end_linear_pipeline() { validate_or_raise(&graph, &[]).expect("validation should pass"); let dir = tempfile::tempdir().unwrap(); - let engine = PipelineEngine::new( + let engine = WorkflowRunEngine::new( make_linear_registry(), Arc::new(EventEmitter::new()), local_env(), @@ -325,7 +325,7 @@ async fn end_to_end_branching_pipeline() { registry.register("codergen", Box::new(CodergenHandler::new(None))); registry.register("conditional", Box::new(ConditionalHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -445,7 +445,7 @@ async fn end_to_end_human_gate_pipeline() { registry.register("exit", Box::new(ExitHandler)); registry.register("wait.human", Box::new(WaitHumanHandler::new(interviewer))); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -551,7 +551,7 @@ async fn goal_gate_routes_to_retry_target_on_failure() { registry.register("exit", Box::new(ExitHandler)); registry.register("always_fail", Box::new(AlwaysFailHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -670,7 +670,7 @@ async fn goal_gate_routes_to_retry_target_when_present() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -976,7 +976,7 @@ async fn retry_on_failure_then_succeed() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -1045,7 +1045,7 @@ async fn pipeline_with_many_nodes() { .push(Edge::new(node_names.last().unwrap(), "exit")); let dir = tempfile::tempdir().unwrap(); - let engine = PipelineEngine::new( + let engine = WorkflowRunEngine::new( make_linear_registry(), Arc::new(EventEmitter::new()), local_env(), @@ -1236,7 +1236,7 @@ impl Handler for ContextSetterHandler { } } -fn collect_events(emitter: &mut EventEmitter) -> Arc>> { +fn collect_events(emitter: &mut EventEmitter) -> Arc>> { let events = Arc::new(std::sync::Mutex::new(Vec::new())); let events_clone = Arc::clone(&events); emitter.on_event(move |event| { @@ -1376,7 +1376,7 @@ async fn smoke_test_with_mock_codergen_backend() { ); registry.register("conditional", Box::new(ConditionalHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -1475,7 +1475,7 @@ async fn end_to_end_parallel_fan_out_fan_in() { Box::new(FanInHandler::new(Some(Box::new(MockCodergenBackend)))), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -1585,7 +1585,7 @@ async fn resume_from_checkpoint_completes_pipeline() { registry.register("start", Box::new(StartHandler)); registry.register("exit", Box::new(ExitHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -1681,7 +1681,7 @@ async fn resume_from_checkpoint_preserves_goal_gate_outcomes() { registry.register("start", Box::new(StartHandler)); registry.register("exit", Box::new(ExitHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -1718,7 +1718,7 @@ async fn graph_goal_in_context() { }"#; let graph = parse(input).expect("parse"); let dir = tempfile::tempdir().unwrap(); - let engine = PipelineEngine::new( + let engine = WorkflowRunEngine::new( make_linear_registry(), Arc::new(EventEmitter::new()), local_env(), @@ -1755,7 +1755,7 @@ async fn event_streaming_lifecycle() { let dir = tempfile::tempdir().unwrap(); let mut emitter = EventEmitter::new(); let events = collect_events(&mut emitter); - let engine = PipelineEngine::new(make_linear_registry(), Arc::new(emitter), local_env()); + let engine = WorkflowRunEngine::new(make_linear_registry(), Arc::new(emitter), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -1772,33 +1772,33 @@ async fn event_streaming_lifecycle() { let collected = events.lock().unwrap(); assert!(collected .iter() - .any(|e| matches!(e, PipelineEvent::PipelineStarted { .. }))); + .any(|e| matches!(e, WorkflowRunEvent::WorkflowRunStarted { .. }))); assert!(collected .iter() - .any(|e| matches!(e, PipelineEvent::StageStarted { name, .. } if name == "start"))); + .any(|e| matches!(e, WorkflowRunEvent::StageStarted { name, .. } if name == "start"))); assert!(collected .iter() - .any(|e| matches!(e, PipelineEvent::StageCompleted { name, .. } if name == "start"))); + .any(|e| matches!(e, WorkflowRunEvent::StageCompleted { name, .. } if name == "start"))); assert!(collected .iter() - .any(|e| matches!(e, PipelineEvent::StageStarted { name, .. } if name == "task"))); + .any(|e| matches!(e, WorkflowRunEvent::StageStarted { name, .. } if name == "task"))); assert!(collected .iter() - .any(|e| matches!(e, PipelineEvent::StageCompleted { name, .. } if name == "task"))); + .any(|e| matches!(e, WorkflowRunEvent::StageCompleted { name, .. } if name == "task"))); assert!(collected .iter() - .any(|e| matches!(e, PipelineEvent::CheckpointSaved { .. }))); + .any(|e| matches!(e, WorkflowRunEvent::CheckpointSaved { .. }))); assert!(collected .iter() - .any(|e| matches!(e, PipelineEvent::PipelineCompleted { .. }))); - // PipelineStarted first, PipelineCompleted last + .any(|e| matches!(e, WorkflowRunEvent::WorkflowRunCompleted { .. }))); + // WorkflowRunStarted first, WorkflowRunCompleted last assert!(matches!( collected.first().unwrap(), - PipelineEvent::PipelineStarted { .. } + WorkflowRunEvent::WorkflowRunStarted { .. } )); assert!(matches!( collected.last().unwrap(), - PipelineEvent::PipelineCompleted { .. } + WorkflowRunEvent::WorkflowRunCompleted { .. } )); } @@ -1828,7 +1828,7 @@ async fn context_flow_between_stages() { graph.edges.push(Edge::new("step_b", "exit")); let dir = tempfile::tempdir().unwrap(); - let engine = PipelineEngine::new( + let engine = WorkflowRunEngine::new( make_linear_registry(), Arc::new(EventEmitter::new()), local_env(), @@ -1878,7 +1878,7 @@ async fn tool_handler_e2e() { let dir = tempfile::tempdir().unwrap(); let interviewer = Arc::new(AutoApproveInterviewer); - let engine = PipelineEngine::new( + let engine = WorkflowRunEngine::new( make_full_registry(interviewer), Arc::new(EventEmitter::new()), local_env(), @@ -1947,7 +1947,7 @@ async fn auto_approve_interviewer_e2e() { let dir = tempfile::tempdir().unwrap(); let interviewer = Arc::new(AutoApproveInterviewer); - let engine = PipelineEngine::new( + let engine = WorkflowRunEngine::new( make_full_registry(interviewer), Arc::new(EventEmitter::new()), local_env(), @@ -1981,7 +1981,7 @@ async fn codergen_without_backend_simulated() { }"#; let graph = parse(input).expect("parse"); let dir = tempfile::tempdir().unwrap(); - let engine = PipelineEngine::new( + let engine = WorkflowRunEngine::new( make_linear_registry(), Arc::new(EventEmitter::new()), local_env(), @@ -2087,7 +2087,7 @@ async fn branching_loop_back_on_failure() { call_count: std::sync::atomic::AtomicU32::new(0), }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -2169,7 +2169,7 @@ async fn human_gate_loops_back() { registry.register("start", Box::new(StartHandler)); registry.register("exit", Box::new(ExitHandler)); registry.register("wait.human", Box::new(WaitHumanHandler::new(interviewer))); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -2220,7 +2220,7 @@ async fn scenario_ship_a_feature() { let dir = tempfile::tempdir().unwrap(); let mut emitter = EventEmitter::new(); let events = collect_events(&mut emitter); - let engine = PipelineEngine::new( + let engine = WorkflowRunEngine::new( make_full_registry(interviewer), Arc::new(emitter), local_env(), @@ -2253,10 +2253,10 @@ async fn scenario_ship_a_feature() { let collected = events.lock().unwrap(); assert!(collected .iter() - .any(|e| matches!(e, PipelineEvent::PipelineStarted { .. }))); + .any(|e| matches!(e, WorkflowRunEvent::WorkflowRunStarted { .. }))); assert!(collected .iter() - .any(|e| matches!(e, PipelineEvent::PipelineCompleted { .. }))); + .any(|e| matches!(e, WorkflowRunEvent::WorkflowRunCompleted { .. }))); } #[tokio::test] @@ -2307,7 +2307,7 @@ async fn scenario_parallel_expert_review() { ); registry.register("wait.human", Box::new(WaitHumanHandler::new(interviewer))); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -2384,7 +2384,7 @@ async fn scenario_node_retries_on_retry_status() { call_count: std::sync::atomic::AtomicU32::new(0), }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -2443,7 +2443,7 @@ async fn scenario_loop_restart_resets_context() { call_count: Arc::clone(&call_count), }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -2508,7 +2508,7 @@ async fn scenario_bug_triage_router() { registry.register("start", Box::new(StartHandler)); registry.register("exit", Box::new(ExitHandler)); registry.register("conditional", Box::new(ConditionalHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -2563,7 +2563,7 @@ async fn scenario_crash_recovery() { let mut registry = HandlerRegistry::new(Box::new(StartHandler)); registry.register("start", Box::new(StartHandler)); registry.register("exit", Box::new(ExitHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -2669,7 +2669,7 @@ async fn manager_loop_stop_condition_satisfied_e2e() { registry.register("exit", Box::new(ExitHandler)); registry.register("done_setter", Box::new(DoneSetterHandler)); registry.register("stack.manager_loop", Box::new(SubWorkflowHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -2743,7 +2743,7 @@ async fn manager_loop_max_cycles_exceeded_e2e() { registry.register("start", Box::new(StartHandler)); registry.register("exit", Box::new(ExitHandler)); registry.register("stack.manager_loop", Box::new(SubWorkflowHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -2877,7 +2877,7 @@ async fn conditional_branching_success_fail_paths() { registry.register("start", Box::new(StartHandler)); registry.register("exit", Box::new(ExitHandler)); registry.register("always_fail", Box::new(AlwaysFailHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -2927,7 +2927,7 @@ async fn edge_selection_condition_match_wins_over_weight() { let mut registry = HandlerRegistry::new(Box::new(StartHandler)); registry.register("start", Box::new(StartHandler)); registry.register("exit", Box::new(ExitHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -2971,7 +2971,7 @@ async fn edge_selection_weight_breaks_ties() { let mut registry = HandlerRegistry::new(Box::new(StartHandler)); registry.register("start", Box::new(StartHandler)); registry.register("exit", Box::new(ExitHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -3007,7 +3007,7 @@ async fn edge_selection_lexical_tiebreak() { let mut registry = HandlerRegistry::new(Box::new(StartHandler)); registry.register("start", Box::new(StartHandler)); registry.register("exit", Box::new(ExitHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -3062,7 +3062,7 @@ async fn context_updates_visible_across_nodes() { registry.register("exit", Box::new(ExitHandler)); registry.register("conditional", Box::new(ConditionalHandler)); registry.register("context_setter", Box::new(ContextSetterHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -3099,7 +3099,7 @@ async fn stylesheet_applies_model_override() { assert_eq!(graph.nodes["work"].llm_model(), Some("custom-model")); let dir = tempfile::tempdir().unwrap(); - let engine = PipelineEngine::new( + let engine = WorkflowRunEngine::new( make_linear_registry(), Arc::new(EventEmitter::new()), local_env(), @@ -3156,7 +3156,7 @@ async fn custom_handler_registration_and_execution() { registry.register("start", Box::new(StartHandler)); registry.register("exit", Box::new(ExitHandler)); registry.register("my_custom", Box::new(CustomHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -3220,7 +3220,7 @@ async fn integration_smoke_plan_implement_review_done() { let dir = tempfile::tempdir().unwrap(); let mut emitter = EventEmitter::new(); let events = collect_events(&mut emitter); - let engine = PipelineEngine::new( + let engine = WorkflowRunEngine::new( make_full_registry(interviewer), Arc::new(emitter), local_env(), @@ -3263,10 +3263,10 @@ async fn integration_smoke_plan_implement_review_done() { let collected = events.lock().unwrap(); assert!(collected .iter() - .any(|e| matches!(e, PipelineEvent::PipelineStarted { .. }))); + .any(|e| matches!(e, WorkflowRunEvent::WorkflowRunStarted { .. }))); assert!(collected .iter() - .any(|e| matches!(e, PipelineEvent::PipelineCompleted { .. }))); + .any(|e| matches!(e, WorkflowRunEvent::WorkflowRunCompleted { .. }))); } // =========================================================================== @@ -3325,7 +3325,7 @@ async fn manager_loop_runs_child_engine_e2e() { registry.register("exit", Box::new(ExitHandler)); registry.register("stack.manager_loop", Box::new(SubWorkflowHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -3458,7 +3458,7 @@ async fn manager_loop_context_flows_e2e() { registry.register("setter", Box::new(SetterHandler)); registry.register("stack.manager_loop", Box::new(SubWorkflowHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -3529,7 +3529,7 @@ async fn manager_loop_child_dotfile_e2e() { registry.register("exit", Box::new(ExitHandler)); registry.register("stack.manager_loop", Box::new(SubWorkflowHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -3636,7 +3636,7 @@ async fn graph_merge_e2e_through_engine() { assert!(main_graph.nodes.contains_key("dep.release")); let dir = tempfile::tempdir().unwrap(); - let engine = PipelineEngine::new( + let engine = WorkflowRunEngine::new( make_linear_registry(), Arc::new(EventEmitter::new()), local_env(), @@ -3788,7 +3788,7 @@ async fn fidelity_default_is_compact() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -3842,7 +3842,7 @@ async fn fidelity_graph_default_applied() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -3892,7 +3892,7 @@ async fn fidelity_node_overrides_graph_default() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -3948,7 +3948,7 @@ async fn fidelity_edge_overrides_node_and_graph() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -3994,7 +3994,7 @@ async fn fidelity_full_produces_empty_preamble() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -4050,7 +4050,7 @@ async fn fidelity_truncate_preamble_minimal() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -4119,7 +4119,7 @@ async fn fidelity_summary_low_mode() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -4183,7 +4183,7 @@ async fn fidelity_summary_medium_mode() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -4247,7 +4247,7 @@ async fn fidelity_summary_high_mode() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -4304,7 +4304,7 @@ async fn fidelity_full_sets_thread_id_in_context() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -4372,7 +4372,7 @@ async fn fidelity_full_nodes_share_thread_id() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -4449,7 +4449,7 @@ async fn fidelity_resume_degrades_full_to_summary_high() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -4542,7 +4542,7 @@ async fn fidelity_resume_degrade_only_affects_first_hop() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -4622,7 +4622,7 @@ async fn fidelity_resume_no_degrade_when_not_full() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -4661,7 +4661,7 @@ async fn fidelity_stored_in_checkpoint_context() { registry.register("start", Box::new(StartHandler)); registry.register("exit", Box::new(ExitHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -4744,7 +4744,7 @@ async fn fidelity_precedence_multi_node_pipeline() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -4809,7 +4809,7 @@ async fn fidelity_compact_preamble_includes_completed_stages_and_context() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -4881,7 +4881,7 @@ async fn fidelity_summary_low_excludes_context_values_in_pipeline() { captures: captures_low.clone(), }), ); - let engine_low = PipelineEngine::new(registry_low, Arc::new(EventEmitter::new()), local_env()); + 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(), cancel_token: None, @@ -4945,7 +4945,7 @@ async fn fidelity_summary_low_excludes_context_values_in_pipeline() { captures: captures_med.clone(), }), ); - let engine_med = PipelineEngine::new(registry_med, Arc::new(EventEmitter::new()), local_env()); + 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(), cancel_token: None, @@ -5013,7 +5013,7 @@ async fn fidelity_thread_id_fallback_to_previous_node_in_pipeline() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -5064,7 +5064,7 @@ async fn fidelity_thread_id_from_node_class_in_pipeline() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -5118,7 +5118,7 @@ async fn fidelity_edge_thread_id_override_in_pipeline() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -5173,7 +5173,7 @@ async fn fidelity_full_without_explicit_thread_id_uses_previous_node() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -5238,7 +5238,7 @@ async fn fidelity_from_parsed_dot_pipeline() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -5283,7 +5283,7 @@ async fn fidelity_checkpoint_roundtrip_preserves_fidelity() { registry.register("start", Box::new(StartHandler)); registry.register("exit", Box::new(ExitHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -5350,7 +5350,7 @@ async fn fidelity_node_thread_id_overrides_edge_thread_id_in_pipeline() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -5433,7 +5433,7 @@ async fn fidelity_resume_preserves_context_values_across_checkpoint() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -5547,7 +5547,7 @@ mod real_llm { use super::local_env; use arc_workflows::checkpoint::Checkpoint; - use arc_workflows::engine::{PipelineEngine, RunConfig}; + use arc_workflows::engine::{WorkflowRunEngine, RunConfig}; use arc_workflows::event::EventEmitter; use arc_workflows::graph::{AttrValue, Edge, Graph}; use arc_workflows::handler::exit::ExitHandler; @@ -5624,7 +5624,7 @@ mod real_llm { )))), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -5736,7 +5736,7 @@ mod real_llm { Box::new(CodergenHandler::new(Some(make_llm_backend(client)))), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -5875,7 +5875,7 @@ mod real_llm { ); registry.register("wait.human", Box::new(WaitHumanHandler::new(interviewer))); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -5982,7 +5982,7 @@ mod real_llm { Box::new(CodergenHandler::new(Some(make_llm_backend(client)))), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -6078,7 +6078,7 @@ async fn human_gate_freeform_only_routes_text() { registry.register("exit", Box::new(ExitHandler)); registry.register("wait.human", Box::new(WaitHumanHandler::new(interviewer))); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -6208,7 +6208,7 @@ async fn human_gate_freeform_with_fixed_choice_match() { registry.register("exit", Box::new(ExitHandler)); registry.register("wait.human", Box::new(WaitHumanHandler::new(interviewer))); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -6323,7 +6323,7 @@ async fn human_gate_freeform_fallback_on_unmatched_text() { registry.register("exit", Box::new(ExitHandler)); registry.register("wait.human", Box::new(WaitHumanHandler::new(interviewer))); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -6451,7 +6451,7 @@ async fn human_gate_freeform_sets_allow_freeform_on_question() { registry.register("exit", Box::new(ExitHandler)); registry.register("wait.human", Box::new(WaitHumanHandler::new(interviewer))); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -6559,7 +6559,7 @@ async fn human_gate_without_freeform_sets_allow_freeform_false() { registry.register("exit", Box::new(ExitHandler)); registry.register("wait.human", Box::new(WaitHumanHandler::new(interviewer))); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -6797,7 +6797,7 @@ async fn tool_hooks_pre_success_allows_pipeline_to_proceed() { validate_or_raise(&graph, &[]).expect("validation should pass"); let dir = tempfile::tempdir().unwrap(); - let engine = PipelineEngine::new( + let engine = WorkflowRunEngine::new( make_linear_registry(), Arc::new(EventEmitter::new()), local_env(), @@ -6846,7 +6846,7 @@ async fn tool_hooks_pre_failure_skips_tool_call() { validate_or_raise(&graph, &[]).expect("validation should pass"); let dir = tempfile::tempdir().unwrap(); - let engine = PipelineEngine::new( + let engine = WorkflowRunEngine::new( make_linear_registry(), Arc::new(EventEmitter::new()), local_env(), @@ -6898,7 +6898,7 @@ async fn tool_hooks_post_success_does_not_affect_outcome() { validate_or_raise(&graph, &[]).expect("validation should pass"); let dir = tempfile::tempdir().unwrap(); - let engine = PipelineEngine::new( + let engine = WorkflowRunEngine::new( make_linear_registry(), Arc::new(EventEmitter::new()), local_env(), @@ -6942,7 +6942,7 @@ async fn tool_hooks_post_failure_does_not_block_pipeline() { validate_or_raise(&graph, &[]).expect("validation should pass"); let dir = tempfile::tempdir().unwrap(); - let engine = PipelineEngine::new( + let engine = WorkflowRunEngine::new( make_linear_registry(), Arc::new(EventEmitter::new()), local_env(), @@ -6988,7 +6988,7 @@ async fn tool_hooks_graph_level_applies_to_all_nodes() { validate_or_raise(&graph, &[]).expect("validation should pass"); let dir = tempfile::tempdir().unwrap(); - let engine = PipelineEngine::new( + let engine = WorkflowRunEngine::new( make_linear_registry(), Arc::new(EventEmitter::new()), local_env(), @@ -7045,7 +7045,7 @@ async fn tool_hooks_node_level_overrides_graph_level() { validate_or_raise(&graph, &[]).expect("validation should pass"); let dir = tempfile::tempdir().unwrap(); - let engine = PipelineEngine::new( + let engine = WorkflowRunEngine::new( make_linear_registry(), Arc::new(EventEmitter::new()), local_env(), @@ -7109,7 +7109,7 @@ async fn tool_hooks_pre_receives_node_id_env_var() { let graph = parse(&input).expect("parse should succeed"); validate_or_raise(&graph, &[]).expect("validation should pass"); - let engine = PipelineEngine::new( + let engine = WorkflowRunEngine::new( make_linear_registry(), Arc::new(EventEmitter::new()), local_env(), @@ -7215,7 +7215,7 @@ async fn arc_e2e_with_real_llm() { }); let logs_dir = tempfile::tempdir().unwrap(); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: logs_dir.path().to_path_buf(), cancel_token: None, @@ -7341,7 +7341,7 @@ async fn run_fidelity_prompt_pipeline(fidelity: &str) -> String { Box::new(CodergenHandler::new(Some(Box::new(MockCodergenBackend)))), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -7538,7 +7538,7 @@ async fn large_context_values_are_offloaded_to_artifact_store() { let mut emitter = EventEmitter::new(); let events = collect_events(&mut emitter); - let engine = PipelineEngine::new(registry, Arc::new(emitter), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(emitter), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -7593,13 +7593,13 @@ async fn large_context_values_are_offloaded_to_artifact_store() { "artifact should contain the original 150KB value" ); - // PipelineCompleted event should report artifact_count > 0 + // WorkflowRunCompleted event should report artifact_count > 0 let evts = events.lock().unwrap(); let completed_event = evts .iter() - .find(|e| matches!(e, PipelineEvent::PipelineCompleted { .. })) - .expect("should have PipelineCompleted event"); - if let PipelineEvent::PipelineCompleted { artifact_count, .. } = completed_event { + .find(|e| matches!(e, WorkflowRunEvent::WorkflowRunCompleted { .. })) + .expect("should have WorkflowRunCompleted event"); + if let WorkflowRunEvent::WorkflowRunCompleted { artifact_count, .. } = completed_event { assert!( *artifact_count > 0, "artifact_count should be > 0, got {artifact_count}" @@ -7738,7 +7738,7 @@ async fn artifact_pointers_rewritten_for_remote_execution_env() { registry.register("exit", Box::new(ExitHandler)); let remote_env = Arc::new(RemoteMockEnv::new("/sandbox")); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), remote_env.clone()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), remote_env.clone()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -7866,7 +7866,7 @@ async fn node_dir_uses_visit_count_on_revisit() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -8685,7 +8685,7 @@ async fn full_pipeline_with_cli_backend_node() { ); let dir = tempfile::tempdir().unwrap(); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), env); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), env); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -8812,7 +8812,7 @@ async fn stylesheet_backend_property_routes_to_cli() { ); let dir = tempfile::tempdir().unwrap(); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), env); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), env); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -9079,7 +9079,7 @@ async fn git_checkpoint_host_emits_events_and_diff_patch() { let mut registry = HandlerRegistry::new(Box::new(ContextSetterHandler)); registry.register("start", Box::new(StartHandler)); registry.register("exit", Box::new(ExitHandler)); - let engine = PipelineEngine::new(registry, Arc::new(emitter), env); + let engine = WorkflowRunEngine::new(registry, Arc::new(emitter), env); let config = RunConfig { logs_root: logs_dir.path().to_path_buf(), @@ -9105,7 +9105,7 @@ async fn git_checkpoint_host_emits_events_and_diff_patch() { let git_events: Vec<_> = events .iter() .filter_map(|e| { - if let PipelineEvent::GitCheckpoint { + if let WorkflowRunEvent::GitCheckpoint { node_id, git_commit_sha, .. @@ -9259,7 +9259,7 @@ async fn git_checkpoint_host_writes_shadow_branch() { let mut registry = HandlerRegistry::new(Box::new(ContextSetterHandler)); registry.register("start", Box::new(StartHandler)); registry.register("exit", Box::new(ExitHandler)); - let engine = PipelineEngine::new(registry, Arc::new(emitter), env); + let engine = WorkflowRunEngine::new(registry, Arc::new(emitter), env); let meta_branch = MetadataStore::branch_name(run_id); let config = RunConfig { @@ -9453,7 +9453,7 @@ async fn parallel_git_branching_host_e2e() { Box::new(FanInHandler::new(None)), // heuristic select — picks branch_a (lexical tiebreak) ); - let engine = PipelineEngine::new(registry, Arc::new(emitter), env); + let engine = WorkflowRunEngine::new(registry, Arc::new(emitter), env); let config = RunConfig { logs_root: logs_dir.path().to_path_buf(), @@ -9606,7 +9606,7 @@ async fn parallel_git_branching_host_e2e() { let events = events.lock().unwrap(); let parallel_started: Vec<_> = events .iter() - .filter(|e| matches!(e, PipelineEvent::ParallelStarted { .. })) + .filter(|e| matches!(e, WorkflowRunEvent::ParallelStarted { .. })) .collect(); assert_eq!( parallel_started.len(), @@ -9616,7 +9616,7 @@ async fn parallel_git_branching_host_e2e() { let parallel_completed: Vec<_> = events .iter() - .filter(|e| matches!(e, PipelineEvent::ParallelCompleted { .. })) + .filter(|e| matches!(e, WorkflowRunEvent::ParallelCompleted { .. })) .collect(); assert_eq!( parallel_completed.len(), @@ -9974,7 +9974,7 @@ async fn e2e_circuit_breaker_deterministic_self_loop() { )), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -10019,7 +10019,7 @@ async fn e2e_circuit_breaker_custom_limit() { Box::new(DeterministicFailHandler::new("same error every time")), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -10057,7 +10057,7 @@ async fn e2e_circuit_breaker_ignores_transient_failures() { registry.register("exit", Box::new(ExitHandler)); registry.register("test_handler", Box::new(TransientInfraFailHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -10102,7 +10102,7 @@ async fn e2e_circuit_breaker_different_reasons_separate_counters() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -10140,7 +10140,7 @@ async fn e2e_circuit_breaker_loop_restart() { Box::new(DeterministicFailHandler::new("verify step failed")), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -10200,7 +10200,7 @@ async fn e2e_failure_signature_persisted_in_context() { Box::new(DeterministicFailHandler::new("test assertion failed")), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -10262,7 +10262,7 @@ async fn e2e_failure_signature_hint_overrides_reason_in_context() { registry.register("exit", Box::new(ExitHandler)); registry.register("hint_handler", Box::new(SignatureHintHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -10316,7 +10316,7 @@ async fn e2e_signature_maps_persist_in_checkpoint() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -10440,7 +10440,7 @@ async fn e2e_circuit_breaker_emits_events_before_abort() { Box::new(DeterministicFailHandler::new("assertion failed")), ); - let engine = PipelineEngine::new(registry, Arc::new(emitter), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(emitter), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -10457,13 +10457,13 @@ async fn e2e_circuit_breaker_emits_events_before_abort() { assert!(result.is_err()); let events = events.lock().unwrap(); - // Should have at least PipelineStarted and some StageFailed/StageCompleted events + // Should have at least WorkflowRunStarted and some StageFailed/StageCompleted events let has_pipeline_started = events .iter() - .any(|e| matches!(e, PipelineEvent::PipelineStarted { .. })); + .any(|e| matches!(e, WorkflowRunEvent::WorkflowRunStarted { .. })); assert!( has_pipeline_started, - "PipelineStarted event should be emitted" + "WorkflowRunStarted event should be emitted" ); // Verify we got stage events for the failing work node. @@ -10471,11 +10471,11 @@ async fn e2e_circuit_breaker_emits_events_before_abort() { // the stage event for that iteration is emitted, so we see limit-1 events. let stage_failed_count = events .iter() - .filter(|e| matches!(e, PipelineEvent::StageFailed { name, .. } if name == "work")) + .filter(|e| matches!(e, WorkflowRunEvent::StageFailed { name, .. } if name == "work")) .count(); let stage_completed_count = events .iter() - .filter(|e| matches!(e, PipelineEvent::StageCompleted { name, .. } if name == "work")) + .filter(|e| matches!(e, WorkflowRunEvent::StageCompleted { name, .. } if name == "work")) .count(); let total_work_events = stage_completed_count + stage_failed_count; // With limit=3, the breaker fires on the 3rd failure before its event is emitted. @@ -10505,7 +10505,7 @@ async fn e2e_circuit_breaker_does_not_fire_below_limit() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -10599,7 +10599,7 @@ async fn e2e_circuit_breaker_multi_stage_impl_verify_cycle() { )), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -10693,7 +10693,7 @@ async fn e2e_loop_restart_blocked_for_deterministic_failure() { Box::new(ClassifiedFailHandler::always("deterministic")), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -10728,7 +10728,7 @@ async fn e2e_loop_restart_blocked_for_structural_failure() { Box::new(ClassifiedFailHandler::always("structural")), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -10763,7 +10763,7 @@ async fn e2e_loop_restart_blocked_for_budget_exhausted_failure() { Box::new(ClassifiedFailHandler::always("budget_exhausted")), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -10798,7 +10798,7 @@ async fn e2e_loop_restart_blocked_for_canceled_failure() { Box::new(ClassifiedFailHandler::always("canceled")), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -10833,7 +10833,7 @@ async fn e2e_loop_restart_blocked_for_compilation_loop_failure() { Box::new(ClassifiedFailHandler::always("compilation_loop")), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -10869,7 +10869,7 @@ async fn e2e_loop_restart_allowed_for_transient_infra() { Box::new(ClassifiedFailHandler::succeed_on("transient_infra", 1)), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -10931,7 +10931,7 @@ impl Handler for KeepaliveHandler { let start = std::time::Instant::now(); while start.elapsed() < std::time::Duration::from_millis(self.total_ms) { tokio::time::sleep(std::time::Duration::from_millis(self.interval_ms)).await; - services.emitter.emit(&PipelineEvent::Prompt { + services.emitter.emit(&WorkflowRunEvent::Prompt { stage: node.id.clone(), text: "keepalive".to_string(), }); @@ -10974,7 +10974,7 @@ async fn e2e_stall_watchdog_triggers_from_dot_parsed_pipeline() { .push(format!("{event:?}")); }); - let engine = PipelineEngine::new(registry, Arc::new(emitter), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(emitter), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -11028,7 +11028,7 @@ async fn e2e_stall_watchdog_kept_alive_by_handler_events() { }), ); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -11065,7 +11065,7 @@ async fn e2e_stall_watchdog_disabled_with_zero_timeout() { registry.register("exit", Box::new(ExitHandler)); registry.register("slow", Box::new(SlowTestHandler { sleep_ms: 200 })); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None, @@ -11125,7 +11125,7 @@ async fn e2e_stall_watchdog_with_explicit_timeout_override() { registry.register("exit", Box::new(ExitHandler)); registry.register("hanging", Box::new(HangingHandler)); - let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); + let engine = WorkflowRunEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), cancel_token: None,