claude-mpm 4.8.2__py3-none-any.whl → 4.8.6__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 (53) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +118 -0
  3. claude_mpm/agents/BASE_PM.md +75 -1
  4. claude_mpm/agents/templates/agent-manager.json +4 -1
  5. claude_mpm/agents/templates/agentic-coder-optimizer.json +4 -1
  6. claude_mpm/agents/templates/api_qa.json +4 -1
  7. claude_mpm/agents/templates/clerk-ops.json +4 -1
  8. claude_mpm/agents/templates/code_analyzer.json +4 -1
  9. claude_mpm/agents/templates/content-agent.json +4 -1
  10. claude_mpm/agents/templates/dart_engineer.json +4 -1
  11. claude_mpm/agents/templates/data_engineer.json +4 -1
  12. claude_mpm/agents/templates/documentation.json +4 -1
  13. claude_mpm/agents/templates/engineer.json +4 -1
  14. claude_mpm/agents/templates/gcp_ops_agent.json +4 -1
  15. claude_mpm/agents/templates/golang_engineer.json +4 -1
  16. claude_mpm/agents/templates/imagemagick.json +4 -1
  17. claude_mpm/agents/templates/local_ops_agent.json +12 -2
  18. claude_mpm/agents/templates/memory_manager.json +4 -1
  19. claude_mpm/agents/templates/nextjs_engineer.json +13 -5
  20. claude_mpm/agents/templates/ops.json +4 -1
  21. claude_mpm/agents/templates/php-engineer.json +5 -2
  22. claude_mpm/agents/templates/product_owner.json +338 -0
  23. claude_mpm/agents/templates/project_organizer.json +4 -1
  24. claude_mpm/agents/templates/prompt-engineer.json +8 -1
  25. claude_mpm/agents/templates/python_engineer.json +74 -5
  26. claude_mpm/agents/templates/qa.json +4 -1
  27. claude_mpm/agents/templates/react_engineer.json +5 -2
  28. claude_mpm/agents/templates/refactoring_engineer.json +4 -1
  29. claude_mpm/agents/templates/research.json +4 -1
  30. claude_mpm/agents/templates/ruby-engineer.json +5 -2
  31. claude_mpm/agents/templates/rust_engineer.json +4 -1
  32. claude_mpm/agents/templates/security.json +4 -1
  33. claude_mpm/agents/templates/ticketing.json +4 -1
  34. claude_mpm/agents/templates/typescript_engineer.json +5 -2
  35. claude_mpm/agents/templates/vercel_ops_agent.json +4 -1
  36. claude_mpm/agents/templates/version_control.json +4 -1
  37. claude_mpm/agents/templates/web_qa.json +4 -1
  38. claude_mpm/agents/templates/web_ui.json +4 -1
  39. claude_mpm/hooks/__init__.py +14 -0
  40. claude_mpm/hooks/claude_hooks/event_handlers.py +4 -2
  41. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +23 -2
  42. claude_mpm/hooks/failure_learning/__init__.py +60 -0
  43. claude_mpm/hooks/failure_learning/failure_detection_hook.py +235 -0
  44. claude_mpm/hooks/failure_learning/fix_detection_hook.py +217 -0
  45. claude_mpm/hooks/failure_learning/learning_extraction_hook.py +286 -0
  46. claude_mpm/services/memory/failure_tracker.py +563 -0
  47. claude_mpm/services/memory_hook_service.py +76 -0
  48. {claude_mpm-4.8.2.dist-info → claude_mpm-4.8.6.dist-info}/METADATA +1 -1
  49. {claude_mpm-4.8.2.dist-info → claude_mpm-4.8.6.dist-info}/RECORD +53 -47
  50. {claude_mpm-4.8.2.dist-info → claude_mpm-4.8.6.dist-info}/WHEEL +0 -0
  51. {claude_mpm-4.8.2.dist-info → claude_mpm-4.8.6.dist-info}/entry_points.txt +0 -0
  52. {claude_mpm-4.8.2.dist-info → claude_mpm-4.8.6.dist-info}/licenses/LICENSE +0 -0
  53. {claude_mpm-4.8.2.dist-info → claude_mpm-4.8.6.dist-info}/top_level.txt +0 -0
