Skip to main content

agent_sdk_tools/
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 agent_sdk_foundation::events::AgentEvent;
17use agent_sdk_foundation::llm;
18use agent_sdk_foundation::types::{ToolInvocation, ToolResult, ToolTier};
19use async_trait::async_trait;
20
21/// Decision returned by pre-tool hooks
22#[derive(Debug, Clone)]
23#[non_exhaustive]
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    ///
39    /// Receives a structured [`ToolInvocation`] that bundles tool identity,
40    /// tier, requested input, effective input, and listen-context — everything
41    /// a server-side policy engine needs for an allow / block / confirm decision.
42    ///
43    /// Return [`ToolDecision::Allow`] to proceed, [`ToolDecision::Block`] to
44    /// reject, or [`ToolDecision::RequiresConfirmation`] to yield for user
45    /// approval.
46    async fn pre_tool_use(&self, invocation: &ToolInvocation) -> ToolDecision {
47        match invocation.tier {
48            ToolTier::Observe => ToolDecision::Allow,
49            ToolTier::Confirm => {
50                ToolDecision::RequiresConfirmation(format!("Confirm {}?", invocation.tool_name))
51            }
52        }
53    }
54
55    /// Called after a tool completes execution.
56    async fn post_tool_use(&self, _tool_name: &str, _result: &ToolResult) {
57        // Default: no-op
58    }
59
60    /// Called when the agent emits an event.
61    /// Can be used for logging, metrics, or custom handling.
62    async fn on_event(&self, _event: &AgentEvent) {
63        // Default: no-op
64    }
65
66    /// Called when an error occurs.
67    /// Return true to attempt recovery, false to abort.
68    async fn on_error(&self, _error: &anyhow::Error) -> bool {
69        // Default: don't recover
70        false
71    }
72
73    /// Called when context is about to be compacted due to length.
74    /// Return a summary to use, or None to use default summarization.
75    async fn on_context_compact(&self, _messages: &[llm::Message]) -> Option<String> {
76        // Default: use built-in summarization
77        None
78    }
79}
80
81/// Default hooks implementation that uses tier-based decisions
82#[derive(Clone, Copy, Default)]
83pub struct DefaultHooks;
84
85#[async_trait]
86impl AgentHooks for DefaultHooks {}
87
88/// Hooks that allow all tools without confirmation
89#[derive(Clone, Copy, Default)]
90pub struct AllowAllHooks;
91
92#[async_trait]
93impl AgentHooks for AllowAllHooks {
94    async fn pre_tool_use(&self, _invocation: &ToolInvocation) -> ToolDecision {
95        ToolDecision::Allow
96    }
97}
98
99/// Hooks that log all events (useful for debugging)
100#[derive(Clone, Copy, Default)]
101pub struct LoggingHooks;
102
103#[async_trait]
104impl AgentHooks for LoggingHooks {
105    async fn pre_tool_use(&self, invocation: &ToolInvocation) -> ToolDecision {
106        log::debug!(
107            "Pre-tool use tool={} input={:?} tier={:?}",
108            invocation.tool_name,
109            invocation.requested_input,
110            invocation.tier,
111        );
112        DefaultHooks.pre_tool_use(invocation).await
113    }
114
115    async fn post_tool_use(&self, tool_name: &str, result: &ToolResult) {
116        log::debug!(
117            "Post-tool use tool={tool_name} success={} duration_ms={:?}",
118            result.success,
119            result.duration_ms
120        );
121    }
122
123    async fn on_event(&self, event: &AgentEvent) {
124        log::debug!("Agent event {event:?}");
125    }
126
127    async fn on_error(&self, error: &anyhow::Error) -> bool {
128        log::error!("Agent error {error:?}");
129        false
130    }
131}