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}