Skip to main content

agent_sdk/
hooks.rs

1//! Agent lifecycle hooks for customization.
2//!
3//! Hooks allow you to intercept and customize agent behavior at key points:
4//!
5//! - [`AgentHooks::pre_tool_use`] - Control tool execution permissions
6//! - [`AgentHooks::post_tool_use`] - React to tool completion
7//! - [`AgentHooks::on_event`] - Log or process events
8//! - [`AgentHooks::on_error`] - Handle errors and decide recovery
9//!
10//! # Built-in Implementations
11//!
12//! - [`DefaultHooks`] - Tier-based permissions (default)
13//! - [`AllowAllHooks`] - Allow all tools without confirmation
14//! - [`LoggingHooks`] - Debug logging for all events
15
16use crate::events::AgentEvent;
17use crate::llm;
18use crate::types::{ToolResult, ToolTier};
19use async_trait::async_trait;
20use serde_json::Value;
21
22/// Decision returned by pre-tool hooks
23#[derive(Debug, Clone)]
24pub enum ToolDecision {
25    /// Allow the tool to execute
26    Allow,
27    /// Block the tool execution with a message
28    Block(String),
29    /// Tool requires user confirmation.
30    RequiresConfirmation(String),
31}
32
33/// Lifecycle hooks for the agent loop.
34/// Implement this trait to customize agent behavior.
35#[async_trait]
36pub trait AgentHooks: Send + Sync {
37    /// Called before a tool is executed.
38    /// Return `ToolDecision::Allow` to proceed, or block/require confirmation.
39    async fn pre_tool_use(&self, tool_name: &str, input: &Value, tier: ToolTier) -> ToolDecision {
40        // Default: allow Observe tier, require confirmation for Confirm tier
41        // input is available for implementors but not used in default
42        let _ = input;
43        match tier {
44            ToolTier::Observe => ToolDecision::Allow,
45            ToolTier::Confirm => {
46                ToolDecision::RequiresConfirmation(format!("Confirm {tool_name}?"))
47            }
48        }
49    }
50
51    /// Called after a tool completes execution.
52    async fn post_tool_use(&self, _tool_name: &str, _result: &ToolResult) {
53        // Default: no-op
54    }
55
56    /// Called when the agent emits an event.
57    /// Can be used for logging, metrics, or custom handling.
58    async fn on_event(&self, _event: &AgentEvent) {
59        // Default: no-op
60    }
61
62    /// Called when an error occurs.
63    /// Return true to attempt recovery, false to abort.
64    async fn on_error(&self, _error: &anyhow::Error) -> bool {
65        // Default: don't recover
66        false
67    }
68
69    /// Called when context is about to be compacted due to length.
70    /// Return a summary to use, or None to use default summarization.
71    async fn on_context_compact(&self, _messages: &[llm::Message]) -> Option<String> {
72        // Default: use built-in summarization
73        None
74    }
75}
76
77/// Default hooks implementation that uses tier-based decisions
78#[derive(Clone, Copy, Default)]
79pub struct DefaultHooks;
80
81#[async_trait]
82impl AgentHooks for DefaultHooks {}
83
84/// Hooks that allow all tools without confirmation
85#[derive(Clone, Copy, Default)]
86pub struct AllowAllHooks;
87
88#[async_trait]
89impl AgentHooks for AllowAllHooks {
90    async fn pre_tool_use(
91        &self,
92        _tool_name: &str,
93        _input: &Value,
94        _tier: ToolTier,
95    ) -> ToolDecision {
96        ToolDecision::Allow
97    }
98}
99
100/// Hooks that log all events (useful for debugging)
101#[derive(Clone, Copy, Default)]
102pub struct LoggingHooks;
103
104#[async_trait]
105impl AgentHooks for LoggingHooks {
106    async fn pre_tool_use(&self, tool_name: &str, input: &Value, tier: ToolTier) -> ToolDecision {
107        log::debug!("Pre-tool use tool={tool_name} input={input:?} tier={tier:?}");
108        DefaultHooks.pre_tool_use(tool_name, input, tier).await
109    }
110
111    async fn post_tool_use(&self, tool_name: &str, result: &ToolResult) {
112        log::debug!(
113            "Post-tool use tool={tool_name} success={} duration_ms={:?}",
114            result.success,
115            result.duration_ms
116        );
117    }
118
119    async fn on_event(&self, event: &AgentEvent) {
120        log::debug!("Agent event {event:?}");
121    }
122
123    async fn on_error(&self, error: &anyhow::Error) -> bool {
124        log::error!("Agent error {error:?}");
125        false
126    }
127}