claude-mpm 3.9.8__py3-none-any.whl → 3.9.11__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.json +1 -1
- claude_mpm/agents/templates/memory_manager.json +155 -0
- claude_mpm/cli/__init__.py +18 -3
- claude_mpm/cli/commands/__init__.py +6 -1
- claude_mpm/cli/commands/cleanup.py +21 -1
- claude_mpm/cli/commands/mcp.py +967 -0
- claude_mpm/cli/commands/run_guarded.py +511 -0
- claude_mpm/cli/parser.py +156 -3
- claude_mpm/config/experimental_features.py +219 -0
- claude_mpm/config/memory_guardian_config.py +325 -0
- claude_mpm/config/memory_guardian_yaml.py +335 -0
- claude_mpm/constants.py +14 -0
- claude_mpm/core/memory_aware_runner.py +353 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +76 -19
- claude_mpm/models/state_models.py +433 -0
- claude_mpm/services/communication/__init__.py +2 -2
- claude_mpm/services/communication/socketio.py +18 -16
- claude_mpm/services/infrastructure/__init__.py +4 -1
- claude_mpm/services/infrastructure/context_preservation.py +537 -0
- claude_mpm/services/infrastructure/graceful_degradation.py +616 -0
- claude_mpm/services/infrastructure/health_monitor.py +775 -0
- claude_mpm/services/infrastructure/logging.py +3 -3
- claude_mpm/services/infrastructure/memory_dashboard.py +479 -0
- claude_mpm/services/infrastructure/memory_guardian.py +944 -0
- claude_mpm/services/infrastructure/restart_protection.py +642 -0
- claude_mpm/services/infrastructure/state_manager.py +774 -0
- claude_mpm/services/mcp_gateway/__init__.py +39 -23
- claude_mpm/services/mcp_gateway/core/__init__.py +2 -2
- claude_mpm/services/mcp_gateway/core/interfaces.py +10 -9
- claude_mpm/services/mcp_gateway/main.py +356 -0
- claude_mpm/services/mcp_gateway/manager.py +334 -0
- claude_mpm/services/mcp_gateway/registry/__init__.py +6 -3
- claude_mpm/services/mcp_gateway/registry/service_registry.py +393 -0
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +477 -0
- claude_mpm/services/mcp_gateway/server/__init__.py +9 -3
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +431 -0
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +373 -0
- claude_mpm/services/mcp_gateway/tools/__init__.py +16 -3
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +496 -0
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +729 -0
- claude_mpm/services/mcp_gateway/tools/hello_world.py +551 -0
- claude_mpm/services/ticket_manager.py +8 -8
- claude_mpm/services/ticket_manager_di.py +5 -5
- claude_mpm/storage/__init__.py +9 -0
- claude_mpm/storage/state_storage.py +556 -0
- claude_mpm/utils/file_utils.py +293 -0
- claude_mpm/utils/platform_memory.py +524 -0
- claude_mpm/utils/subprocess_utils.py +305 -0
- {claude_mpm-3.9.8.dist-info → claude_mpm-3.9.11.dist-info}/METADATA +27 -2
- {claude_mpm-3.9.8.dist-info → claude_mpm-3.9.11.dist-info}/RECORD +56 -32
- claude_mpm/agents/templates/.claude-mpm/memories/README.md +0 -36
- claude_mpm/agents/templates/.claude-mpm/memories/engineer_agent.md +0 -39
- claude_mpm/agents/templates/.claude-mpm/memories/qa_agent.md +0 -38
- claude_mpm/agents/templates/.claude-mpm/memories/research_agent.md +0 -39
- claude_mpm/agents/templates/.claude-mpm/memories/version_control_agent.md +0 -38
- /claude_mpm/agents/templates/{research_memory_efficient.json → backup/research_memory_efficient.json} +0 -0
- {claude_mpm-3.9.8.dist-info → claude_mpm-3.9.11.dist-info}/WHEEL +0 -0
- {claude_mpm-3.9.8.dist-info → claude_mpm-3.9.11.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.9.8.dist-info → claude_mpm-3.9.11.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.9.8.dist-info → claude_mpm-3.9.11.dist-info}/top_level.txt +0 -0
| @@ -0,0 +1,219 @@ | |
| 1 | 
            +
            """Experimental features configuration for Claude MPM.
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            WHY: This module manages experimental and beta features, providing a centralized
         | 
| 4 | 
            +
            way to control feature flags and display appropriate warnings to users.
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            DESIGN DECISION: Use a simple configuration class with static defaults that can
         | 
| 7 | 
            +
            be overridden through environment variables or config files. This allows for
         | 
| 8 | 
            +
            gradual rollout of experimental features while maintaining stability in production.
         | 
| 9 | 
            +
            """
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            import os
         | 
| 12 | 
            +
            from typing import Dict, Any, Optional
         | 
| 13 | 
            +
            from pathlib import Path
         | 
| 14 | 
            +
            import json
         | 
| 15 | 
            +
             | 
| 16 | 
            +
             | 
| 17 | 
            +
            class ExperimentalFeatures:
         | 
