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.
Files changed (58) hide show
  1. claude_mpm/agents/templates/data_engineer.json +1 -1
  2. claude_mpm/agents/templates/documentation.json +1 -1
  3. claude_mpm/agents/templates/engineer.json +1 -1
  4. claude_mpm/agents/templates/ops.json +1 -1
  5. claude_mpm/agents/templates/pm.json +1 -1
  6. claude_mpm/agents/templates/qa.json +1 -1
  7. claude_mpm/agents/templates/research.json +1 -1
  8. claude_mpm/agents/templates/security.json +1 -1
  9. claude_mpm/agents/templates/test_integration.json +112 -0
  10. claude_mpm/agents/templates/version_control.json +1 -1
  11. claude_mpm/cli/commands/memory.py +749 -26
  12. claude_mpm/cli/commands/run.py +115 -14
  13. claude_mpm/cli/parser.py +89 -1
  14. claude_mpm/constants.py +6 -0
  15. claude_mpm/core/claude_runner.py +74 -11
  16. claude_mpm/core/config.py +1 -1
  17. claude_mpm/core/session_manager.py +46 -0
  18. claude_mpm/core/simple_runner.py +74 -11
  19. claude_mpm/hooks/builtin/mpm_command_hook.py +5 -5
  20. claude_mpm/hooks/claude_hooks/hook_handler.py +213 -30
  21. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +9 -2
  22. claude_mpm/hooks/memory_integration_hook.py +51 -5
  23. claude_mpm/services/__init__.py +23 -5
  24. claude_mpm/services/agent_memory_manager.py +800 -71
  25. claude_mpm/services/memory_builder.py +823 -0
  26. claude_mpm/services/memory_optimizer.py +619 -0
  27. claude_mpm/services/memory_router.py +445 -0
  28. claude_mpm/services/project_analyzer.py +771 -0
  29. claude_mpm/services/socketio_server.py +649 -45
  30. claude_mpm/services/version_control/git_operations.py +26 -0
  31. claude_mpm-3.4.0.dist-info/METADATA +183 -0
  32. {claude_mpm-3.3.0.dist-info → claude_mpm-3.4.0.dist-info}/RECORD +36 -52
  33. claude_mpm/agents/agent-template.yaml +0 -83
  34. claude_mpm/agents/templates/test-integration-agent.md +0 -34
  35. claude_mpm/agents/test_fix_deployment/.claude-pm/config/project.json +0 -6
  36. claude_mpm/cli/README.md +0 -109
  37. claude_mpm/cli_module/refactoring_guide.md +0 -253
  38. claude_mpm/core/agent_registry.py.bak +0 -312
  39. claude_mpm/core/base_service.py.bak +0 -406
  40. claude_mpm/core/websocket_handler.py +0 -233
  41. claude_mpm/hooks/README.md +0 -97
  42. claude_mpm/orchestration/SUBPROCESS_DESIGN.md +0 -66
  43. claude_mpm/schemas/README_SECURITY.md +0 -92
  44. claude_mpm/schemas/agent_schema.json +0 -395
  45. claude_mpm/schemas/agent_schema_documentation.md +0 -181
  46. claude_mpm/schemas/agent_schema_security_notes.md +0 -165
  47. claude_mpm/schemas/examples/standard_workflow.json +0 -505
  48. claude_mpm/schemas/ticket_workflow_documentation.md +0 -482
  49. claude_mpm/schemas/ticket_workflow_schema.json +0 -590
  50. claude_mpm/services/framework_claude_md_generator/README.md +0 -92
  51. claude_mpm/services/parent_directory_manager/README.md +0 -83
  52. claude_mpm/services/version_control/VERSION +0 -1
  53. claude_mpm/services/websocket_server.py +0 -376
  54. claude_mpm-3.3.0.dist-info/METADATA +0 -432
  55. {claude_mpm-3.3.0.dist-info → claude_mpm-3.4.0.dist-info}/WHEEL +0 -0
  56. {claude_mpm-3.3.0.dist-info → claude_mpm-3.4.0.dist-info}/entry_points.txt +0 -0
  57. {claude_mpm-3.3.0.dist-info → claude_mpm-3.4.0.dist-info}/licenses/LICENSE +0 -0
  58. {claude_mpm-3.3.0.dist-info → claude_mpm-3.4.0.dist-info}/top_level.txt +0 -0
@@ -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 WebSocket server reference
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
- # Start WebSocket server if enabled
164
+ # Connect to Socket.IO server if enabled
156
165
  if self.enable_websocket:
157
166
  try:
158
- # Lazy import to avoid circular dependencies
159
- from claude_mpm.services.websocket_server import WebSocketServer
160
- self.websocket_server = WebSocketServer(port=self.websocket_port)
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 start WebSocket server: {e}")
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
- # Start WebSocket server if enabled
342
+ # Connect to Socket.IO server if enabled
333
343
  if self.enable_websocket:
334
344
  try:
335
- # Lazy import to avoid circular dependencies
336
- from claude_mpm.services.websocket_server import WebSocketServer
337
- self.websocket_server = WebSocketServer(port=self.websocket_port)
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 start WebSocket server: {e}")
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: commands and routes them to the command router."""
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: commands and execute them directly."""
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: command
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:{command} with args: {args}")
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
- # Fallback imports
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 fallback server instance if available (but don't start it)
69
- if SERVER_AVAILABLE:
70
- try:
71
- self.websocket_server = get_server_instance()
72
- except:
73
- self.websocket_server = None
74
- else:
75
- self.websocket_server = None
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
- handler = ClaudeHookHandler()
750
- handler.handle()
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 (now optimized by default)
52
- exec "$PYTHON_CMD" "$SCRIPT_DIR/hook_handler.py" "$@"
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
- self.memory_manager = AgentMemoryManager(self.config)
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
- self.memory_manager = AgentMemoryManager(self.config)
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', False):
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
@@ -1,15 +1,33 @@
1
1
  """Services for Claude MPM."""
2
2
 
3
- from .ticket_manager import TicketManager
4
- from .agent_deployment import AgentDeploymentService
5
- from .agent_memory_manager import AgentMemoryManager, get_memory_manager
6
- from .hook_service import HookService
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
  ]