@@ -12,6 +12,7 @@ This eliminates disconnection issues and matches the process lifecycle.
12
12
  import asyncio
13
13
  import os
14
14
  import sys
15
+ from concurrent.futures import ThreadPoolExecutor
15
16
  from datetime import datetime, timezone
16
17
 
17
18
  # Debug mode is enabled by default for better visibility into hook processing
@@ -79,6 +80,13 @@ class ConnectionManagerService:
79
80
  # Track async emit tasks to prevent garbage collection
80
81
  self._emit_tasks: set = set()
81
82
 
83
+ # Thread pool for non-blocking HTTP requests
84
+ # WHY: Prevents HTTP POST from blocking hook processing (2s timeout → 0ms blocking)
85
+ # max_workers=2: Sufficient for low-frequency HTTP fallback events
86
+ self._http_executor = ThreadPoolExecutor(
87
+ max_workers=2, thread_name_prefix="http-emit"
88
+ )
89
+
82
90
  if DEBUG:
83
91
  print(
84
92
  f"✅ HTTP connection manager initialized - endpoint: {self.http_endpoint}",
@@ -181,7 +189,11 @@ class ConnectionManagerService:
181
189
  return False
182
190
 
183
191
  def _try_http_emit(self, namespace: str, event: str, data: dict):
184
- """Try to emit event using HTTP POST fallback."""
192
+ """Try to emit event using HTTP POST fallback (non-blocking).
193
+
194
+ WHY non-blocking: HTTP POST can take up to 2 seconds (timeout),
195
+ blocking hook processing. Thread pool makes it fire-and-forget.
196
+ """
185
197
  if not REQUESTS_AVAILABLE:
186
198
  if DEBUG:
187
199
  print(
@@ -190,6 +202,11 @@ class ConnectionManagerService:
190
202
  )
191
203
  return
192
204
 
205
+ # Submit to thread pool - don't wait for result (fire-and-forget)
206
+ self._http_executor.submit(self._http_emit_blocking, namespace, event, data)
207
+
208
+ def _http_emit_blocking(self, namespace: str, event: str, data: dict):
209
+ """HTTP emission in background thread (blocking operation isolated)."""
193
210
  try:
194
211
  # Create payload for HTTP API
195
212
  payload = {
@@ -230,4 +247,8 @@ class ConnectionManagerService:
230
247
 
231
248
  def cleanup(self):
232
249
  """Cleanup connections on service destruction."""
233
- # Nothing to cleanup for HTTP POST approach
250
+ # Shutdown HTTP executor gracefully
251
+ if hasattr(self, "_http_executor"):
252
+ self._http_executor.shutdown(wait=False)
253
+ if DEBUG:
254
+ print("✅ HTTP executor shutdown", file=sys.stderr)
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Failure-Learning Hook System
4
+ =============================
5
+
6
+ Automatic learning extraction from failure-fix cycles.
7
+
8
+ WHY: When tasks fail and agents fix them, valuable knowledge is created. This
9
+ hook system automatically captures failures, detects fixes, and extracts learnings
10
+ without requiring manual intervention.
11
+
12
+ Components:
13
+ - FailureDetectionHook (priority 85): Detects task failures from tool outputs
14
+ - FixDetectionHook (priority 87): Matches successful executions with failures
15
+ - LearningExtractionHook (priority 89): Synthesizes and persists learnings
16
+
17
+ Integration:
18
+ The hooks work together as a chain:
19
+ 1. Tool executes and fails → FailureDetectionHook records failure
20
+ 2. User or agent makes changes
21
+ 3. Tool executes and succeeds → FixDetectionHook detects fix
22
+ 4. Fix matched with failure → LearningExtractionHook creates learning
23
+ 5. Learning written to agent memory file
24
+
25
+ Usage:
26
+ from claude_mpm.hooks.failure_learning import (
27
+ get_failure_detection_hook,
28
+ get_fix_detection_hook,
29
+ get_learning_extraction_hook,
30
+ )
31
+
32
+ # Register hooks with hook service
33
+ hook_service.register_hook(get_failure_detection_hook())
34
+ hook_service.register_hook(get_fix_detection_hook())
35
+ hook_service.register_hook(get_learning_extraction_hook())
36
+ """
37
+
38
+ from .failure_detection_hook import (
39
+ FailureDetectionHook,
40
+ get_failure_detection_hook,
41
+ )
42
+ from .fix_detection_hook import (
43
+ FixDetectionHook,
44
+ get_fix_detection_hook,
45
+ )
46
+ from .learning_extraction_hook import (
47
+ LearningExtractionHook,
48
+ get_learning_extraction_hook,
49
+ )
50
+
51
+ __all__ = [
52
+ # Hooks
53
+ "FailureDetectionHook",
54
+ "FixDetectionHook",
55
+ "LearningExtractionHook",
56
+ # Factory functions
57
+ "get_failure_detection_hook",
58
+ "get_fix_detection_hook",
59
+ "get_learning_extraction_hook",
60
+ ]
@@ -0,0 +1,235 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Failure Detection Hook
4
+ ======================
5
+
6
+ Detects task failures from tool outputs and registers them with the FailureTracker.
7
+
8
+ WHY: Failures are the first step in the failure-learning cycle. By detecting
9
+ failures early, we can match them with fixes later and extract valuable learnings.
10
+
11
+ DESIGN DECISION: This hook runs after tool execution (priority=85) to inspect
12
+ tool outputs for failure patterns. It integrates with the PostDelegationHook
13
+ lifecycle to access tool execution results.
14
+
15
+ Integration points:
16
+ - Monitors post_tool events for Bash, NotebookEdit, and other execution tools
17
+ - Extracts error messages, exceptions, and test failures
18
+ - Registers failures with FailureTracker for fix matching
19
+ """
20
+
21
+ import logging
22
+ from typing import Any, Dict
23
+
24
+ from claude_mpm.hooks.base_hook import (
25
+ BaseHook,
26
+ HookContext,
27
+ HookResult,
28
+ HookType,
29
+ )
30
+ from claude_mpm.services.memory.failure_tracker import get_failure_tracker
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+
35
+ class FailureDetectionHook(BaseHook):
36
+ """Hook that detects and tracks task failures.
37
+
38
+ WHY: Automatically captures failures without requiring manual tracking.
39
+ Runs after tool execution to inspect outputs and detect error patterns.
40
+
41
+ DESIGN DECISION: Priority 85 ensures this runs after tool execution but
42
+ before fix detection and learning extraction. This ordering is critical
43
+ for the failure-learning cycle.
44
+ """
45
+
46
+ # Tools to monitor for failures
47
+ MONITORED_TOOLS = [
48
+ "Bash", # Command execution
49
+ "NotebookEdit", # Jupyter notebook execution
50
+ "Task", # Subagent delegation
51
+ ]
52
+
53
+ def __init__(self):
54
+ """Initialize the failure detection hook."""
55
+ super().__init__(
56
+ name="failure_detection",
57
+ priority=85, # After tool execution, before fix detection
58
+ )
59
+ self.tracker = get_failure_tracker()
60
+
61
+ def execute(self, context: HookContext) -> HookResult:
62
+ """Execute failure detection on tool output.
63
+
64
+ WHY: Inspects tool execution results to identify failures. When a failure
65
+ is detected, it's registered with the FailureTracker for later matching
66
+ with fixes.
67
+
68
+ Args:
69
+ context: Hook context containing tool execution data
70
+
71
+ Returns:
72
+ HookResult with detection results
73
+ """
74
+ try:
75
+ # Extract tool execution data
76
+ tool_name = context.data.get("tool_name")
77
+ tool_output = self._extract_tool_output(context.data)
78
+ exit_code = context.data.get("exit_code", 0)
79
+
80
+ # Only process monitored tools
81
+ if tool_name not in self.MONITORED_TOOLS:
82
+ return HookResult(success=True, modified=False)
83
+
84
+ # Skip if tool succeeded
85
+ if exit_code == 0 and not self._contains_failure_indicators(tool_output):
86
+ return HookResult(success=True, modified=False)
87
+
88
+ # Detect failure
89
+ failure_context = self._build_failure_context(context)
90
+ failure = self.tracker.detect_failure(
91
+ tool_name=tool_name, tool_output=tool_output, context=failure_context
92
+ )
93
+
94
+ if failure:
95
+ logger.info(
96
+ f"Failure detected: {failure.task_type} - "
97
+ f"{failure.error_message[:50]}..."
98
+ )
99
+ return HookResult(
100
+ success=True,
101
+ modified=False,
102
+ metadata={"failure_detected": True, "failure_id": failure.task_id},
103
+ )
104
+
105
+ return HookResult(success=True, modified=False)
106
+
107
+ except Exception as e:
108
+ logger.error(f"Error in failure detection hook: {e}", exc_info=True)
109
+ return HookResult(success=False, error=str(e), modified=False)
110
+
111
+ def validate(self, context: HookContext) -> bool:
112
+ """Validate if this hook should run for the given context.
113
+
114
+ Args:
115
+ context: Hook context to validate
116
+
117
+ Returns:
118
+ True if hook should execute
119
+ """
120
+ if not super().validate(context):
121
+ return False
122
+
123
+ # Run for POST_DELEGATION events (after tool execution)
124
+ if context.hook_type != HookType.POST_DELEGATION:
125
+ return False
126
+
127
+ # Must have tool execution data
128
+ return "tool_name" in context.data
129
+
130
+ def _extract_tool_output(self, data: Dict[str, Any]) -> str:
131
+ """Extract tool output from event data.
132
+
133
+ Args:
134
+ data: Event data dictionary
135
+
136
+ Returns:
137
+ Tool output string
138
+ """
139
+ # Try various output fields
140
+ output = (
141
+ data.get("output")
142
+ or data.get("result")
143
+ or data.get("error_output")
144
+ or data.get("stderr")
145
+ or ""
146
+ )
147
+
148
+ # Handle nested result structures
149
+ if isinstance(output, dict):
150
+ output = (
151
+ output.get("output")
152
+ or output.get("content")
153
+ or output.get("error")
154
+ or str(output)
155
+ )
156
+
157
+ return str(output) if output else ""
158
+
159
+ def _contains_failure_indicators(self, output: str) -> bool:
160
+ """Check if output contains failure indicators even if exit_code is 0.
161
+
162
+ WHY: Some tools return 0 exit code but still report failures in output
163
+ (e.g., test runners that catch exceptions).
164
+
165
+ Args:
166
+ output: Tool output
167
+
168
+ Returns:
169
+ True if failure indicators found
170
+ """
171
+ if not output:
172
+ return False
173
+
174
+ failure_keywords = [
175
+ "error:",
176
+ "exception:",
177
+ "failed",
178
+ "failure",
179
+ "traceback",
180
+ "✗",
181
+ "❌",
182
+ ]
183
+
184
+ output_lower = output.lower()
185
+ return any(keyword in output_lower for keyword in failure_keywords)
186
+
187
+ def _build_failure_context(self, context: HookContext) -> Dict[str, str]:
188
+ """Build context dictionary for failure event.
189
+
190
+ Args:
191
+ context: Hook context
192
+
193
+ Returns:
194
+ Context dictionary with agent, session, and other info
195
+ """
196
+ failure_context = {}
197
+
198
+ # Extract relevant context fields
199
+ if context.session_id:
200
+ failure_context["session_id"] = context.session_id
201
+
202
+ # Extract agent info from data or metadata
203
+ agent_type = (
204
+ context.data.get("agent_type")
205
+ or context.data.get("subagent_type")
206
+ or context.metadata.get("agent_type")
207
+ )
208
+ if agent_type:
209
+ failure_context["agent_type"] = agent_type
210
+
211
+ # Extract command/script info for Bash tool
212
+ if context.data.get("tool_name") == "Bash":
213
+ command = context.data.get("command") or context.data.get(
214
+ "tool_input", {}
215
+ ).get("command")
216
+ if command:
217
+ failure_context["command"] = command
218
+
219
+ # Extract working directory
220
+ working_dir = context.data.get("working_directory") or context.data.get("cwd")
221
+ if working_dir:
222
+ failure_context["working_dir"] = working_dir
223
+
224
+ return failure_context
225
+
226
+
227
+ def get_failure_detection_hook() -> FailureDetectionHook:
228
+ """Factory function to create failure detection hook.
229
+
230
+ WHY: Provides consistent hook creation pattern used throughout the framework.
231
+
232
+ Returns:
233
+ Configured FailureDetectionHook instance
234
+ """
235
+ return FailureDetectionHook()
@@ -0,0 +1,217 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Fix Detection Hook
4
+ ==================
5
+
6
+ Detects when previously failed tasks succeed, indicating a fix has been applied.
7
+
8
+ WHY: Detecting fixes is the second step in the failure-learning cycle. When a
9
+ task that previously failed now succeeds, we know a fix was applied. Matching
10
+ fixes with failures creates the foundation for learning extraction.
11
+
12
+ DESIGN DECISION: This hook runs after failure detection (priority=87) to check
13
+ if successful tool executions resolve previous failures. Uses the FailureTracker
14
+ to match fixes with failures.
15
+
16
+ Integration points:
17
+ - Monitors successful tool executions (exit_code=0)
18
+ - Matches with previously detected failures by task type
19
+ - Marks failures as fixed in FailureTracker
20
+ - Triggers learning extraction for failure-fix pairs
21
+ """
22
+
23
+ import logging
24
+ from typing import Any, Dict
25
+
26
+ from claude_mpm.hooks.base_hook import (
27
+ BaseHook,
28
+ HookContext,
29
+ HookResult,
30
+ HookType,
31
+ )
32
+ from claude_mpm.services.memory.failure_tracker import get_failure_tracker
33
+
34
+ logger = logging.getLogger(__name__)
35
+
36
+
37
+ class FixDetectionHook(BaseHook):
38
+ """Hook that detects when failures are fixed.
39
+
40
+ WHY: Automatically matches successful executions with previous failures.
41
+ This enables automatic learning extraction without manual intervention.
42
+
43
+ DESIGN DECISION: Priority 87 ensures this runs after failure detection
44
+ but before learning extraction. The sequencing is critical:
45
+ 1. Failure detection (85)
46
+ 2. Fix detection (87)
47
+ 3. Learning extraction (89)
48
+ """
49
+
50
+ # Tools to monitor for fixes
51
+ MONITORED_TOOLS = [
52
+ "Bash", # Command execution
53
+ "NotebookEdit", # Jupyter notebook execution
54
+ "Task", # Subagent delegation
55
+ ]
56
+
57
+ def __init__(self):
58
+ """Initialize the fix detection hook."""
59
+ super().__init__(
60
+ name="fix_detection",
61
+ priority=87, # After failure detection, before learning extraction
62
+ )
63
+ self.tracker = get_failure_tracker()
64
+
65
+ def execute(self, context: HookContext) -> HookResult:
66
+ """Execute fix detection on successful tool execution.
67
+
68
+ WHY: When a tool succeeds, check if it fixes a previous failure.
69
+ This creates failure-fix pairs that can be used for learning extraction.
70
+
71
+ Args:
72
+ context: Hook context containing tool execution data
73
+
74
+ Returns:
75
+ HookResult with detection results
76
+ """
77
+ try:
78
+ # Extract tool execution data
79
+ tool_name = context.data.get("tool_name")
80
+ exit_code = context.data.get("exit_code", 0)
81
+
82
+ # Only process monitored tools
83
+ if tool_name not in self.MONITORED_TOOLS:
84
+ return HookResult(success=True, modified=False)
85
+
86
+ # Only process successful executions
87
+ if exit_code != 0:
88
+ return HookResult(success=True, modified=False)
89
+
90
+ # Check if there are any unfixed failures to potentially match
91
+ unfixed_failures = self.tracker.get_unfixed_failures()
92
+ if not unfixed_failures:
93
+ # No failures to fix, skip
94
+ return HookResult(success=True, modified=False)
95
+
96
+ # Attempt to detect a fix
97
+ tool_output = self._extract_tool_output(context.data)
98
+ fix_context = self._build_fix_context(context)
99
+
100
+ fix_result = self.tracker.detect_fix(
101
+ tool_name=tool_name,
102
+ tool_output=tool_output,
103
+ exit_code=exit_code,
104
+ context=fix_context,
105
+ )
106
+
107
+ if fix_result:
108
+ fix_event, failure_event = fix_result
109
+ logger.info(
110
+ f"Fix detected: {failure_event.task_type} - "
111
+ f"resolved failure {failure_event.task_id}"
112
+ )
113
+
114
+ # Store fix info in metadata for learning extraction hook
115
+ return HookResult(
116
+ success=True,
117
+ modified=False,
118
+ metadata={
119
+ "fix_detected": True,
120
+ "failure_id": failure_event.task_id,
121
+ "fix_event": fix_event,
122
+ "failure_event": failure_event,
123
+ },
124
+ )
125
+
126
+ return HookResult(success=True, modified=False)
127
+
128
+ except Exception as e:
129
+ logger.error(f"Error in fix detection hook: {e}", exc_info=True)
130
+ return HookResult(success=False, error=str(e), modified=False)
131
+
132
+ def validate(self, context: HookContext) -> bool:
133
+ """Validate if this hook should run for the given context.
134
+
135
+ Args:
136
+ context: Hook context to validate
137
+
138
+ Returns:
139
+ True if hook should execute
140
+ """
141
+ if not super().validate(context):
142
+ return False
143
+
144
+ # Run for POST_DELEGATION events (after tool execution)
145
+ if context.hook_type != HookType.POST_DELEGATION:
146
+ return False
147
+
148
+ # Must have tool execution data
149
+ return "tool_name" in context.data
150
+
151
+ def _extract_tool_output(self, data: Dict[str, Any]) -> str:
152
+ """Extract tool output from event data.
153
+
154
+ Args:
155
+ data: Event data dictionary
156
+
157
+ Returns:
158
+ Tool output string
159
+ """
160
+ # Try various output fields
161
+ output = data.get("output") or data.get("result") or data.get("stdout") or ""
162
+
163
+ # Handle nested result structures
164
+ if isinstance(output, dict):
165
+ output = output.get("output") or output.get("content") or str(output)
166
+
167
+ return str(output) if output else ""
168
+
169
+ def _build_fix_context(self, context: HookContext) -> Dict[str, str]:
170
+ """Build context dictionary for fix event.
171
+
172
+ Args:
173
+ context: Hook context
174
+
175
+ Returns:
176
+ Context dictionary with agent, session, and other info
177
+ """
178
+ fix_context = {}
179
+
180
+ # Extract relevant context fields
181
+ if context.session_id:
182
+ fix_context["session_id"] = context.session_id
183
+
184
+ # Extract agent info
185
+ agent_type = (
186
+ context.data.get("agent_type")
187
+ or context.data.get("subagent_type")
188
+ or context.metadata.get("agent_type")
189
+ )
190
+ if agent_type:
191
+ fix_context["agent_type"] = agent_type
192
+
193
+ # Extract command/script info for Bash tool
194
+ if context.data.get("tool_name") == "Bash":
195
+ command = context.data.get("command") or context.data.get(
196
+ "tool_input", {}
197
+ ).get("command")
198
+ if command:
199
+ fix_context["command"] = command
200
+
201
+ # Extract working directory
202
+ working_dir = context.data.get("working_directory") or context.data.get("cwd")
203
+ if working_dir:
204
+ fix_context["working_dir"] = working_dir
205
+
206
+ return fix_context
207
+
208
+
209
+ def get_fix_detection_hook() -> FixDetectionHook:
210
+ """Factory function to create fix detection hook.
211
+
212
+ WHY: Provides consistent hook creation pattern used throughout the framework.
213
+
214
+ Returns:
215
+ Configured FixDetectionHook instance
216
+ """
217
+ return FixDetectionHook()