| 18 | 
            +
                """Manages experimental feature flags and warnings.
         | 
| 19 | 
            +
                
         | 
| 20 | 
            +
                WHY: Experimental features need special handling to ensure users understand
         | 
| 21 | 
            +
                they are using beta functionality that may change or have issues.
         | 
| 22 | 
            +
                
         | 
| 23 | 
            +
                DESIGN DECISION: Use environment variables for quick override during development,
         | 
| 24 | 
            +
                but also support configuration files for persistent settings.
         | 
| 25 | 
            +
                """
         | 
| 26 | 
            +
                
         | 
| 27 | 
            +
                # Default feature flags
         | 
| 28 | 
            +
                DEFAULTS = {
         | 
| 29 | 
            +
                    'enable_memory_guardian': False,  # Memory Guardian is experimental
         | 
| 30 | 
            +
                    'enable_mcp_gateway': False,      # MCP Gateway is experimental
         | 
| 31 | 
            +
                    'enable_advanced_aggregation': False,  # Advanced aggregation features
         | 
| 32 | 
            +
                    'show_experimental_warnings': True,  # Show warnings for experimental features
         | 
| 33 | 
            +
                    'require_experimental_acceptance': True,  # Require explicit acceptance
         | 
| 34 | 
            +
                }
         | 
| 35 | 
            +
                
         | 
| 36 | 
            +
                # Warning messages for experimental features
         | 
| 37 | 
            +
                WARNINGS = {
         | 
| 38 | 
            +
                    'memory_guardian': (
         | 
| 39 | 
            +
                        "⚠️  EXPERIMENTAL FEATURE: Memory Guardian is in beta.\n"
         | 
| 40 | 
            +
                        "   This feature may change or have issues. Use with caution in production.\n"
         | 
| 41 | 
            +
                        "   Report issues at: https://github.com/bluescreen10/claude-mpm/issues"
         | 
| 42 | 
            +
                    ),
         | 
| 43 | 
            +
                    'mcp_gateway': (
         | 
| 44 | 
            +
                        "⚠️  EXPERIMENTAL FEATURE: MCP Gateway is in early access.\n"
         | 
| 45 | 
            +
                        "   Tool integration may be unstable. Not recommended for production use."
         | 
| 46 | 
            +
                    ),
         | 
| 47 | 
            +
                    'advanced_aggregation': (
         | 
| 48 | 
            +
                        "⚠️  EXPERIMENTAL FEATURE: Advanced aggregation is under development.\n"
         | 
| 49 | 
            +
                        "   Results may vary. Please verify outputs manually."
         | 
| 50 | 
            +
                    ),
         | 
| 51 | 
            +
                }
         | 
| 52 | 
            +
                
         | 
| 53 | 
            +
                def __init__(self, config_file: Optional[Path] = None):
         | 
| 54 | 
            +
                    """Initialize experimental features configuration.
         | 
| 55 | 
            +
                    
         | 
| 56 | 
            +
                    Args:
         | 
| 57 | 
            +
                        config_file: Optional path to configuration file
         | 
| 58 | 
            +
                    """
         | 
| 59 | 
            +
                    self._features = self.DEFAULTS.copy()
         | 
| 60 | 
            +
                    self._config_file = config_file
         | 
| 61 | 
            +
                    self._load_configuration()
         | 
| 62 | 
            +
                    self._apply_environment_overrides()
         | 
| 63 | 
            +
                
         | 
| 64 | 
            +
                def _load_configuration(self):
         | 
| 65 | 
            +
                    """Load configuration from file if it exists.
         | 
| 66 | 
            +
                    
         | 
| 67 | 
            +
                    WHY: Allow persistent configuration of experimental features without
         | 
| 68 | 
            +
                    requiring environment variables to be set every time.
         | 
| 69 | 
            +
                    """
         | 
| 70 | 
            +
                    if self._config_file and self._config_file.exists():
         | 
| 71 | 
            +
                        try:
         | 
| 72 | 
            +
                            with open(self._config_file, 'r') as f:
         | 
| 73 | 
            +
                                config = json.load(f)
         | 
| 74 | 
            +
                                experimental = config.get('experimental_features', {})
         | 
| 75 | 
            +
                                self._features.update(experimental)
         | 
| 76 | 
            +
                        except Exception:
         | 
| 77 | 
            +
                            # Silently ignore configuration errors for experimental features
         | 
| 78 | 
            +
                            pass
         | 
| 79 | 
            +
                
         | 
| 80 | 
            +
                def _apply_environment_overrides(self):
         | 
| 81 | 
            +
                    """Apply environment variable overrides.
         | 
| 82 | 
            +
                    
         | 
| 83 | 
            +
                    WHY: Environment variables provide a quick way to enable/disable features
         | 
| 84 | 
            +
                    during development and testing without modifying configuration files.
         | 
| 85 | 
            +
                    
         | 
| 86 | 
            +
                    Format: CLAUDE_MPM_EXPERIMENTAL_<FEATURE_NAME>=true/false
         | 
| 87 | 
            +
                    """
         | 
| 88 | 
            +
                    for key in self._features:
         | 
| 89 | 
            +
                        env_key = f"CLAUDE_MPM_EXPERIMENTAL_{key.upper()}"
         | 
| 90 | 
            +
                        if env_key in os.environ:
         | 
| 91 | 
            +
                            value = os.environ[env_key].lower()
         | 
| 92 | 
            +
                            self._features[key] = value in ('true', '1', 'yes', 'on')
         | 
| 93 | 
            +
                
         | 
