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    /// Tool requires PIN verification
32    RequiresPin(String),
33}
34
35/// Lifecycle hooks for the agent loop.
36/// Implement this trait to customize agent behavior.
37#[async_trait]
38pub trait AgentHooks: Send + Sync {
39    /// Called before a tool is executed.
40    /// Return `ToolDecision::Allow` to proceed, or block/require confirmation.
41    async fn pre_tool_use(&self, tool_name: &str, input: &Value, tier: ToolTier) -> ToolDecision {
42        // Default: allow Observe tier, require confirmation for others
43        // input is available for implementors but not used in default
44        let _ = input;
45        match tier {
46            ToolTier::Observe => ToolDecision::Allow,
47            ToolTier::Confirm => {
48                ToolDecision::RequiresConfirmation(format!("Confirm {tool_name}?"))
49            }
50            ToolTier::RequiresPin => {
51                ToolDecision::RequiresPin(format!("{tool_name} requires PIN verification"))
52            }
53        }
54    }
55
56    /// Called after a tool completes execution.
57    async fn post_tool_use(&self, _tool_name: &str, _result: &ToolResult) {
58        // Default: no-op
59    }
60
61    /// Called when the agent emits an event.
62    /// Can be used for logging, metrics, or custom handling.
63    async fn on_event(&self, _event: &AgentEvent) {
64        // Default: no-op
65    }
66
67    /// Called when an error occurs.
68    /// Return true to attempt recovery, false to abort.
69    async fn on_error(&self, _error: &anyhow::Error) -> bool {
70        // Default: don't recover
71        false
72    }
73
74    /// Called when context is about to be compacted due to length.
75    /// Return a summary to use, or None to use default summarization.
76    async fn on_context_compact(&self, _messages: &[llm::Message]) -> Option<String> {
77        // Default: use built-in summarization
78        None
79    }
80}
81
82/// Default hooks implementation that uses tier-based decisions
83#[derive(Clone, Copy, Default)]
84pub struct DefaultHooks;
85
86#[async_trait]
87impl AgentHooks for DefaultHooks {}
88
89/// Hooks that allow all tools without confirmation
90#[derive(Clone, Copy, Default)]
91pub struct AllowAllHooks;
92
93#[async_trait]
94impl AgentHooks for AllowAllHooks {
95    async fn pre_tool_use(
96        &self,
97        _tool_name: &str,
98        _input: &Value,
99        _tier: ToolTier,
100    ) -> ToolDecision {
101        ToolDecision::Allow
102    }
103}
104
105/// Hooks that log all events (useful for debugging)
106#[derive(Clone, Copy, Default)]
107pub struct LoggingHooks;
108
109#[async_trait]
110impl AgentHooks for LoggingHooks {
111    async fn pre_tool_use(&self, tool_name: &str, input: &Value, tier: ToolTier) -> ToolDecision {
112        tracing::debug!(tool = tool_name, ?input, ?tier, "Pre-tool use");
113        DefaultHooks.pre_tool_use(tool_name, input, tier).await
114    }
115
116    async fn post_tool_use(&self, tool_name: &str, result: &ToolResult) {
117        tracing::debug!(
118            tool = tool_name,
119            success = result.success,
120            duration_ms = result.duration_ms,
121            "Post-tool use"
122        );
123    }
124
125    async fn on_event(&self, event: &AgentEvent) {
126        tracing::debug!(?event, "Agent event");
127    }
128
129    async fn on_error(&self, error: &anyhow::Error) -> bool {
130        tracing::error!(?error, "Agent error");
131        false
132    }
133}