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}