| 94 | 
            +
                def is_enabled(self, feature: str) -> bool:
         | 
| 95 | 
            +
                    """Check if a feature is enabled.
         | 
| 96 | 
            +
                    
         | 
| 97 | 
            +
                    Args:
         | 
| 98 | 
            +
                        feature: Feature name (without 'enable_' prefix)
         | 
| 99 | 
            +
                        
         | 
| 100 | 
            +
                    Returns:
         | 
| 101 | 
            +
                        True if the feature is enabled
         | 
| 102 | 
            +
                    """
         | 
| 103 | 
            +
                    key = f"enable_{feature}" if not feature.startswith('enable_') else feature
         | 
| 104 | 
            +
                    return self._features.get(key, False)
         | 
| 105 | 
            +
                
         | 
| 106 | 
            +
                def get_warning(self, feature: str) -> Optional[str]:
         | 
| 107 | 
            +
                    """Get warning message for a feature.
         | 
| 108 | 
            +
                    
         | 
| 109 | 
            +
                    Args:
         | 
| 110 | 
            +
                        feature: Feature name
         | 
| 111 | 
            +
                        
         | 
| 112 | 
            +
                    Returns:
         | 
| 113 | 
            +
                        Warning message or None if no warning exists
         | 
| 114 | 
            +
                    """
         | 
| 115 | 
            +
                    return self.WARNINGS.get(feature)
         | 
| 116 | 
            +
                
         | 
| 117 | 
            +
                def should_show_warning(self, feature: str) -> bool:
         | 
| 118 | 
            +
                    """Check if warning should be shown for a feature.
         | 
| 119 | 
            +
                    
         | 
| 120 | 
            +
                    Args:
         | 
| 121 | 
            +
                        feature: Feature name
         | 
| 122 | 
            +
                        
         | 
| 123 | 
            +
                    Returns:
         | 
| 124 | 
            +
                        True if warning should be displayed
         | 
| 125 | 
            +
                    """
         | 
| 126 | 
            +
                    if not self._features.get('show_experimental_warnings', True):
         | 
| 127 | 
            +
                        return False
         | 
| 128 | 
            +
                    
         | 
| 129 | 
            +
                    # Check if user has already accepted this feature
         | 
| 130 | 
            +
                    accepted_file = Path.home() / '.claude-mpm' / '.experimental_accepted'
         | 
| 131 | 
            +
                    if accepted_file.exists():
         | 
| 132 | 
            +
                        try:
         | 
| 133 | 
            +
                            with open(accepted_file, 'r') as f:
         | 
| 134 | 
            +
                                accepted = json.load(f)
         | 
| 135 | 
            +
                                if feature in accepted.get('features', []):
         | 
| 136 | 
            +
                                    return False
         | 
| 137 | 
            +
                        except Exception:
         | 
| 138 | 
            +
                            pass
         | 
| 139 | 
            +
                    
         | 
| 140 | 
            +
                    return True
         | 
| 141 | 
            +
                
         | 
| 142 | 
            +
                def mark_accepted(self, feature: str):
         | 
| 143 | 
            +
                    """Mark a feature as accepted by the user.
         | 
| 144 | 
            +
                    
         | 
| 145 | 
            +
                    WHY: Once a user accepts the experimental status, we don't need to
         | 
| 146 | 
            +
                    warn them every time they use the feature.
         | 
| 147 | 
            +
                    
         | 
| 148 | 
            +
                    Args:
         | 
| 149 | 
            +
                        feature: Feature name to mark as accepted
         | 
| 150 | 
            +
                    """
         | 
| 151 | 
            +
                    accepted_file = Path.home() / '.claude-mpm' / '.experimental_accepted'
         | 
| 152 | 
            +
                    accepted_file.parent.mkdir(parents=True, exist_ok=True)
         | 
| 153 | 
            +
                    
         | 
| 154 | 
            +
                    try:
         | 
| 155 | 
            +
                        if accepted_file.exists():
         | 
| 156 | 
            +
                            with open(accepted_file, 'r') as f:
         | 
| 157 | 
            +
                                data = json.load(f)
         | 
| 158 | 
            +
                        else:
         | 
| 159 | 
            +
                            data = {'features': [], 'timestamp': {}}
         | 
| 160 | 
            +
                        
         | 
| 161 | 
            +
                        if feature not in data['features']:
         | 
| 162 | 
            +
                            data['features'].append(feature)
         | 
| 163 | 
            +
                            data['timestamp'][feature] = os.environ.get('CLAUDE_MPM_TIMESTAMP', 
         | 
| 164 | 
            +
                                                                       str(Path.cwd()))
         | 
| 165 | 
            +
                        
         | 
| 166 | 
            +
                        with open(accepted_file, 'w') as f:
         | 
| 167 | 
            +
                            json.dump(data, f, indent=2)
         | 
| 168 | 
            +
                    except Exception:
         | 
| 169 | 
            +
                        # Silently ignore errors in acceptance tracking
         | 
| 170 | 
            +
                        pass
         | 
| 171 | 
            +
                
         | 
| 172 | 
            +
                def requires_acceptance(self) -> bool:
         | 
| 173 | 
            +
                    """Check if experimental features require explicit acceptance.
         | 
| 174 | 
            +
                    
         | 
| 175 | 
            +
                    Returns:
         | 
| 176 | 
            +
                        True if acceptance is required
         | 
| 177 | 
            +
                    """
         | 
