Skip to main content

agent_sdk/agent/
hooks.rs

1use crate::error::AgentId;
2use crate::types::task::Task;
3
4/// Events that can trigger hooks.
5#[derive(Debug, Clone)]
6pub enum HookEvent {
7    /// A teammate has finished its current work and is about to go idle.
8    /// Return `HookResult::Reject` with feedback to keep it working.
9    TeammateIdle {
10        agent_id: AgentId,
11        name: String,
12        tasks_completed: usize,
13    },
14
15    /// A task is being created in the task store.
16    /// Return `HookResult::Reject` to prevent creation.
17    TaskCreated {
18        task: Task,
19    },
20
21    /// A task is being marked as completed.
22    /// Return `HookResult::Reject` to prevent completion and send feedback.
23    TaskCompleted {
24        task: Task,
25        agent_id: AgentId,
26    },
27}
28
29/// Result of a hook evaluation.
30#[derive(Debug, Clone)]
31pub enum HookResult {
32    /// Allow the action to proceed.
33    Continue,
34    /// Reject the action with feedback (equivalent to exit code 2 in Claude Code).
35    Reject { feedback: String },
36}
37
38/// Trait for implementing quality gates and lifecycle hooks.
39///
40/// Hooks run synchronously during agent team execution. They can inspect
41/// events and either allow them to proceed or reject them with feedback.
42pub trait Hook: Send + Sync {
43    fn on_event(&self, event: &HookEvent) -> HookResult;
44}
45
46/// A collection of hooks that are evaluated in order.
47pub struct HookRegistry {
48    hooks: Vec<Box<dyn Hook>>,
49}
50
51impl HookRegistry {
52    pub fn new() -> Self {
53        Self { hooks: Vec::new() }
54    }
55
56    pub fn add(&mut self, hook: impl Hook + 'static) {
57        self.hooks.push(Box::new(hook));
58    }
59
60    /// Evaluate all hooks for an event. Returns `Reject` on the first rejection.
61    pub fn evaluate(&self, event: &HookEvent) -> HookResult {
62        for hook in &self.hooks {
63            if let HookResult::Reject { feedback } = hook.on_event(event) {
64                return HookResult::Reject { feedback };
65            }
66        }
67        HookResult::Continue
68    }
69
70    pub fn is_empty(&self) -> bool {
71        self.hooks.is_empty()
72    }
73}
74
75impl Default for HookRegistry {
76    fn default() -> Self {
77        Self::new()
78    }
79}