claude-mpm 4.7.4__py3-none-any.whl → 4.18.2__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/BASE_AGENT_TEMPLATE.md +118 -0
- claude_mpm/agents/BASE_ENGINEER.md +286 -0
- claude_mpm/agents/BASE_PM.md +106 -1
- claude_mpm/agents/OUTPUT_STYLE.md +329 -11
- claude_mpm/agents/PM_INSTRUCTIONS.md +397 -459
- claude_mpm/agents/agent_loader.py +17 -5
- claude_mpm/agents/frontmatter_validator.py +284 -253
- claude_mpm/agents/templates/README.md +465 -0
- claude_mpm/agents/templates/agent-manager.json +4 -1
- claude_mpm/agents/templates/agentic-coder-optimizer.json +13 -3
- claude_mpm/agents/templates/api_qa.json +11 -2
- claude_mpm/agents/templates/circuit_breakers.md +638 -0
- claude_mpm/agents/templates/clerk-ops.json +12 -2
- claude_mpm/agents/templates/code_analyzer.json +8 -2
- claude_mpm/agents/templates/content-agent.json +358 -0
- claude_mpm/agents/templates/dart_engineer.json +15 -2
- claude_mpm/agents/templates/data_engineer.json +15 -2
- claude_mpm/agents/templates/documentation.json +10 -2
- claude_mpm/agents/templates/engineer.json +21 -1
- claude_mpm/agents/templates/gcp_ops_agent.json +12 -2
- claude_mpm/agents/templates/git_file_tracking.md +584 -0
- claude_mpm/agents/templates/golang_engineer.json +270 -0
- claude_mpm/agents/templates/imagemagick.json +4 -1
- claude_mpm/agents/templates/java_engineer.json +346 -0
- claude_mpm/agents/templates/local_ops_agent.json +1227 -6
- claude_mpm/agents/templates/memory_manager.json +4 -1
- claude_mpm/agents/templates/nextjs_engineer.json +141 -133
- claude_mpm/agents/templates/ops.json +12 -2
- claude_mpm/agents/templates/php-engineer.json +270 -174
- claude_mpm/agents/templates/pm_examples.md +474 -0
- claude_mpm/agents/templates/pm_red_flags.md +240 -0
- claude_mpm/agents/templates/product_owner.json +338 -0
- claude_mpm/agents/templates/project_organizer.json +14 -4
- claude_mpm/agents/templates/prompt-engineer.json +13 -2
- claude_mpm/agents/templates/python_engineer.json +174 -81
- claude_mpm/agents/templates/qa.json +11 -2
- claude_mpm/agents/templates/react_engineer.json +16 -3
- claude_mpm/agents/templates/refactoring_engineer.json +12 -2
- claude_mpm/agents/templates/research.json +34 -21
- claude_mpm/agents/templates/response_format.md +583 -0
- claude_mpm/agents/templates/ruby-engineer.json +129 -192
- claude_mpm/agents/templates/rust_engineer.json +270 -0
- claude_mpm/agents/templates/security.json +10 -2
- claude_mpm/agents/templates/svelte-engineer.json +225 -0
- claude_mpm/agents/templates/ticketing.json +10 -2
- claude_mpm/agents/templates/typescript_engineer.json +116 -125
- claude_mpm/agents/templates/validation_templates.md +312 -0
- claude_mpm/agents/templates/vercel_ops_agent.json +12 -2
- claude_mpm/agents/templates/version_control.json +12 -2
- claude_mpm/agents/templates/web_qa.json +11 -2
- claude_mpm/agents/templates/web_ui.json +15 -2
- claude_mpm/cli/__init__.py +34 -614
- claude_mpm/cli/commands/agent_manager.py +25 -12
- claude_mpm/cli/commands/agent_state_manager.py +186 -0
- claude_mpm/cli/commands/agents.py +235 -148
- claude_mpm/cli/commands/agents_detect.py +380 -0
- claude_mpm/cli/commands/agents_recommend.py +309 -0
- claude_mpm/cli/commands/aggregate.py +7 -3
- claude_mpm/cli/commands/analyze.py +9 -4
- claude_mpm/cli/commands/analyze_code.py +7 -2
- claude_mpm/cli/commands/auto_configure.py +570 -0
- claude_mpm/cli/commands/config.py +47 -13
- claude_mpm/cli/commands/configure.py +419 -1571
- claude_mpm/cli/commands/configure_agent_display.py +261 -0
- claude_mpm/cli/commands/configure_behavior_manager.py +204 -0
- claude_mpm/cli/commands/configure_hook_manager.py +225 -0
- claude_mpm/cli/commands/configure_models.py +18 -0
- claude_mpm/cli/commands/configure_navigation.py +167 -0
- claude_mpm/cli/commands/configure_paths.py +104 -0
- claude_mpm/cli/commands/configure_persistence.py +254 -0
- claude_mpm/cli/commands/configure_startup_manager.py +646 -0
- claude_mpm/cli/commands/configure_template_editor.py +497 -0
- claude_mpm/cli/commands/configure_validators.py +73 -0
- claude_mpm/cli/commands/local_deploy.py +537 -0
- claude_mpm/cli/commands/memory.py +54 -20
- claude_mpm/cli/commands/mpm_init.py +585 -196
- claude_mpm/cli/commands/mpm_init_handler.py +37 -3
- claude_mpm/cli/commands/search.py +170 -4
- claude_mpm/cli/commands/upgrade.py +152 -0
- claude_mpm/cli/executor.py +202 -0
- claude_mpm/cli/helpers.py +105 -0
- claude_mpm/cli/interactive/__init__.py +3 -0
- claude_mpm/cli/interactive/skills_wizard.py +491 -0
- claude_mpm/cli/parsers/__init__.py +7 -1
- claude_mpm/cli/parsers/agents_parser.py +9 -0
- claude_mpm/cli/parsers/auto_configure_parser.py +245 -0
- claude_mpm/cli/parsers/base_parser.py +110 -3
- claude_mpm/cli/parsers/local_deploy_parser.py +227 -0
- claude_mpm/cli/parsers/mpm_init_parser.py +65 -5
- claude_mpm/cli/shared/output_formatters.py +28 -19
- claude_mpm/cli/startup.py +481 -0
- claude_mpm/cli/utils.py +52 -1
- claude_mpm/commands/mpm-agents-detect.md +168 -0
- claude_mpm/commands/mpm-agents-recommend.md +214 -0
- claude_mpm/commands/mpm-agents.md +75 -1
- claude_mpm/commands/mpm-auto-configure.md +217 -0
- claude_mpm/commands/mpm-help.md +163 -0
- claude_mpm/commands/mpm-init.md +148 -3
- claude_mpm/commands/mpm-version.md +113 -0
- claude_mpm/commands/mpm.md +1 -0
- claude_mpm/config/agent_config.py +2 -2
- claude_mpm/config/model_config.py +428 -0
- claude_mpm/constants.py +1 -0
- claude_mpm/core/base_service.py +13 -12
- claude_mpm/core/enums.py +452 -0
- claude_mpm/core/factories.py +1 -1
- claude_mpm/core/instruction_reinforcement_hook.py +2 -1
- claude_mpm/core/interactive_session.py +9 -3
- claude_mpm/core/log_manager.py +2 -0
- claude_mpm/core/logging_config.py +6 -2
- claude_mpm/core/oneshot_session.py +8 -4
- claude_mpm/core/optimized_agent_loader.py +3 -3
- claude_mpm/core/output_style_manager.py +12 -192
- claude_mpm/core/service_registry.py +5 -1
- claude_mpm/core/types.py +2 -9
- claude_mpm/core/typing_utils.py +7 -6
- claude_mpm/dashboard/static/js/dashboard.js +0 -14
- claude_mpm/dashboard/templates/index.html +3 -41
- claude_mpm/hooks/__init__.py +20 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +4 -2
- claude_mpm/hooks/claude_hooks/response_tracking.py +35 -1
- 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/hooks/instruction_reinforcement.py +7 -2
- claude_mpm/hooks/kuzu_enrichment_hook.py +263 -0
- claude_mpm/hooks/kuzu_memory_hook.py +37 -12
- claude_mpm/hooks/kuzu_response_hook.py +183 -0
- claude_mpm/models/resume_log.py +340 -0
- claude_mpm/services/agents/__init__.py +18 -5
- claude_mpm/services/agents/auto_config_manager.py +796 -0
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +1 -1
- claude_mpm/services/agents/deployment/agent_record_service.py +1 -1
- claude_mpm/services/agents/deployment/agent_validator.py +17 -1
- claude_mpm/services/agents/deployment/async_agent_deployment.py +1 -1
- claude_mpm/services/agents/deployment/interface_adapter.py +3 -2
- claude_mpm/services/agents/deployment/local_template_deployment.py +1 -1
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +7 -6
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +7 -16
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +4 -3
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +5 -3
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +6 -5
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +9 -6
- claude_mpm/services/agents/deployment/validation/__init__.py +3 -1
- claude_mpm/services/agents/deployment/validation/validation_result.py +1 -9
- claude_mpm/services/agents/local_template_manager.py +1 -1
- claude_mpm/services/agents/memory/agent_memory_manager.py +5 -2
- claude_mpm/services/agents/observers.py +547 -0
- claude_mpm/services/agents/recommender.py +568 -0
- claude_mpm/services/agents/registry/modification_tracker.py +5 -2
- claude_mpm/services/command_handler_service.py +11 -5
- claude_mpm/services/core/__init__.py +33 -1
- claude_mpm/services/core/interfaces/__init__.py +90 -3
- claude_mpm/services/core/interfaces/agent.py +184 -0
- claude_mpm/services/core/interfaces/health.py +172 -0
- claude_mpm/services/core/interfaces/model.py +281 -0
- claude_mpm/services/core/interfaces/process.py +372 -0
- claude_mpm/services/core/interfaces/project.py +121 -0
- claude_mpm/services/core/interfaces/restart.py +307 -0
- claude_mpm/services/core/interfaces/stability.py +260 -0
- claude_mpm/services/core/memory_manager.py +11 -24
- claude_mpm/services/core/models/__init__.py +79 -0
- claude_mpm/services/core/models/agent_config.py +381 -0
- claude_mpm/services/core/models/health.py +162 -0
- claude_mpm/services/core/models/process.py +235 -0
- claude_mpm/services/core/models/restart.py +302 -0
- claude_mpm/services/core/models/stability.py +264 -0
- claude_mpm/services/core/models/toolchain.py +306 -0
- claude_mpm/services/core/path_resolver.py +23 -7
- claude_mpm/services/diagnostics/__init__.py +2 -2
- claude_mpm/services/diagnostics/checks/agent_check.py +25 -24
- claude_mpm/services/diagnostics/checks/claude_code_check.py +24 -23
- claude_mpm/services/diagnostics/checks/common_issues_check.py +25 -24
- claude_mpm/services/diagnostics/checks/configuration_check.py +24 -23
- claude_mpm/services/diagnostics/checks/filesystem_check.py +18 -17
- claude_mpm/services/diagnostics/checks/installation_check.py +30 -29
- claude_mpm/services/diagnostics/checks/instructions_check.py +20 -19
- claude_mpm/services/diagnostics/checks/mcp_check.py +50 -36
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +38 -33
- claude_mpm/services/diagnostics/checks/monitor_check.py +23 -22
- claude_mpm/services/diagnostics/checks/startup_log_check.py +9 -8
- claude_mpm/services/diagnostics/diagnostic_runner.py +6 -5
- claude_mpm/services/diagnostics/doctor_reporter.py +28 -25
- claude_mpm/services/diagnostics/models.py +19 -24
- claude_mpm/services/infrastructure/monitoring/__init__.py +1 -1
- claude_mpm/services/infrastructure/monitoring/aggregator.py +12 -12
- claude_mpm/services/infrastructure/monitoring/base.py +5 -13
- claude_mpm/services/infrastructure/monitoring/network.py +7 -6
- claude_mpm/services/infrastructure/monitoring/process.py +13 -12
- claude_mpm/services/infrastructure/monitoring/resources.py +7 -6
- claude_mpm/services/infrastructure/monitoring/service.py +16 -15
- claude_mpm/services/infrastructure/resume_log_generator.py +439 -0
- claude_mpm/services/local_ops/__init__.py +163 -0
- claude_mpm/services/local_ops/crash_detector.py +257 -0
- claude_mpm/services/local_ops/health_checks/__init__.py +28 -0
- claude_mpm/services/local_ops/health_checks/http_check.py +224 -0
- claude_mpm/services/local_ops/health_checks/process_check.py +236 -0
- claude_mpm/services/local_ops/health_checks/resource_check.py +255 -0
- claude_mpm/services/local_ops/health_manager.py +430 -0
- claude_mpm/services/local_ops/log_monitor.py +396 -0
- claude_mpm/services/local_ops/memory_leak_detector.py +294 -0
- claude_mpm/services/local_ops/process_manager.py +595 -0
- claude_mpm/services/local_ops/resource_monitor.py +331 -0
- claude_mpm/services/local_ops/restart_manager.py +401 -0
- claude_mpm/services/local_ops/restart_policy.py +387 -0
- claude_mpm/services/local_ops/state_manager.py +372 -0
- claude_mpm/services/local_ops/unified_manager.py +600 -0
- claude_mpm/services/mcp_config_manager.py +9 -4
- claude_mpm/services/mcp_gateway/core/__init__.py +1 -2
- claude_mpm/services/mcp_gateway/core/base.py +18 -31
- claude_mpm/services/mcp_gateway/main.py +30 -0
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +206 -32
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +30 -28
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +25 -5
- claude_mpm/services/mcp_service_verifier.py +1 -1
- claude_mpm/services/memory/failure_tracker.py +563 -0
- claude_mpm/services/memory_hook_service.py +165 -4
- claude_mpm/services/model/__init__.py +147 -0
- claude_mpm/services/model/base_provider.py +365 -0
- claude_mpm/services/model/claude_provider.py +412 -0
- claude_mpm/services/model/model_router.py +453 -0
- claude_mpm/services/model/ollama_provider.py +415 -0
- claude_mpm/services/monitor/daemon_manager.py +3 -2
- claude_mpm/services/monitor/handlers/dashboard.py +2 -1
- claude_mpm/services/monitor/handlers/hooks.py +2 -1
- claude_mpm/services/monitor/management/lifecycle.py +3 -2
- claude_mpm/services/monitor/server.py +2 -1
- claude_mpm/services/project/__init__.py +23 -0
- claude_mpm/services/project/detection_strategies.py +719 -0
- claude_mpm/services/project/toolchain_analyzer.py +581 -0
- claude_mpm/services/self_upgrade_service.py +342 -0
- claude_mpm/services/session_management_service.py +3 -2
- claude_mpm/services/session_manager.py +205 -1
- claude_mpm/services/shared/async_service_base.py +16 -27
- claude_mpm/services/shared/lifecycle_service_base.py +1 -14
- claude_mpm/services/socketio/handlers/__init__.py +5 -2
- claude_mpm/services/socketio/handlers/hook.py +13 -2
- claude_mpm/services/socketio/handlers/registry.py +4 -2
- claude_mpm/services/socketio/server/main.py +10 -8
- claude_mpm/services/subprocess_launcher_service.py +14 -5
- claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +8 -7
- claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +6 -5
- claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +8 -7
- claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +7 -6
- claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +5 -4
- claude_mpm/services/unified/config_strategies/validation_strategy.py +13 -9
- claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +10 -3
- claude_mpm/services/unified/deployment_strategies/local.py +6 -5
- claude_mpm/services/unified/deployment_strategies/utils.py +6 -5
- claude_mpm/services/unified/deployment_strategies/vercel.py +7 -6
- claude_mpm/services/unified/interfaces.py +3 -1
- claude_mpm/services/unified/unified_analyzer.py +14 -10
- claude_mpm/services/unified/unified_config.py +2 -1
- claude_mpm/services/unified/unified_deployment.py +9 -4
- claude_mpm/services/version_service.py +104 -1
- claude_mpm/skills/__init__.py +21 -0
- claude_mpm/skills/bundled/__init__.py +6 -0
- claude_mpm/skills/bundled/api-documentation.md +393 -0
- claude_mpm/skills/bundled/async-testing.md +571 -0
- claude_mpm/skills/bundled/code-review.md +143 -0
- claude_mpm/skills/bundled/database-migration.md +199 -0
- claude_mpm/skills/bundled/docker-containerization.md +194 -0
- claude_mpm/skills/bundled/express-local-dev.md +1429 -0
- claude_mpm/skills/bundled/fastapi-local-dev.md +1199 -0
- claude_mpm/skills/bundled/git-workflow.md +414 -0
- claude_mpm/skills/bundled/imagemagick.md +204 -0
- claude_mpm/skills/bundled/json-data-handling.md +223 -0
- claude_mpm/skills/bundled/nextjs-local-dev.md +807 -0
- claude_mpm/skills/bundled/pdf.md +141 -0
- claude_mpm/skills/bundled/performance-profiling.md +567 -0
- claude_mpm/skills/bundled/refactoring-patterns.md +180 -0
- claude_mpm/skills/bundled/security-scanning.md +327 -0
- claude_mpm/skills/bundled/systematic-debugging.md +473 -0
- claude_mpm/skills/bundled/test-driven-development.md +378 -0
- claude_mpm/skills/bundled/vite-local-dev.md +1061 -0
- claude_mpm/skills/bundled/web-performance-optimization.md +2305 -0
- claude_mpm/skills/bundled/xlsx.md +157 -0
- claude_mpm/skills/registry.py +286 -0
- claude_mpm/skills/skill_manager.py +310 -0
- claude_mpm/storage/state_storage.py +15 -15
- claude_mpm/tools/code_tree_analyzer.py +177 -141
- claude_mpm/tools/code_tree_events.py +4 -2
- claude_mpm/utils/agent_dependency_loader.py +40 -20
- claude_mpm/utils/display_helper.py +260 -0
- claude_mpm/utils/git_analyzer.py +407 -0
- claude_mpm/utils/robust_installer.py +73 -19
- {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/METADATA +129 -12
- {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/RECORD +295 -193
- claude_mpm/dashboard/static/css/code-tree.css +0 -1639
- claude_mpm/dashboard/static/index-hub-backup.html +0 -713
- claude_mpm/dashboard/static/js/components/code-tree/tree-breadcrumb.js +0 -353
- claude_mpm/dashboard/static/js/components/code-tree/tree-constants.js +0 -235
- claude_mpm/dashboard/static/js/components/code-tree/tree-search.js +0 -409
- claude_mpm/dashboard/static/js/components/code-tree/tree-utils.js +0 -435
- claude_mpm/dashboard/static/js/components/code-tree.js +0 -5869
- claude_mpm/dashboard/static/js/components/code-viewer.js +0 -1386
- claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +0 -425
- claude_mpm/hooks/claude_hooks/hook_handler_original.py +0 -1041
- claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +0 -347
- claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +0 -575
- claude_mpm/services/project/analyzer_refactored.py +0 -450
- {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/WHEEL +0 -0
- {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.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()
|
|
@@ -10,6 +10,7 @@ from dataclasses import dataclass
|
|
|
10
10
|
from enum import Enum
|
|
11
11
|
from typing import Dict, List, Optional, Tuple
|
|
12
12
|
|
|
13
|
+
from claude_mpm.core.enums import OperationResult
|
|
13
14
|
from claude_mpm.core.logging_utils import get_logger
|
|
14
15
|
|
|
15
16
|
logger = get_logger(__name__)
|
|
@@ -267,7 +268,7 @@ class InstructionReinforcementHook:
|
|
|
267
268
|
if not self.violations:
|
|
268
269
|
return {
|
|
269
270
|
"total_violations": 0,
|
|
270
|
-
"status":
|
|
271
|
+
"status": OperationResult.SUCCESS,
|
|
271
272
|
"message": "No PM delegation violations detected",
|
|
272
273
|
}
|
|
273
274
|
|
|
@@ -276,7 +277,11 @@ class InstructionReinforcementHook:
|
|
|
276
277
|
vtype = v.violation_type.value
|
|
277
278
|
violation_types[vtype] = violation_types.get(vtype, 0) + 1
|
|
278
279
|
|
|
279
|
-
status =
|
|
280
|
+
status = (
|
|
281
|
+
OperationResult.ERROR
|
|
282
|
+
if self.violation_count < 3
|
|
283
|
+
else OperationResult.FAILED
|
|
284
|
+
)
|
|
280
285
|
|
|
281
286
|
return {
|
|
282
287
|
"total_violations": self.violation_count,
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Kuzu-Memory Pre-Delegation Enrichment Hook
|
|
3
|
+
==========================================
|
|
4
|
+
|
|
5
|
+
Enriches agent delegation context with relevant memories from kuzu-memory
|
|
6
|
+
before the agent receives the task. This is the READ side of bidirectional
|
|
7
|
+
enrichment.
|
|
8
|
+
|
|
9
|
+
WHY: Agents need access to relevant historical knowledge when performing tasks.
|
|
10
|
+
This hook retrieves memories from kuzu-memory and injects them into the
|
|
11
|
+
delegation context.
|
|
12
|
+
|
|
13
|
+
DESIGN DECISIONS:
|
|
14
|
+
- Priority 10 to run early, before other context modifications
|
|
15
|
+
- Reuses KuzuMemoryHook's retrieval methods for consistency
|
|
16
|
+
- Injects memories as a dedicated section in agent context
|
|
17
|
+
- Falls back gracefully if kuzu-memory is not available
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import logging
|
|
21
|
+
from typing import Any, Dict, Optional
|
|
22
|
+
|
|
23
|
+
from claude_mpm.hooks.base_hook import HookContext, HookResult, PreDelegationHook
|
|
24
|
+
from claude_mpm.hooks.kuzu_memory_hook import get_kuzu_memory_hook
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class KuzuEnrichmentHook(PreDelegationHook):
|
|
30
|
+
"""
|
|
31
|
+
Hook that enriches agent delegation context with kuzu-memory.
|
|
32
|
+
|
|
33
|
+
This hook:
|
|
34
|
+
1. Extracts the task/prompt from delegation context
|
|
35
|
+
2. Retrieves relevant memories from kuzu-memory
|
|
36
|
+
3. Injects memories into agent context
|
|
37
|
+
4. Formats memories for optimal agent understanding
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(self):
|
|
41
|
+
"""Initialize the kuzu-memory enrichment hook."""
|
|
42
|
+
super().__init__(name="kuzu_memory_enrichment", priority=10)
|
|
43
|
+
|
|
44
|
+
# Reuse the kuzu-memory hook instance for retrieval
|
|
45
|
+
self.kuzu_hook = get_kuzu_memory_hook()
|
|
46
|
+
self.enabled = self.kuzu_hook.enabled
|
|
47
|
+
|
|
48
|
+
if not self.enabled:
|
|
49
|
+
logger.info(
|
|
50
|
+
"Kuzu-memory enrichment hook disabled (kuzu-memory not available)"
|
|
51
|
+
)
|
|
52
|
+
else:
|
|
53
|
+
logger.info("Kuzu-memory enrichment hook enabled")
|
|
54
|
+
|
|
55
|
+
def validate(self, context: HookContext) -> bool:
|
|
56
|
+
"""
|
|
57
|
+
Validate if hook should process this context.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
context: Hook context to validate
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
True if hook should execute
|
|
64
|
+
"""
|
|
65
|
+
if not self.enabled:
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
# Check base validation (enabled, correct hook type, has agent)
|
|
69
|
+
if not super().validate(context):
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
# Must have agent and context data
|
|
73
|
+
if not context.data.get("agent"):
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
return True
|
|
77
|
+
|
|
78
|
+
def execute(self, context: HookContext) -> HookResult:
|
|
79
|
+
"""
|
|
80
|
+
Enrich delegation context with relevant memories.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
context: Hook context containing delegation data
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
HookResult with enriched context
|
|
87
|
+
"""
|
|
88
|
+
if not self.enabled:
|
|
89
|
+
return HookResult(success=True, data=context.data, modified=False)
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
# Extract query for memory retrieval
|
|
93
|
+
query = self._extract_query_from_context(context.data)
|
|
94
|
+
|
|
95
|
+
if not query:
|
|
96
|
+
logger.debug("No query extracted from context for memory retrieval")
|
|
97
|
+
return HookResult(success=True, data=context.data, modified=False)
|
|
98
|
+
|
|
99
|
+
# Retrieve relevant memories
|
|
100
|
+
memories = self.kuzu_hook._retrieve_memories(query)
|
|
101
|
+
|
|
102
|
+
if not memories:
|
|
103
|
+
logger.debug("No relevant memories found")
|
|
104
|
+
return HookResult(success=True, data=context.data, modified=False)
|
|
105
|
+
|
|
106
|
+
# Enrich context with memories
|
|
107
|
+
enriched_data = self._enrich_delegation_context(
|
|
108
|
+
context.data, memories, context.data.get("agent", "Agent")
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
logger.info(
|
|
112
|
+
f"Enriched delegation context with {len(memories)} memories for {context.data.get('agent')}"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
return HookResult(
|
|
116
|
+
success=True,
|
|
117
|
+
data=enriched_data,
|
|
118
|
+
modified=True,
|
|
119
|
+
metadata={
|
|
120
|
+
"memories_added": len(memories),
|
|
121
|
+
"memory_source": "kuzu",
|
|
122
|
+
"agent": context.data.get("agent"),
|
|
123
|
+
},
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
except Exception as e:
|
|
127
|
+
logger.error(f"Error in kuzu enrichment hook: {e}")
|
|
128
|
+
# Don't fail the delegation if memory enrichment fails
|
|
129
|
+
return HookResult(
|
|
130
|
+
success=True,
|
|
131
|
+
data=context.data,
|
|
132
|
+
modified=False,
|
|
133
|
+
error=f"Memory enrichment failed: {e}",
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def _extract_query_from_context(self, data: Dict[str, Any]) -> Optional[str]:
|
|
137
|
+
"""
|
|
138
|
+
Extract query text for memory retrieval.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
data: Delegation context data
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Query string or None
|
|
145
|
+
"""
|
|
146
|
+
# Try various context fields
|
|
147
|
+
delegation_context = data.get("context", {})
|
|
148
|
+
|
|
149
|
+
# Handle string context
|
|
150
|
+
if isinstance(delegation_context, str):
|
|
151
|
+
return delegation_context
|
|
152
|
+
|
|
153
|
+
# Handle dict context
|
|
154
|
+
if isinstance(delegation_context, dict):
|
|
155
|
+
# Try common fields
|
|
156
|
+
for field in ["prompt", "task", "query", "user_request", "description"]:
|
|
157
|
+
if field in delegation_context:
|
|
158
|
+
value = delegation_context[field]
|
|
159
|
+
if isinstance(value, str):
|
|
160
|
+
return value
|
|
161
|
+
|
|
162
|
+
# If no specific field, join all string values
|
|
163
|
+
text_parts = [
|
|
164
|
+
str(v) for v in delegation_context.values() if isinstance(v, str)
|
|
165
|
+
]
|
|
166
|
+
if text_parts:
|
|
167
|
+
return " ".join(text_parts)
|
|
168
|
+
|
|
169
|
+
# Fallback: try to get task or instruction directly
|
|
170
|
+
if "task" in data and isinstance(data["task"], str):
|
|
171
|
+
return data["task"]
|
|
172
|
+
|
|
173
|
+
if "instruction" in data and isinstance(data["instruction"], str):
|
|
174
|
+
return data["instruction"]
|
|
175
|
+
|
|
176
|
+
return None
|
|
177
|
+
|
|
178
|
+
def _enrich_delegation_context(
|
|
179
|
+
self, original_data: Dict[str, Any], memories: list, agent_name: str
|
|
180
|
+
) -> Dict[str, Any]:
|
|
181
|
+
"""
|
|
182
|
+
Enrich delegation context with memories.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
original_data: Original delegation data
|
|
186
|
+
memories: Retrieved memories
|
|
187
|
+
agent_name: Name of the agent
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
Enriched delegation data
|
|
191
|
+
"""
|
|
192
|
+
# Format memories
|
|
193
|
+
memory_section = self._format_memory_section(memories, agent_name)
|
|
194
|
+
|
|
195
|
+
# Create enriched data
|
|
196
|
+
enriched_data = original_data.copy()
|
|
197
|
+
|
|
198
|
+
# Get existing context
|
|
199
|
+
delegation_context = enriched_data.get("context", {})
|
|
200
|
+
if isinstance(delegation_context, str):
|
|
201
|
+
delegation_context = {"prompt": delegation_context}
|
|
202
|
+
|
|
203
|
+
# Add memory section
|
|
204
|
+
if isinstance(delegation_context, dict):
|
|
205
|
+
# Prepend memory section to context
|
|
206
|
+
delegation_context["kuzu_memories"] = memory_section
|
|
207
|
+
|
|
208
|
+
# If there's a main prompt/task, prepend memory note
|
|
209
|
+
for field in ["prompt", "task", "instruction"]:
|
|
210
|
+
if field in delegation_context and isinstance(
|
|
211
|
+
delegation_context[field], str
|
|
212
|
+
):
|
|
213
|
+
delegation_context[field] = (
|
|
214
|
+
f"{memory_section}\n\n{delegation_context[field]}"
|
|
215
|
+
)
|
|
216
|
+
break
|
|
217
|
+
else:
|
|
218
|
+
# If context is not dict, create new dict with memory
|
|
219
|
+
delegation_context = {
|
|
220
|
+
"kuzu_memories": memory_section,
|
|
221
|
+
"original_context": delegation_context,
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
enriched_data["context"] = delegation_context
|
|
225
|
+
enriched_data["_kuzu_enriched"] = True
|
|
226
|
+
|
|
227
|
+
return enriched_data
|
|
228
|
+
|
|
229
|
+
def _format_memory_section(self, memories: list, agent_name: str) -> str:
|
|
230
|
+
"""
|
|
231
|
+
Format memories into a readable section.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
memories: List of memory dictionaries
|
|
235
|
+
agent_name: Name of the agent
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Formatted memory section
|
|
239
|
+
"""
|
|
240
|
+
memory_text = self.kuzu_hook._format_memories(memories)
|
|
241
|
+
|
|
242
|
+
return f"""
|
|
243
|
+
=== RELEVANT KNOWLEDGE FROM KUZU MEMORY ===
|
|
244
|
+
{agent_name}, you have access to these relevant memories from the knowledge graph:
|
|
245
|
+
|
|
246
|
+
{memory_text}
|
|
247
|
+
|
|
248
|
+
INSTRUCTIONS: Review these memories before proceeding. Apply learned patterns and avoid known mistakes.
|
|
249
|
+
Use this knowledge to provide more informed and contextual responses.
|
|
250
|
+
===========================================
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
# Create a singleton instance
|
|
255
|
+
_kuzu_enrichment_hook = None
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def get_kuzu_enrichment_hook() -> KuzuEnrichmentHook:
|
|
259
|
+
"""Get the singleton kuzu-memory enrichment hook instance."""
|
|
260
|
+
global _kuzu_enrichment_hook
|
|
261
|
+
if _kuzu_enrichment_hook is None:
|
|
262
|
+
_kuzu_enrichment_hook = KuzuEnrichmentHook()
|
|
263
|
+
return _kuzu_enrichment_hook
|
|
@@ -13,8 +13,9 @@ for structured memory storage with semantic search capabilities.
|
|
|
13
13
|
DESIGN DECISIONS:
|
|
14
14
|
- Priority 10 for early execution to enrich prompts before other hooks
|
|
15
15
|
- Uses subprocess to call kuzu-memory directly for maximum compatibility
|
|
16
|
-
- Graceful degradation if kuzu-memory is not
|
|
16
|
+
- Graceful degradation if kuzu-memory is not in PATH (though it's now required)
|
|
17
17
|
- Automatic extraction and storage of important information
|
|
18
|
+
- kuzu-memory>=1.1.5 is now a REQUIRED dependency (moved from optional in v4.8.6)
|
|
18
19
|
"""
|
|
19
20
|
|
|
20
21
|
import json
|
|
@@ -50,7 +51,10 @@ class KuzuMemoryHook(SubmitHook):
|
|
|
50
51
|
self.enabled = self.kuzu_memory_cmd is not None
|
|
51
52
|
|
|
52
53
|
if not self.enabled:
|
|
53
|
-
logger.
|
|
54
|
+
logger.warning(
|
|
55
|
+
"Kuzu-memory not found in PATH. As of v4.8.6, it's a required dependency. "
|
|
56
|
+
"Install with: pip install kuzu-memory>=1.1.5 or pipx install kuzu-memory"
|
|
57
|
+
)
|
|
54
58
|
else:
|
|
55
59
|
logger.info(f"Kuzu-memory integration enabled: {self.kuzu_memory_cmd}")
|
|
56
60
|
|
|
@@ -72,6 +76,10 @@ class KuzuMemoryHook(SubmitHook):
|
|
|
72
76
|
1. Check pipx installation
|
|
73
77
|
2. Check system PATH
|
|
74
78
|
3. Return None if not found
|
|
79
|
+
|
|
80
|
+
NOTE: As of v4.8.6, kuzu-memory is a required dependency and should be
|
|
81
|
+
installed via pip. This method checks both pipx and system PATH for
|
|
82
|
+
backward compatibility.
|
|
75
83
|
"""
|
|
76
84
|
# Check pipx installation
|
|
77
85
|
pipx_path = (
|
|
@@ -157,9 +165,13 @@ class KuzuMemoryHook(SubmitHook):
|
|
|
157
165
|
List of relevant memory dictionaries
|
|
158
166
|
"""
|
|
159
167
|
try:
|
|
160
|
-
#
|
|
168
|
+
# Type narrowing: ensure kuzu_memory_cmd is not None before using
|
|
169
|
+
if self.kuzu_memory_cmd is None:
|
|
170
|
+
return []
|
|
171
|
+
|
|
172
|
+
# Use kuzu-memory recall command (v1.2.7+ syntax)
|
|
161
173
|
result = subprocess.run(
|
|
162
|
-
[self.kuzu_memory_cmd, "recall", query, "--format", "json"],
|
|
174
|
+
[self.kuzu_memory_cmd, "memory", "recall", query, "--format", "json"],
|
|
163
175
|
capture_output=True,
|
|
164
176
|
text=True,
|
|
165
177
|
timeout=5,
|
|
@@ -168,10 +180,21 @@ class KuzuMemoryHook(SubmitHook):
|
|
|
168
180
|
)
|
|
169
181
|
|
|
170
182
|
if result.returncode == 0 and result.stdout:
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
183
|
+
try:
|
|
184
|
+
# Parse JSON with strict=False to handle control characters
|
|
185
|
+
data = json.loads(result.stdout, strict=False)
|
|
186
|
+
# v1.2.7 returns dict with 'memories' key, not array
|
|
187
|
+
if isinstance(data, dict):
|
|
188
|
+
memories = data.get("memories", [])
|
|
189
|
+
else:
|
|
190
|
+
memories = data if isinstance(data, list) else []
|
|
191
|
+
return memories
|
|
192
|
+
except json.JSONDecodeError as e:
|
|
193
|
+
logger.warning(f"Failed to parse kuzu-memory JSON output: {e}")
|
|
194
|
+
logger.debug(f"Raw output: {result.stdout[:200]}")
|
|
195
|
+
return [] # Graceful fallback
|
|
196
|
+
|
|
197
|
+
except (subprocess.TimeoutExpired, Exception) as e:
|
|
175
198
|
logger.debug(f"Memory retrieval failed: {e}")
|
|
176
199
|
|
|
177
200
|
return []
|
|
@@ -255,12 +278,12 @@ Note: Use the memories above to provide more informed and contextual responses.
|
|
|
255
278
|
Returns:
|
|
256
279
|
True if storage was successful
|
|
257
280
|
"""
|
|
258
|
-
if not self.enabled:
|
|
281
|
+
if not self.enabled or self.kuzu_memory_cmd is None:
|
|
259
282
|
return False
|
|
260
283
|
|
|
261
284
|
try:
|
|
262
|
-
# Use kuzu-memory
|
|
263
|
-
cmd = [self.kuzu_memory_cmd, "
|
|
285
|
+
# Use kuzu-memory store command (v1.2.7+ syntax)
|
|
286
|
+
cmd = [self.kuzu_memory_cmd, "memory", "store", content]
|
|
264
287
|
|
|
265
288
|
# Execute store command in project directory
|
|
266
289
|
result = subprocess.run(
|
|
@@ -273,8 +296,10 @@ Note: Use the memories above to provide more informed and contextual responses.
|
|
|
273
296
|
)
|
|
274
297
|
|
|
275
298
|
if result.returncode == 0:
|
|
276
|
-
logger.debug(f"Stored memory: {content[:50]}...")
|
|
299
|
+
logger.debug(f"Stored memory in kuzu: {content[:50]}...")
|
|
277
300
|
return True
|
|
301
|
+
logger.warning(f"Failed to store memory in kuzu: {result.stderr}")
|
|
302
|
+
return False
|
|
278
303
|
|
|
279
304
|
except Exception as e:
|
|
280
305
|
logger.error(f"Failed to store memory: {e}")
|