| 178 | 
            +
                    return self._features.get('require_experimental_acceptance', True)
         | 
| 179 | 
            +
                
         | 
| 180 | 
            +
                def get_all_features(self) -> Dict[str, bool]:
         | 
| 181 | 
            +
                    """Get all feature flags and their current values.
         | 
| 182 | 
            +
                    
         | 
| 183 | 
            +
                    Returns:
         | 
| 184 | 
            +
                        Dictionary of feature flags and their values
         | 
| 185 | 
            +
                    """
         | 
| 186 | 
            +
                    return self._features.copy()
         | 
| 187 | 
            +
             | 
| 188 | 
            +
             | 
| 189 | 
            +
            # Global instance for easy access
         | 
| 190 | 
            +
            _experimental_features = None
         | 
| 191 | 
            +
             | 
| 192 | 
            +
             | 
| 193 | 
            +
            def get_experimental_features(config_file: Optional[Path] = None) -> ExperimentalFeatures:
         | 
| 194 | 
            +
                """Get the global experimental features instance.
         | 
| 195 | 
            +
                
         | 
| 196 | 
            +
                WHY: Provide a singleton-like access pattern to experimental features
         | 
| 197 | 
            +
                configuration to ensure consistency across the application.
         | 
| 198 | 
            +
                
         | 
| 199 | 
            +
                Args:
         | 
| 200 | 
            +
                    config_file: Optional configuration file path
         | 
| 201 | 
            +
                    
         | 
| 202 | 
            +
                Returns:
         | 
| 203 | 
            +
                    ExperimentalFeatures instance
         | 
| 204 | 
            +
                """
         | 
| 205 | 
            +
                global _experimental_features
         | 
| 206 | 
            +
                if _experimental_features is None:
         | 
| 207 | 
            +
                    # Check for config file in standard locations
         | 
| 208 | 
            +
                    if config_file is None:
         | 
| 209 | 
            +
                        for path in [
         | 
| 210 | 
            +
                            Path.cwd() / '.claude-mpm' / 'experimental.json',
         | 
| 211 | 
            +
                            Path.home() / '.claude-mpm' / 'experimental.json',
         | 
| 212 | 
            +
                        ]:
         | 
| 213 | 
            +
                            if path.exists():
         | 
| 214 | 
            +
                                config_file = path
         | 
| 215 | 
            +
                                break
         | 
| 216 | 
            +
                    
         | 
| 217 | 
            +
                    _experimental_features = ExperimentalFeatures(config_file)
         | 
| 218 | 
            +
                
         | 
| 219 | 
            +
                return _experimental_features
         | 
| @@ -0,0 +1,325 @@ | |
| 1 | 
            +
            """Memory Guardian configuration for managing Claude Code memory usage.
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            This module provides configuration management for the MemoryGuardian service
         | 
| 4 | 
            +
            that monitors and manages Claude Code subprocess memory consumption.
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            Design Principles:
         | 
| 7 | 
            +
            - Platform-agnostic configuration with OS-specific overrides
         | 
| 8 | 
            +
            - Environment-based configuration for different deployment scenarios
         | 
| 9 | 
            +
            - Flexible thresholds for memory monitoring
         | 
| 10 | 
            +
            - Support for both psutil and fallback monitoring methods
         | 
| 11 | 
            +
            """
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            import os
         | 
| 14 | 
            +
            import platform
         | 
| 15 | 
            +
            from dataclasses import dataclass, field
         | 
| 16 | 
            +
            from pathlib import Path
         | 
| 17 | 
            +
            from typing import Dict, Any, Optional, List
         | 
| 18 | 
            +
             | 
| 19 | 
            +
             | 
| 20 | 
            +
            @dataclass
         | 
| 21 | 
            +
            class MemoryThresholds:
         | 
| 22 | 
            +
                """Memory threshold configuration in MB."""
         | 
| 23 | 
            +
                
         | 
| 24 | 
            +
                # Memory thresholds in MB (defaults for 24GB system)
         | 
| 25 | 
            +
                warning: int = 12288  # 12GB - Start monitoring closely
         | 
| 26 | 
            +
                critical: int = 15360  # 15GB - Consider restart
         | 
| 27 | 
            +
                emergency: int = 18432  # 18GB - Force restart
         | 
| 28 | 
            +
                
         | 
| 29 | 
            +
                # Percentage-based thresholds (as fallback when system memory is detected)
         | 
| 30 | 
            +
                warning_percent: float = 50.0  # 50% of system memory
         | 
| 31 | 
            +
                critical_percent: float = 65.0  # 65% of system memory
         | 
| 32 | 
            +
                emergency_percent: float = 75.0  # 75% of system memory
         | 
| 33 | 
            +
                
         | 
| 34 | 
            +
                def adjust_for_system_memory(self, total_memory_mb: int) -> None:
         | 
| 35 | 
            +
                    """Adjust thresholds based on available system memory."""
         | 
| 36 | 
            +
                    if total_memory_mb > 0:
         | 
| 37 | 
            +
                        self.warning = int(total_memory_mb * (self.warning_percent / 100))
         | 
| 38 | 
            +
                        self.critical = int(total_memory_mb * (self.critical_percent / 100))
         | 
| 39 | 
            +
                        self.emergency = int(total_memory_mb * (self.emergency_percent / 100))
         | 
