Rename ManagerLoopHandler to SubWorkflowHandler

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-03-01 23:39:58 -05:00
parent f45c1b1e2d
commit 7002f0638a
5 changed files with 19 additions and 19 deletions

View file

@ -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`.

View file

@ -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(),

View file

@ -138,7 +138,7 @@ pub fn default_registry(
);
registry.register(
"stack.manager_loop",
Box::new(manager_loop::ManagerLoopHandler),
Box::new(manager_loop::SubWorkflowHandler),
);
registry
}

View file

@ -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<dyn Interviewer>) -> 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 {

View file

@ -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"))