nexus_memory_hooks/base.rs
1//! AgentHook trait definition
2//!
3//! This module defines the core AgentHook trait that all agent hooks must implement.
4
5use async_trait::async_trait;
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use std::sync::Arc;
9
10use crate::error::Result;
11use crate::session::SessionContext;
12use crate::types::{ExtractionSource, SessionActivity};
13
14/// Callback type for session end events
15pub type SessionEndCallback = Arc<dyn Fn(SessionContext) + Send + Sync>;
16
17/// Result of a hook operation
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct HookResult {
20 /// Whether the operation succeeded
21 pub success: bool,
22
23 /// Agent type
24 pub agent_type: String,
25
26 /// Source of the hook trigger
27 pub source: ExtractionSource,
28
29 /// Extracted context (if any)
30 pub context: Option<SessionContext>,
31
32 /// Error message (if failed)
33 pub error: Option<String>,
34
35 /// When this result was created
36 pub timestamp: DateTime<Utc>,
37}
38
39impl HookResult {
40 /// Create a successful result
41 pub fn success(agent_type: impl Into<String>, source: ExtractionSource) -> Self {
42 Self {
43 success: true,
44 agent_type: agent_type.into(),
45 source,
46 context: None,
47 error: None,
48 timestamp: Utc::now(),
49 }
50 }
51
52 /// Create a successful result with context
53 pub fn success_with_context(
54 agent_type: impl Into<String>,
55 source: ExtractionSource,
56 context: SessionContext,
57 ) -> Self {
58 Self {
59 success: true,
60 agent_type: agent_type.into(),
61 source,
62 context: Some(context),
63 error: None,
64 timestamp: Utc::now(),
65 }
66 }
67
68 /// Create a failed result
69 pub fn failure(
70 agent_type: impl Into<String>,
71 source: ExtractionSource,
72 error: impl Into<String>,
73 ) -> Self {
74 Self {
75 success: false,
76 agent_type: agent_type.into(),
77 source,
78 context: None,
79 error: Some(error.into()),
80 timestamp: Utc::now(),
81 }
82 }
83}
84
85/// AgentHook trait - all agent hooks must implement this
86///
87/// This trait defines the interface for agent-specific hooks that enable
88/// automated memory extraction from agent sessions.
89///
90/// # Implementation Notes
91///
92/// - All methods are async for non-blocking operation
93/// - Hooks should be thread-safe (Send + Sync)
94/// - Installation should be idempotent
95///
96/// # Example
97///
98/// ```rust,ignore
99/// use nexus_hooks::{AgentHook, SessionContext};
100/// use async_trait::async_trait;
101///
102/// struct MyAgentHook {
103/// agent_type: String,
104/// }
105///
106/// #[async_trait]
107/// impl AgentHook for MyAgentHook {
108/// fn agent_type(&self) -> &str {
109/// &self.agent_type
110/// }
111///
112/// async fn install_session_end_hook(&mut self, callback: SessionEndCallback) -> Result<()> {
113/// // Install hook...
114/// Ok(())
115/// }
116///
117/// async fn detect_session_activity(&self) -> Result<SessionActivity> {
118/// // Detect activity...
119/// Ok(SessionActivity::new(AgentType::Generic))
120/// }
121///
122/// async fn extract_session_context(&self) -> Result<SessionContext> {
123/// // Extract context...
124/// Ok(SessionContext::new(self.agent_type.clone()))
125/// }
126/// }
127/// ```
128#[async_trait]
129pub trait AgentHook: Send + Sync {
130 /// Get the agent type this hook handles
131 fn agent_type(&self) -> &str;
132
133 /// Install the session end hook
134 ///
135 /// This sets up the native hook mechanism for the agent. When a session
136 /// ends, the callback will be invoked with the extracted context.
137 ///
138 /// # Arguments
139 ///
140 /// * `callback` - Function to call when session ends
141 ///
142 /// # Returns
143 ///
144 /// Ok(()) if hook was installed successfully
145 async fn install_session_end_hook(&mut self, callback: SessionEndCallback) -> Result<()>;
146
147 /// Detect if the agent session is currently active
148 ///
149 /// This method checks for agent activity through various means:
150 /// - Process detection
151 /// - Session file monitoring
152 /// - Agent-specific indicators
153 ///
154 /// # Returns
155 ///
156 /// SessionActivity with current state
157 async fn detect_session_activity(&self) -> Result<SessionActivity>;
158
159 /// Extract session context from the agent
160 ///
161 /// This method extracts all relevant context from the current or
162 /// recent agent session, including:
163 /// - Conversation history
164 /// - Decisions made
165 /// - Files modified
166 /// - Commands executed
167 /// - Errors encountered
168 ///
169 /// # Returns
170 ///
171 /// SessionContext with extracted data
172 async fn extract_session_context(&self) -> Result<SessionContext>;
173
174 /// Optional: Install a checkpoint hook
175 ///
176 /// Some agents support periodic checkpointing during long sessions.
177 /// This allows for incremental context extraction.
178 async fn install_checkpoint_hook(&mut self, _callback: SessionEndCallback) -> Result<()> {
179 // Default: not supported
180 Err(crate::error::HookError::NotSupported(
181 "Checkpoint hooks not supported for this agent".to_string(),
182 ))
183 }
184
185 /// Optional: Install an error hook
186 ///
187 /// Some agents can trigger hooks when errors occur.
188 async fn install_error_hook(&mut self, _callback: SessionEndCallback) -> Result<()> {
189 // Default: not supported
190 Err(crate::error::HookError::NotSupported(
191 "Error hooks not supported for this agent".to_string(),
192 ))
193 }
194
195 /// Optional: Check if native hook is installed
196 fn is_hook_installed(&self) -> bool {
197 false
198 }
199
200 /// Optional: Uninstall all hooks
201 async fn uninstall_hooks(&mut self) -> Result<()> {
202 Ok(())
203 }
204
205 /// Optional: Get hook reliability score (0.0-1.0)
206 fn reliability_score(&self) -> f32 {
207 1.0
208 }
209}
210
211/// Base hook implementation with common functionality
212pub struct BaseHook {
213 /// Agent type name
214 pub agent_type: String,
215
216 /// Whether hook is installed
217 pub installed: bool,
218
219 /// Registered callbacks
220 pub callbacks: Vec<SessionEndCallback>,
221}
222
223impl BaseHook {
224 /// Create a new base hook
225 pub fn new(agent_type: impl Into<String>) -> Self {
226 Self {
227 agent_type: agent_type.into(),
228 installed: false,
229 callbacks: Vec::new(),
230 }
231 }
232
233 /// Add a callback
234 pub fn add_callback(&mut self, callback: SessionEndCallback) {
235 self.callbacks.push(callback);
236 }
237
238 /// Trigger all callbacks
239 pub fn trigger_callbacks(&self, context: SessionContext) {
240 for callback in &self.callbacks {
241 callback(context.clone());
242 }
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249
250 #[test]
251 fn test_hook_result_success() {
252 let result = HookResult::success("test-agent", ExtractionSource::Manual);
253 assert!(result.success);
254 assert!(result.error.is_none());
255 }
256
257 #[test]
258 fn test_hook_result_failure() {
259 let result = HookResult::failure(
260 "test-agent",
261 ExtractionSource::Manual,
262 "Something went wrong",
263 );
264 assert!(!result.success);
265 assert!(result.error.is_some());
266 assert_eq!(result.error.unwrap(), "Something went wrong");
267 }
268
269 #[test]
270 fn test_hook_result_with_context() {
271 let ctx = SessionContext::new("test");
272 let result = HookResult::success_with_context(
273 "test-agent",
274 ExtractionSource::NativeHook("skill".to_string()),
275 ctx,
276 );
277 assert!(result.success);
278 assert!(result.context.is_some());
279 }
280
281 #[test]
282 fn test_base_hook() {
283 let mut hook = BaseHook::new("test");
284 assert_eq!(hook.agent_type, "test");
285 assert!(!hook.installed);
286
287 let called = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
288 let called_clone = called.clone();
289 hook.add_callback(Arc::new(move |_ctx| {
290 called_clone.store(true, std::sync::atomic::Ordering::SeqCst);
291 }));
292
293 hook.trigger_callbacks(SessionContext::new("test"));
294 assert!(called.load(std::sync::atomic::Ordering::SeqCst));
295 }
296}