| 40 | 
            +
                
         | 
| 41 | 
            +
                def to_dict(self) -> Dict[str, Any]:
         | 
| 42 | 
            +
                    """Convert thresholds to dictionary."""
         | 
| 43 | 
            +
                    return {
         | 
| 44 | 
            +
                        'warning_mb': self.warning,
         | 
| 45 | 
            +
                        'critical_mb': self.critical,
         | 
| 46 | 
            +
                        'emergency_mb': self.emergency,
         | 
| 47 | 
            +
                        'warning_percent': self.warning_percent,
         | 
| 48 | 
            +
                        'critical_percent': self.critical_percent,
         | 
| 49 | 
            +
                        'emergency_percent': self.emergency_percent
         | 
| 50 | 
            +
                    }
         | 
| 51 | 
            +
             | 
| 52 | 
            +
             | 
| 53 | 
            +
            @dataclass
         | 
| 54 | 
            +
            class RestartPolicy:
         | 
| 55 | 
            +
                """Configuration for process restart behavior."""
         | 
| 56 | 
            +
                
         | 
| 57 | 
            +
                # Restart attempts
         | 
| 58 | 
            +
                max_attempts: int = 3  # Maximum restart attempts before giving up
         | 
| 59 | 
            +
                attempt_window: int = 3600  # Window in seconds to count attempts (1 hour)
         | 
| 60 | 
            +
                
         | 
| 61 | 
            +
                # Cooldown periods
         | 
| 62 | 
            +
                initial_cooldown: int = 30  # Initial cooldown after restart (seconds)
         | 
| 63 | 
            +
                max_cooldown: int = 300  # Maximum cooldown period (5 minutes)
         | 
| 64 | 
            +
                cooldown_multiplier: float = 2.0  # Multiply cooldown on each retry
         | 
| 65 | 
            +
                
         | 
| 66 | 
            +
                # Graceful shutdown
         | 
| 67 | 
            +
                graceful_timeout: int = 30  # Time to wait for graceful shutdown (seconds)
         | 
| 68 | 
            +
                force_kill_timeout: int = 10  # Time to wait before SIGKILL after SIGTERM
         | 
| 69 | 
            +
                
         | 
| 70 | 
            +
                def get_cooldown(self, attempt: int) -> int:
         | 
| 71 | 
            +
                    """Calculate cooldown period for given attempt number."""
         | 
| 72 | 
            +
                    cooldown = self.initial_cooldown * (self.cooldown_multiplier ** (attempt - 1))
         | 
| 73 | 
            +
                    return min(int(cooldown), self.max_cooldown)
         | 
| 74 | 
            +
                
         | 
| 75 | 
            +
                def to_dict(self) -> Dict[str, Any]:
         | 
| 76 | 
            +
                    """Convert restart policy to dictionary."""
         | 
| 77 | 
            +
                    return {
         | 
| 78 | 
            +
                        'max_attempts': self.max_attempts,
         | 
| 79 | 
            +
                        'attempt_window': self.attempt_window,
         | 
| 80 | 
            +
                        'initial_cooldown': self.initial_cooldown,
         | 
| 81 | 
            +
                        'max_cooldown': self.max_cooldown,
         | 
| 82 | 
            +
                        'cooldown_multiplier': self.cooldown_multiplier,
         | 
| 83 | 
            +
                        'graceful_timeout': self.graceful_timeout,
         | 
| 84 | 
            +
                        'force_kill_timeout': self.force_kill_timeout
         | 
| 85 | 
            +
                    }
         | 
| 86 | 
            +
             | 
| 87 | 
            +
             | 
| 88 | 
            +
            @dataclass
         | 
| 89 | 
            +
            class MonitoringConfig:
         | 
| 90 | 
            +
                """Configuration for memory monitoring behavior."""
         | 
| 91 | 
            +
                
         | 
| 92 | 
            +
                # Check intervals (seconds)
         | 
| 93 | 
            +
                normal_interval: int = 30  # Normal check interval
         | 
| 94 | 
            +
                warning_interval: int = 15  # Check interval when in warning state
         | 
| 95 | 
            +
                critical_interval: int = 5  # Check interval when in critical state
         | 
| 96 | 
            +
                
         | 
| 97 | 
            +
                # Monitoring method preferences
         | 
| 98 | 
            +
                prefer_psutil: bool = True  # Prefer psutil if available
         | 
| 99 | 
            +
                fallback_methods: List[str] = field(default_factory=lambda: [
         | 
| 100 | 
            +
                    'platform_specific',  # Use OS-specific commands
         | 
| 101 | 
            +
                    'resource_module',    # Use resource module as last resort
         | 
| 102 | 
            +
                ])
         | 
| 103 | 
            +
                
         | 
| 104 | 
            +
                # Logging and reporting
         | 
| 105 | 
            +
                log_memory_stats: bool = True  # Log memory statistics
         | 
| 106 | 
            +
                log_interval: int = 300  # Log summary every 5 minutes
         | 
| 107 | 
            +
                detailed_logging: bool = False  # Enable detailed debug logging
         | 
| 108 | 
            +
                
         | 
| 109 | 
            +
                def get_check_interval(self, memory_state: str) -> int:
         | 
| 110 | 
            +
                    """Get check interval based on current memory state."""
         | 
