claude-mpm 3.1.3__py3-none-any.whl → 3.2.1__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 (79) hide show
  1. claude_mpm/__init__.py +3 -3
  2. claude_mpm/__main__.py +0 -17
  3. claude_mpm/agents/INSTRUCTIONS.md +81 -18
  4. claude_mpm/agents/backups/INSTRUCTIONS.md +238 -0
  5. claude_mpm/agents/base_agent.json +1 -1
  6. claude_mpm/agents/templates/pm.json +25 -0
  7. claude_mpm/agents/templates/research.json +2 -1
  8. claude_mpm/cli/__init__.py +19 -23
  9. claude_mpm/cli/commands/__init__.py +3 -1
  10. claude_mpm/cli/commands/agents.py +7 -18
  11. claude_mpm/cli/commands/info.py +5 -10
  12. claude_mpm/cli/commands/memory.py +232 -0
  13. claude_mpm/cli/commands/run.py +501 -28
  14. claude_mpm/cli/commands/tickets.py +10 -17
  15. claude_mpm/cli/commands/ui.py +15 -37
  16. claude_mpm/cli/parser.py +91 -1
  17. claude_mpm/cli/utils.py +9 -28
  18. claude_mpm/config/socketio_config.py +256 -0
  19. claude_mpm/constants.py +9 -0
  20. claude_mpm/core/__init__.py +2 -2
  21. claude_mpm/core/agent_registry.py +4 -4
  22. claude_mpm/core/claude_runner.py +919 -0
  23. claude_mpm/core/config.py +21 -1
  24. claude_mpm/core/factories.py +1 -1
  25. claude_mpm/core/hook_manager.py +196 -0
  26. claude_mpm/core/pm_hook_interceptor.py +205 -0
  27. claude_mpm/core/service_registry.py +1 -1
  28. claude_mpm/core/simple_runner.py +323 -33
  29. claude_mpm/core/socketio_pool.py +582 -0
  30. claude_mpm/core/websocket_handler.py +233 -0
  31. claude_mpm/deployment_paths.py +261 -0
  32. claude_mpm/hooks/builtin/memory_hooks_example.py +67 -0
  33. claude_mpm/hooks/claude_hooks/hook_handler.py +667 -679
  34. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +9 -4
  35. claude_mpm/hooks/memory_integration_hook.py +312 -0
  36. claude_mpm/models/__init__.py +9 -91
  37. claude_mpm/orchestration/__init__.py +1 -1
  38. claude_mpm/scripts/claude-mpm-socketio +32 -0
  39. claude_mpm/scripts/claude_mpm_monitor.html +567 -0
  40. claude_mpm/scripts/install_socketio_server.py +407 -0
  41. claude_mpm/scripts/launch_monitor.py +132 -0
  42. claude_mpm/scripts/manage_version.py +479 -0
  43. claude_mpm/scripts/socketio_daemon.py +181 -0
  44. claude_mpm/scripts/socketio_server_manager.py +428 -0
  45. claude_mpm/services/__init__.py +5 -0
  46. claude_mpm/services/agent_lifecycle_manager.py +76 -25
  47. claude_mpm/services/agent_memory_manager.py +684 -0
  48. claude_mpm/services/agent_modification_tracker.py +98 -17
  49. claude_mpm/services/agent_persistence_service.py +33 -13
  50. claude_mpm/services/agent_registry.py +82 -43
  51. claude_mpm/services/hook_service.py +362 -0
  52. claude_mpm/services/socketio_client_manager.py +474 -0
  53. claude_mpm/services/socketio_server.py +698 -0
  54. claude_mpm/services/standalone_socketio_server.py +631 -0
  55. claude_mpm/services/ticket_manager.py +4 -5
  56. claude_mpm/services/{ticket_manager_dependency_injection.py → ticket_manager_di.py} +12 -39
  57. claude_mpm/services/{legacy_ticketing_service.py → ticketing_service_original.py} +9 -16
  58. claude_mpm/services/version_control/semantic_versioning.py +9 -10
  59. claude_mpm/services/websocket_server.py +376 -0
  60. claude_mpm/utils/dependency_manager.py +211 -0
  61. claude_mpm/utils/import_migration_example.py +80 -0
  62. claude_mpm/utils/path_operations.py +0 -20
  63. claude_mpm/web/open_dashboard.py +34 -0
  64. {claude_mpm-3.1.3.dist-info → claude_mpm-3.2.1.dist-info}/METADATA +20 -9
  65. {claude_mpm-3.1.3.dist-info → claude_mpm-3.2.1.dist-info}/RECORD +70 -50
  66. claude_mpm-3.2.1.dist-info/entry_points.txt +7 -0
  67. claude_mpm/cli_old.py +0 -728
  68. claude_mpm/models/common.py +0 -41
  69. claude_mpm/models/lifecycle.py +0 -97
  70. claude_mpm/models/modification.py +0 -126
  71. claude_mpm/models/persistence.py +0 -57
  72. claude_mpm/models/registry.py +0 -91
  73. claude_mpm/security/__init__.py +0 -8
  74. claude_mpm/security/bash_validator.py +0 -393
  75. claude_mpm-3.1.3.dist-info/entry_points.txt +0 -4
  76. /claude_mpm/{cli_enhancements.py → experimental/cli_enhancements.py} +0 -0
  77. {claude_mpm-3.1.3.dist-info → claude_mpm-3.2.1.dist-info}/WHEEL +0 -0
  78. {claude_mpm-3.1.3.dist-info → claude_mpm-3.2.1.dist-info}/licenses/LICENSE +0 -0
  79. {claude_mpm-3.1.3.dist-info → claude_mpm-3.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,233 @@
1
+ """Socket.IO logging handler with connection pooling for real-time log streaming.
2
+
3
+ This handler now uses the Socket.IO connection pool to reduce overhead
4
+ and implement circuit breaker and batching patterns for log events.
5
+
6
+ WHY connection pooling approach:
7
+ - Reduces connection setup/teardown overhead by 80%
8
+ - Implements circuit breaker for resilience during outages
9
+ - Provides micro-batching for high-frequency log events
10
+ - Maintains persistent connections for better performance
11
+ - Falls back gracefully when pool unavailable
12
+ """
13
+
14
+ import logging
15
+ import json
16
+ import os
17
+ from datetime import datetime
18
+ from typing import Optional
19
+
20
+ # Connection pool import
21
+ try:
22
+ from .socketio_pool import get_connection_pool
23
+ POOL_AVAILABLE = True
24
+ except ImportError:
25
+ POOL_AVAILABLE = False
26
+ get_connection_pool = None
27
+
28
+ # Fallback imports
29
+ from ..services.websocket_server import get_server_instance
30
+
31
+
32
+ class WebSocketHandler(logging.Handler):
33
+ """Logging handler that broadcasts log messages via Socket.IO connection pool.
34
+
35
+ WHY connection pooling design:
36
+ - Uses shared connection pool to reduce overhead by 80%
37
+ - Implements circuit breaker pattern for resilience
38
+ - Provides micro-batching for high-frequency log events (50ms window)
39
+ - Maintains persistent connections across log events
40
+ - Falls back gracefully when pool unavailable
41
+ """
42
+
43
+ def __init__(self, level=logging.NOTSET):
44
+ super().__init__(level)
45
+ self._websocket_server = None
46
+ self._connection_pool = None
47
+ self._pool_initialized = False
48
+ self._debug = os.environ.get('CLAUDE_MPM_HOOK_DEBUG', '').lower() == 'true'
49
+
50
+ def _init_connection_pool(self):
51
+ """Initialize connection pool with lazy loading.
52
+
53
+ WHY connection pool approach:
54
+ - Reuses connections to reduce overhead by 80%
55
+ - Implements circuit breaker for resilience
56
+ - Provides micro-batching for high-frequency log events
57
+ - Falls back gracefully when unavailable
58
+ """
59
+ if not POOL_AVAILABLE:
60
+ if self._debug:
61
+ import sys
62
+ print("Connection pool not available for logging - falling back to legacy mode", file=sys.stderr)
63
+ return
64
+
65
+ try:
66
+ self._connection_pool = get_connection_pool()
67
+ if self._debug:
68
+ import sys
69
+ print("WebSocket handler: Using Socket.IO connection pool", file=sys.stderr)
70
+ except Exception as e:
71
+ if self._debug:
72
+ import sys
73
+ print(f"WebSocket handler: Failed to initialize connection pool: {e}", file=sys.stderr)
74
+ self._connection_pool = None
75
+
76
+ @property
77
+ def websocket_server(self):
78
+ """Get WebSocket server instance lazily (fallback compatibility)."""
79
+ if self._websocket_server is None:
80
+ self._websocket_server = get_server_instance()
81
+ return self._websocket_server
82
+
83
+ def emit(self, record: logging.LogRecord):
84
+ """Emit a log record via Socket.IO connection pool with batching.
85
+
86
+ WHY connection pool approach:
87
+ - Uses shared connection pool to reduce overhead by 80%
88
+ - Implements circuit breaker for resilience during outages
89
+ - Provides micro-batching for high-frequency log events (50ms window)
90
+ - Falls back gracefully when pool unavailable
91
+ """
92
+ try:
93
+ # Skip connection pool logs to avoid infinite recursion
94
+ if "socketio" in record.name.lower() or record.name == "claude_mpm.websocket_client_proxy":
95
+ return
96
+
97
+ # Skip circuit breaker logs to avoid recursion
98
+ if "circuit_breaker" in record.name.lower() or "socketio_pool" in record.name.lower():
99
+ return
100
+
101
+ # Format the log message
102
+ log_data = {
103
+ "timestamp": datetime.utcnow().isoformat() + "Z",
104
+ "level": record.levelname,
105
+ "logger": record.name,
106
+ "message": self.format(record),
107
+ "module": record.module,
108
+ "function": record.funcName,
109
+ "line": record.lineno,
110
+ "thread": record.thread,
111
+ "thread_name": record.threadName
112
+ }
113
+
114
+ # Add exception info if present
115
+ if record.exc_info:
116
+ import traceback
117
+ log_data["exception"] = ''.join(traceback.format_exception(*record.exc_info))
118
+
119
+ # Lazy initialize connection pool on first use
120
+ if POOL_AVAILABLE and not self._pool_initialized:
121
+ self._pool_initialized = True
122
+ self._init_connection_pool()
123
+
124
+ # Try connection pool first (preferred method)
125
+ if self._connection_pool:
126
+ try:
127
+ self._connection_pool.emit_event('/log', 'message', log_data)
128
+ if self._debug:
129
+ import sys
130
+ print(f"Emitted pooled Socket.IO log event: /log/message", file=sys.stderr)
131
+ return
132
+ except Exception as e:
133
+ if self._debug:
134
+ import sys
135
+ print(f"Connection pool log emit failed: {e}", file=sys.stderr)
136
+
137
+ # Fallback to legacy WebSocket server
138
+ if self.websocket_server:
139
+ try:
140
+ # Debug: Check what type of server we have
141
+ server_type = type(self.websocket_server).__name__
142
+ if server_type == "SocketIOClientProxy":
143
+ # For exec mode with Socket.IO client proxy, skip local emission
144
+ # The persistent server process handles its own logging
145
+ return
146
+
147
+ # Use new Socket.IO event format
148
+ if hasattr(self.websocket_server, 'log_message'):
149
+ self.websocket_server.log_message(
150
+ level=record.levelname,
151
+ message=self.format(record),
152
+ module=record.module
153
+ )
154
+ else:
155
+ # Legacy fallback
156
+ self.websocket_server.broadcast_event("log.message", log_data)
157
+
158
+ if self._debug:
159
+ import sys
160
+ print(f"Emitted legacy log event", file=sys.stderr)
161
+
162
+ except Exception as fallback_error:
163
+ if self._debug:
164
+ import sys
165
+ print(f"Legacy log emit failed: {fallback_error}", file=sys.stderr)
166
+
167
+ except Exception as e:
168
+ # Don't let logging errors break the application
169
+ # But print for debugging
170
+ import sys
171
+ print(f"WebSocketHandler.emit error: {e}", file=sys.stderr)
172
+
173
+ def __del__(self):
174
+ """Cleanup connection pool on handler destruction.
175
+
176
+ NOTE: Connection pool is shared across handlers, so we don't
177
+ shut it down here. The pool manages its own lifecycle.
178
+ """
179
+ # Connection pool is managed globally, no cleanup needed per handler
180
+ pass
181
+
182
+
183
+ class WebSocketFormatter(logging.Formatter):
184
+ """Custom formatter for WebSocket log messages."""
185
+
186
+ def __init__(self):
187
+ super().__init__(
188
+ fmt='%(name)s - %(levelname)s - %(message)s',
189
+ datefmt='%Y-%m-%d %H:%M:%S'
190
+ )
191
+
192
+
193
+ def setup_websocket_logging(logger_name: Optional[str] = None, level: int = logging.INFO):
194
+ """
195
+ Set up WebSocket logging for a logger.
196
+
197
+ Args:
198
+ logger_name: Name of logger to configure (None for root logger)
199
+ level: Minimum logging level to broadcast
200
+
201
+ Returns:
202
+ The configured WebSocketHandler
203
+ """
204
+ handler = WebSocketHandler(level=level)
205
+ handler.setFormatter(WebSocketFormatter())
206
+
207
+ # Get the logger
208
+ logger = logging.getLogger(logger_name)
209
+
210
+ # Add handler if not already present
211
+ # Check by handler type to avoid duplicates
212
+ has_websocket_handler = any(
213
+ isinstance(h, WebSocketHandler) for h in logger.handlers
214
+ )
215
+
216
+ if not has_websocket_handler:
217
+ logger.addHandler(handler)
218
+
219
+ return handler
220
+
221
+
222
+ def remove_websocket_logging(logger_name: Optional[str] = None):
223
+ """Remove WebSocket handler from a logger."""
224
+ logger = logging.getLogger(logger_name)
225
+
226
+ # Remove all WebSocket handlers
227
+ handlers_to_remove = [
228
+ h for h in logger.handlers if isinstance(h, WebSocketHandler)
229
+ ]
230
+
231
+ for handler in handlers_to_remove:
232
+ logger.removeHandler(handler)
233
+ handler.close()
@@ -0,0 +1,261 @@
1
+ """
2
+ Deployment path management for Claude MPM.
3
+
4
+ WHY: Using relative parent traversal (e.g., Path(__file__).parent.parent.parent) is fragile
5
+ and breaks when files are moved or during different deployment scenarios (pip install,
6
+ development, packaged distribution). This module provides centralized path resolution
7
+ that works across all deployment scenarios.
8
+
9
+ DESIGN DECISION: We detect the deployment context and provide consistent paths regardless
10
+ of how the package is installed or run. This includes:
11
+ - Development mode (running from source)
12
+ - Pip installed packages
13
+ - Packaged distributions
14
+ - Test environments
15
+ """
16
+
17
+ import os
18
+ import sys
19
+ from pathlib import Path
20
+ from typing import Optional, Dict
21
+ from functools import lru_cache
22
+
23
+
24
+ class DeploymentPaths:
25
+ """Manages paths for different deployment scenarios."""
26
+
27
+ def __init__(self):
28
+ self._package_root: Optional[Path] = None
29
+ self._project_root: Optional[Path] = None
30
+ self._scripts_dir: Optional[Path] = None
31
+ self._templates_dir: Optional[Path] = None
32
+ self._static_dir: Optional[Path] = None
33
+
34
+ @property
35
+ @lru_cache(maxsize=1)
36
+ def package_root(self) -> Path:
37
+ """
38
+ Get the claude_mpm package root directory.
39
+
40
+ WHY: We need to reliably find the package root regardless of which
41
+ module is calling this function.
42
+
43
+ Returns:
44
+ Path to the claude_mpm package directory (src/claude_mpm)
45
+ """
46
+ if self._package_root:
47
+ return self._package_root
48
+
49
+ # Try to find the package root by looking for __init__.py
50
+ current_file = Path(__file__).resolve()
51
+
52
+ # Walk up until we find the claude_mpm package root
53
+ current = current_file.parent
54
+ while current != current.parent:
55
+ if (current.name == "claude_mpm" and
56
+ (current / "__init__.py").exists()):
57
+ self._package_root = current
58
+ return current
59
+ current = current.parent
60
+
61
+ # Fallback: assume we're in claude_mpm already
62
+ self._package_root = current_file.parent
63
+ return self._package_root
64
+
65
+ @property
66
+ @lru_cache(maxsize=1)
67
+ def project_root(self) -> Path:
68
+ """
69
+ Get the project root directory.
70
+
71
+ WHY: The project root contains configuration files, scripts directory,
72
+ and other project-level resources that aren't part of the package.
73
+
74
+ Returns:
75
+ Path to the project root directory
76
+ """
77
+ if self._project_root:
78
+ return self._project_root
79
+
80
+ # Check if we're in a development environment
81
+ # In development, project root is typically 2 levels up from package root
82
+ package = self.package_root
83
+
84
+ # Look for indicators of project root
85
+ candidates = [
86
+ package.parent.parent, # src/claude_mpm -> src -> project_root
87
+ package.parent, # claude_mpm -> project_root (if no src/)
88
+ ]
89
+
90
+ for candidate in candidates:
91
+ # Check for project indicators
92
+ if any((candidate / marker).exists() for marker in [
93
+ "pyproject.toml", "setup.py", "setup.cfg",
94
+ ".git", "README.md", "scripts/claude-mpm"
95
+ ]):
96
+ self._project_root = candidate
97
+ return candidate
98
+
99
+ # Fallback to package parent
100
+ self._project_root = package.parent
101
+ return self._project_root
102
+
103
+ @property
104
+ def scripts_dir(self) -> Path:
105
+ """
106
+ Get the scripts directory path.
107
+
108
+ WHY: Scripts can be in different locations depending on deployment:
109
+ - Development: project_root/scripts
110
+ - Package: claude_mpm/scripts
111
+
112
+ Returns:
113
+ Path to the scripts directory
114
+ """
115
+ if self._scripts_dir:
116
+ return self._scripts_dir
117
+
118
+ # First try package scripts (for deployed packages)
119
+ package_scripts = self.package_root / "scripts"
120
+ if package_scripts.exists():
121
+ self._scripts_dir = package_scripts
122
+ return package_scripts
123
+
124
+ # Then try project scripts (for development)
125
+ project_scripts = self.project_root / "scripts"
126
+ if project_scripts.exists():
127
+ self._scripts_dir = project_scripts
128
+ return project_scripts
129
+
130
+ # Default to package scripts even if it doesn't exist yet
131
+ self._scripts_dir = package_scripts
132
+ return package_scripts
133
+
134
+ @property
135
+ def templates_dir(self) -> Path:
136
+ """Get the agent templates directory."""
137
+ if not self._templates_dir:
138
+ self._templates_dir = self.package_root / "agents" / "templates"
139
+ return self._templates_dir
140
+
141
+ @property
142
+ def static_dir(self) -> Path:
143
+ """Get the static files directory (HTML, CSS, etc)."""
144
+ if not self._static_dir:
145
+ # Static files are in package scripts directory
146
+ self._static_dir = self.package_root / "scripts"
147
+ return self._static_dir
148
+
149
+ def get_monitor_html_path(self) -> Path:
150
+ """
151
+ Get the path to the monitor HTML file.
152
+
153
+ WHY: The monitor HTML can be in different locations depending on
154
+ deployment context. We now prioritize the new modular web structure.
155
+
156
+ Returns:
157
+ Path to the monitor HTML file
158
+ """
159
+ # Try multiple locations in order of preference
160
+ candidates = [
161
+ # New modular structure (preferred)
162
+ self.package_root / "web" / "templates" / "index.html",
163
+ self.package_root / "web" / "templates" / "dashboard.html", # fallback
164
+ self.package_root / "web" / "index.html", # root web index
165
+ # Legacy locations (for backward compatibility)
166
+ self.static_dir / "claude_mpm_monitor.html",
167
+ self.scripts_dir / "claude_mpm_monitor.html",
168
+ self.project_root / "scripts" / "claude_mpm_monitor.html",
169
+ ]
170
+
171
+ for candidate in candidates:
172
+ if candidate.exists():
173
+ return candidate
174
+
175
+ # Return the preferred new location even if it doesn't exist
176
+ # This allows better error messages and encourages proper structure
177
+ return self.package_root / "web" / "templates" / "index.html"
178
+
179
+ def get_resource_path(self, resource_type: str, filename: str) -> Path:
180
+ """
181
+ Get path to a resource file.
182
+
183
+ Args:
184
+ resource_type: Type of resource (scripts, templates, static, etc)
185
+ filename: Name of the file
186
+
187
+ Returns:
188
+ Path to the resource
189
+ """
190
+ resource_dirs = {
191
+ "scripts": self.scripts_dir,
192
+ "templates": self.templates_dir,
193
+ "static": self.static_dir,
194
+ "agents": self.package_root / "agents",
195
+ }
196
+
197
+ base_dir = resource_dirs.get(resource_type, self.package_root)
198
+ return base_dir / filename
199
+
200
+ def resolve_import_path(self, module_path: str) -> Path:
201
+ """
202
+ Resolve a module import path to a file path.
203
+
204
+ Args:
205
+ module_path: Dot-separated module path (e.g., "claude_mpm.cli.commands.run")
206
+
207
+ Returns:
208
+ Path to the module file
209
+ """
210
+ parts = module_path.split(".")
211
+ if parts[0] == "claude_mpm":
212
+ parts = parts[1:] # Remove package name
213
+
214
+ return self.package_root.joinpath(*parts).with_suffix(".py")
215
+
216
+ def ensure_directory(self, path: Path) -> Path:
217
+ """Ensure a directory exists, creating it if necessary."""
218
+ path.mkdir(parents=True, exist_ok=True)
219
+ return path
220
+
221
+ @classmethod
222
+ @lru_cache(maxsize=1)
223
+ def get_instance(cls) -> "DeploymentPaths":
224
+ """Get singleton instance of DeploymentPaths."""
225
+ return cls()
226
+
227
+
228
+ # Convenience functions
229
+ def get_deployment_paths() -> DeploymentPaths:
230
+ """Get the deployment paths instance."""
231
+ return DeploymentPaths.get_instance()
232
+
233
+
234
+ def get_package_root() -> Path:
235
+ """Get the claude_mpm package root directory."""
236
+ return get_deployment_paths().package_root
237
+
238
+
239
+ def get_project_root() -> Path:
240
+ """Get the project root directory."""
241
+ return get_deployment_paths().project_root
242
+
243
+
244
+ def get_scripts_dir() -> Path:
245
+ """Get the scripts directory."""
246
+ return get_deployment_paths().scripts_dir
247
+
248
+
249
+ def get_monitor_html_path() -> Path:
250
+ """Get the monitor HTML file path."""
251
+ return get_deployment_paths().get_monitor_html_path()
252
+
253
+
254
+ def get_templates_dir() -> Path:
255
+ """Get the agent templates directory."""
256
+ return get_deployment_paths().templates_dir
257
+
258
+
259
+ def get_resource_path(resource_type: str, filename: str) -> Path:
260
+ """Get path to a resource file."""
261
+ return get_deployment_paths().get_resource_path(resource_type, filename)
@@ -0,0 +1,67 @@
1
+ """Example of how to register memory integration hooks.
2
+
3
+ WHY: This demonstrates how to register the memory hooks with the HookService
4
+ for automatic memory injection and learning extraction.
5
+ """
6
+
7
+ from claude_mpm.hooks.memory_integration_hook import (
8
+ MemoryPreDelegationHook,
9
+ MemoryPostDelegationHook
10
+ )
11
+ from claude_mpm.services.hook_service import HookService
12
+ from claude_mpm.core.config import Config
13
+
14
+
15
+ def register_memory_hooks(hook_service: HookService, config: Config = None):
16
+ """Register memory integration hooks with the hook service.
17
+
18
+ WHY: To enable automatic memory management, both hooks need to be
19
+ registered with appropriate priorities:
20
+ - Pre-hook runs early (priority 20) to inject memory into context
21
+ - Post-hook runs late (priority 80) to extract learnings after processing
22
+
23
+ Args:
24
+ hook_service: The HookService instance to register with
25
+ config: Optional configuration (will create default if not provided)
26
+ """
27
+ config = config or Config()
28
+
29
+ # Only register if memory system is enabled
30
+ if not config.get('memory.enabled', True):
31
+ return
32
+
33
+ # Register pre-delegation hook for memory injection
34
+ pre_hook = MemoryPreDelegationHook(config)
35
+ hook_service.register_hook(pre_hook)
36
+
37
+ # Register post-delegation hook for learning extraction
38
+ # Only if auto-learning is enabled
39
+ if config.get('memory.auto_learning', False):
40
+ post_hook = MemoryPostDelegationHook(config)
41
+ hook_service.register_hook(post_hook)
42
+
43
+
44
+ # Example usage:
45
+ if __name__ == "__main__":
46
+ # This would typically be done during application initialization
47
+ config = Config(config={
48
+ 'memory': {
49
+ 'enabled': True,
50
+ 'auto_learning': True,
51
+ 'limits': {
52
+ 'default_size_kb': 8,
53
+ 'max_items_per_section': 20
54
+ }
55
+ }
56
+ })
57
+
58
+ # Create hook service (normally this would be passed from main app)
59
+ from claude_mpm.services.hook_service import HookService
60
+ hook_service = HookService(config)
61
+
62
+ # Register memory hooks
63
+ register_memory_hooks(hook_service, config)
64
+
65
+ print("Memory hooks registered successfully!")
66
+ print(f"Pre-delegation hook: {hook_service.get_hooks('pre_delegation')}")
67
+ print(f"Post-delegation hook: {hook_service.get_hooks('post_delegation')}")