claude-mpm 3.7.4__py3-none-any.whl → 3.8.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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/BASE_PM.md +0 -106
- claude_mpm/agents/INSTRUCTIONS.md +0 -78
- claude_mpm/agents/MEMORY.md +88 -0
- claude_mpm/agents/WORKFLOW.md +86 -0
- claude_mpm/agents/schema/agent_schema.json +1 -1
- claude_mpm/agents/templates/code_analyzer.json +26 -11
- claude_mpm/agents/templates/data_engineer.json +4 -7
- claude_mpm/agents/templates/documentation.json +2 -2
- claude_mpm/agents/templates/engineer.json +2 -2
- claude_mpm/agents/templates/ops.json +3 -8
- claude_mpm/agents/templates/qa.json +2 -3
- claude_mpm/agents/templates/research.json +2 -3
- claude_mpm/agents/templates/security.json +3 -6
- claude_mpm/agents/templates/ticketing.json +4 -9
- claude_mpm/agents/templates/version_control.json +3 -3
- claude_mpm/agents/templates/web_qa.json +4 -4
- claude_mpm/agents/templates/web_ui.json +4 -4
- claude_mpm/cli/__init__.py +2 -2
- claude_mpm/cli/commands/__init__.py +2 -1
- claude_mpm/cli/commands/agents.py +118 -1
- claude_mpm/cli/commands/tickets.py +596 -19
- claude_mpm/cli/parser.py +228 -5
- claude_mpm/config/__init__.py +30 -39
- claude_mpm/config/socketio_config.py +8 -5
- claude_mpm/constants.py +13 -0
- claude_mpm/core/__init__.py +8 -18
- claude_mpm/core/cache.py +596 -0
- claude_mpm/core/claude_runner.py +166 -622
- claude_mpm/core/config.py +5 -1
- claude_mpm/core/constants.py +339 -0
- claude_mpm/core/container.py +461 -22
- claude_mpm/core/exceptions.py +392 -0
- claude_mpm/core/framework_loader.py +208 -93
- claude_mpm/core/interactive_session.py +432 -0
- claude_mpm/core/interfaces.py +424 -0
- claude_mpm/core/lazy.py +467 -0
- claude_mpm/core/logging_config.py +444 -0
- claude_mpm/core/oneshot_session.py +465 -0
- claude_mpm/core/optimized_agent_loader.py +485 -0
- claude_mpm/core/optimized_startup.py +490 -0
- claude_mpm/core/service_registry.py +52 -26
- claude_mpm/core/socketio_pool.py +162 -5
- claude_mpm/core/types.py +292 -0
- claude_mpm/core/typing_utils.py +477 -0
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +46 -2
- claude_mpm/dashboard/templates/index.html +5 -5
- claude_mpm/hooks/claude_hooks/hook_handler.py +213 -99
- claude_mpm/init.py +2 -1
- claude_mpm/services/__init__.py +78 -14
- claude_mpm/services/agent/__init__.py +24 -0
- claude_mpm/services/agent/deployment.py +2548 -0
- claude_mpm/services/agent/management.py +598 -0
- claude_mpm/services/agent/registry.py +813 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +592 -269
- claude_mpm/services/agents/deployment/async_agent_deployment.py +5 -1
- claude_mpm/services/agents/management/agent_capabilities_generator.py +21 -11
- claude_mpm/services/agents/memory/agent_memory_manager.py +156 -1
- claude_mpm/services/async_session_logger.py +8 -3
- claude_mpm/services/communication/__init__.py +21 -0
- claude_mpm/services/communication/socketio.py +1933 -0
- claude_mpm/services/communication/websocket.py +479 -0
- claude_mpm/services/core/__init__.py +123 -0
- claude_mpm/services/core/base.py +247 -0
- claude_mpm/services/core/interfaces.py +951 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +23 -23
- claude_mpm/services/framework_claude_md_generator.py +3 -2
- claude_mpm/services/health_monitor.py +4 -3
- claude_mpm/services/hook_service.py +64 -4
- claude_mpm/services/infrastructure/__init__.py +21 -0
- claude_mpm/services/infrastructure/logging.py +202 -0
- claude_mpm/services/infrastructure/monitoring.py +893 -0
- claude_mpm/services/memory/indexed_memory.py +648 -0
- claude_mpm/services/project/__init__.py +21 -0
- claude_mpm/services/project/analyzer.py +864 -0
- claude_mpm/services/project/registry.py +608 -0
- claude_mpm/services/project_analyzer.py +95 -2
- claude_mpm/services/recovery_manager.py +15 -9
- claude_mpm/services/socketio/__init__.py +25 -0
- claude_mpm/services/socketio/handlers/__init__.py +25 -0
- claude_mpm/services/socketio/handlers/base.py +121 -0
- claude_mpm/services/socketio/handlers/connection.py +198 -0
- claude_mpm/services/socketio/handlers/file.py +213 -0
- claude_mpm/services/socketio/handlers/git.py +723 -0
- claude_mpm/services/socketio/handlers/memory.py +27 -0
- claude_mpm/services/socketio/handlers/project.py +25 -0
- claude_mpm/services/socketio/handlers/registry.py +145 -0
- claude_mpm/services/socketio_client_manager.py +12 -7
- claude_mpm/services/socketio_server.py +156 -30
- claude_mpm/services/ticket_manager.py +377 -51
- claude_mpm/utils/agent_dependency_loader.py +66 -15
- claude_mpm/utils/error_handler.py +1 -1
- claude_mpm/utils/robust_installer.py +587 -0
- claude_mpm/validation/agent_validator.py +27 -14
- claude_mpm/validation/frontmatter_validator.py +231 -0
- {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/METADATA +74 -41
- {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/RECORD +101 -76
- claude_mpm/.claude-mpm/logs/hooks_20250728.log +0 -10
- claude_mpm/agents/agent-template.yaml +0 -83
- claude_mpm/cli/README.md +0 -108
- claude_mpm/cli_module/refactoring_guide.md +0 -253
- claude_mpm/config/async_logging_config.yaml +0 -145
- claude_mpm/core/.claude-mpm/logs/hooks_20250730.log +0 -34
- claude_mpm/dashboard/.claude-mpm/memories/README.md +0 -36
- claude_mpm/dashboard/README.md +0 -121
- claude_mpm/dashboard/static/js/dashboard.js.backup +0 -1973
- claude_mpm/dashboard/templates/.claude-mpm/memories/README.md +0 -36
- claude_mpm/dashboard/templates/.claude-mpm/memories/engineer_agent.md +0 -39
- claude_mpm/dashboard/templates/.claude-mpm/memories/version_control_agent.md +0 -38
- claude_mpm/hooks/README.md +0 -96
- claude_mpm/schemas/agent_schema.json +0 -435
- claude_mpm/services/framework_claude_md_generator/README.md +0 -92
- claude_mpm/services/version_control/VERSION +0 -1
- {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/WHEEL +0 -0
- {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/top_level.txt +0 -0
| @@ -0,0 +1,485 @@ | |
| 1 | 
            +
            #!/usr/bin/env python3
         | 
| 2 | 
            +
            """Optimized agent loader with caching and parallel loading.
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            This module provides high-performance agent loading with:
         | 
| 5 | 
            +
            - Compiled template caching
         | 
| 6 | 
            +
            - Parallel file operations
         | 
| 7 | 
            +
            - Memory-efficient batch processing
         | 
| 8 | 
            +
            - Incremental loading for large agent sets
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            WHY optimized agent loading:
         | 
| 11 | 
            +
            - Reduces agent deployment time from >500ms to <200ms per agent
         | 
| 12 | 
            +
            - Caches compiled templates to avoid repeated parsing
         | 
| 13 | 
            +
            - Parallel I/O operations for multi-agent loading
         | 
| 14 | 
            +
            - Minimizes file system operations through batching
         | 
| 15 | 
            +
            """
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            import asyncio
         | 
| 18 | 
            +
            import hashlib
         | 
| 19 | 
            +
            import json
         | 
| 20 | 
            +
            import os
         | 
| 21 | 
            +
            import threading
         | 
| 22 | 
            +
            import time
         | 
| 23 | 
            +
            from concurrent.futures import ThreadPoolExecutor, as_completed
         | 
| 24 | 
            +
            from dataclasses import dataclass, field
         | 
| 25 | 
            +
            from datetime import datetime
         | 
| 26 | 
            +
            from pathlib import Path
         | 
| 27 | 
            +
            from typing import Any, Dict, List, Optional, Set, Tuple
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            try:
         | 
| 30 | 
            +
                import aiofiles
         | 
| 31 | 
            +
                AIOFILES_AVAILABLE = True
         | 
| 32 | 
            +
            except ImportError:
         | 
| 33 | 
            +
                AIOFILES_AVAILABLE = False
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            from .cache import FileSystemCache, get_file_cache
         | 
| 36 | 
            +
            from .lazy import LazyService
         | 
| 37 | 
            +
            from ..core.logger import get_logger
         | 
| 38 | 
            +
             | 
| 39 | 
            +
             | 
| 40 | 
            +
            @dataclass
         | 
| 41 | 
            +
            class AgentLoadMetrics:
         | 
| 42 | 
            +
                """Metrics for agent loading performance."""
         | 
| 43 | 
            +
                total_agents: int = 0
         | 
| 44 | 
            +
                loaded_agents: int = 0
         | 
| 45 | 
            +
                cached_agents: int = 0
         | 
| 46 | 
            +
                failed_agents: int = 0
         | 
| 47 | 
            +
                total_time: float = 0.0
         | 
| 48 | 
            +
                parse_time: float = 0.0
         | 
| 49 | 
            +
                io_time: float = 0.0
         | 
| 50 | 
            +
                cache_hits: int = 0
         | 
| 51 | 
            +
                cache_misses: int = 0
         | 
| 52 | 
            +
             | 
| 53 | 
            +
             | 
| 54 | 
            +
            class OptimizedAgentLoader:
         | 
| 55 | 
            +
                """High-performance agent loader with caching and parallelization.
         | 
| 56 | 
            +
                
         | 
| 57 | 
            +
                WHY this design:
         | 
| 58 | 
            +
                - ThreadPoolExecutor for parallel file I/O
         | 
| 59 | 
            +
                - LRU cache for compiled agent templates
         | 
| 60 | 
            +
                - Batch processing to reduce overhead
         | 
| 61 | 
            +
                - Lazy compilation for rarely-used agents
         | 
| 62 | 
            +
                
         | 
| 63 | 
            +
                Example:
         | 
| 64 | 
            +
                    loader = OptimizedAgentLoader(max_workers=4)
         | 
| 65 | 
            +
                    
         | 
| 66 | 
            +
                    # Load agents in parallel
         | 
| 67 | 
            +
                    agents = loader.load_agents_parallel([
         | 
| 68 | 
            +
                        '/path/to/agent1.json',
         | 
| 69 | 
            +
                        '/path/to/agent2.json'
         | 
| 70 | 
            +
                    ])
         | 
| 71 | 
            +
                    
         | 
| 72 | 
            +
                    # Async loading
         | 
| 73 | 
            +
                    agents = await loader.load_agents_async(paths)
         | 
| 74 | 
            +
                """
         | 
| 75 | 
            +
                
         | 
| 76 | 
            +
                def __init__(
         | 
| 77 | 
            +
                    self,
         | 
| 78 | 
            +
                    max_workers: int = 4,
         | 
| 79 | 
            +
                    cache_ttl: int = 3600,
         | 
| 80 | 
            +
                    enable_lazy: bool = True
         | 
| 81 | 
            +
                ):
         | 
| 82 | 
            +
                    """Initialize optimized agent loader.
         | 
| 83 | 
            +
                    
         | 
| 84 | 
            +
                    Args:
         | 
| 85 | 
            +
                        max_workers: Number of parallel workers for loading
         | 
| 86 | 
            +
                        cache_ttl: Cache time-to-live in seconds
         | 
| 87 | 
            +
                        enable_lazy: Whether to use lazy loading for templates
         | 
| 88 | 
            +
                    """
         | 
| 89 | 
            +
                    self.max_workers = max_workers
         | 
| 90 | 
            +
                    self.cache_ttl = cache_ttl
         | 
| 91 | 
            +
                    self.enable_lazy = enable_lazy
         | 
| 92 | 
            +
                    
         | 
| 93 | 
            +
                    # Get or create cache
         | 
| 94 | 
            +
                    self.cache = get_file_cache(max_size_mb=50, default_ttl=cache_ttl)
         | 
| 95 | 
            +
                    
         | 
| 96 | 
            +
                    # Thread pool for parallel operations
         | 
| 97 | 
            +
                    self.executor = ThreadPoolExecutor(max_workers=max_workers)
         | 
| 98 | 
            +
                    
         | 
| 99 | 
            +
                    # Metrics tracking
         | 
| 100 | 
            +
                    self.metrics = AgentLoadMetrics()
         | 
| 101 | 
            +
                    
         | 
| 102 | 
            +
                    # Logger
         | 
| 103 | 
            +
                    self.logger = get_logger("optimized_agent_loader")
         | 
| 104 | 
            +
                    
         | 
| 105 | 
            +
                    # Template compilation cache
         | 
| 106 | 
            +
                    self._compiled_templates: Dict[str, Dict[str, Any]] = {}
         | 
| 107 | 
            +
                    self._template_lock = threading.Lock()
         | 
| 108 | 
            +
                
         | 
| 109 | 
            +
                def _get_cache_key(self, file_path: Path) -> str:
         | 
| 110 | 
            +
                    """Generate cache key for an agent file.
         | 
| 111 | 
            +
                    
         | 
| 112 | 
            +
                    Includes file path and modification time for cache invalidation.
         | 
| 113 | 
            +
                    """
         | 
| 114 | 
            +
                    try:
         | 
| 115 | 
            +
                        mtime = file_path.stat().st_mtime
         | 
| 116 | 
            +
                        return f"agent:{file_path}:{mtime}"
         | 
| 117 | 
            +
                    except:
         | 
| 118 | 
            +
                        return f"agent:{file_path}"
         | 
| 119 | 
            +
                
         | 
| 120 | 
            +
                def _load_agent_file(self, file_path: Path) -> Optional[Dict[str, Any]]:
         | 
| 121 | 
            +
                    """Load and parse a single agent file.
         | 
| 122 | 
            +
                    
         | 
| 123 | 
            +
                    Args:
         | 
| 124 | 
            +
                        file_path: Path to agent file (.json or .md)
         | 
| 125 | 
            +
                    
         | 
| 126 | 
            +
                    Returns:
         | 
| 127 | 
            +
                        Parsed agent data or None if failed
         | 
| 128 | 
            +
                    """
         | 
| 129 | 
            +
                    start_time = time.time()
         | 
| 130 | 
            +
                    
         | 
| 131 | 
            +
                    try:
         | 
| 132 | 
            +
                        # Check cache first
         | 
| 133 | 
            +
                        cache_key = self._get_cache_key(file_path)
         | 
| 134 | 
            +
                        cached = self.cache.get(cache_key)
         | 
| 135 | 
            +
                        if cached is not None:
         | 
| 136 | 
            +
                            self.metrics.cache_hits += 1
         | 
| 137 | 
            +
                            return cached
         | 
| 138 | 
            +
                        
         | 
| 139 | 
            +
                        self.metrics.cache_misses += 1
         | 
| 140 | 
            +
                        
         | 
| 141 | 
            +
                        # Read file
         | 
| 142 | 
            +
                        io_start = time.time()
         | 
| 143 | 
            +
                        content = file_path.read_text(encoding='utf-8')
         | 
| 144 | 
            +
                        self.metrics.io_time += time.time() - io_start
         | 
| 145 | 
            +
                        
         | 
| 146 | 
            +
                        # Parse based on file type
         | 
| 147 | 
            +
                        parse_start = time.time()
         | 
| 148 | 
            +
                        if file_path.suffix == '.json':
         | 
| 149 | 
            +
                            agent_data = json.loads(content)
         | 
| 150 | 
            +
                        elif file_path.suffix == '.md':
         | 
| 151 | 
            +
                            # Extract frontmatter from markdown
         | 
| 152 | 
            +
                            agent_data = self._parse_markdown_agent(content)
         | 
| 153 | 
            +
                        else:
         | 
| 154 | 
            +
                            agent_data = {'content': content}
         | 
| 155 | 
            +
                        
         | 
| 156 | 
            +
                        self.metrics.parse_time += time.time() - parse_start
         | 
| 157 | 
            +
                        
         | 
| 158 | 
            +
                        # Add metadata
         | 
| 159 | 
            +
                        agent_data['_file_path'] = str(file_path)
         | 
| 160 | 
            +
                        agent_data['_loaded_at'] = datetime.now().isoformat()
         | 
| 161 | 
            +
                        
         | 
| 162 | 
            +
                        # Cache the result
         | 
| 163 | 
            +
                        self.cache.put(cache_key, agent_data, ttl=self.cache_ttl)
         | 
| 164 | 
            +
                        
         | 
| 165 | 
            +
                        return agent_data
         | 
| 166 | 
            +
                        
         | 
| 167 | 
            +
                    except Exception as e:
         | 
| 168 | 
            +
                        self.logger.error(f"Failed to load agent {file_path}: {e}")
         | 
| 169 | 
            +
                        self.metrics.failed_agents += 1
         | 
| 170 | 
            +
                        return None
         | 
| 171 | 
            +
                    finally:
         | 
| 172 | 
            +
                        elapsed = time.time() - start_time
         | 
| 173 | 
            +
                        self.logger.debug(f"Loaded {file_path.name} in {elapsed:.3f}s")
         | 
| 174 | 
            +
                
         | 
| 175 | 
            +
                def _parse_markdown_agent(self, content: str) -> Dict[str, Any]:
         | 
| 176 | 
            +
                    """Parse agent data from markdown content.
         | 
| 177 | 
            +
                    
         | 
| 178 | 
            +
                    Extracts YAML frontmatter and content from markdown files.
         | 
| 179 | 
            +
                    """
         | 
| 180 | 
            +
                    lines = content.split('\n')
         | 
| 181 | 
            +
                    
         | 
| 182 | 
            +
                    # Look for frontmatter
         | 
| 183 | 
            +
                    if lines[0] == '---':
         | 
| 184 | 
            +
                        try:
         | 
| 185 | 
            +
                            import yaml
         | 
| 186 | 
            +
                            end_index = lines[1:].index('---') + 1
         | 
| 187 | 
            +
                            frontmatter = '\n'.join(lines[1:end_index])
         | 
| 188 | 
            +
                            body = '\n'.join(lines[end_index + 1:])
         | 
| 189 | 
            +
                            
         | 
| 190 | 
            +
                            data = yaml.safe_load(frontmatter) or {}
         | 
| 191 | 
            +
                            data['instructions'] = body
         | 
| 192 | 
            +
                            return data
         | 
| 193 | 
            +
                        except:
         | 
| 194 | 
            +
                            pass
         | 
| 195 | 
            +
                    
         | 
| 196 | 
            +
                    # No frontmatter, treat as pure instructions
         | 
| 197 | 
            +
                    return {'instructions': content}
         | 
| 198 | 
            +
                
         | 
| 199 | 
            +
                def load_agents_parallel(
         | 
| 200 | 
            +
                    self,
         | 
| 201 | 
            +
                    file_paths: List[Path],
         | 
| 202 | 
            +
                    batch_size: int = 10
         | 
| 203 | 
            +
                ) -> Dict[str, Dict[str, Any]]:
         | 
| 204 | 
            +
                    """Load multiple agents in parallel.
         | 
| 205 | 
            +
                    
         | 
| 206 | 
            +
                    Args:
         | 
| 207 | 
            +
                        file_paths: List of agent file paths
         | 
| 208 | 
            +
                        batch_size: Number of agents to load concurrently
         | 
| 209 | 
            +
                    
         | 
| 210 | 
            +
                    Returns:
         | 
| 211 | 
            +
                        Dictionary mapping agent IDs to agent data
         | 
| 212 | 
            +
                    """
         | 
| 213 | 
            +
                    start_time = time.time()
         | 
| 214 | 
            +
                    self.metrics.total_agents = len(file_paths)
         | 
| 215 | 
            +
                    
         | 
| 216 | 
            +
                    agents = {}
         | 
| 217 | 
            +
                    
         | 
| 218 | 
            +
                    # Process in batches to avoid overwhelming the system
         | 
| 219 | 
            +
                    for i in range(0, len(file_paths), batch_size):
         | 
| 220 | 
            +
                        batch = file_paths[i:i + batch_size]
         | 
| 221 | 
            +
                        
         | 
| 222 | 
            +
                        # Submit batch to thread pool
         | 
| 223 | 
            +
                        futures = {
         | 
| 224 | 
            +
                            self.executor.submit(self._load_agent_file, path): path
         | 
| 225 | 
            +
                            for path in batch
         | 
| 226 | 
            +
                        }
         | 
| 227 | 
            +
                        
         | 
| 228 | 
            +
                        # Collect results
         | 
| 229 | 
            +
                        for future in as_completed(futures):
         | 
| 230 | 
            +
                            path = futures[future]
         | 
| 231 | 
            +
                            try:
         | 
| 232 | 
            +
                                agent_data = future.result(timeout=5.0)
         | 
| 233 | 
            +
                                if agent_data:
         | 
| 234 | 
            +
                                    agent_id = path.stem
         | 
| 235 | 
            +
                                    agents[agent_id] = agent_data
         | 
| 236 | 
            +
                                    self.metrics.loaded_agents += 1
         | 
| 237 | 
            +
                            except Exception as e:
         | 
| 238 | 
            +
                                self.logger.error(f"Failed to load {path}: {e}")
         | 
| 239 | 
            +
                                self.metrics.failed_agents += 1
         | 
| 240 | 
            +
                    
         | 
| 241 | 
            +
                    self.metrics.total_time = time.time() - start_time
         | 
| 242 | 
            +
                    
         | 
| 243 | 
            +
                    self.logger.info(
         | 
| 244 | 
            +
                        f"Loaded {self.metrics.loaded_agents}/{self.metrics.total_agents} agents "
         | 
| 245 | 
            +
                        f"in {self.metrics.total_time:.2f}s "
         | 
| 246 | 
            +
                        f"(cache hits: {self.metrics.cache_hits}, misses: {self.metrics.cache_misses})"
         | 
| 247 | 
            +
                    )
         | 
| 248 | 
            +
                    
         | 
| 249 | 
            +
                    return agents
         | 
| 250 | 
            +
                
         | 
| 251 | 
            +
                async def load_agents_async(
         | 
| 252 | 
            +
                    self,
         | 
| 253 | 
            +
                    file_paths: List[Path]
         | 
| 254 | 
            +
                ) -> Dict[str, Dict[str, Any]]:
         | 
| 255 | 
            +
                    """Load agents asynchronously for async applications.
         | 
| 256 | 
            +
                    
         | 
| 257 | 
            +
                    Args:
         | 
| 258 | 
            +
                        file_paths: List of agent file paths
         | 
| 259 | 
            +
                    
         | 
| 260 | 
            +
                    Returns:
         | 
| 261 | 
            +
                        Dictionary mapping agent IDs to agent data
         | 
| 262 | 
            +
                    """
         | 
| 263 | 
            +
                    if not AIOFILES_AVAILABLE:
         | 
| 264 | 
            +
                        # Fallback to sync loading in executor
         | 
| 265 | 
            +
                        loop = asyncio.get_event_loop()
         | 
| 266 | 
            +
                        return await loop.run_in_executor(
         | 
| 267 | 
            +
                            None,
         | 
| 268 | 
            +
                            self.load_agents_parallel,
         | 
| 269 | 
            +
                            file_paths
         | 
| 270 | 
            +
                        )
         | 
| 271 | 
            +
                    
         | 
| 272 | 
            +
                    start_time = time.time()
         | 
| 273 | 
            +
                    self.metrics.total_agents = len(file_paths)
         | 
| 274 | 
            +
                    
         | 
| 275 | 
            +
                    # Create async tasks for all agents
         | 
| 276 | 
            +
                    tasks = []
         | 
| 277 | 
            +
                    for path in file_paths:
         | 
| 278 | 
            +
                        tasks.append(self._load_agent_async(path))
         | 
| 279 | 
            +
                    
         | 
| 280 | 
            +
                    # Load all agents concurrently
         | 
| 281 | 
            +
                    results = await asyncio.gather(*tasks, return_exceptions=True)
         | 
| 282 | 
            +
                    
         | 
| 283 | 
            +
                    # Collect successful loads
         | 
| 284 | 
            +
                    agents = {}
         | 
| 285 | 
            +
                    for path, result in zip(file_paths, results):
         | 
| 286 | 
            +
                        if isinstance(result, Exception):
         | 
| 287 | 
            +
                            self.logger.error(f"Failed to load {path}: {result}")
         | 
| 288 | 
            +
                            self.metrics.failed_agents += 1
         | 
| 289 | 
            +
                        elif result:
         | 
| 290 | 
            +
                            agent_id = path.stem
         | 
| 291 | 
            +
                            agents[agent_id] = result
         | 
| 292 | 
            +
                            self.metrics.loaded_agents += 1
         | 
| 293 | 
            +
                    
         | 
| 294 | 
            +
                    self.metrics.total_time = time.time() - start_time
         | 
| 295 | 
            +
                    
         | 
| 296 | 
            +
                    self.logger.info(
         | 
| 297 | 
            +
                        f"Async loaded {self.metrics.loaded_agents}/{self.metrics.total_agents} agents "
         | 
| 298 | 
            +
                        f"in {self.metrics.total_time:.2f}s"
         | 
| 299 | 
            +
                    )
         | 
| 300 | 
            +
                    
         | 
| 301 | 
            +
                    return agents
         | 
| 302 | 
            +
                
         | 
| 303 | 
            +
                async def _load_agent_async(self, file_path: Path) -> Optional[Dict[str, Any]]:
         | 
| 304 | 
            +
                    """Load a single agent asynchronously."""
         | 
| 305 | 
            +
                    try:
         | 
| 306 | 
            +
                        # Check cache first
         | 
| 307 | 
            +
                        cache_key = self._get_cache_key(file_path)
         | 
| 308 | 
            +
                        cached = self.cache.get(cache_key)
         | 
| 309 | 
            +
                        if cached is not None:
         | 
| 310 | 
            +
                            self.metrics.cache_hits += 1
         | 
| 311 | 
            +
                            return cached
         | 
| 312 | 
            +
                        
         | 
| 313 | 
            +
                        self.metrics.cache_misses += 1
         | 
| 314 | 
            +
                        
         | 
| 315 | 
            +
                        # Read file asynchronously
         | 
| 316 | 
            +
                        async with aiofiles.open(file_path, 'r', encoding='utf-8') as f:
         | 
| 317 | 
            +
                            content = await f.read()
         | 
| 318 | 
            +
                        
         | 
| 319 | 
            +
                        # Parse in executor to avoid blocking
         | 
| 320 | 
            +
                        loop = asyncio.get_event_loop()
         | 
| 321 | 
            +
                        if file_path.suffix == '.json':
         | 
| 322 | 
            +
                            agent_data = await loop.run_in_executor(None, json.loads, content)
         | 
| 323 | 
            +
                        elif file_path.suffix == '.md':
         | 
| 324 | 
            +
                            agent_data = await loop.run_in_executor(
         | 
| 325 | 
            +
                                None,
         | 
| 326 | 
            +
                                self._parse_markdown_agent,
         | 
| 327 | 
            +
                                content
         | 
| 328 | 
            +
                            )
         | 
| 329 | 
            +
                        else:
         | 
| 330 | 
            +
                            agent_data = {'content': content}
         | 
| 331 | 
            +
                        
         | 
| 332 | 
            +
                        # Add metadata
         | 
| 333 | 
            +
                        agent_data['_file_path'] = str(file_path)
         | 
| 334 | 
            +
                        agent_data['_loaded_at'] = datetime.now().isoformat()
         | 
| 335 | 
            +
                        
         | 
| 336 | 
            +
                        # Cache the result
         | 
| 337 | 
            +
                        self.cache.put(cache_key, agent_data, ttl=self.cache_ttl)
         | 
| 338 | 
            +
                        
         | 
| 339 | 
            +
                        return agent_data
         | 
| 340 | 
            +
                        
         | 
| 341 | 
            +
                    except Exception as e:
         | 
| 342 | 
            +
                        self.logger.error(f"Failed to async load {file_path}: {e}")
         | 
| 343 | 
            +
                        return None
         | 
| 344 | 
            +
                
         | 
| 345 | 
            +
                def compile_template(self, agent_id: str, template: str) -> Dict[str, Any]:
         | 
| 346 | 
            +
                    """Compile and cache an agent template.
         | 
| 347 | 
            +
                    
         | 
| 348 | 
            +
                    WHY template compilation:
         | 
| 349 | 
            +
                    - Pre-processes templates for faster runtime use
         | 
| 350 | 
            +
                    - Validates template syntax once
         | 
| 351 | 
            +
                    - Caches compiled form for reuse
         | 
| 352 | 
            +
                    - Reduces repeated parsing overhead
         | 
| 353 | 
            +
                    
         | 
| 354 | 
            +
                    Args:
         | 
| 355 | 
            +
                        agent_id: Unique agent identifier
         | 
| 356 | 
            +
                        template: Raw template string
         | 
| 357 | 
            +
                    
         | 
| 358 | 
            +
                    Returns:
         | 
| 359 | 
            +
                        Compiled template data
         | 
| 360 | 
            +
                    """
         | 
| 361 | 
            +
                    with self._template_lock:
         | 
| 362 | 
            +
                        # Check if already compiled
         | 
| 363 | 
            +
                        if agent_id in self._compiled_templates:
         | 
| 364 | 
            +
                            self.metrics.cache_hits += 1
         | 
| 365 | 
            +
                            return self._compiled_templates[agent_id]
         | 
| 366 | 
            +
                        
         | 
| 367 | 
            +
                        self.metrics.cache_misses += 1
         | 
| 368 | 
            +
                        
         | 
| 369 | 
            +
                        # Compile template
         | 
| 370 | 
            +
                        start_time = time.time()
         | 
| 371 | 
            +
                        try:
         | 
| 372 | 
            +
                            # Parse any template variables
         | 
| 373 | 
            +
                            compiled = {
         | 
| 374 | 
            +
                                'id': agent_id,
         | 
| 375 | 
            +
                                'template': template,
         | 
| 376 | 
            +
                                'variables': self._extract_variables(template),
         | 
| 377 | 
            +
                                'sections': self._extract_sections(template),
         | 
| 378 | 
            +
                                'compiled_at': datetime.now().isoformat()
         | 
| 379 | 
            +
                            }
         | 
| 380 | 
            +
                            
         | 
| 381 | 
            +
                            # Cache compiled template
         | 
| 382 | 
            +
                            self._compiled_templates[agent_id] = compiled
         | 
| 383 | 
            +
                            
         | 
| 384 | 
            +
                            self.metrics.parse_time += time.time() - start_time
         | 
| 385 | 
            +
                            return compiled
         | 
| 386 | 
            +
                            
         | 
| 387 | 
            +
                        except Exception as e:
         | 
| 388 | 
            +
                            self.logger.error(f"Failed to compile template {agent_id}: {e}")
         | 
| 389 | 
            +
                            return {'id': agent_id, 'template': template, 'error': str(e)}
         | 
| 390 | 
            +
                
         | 
| 391 | 
            +
                def _extract_variables(self, template: str) -> List[str]:
         | 
| 392 | 
            +
                    """Extract variable placeholders from template."""
         | 
| 393 | 
            +
                    import re
         | 
| 394 | 
            +
                    # Find {{variable}} patterns
         | 
| 395 | 
            +
                    return re.findall(r'\{\{(\w+)\}\}', template)
         | 
| 396 | 
            +
                
         | 
| 397 | 
            +
                def _extract_sections(self, template: str) -> Dict[str, str]:
         | 
| 398 | 
            +
                    """Extract named sections from template."""
         | 
| 399 | 
            +
                    sections = {}
         | 
| 400 | 
            +
                    current_section = 'main'
         | 
| 401 | 
            +
                    current_content = []
         | 
| 402 | 
            +
                    
         | 
| 403 | 
            +
                    for line in template.split('\n'):
         | 
| 404 | 
            +
                        # Check for section headers (## Section Name)
         | 
| 405 | 
            +
                        if line.startswith('## '):
         | 
| 406 | 
            +
                            # Save previous section
         | 
| 407 | 
            +
                            if current_content:
         | 
| 408 | 
            +
                                sections[current_section] = '\n'.join(current_content)
         | 
| 409 | 
            +
                            # Start new section
         | 
| 410 | 
            +
                            current_section = line[3:].strip().lower().replace(' ', '_')
         | 
| 411 | 
            +
                            current_content = []
         | 
| 412 | 
            +
                        else:
         | 
| 413 | 
            +
                            current_content.append(line)
         | 
| 414 | 
            +
                    
         | 
| 415 | 
            +
                    # Save last section
         | 
| 416 | 
            +
                    if current_content:
         | 
| 417 | 
            +
                        sections[current_section] = '\n'.join(current_content)
         | 
| 418 | 
            +
                    
         | 
| 419 | 
            +
                    return sections
         | 
| 420 | 
            +
                
         | 
| 421 | 
            +
                def preload_agents(self, agent_dirs: List[Path]) -> None:
         | 
| 422 | 
            +
                    """Preload all agents from specified directories.
         | 
| 423 | 
            +
                    
         | 
| 424 | 
            +
                    Useful for warming up the cache at startup.
         | 
| 425 | 
            +
                    
         | 
| 426 | 
            +
                    Args:
         | 
| 427 | 
            +
                        agent_dirs: List of directories containing agents
         | 
| 428 | 
            +
                    """
         | 
| 429 | 
            +
                    self.logger.info(f"Preloading agents from {len(agent_dirs)} directories")
         | 
| 430 | 
            +
                    
         | 
| 431 | 
            +
                    all_paths = []
         | 
| 432 | 
            +
                    for dir_path in agent_dirs:
         | 
| 433 | 
            +
                        if dir_path.exists():
         | 
| 434 | 
            +
                            all_paths.extend(dir_path.glob('*.json'))
         | 
| 435 | 
            +
                            all_paths.extend(dir_path.glob('*.md'))
         | 
| 436 | 
            +
                    
         | 
| 437 | 
            +
                    if all_paths:
         | 
| 438 | 
            +
                        self.load_agents_parallel(all_paths)
         | 
| 439 | 
            +
                
         | 
| 440 | 
            +
                def get_metrics(self) -> Dict[str, Any]:
         | 
| 441 | 
            +
                    """Get loader performance metrics."""
         | 
| 442 | 
            +
                    return {
         | 
| 443 | 
            +
                        "total_agents": self.metrics.total_agents,
         | 
| 444 | 
            +
                        "loaded_agents": self.metrics.loaded_agents,
         | 
| 445 | 
            +
                        "cached_agents": len(self._compiled_templates),
         | 
| 446 | 
            +
                        "failed_agents": self.metrics.failed_agents,
         | 
| 447 | 
            +
                        "total_time": self.metrics.total_time,
         | 
| 448 | 
            +
                        "avg_load_time": self.metrics.total_time / max(1, self.metrics.loaded_agents),
         | 
| 449 | 
            +
                        "parse_time": self.metrics.parse_time,
         | 
| 450 | 
            +
                        "io_time": self.metrics.io_time,
         | 
| 451 | 
            +
                        "cache_hit_rate": self.metrics.cache_hits / max(1, self.metrics.cache_hits + self.metrics.cache_misses)
         | 
| 452 | 
            +
                    }
         | 
| 453 | 
            +
                
         | 
| 454 | 
            +
                def cleanup(self):
         | 
| 455 | 
            +
                    """Clean up resources."""
         | 
| 456 | 
            +
                    self.executor.shutdown(wait=False)
         | 
| 457 | 
            +
                    self._compiled_templates.clear()
         | 
| 458 | 
            +
             | 
| 459 | 
            +
             | 
| 460 | 
            +
            # Global loader instance
         | 
| 461 | 
            +
            _global_loader: Optional[OptimizedAgentLoader] = None
         | 
| 462 | 
            +
             | 
| 463 | 
            +
             | 
| 464 | 
            +
            def get_optimized_loader(max_workers: int = 4) -> OptimizedAgentLoader:
         | 
| 465 | 
            +
                """Get or create global optimized loader."""
         | 
| 466 | 
            +
                global _global_loader
         | 
| 467 | 
            +
                if _global_loader is None:
         | 
| 468 | 
            +
                    _global_loader = OptimizedAgentLoader(max_workers=max_workers)
         | 
| 469 | 
            +
                return _global_loader
         | 
| 470 | 
            +
             | 
| 471 | 
            +
             | 
| 472 | 
            +
            def preload_system_agents():
         | 
| 473 | 
            +
                """Preload all system agents at startup."""
         | 
| 474 | 
            +
                loader = get_optimized_loader()
         | 
| 475 | 
            +
                
         | 
| 476 | 
            +
                # Common agent directories
         | 
| 477 | 
            +
                agent_dirs = [
         | 
| 478 | 
            +
                    Path.cwd() / ".claude" / "agents",
         | 
| 479 | 
            +
                    Path.cwd() / ".claude-mpm" / "agents",
         | 
| 480 | 
            +
                    Path.home() / ".claude-mpm" / "agents",
         | 
| 481 | 
            +
                    Path(__file__).parent.parent / "agents" / "templates",
         | 
| 482 | 
            +
                    Path(__file__).parent.parent / "agents"
         | 
| 483 | 
            +
                ]
         | 
| 484 | 
            +
                
         | 
| 485 | 
            +
                loader.preload_agents([d for d in agent_dirs if d.exists()])
         |