claude-mpm 4.8.0__py3-none-any.whl → 4.8.3__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/VERSION +1 -1
- claude_mpm/agents/templates/golang_engineer.json +257 -0
- claude_mpm/agents/templates/nextjs_engineer.json +122 -132
- claude_mpm/agents/templates/php-engineer.json +258 -175
- claude_mpm/agents/templates/product_owner.json +335 -0
- claude_mpm/agents/templates/python_engineer.json +150 -80
- claude_mpm/agents/templates/ruby-engineer.json +115 -191
- claude_mpm/agents/templates/rust_engineer.json +257 -0
- claude_mpm/agents/templates/typescript_engineer.json +102 -124
- claude_mpm/hooks/__init__.py +14 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +4 -2
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +23 -2
- claude_mpm/hooks/failure_learning/__init__.py +60 -0
- claude_mpm/hooks/failure_learning/failure_detection_hook.py +235 -0
- claude_mpm/hooks/failure_learning/fix_detection_hook.py +217 -0
- claude_mpm/hooks/failure_learning/learning_extraction_hook.py +286 -0
- claude_mpm/services/memory/failure_tracker.py +563 -0
- claude_mpm/services/memory_hook_service.py +76 -0
- {claude_mpm-4.8.0.dist-info → claude_mpm-4.8.3.dist-info}/METADATA +1 -1
- {claude_mpm-4.8.0.dist-info → claude_mpm-4.8.3.dist-info}/RECORD +24 -16
- {claude_mpm-4.8.0.dist-info → claude_mpm-4.8.3.dist-info}/WHEEL +0 -0
- {claude_mpm-4.8.0.dist-info → claude_mpm-4.8.3.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.8.0.dist-info → claude_mpm-4.8.3.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.8.0.dist-info → claude_mpm-4.8.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,286 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Learning Extraction Hook
|
4
|
+
=========================
|
5
|
+
|
6
|
+
Synthesizes learnings from failure-fix pairs and writes them to agent memory.
|
7
|
+
|
8
|
+
WHY: The final step in the failure-learning cycle is extracting actionable
|
9
|
+
learnings and persisting them to agent memory files. This hook completes the
|
10
|
+
cycle by taking failure-fix pairs and creating formatted learning entries.
|
11
|
+
|
12
|
+
DESIGN DECISION: This hook runs last (priority=89) after both failure and fix
|
13
|
+
detection. It uses template-based synthesis for MVP (no AI) and integrates
|
14
|
+
with AgentMemoryManager to write learnings to the appropriate memory files.
|
15
|
+
|
16
|
+
Integration points:
|
17
|
+
- Monitors for fix_detected metadata from FixDetectionHook
|
18
|
+
- Extracts learnings using FailureTracker
|
19
|
+
- Formats learnings as markdown
|
20
|
+
- Writes to agent memory files via AgentMemoryManager
|
21
|
+
"""
|
22
|
+
|
23
|
+
import logging
|
24
|
+
from typing import Any
|
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 LearningExtractionHook(BaseHook):
|
38
|
+
"""Hook that extracts and persists learnings from failure-fix pairs.
|
39
|
+
|
40
|
+
WHY: Automatically converts failure-fix pairs into persistent learnings
|
41
|
+
stored in agent memory files. This completes the failure-learning cycle
|
42
|
+
without requiring manual intervention.
|
43
|
+
|
44
|
+
DESIGN DECISION: Priority 89 ensures this runs last in the chain:
|
45
|
+
1. Failure detection (85) - detects failures
|
46
|
+
2. Fix detection (87) - matches fixes with failures
|
47
|
+
3. Learning extraction (89) - creates and persists learnings
|
48
|
+
|
49
|
+
MVP uses template-based learning synthesis. Future versions could use
|
50
|
+
AI to analyze git diffs, code changes, and generate richer learnings.
|
51
|
+
"""
|
52
|
+
|
53
|
+
def __init__(self):
|
54
|
+
"""Initialize the learning extraction hook."""
|
55
|
+
super().__init__(
|
56
|
+
name="learning_extraction",
|
57
|
+
priority=89, # Last in the chain, after fix detection
|
58
|
+
)
|
59
|
+
self.tracker = get_failure_tracker()
|
60
|
+
self._memory_manager = None
|
61
|
+
|
62
|
+
@property
|
63
|
+
def memory_manager(self):
|
64
|
+
"""Lazy-load memory manager to avoid circular imports.
|
65
|
+
|
66
|
+
WHY: AgentMemoryManager may import hooks, so we lazy-load to prevent
|
67
|
+
circular dependency issues.
|
68
|
+
|
69
|
+
Returns:
|
70
|
+
AgentMemoryManager instance
|
71
|
+
"""
|
72
|
+
if self._memory_manager is None:
|
73
|
+
try:
|
74
|
+
from claude_mpm.services.agents.memory.agent_memory_manager import (
|
75
|
+
get_memory_manager,
|
76
|
+
)
|
77
|
+
|
78
|
+
self._memory_manager = get_memory_manager()
|
79
|
+
except ImportError as e:
|
80
|
+
logger.error(f"Failed to import AgentMemoryManager: {e}")
|
81
|
+
raise
|
82
|
+
|
83
|
+
return self._memory_manager
|
84
|
+
|
85
|
+
def execute(self, context: HookContext) -> HookResult:
|
86
|
+
"""Execute learning extraction from failure-fix pairs.
|
87
|
+
|
88
|
+
WHY: When a fix is detected, we have everything needed to extract a
|
89
|
+
learning: the original failure, the fix that resolved it, and the
|
90
|
+
context. This method synthesizes a learning and writes it to memory.
|
91
|
+
|
92
|
+
Args:
|
93
|
+
context: Hook context containing fix detection metadata
|
94
|
+
|
95
|
+
Returns:
|
96
|
+
HookResult with extraction results
|
97
|
+
"""
|
98
|
+
try:
|
99
|
+
# Check if this is a fix detection event
|
100
|
+
metadata = context.metadata or {}
|
101
|
+
if not metadata.get("fix_detected"):
|
102
|
+
# Not a fix event, skip
|
103
|
+
return HookResult(success=True, modified=False)
|
104
|
+
|
105
|
+
# Extract failure and fix events from metadata
|
106
|
+
failure_event = metadata.get("failure_event")
|
107
|
+
fix_event = metadata.get("fix_event")
|
108
|
+
|
109
|
+
if not failure_event or not fix_event:
|
110
|
+
logger.warning("Fix detected but failure/fix events not in metadata")
|
111
|
+
return HookResult(success=True, modified=False)
|
112
|
+
|
113
|
+
# Extract learning from failure-fix pair
|
114
|
+
learning = self.tracker.extract_learning(
|
115
|
+
fix_event=fix_event,
|
116
|
+
failure_event=failure_event,
|
117
|
+
target_agent=self._determine_target_agent(context, failure_event),
|
118
|
+
)
|
119
|
+
|
120
|
+
# Format learning as markdown
|
121
|
+
learning_markdown = learning.to_markdown()
|
122
|
+
|
123
|
+
# Write to agent memory
|
124
|
+
success = self._write_to_memory(
|
125
|
+
agent_id=learning.target_agent, learning_text=learning_markdown
|
126
|
+
)
|
127
|
+
|
128
|
+
if success:
|
129
|
+
logger.info(
|
130
|
+
f"Learning extracted and saved for {learning.target_agent}: "
|
131
|
+
f"{learning.category}"
|
132
|
+
)
|
133
|
+
return HookResult(
|
134
|
+
success=True,
|
135
|
+
modified=False,
|
136
|
+
metadata={
|
137
|
+
"learning_extracted": True,
|
138
|
+
"target_agent": learning.target_agent,
|
139
|
+
"learning_category": learning.category,
|
140
|
+
},
|
141
|
+
)
|
142
|
+
logger.warning(
|
143
|
+
f"Failed to write learning to memory for {learning.target_agent}"
|
144
|
+
)
|
145
|
+
return HookResult(
|
146
|
+
success=True, # Don't fail the hook, just log warning
|
147
|
+
modified=False,
|
148
|
+
metadata={"learning_extracted": False},
|
149
|
+
)
|
150
|
+
|
151
|
+
except Exception as e:
|
152
|
+
logger.error(f"Error in learning extraction hook: {e}", exc_info=True)
|
153
|
+
return HookResult(success=False, error=str(e), modified=False)
|
154
|
+
|
155
|
+
def validate(self, context: HookContext) -> bool:
|
156
|
+
"""Validate if this hook should run for the given context.
|
157
|
+
|
158
|
+
Args:
|
159
|
+
context: Hook context to validate
|
160
|
+
|
161
|
+
Returns:
|
162
|
+
True if hook should execute
|
163
|
+
"""
|
164
|
+
if not super().validate(context):
|
165
|
+
return False
|
166
|
+
|
167
|
+
# Run for POST_DELEGATION events (after tool execution)
|
168
|
+
if context.hook_type != HookType.POST_DELEGATION:
|
169
|
+
return False
|
170
|
+
|
171
|
+
# Must have fix detection metadata
|
172
|
+
metadata = context.metadata or {}
|
173
|
+
return metadata.get("fix_detected", False)
|
174
|
+
|
175
|
+
def _determine_target_agent(self, context: HookContext, failure_event: Any) -> str:
|
176
|
+
"""Determine which agent should receive the learning.
|
177
|
+
|
178
|
+
WHY: Learnings should go to the agent most likely to benefit from them.
|
179
|
+
This method applies routing logic to determine the best target.
|
180
|
+
|
181
|
+
Args:
|
182
|
+
context: Hook context
|
183
|
+
failure_event: The failure event
|
184
|
+
|
185
|
+
Returns:
|
186
|
+
Agent identifier (PM, engineer, qa, etc.)
|
187
|
+
"""
|
188
|
+
# Try to get agent from context first
|
189
|
+
agent_type = (
|
190
|
+
context.data.get("agent_type")
|
191
|
+
or context.data.get("subagent_type")
|
192
|
+
or context.metadata.get("agent_type")
|
193
|
+
)
|
194
|
+
if agent_type:
|
195
|
+
return agent_type
|
196
|
+
|
197
|
+
# Check failure event context
|
198
|
+
if hasattr(failure_event, "context") and failure_event.context.get(
|
199
|
+
"agent_type"
|
200
|
+
):
|
201
|
+
return failure_event.context["agent_type"]
|
202
|
+
|
203
|
+
# Fall back to task-based routing
|
204
|
+
if hasattr(failure_event, "task_type"):
|
205
|
+
task_type = failure_event.task_type
|
206
|
+
if task_type in ("test", "lint"):
|
207
|
+
return "qa"
|
208
|
+
if task_type in ("build", "install", "script") or task_type == "git":
|
209
|
+
return "engineer"
|
210
|
+
|
211
|
+
# Default to PM
|
212
|
+
return "PM"
|
213
|
+
|
214
|
+
def _write_to_memory(self, agent_id: str, learning_text: str) -> bool:
|
215
|
+
"""Write learning to agent memory file.
|
216
|
+
|
217
|
+
WHY: Learnings must be persisted to memory files so agents can access
|
218
|
+
them in future sessions. This method uses AgentMemoryManager to handle
|
219
|
+
the actual file operations.
|
220
|
+
|
221
|
+
Args:
|
222
|
+
agent_id: Agent identifier
|
223
|
+
learning_text: Markdown-formatted learning
|
224
|
+
|
225
|
+
Returns:
|
226
|
+
True if write succeeded, False otherwise
|
227
|
+
"""
|
228
|
+
try:
|
229
|
+
# Parse learning sections from markdown
|
230
|
+
learning_items = self._parse_learning_markdown(learning_text)
|
231
|
+
|
232
|
+
if not learning_items:
|
233
|
+
logger.warning(f"No learning items parsed from: {learning_text}")
|
234
|
+
return False
|
235
|
+
|
236
|
+
# Add to agent memory
|
237
|
+
return self.memory_manager.update_agent_memory(
|
238
|
+
agent_id=agent_id, new_items=learning_items
|
239
|
+
)
|
240
|
+
|
241
|
+
except Exception as e:
|
242
|
+
logger.error(f"Failed to write learning to memory for {agent_id}: {e}")
|
243
|
+
return False
|
244
|
+
|
245
|
+
def _parse_learning_markdown(self, learning_markdown: str) -> list:
|
246
|
+
"""Parse learning markdown into list items for memory.
|
247
|
+
|
248
|
+
WHY: AgentMemoryManager expects a list of learning items. We need to
|
249
|
+
convert the markdown-formatted learning into individual list items.
|
250
|
+
|
251
|
+
Args:
|
252
|
+
learning_markdown: Markdown-formatted learning
|
253
|
+
|
254
|
+
Returns:
|
255
|
+
List of learning items
|
256
|
+
"""
|
257
|
+
items = []
|
258
|
+
|
259
|
+
# Split by lines and extract bullet points
|
260
|
+
lines = learning_markdown.split("\n")
|
261
|
+
|
262
|
+
for line in lines:
|
263
|
+
line = line.strip()
|
264
|
+
if line.startswith("- **"):
|
265
|
+
# This is a learning item (e.g., "- **Problem**: ...")
|
266
|
+
items.append(line)
|
267
|
+
elif line.startswith("## "):
|
268
|
+
# This is a category header, skip it
|
269
|
+
continue
|
270
|
+
|
271
|
+
# If no items found, return the whole thing as a single item
|
272
|
+
if not items:
|
273
|
+
items = [f"- {learning_markdown.strip()}"]
|
274
|
+
|
275
|
+
return items
|
276
|
+
|
277
|
+
|
278
|
+
def get_learning_extraction_hook() -> LearningExtractionHook:
|
279
|
+
"""Factory function to create learning extraction hook.
|
280
|
+
|
281
|
+
WHY: Provides consistent hook creation pattern used throughout the framework.
|
282
|
+
|
283
|
+
Returns:
|
284
|
+
Configured LearningExtractionHook instance
|
285
|
+
"""
|
286
|
+
return LearningExtractionHook()
|