| 111 | 
            +
                    if memory_state == 'critical':
         | 
| 112 | 
            +
                        return self.critical_interval
         | 
| 113 | 
            +
                    elif memory_state == 'warning':
         | 
| 114 | 
            +
                        return self.warning_interval
         | 
| 115 | 
            +
                    else:
         | 
| 116 | 
            +
                        return self.normal_interval
         | 
| 117 | 
            +
                
         | 
| 118 | 
            +
                def to_dict(self) -> Dict[str, Any]:
         | 
| 119 | 
            +
                    """Convert monitoring config to dictionary."""
         | 
| 120 | 
            +
                    return {
         | 
| 121 | 
            +
                        'normal_interval': self.normal_interval,
         | 
| 122 | 
            +
                        'warning_interval': self.warning_interval,
         | 
| 123 | 
            +
                        'critical_interval': self.critical_interval,
         | 
| 124 | 
            +
                        'prefer_psutil': self.prefer_psutil,
         | 
| 125 | 
            +
                        'fallback_methods': self.fallback_methods,
         | 
| 126 | 
            +
                        'log_memory_stats': self.log_memory_stats,
         | 
| 127 | 
            +
                        'log_interval': self.log_interval,
         | 
| 128 | 
            +
                        'detailed_logging': self.detailed_logging
         | 
| 129 | 
            +
                    }
         | 
| 130 | 
            +
             | 
| 131 | 
            +
             | 
| 132 | 
            +
            @dataclass
         | 
| 133 | 
            +
            class PlatformOverrides:
         | 
| 134 | 
            +
                """Platform-specific configuration overrides."""
         | 
| 135 | 
            +
                
         | 
| 136 | 
            +
                # macOS specific
         | 
| 137 | 
            +
                macos_use_activity_monitor: bool = False  # Use Activity Monitor data if available
         | 
| 138 | 
            +
                macos_memory_pressure_check: bool = True  # Check system memory pressure
         | 
| 139 | 
            +
                
         | 
| 140 | 
            +
                # Linux specific
         | 
| 141 | 
            +
                linux_use_proc: bool = True  # Use /proc filesystem
         | 
| 142 | 
            +
                linux_check_oom_score: bool = True  # Monitor OOM killer score
         | 
| 143 | 
            +
                
         | 
| 144 | 
            +
                # Windows specific
         | 
| 145 | 
            +
                windows_use_wmi: bool = True  # Use WMI for monitoring
         | 
| 146 | 
            +
                windows_use_performance_counter: bool = False  # Use performance counters
         | 
| 147 | 
            +
                
         | 
| 148 | 
            +
                def to_dict(self) -> Dict[str, Any]:
         | 
| 149 | 
            +
                    """Convert platform overrides to dictionary."""
         | 
| 150 | 
            +
                    return {
         | 
| 151 | 
            +
                        'macos_use_activity_monitor': self.macos_use_activity_monitor,
         | 
| 152 | 
            +
                        'macos_memory_pressure_check': self.macos_memory_pressure_check,
         | 
| 153 | 
            +
                        'linux_use_proc': self.linux_use_proc,
         | 
| 154 | 
            +
                        'linux_check_oom_score': self.linux_check_oom_score,
         | 
| 155 | 
            +
                        'windows_use_wmi': self.windows_use_wmi,
         | 
| 156 | 
            +
                        'windows_use_performance_counter': self.windows_use_performance_counter
         | 
| 157 | 
            +
                    }
         | 
| 158 | 
            +
             | 
| 159 | 
            +
             | 
| 160 | 
            +
            @dataclass
         | 
| 161 | 
            +
            class MemoryGuardianConfig:
         | 
| 162 | 
            +
                """Complete configuration for MemoryGuardian service."""
         | 
| 163 | 
            +
                
         | 
| 164 | 
            +
                # Core configurations
         | 
| 165 | 
            +
                thresholds: MemoryThresholds = field(default_factory=MemoryThresholds)
         | 
| 166 | 
            +
                restart_policy: RestartPolicy = field(default_factory=RestartPolicy)
         | 
| 167 | 
            +
                monitoring: MonitoringConfig = field(default_factory=MonitoringConfig)
         | 
| 168 | 
            +
                platform_overrides: PlatformOverrides = field(default_factory=PlatformOverrides)
         | 
| 169 | 
            +
                
         | 
| 170 | 
            +
                # Process configuration
         | 
| 171 | 
            +
                process_command: List[str] = field(default_factory=lambda: ['claude-code'])
         | 
| 172 | 
            +
                process_args: List[str] = field(default_factory=list)
         | 
| 173 | 
            +
                process_env: Dict[str, str] = field(default_factory=dict)
         | 
| 174 | 
            +
                working_directory: Optional[str] = None
         | 
| 175 | 
            +
                
         | 
| 176 | 
            +
                # Service configuration
         | 
| 177 | 
            +
                enabled: bool = True  # Enable memory guardian
         | 
| 178 | 
            +
                auto_start: bool = True  # Auto-start monitored process
         | 
| 179 | 
            +
                persist_state: bool = True  # Persist state across restarts
         | 
| 180 | 
            +
                state_file: Optional[str] = None  # State file path
         | 
| 181 | 
            +
                
         | 
| 182 | 
            +
                @classmethod
         | 
