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