diff --git a/crates/arc-workflows/README.md b/crates/arc-workflows/README.md index a92acd024..fc9fdf4d6 100644 --- a/crates/arc-workflows/README.md +++ b/crates/arc-workflows/README.md @@ -7,7 +7,7 @@ A DOT-based pipeline runner for multi-stage AI workflows. Define workflows as Gr - **Graph** -- A directed graph parsed from DOT syntax containing nodes, edges, and attributes. The graph carries a `goal` describing the pipeline's purpose. - **Node** -- A workflow step. Graphviz shapes map to handler types (e.g., `Mdiamond` = start, `Msquare` = exit, `box` = codergen/LLM, `diamond` = conditional, `hexagon` = human gate, `component` = parallel). - **Edge** -- A connection between nodes with optional `condition`, `label`, `weight`, and `fidelity` attributes that control routing. -- **Handler** -- An async trait implementation that executes a node and returns an `Outcome`. Built-in handlers include `StartHandler`, `ExitHandler`, `CodergenHandler`, `ConditionalHandler`, `WaitHumanHandler`, `ParallelHandler`, `FanInHandler`, `ScriptHandler`, and `ManagerLoopHandler`. +- **Handler** -- An async trait implementation that executes a node and returns an `Outcome`. Built-in handlers include `StartHandler`, `ExitHandler`, `CodergenHandler`, `ConditionalHandler`, `WaitHumanHandler`, `ParallelHandler`, `FanInHandler`, `ScriptHandler`, and `SubWorkflowHandler`. - **Outcome** -- The result of executing a handler, carrying a `StageStatus` (Success, Fail, PartialSuccess, Retry, Skipped), optional routing hints (`preferred_label`, `suggested_next_ids`), and context updates. - **Context** -- A thread-safe key-value store shared across pipeline stages, supporting snapshots and isolated cloning for parallel branches. - **Interviewer** -- A trait for human-in-the-loop interactions. Implementations include `AutoApproveInterviewer`, `QueueInterviewer`, `CallbackInterviewer`, `ConsoleInterviewer`, and `RecordingInterviewer`. diff --git a/crates/arc-workflows/src/handler/manager_loop.rs b/crates/arc-workflows/src/handler/manager_loop.rs index 9847886b1..c3e0e2ffd 100644 --- a/crates/arc-workflows/src/handler/manager_loop.rs +++ b/crates/arc-workflows/src/handler/manager_loop.rs @@ -17,7 +17,7 @@ use crate::pipeline::prepare_pipeline; use super::{EngineServices, Handler}; /// Orchestrates a child pipeline engine, polling for completion or stop conditions. -pub struct ManagerLoopHandler; +pub struct SubWorkflowHandler; /// Parse a duration string like "45s", "200ms", "5m" into a Duration. /// Falls back to 45 seconds on parse failure. @@ -69,7 +69,7 @@ fn context_diff( } #[async_trait] -impl Handler for ManagerLoopHandler { +impl Handler for SubWorkflowHandler { async fn execute( &self, node: &Node, @@ -247,7 +247,7 @@ mod tests { #[tokio::test] async fn child_pipeline_succeeds() { - let handler = ManagerLoopHandler; + let handler = SubWorkflowHandler; let mut node = Node::new("manager"); node.attrs.insert( "stack.child_dot_source".to_string(), @@ -274,7 +274,7 @@ mod tests { #[tokio::test] async fn no_dot_source_fails() { - let handler = ManagerLoopHandler; + let handler = SubWorkflowHandler; let mut node = Node::new("manager"); node.attrs .insert("manager.max_cycles".to_string(), AttrValue::Integer(10)); @@ -301,7 +301,7 @@ mod tests { #[tokio::test] async fn invalid_dot_source_fails() { - let handler = ManagerLoopHandler; + let handler = SubWorkflowHandler; let mut node = Node::new("manager"); node.attrs.insert( "stack.child_dot_source".to_string(), @@ -370,7 +370,7 @@ mod tests { git_state: std::sync::RwLock::new(None), }; - let handler = ManagerLoopHandler; + let handler = SubWorkflowHandler; let mut node = Node::new("manager"); // Child pipeline with a "work" node (default handler = ContextEchoHandler) node.attrs.insert( @@ -415,7 +415,7 @@ mod tests { let dot_path = dir.path().join("child.dot"); std::fs::write(&dot_path, child_dot_succeeds()).unwrap(); - let handler = ManagerLoopHandler; + let handler = SubWorkflowHandler; let mut node = Node::new("manager"); node.attrs.insert( "stack.child_dotfile".to_string(), @@ -477,7 +477,7 @@ mod tests { git_state: std::sync::RwLock::new(None), }; - let handler = ManagerLoopHandler; + let handler = SubWorkflowHandler; let mut node = Node::new("manager"); node.attrs.insert( "stack.child_dot_source".to_string(), @@ -540,7 +540,7 @@ mod tests { git_state: std::sync::RwLock::new(None), }; - let handler = ManagerLoopHandler; + let handler = SubWorkflowHandler; let mut node = Node::new("manager"); node.attrs.insert( "stack.child_dot_source".to_string(), diff --git a/crates/arc-workflows/src/handler/mod.rs b/crates/arc-workflows/src/handler/mod.rs index 9df890c75..4ae28b71f 100644 --- a/crates/arc-workflows/src/handler/mod.rs +++ b/crates/arc-workflows/src/handler/mod.rs @@ -138,7 +138,7 @@ pub fn default_registry( ); registry.register( "stack.manager_loop", - Box::new(manager_loop::ManagerLoopHandler), + Box::new(manager_loop::SubWorkflowHandler), ); registry } diff --git a/crates/arc-workflows/tests/integration.rs b/crates/arc-workflows/tests/integration.rs index 74f2f5001..e2baab1e9 100644 --- a/crates/arc-workflows/tests/integration.rs +++ b/crates/arc-workflows/tests/integration.rs @@ -15,7 +15,7 @@ use arc_workflows::handler::codergen::{CodergenBackend, CodergenHandler, Coderge use arc_workflows::handler::conditional::ConditionalHandler; use arc_workflows::handler::default_registry; use arc_workflows::handler::exit::ExitHandler; -use arc_workflows::handler::manager_loop::ManagerLoopHandler; +use arc_workflows::handler::manager_loop::SubWorkflowHandler; use arc_workflows::handler::script::ScriptHandler; use arc_workflows::handler::start::StartHandler; use arc_workflows::handler::wait_human::WaitHumanHandler; @@ -1255,7 +1255,7 @@ fn make_full_registry(interviewer: Arc) -> HandlerRegistry { registry.register("wait.human", Box::new(WaitHumanHandler::new(interviewer))); registry.register( "stack.manager_loop", - Box::new(ManagerLoopHandler), + Box::new(SubWorkflowHandler), ); registry } @@ -2668,7 +2668,7 @@ async fn manager_loop_stop_condition_satisfied_e2e() { registry.register("start", Box::new(StartHandler)); registry.register("exit", Box::new(ExitHandler)); registry.register("done_setter", Box::new(DoneSetterHandler)); - registry.register("stack.manager_loop", Box::new(ManagerLoopHandler)); + registry.register("stack.manager_loop", Box::new(SubWorkflowHandler)); let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), @@ -2742,7 +2742,7 @@ async fn manager_loop_max_cycles_exceeded_e2e() { let mut registry = HandlerRegistry::new(Box::new(SlowHandler)); registry.register("start", Box::new(StartHandler)); registry.register("exit", Box::new(ExitHandler)); - registry.register("stack.manager_loop", Box::new(ManagerLoopHandler)); + registry.register("stack.manager_loop", Box::new(SubWorkflowHandler)); let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { logs_root: dir.path().to_path_buf(), @@ -3323,7 +3323,7 @@ async fn manager_loop_runs_child_engine_e2e() { let mut registry = HandlerRegistry::new(Box::new(StartHandler)); registry.register("start", Box::new(StartHandler)); registry.register("exit", Box::new(ExitHandler)); - registry.register("stack.manager_loop", Box::new(ManagerLoopHandler)); + registry.register("stack.manager_loop", Box::new(SubWorkflowHandler)); let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { @@ -3456,7 +3456,7 @@ async fn manager_loop_context_flows_e2e() { registry.register("start", Box::new(StartHandler)); registry.register("exit", Box::new(ExitHandler)); registry.register("setter", Box::new(SetterHandler)); - registry.register("stack.manager_loop", Box::new(ManagerLoopHandler)); + registry.register("stack.manager_loop", Box::new(SubWorkflowHandler)); let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { @@ -3527,7 +3527,7 @@ async fn manager_loop_child_dotfile_e2e() { let mut registry = HandlerRegistry::new(Box::new(StartHandler)); registry.register("start", Box::new(StartHandler)); registry.register("exit", Box::new(ExitHandler)); - registry.register("stack.manager_loop", Box::new(ManagerLoopHandler)); + registry.register("stack.manager_loop", Box::new(SubWorkflowHandler)); let engine = PipelineEngine::new(registry, Arc::new(EventEmitter::new()), local_env()); let config = RunConfig { diff --git a/docs/specs/arc-spec.md b/docs/specs/arc-spec.md index b1b49d1b3..5a2689b4e 100644 --- a/docs/specs/arc-spec.md +++ b/docs/specs/arc-spec.md @@ -955,7 +955,7 @@ ScriptHandler: Orchestrates sprint-based iteration by supervising a child pipeline. The manager observes the child's telemetry, evaluates progress via a guard function, and optionally steers the child through intervention. ``` -ManagerLoopHandler: +SubWorkflowHandler: FUNCTION execute(node, context, graph, logs_root) -> Outcome: child_dotfile = graph.attrs.get("stack.child_dotfile") poll_interval = parse_duration(node.attrs.get("manager.poll_interval", "45s"))