| 183 | 
            +
                def from_env(cls) -> 'MemoryGuardianConfig':
         | 
| 184 | 
            +
                    """Create configuration from environment variables."""
         | 
| 185 | 
            +
                    config = cls()
         | 
| 186 | 
            +
                    
         | 
| 187 | 
            +
                    # Memory thresholds
         | 
| 188 | 
            +
                    if warning := os.getenv('CLAUDE_MPM_MEMORY_WARNING_MB'):
         | 
| 189 | 
            +
                        config.thresholds.warning = int(warning)
         | 
| 190 | 
            +
                    if critical := os.getenv('CLAUDE_MPM_MEMORY_CRITICAL_MB'):
         | 
| 191 | 
            +
                        config.thresholds.critical = int(critical)
         | 
| 192 | 
            +
                    if emergency := os.getenv('CLAUDE_MPM_MEMORY_EMERGENCY_MB'):
         | 
| 193 | 
            +
                        config.thresholds.emergency = int(emergency)
         | 
| 194 | 
            +
                    
         | 
| 195 | 
            +
                    # Restart policy
         | 
| 196 | 
            +
                    if max_attempts := os.getenv('CLAUDE_MPM_RESTART_MAX_ATTEMPTS'):
         | 
| 197 | 
            +
                        config.restart_policy.max_attempts = int(max_attempts)
         | 
| 198 | 
            +
                    if cooldown := os.getenv('CLAUDE_MPM_RESTART_COOLDOWN'):
         | 
| 199 | 
            +
                        config.restart_policy.initial_cooldown = int(cooldown)
         | 
| 200 | 
            +
                    
         | 
| 201 | 
            +
                    # Monitoring intervals
         | 
| 202 | 
            +
                    if interval := os.getenv('CLAUDE_MPM_MONITOR_INTERVAL'):
         | 
| 203 | 
            +
                        config.monitoring.normal_interval = int(interval)
         | 
| 204 | 
            +
                    if log_interval := os.getenv('CLAUDE_MPM_LOG_INTERVAL'):
         | 
| 205 | 
            +
                        config.monitoring.log_interval = int(log_interval)
         | 
| 206 | 
            +
                    
         | 
| 207 | 
            +
                    # Service settings
         | 
| 208 | 
            +
                    config.enabled = os.getenv('CLAUDE_MPM_MEMORY_GUARDIAN_ENABLED', 'true').lower() == 'true'
         | 
| 209 | 
            +
                    config.auto_start = os.getenv('CLAUDE_MPM_AUTO_START', 'true').lower() == 'true'
         | 
| 210 | 
            +
                    
         | 
| 211 | 
            +
                    # Process command
         | 
| 212 | 
            +
                    if command := os.getenv('CLAUDE_MPM_PROCESS_COMMAND'):
         | 
| 213 | 
            +
                        config.process_command = command.split()
         | 
| 214 | 
            +
                    
         | 
| 215 | 
            +
                    return config
         | 
| 216 | 
            +
                
         | 
| 217 | 
            +
                @classmethod
         | 
| 218 | 
            +
                def for_development(cls) -> 'MemoryGuardianConfig':
         | 
| 219 | 
            +
                    """Configuration optimized for development."""
         | 
| 220 | 
            +
                    config = cls()
         | 
| 221 | 
            +
                    config.thresholds.warning = 8192  # 8GB for dev machines
         | 
| 222 | 
            +
                    config.thresholds.critical = 10240  # 10GB
         | 
| 223 | 
            +
                    config.thresholds.emergency = 12288  # 12GB
         | 
| 224 | 
            +
                    config.monitoring.normal_interval = 60  # Check less frequently
         | 
| 225 | 
            +
                    config.monitoring.detailed_logging = True
         | 
| 226 | 
            +
                    return config
         | 
| 227 | 
            +
                
         | 
| 228 | 
            +
                @classmethod
         | 
| 229 | 
            +
                def for_production(cls) -> 'MemoryGuardianConfig':
         | 
| 230 | 
            +
                    """Configuration optimized for production."""
         | 
| 231 | 
            +
                    config = cls()
         | 
| 232 | 
            +
                    config.monitoring.normal_interval = 30
         | 
| 233 | 
            +
                    config.monitoring.log_memory_stats = True
         | 
| 234 | 
            +
                    config.persist_state = True
         | 
| 235 | 
            +
                    config.restart_policy.max_attempts = 5
         | 
| 236 | 
            +
                    return config
         | 
| 237 | 
            +
                
         | 
| 238 | 
            +
                @classmethod
         | 
| 239 | 
            +
                def for_platform(cls, platform_name: Optional[str] = None) -> 'MemoryGuardianConfig':
         | 
| 240 | 
            +
                    """Get platform-specific configuration."""
         | 
| 241 | 
            +
                    if platform_name is None:
         | 
| 242 | 
            +
                        platform_name = platform.system().lower()
         | 
| 243 | 
            +
                    
         | 
| 244 | 
            +
                    config = cls()
         | 
| 245 | 
            +
                    
         | 
| 246 | 
            +
                    if platform_name == 'darwin':  # macOS
         | 
| 247 | 
            +
                        config.platform_overrides.macos_memory_pressure_check = True
         | 
| 248 | 
            +
                    elif platform_name == 'linux':
         | 
| 249 | 
            +
                        config.platform_overrides.linux_use_proc = True
         | 
