Skip to main content

scud/attractor/handlers/
mod.rs

1//! Node handler trait and registry.
2
3pub mod codergen;
4pub mod conditional;
5pub mod exit;
6pub mod fan_in;
7pub mod human;
8pub mod manager;
9pub mod parallel;
10pub mod start;
11pub mod tool;
12
13use anyhow::Result;
14use async_trait::async_trait;
15use std::collections::HashMap;
16
17use super::context::Context;
18use super::graph::{PipelineGraph, PipelineNode};
19use super::outcome::Outcome;
20use super::run_directory::RunDirectory;
21
22/// Trait for node handlers that execute pipeline stages.
23#[async_trait]
24pub trait Handler: Send + Sync {
25    /// Execute this handler for the given node.
26    async fn execute(
27        &self,
28        node: &PipelineNode,
29        context: &Context,
30        graph: &PipelineGraph,
31        run_dir: &RunDirectory,
32    ) -> Result<Outcome>;
33}
34
35/// Registry that maps handler type names to handler implementations.
36pub struct HandlerRegistry {
37    handlers: HashMap<String, Box<dyn Handler>>,
38    default_handler: Box<dyn Handler>,
39}
40
41impl HandlerRegistry {
42    /// Create a new registry with default handlers.
43    pub fn new_with_defaults() -> Self {
44        let mut handlers: HashMap<String, Box<dyn Handler>> = HashMap::new();
45        handlers.insert("start".into(), Box::new(start::StartHandler));
46        handlers.insert("exit".into(), Box::new(exit::ExitHandler));
47        handlers.insert("conditional".into(), Box::new(conditional::ConditionalHandler));
48        handlers.insert("wait.human".into(), Box::new(human::HumanHandler));
49        handlers.insert("tool".into(), Box::new(tool::ToolHandler));
50        handlers.insert("parallel".into(), Box::new(parallel::ParallelHandler));
51        handlers.insert(
52            "parallel.fan_in".into(),
53            Box::new(fan_in::FanInHandler),
54        );
55        handlers.insert(
56            "stack.manager_loop".into(),
57            Box::new(manager::ManagerHandler),
58        );
59
60        Self {
61            handlers,
62            default_handler: Box::new(codergen::CodergenHandler::simulated()),
63        }
64    }
65
66    /// Create a registry with a codergen handler backed by an AgentBackend.
67    ///
68    /// Both the explicit "codergen" entry and the default handler use the
69    /// provided backend, so unknown handler types also get real LLM execution.
70    pub fn with_backend(backend: std::sync::Arc<dyn crate::backend::AgentBackend>) -> Self {
71        let mut registry = Self::new_with_defaults();
72        registry.handlers.insert(
73            "codergen".into(),
74            Box::new(codergen::CodergenHandler::new(backend.clone())),
75        );
76        registry.default_handler = Box::new(codergen::CodergenHandler::new(backend));
77        registry
78    }
79
80    /// Register a custom handler.
81    pub fn register(&mut self, handler_type: &str, handler: Box<dyn Handler>) {
82        self.handlers.insert(handler_type.to_string(), handler);
83    }
84
85    /// Get the handler for a given type, falling back to the default.
86    pub fn get(&self, handler_type: &str) -> &dyn Handler {
87        self.handlers
88            .get(handler_type)
89            .map(|h| h.as_ref())
90            .unwrap_or(self.default_handler.as_ref())
91    }
92}