Skip to main content

agentic_memory/v3/
claude_hooks.rs

1//! Hooks for integrating with Claude Code's session management.
2//! These functions are called by Claude Code at key moments.
3
4use super::block::*;
5use super::engine::{MemoryEngineV3, SessionResumeResult};
6
7/// Claude Code integration hooks
8pub struct ClaudeHooks;
9
10impl ClaudeHooks {
11    /// Hook called at the START of every Claude Code message
12    pub fn on_message_start(
13        engine: &MemoryEngineV3,
14        role: &str,
15        content: &str,
16        _context_tokens: u32,
17    ) {
18        let _ = match role {
19            "user" => engine.capture_user_message(content, Some(estimate_tokens(content))),
20            "assistant" => {
21                engine.capture_assistant_message(content, Some(estimate_tokens(content)))
22            }
23            _ => engine.capture_user_message(content, Some(estimate_tokens(content))),
24        };
25    }
26
27    /// Hook called BEFORE every tool execution
28    pub fn on_tool_start(_engine: &MemoryEngineV3, _tool_name: &str, _input: &serde_json::Value) {
29        // We'll capture the full tool call when it completes
30        // This is just for tracking that a tool started
31    }
32
33    /// Hook called AFTER every tool execution
34    pub fn on_tool_complete(
35        engine: &MemoryEngineV3,
36        tool_name: &str,
37        input: serde_json::Value,
38        output: serde_json::Value,
39        duration_ms: u64,
40        success: bool,
41    ) {
42        let _ = engine.capture_tool_call(
43            tool_name,
44            input.clone(),
45            Some(output),
46            Some(duration_ms),
47            success,
48        );
49
50        // Special handling for file tools
51        if tool_name == "create_file" || tool_name == "str_replace" || tool_name == "view" {
52            if let Some(path) = input.get("path").and_then(|p| p.as_str()) {
53                let op = match tool_name {
54                    "create_file" => FileOperation::Create,
55                    "str_replace" => FileOperation::Update,
56                    "view" => FileOperation::Read,
57                    _ => FileOperation::Read,
58                };
59                let _ = engine.capture_file_operation(path, op, None, None, None);
60            }
61        }
62    }
63
64    /// Hook called when Claude Code detects context pressure
65    pub fn on_context_pressure(engine: &MemoryEngineV3, current_tokens: u32, max_tokens: u32) {
66        if current_tokens as f32 / max_tokens as f32 > 0.8 {
67            // Context is getting full, capture checkpoint
68            let _ = engine.capture_checkpoint(
69                vec![], // Would be populated by Claude Code
70                "Context pressure detected",
71                vec![],
72            );
73        }
74    }
75
76    /// Hook called BEFORE compaction happens
77    /// THIS IS THE CRITICAL MOMENT — capture everything before it's lost
78    pub fn on_pre_compaction(
79        engine: &MemoryEngineV3,
80        context_tokens_before: u32,
81        summary: &str,
82        active_files: Vec<String>,
83        pending_tasks: Vec<String>,
84        working_context: &str,
85    ) {
86        // Capture full checkpoint
87        let _ = engine.capture_checkpoint(active_files.clone(), working_context, pending_tasks);
88
89        // Capture the boundary event
90        let _ = engine.capture_boundary(
91            BoundaryType::Compaction,
92            context_tokens_before,
93            0, // Will be filled after compaction
94            summary,
95            Some(&format!("Active files: {:?}", active_files)),
96        );
97    }
98
99    /// Hook called AFTER compaction completes
100    pub fn on_post_compaction(_engine: &MemoryEngineV3, _context_tokens_after: u32) {
101        // In practice, we'd update the last boundary block
102        // with the post-compaction token count
103    }
104
105    /// Hook called at SESSION END
106    pub fn on_session_end(engine: &MemoryEngineV3, summary: &str) {
107        let _ = engine.capture_boundary(BoundaryType::SessionEnd, 0, 0, summary, None);
108    }
109
110    /// Hook called at SESSION START (resume)
111    pub fn on_session_start(engine: &MemoryEngineV3) -> SessionResumeResult {
112        // Mark new session
113        let _ = engine.capture_boundary(
114            BoundaryType::SessionStart,
115            0,
116            0,
117            "New session started",
118            None,
119        );
120
121        // Return full context for Claude Code to use
122        engine.session_resume()
123    }
124}
125
126/// Helper: estimate tokens from text
127fn estimate_tokens(text: &str) -> u32 {
128    // Rough estimate: 4 characters per token
129    (text.len() / 4) as u32 + 1
130}