| 250 | 
            +
                        config.platform_overrides.linux_check_oom_score = True
         | 
| 251 | 
            +
                    elif platform_name == 'windows':
         | 
| 252 | 
            +
                        config.platform_overrides.windows_use_wmi = True
         | 
| 253 | 
            +
                    
         | 
| 254 | 
            +
                    return config
         | 
| 255 | 
            +
                
         | 
| 256 | 
            +
                def to_dict(self) -> Dict[str, Any]:
         | 
| 257 | 
            +
                    """Convert configuration to dictionary."""
         | 
| 258 | 
            +
                    return {
         | 
| 259 | 
            +
                        'thresholds': self.thresholds.to_dict(),
         | 
| 260 | 
            +
                        'restart_policy': self.restart_policy.to_dict(),
         | 
| 261 | 
            +
                        'monitoring': self.monitoring.to_dict(),
         | 
| 262 | 
            +
                        'platform_overrides': self.platform_overrides.to_dict(),
         | 
| 263 | 
            +
                        'process_command': self.process_command,
         | 
| 264 | 
            +
                        'process_args': self.process_args,
         | 
| 265 | 
            +
                        'process_env': self.process_env,
         | 
| 266 | 
            +
                        'working_directory': self.working_directory,
         | 
| 267 | 
            +
                        'enabled': self.enabled,
         | 
| 268 | 
            +
                        'auto_start': self.auto_start,
         | 
| 269 | 
            +
                        'persist_state': self.persist_state,
         | 
| 270 | 
            +
                        'state_file': self.state_file
         | 
| 271 | 
            +
                    }
         | 
| 272 | 
            +
                
         | 
| 273 | 
            +
                def validate(self) -> List[str]:
         | 
| 274 | 
            +
                    """Validate configuration and return list of issues."""
         | 
| 275 | 
            +
                    issues = []
         | 
| 276 | 
            +
                    
         | 
| 277 | 
            +
                    # Validate thresholds are in correct order
         | 
| 278 | 
            +
                    if self.thresholds.warning >= self.thresholds.critical:
         | 
| 279 | 
            +
                        issues.append("Warning threshold must be less than critical threshold")
         | 
| 280 | 
            +
                    if self.thresholds.critical >= self.thresholds.emergency:
         | 
| 281 | 
            +
                        issues.append("Critical threshold must be less than emergency threshold")
         | 
| 282 | 
            +
                    
         | 
| 283 | 
            +
                    # Validate intervals
         | 
| 284 | 
            +
                    if self.monitoring.normal_interval <= 0:
         | 
| 285 | 
            +
                        issues.append("Normal monitoring interval must be positive")
         | 
| 286 | 
            +
                    if self.monitoring.warning_interval <= 0:
         | 
| 287 | 
            +
                        issues.append("Warning monitoring interval must be positive")
         | 
| 288 | 
            +
                    if self.monitoring.critical_interval <= 0:
         | 
| 289 | 
            +
                        issues.append("Critical monitoring interval must be positive")
         | 
| 290 | 
            +
                    
         | 
| 291 | 
            +
                    # Validate restart policy
         | 
| 292 | 
            +
                    if self.restart_policy.max_attempts < 0:
         | 
| 293 | 
            +
                        issues.append("Max restart attempts cannot be negative")
         | 
| 294 | 
            +
                    if self.restart_policy.initial_cooldown <= 0:
         | 
| 295 | 
            +
                        issues.append("Initial cooldown must be positive")
         | 
| 296 | 
            +
                    
         | 
| 297 | 
            +
                    # Validate process command
         | 
| 298 | 
            +
                    if not self.process_command:
         | 
| 299 | 
            +
                        issues.append("Process command cannot be empty")
         | 
| 300 | 
            +
                    
         | 
| 301 | 
            +
                    return issues
         | 
| 302 | 
            +
             | 
| 303 | 
            +
             | 
| 304 | 
            +
            def get_default_config() -> MemoryGuardianConfig:
         | 
| 305 | 
            +
                """Get default configuration adjusted for current platform."""
         | 
| 306 | 
            +
                config = MemoryGuardianConfig.for_platform()
         | 
| 307 | 
            +
                
         | 
| 308 | 
            +
                # Try to adjust for available system memory
         | 
| 309 | 
            +
                try:
         | 
| 310 | 
            +
                    import psutil
         | 
| 311 | 
            +
                    total_memory_mb = psutil.virtual_memory().total // (1024 * 1024)
         | 
| 312 | 
            +
                    config.thresholds.adjust_for_system_memory(total_memory_mb)
         | 
| 313 | 
            +
                except ImportError:
         | 
| 314 | 
            +
                    # psutil not available, use defaults
         | 
| 315 | 
            +
                    pass
         | 
| 316 | 
            +
                except Exception:
         | 
| 317 | 
            +
                    # Any other error, use defaults
         | 
| 318 | 
            +
                    pass
         | 
| 319 | 
            +
                
         | 
| 320 | 
            +
                # Override with environment variables
         | 
| 321 | 
            +
                env_config = MemoryGuardianConfig.from_env()
         | 
| 322 | 
            +
                if env_config.enabled != config.enabled:
         | 
| 323 | 
            +
                    config = env_config
         | 
| 324 | 
            +
                
         | 
| 325 | 
            +
                return config
         |