claude-mpm 3.3.0__py3-none-any.whl → 3.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/agents/templates/data_engineer.json +1 -1
- claude_mpm/agents/templates/documentation.json +1 -1
- claude_mpm/agents/templates/engineer.json +1 -1
- claude_mpm/agents/templates/ops.json +1 -1
- claude_mpm/agents/templates/pm.json +1 -1
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/agents/templates/research.json +1 -1
- claude_mpm/agents/templates/security.json +1 -1
- claude_mpm/agents/templates/test_integration.json +112 -0
- claude_mpm/agents/templates/version_control.json +1 -1
- claude_mpm/cli/commands/memory.py +749 -26
- claude_mpm/cli/commands/run.py +115 -14
- claude_mpm/cli/parser.py +89 -1
- claude_mpm/constants.py +6 -0
- claude_mpm/core/claude_runner.py +74 -11
- claude_mpm/core/config.py +1 -1
- claude_mpm/core/session_manager.py +46 -0
- claude_mpm/core/simple_runner.py +74 -11
- claude_mpm/hooks/builtin/mpm_command_hook.py +5 -5
- claude_mpm/hooks/claude_hooks/hook_handler.py +213 -30
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +9 -2
- claude_mpm/hooks/memory_integration_hook.py +51 -5
- claude_mpm/services/__init__.py +23 -5
- claude_mpm/services/agent_memory_manager.py +800 -71
- claude_mpm/services/memory_builder.py +823 -0
- claude_mpm/services/memory_optimizer.py +619 -0
- claude_mpm/services/memory_router.py +445 -0
- claude_mpm/services/project_analyzer.py +771 -0
- claude_mpm/services/socketio_server.py +649 -45
- claude_mpm/services/version_control/git_operations.py +26 -0
- claude_mpm-3.4.0.dist-info/METADATA +183 -0
- {claude_mpm-3.3.0.dist-info → claude_mpm-3.4.0.dist-info}/RECORD +36 -52
- claude_mpm/agents/agent-template.yaml +0 -83
- claude_mpm/agents/templates/test-integration-agent.md +0 -34
- claude_mpm/agents/test_fix_deployment/.claude-pm/config/project.json +0 -6
- claude_mpm/cli/README.md +0 -109
- claude_mpm/cli_module/refactoring_guide.md +0 -253
- claude_mpm/core/agent_registry.py.bak +0 -312
- claude_mpm/core/base_service.py.bak +0 -406
- claude_mpm/core/websocket_handler.py +0 -233
- claude_mpm/hooks/README.md +0 -97
- claude_mpm/orchestration/SUBPROCESS_DESIGN.md +0 -66
- claude_mpm/schemas/README_SECURITY.md +0 -92
- claude_mpm/schemas/agent_schema.json +0 -395
- claude_mpm/schemas/agent_schema_documentation.md +0 -181
- claude_mpm/schemas/agent_schema_security_notes.md +0 -165
- claude_mpm/schemas/examples/standard_workflow.json +0 -505
- claude_mpm/schemas/ticket_workflow_documentation.md +0 -482
- claude_mpm/schemas/ticket_workflow_schema.json +0 -590
- claude_mpm/services/framework_claude_md_generator/README.md +0 -92
- claude_mpm/services/parent_directory_manager/README.md +0 -83
- claude_mpm/services/version_control/VERSION +0 -1
- claude_mpm/services/websocket_server.py +0 -376
- claude_mpm-3.3.0.dist-info/METADATA +0 -432
- {claude_mpm-3.3.0.dist-info → claude_mpm-3.4.0.dist-info}/WHEEL +0 -0
- {claude_mpm-3.3.0.dist-info → claude_mpm-3.4.0.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.3.0.dist-info → claude_mpm-3.4.0.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.3.0.dist-info → claude_mpm-3.4.0.dist-info}/top_level.txt +0 -0
claude_mpm/core/simple_runner.py
CHANGED
|
@@ -13,10 +13,14 @@ import uuid
|
|
|
13
13
|
try:
|
|
14
14
|
from claude_mpm.services.agent_deployment import AgentDeploymentService
|
|
15
15
|
from claude_mpm.services.ticket_manager import TicketManager
|
|
16
|
+
from claude_mpm.services.hook_service import HookService
|
|
17
|
+
from claude_mpm.core.config import Config
|
|
16
18
|
from claude_mpm.core.logger import get_logger, get_project_logger, ProjectLogger
|
|
17
19
|
except ImportError:
|
|
18
20
|
from claude_mpm.services.agent_deployment import AgentDeploymentService
|
|
19
21
|
from claude_mpm.services.ticket_manager import TicketManager
|
|
22
|
+
from claude_mpm.services.hook_service import HookService
|
|
23
|
+
from claude_mpm.core.config import Config
|
|
20
24
|
from claude_mpm.core.logger import get_logger, get_project_logger, ProjectLogger
|
|
21
25
|
|
|
22
26
|
|
|
@@ -76,6 +80,11 @@ class ClaudeRunner:
|
|
|
76
80
|
self.ticket_manager = None
|
|
77
81
|
self.enable_tickets = False
|
|
78
82
|
|
|
83
|
+
# Initialize hook service and register memory hooks
|
|
84
|
+
self.config = Config()
|
|
85
|
+
self.hook_service = HookService(self.config)
|
|
86
|
+
self._register_memory_hooks()
|
|
87
|
+
|
|
79
88
|
# Load system instructions
|
|
80
89
|
self.system_instructions = self._load_system_instructions()
|
|
81
90
|
|
|
@@ -95,7 +104,7 @@ class ClaudeRunner:
|
|
|
95
104
|
except Exception as e:
|
|
96
105
|
self.logger.debug(f"Failed to create session log file: {e}")
|
|
97
106
|
|
|
98
|
-
# Initialize
|
|
107
|
+
# Initialize Socket.IO server reference
|
|
99
108
|
self.websocket_server = None
|
|
100
109
|
|
|
101
110
|
def setup_agents(self) -> bool:
|
|
@@ -152,13 +161,14 @@ class ClaudeRunner:
|
|
|
152
161
|
|
|
153
162
|
def run_interactive(self, initial_context: Optional[str] = None):
|
|
154
163
|
"""Run Claude in interactive mode."""
|
|
155
|
-
#
|
|
164
|
+
# Connect to Socket.IO server if enabled
|
|
156
165
|
if self.enable_websocket:
|
|
157
166
|
try:
|
|
158
|
-
#
|
|
159
|
-
from claude_mpm.services.
|
|
160
|
-
self.websocket_server =
|
|
167
|
+
# Use Socket.IO client proxy to connect to monitoring server
|
|
168
|
+
from claude_mpm.services.socketio_server import SocketIOClientProxy
|
|
169
|
+
self.websocket_server = SocketIOClientProxy(port=self.websocket_port)
|
|
161
170
|
self.websocket_server.start()
|
|
171
|
+
self.logger.info("Connected to Socket.IO monitoring server")
|
|
162
172
|
|
|
163
173
|
# Generate session ID
|
|
164
174
|
session_id = str(uuid.uuid4())
|
|
@@ -171,7 +181,7 @@ class ClaudeRunner:
|
|
|
171
181
|
working_dir=working_dir
|
|
172
182
|
)
|
|
173
183
|
except Exception as e:
|
|
174
|
-
self.logger.warning(f"Failed to
|
|
184
|
+
self.logger.warning(f"Failed to connect to Socket.IO server: {e}")
|
|
175
185
|
self.websocket_server = None
|
|
176
186
|
|
|
177
187
|
# Get version
|
|
@@ -329,13 +339,14 @@ class ClaudeRunner:
|
|
|
329
339
|
"""Run Claude with a single prompt and return success status."""
|
|
330
340
|
start_time = time.time()
|
|
331
341
|
|
|
332
|
-
#
|
|
342
|
+
# Connect to Socket.IO server if enabled
|
|
333
343
|
if self.enable_websocket:
|
|
334
344
|
try:
|
|
335
|
-
#
|
|
336
|
-
from claude_mpm.services.
|
|
337
|
-
self.websocket_server =
|
|
345
|
+
# Use Socket.IO client proxy to connect to monitoring server
|
|
346
|
+
from claude_mpm.services.socketio_server import SocketIOClientProxy
|
|
347
|
+
self.websocket_server = SocketIOClientProxy(port=self.websocket_port)
|
|
338
348
|
self.websocket_server.start()
|
|
349
|
+
self.logger.info("Connected to Socket.IO monitoring server")
|
|
339
350
|
|
|
340
351
|
# Generate session ID
|
|
341
352
|
session_id = str(uuid.uuid4())
|
|
@@ -348,7 +359,7 @@ class ClaudeRunner:
|
|
|
348
359
|
working_dir=working_dir
|
|
349
360
|
)
|
|
350
361
|
except Exception as e:
|
|
351
|
-
self.logger.warning(f"Failed to
|
|
362
|
+
self.logger.warning(f"Failed to connect to Socket.IO server: {e}")
|
|
352
363
|
self.websocket_server = None
|
|
353
364
|
|
|
354
365
|
# Check for /mpm: commands
|
|
@@ -739,6 +750,58 @@ class ClaudeRunner:
|
|
|
739
750
|
except Exception as e:
|
|
740
751
|
self.logger.debug(f"Failed to log session event: {e}")
|
|
741
752
|
|
|
753
|
+
def _register_memory_hooks(self):
|
|
754
|
+
"""Register memory integration hooks with the hook service.
|
|
755
|
+
|
|
756
|
+
WHY: This activates the memory system by registering hooks that automatically
|
|
757
|
+
inject agent memory before delegation and extract learnings after delegation.
|
|
758
|
+
This is the critical connection point between the memory system and the CLI.
|
|
759
|
+
|
|
760
|
+
DESIGN DECISION: We register hooks here instead of in __init__ to ensure
|
|
761
|
+
all services are initialized first. Hooks are only registered if the memory
|
|
762
|
+
system is enabled in configuration.
|
|
763
|
+
"""
|
|
764
|
+
try:
|
|
765
|
+
# Only register if memory system is enabled
|
|
766
|
+
if not self.config.get('memory.enabled', True):
|
|
767
|
+
self.logger.debug("Memory system disabled - skipping hook registration")
|
|
768
|
+
return
|
|
769
|
+
|
|
770
|
+
# Import hook classes (lazy import to avoid circular dependencies)
|
|
771
|
+
from claude_mpm.hooks.memory_integration_hook import (
|
|
772
|
+
MemoryPreDelegationHook,
|
|
773
|
+
MemoryPostDelegationHook
|
|
774
|
+
)
|
|
775
|
+
|
|
776
|
+
# Register pre-delegation hook for memory injection
|
|
777
|
+
pre_hook = MemoryPreDelegationHook(self.config)
|
|
778
|
+
success = self.hook_service.register_hook(pre_hook)
|
|
779
|
+
if success:
|
|
780
|
+
self.logger.info(f"✅ Registered memory pre-delegation hook (priority: {pre_hook.priority})")
|
|
781
|
+
else:
|
|
782
|
+
self.logger.warning("❌ Failed to register memory pre-delegation hook")
|
|
783
|
+
|
|
784
|
+
# Register post-delegation hook if auto-learning is enabled
|
|
785
|
+
if self.config.get('memory.auto_learning', True): # Default to True now
|
|
786
|
+
post_hook = MemoryPostDelegationHook(self.config)
|
|
787
|
+
success = self.hook_service.register_hook(post_hook)
|
|
788
|
+
if success:
|
|
789
|
+
self.logger.info(f"✅ Registered memory post-delegation hook (priority: {post_hook.priority})")
|
|
790
|
+
else:
|
|
791
|
+
self.logger.warning("❌ Failed to register memory post-delegation hook")
|
|
792
|
+
else:
|
|
793
|
+
self.logger.info("ℹ️ Auto-learning disabled - skipping post-delegation hook")
|
|
794
|
+
|
|
795
|
+
# Log summary of registered hooks
|
|
796
|
+
hooks = self.hook_service.list_hooks()
|
|
797
|
+
pre_count = len(hooks.get('pre_delegation', []))
|
|
798
|
+
post_count = len(hooks.get('post_delegation', []))
|
|
799
|
+
self.logger.info(f"📋 Hook Service initialized: {pre_count} pre-delegation, {post_count} post-delegation hooks")
|
|
800
|
+
|
|
801
|
+
except Exception as e:
|
|
802
|
+
self.logger.error(f"❌ Failed to register memory hooks: {e}")
|
|
803
|
+
# Don't fail the entire initialization - memory system is optional
|
|
804
|
+
|
|
742
805
|
def _launch_subprocess_interactive(self, cmd: list, env: dict):
|
|
743
806
|
"""Launch Claude as a subprocess with PTY for interactive mode."""
|
|
744
807
|
import pty
|
|
@@ -12,11 +12,11 @@ logger = get_logger(__name__)
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class MpmCommandHook(SubmitHook):
|
|
15
|
-
"""Hook that intercepts /mpm
|
|
15
|
+
"""Hook that intercepts /mpm commands and routes them to the command router."""
|
|
16
16
|
|
|
17
17
|
def __init__(self):
|
|
18
18
|
super().__init__(name="mpm_command", priority=1) # High priority to intercept early
|
|
19
|
-
self.command_prefix = "/mpm
|
|
19
|
+
self.command_prefix = "/mpm "
|
|
20
20
|
self.command_router_path = self._find_command_router()
|
|
21
21
|
|
|
22
22
|
def _find_command_router(self) -> Path:
|
|
@@ -35,11 +35,11 @@ class MpmCommandHook(SubmitHook):
|
|
|
35
35
|
return Path(".claude/scripts/command_router.py").resolve()
|
|
36
36
|
|
|
37
37
|
def execute(self, context: HookContext) -> HookResult:
|
|
38
|
-
"""Check for /mpm
|
|
38
|
+
"""Check for /mpm commands and execute them directly."""
|
|
39
39
|
try:
|
|
40
40
|
prompt = context.data.get('prompt', '').strip()
|
|
41
41
|
|
|
42
|
-
# Check if this is an /mpm
|
|
42
|
+
# Check if this is an /mpm command
|
|
43
43
|
if not prompt.startswith(self.command_prefix):
|
|
44
44
|
# Not our command, pass through
|
|
45
45
|
return HookResult(
|
|
@@ -67,7 +67,7 @@ class MpmCommandHook(SubmitHook):
|
|
|
67
67
|
command = parts[0]
|
|
68
68
|
args = parts[1:]
|
|
69
69
|
|
|
70
|
-
logger.info(f"Executing /mpm
|
|
70
|
+
logger.info(f"Executing /mpm {command} with args: {args}")
|
|
71
71
|
|
|
72
72
|
# Execute command using command router
|
|
73
73
|
try:
|
|
@@ -20,9 +20,33 @@ from datetime import datetime
|
|
|
20
20
|
from pathlib import Path
|
|
21
21
|
from collections import deque
|
|
22
22
|
|
|
23
|
-
# Quick environment check
|
|
23
|
+
# Quick environment check - must be defined before any code that might use it
|
|
24
24
|
DEBUG = os.environ.get('CLAUDE_MPM_HOOK_DEBUG', '').lower() == 'true'
|
|
25
25
|
|
|
26
|
+
# Add imports for memory hook integration with comprehensive error handling
|
|
27
|
+
MEMORY_HOOKS_AVAILABLE = False
|
|
28
|
+
try:
|
|
29
|
+
# Try to add src to path if not already there (fallback for missing PYTHONPATH)
|
|
30
|
+
import sys
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
src_path = Path(__file__).parent.parent.parent.parent
|
|
33
|
+
if src_path.exists() and str(src_path) not in sys.path:
|
|
34
|
+
sys.path.insert(0, str(src_path))
|
|
35
|
+
|
|
36
|
+
from claude_mpm.services.hook_service import HookService
|
|
37
|
+
from claude_mpm.hooks.memory_integration_hook import (
|
|
38
|
+
MemoryPreDelegationHook,
|
|
39
|
+
MemoryPostDelegationHook
|
|
40
|
+
)
|
|
41
|
+
from claude_mpm.hooks.base_hook import HookContext, HookType
|
|
42
|
+
from claude_mpm.core.config import Config
|
|
43
|
+
MEMORY_HOOKS_AVAILABLE = True
|
|
44
|
+
except Exception as e:
|
|
45
|
+
# Catch all exceptions to prevent any import errors from breaking the handler
|
|
46
|
+
if DEBUG:
|
|
47
|
+
print(f"Memory hooks not available: {e}", file=sys.stderr)
|
|
48
|
+
MEMORY_HOOKS_AVAILABLE = False
|
|
49
|
+
|
|
26
50
|
# Socket.IO import
|
|
27
51
|
try:
|
|
28
52
|
import socketio
|
|
@@ -31,13 +55,7 @@ except ImportError:
|
|
|
31
55
|
SOCKETIO_AVAILABLE = False
|
|
32
56
|
socketio = None
|
|
33
57
|
|
|
34
|
-
#
|
|
35
|
-
try:
|
|
36
|
-
from ...services.websocket_server import get_server_instance
|
|
37
|
-
SERVER_AVAILABLE = True
|
|
38
|
-
except ImportError:
|
|
39
|
-
SERVER_AVAILABLE = False
|
|
40
|
-
get_server_instance = None
|
|
58
|
+
# No fallback needed - we only use Socket.IO now
|
|
41
59
|
|
|
42
60
|
|
|
43
61
|
class ClaudeHookHandler:
|
|
@@ -65,14 +83,14 @@ class ClaudeHookHandler:
|
|
|
65
83
|
self._git_branch_cache = {}
|
|
66
84
|
self._git_branch_cache_time = {}
|
|
67
85
|
|
|
68
|
-
# Initialize
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
86
|
+
# Initialize memory hooks if available
|
|
87
|
+
self.memory_hooks_initialized = False
|
|
88
|
+
self.pre_delegation_hook = None
|
|
89
|
+
self.post_delegation_hook = None
|
|
90
|
+
if MEMORY_HOOKS_AVAILABLE:
|
|
91
|
+
self._initialize_memory_hooks()
|
|
92
|
+
|
|
93
|
+
# No fallback server needed - we only use Socket.IO now
|
|
76
94
|
|
|
77
95
|
def _track_delegation(self, session_id: str, agent_type: str):
|
|
78
96
|
"""Track a new agent delegation."""
|
|
@@ -113,6 +131,49 @@ class ClaudeHookHandler:
|
|
|
113
131
|
|
|
114
132
|
return 'unknown'
|
|
115
133
|
|
|
134
|
+
def _initialize_memory_hooks(self):
|
|
135
|
+
"""Initialize memory hooks for automatic agent memory management.
|
|
136
|
+
|
|
137
|
+
WHY: This activates the memory system by connecting Claude Code hook events
|
|
138
|
+
to our memory integration hooks. This enables automatic memory injection
|
|
139
|
+
before delegations and learning extraction after delegations.
|
|
140
|
+
|
|
141
|
+
DESIGN DECISION: We initialize hooks here in the Claude hook handler because
|
|
142
|
+
this is where Claude Code events are processed. This ensures memory hooks
|
|
143
|
+
are triggered at the right times during agent delegation.
|
|
144
|
+
"""
|
|
145
|
+
try:
|
|
146
|
+
# Create configuration
|
|
147
|
+
config = Config()
|
|
148
|
+
|
|
149
|
+
# Only initialize if memory system is enabled
|
|
150
|
+
if not config.get('memory.enabled', True):
|
|
151
|
+
if DEBUG:
|
|
152
|
+
print("Memory system disabled - skipping hook initialization", file=sys.stderr)
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
# Initialize pre-delegation hook for memory injection
|
|
156
|
+
self.pre_delegation_hook = MemoryPreDelegationHook(config)
|
|
157
|
+
|
|
158
|
+
# Initialize post-delegation hook if auto-learning is enabled
|
|
159
|
+
if config.get('memory.auto_learning', True): # Default to True now
|
|
160
|
+
self.post_delegation_hook = MemoryPostDelegationHook(config)
|
|
161
|
+
|
|
162
|
+
self.memory_hooks_initialized = True
|
|
163
|
+
|
|
164
|
+
if DEBUG:
|
|
165
|
+
hooks_info = []
|
|
166
|
+
if self.pre_delegation_hook:
|
|
167
|
+
hooks_info.append("pre-delegation")
|
|
168
|
+
if self.post_delegation_hook:
|
|
169
|
+
hooks_info.append("post-delegation")
|
|
170
|
+
print(f"✅ Memory hooks initialized: {', '.join(hooks_info)}", file=sys.stderr)
|
|
171
|
+
|
|
172
|
+
except Exception as e:
|
|
173
|
+
if DEBUG:
|
|
174
|
+
print(f"❌ Failed to initialize memory hooks: {e}", file=sys.stderr)
|
|
175
|
+
# Don't fail the entire handler - memory system is optional
|
|
176
|
+
|
|
116
177
|
def _get_git_branch(self, working_dir: str = None) -> str:
|
|
117
178
|
"""Get git branch for the given directory with caching.
|
|
118
179
|
|
|
@@ -291,17 +352,6 @@ class ClaudeHookHandler:
|
|
|
291
352
|
print(f"Socket.IO emit failed: {e}", file=sys.stderr)
|
|
292
353
|
# Mark as disconnected so next call will reconnect
|
|
293
354
|
self.sio_connected = False
|
|
294
|
-
|
|
295
|
-
# Fallback to legacy WebSocket server
|
|
296
|
-
elif hasattr(self, 'websocket_server') and self.websocket_server:
|
|
297
|
-
try:
|
|
298
|
-
# Map to legacy event format
|
|
299
|
-
legacy_event = f"hook.{event}"
|
|
300
|
-
self.websocket_server.broadcast_event(legacy_event, data)
|
|
301
|
-
if DEBUG:
|
|
302
|
-
print(f"Emitted legacy event: {legacy_event}", file=sys.stderr)
|
|
303
|
-
except:
|
|
304
|
-
pass # Silent failure
|
|
305
355
|
|
|
306
356
|
def _handle_user_prompt_fast(self, event):
|
|
307
357
|
"""Handle user prompt with comprehensive data capture.
|
|
@@ -390,6 +440,9 @@ class ClaudeHookHandler:
|
|
|
390
440
|
session_id = event.get('session_id', '')
|
|
391
441
|
if session_id and agent_type != 'unknown':
|
|
392
442
|
self._track_delegation(session_id, agent_type)
|
|
443
|
+
|
|
444
|
+
# Trigger memory pre-delegation hook
|
|
445
|
+
self._trigger_memory_pre_delegation_hook(agent_type, tool_input, session_id)
|
|
393
446
|
|
|
394
447
|
self._emit_socketio_event('/hook', 'pre_tool', pre_tool_data)
|
|
395
448
|
|
|
@@ -431,6 +484,12 @@ class ClaudeHookHandler:
|
|
|
431
484
|
'output_size': len(str(result_data.get('output', ''))) if result_data.get('output') else 0
|
|
432
485
|
}
|
|
433
486
|
|
|
487
|
+
# Handle Task delegation completion for memory hooks
|
|
488
|
+
if tool_name == 'Task':
|
|
489
|
+
session_id = event.get('session_id', '')
|
|
490
|
+
agent_type = self._get_delegation_agent_type(session_id)
|
|
491
|
+
self._trigger_memory_post_delegation_hook(agent_type, event, session_id)
|
|
492
|
+
|
|
434
493
|
self._emit_socketio_event('/hook', 'post_tool', post_tool_data)
|
|
435
494
|
|
|
436
495
|
def _extract_tool_parameters(self, tool_name: str, tool_input: dict) -> dict:
|
|
@@ -735,6 +794,122 @@ class ClaudeHookHandler:
|
|
|
735
794
|
# Emit to /hook namespace
|
|
736
795
|
self._emit_socketio_event('/hook', 'subagent_stop', subagent_stop_data)
|
|
737
796
|
|
|
797
|
+
def _trigger_memory_pre_delegation_hook(self, agent_type: str, tool_input: dict, session_id: str):
|
|
798
|
+
"""Trigger memory pre-delegation hook for agent memory injection.
|
|
799
|
+
|
|
800
|
+
WHY: This connects Claude Code's Task delegation events to our memory system.
|
|
801
|
+
When Claude is about to delegate to an agent, we inject the agent's memory
|
|
802
|
+
into the delegation context so the agent has access to accumulated knowledge.
|
|
803
|
+
|
|
804
|
+
DESIGN DECISION: We modify the tool_input in place to inject memory context.
|
|
805
|
+
This ensures the agent receives the memory as part of their initial context.
|
|
806
|
+
"""
|
|
807
|
+
if not self.memory_hooks_initialized or not self.pre_delegation_hook:
|
|
808
|
+
return
|
|
809
|
+
|
|
810
|
+
try:
|
|
811
|
+
# Create hook context for memory injection
|
|
812
|
+
hook_context = HookContext(
|
|
813
|
+
hook_type=HookType.PRE_DELEGATION,
|
|
814
|
+
data={
|
|
815
|
+
'agent': agent_type,
|
|
816
|
+
'context': tool_input,
|
|
817
|
+
'session_id': session_id
|
|
818
|
+
},
|
|
819
|
+
metadata={
|
|
820
|
+
'source': 'claude_hook_handler',
|
|
821
|
+
'tool_name': 'Task'
|
|
822
|
+
},
|
|
823
|
+
timestamp=datetime.now().isoformat(),
|
|
824
|
+
session_id=session_id
|
|
825
|
+
)
|
|
826
|
+
|
|
827
|
+
# Execute pre-delegation hook
|
|
828
|
+
result = self.pre_delegation_hook.execute(hook_context)
|
|
829
|
+
|
|
830
|
+
if result.success and result.modified and result.data:
|
|
831
|
+
# Update tool_input with memory-enhanced context
|
|
832
|
+
enhanced_context = result.data.get('context', {})
|
|
833
|
+
if enhanced_context and 'agent_memory' in enhanced_context:
|
|
834
|
+
# Inject memory into the task prompt/description
|
|
835
|
+
original_prompt = tool_input.get('prompt', '')
|
|
836
|
+
memory_section = enhanced_context['agent_memory']
|
|
837
|
+
|
|
838
|
+
# Prepend memory to the original prompt
|
|
839
|
+
enhanced_prompt = f"{memory_section}\n\n{original_prompt}"
|
|
840
|
+
tool_input['prompt'] = enhanced_prompt
|
|
841
|
+
|
|
842
|
+
if DEBUG:
|
|
843
|
+
memory_size = len(memory_section.encode('utf-8'))
|
|
844
|
+
print(f"✅ Injected {memory_size} bytes of memory for agent '{agent_type}'", file=sys.stderr)
|
|
845
|
+
|
|
846
|
+
except Exception as e:
|
|
847
|
+
if DEBUG:
|
|
848
|
+
print(f"❌ Memory pre-delegation hook failed: {e}", file=sys.stderr)
|
|
849
|
+
# Don't fail the delegation - memory is optional
|
|
850
|
+
|
|
851
|
+
def _trigger_memory_post_delegation_hook(self, agent_type: str, event: dict, session_id: str):
|
|
852
|
+
"""Trigger memory post-delegation hook for learning extraction.
|
|
853
|
+
|
|
854
|
+
WHY: This connects Claude Code's Task completion events to our memory system.
|
|
855
|
+
When an agent completes a task, we extract learnings from the result and
|
|
856
|
+
store them in the agent's memory for future use.
|
|
857
|
+
|
|
858
|
+
DESIGN DECISION: We extract learnings from both the tool output and any
|
|
859
|
+
error messages, providing comprehensive context for the memory system.
|
|
860
|
+
"""
|
|
861
|
+
if not self.memory_hooks_initialized or not self.post_delegation_hook:
|
|
862
|
+
return
|
|
863
|
+
|
|
864
|
+
try:
|
|
865
|
+
# Extract result content from the event
|
|
866
|
+
result_content = ""
|
|
867
|
+
output = event.get('output', '')
|
|
868
|
+
error = event.get('error', '')
|
|
869
|
+
exit_code = event.get('exit_code', 0)
|
|
870
|
+
|
|
871
|
+
# Build result content
|
|
872
|
+
if output:
|
|
873
|
+
result_content = str(output)
|
|
874
|
+
elif error:
|
|
875
|
+
result_content = f"Error: {str(error)}"
|
|
876
|
+
else:
|
|
877
|
+
result_content = f"Task completed with exit code: {exit_code}"
|
|
878
|
+
|
|
879
|
+
# Create hook context for learning extraction
|
|
880
|
+
hook_context = HookContext(
|
|
881
|
+
hook_type=HookType.POST_DELEGATION,
|
|
882
|
+
data={
|
|
883
|
+
'agent': agent_type,
|
|
884
|
+
'result': {
|
|
885
|
+
'content': result_content,
|
|
886
|
+
'success': exit_code == 0,
|
|
887
|
+
'exit_code': exit_code
|
|
888
|
+
},
|
|
889
|
+
'session_id': session_id
|
|
890
|
+
},
|
|
891
|
+
metadata={
|
|
892
|
+
'source': 'claude_hook_handler',
|
|
893
|
+
'tool_name': 'Task',
|
|
894
|
+
'duration_ms': event.get('duration_ms', 0)
|
|
895
|
+
},
|
|
896
|
+
timestamp=datetime.now().isoformat(),
|
|
897
|
+
session_id=session_id
|
|
898
|
+
)
|
|
899
|
+
|
|
900
|
+
# Execute post-delegation hook
|
|
901
|
+
result = self.post_delegation_hook.execute(hook_context)
|
|
902
|
+
|
|
903
|
+
if result.success and result.metadata:
|
|
904
|
+
learnings_extracted = result.metadata.get('learnings_extracted', 0)
|
|
905
|
+
if learnings_extracted > 0 and DEBUG:
|
|
906
|
+
print(f"✅ Extracted {learnings_extracted} learnings for agent '{agent_type}'", file=sys.stderr)
|
|
907
|
+
|
|
908
|
+
except Exception as e:
|
|
909
|
+
if DEBUG:
|
|
910
|
+
print(f"❌ Memory post-delegation hook failed: {e}", file=sys.stderr)
|
|
911
|
+
# Don't fail the delegation result - memory is optional
|
|
912
|
+
|
|
738
913
|
def __del__(self):
|
|
739
914
|
"""Cleanup Socket.IO client on handler destruction."""
|
|
740
915
|
if self.sio_client and self.sio_connected:
|
|
@@ -745,9 +920,17 @@ class ClaudeHookHandler:
|
|
|
745
920
|
|
|
746
921
|
|
|
747
922
|
def main():
|
|
748
|
-
"""Entry point."""
|
|
749
|
-
|
|
750
|
-
|
|
923
|
+
"""Entry point with comprehensive error handling."""
|
|
924
|
+
try:
|
|
925
|
+
handler = ClaudeHookHandler()
|
|
926
|
+
handler.handle()
|
|
927
|
+
except Exception as e:
|
|
928
|
+
# Always output continue action to not block Claude
|
|
929
|
+
print(json.dumps({"action": "continue"}))
|
|
930
|
+
# Log error for debugging
|
|
931
|
+
if DEBUG:
|
|
932
|
+
print(f"Hook handler error: {e}", file=sys.stderr)
|
|
933
|
+
sys.exit(0) # Exit cleanly even on error
|
|
751
934
|
|
|
752
935
|
|
|
753
936
|
if __name__ == "__main__":
|
|
@@ -48,5 +48,12 @@ echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] PYTHONPATH: $PYTHONPATH" >> /tmp/hook
|
|
|
48
48
|
echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] Running: $PYTHON_CMD $SCRIPT_DIR/hook_handler.py" >> /tmp/hook-wrapper.log
|
|
49
49
|
echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] SOCKETIO_PORT: $CLAUDE_MPM_SOCKETIO_PORT" >> /tmp/hook-wrapper.log
|
|
50
50
|
|
|
51
|
-
# Run the Python hook handler
|
|
52
|
-
exec
|
|
51
|
+
# Run the Python hook handler with error handling
|
|
52
|
+
# Use exec to replace the shell process, but wrap in error handling
|
|
53
|
+
if ! "$PYTHON_CMD" "$SCRIPT_DIR/hook_handler.py" "$@" 2>/tmp/hook-error.log; then
|
|
54
|
+
# If the Python handler fails, always return continue to not block Claude
|
|
55
|
+
echo '{"action": "continue"}'
|
|
56
|
+
# Log the error for debugging
|
|
57
|
+
echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] Hook handler failed, see /tmp/hook-error.log" >> /tmp/hook-wrapper.log
|
|
58
|
+
exit 0
|
|
59
|
+
fi
|
|
@@ -15,13 +15,29 @@ agent outputs because:
|
|
|
15
15
|
import re
|
|
16
16
|
from typing import Dict, Any, List
|
|
17
17
|
from claude_mpm.hooks.base_hook import PreDelegationHook, PostDelegationHook, HookContext, HookResult
|
|
18
|
-
from claude_mpm.services.agent_memory_manager import AgentMemoryManager
|
|
19
|
-
from claude_mpm.services.socketio_server import get_socketio_server
|
|
20
18
|
from claude_mpm.core.config import Config
|
|
21
19
|
from claude_mpm.core.logger import get_logger
|
|
22
20
|
|
|
23
21
|
logger = get_logger(__name__)
|
|
24
22
|
|
|
23
|
+
# Try to import memory manager with fallback handling
|
|
24
|
+
try:
|
|
25
|
+
from claude_mpm.services.agent_memory_manager import AgentMemoryManager
|
|
26
|
+
MEMORY_MANAGER_AVAILABLE = True
|
|
27
|
+
except ImportError as e:
|
|
28
|
+
logger.warning(f"AgentMemoryManager not available: {e}")
|
|
29
|
+
MEMORY_MANAGER_AVAILABLE = False
|
|
30
|
+
AgentMemoryManager = None
|
|
31
|
+
|
|
32
|
+
# Try to import socketio server with fallback handling
|
|
33
|
+
try:
|
|
34
|
+
from claude_mpm.services.socketio_server import get_socketio_server
|
|
35
|
+
SOCKETIO_AVAILABLE = True
|
|
36
|
+
except ImportError as e:
|
|
37
|
+
logger.debug(f"SocketIO server not available: {e}")
|
|
38
|
+
SOCKETIO_AVAILABLE = False
|
|
39
|
+
get_socketio_server = None
|
|
40
|
+
|
|
25
41
|
|
|
26
42
|
class MemoryPreDelegationHook(PreDelegationHook):
|
|
27
43
|
"""Inject agent memory into delegation context.
|
|
@@ -42,7 +58,17 @@ class MemoryPreDelegationHook(PreDelegationHook):
|
|
|
42
58
|
"""
|
|
43
59
|
super().__init__(name="memory_pre_delegation", priority=20)
|
|
44
60
|
self.config = config or Config()
|
|
45
|
-
|
|
61
|
+
|
|
62
|
+
# Initialize memory manager only if available
|
|
63
|
+
if MEMORY_MANAGER_AVAILABLE and AgentMemoryManager:
|
|
64
|
+
try:
|
|
65
|
+
self.memory_manager = AgentMemoryManager(self.config)
|
|
66
|
+
except Exception as e:
|
|
67
|
+
logger.error(f"Failed to initialize AgentMemoryManager: {e}")
|
|
68
|
+
self.memory_manager = None
|
|
69
|
+
else:
|
|
70
|
+
logger.info("Memory manager not available - hook will be inactive")
|
|
71
|
+
self.memory_manager = None
|
|
46
72
|
|
|
47
73
|
def execute(self, context: HookContext) -> HookResult:
|
|
48
74
|
"""Add agent memory to delegation context.
|
|
@@ -50,6 +76,11 @@ class MemoryPreDelegationHook(PreDelegationHook):
|
|
|
50
76
|
WHY: By loading memory before delegation, agents can reference their
|
|
51
77
|
accumulated knowledge when performing tasks, leading to better outcomes.
|
|
52
78
|
"""
|
|
79
|
+
# If memory manager is not available, skip memory injection
|
|
80
|
+
if not self.memory_manager:
|
|
81
|
+
logger.debug("Memory manager not available - skipping memory injection")
|
|
82
|
+
return HookResult(success=True, data=context.data, modified=False)
|
|
83
|
+
|
|
53
84
|
try:
|
|
54
85
|
# Extract and normalize agent ID from context
|
|
55
86
|
agent_name = context.data.get('agent', '')
|
|
@@ -152,7 +183,17 @@ class MemoryPostDelegationHook(PostDelegationHook):
|
|
|
152
183
|
"""
|
|
153
184
|
super().__init__(name="memory_post_delegation", priority=80)
|
|
154
185
|
self.config = config or Config()
|
|
155
|
-
|
|
186
|
+
|
|
187
|
+
# Initialize memory manager only if available
|
|
188
|
+
if MEMORY_MANAGER_AVAILABLE and AgentMemoryManager:
|
|
189
|
+
try:
|
|
190
|
+
self.memory_manager = AgentMemoryManager(self.config)
|
|
191
|
+
except Exception as e:
|
|
192
|
+
logger.error(f"Failed to initialize AgentMemoryManager in PostDelegationHook: {e}")
|
|
193
|
+
self.memory_manager = None
|
|
194
|
+
else:
|
|
195
|
+
logger.info("Memory manager not available - post-delegation hook will be inactive")
|
|
196
|
+
self.memory_manager = None
|
|
156
197
|
|
|
157
198
|
# Map of supported types to memory sections
|
|
158
199
|
self.type_mapping = {
|
|
@@ -172,9 +213,14 @@ class MemoryPostDelegationHook(PostDelegationHook):
|
|
|
172
213
|
WHY: Capturing learnings immediately after task completion ensures we
|
|
173
214
|
don't lose valuable insights that agents discover during execution.
|
|
174
215
|
"""
|
|
216
|
+
# If memory manager is not available, skip learning extraction
|
|
217
|
+
if not self.memory_manager:
|
|
218
|
+
logger.debug("Memory manager not available - skipping learning extraction")
|
|
219
|
+
return HookResult(success=True, data=context.data, modified=False)
|
|
220
|
+
|
|
175
221
|
try:
|
|
176
222
|
# Check if auto-learning is enabled
|
|
177
|
-
if not self.config.get('memory.auto_learning',
|
|
223
|
+
if not self.config.get('memory.auto_learning', True): # Changed default to True
|
|
178
224
|
return HookResult(success=True, data=context.data, modified=False)
|
|
179
225
|
|
|
180
226
|
# Extract agent ID
|
claude_mpm/services/__init__.py
CHANGED
|
@@ -1,15 +1,33 @@
|
|
|
1
1
|
"""Services for Claude MPM."""
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
# Use lazy imports to prevent circular dependency issues
|
|
4
|
+
def __getattr__(name):
|
|
5
|
+
"""Lazy import to prevent circular dependencies."""
|
|
6
|
+
if name == "TicketManager":
|
|
7
|
+
from .ticket_manager import TicketManager
|
|
8
|
+
return TicketManager
|
|
9
|
+
elif name == "AgentDeploymentService":
|
|
10
|
+
from .agent_deployment import AgentDeploymentService
|
|
11
|
+
return AgentDeploymentService
|
|
12
|
+
elif name == "AgentMemoryManager":
|
|
13
|
+
from .agent_memory_manager import AgentMemoryManager
|
|
14
|
+
return AgentMemoryManager
|
|
15
|
+
elif name == "get_memory_manager":
|
|
16
|
+
from .agent_memory_manager import get_memory_manager
|
|
17
|
+
return get_memory_manager
|
|
18
|
+
elif name == "HookService":
|
|
19
|
+
from .hook_service import HookService
|
|
20
|
+
return HookService
|
|
21
|
+
elif name == "ProjectAnalyzer":
|
|
22
|
+
from .project_analyzer import ProjectAnalyzer
|
|
23
|
+
return ProjectAnalyzer
|
|
24
|
+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
|
7
25
|
|
|
8
|
-
# Import other services as needed
|
|
9
26
|
__all__ = [
|
|
10
27
|
"TicketManager",
|
|
11
28
|
"AgentDeploymentService",
|
|
12
29
|
"AgentMemoryManager",
|
|
13
30
|
"get_memory_manager",
|
|
14
31
|
"HookService",
|
|
32
|
+
"ProjectAnalyzer",
|
|
15
33
|
]
|