claude-mpm 3.1.3__py3-none-any.whl → 3.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/__init__.py +3 -3
 - claude_mpm/__main__.py +0 -17
 - claude_mpm/agents/INSTRUCTIONS.md +81 -18
 - claude_mpm/agents/backups/INSTRUCTIONS.md +238 -0
 - claude_mpm/agents/base_agent.json +1 -1
 - claude_mpm/agents/templates/pm.json +25 -0
 - claude_mpm/agents/templates/research.json +2 -1
 - claude_mpm/cli/__init__.py +19 -23
 - claude_mpm/cli/commands/__init__.py +3 -1
 - claude_mpm/cli/commands/agents.py +7 -18
 - claude_mpm/cli/commands/info.py +5 -10
 - claude_mpm/cli/commands/memory.py +232 -0
 - claude_mpm/cli/commands/run.py +501 -28
 - claude_mpm/cli/commands/tickets.py +10 -17
 - claude_mpm/cli/commands/ui.py +15 -37
 - claude_mpm/cli/parser.py +91 -1
 - claude_mpm/cli/utils.py +9 -28
 - claude_mpm/config/socketio_config.py +256 -0
 - claude_mpm/constants.py +9 -0
 - claude_mpm/core/__init__.py +2 -2
 - claude_mpm/core/agent_registry.py +4 -4
 - claude_mpm/core/claude_runner.py +919 -0
 - claude_mpm/core/config.py +21 -1
 - claude_mpm/core/factories.py +1 -1
 - claude_mpm/core/hook_manager.py +196 -0
 - claude_mpm/core/pm_hook_interceptor.py +205 -0
 - claude_mpm/core/service_registry.py +1 -1
 - claude_mpm/core/simple_runner.py +323 -33
 - claude_mpm/core/socketio_pool.py +582 -0
 - claude_mpm/core/websocket_handler.py +233 -0
 - claude_mpm/deployment_paths.py +261 -0
 - claude_mpm/hooks/builtin/memory_hooks_example.py +67 -0
 - claude_mpm/hooks/claude_hooks/hook_handler.py +667 -679
 - claude_mpm/hooks/claude_hooks/hook_wrapper.sh +9 -4
 - claude_mpm/hooks/memory_integration_hook.py +312 -0
 - claude_mpm/models/__init__.py +9 -91
 - claude_mpm/orchestration/__init__.py +1 -1
 - claude_mpm/scripts/claude-mpm-socketio +32 -0
 - claude_mpm/scripts/claude_mpm_monitor.html +567 -0
 - claude_mpm/scripts/install_socketio_server.py +407 -0
 - claude_mpm/scripts/launch_monitor.py +132 -0
 - claude_mpm/scripts/manage_version.py +479 -0
 - claude_mpm/scripts/socketio_daemon.py +181 -0
 - claude_mpm/scripts/socketio_server_manager.py +428 -0
 - claude_mpm/services/__init__.py +5 -0
 - claude_mpm/services/agent_lifecycle_manager.py +76 -25
 - claude_mpm/services/agent_memory_manager.py +684 -0
 - claude_mpm/services/agent_modification_tracker.py +98 -17
 - claude_mpm/services/agent_persistence_service.py +33 -13
 - claude_mpm/services/agent_registry.py +82 -43
 - claude_mpm/services/hook_service.py +362 -0
 - claude_mpm/services/socketio_client_manager.py +474 -0
 - claude_mpm/services/socketio_server.py +698 -0
 - claude_mpm/services/standalone_socketio_server.py +631 -0
 - claude_mpm/services/ticket_manager.py +4 -5
 - claude_mpm/services/{ticket_manager_dependency_injection.py → ticket_manager_di.py} +12 -39
 - claude_mpm/services/{legacy_ticketing_service.py → ticketing_service_original.py} +9 -16
 - claude_mpm/services/version_control/semantic_versioning.py +9 -10
 - claude_mpm/services/websocket_server.py +376 -0
 - claude_mpm/utils/dependency_manager.py +211 -0
 - claude_mpm/utils/import_migration_example.py +80 -0
 - claude_mpm/utils/path_operations.py +0 -20
 - claude_mpm/web/open_dashboard.py +34 -0
 - {claude_mpm-3.1.3.dist-info → claude_mpm-3.2.1.dist-info}/METADATA +20 -9
 - {claude_mpm-3.1.3.dist-info → claude_mpm-3.2.1.dist-info}/RECORD +70 -50
 - claude_mpm-3.2.1.dist-info/entry_points.txt +7 -0
 - claude_mpm/cli_old.py +0 -728
 - claude_mpm/models/common.py +0 -41
 - claude_mpm/models/lifecycle.py +0 -97
 - claude_mpm/models/modification.py +0 -126
 - claude_mpm/models/persistence.py +0 -57
 - claude_mpm/models/registry.py +0 -91
 - claude_mpm/security/__init__.py +0 -8
 - claude_mpm/security/bash_validator.py +0 -393
 - claude_mpm-3.1.3.dist-info/entry_points.txt +0 -4
 - /claude_mpm/{cli_enhancements.py → experimental/cli_enhancements.py} +0 -0
 - {claude_mpm-3.1.3.dist-info → claude_mpm-3.2.1.dist-info}/WHEEL +0 -0
 - {claude_mpm-3.1.3.dist-info → claude_mpm-3.2.1.dist-info}/licenses/LICENSE +0 -0
 - {claude_mpm-3.1.3.dist-info → claude_mpm-3.2.1.dist-info}/top_level.txt +0 -0
 
| 
         @@ -0,0 +1,684 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #!/usr/bin/env python3
         
     | 
| 
      
 2 
     | 
    
         
            +
            """
         
     | 
| 
      
 3 
     | 
    
         
            +
            Agent Memory Manager Service
         
     | 
| 
      
 4 
     | 
    
         
            +
            ===========================
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            Manages agent memory files with size limits and validation.
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            This service provides:
         
     | 
| 
      
 9 
     | 
    
         
            +
            - Memory file operations (load, save, validate)
         
     | 
| 
      
 10 
     | 
    
         
            +
            - Size limit enforcement (8KB default)
         
     | 
| 
      
 11 
     | 
    
         
            +
            - Auto-truncation when limits exceeded
         
     | 
| 
      
 12 
     | 
    
         
            +
            - Default memory template creation
         
     | 
| 
      
 13 
     | 
    
         
            +
            - Section management with item limits
         
     | 
| 
      
 14 
     | 
    
         
            +
            - Timestamp updates
         
     | 
| 
      
 15 
     | 
    
         
            +
            - Directory initialization with README
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
            Memory files are stored in .claude-mpm/memories/ directory
         
     | 
| 
      
 18 
     | 
    
         
            +
            following the naming convention: {agent_id}_agent.md
         
     | 
| 
      
 19 
     | 
    
         
            +
            """
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            from pathlib import Path
         
     | 
| 
      
 22 
     | 
    
         
            +
            from typing import Dict, List, Optional, Any
         
     | 
| 
      
 23 
     | 
    
         
            +
            from datetime import datetime
         
     | 
| 
      
 24 
     | 
    
         
            +
            import re
         
     | 
| 
      
 25 
     | 
    
         
            +
            import logging
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            from claude_mpm.core import LoggerMixin
         
     | 
| 
      
 28 
     | 
    
         
            +
            from claude_mpm.core.config import Config
         
     | 
| 
      
 29 
     | 
    
         
            +
            from claude_mpm.utils.paths import PathResolver
         
     | 
| 
      
 30 
     | 
    
         
            +
            from claude_mpm.services.websocket_server import get_websocket_server
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
            class AgentMemoryManager(LoggerMixin):
         
     | 
| 
      
 34 
     | 
    
         
            +
                """Manages agent memory files with size limits and validation.
         
     | 
| 
      
 35 
     | 
    
         
            +
                
         
     | 
| 
      
 36 
     | 
    
         
            +
                WHY: Agents need to accumulate project-specific knowledge over time to become
         
     | 
| 
      
 37 
     | 
    
         
            +
                more effective. This service manages persistent memory files that agents can
         
     | 
| 
      
 38 
     | 
    
         
            +
                read before tasks and update with new learnings.
         
     | 
| 
      
 39 
     | 
    
         
            +
                
         
     | 
| 
      
 40 
     | 
    
         
            +
                DESIGN DECISION: Memory files are stored in .claude-mpm/memories/ (not project root)
         
     | 
| 
      
 41 
     | 
    
         
            +
                to keep them organized and separate from other project files. Files follow a
         
     | 
| 
      
 42 
     | 
    
         
            +
                standardized markdown format with enforced size limits to prevent unbounded growth.
         
     | 
| 
      
 43 
     | 
    
         
            +
                
         
     | 
| 
      
 44 
     | 
    
         
            +
                The 8KB limit (~2000 tokens) balances comprehensive knowledge storage with
         
     | 
| 
      
 45 
     | 
    
         
            +
                reasonable context size for agent prompts.
         
     | 
| 
      
 46 
     | 
    
         
            +
                """
         
     | 
| 
      
 47 
     | 
    
         
            +
                
         
     | 
| 
      
 48 
     | 
    
         
            +
                # Default limits - will be overridden by configuration
         
     | 
| 
      
 49 
     | 
    
         
            +
                DEFAULT_MEMORY_LIMITS = {
         
     | 
| 
      
 50 
     | 
    
         
            +
                    'max_file_size_kb': 8,
         
     | 
| 
      
 51 
     | 
    
         
            +
                    'max_sections': 10,
         
     | 
| 
      
 52 
     | 
    
         
            +
                    'max_items_per_section': 15,
         
     | 
| 
      
 53 
     | 
    
         
            +
                    'max_line_length': 120
         
     | 
| 
      
 54 
     | 
    
         
            +
                }
         
     | 
| 
      
 55 
     | 
    
         
            +
                
         
     | 
| 
      
 56 
     | 
    
         
            +
                REQUIRED_SECTIONS = [
         
     | 
| 
      
 57 
     | 
    
         
            +
                    'Project Architecture',
         
     | 
| 
      
 58 
     | 
    
         
            +
                    'Implementation Guidelines', 
         
     | 
| 
      
 59 
     | 
    
         
            +
                    'Common Mistakes to Avoid',
         
     | 
| 
      
 60 
     | 
    
         
            +
                    'Current Technical Context'
         
     | 
| 
      
 61 
     | 
    
         
            +
                ]
         
     | 
| 
      
 62 
     | 
    
         
            +
                
         
     | 
| 
      
 63 
     | 
    
         
            +
                def __init__(self, config: Optional[Config] = None):
         
     | 
| 
      
 64 
     | 
    
         
            +
                    """Initialize the memory manager.
         
     | 
| 
      
 65 
     | 
    
         
            +
                    
         
     | 
| 
      
 66 
     | 
    
         
            +
                    Sets up the memories directory and ensures it exists with proper README.
         
     | 
| 
      
 67 
     | 
    
         
            +
                    
         
     | 
| 
      
 68 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 69 
     | 
    
         
            +
                        config: Optional Config object. If not provided, will create default Config.
         
     | 
| 
      
 70 
     | 
    
         
            +
                    """
         
     | 
| 
      
 71 
     | 
    
         
            +
                    super().__init__()
         
     | 
| 
      
 72 
     | 
    
         
            +
                    self.config = config or Config()
         
     | 
| 
      
 73 
     | 
    
         
            +
                    self.project_root = PathResolver.get_project_root()
         
     | 
| 
      
 74 
     | 
    
         
            +
                    self.memories_dir = self.project_root / ".claude-mpm" / "memories"
         
     | 
| 
      
 75 
     | 
    
         
            +
                    self._ensure_memories_directory()
         
     | 
| 
      
 76 
     | 
    
         
            +
                    
         
     | 
| 
      
 77 
     | 
    
         
            +
                    # Initialize memory limits from configuration
         
     | 
| 
      
 78 
     | 
    
         
            +
                    self._init_memory_limits()
         
     | 
| 
      
 79 
     | 
    
         
            +
                
         
     | 
| 
      
 80 
     | 
    
         
            +
                def _init_memory_limits(self):
         
     | 
| 
      
 81 
     | 
    
         
            +
                    """Initialize memory limits from configuration.
         
     | 
| 
      
 82 
     | 
    
         
            +
                    
         
     | 
| 
      
 83 
     | 
    
         
            +
                    WHY: Allows configuration-driven memory limits instead of hardcoded values.
         
     | 
| 
      
 84 
     | 
    
         
            +
                    Supports agent-specific overrides for different memory requirements.
         
     | 
| 
      
 85 
     | 
    
         
            +
                    """
         
     | 
| 
      
 86 
     | 
    
         
            +
                    # Check if memory system is enabled
         
     | 
| 
      
 87 
     | 
    
         
            +
                    self.memory_enabled = self.config.get('memory.enabled', True)
         
     | 
| 
      
 88 
     | 
    
         
            +
                    self.auto_learning = self.config.get('memory.auto_learning', False)
         
     | 
| 
      
 89 
     | 
    
         
            +
                    
         
     | 
| 
      
 90 
     | 
    
         
            +
                    # Load default limits from configuration
         
     | 
| 
      
 91 
     | 
    
         
            +
                    config_limits = self.config.get('memory.limits', {})
         
     | 
| 
      
 92 
     | 
    
         
            +
                    self.memory_limits = {
         
     | 
| 
      
 93 
     | 
    
         
            +
                        'max_file_size_kb': config_limits.get('default_size_kb', 
         
     | 
| 
      
 94 
     | 
    
         
            +
                                                              self.DEFAULT_MEMORY_LIMITS['max_file_size_kb']),
         
     | 
| 
      
 95 
     | 
    
         
            +
                        'max_sections': config_limits.get('max_sections', 
         
     | 
| 
      
 96 
     | 
    
         
            +
                                                        self.DEFAULT_MEMORY_LIMITS['max_sections']),
         
     | 
| 
      
 97 
     | 
    
         
            +
                        'max_items_per_section': config_limits.get('max_items_per_section', 
         
     | 
| 
      
 98 
     | 
    
         
            +
                                                                  self.DEFAULT_MEMORY_LIMITS['max_items_per_section']),
         
     | 
| 
      
 99 
     | 
    
         
            +
                        'max_line_length': config_limits.get('max_line_length', 
         
     | 
| 
      
 100 
     | 
    
         
            +
                                                           self.DEFAULT_MEMORY_LIMITS['max_line_length'])
         
     | 
| 
      
 101 
     | 
    
         
            +
                    }
         
     | 
| 
      
 102 
     | 
    
         
            +
                    
         
     | 
| 
      
 103 
     | 
    
         
            +
                    # Load agent-specific overrides
         
     | 
| 
      
 104 
     | 
    
         
            +
                    self.agent_overrides = self.config.get('memory.agent_overrides', {})
         
     | 
| 
      
 105 
     | 
    
         
            +
                
         
     | 
| 
      
 106 
     | 
    
         
            +
                def _get_agent_limits(self, agent_id: str) -> Dict[str, Any]:
         
     | 
| 
      
 107 
     | 
    
         
            +
                    """Get memory limits for specific agent, including overrides.
         
     | 
| 
      
 108 
     | 
    
         
            +
                    
         
     | 
| 
      
 109 
     | 
    
         
            +
                    WHY: Different agents may need different memory capacities. Research agents
         
     | 
| 
      
 110 
     | 
    
         
            +
                    might need larger memory for comprehensive findings, while simple agents
         
     | 
| 
      
 111 
     | 
    
         
            +
                    can work with smaller limits.
         
     | 
| 
      
 112 
     | 
    
         
            +
                    
         
     | 
| 
      
 113 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 114 
     | 
    
         
            +
                        agent_id: The agent identifier
         
     | 
| 
      
 115 
     | 
    
         
            +
                        
         
     | 
| 
      
 116 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 117 
     | 
    
         
            +
                        Dict containing the effective limits for this agent
         
     | 
| 
      
 118 
     | 
    
         
            +
                    """
         
     | 
| 
      
 119 
     | 
    
         
            +
                    # Start with default limits
         
     | 
| 
      
 120 
     | 
    
         
            +
                    limits = self.memory_limits.copy()
         
     | 
| 
      
 121 
     | 
    
         
            +
                    
         
     | 
| 
      
 122 
     | 
    
         
            +
                    # Apply agent-specific overrides if they exist
         
     | 
| 
      
 123 
     | 
    
         
            +
                    if agent_id in self.agent_overrides:
         
     | 
| 
      
 124 
     | 
    
         
            +
                        overrides = self.agent_overrides[agent_id]
         
     | 
| 
      
 125 
     | 
    
         
            +
                        if 'size_kb' in overrides:
         
     | 
| 
      
 126 
     | 
    
         
            +
                            limits['max_file_size_kb'] = overrides['size_kb']
         
     | 
| 
      
 127 
     | 
    
         
            +
                    
         
     | 
| 
      
 128 
     | 
    
         
            +
                    return limits
         
     | 
| 
      
 129 
     | 
    
         
            +
                
         
     | 
| 
      
 130 
     | 
    
         
            +
                def _get_agent_auto_learning(self, agent_id: str) -> bool:
         
     | 
| 
      
 131 
     | 
    
         
            +
                    """Check if auto-learning is enabled for specific agent.
         
     | 
| 
      
 132 
     | 
    
         
            +
                    
         
     | 
| 
      
 133 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 134 
     | 
    
         
            +
                        agent_id: The agent identifier
         
     | 
| 
      
 135 
     | 
    
         
            +
                        
         
     | 
| 
      
 136 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 137 
     | 
    
         
            +
                        bool: True if auto-learning is enabled for this agent
         
     | 
| 
      
 138 
     | 
    
         
            +
                    """
         
     | 
| 
      
 139 
     | 
    
         
            +
                    # Check agent-specific override first
         
     | 
| 
      
 140 
     | 
    
         
            +
                    if agent_id in self.agent_overrides:
         
     | 
| 
      
 141 
     | 
    
         
            +
                        return self.agent_overrides[agent_id].get('auto_learning', self.auto_learning)
         
     | 
| 
      
 142 
     | 
    
         
            +
                    
         
     | 
| 
      
 143 
     | 
    
         
            +
                    # Fall back to global setting
         
     | 
| 
      
 144 
     | 
    
         
            +
                    return self.auto_learning
         
     | 
| 
      
 145 
     | 
    
         
            +
                
         
     | 
| 
      
 146 
     | 
    
         
            +
                def load_agent_memory(self, agent_id: str) -> str:
         
     | 
| 
      
 147 
     | 
    
         
            +
                    """Load agent memory file content.
         
     | 
| 
      
 148 
     | 
    
         
            +
                    
         
     | 
| 
      
 149 
     | 
    
         
            +
                    WHY: Agents need to read their accumulated knowledge before starting tasks
         
     | 
| 
      
 150 
     | 
    
         
            +
                    to apply learned patterns and avoid repeated mistakes.
         
     | 
| 
      
 151 
     | 
    
         
            +
                    
         
     | 
| 
      
 152 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 153 
     | 
    
         
            +
                        agent_id: The agent identifier (e.g., 'research', 'engineer')
         
     | 
| 
      
 154 
     | 
    
         
            +
                        
         
     | 
| 
      
 155 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 156 
     | 
    
         
            +
                        str: The memory file content, creating default if doesn't exist
         
     | 
| 
      
 157 
     | 
    
         
            +
                    """
         
     | 
| 
      
 158 
     | 
    
         
            +
                    memory_file = self.memories_dir / f"{agent_id}_agent.md"
         
     | 
| 
      
 159 
     | 
    
         
            +
                    
         
     | 
| 
      
 160 
     | 
    
         
            +
                    if not memory_file.exists():
         
     | 
| 
      
 161 
     | 
    
         
            +
                        self.logger.info(f"Creating default memory for agent: {agent_id}")
         
     | 
| 
      
 162 
     | 
    
         
            +
                        return self._create_default_memory(agent_id)
         
     | 
| 
      
 163 
     | 
    
         
            +
                    
         
     | 
| 
      
 164 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 165 
     | 
    
         
            +
                        content = memory_file.read_text(encoding='utf-8')
         
     | 
| 
      
 166 
     | 
    
         
            +
                        
         
     | 
| 
      
 167 
     | 
    
         
            +
                        # Emit WebSocket event for memory loaded
         
     | 
| 
      
 168 
     | 
    
         
            +
                        try:
         
     | 
| 
      
 169 
     | 
    
         
            +
                            ws_server = get_websocket_server()
         
     | 
| 
      
 170 
     | 
    
         
            +
                            file_size = len(content.encode('utf-8'))
         
     | 
| 
      
 171 
     | 
    
         
            +
                            # Count sections by looking for lines starting with ##
         
     | 
| 
      
 172 
     | 
    
         
            +
                            sections_count = sum(1 for line in content.split('\n') if line.startswith('## '))
         
     | 
| 
      
 173 
     | 
    
         
            +
                            ws_server.memory_loaded(agent_id, file_size, sections_count)
         
     | 
| 
      
 174 
     | 
    
         
            +
                        except Exception as ws_error:
         
     | 
| 
      
 175 
     | 
    
         
            +
                            self.logger.debug(f"WebSocket notification failed: {ws_error}")
         
     | 
| 
      
 176 
     | 
    
         
            +
                        
         
     | 
| 
      
 177 
     | 
    
         
            +
                        return self._validate_and_repair(content, agent_id)
         
     | 
| 
      
 178 
     | 
    
         
            +
                    except Exception as e:
         
     | 
| 
      
 179 
     | 
    
         
            +
                        self.logger.error(f"Error reading memory file for {agent_id}: {e}")
         
     | 
| 
      
 180 
     | 
    
         
            +
                        # Return default memory on error - never fail
         
     | 
| 
      
 181 
     | 
    
         
            +
                        return self._create_default_memory(agent_id)
         
     | 
| 
      
 182 
     | 
    
         
            +
                
         
     | 
| 
      
 183 
     | 
    
         
            +
                def update_agent_memory(self, agent_id: str, section: str, new_item: str) -> bool:
         
     | 
| 
      
 184 
     | 
    
         
            +
                    """Add new learning item to specified section.
         
     | 
| 
      
 185 
     | 
    
         
            +
                    
         
     | 
| 
      
 186 
     | 
    
         
            +
                    WHY: Agents discover new patterns and insights during task execution that
         
     | 
| 
      
 187 
     | 
    
         
            +
                    should be preserved for future tasks. This method adds new learnings while
         
     | 
| 
      
 188 
     | 
    
         
            +
                    enforcing size limits to prevent unbounded growth.
         
     | 
| 
      
 189 
     | 
    
         
            +
                    
         
     | 
| 
      
 190 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 191 
     | 
    
         
            +
                        agent_id: The agent identifier
         
     | 
| 
      
 192 
     | 
    
         
            +
                        section: The section name to add the item to
         
     | 
| 
      
 193 
     | 
    
         
            +
                        new_item: The learning item to add
         
     | 
| 
      
 194 
     | 
    
         
            +
                        
         
     | 
| 
      
 195 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 196 
     | 
    
         
            +
                        bool: True if update succeeded, False otherwise
         
     | 
| 
      
 197 
     | 
    
         
            +
                    """
         
     | 
| 
      
 198 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 199 
     | 
    
         
            +
                        current_memory = self.load_agent_memory(agent_id)
         
     | 
| 
      
 200 
     | 
    
         
            +
                        updated_memory = self._add_item_to_section(current_memory, section, new_item)
         
     | 
| 
      
 201 
     | 
    
         
            +
                        
         
     | 
| 
      
 202 
     | 
    
         
            +
                        # Enforce limits
         
     | 
| 
      
 203 
     | 
    
         
            +
                        if self._exceeds_limits(updated_memory, agent_id):
         
     | 
| 
      
 204 
     | 
    
         
            +
                            self.logger.debug(f"Memory for {agent_id} exceeds limits, truncating")
         
     | 
| 
      
 205 
     | 
    
         
            +
                            updated_memory = self._truncate_to_limits(updated_memory, agent_id)
         
     | 
| 
      
 206 
     | 
    
         
            +
                        
         
     | 
| 
      
 207 
     | 
    
         
            +
                        # Save with timestamp
         
     | 
| 
      
 208 
     | 
    
         
            +
                        return self._save_memory_file(agent_id, updated_memory)
         
     | 
| 
      
 209 
     | 
    
         
            +
                    except Exception as e:
         
     | 
| 
      
 210 
     | 
    
         
            +
                        self.logger.error(f"Error updating memory for {agent_id}: {e}")
         
     | 
| 
      
 211 
     | 
    
         
            +
                        # Never fail on memory errors
         
     | 
| 
      
 212 
     | 
    
         
            +
                        return False
         
     | 
| 
      
 213 
     | 
    
         
            +
                
         
     | 
| 
      
 214 
     | 
    
         
            +
                def add_learning(self, agent_id: str, learning_type: str, content: str) -> bool:
         
     | 
| 
      
 215 
     | 
    
         
            +
                    """Add structured learning to appropriate section.
         
     | 
| 
      
 216 
     | 
    
         
            +
                    
         
     | 
| 
      
 217 
     | 
    
         
            +
                    WHY: Different types of learnings belong in different sections for better
         
     | 
| 
      
 218 
     | 
    
         
            +
                    organization and retrieval. This method maps learning types to appropriate
         
     | 
| 
      
 219 
     | 
    
         
            +
                    sections automatically.
         
     | 
| 
      
 220 
     | 
    
         
            +
                    
         
     | 
| 
      
 221 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 222 
     | 
    
         
            +
                        agent_id: The agent identifier
         
     | 
| 
      
 223 
     | 
    
         
            +
                        learning_type: Type of learning (pattern, architecture, guideline, etc.)
         
     | 
| 
      
 224 
     | 
    
         
            +
                        content: The learning content
         
     | 
| 
      
 225 
     | 
    
         
            +
                        
         
     | 
| 
      
 226 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 227 
     | 
    
         
            +
                        bool: True if learning was added successfully
         
     | 
| 
      
 228 
     | 
    
         
            +
                    """
         
     | 
| 
      
 229 
     | 
    
         
            +
                    section_mapping = {
         
     | 
| 
      
 230 
     | 
    
         
            +
                        'pattern': 'Coding Patterns Learned',
         
     | 
| 
      
 231 
     | 
    
         
            +
                        'architecture': 'Project Architecture', 
         
     | 
| 
      
 232 
     | 
    
         
            +
                        'guideline': 'Implementation Guidelines',
         
     | 
| 
      
 233 
     | 
    
         
            +
                        'mistake': 'Common Mistakes to Avoid',
         
     | 
| 
      
 234 
     | 
    
         
            +
                        'strategy': 'Effective Strategies',
         
     | 
| 
      
 235 
     | 
    
         
            +
                        'integration': 'Integration Points',
         
     | 
| 
      
 236 
     | 
    
         
            +
                        'performance': 'Performance Considerations',
         
     | 
| 
      
 237 
     | 
    
         
            +
                        'domain': 'Domain-Specific Knowledge',
         
     | 
| 
      
 238 
     | 
    
         
            +
                        'context': 'Current Technical Context'
         
     | 
| 
      
 239 
     | 
    
         
            +
                    }
         
     | 
| 
      
 240 
     | 
    
         
            +
                    
         
     | 
| 
      
 241 
     | 
    
         
            +
                    section = section_mapping.get(learning_type, 'Recent Learnings')
         
     | 
| 
      
 242 
     | 
    
         
            +
                    success = self.update_agent_memory(agent_id, section, content)
         
     | 
| 
      
 243 
     | 
    
         
            +
                    
         
     | 
| 
      
 244 
     | 
    
         
            +
                    # Emit WebSocket event for memory updated
         
     | 
| 
      
 245 
     | 
    
         
            +
                    if success:
         
     | 
| 
      
 246 
     | 
    
         
            +
                        try:
         
     | 
| 
      
 247 
     | 
    
         
            +
                            ws_server = get_websocket_server()
         
     | 
| 
      
 248 
     | 
    
         
            +
                            ws_server.memory_updated(agent_id, learning_type, content, section)
         
     | 
| 
      
 249 
     | 
    
         
            +
                        except Exception as ws_error:
         
     | 
| 
      
 250 
     | 
    
         
            +
                            self.logger.debug(f"WebSocket notification failed: {ws_error}")
         
     | 
| 
      
 251 
     | 
    
         
            +
                    
         
     | 
| 
      
 252 
     | 
    
         
            +
                    return success
         
     | 
| 
      
 253 
     | 
    
         
            +
                
         
     | 
| 
      
 254 
     | 
    
         
            +
                def _create_default_memory(self, agent_id: str) -> str:
         
     | 
| 
      
 255 
     | 
    
         
            +
                    """Create default memory file for agent.
         
     | 
| 
      
 256 
     | 
    
         
            +
                    
         
     | 
| 
      
 257 
     | 
    
         
            +
                    WHY: New agents need a starting template with essential project knowledge
         
     | 
| 
      
 258 
     | 
    
         
            +
                    and the correct structure for adding new learnings.
         
     | 
| 
      
 259 
     | 
    
         
            +
                    
         
     | 
| 
      
 260 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 261 
     | 
    
         
            +
                        agent_id: The agent identifier
         
     | 
| 
      
 262 
     | 
    
         
            +
                        
         
     | 
| 
      
 263 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 264 
     | 
    
         
            +
                        str: The default memory template content
         
     | 
| 
      
 265 
     | 
    
         
            +
                    """
         
     | 
| 
      
 266 
     | 
    
         
            +
                    # Convert agent_id to proper name, handling cases like "test_agent" -> "Test"
         
     | 
| 
      
 267 
     | 
    
         
            +
                    agent_name = agent_id.replace('_agent', '').replace('_', ' ').title()
         
     | 
| 
      
 268 
     | 
    
         
            +
                    project_name = self.project_root.name
         
     | 
| 
      
 269 
     | 
    
         
            +
                    timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
         
     | 
| 
      
 270 
     | 
    
         
            +
                    
         
     | 
| 
      
 271 
     | 
    
         
            +
                    # Get limits for this agent
         
     | 
| 
      
 272 
     | 
    
         
            +
                    limits = self._get_agent_limits(agent_id)
         
     | 
| 
      
 273 
     | 
    
         
            +
                    
         
     | 
| 
      
 274 
     | 
    
         
            +
                    template = f"""# {agent_name} Agent Memory - {project_name}
         
     | 
| 
      
 275 
     | 
    
         
            +
             
     | 
| 
      
 276 
     | 
    
         
            +
            <!-- MEMORY LIMITS: {limits['max_file_size_kb']}KB max | {limits['max_sections']} sections max | {limits['max_items_per_section']} items per section -->
         
     | 
| 
      
 277 
     | 
    
         
            +
            <!-- Last Updated: {timestamp} | Auto-updated by: {agent_id} -->
         
     | 
| 
      
 278 
     | 
    
         
            +
             
     | 
| 
      
 279 
     | 
    
         
            +
            ## Project Architecture (Max: 15 items)
         
     | 
| 
      
 280 
     | 
    
         
            +
            - Service-oriented architecture with clear module boundaries
         
     | 
| 
      
 281 
     | 
    
         
            +
            - Three-tier agent hierarchy: project → user → system
         
     | 
| 
      
 282 
     | 
    
         
            +
            - Agent definitions use standardized JSON schema validation
         
     | 
| 
      
 283 
     | 
    
         
            +
             
     | 
| 
      
 284 
     | 
    
         
            +
            ## Coding Patterns Learned (Max: 15 items)
         
     | 
| 
      
 285 
     | 
    
         
            +
            - Always use PathResolver for path operations, never hardcode paths
         
     | 
| 
      
 286 
     | 
    
         
            +
            - SubprocessRunner utility for external command execution
         
     | 
| 
      
 287 
     | 
    
         
            +
            - LoggerMixin provides consistent logging across all services
         
     | 
| 
      
 288 
     | 
    
         
            +
             
     | 
| 
      
 289 
     | 
    
         
            +
            ## Implementation Guidelines (Max: 15 items)
         
     | 
| 
      
 290 
     | 
    
         
            +
            - Check docs/STRUCTURE.md before creating new files
         
     | 
| 
      
 291 
     | 
    
         
            +
            - Follow existing import patterns: from claude_mpm.module import Class
         
     | 
| 
      
 292 
     | 
    
         
            +
            - Use existing utilities instead of reimplementing functionality
         
     | 
| 
      
 293 
     | 
    
         
            +
             
     | 
| 
      
 294 
     | 
    
         
            +
            ## Domain-Specific Knowledge (Max: 15 items)
         
     | 
| 
      
 295 
     | 
    
         
            +
            <!-- Agent-specific knowledge accumulates here -->
         
     | 
| 
      
 296 
     | 
    
         
            +
             
     | 
| 
      
 297 
     | 
    
         
            +
            ## Effective Strategies (Max: 15 items)
         
     | 
| 
      
 298 
     | 
    
         
            +
            <!-- Successful approaches discovered through experience -->
         
     | 
| 
      
 299 
     | 
    
         
            +
             
     | 
| 
      
 300 
     | 
    
         
            +
            ## Common Mistakes to Avoid (Max: 15 items)
         
     | 
| 
      
 301 
     | 
    
         
            +
            - Don't modify Claude Code core functionality, only extend it
         
     | 
| 
      
 302 
     | 
    
         
            +
            - Avoid duplicating code - check utils/ for existing implementations
         
     | 
| 
      
 303 
     | 
    
         
            +
            - Never hardcode file paths, use PathResolver utilities
         
     | 
| 
      
 304 
     | 
    
         
            +
             
     | 
| 
      
 305 
     | 
    
         
            +
            ## Integration Points (Max: 15 items)
         
     | 
| 
      
 306 
     | 
    
         
            +
            <!-- Key interfaces and integration patterns -->
         
     | 
| 
      
 307 
     | 
    
         
            +
             
     | 
| 
      
 308 
     | 
    
         
            +
            ## Performance Considerations (Max: 15 items)
         
     | 
| 
      
 309 
     | 
    
         
            +
            <!-- Performance insights and optimization patterns -->
         
     | 
| 
      
 310 
     | 
    
         
            +
             
     | 
| 
      
 311 
     | 
    
         
            +
            ## Current Technical Context (Max: 15 items)
         
     | 
| 
      
 312 
     | 
    
         
            +
            - EP-0001: Technical debt reduction in progress
         
     | 
| 
      
 313 
     | 
    
         
            +
            - Target: 80% test coverage (current: 23.6%)
         
     | 
| 
      
 314 
     | 
    
         
            +
            - Integration with Claude Code 1.0.60+ native agent framework
         
     | 
| 
      
 315 
     | 
    
         
            +
             
     | 
| 
      
 316 
     | 
    
         
            +
            ## Recent Learnings (Max: 15 items)
         
     | 
| 
      
 317 
     | 
    
         
            +
            <!-- Most recent discoveries and insights -->
         
     | 
| 
      
 318 
     | 
    
         
            +
            """
         
     | 
| 
      
 319 
     | 
    
         
            +
                    
         
     | 
| 
      
 320 
     | 
    
         
            +
                    # Save default file
         
     | 
| 
      
 321 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 322 
     | 
    
         
            +
                        memory_file = self.memories_dir / f"{agent_id}_agent.md"
         
     | 
| 
      
 323 
     | 
    
         
            +
                        memory_file.write_text(template, encoding='utf-8')
         
     | 
| 
      
 324 
     | 
    
         
            +
                        self.logger.info(f"Created default memory file for {agent_id}")
         
     | 
| 
      
 325 
     | 
    
         
            +
                        
         
     | 
| 
      
 326 
     | 
    
         
            +
                        # Emit WebSocket event for memory created
         
     | 
| 
      
 327 
     | 
    
         
            +
                        try:
         
     | 
| 
      
 328 
     | 
    
         
            +
                            ws_server = get_websocket_server()
         
     | 
| 
      
 329 
     | 
    
         
            +
                            ws_server.memory_created(agent_id, "default")
         
     | 
| 
      
 330 
     | 
    
         
            +
                        except Exception as ws_error:
         
     | 
| 
      
 331 
     | 
    
         
            +
                            self.logger.debug(f"WebSocket notification failed: {ws_error}")
         
     | 
| 
      
 332 
     | 
    
         
            +
                    except Exception as e:
         
     | 
| 
      
 333 
     | 
    
         
            +
                        self.logger.error(f"Error saving default memory for {agent_id}: {e}")
         
     | 
| 
      
 334 
     | 
    
         
            +
                    
         
     | 
| 
      
 335 
     | 
    
         
            +
                    return template
         
     | 
| 
      
 336 
     | 
    
         
            +
                
         
     | 
| 
      
 337 
     | 
    
         
            +
                def _add_item_to_section(self, content: str, section: str, new_item: str) -> str:
         
     | 
| 
      
 338 
     | 
    
         
            +
                    """Add item to specified section, respecting limits.
         
     | 
| 
      
 339 
     | 
    
         
            +
                    
         
     | 
| 
      
 340 
     | 
    
         
            +
                    WHY: Each section has a maximum item limit to prevent information overload
         
     | 
| 
      
 341 
     | 
    
         
            +
                    and maintain readability. When limits are reached, oldest items are removed
         
     | 
| 
      
 342 
     | 
    
         
            +
                    to make room for new learnings (FIFO strategy).
         
     | 
| 
      
 343 
     | 
    
         
            +
                    
         
     | 
| 
      
 344 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 345 
     | 
    
         
            +
                        content: Current memory file content
         
     | 
| 
      
 346 
     | 
    
         
            +
                        section: Section name to add item to
         
     | 
| 
      
 347 
     | 
    
         
            +
                        new_item: Item to add
         
     | 
| 
      
 348 
     | 
    
         
            +
                        
         
     | 
| 
      
 349 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 350 
     | 
    
         
            +
                        str: Updated content with new item added
         
     | 
| 
      
 351 
     | 
    
         
            +
                    """
         
     | 
| 
      
 352 
     | 
    
         
            +
                    lines = content.split('\n')
         
     | 
| 
      
 353 
     | 
    
         
            +
                    section_start = None
         
     | 
| 
      
 354 
     | 
    
         
            +
                    section_end = None
         
     | 
| 
      
 355 
     | 
    
         
            +
                    
         
     | 
| 
      
 356 
     | 
    
         
            +
                    # Find section boundaries
         
     | 
| 
      
 357 
     | 
    
         
            +
                    for i, line in enumerate(lines):
         
     | 
| 
      
 358 
     | 
    
         
            +
                        if line.startswith(f'## {section}'):
         
     | 
| 
      
 359 
     | 
    
         
            +
                            section_start = i
         
     | 
| 
      
 360 
     | 
    
         
            +
                        elif section_start is not None and line.startswith('## '):
         
     | 
| 
      
 361 
     | 
    
         
            +
                            section_end = i
         
     | 
| 
      
 362 
     | 
    
         
            +
                            break
         
     | 
| 
      
 363 
     | 
    
         
            +
                    
         
     | 
| 
      
 364 
     | 
    
         
            +
                    if section_start is None:
         
     | 
| 
      
 365 
     | 
    
         
            +
                        # Section doesn't exist, add it
         
     | 
| 
      
 366 
     | 
    
         
            +
                        return self._add_new_section(content, section, new_item)
         
     | 
| 
      
 367 
     | 
    
         
            +
                    
         
     | 
| 
      
 368 
     | 
    
         
            +
                    if section_end is None:
         
     | 
| 
      
 369 
     | 
    
         
            +
                        section_end = len(lines)
         
     | 
| 
      
 370 
     | 
    
         
            +
                    
         
     | 
| 
      
 371 
     | 
    
         
            +
                    # Count existing items in section and find first item index
         
     | 
| 
      
 372 
     | 
    
         
            +
                    item_count = 0
         
     | 
| 
      
 373 
     | 
    
         
            +
                    first_item_index = None
         
     | 
| 
      
 374 
     | 
    
         
            +
                    for i in range(section_start + 1, section_end):
         
     | 
| 
      
 375 
     | 
    
         
            +
                        if lines[i].strip().startswith('- '):
         
     | 
| 
      
 376 
     | 
    
         
            +
                            if first_item_index is None:
         
     | 
| 
      
 377 
     | 
    
         
            +
                                first_item_index = i
         
     | 
| 
      
 378 
     | 
    
         
            +
                            item_count += 1
         
     | 
| 
      
 379 
     | 
    
         
            +
                    
         
     | 
| 
      
 380 
     | 
    
         
            +
                    # Check if we can add more items
         
     | 
| 
      
 381 
     | 
    
         
            +
                    if item_count >= self.memory_limits['max_items_per_section']:
         
     | 
| 
      
 382 
     | 
    
         
            +
                        # Remove oldest item (first one) to make room
         
     | 
| 
      
 383 
     | 
    
         
            +
                        if first_item_index is not None:
         
     | 
| 
      
 384 
     | 
    
         
            +
                            lines.pop(first_item_index)
         
     | 
| 
      
 385 
     | 
    
         
            +
                            section_end -= 1  # Adjust section end after removal
         
     | 
| 
      
 386 
     | 
    
         
            +
                    
         
     | 
| 
      
 387 
     | 
    
         
            +
                    # Add new item (find insertion point after any comments)
         
     | 
| 
      
 388 
     | 
    
         
            +
                    insert_point = section_start + 1
         
     | 
| 
      
 389 
     | 
    
         
            +
                    while insert_point < section_end and (
         
     | 
| 
      
 390 
     | 
    
         
            +
                        not lines[insert_point].strip() or 
         
     | 
| 
      
 391 
     | 
    
         
            +
                        lines[insert_point].strip().startswith('<!--')
         
     | 
| 
      
 392 
     | 
    
         
            +
                    ):
         
     | 
| 
      
 393 
     | 
    
         
            +
                        insert_point += 1
         
     | 
| 
      
 394 
     | 
    
         
            +
                    
         
     | 
| 
      
 395 
     | 
    
         
            +
                    # Ensure line length limit
         
     | 
| 
      
 396 
     | 
    
         
            +
                    if len(new_item) > self.memory_limits['max_line_length']:
         
     | 
| 
      
 397 
     | 
    
         
            +
                        new_item = new_item[:self.memory_limits['max_line_length'] - 3] + '...'
         
     | 
| 
      
 398 
     | 
    
         
            +
                    
         
     | 
| 
      
 399 
     | 
    
         
            +
                    lines.insert(insert_point, f"- {new_item}")
         
     | 
| 
      
 400 
     | 
    
         
            +
                    
         
     | 
| 
      
 401 
     | 
    
         
            +
                    # Update timestamp
         
     | 
| 
      
 402 
     | 
    
         
            +
                    updated_content = '\n'.join(lines)
         
     | 
| 
      
 403 
     | 
    
         
            +
                    return self._update_timestamp(updated_content)
         
     | 
| 
      
 404 
     | 
    
         
            +
                
         
     | 
| 
      
 405 
     | 
    
         
            +
                def _add_new_section(self, content: str, section: str, new_item: str) -> str:
         
     | 
| 
      
 406 
     | 
    
         
            +
                    """Add a new section with the given item.
         
     | 
| 
      
 407 
     | 
    
         
            +
                    
         
     | 
| 
      
 408 
     | 
    
         
            +
                    WHY: When agents discover learnings that don't fit existing sections,
         
     | 
| 
      
 409 
     | 
    
         
            +
                    we need to create new sections dynamically while respecting the maximum
         
     | 
| 
      
 410 
     | 
    
         
            +
                    section limit.
         
     | 
| 
      
 411 
     | 
    
         
            +
                    
         
     | 
| 
      
 412 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 413 
     | 
    
         
            +
                        content: Current memory content
         
     | 
| 
      
 414 
     | 
    
         
            +
                        section: New section name
         
     | 
| 
      
 415 
     | 
    
         
            +
                        new_item: First item for the section
         
     | 
| 
      
 416 
     | 
    
         
            +
                        
         
     | 
| 
      
 417 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 418 
     | 
    
         
            +
                        str: Updated content with new section
         
     | 
| 
      
 419 
     | 
    
         
            +
                    """
         
     | 
| 
      
 420 
     | 
    
         
            +
                    lines = content.split('\n')
         
     | 
| 
      
 421 
     | 
    
         
            +
                    
         
     | 
| 
      
 422 
     | 
    
         
            +
                    # Count existing sections
         
     | 
| 
      
 423 
     | 
    
         
            +
                    section_count = sum(1 for line in lines if line.startswith('## '))
         
     | 
| 
      
 424 
     | 
    
         
            +
                    
         
     | 
| 
      
 425 
     | 
    
         
            +
                    if section_count >= self.memory_limits['max_sections']:
         
     | 
| 
      
 426 
     | 
    
         
            +
                        self.logger.warning(f"Maximum sections reached, cannot add '{section}'")
         
     | 
| 
      
 427 
     | 
    
         
            +
                        # Try to add to Recent Learnings instead
         
     | 
| 
      
 428 
     | 
    
         
            +
                        return self._add_item_to_section(content, 'Recent Learnings', new_item)
         
     | 
| 
      
 429 
     | 
    
         
            +
                    
         
     | 
| 
      
 430 
     | 
    
         
            +
                    # Find insertion point (before Recent Learnings or at end)
         
     | 
| 
      
 431 
     | 
    
         
            +
                    insert_point = len(lines)
         
     | 
| 
      
 432 
     | 
    
         
            +
                    for i, line in enumerate(lines):
         
     | 
| 
      
 433 
     | 
    
         
            +
                        if line.startswith('## Recent Learnings'):
         
     | 
| 
      
 434 
     | 
    
         
            +
                            insert_point = i
         
     | 
| 
      
 435 
     | 
    
         
            +
                            break
         
     | 
| 
      
 436 
     | 
    
         
            +
                    
         
     | 
| 
      
 437 
     | 
    
         
            +
                    # Insert new section
         
     | 
| 
      
 438 
     | 
    
         
            +
                    new_section = [
         
     | 
| 
      
 439 
     | 
    
         
            +
                        '',
         
     | 
| 
      
 440 
     | 
    
         
            +
                        f'## {section} (Max: 15 items)',
         
     | 
| 
      
 441 
     | 
    
         
            +
                        f'- {new_item}',
         
     | 
| 
      
 442 
     | 
    
         
            +
                        ''
         
     | 
| 
      
 443 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 444 
     | 
    
         
            +
                    
         
     | 
| 
      
 445 
     | 
    
         
            +
                    for j, line in enumerate(new_section):
         
     | 
| 
      
 446 
     | 
    
         
            +
                        lines.insert(insert_point + j, line)
         
     | 
| 
      
 447 
     | 
    
         
            +
                    
         
     | 
| 
      
 448 
     | 
    
         
            +
                    return '\n'.join(lines)
         
     | 
| 
      
 449 
     | 
    
         
            +
                
         
     | 
| 
      
 450 
     | 
    
         
            +
                def _exceeds_limits(self, content: str, agent_id: Optional[str] = None) -> bool:
         
     | 
| 
      
 451 
     | 
    
         
            +
                    """Check if content exceeds size limits.
         
     | 
| 
      
 452 
     | 
    
         
            +
                    
         
     | 
| 
      
 453 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 454 
     | 
    
         
            +
                        content: Content to check
         
     | 
| 
      
 455 
     | 
    
         
            +
                        agent_id: Optional agent ID for agent-specific limits
         
     | 
| 
      
 456 
     | 
    
         
            +
                        
         
     | 
| 
      
 457 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 458 
     | 
    
         
            +
                        bool: True if content exceeds limits
         
     | 
| 
      
 459 
     | 
    
         
            +
                    """
         
     | 
| 
      
 460 
     | 
    
         
            +
                    # Get appropriate limits based on agent
         
     | 
| 
      
 461 
     | 
    
         
            +
                    if agent_id:
         
     | 
| 
      
 462 
     | 
    
         
            +
                        limits = self._get_agent_limits(agent_id)
         
     | 
| 
      
 463 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 464 
     | 
    
         
            +
                        limits = self.memory_limits
         
     | 
| 
      
 465 
     | 
    
         
            +
                        
         
     | 
| 
      
 466 
     | 
    
         
            +
                    size_kb = len(content.encode('utf-8')) / 1024
         
     | 
| 
      
 467 
     | 
    
         
            +
                    return size_kb > limits['max_file_size_kb']
         
     | 
| 
      
 468 
     | 
    
         
            +
                
         
     | 
| 
      
 469 
     | 
    
         
            +
                def _truncate_to_limits(self, content: str, agent_id: Optional[str] = None) -> str:
         
     | 
| 
      
 470 
     | 
    
         
            +
                    """Truncate content to fit within limits.
         
     | 
| 
      
 471 
     | 
    
         
            +
                    
         
     | 
| 
      
 472 
     | 
    
         
            +
                    WHY: When memory files exceed size limits, we need a strategy to reduce
         
     | 
| 
      
 473 
     | 
    
         
            +
                    size while preserving the most important information. This implementation
         
     | 
| 
      
 474 
     | 
    
         
            +
                    removes items from "Recent Learnings" first as they're typically less
         
     | 
| 
      
 475 
     | 
    
         
            +
                    consolidated than other sections.
         
     | 
| 
      
 476 
     | 
    
         
            +
                    
         
     | 
| 
      
 477 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 478 
     | 
    
         
            +
                        content: Content to truncate
         
     | 
| 
      
 479 
     | 
    
         
            +
                        
         
     | 
| 
      
 480 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 481 
     | 
    
         
            +
                        str: Truncated content within size limits
         
     | 
| 
      
 482 
     | 
    
         
            +
                    """
         
     | 
| 
      
 483 
     | 
    
         
            +
                    lines = content.split('\n')
         
     | 
| 
      
 484 
     | 
    
         
            +
                    
         
     | 
| 
      
 485 
     | 
    
         
            +
                    # Get appropriate limits based on agent
         
     | 
| 
      
 486 
     | 
    
         
            +
                    if agent_id:
         
     | 
| 
      
 487 
     | 
    
         
            +
                        limits = self._get_agent_limits(agent_id)
         
     | 
| 
      
 488 
     | 
    
         
            +
                    else:
         
     | 
| 
      
 489 
     | 
    
         
            +
                        limits = self.memory_limits
         
     | 
| 
      
 490 
     | 
    
         
            +
                        
         
     | 
| 
      
 491 
     | 
    
         
            +
                    # Strategy: Remove items from Recent Learnings first
         
     | 
| 
      
 492 
     | 
    
         
            +
                    while self._exceeds_limits('\n'.join(lines), agent_id):
         
     | 
| 
      
 493 
     | 
    
         
            +
                        removed = False
         
     | 
| 
      
 494 
     | 
    
         
            +
                        
         
     | 
| 
      
 495 
     | 
    
         
            +
                        # First try Recent Learnings
         
     | 
| 
      
 496 
     | 
    
         
            +
                        for i, line in enumerate(lines):
         
     | 
| 
      
 497 
     | 
    
         
            +
                            if line.startswith('## Recent Learnings'):
         
     | 
| 
      
 498 
     | 
    
         
            +
                                # Find and remove first item in this section
         
     | 
| 
      
 499 
     | 
    
         
            +
                                for j in range(i + 1, len(lines)):
         
     | 
| 
      
 500 
     | 
    
         
            +
                                    if lines[j].strip().startswith('- '):
         
     | 
| 
      
 501 
     | 
    
         
            +
                                        lines.pop(j)
         
     | 
| 
      
 502 
     | 
    
         
            +
                                        removed = True
         
     | 
| 
      
 503 
     | 
    
         
            +
                                        break
         
     | 
| 
      
 504 
     | 
    
         
            +
                                    elif lines[j].startswith('## '):
         
     | 
| 
      
 505 
     | 
    
         
            +
                                        break
         
     | 
| 
      
 506 
     | 
    
         
            +
                                break
         
     | 
| 
      
 507 
     | 
    
         
            +
                        
         
     | 
| 
      
 508 
     | 
    
         
            +
                        # If no Recent Learnings items, remove from other sections
         
     | 
| 
      
 509 
     | 
    
         
            +
                        if not removed:
         
     | 
| 
      
 510 
     | 
    
         
            +
                            # Remove from sections in reverse order (bottom up)
         
     | 
| 
      
 511 
     | 
    
         
            +
                            for i in range(len(lines) - 1, -1, -1):
         
     | 
| 
      
 512 
     | 
    
         
            +
                                if lines[i].strip().startswith('- '):
         
     | 
| 
      
 513 
     | 
    
         
            +
                                    lines.pop(i)
         
     | 
| 
      
 514 
     | 
    
         
            +
                                    removed = True
         
     | 
| 
      
 515 
     | 
    
         
            +
                                    break
         
     | 
| 
      
 516 
     | 
    
         
            +
                        
         
     | 
| 
      
 517 
     | 
    
         
            +
                        # Safety: If nothing removed, truncate from end
         
     | 
| 
      
 518 
     | 
    
         
            +
                        if not removed:
         
     | 
| 
      
 519 
     | 
    
         
            +
                            lines = lines[:-10]
         
     | 
| 
      
 520 
     | 
    
         
            +
                    
         
     | 
| 
      
 521 
     | 
    
         
            +
                    return '\n'.join(lines)
         
     | 
| 
      
 522 
     | 
    
         
            +
                
         
     | 
| 
      
 523 
     | 
    
         
            +
                def _update_timestamp(self, content: str) -> str:
         
     | 
| 
      
 524 
     | 
    
         
            +
                    """Update the timestamp in the file header.
         
     | 
| 
      
 525 
     | 
    
         
            +
                    
         
     | 
| 
      
 526 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 527 
     | 
    
         
            +
                        content: Content to update
         
     | 
| 
      
 528 
     | 
    
         
            +
                        
         
     | 
| 
      
 529 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 530 
     | 
    
         
            +
                        str: Content with updated timestamp
         
     | 
| 
      
 531 
     | 
    
         
            +
                    """
         
     | 
| 
      
 532 
     | 
    
         
            +
                    timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
         
     | 
| 
      
 533 
     | 
    
         
            +
                    return re.sub(
         
     | 
| 
      
 534 
     | 
    
         
            +
                        r'<!-- Last Updated: .+ \| Auto-updated by: .+ -->',
         
     | 
| 
      
 535 
     | 
    
         
            +
                        f'<!-- Last Updated: {timestamp} | Auto-updated by: system -->',
         
     | 
| 
      
 536 
     | 
    
         
            +
                        content
         
     | 
| 
      
 537 
     | 
    
         
            +
                    )
         
     | 
| 
      
 538 
     | 
    
         
            +
                
         
     | 
| 
      
 539 
     | 
    
         
            +
                def _validate_and_repair(self, content: str, agent_id: str) -> str:
         
     | 
| 
      
 540 
     | 
    
         
            +
                    """Validate memory file and repair if needed.
         
     | 
| 
      
 541 
     | 
    
         
            +
                    
         
     | 
| 
      
 542 
     | 
    
         
            +
                    WHY: Memory files might be manually edited by developers or corrupted.
         
     | 
| 
      
 543 
     | 
    
         
            +
                    This method ensures the file maintains required structure and sections.
         
     | 
| 
      
 544 
     | 
    
         
            +
                    
         
     | 
| 
      
 545 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 546 
     | 
    
         
            +
                        content: Content to validate
         
     | 
| 
      
 547 
     | 
    
         
            +
                        agent_id: Agent identifier
         
     | 
| 
      
 548 
     | 
    
         
            +
                        
         
     | 
| 
      
 549 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 550 
     | 
    
         
            +
                        str: Validated and repaired content
         
     | 
| 
      
 551 
     | 
    
         
            +
                    """
         
     | 
| 
      
 552 
     | 
    
         
            +
                    lines = content.split('\n')
         
     | 
| 
      
 553 
     | 
    
         
            +
                    existing_sections = set()
         
     | 
| 
      
 554 
     | 
    
         
            +
                    
         
     | 
| 
      
 555 
     | 
    
         
            +
                    # Find existing sections
         
     | 
| 
      
 556 
     | 
    
         
            +
                    for line in lines:
         
     | 
| 
      
 557 
     | 
    
         
            +
                        if line.startswith('## '):
         
     | 
| 
      
 558 
     | 
    
         
            +
                            section_name = line[3:].split('(')[0].strip()
         
     | 
| 
      
 559 
     | 
    
         
            +
                            existing_sections.add(section_name)
         
     | 
| 
      
 560 
     | 
    
         
            +
                    
         
     | 
| 
      
 561 
     | 
    
         
            +
                    # Check for required sections
         
     | 
| 
      
 562 
     | 
    
         
            +
                    missing_sections = []
         
     | 
| 
      
 563 
     | 
    
         
            +
                    for required in self.REQUIRED_SECTIONS:
         
     | 
| 
      
 564 
     | 
    
         
            +
                        if required not in existing_sections:
         
     | 
| 
      
 565 
     | 
    
         
            +
                            missing_sections.append(required)
         
     | 
| 
      
 566 
     | 
    
         
            +
                    
         
     | 
| 
      
 567 
     | 
    
         
            +
                    if missing_sections:
         
     | 
| 
      
 568 
     | 
    
         
            +
                        self.logger.info(f"Adding missing sections to {agent_id} memory: {missing_sections}")
         
     | 
| 
      
 569 
     | 
    
         
            +
                        
         
     | 
| 
      
 570 
     | 
    
         
            +
                        # Add missing sections before Recent Learnings
         
     | 
| 
      
 571 
     | 
    
         
            +
                        insert_point = len(lines)
         
     | 
| 
      
 572 
     | 
    
         
            +
                        for i, line in enumerate(lines):
         
     | 
| 
      
 573 
     | 
    
         
            +
                            if line.startswith('## Recent Learnings'):
         
     | 
| 
      
 574 
     | 
    
         
            +
                                insert_point = i
         
     | 
| 
      
 575 
     | 
    
         
            +
                                break
         
     | 
| 
      
 576 
     | 
    
         
            +
                        
         
     | 
| 
      
 577 
     | 
    
         
            +
                        for section in missing_sections:
         
     | 
| 
      
 578 
     | 
    
         
            +
                            section_content = [
         
     | 
| 
      
 579 
     | 
    
         
            +
                                '',
         
     | 
| 
      
 580 
     | 
    
         
            +
                                f'## {section} (Max: 15 items)',
         
     | 
| 
      
 581 
     | 
    
         
            +
                                '<!-- Section added by repair -->',
         
     | 
| 
      
 582 
     | 
    
         
            +
                                ''
         
     | 
| 
      
 583 
     | 
    
         
            +
                            ]
         
     | 
| 
      
 584 
     | 
    
         
            +
                            for j, line in enumerate(section_content):
         
     | 
| 
      
 585 
     | 
    
         
            +
                                lines.insert(insert_point + j, line)
         
     | 
| 
      
 586 
     | 
    
         
            +
                            insert_point += len(section_content)
         
     | 
| 
      
 587 
     | 
    
         
            +
                    
         
     | 
| 
      
 588 
     | 
    
         
            +
                    return '\n'.join(lines)
         
     | 
| 
      
 589 
     | 
    
         
            +
                
         
     | 
| 
      
 590 
     | 
    
         
            +
                def _save_memory_file(self, agent_id: str, content: str) -> bool:
         
     | 
| 
      
 591 
     | 
    
         
            +
                    """Save memory content to file.
         
     | 
| 
      
 592 
     | 
    
         
            +
                    
         
     | 
| 
      
 593 
     | 
    
         
            +
                    WHY: Memory updates need to be persisted atomically to prevent corruption
         
     | 
| 
      
 594 
     | 
    
         
            +
                    and ensure learnings are preserved across agent invocations.
         
     | 
| 
      
 595 
     | 
    
         
            +
                    
         
     | 
| 
      
 596 
     | 
    
         
            +
                    Args:
         
     | 
| 
      
 597 
     | 
    
         
            +
                        agent_id: Agent identifier
         
     | 
| 
      
 598 
     | 
    
         
            +
                        content: Content to save
         
     | 
| 
      
 599 
     | 
    
         
            +
                        
         
     | 
| 
      
 600 
     | 
    
         
            +
                    Returns:
         
     | 
| 
      
 601 
     | 
    
         
            +
                        bool: True if save succeeded
         
     | 
| 
      
 602 
     | 
    
         
            +
                    """
         
     | 
| 
      
 603 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 604 
     | 
    
         
            +
                        memory_file = self.memories_dir / f"{agent_id}_agent.md"
         
     | 
| 
      
 605 
     | 
    
         
            +
                        memory_file.write_text(content, encoding='utf-8')
         
     | 
| 
      
 606 
     | 
    
         
            +
                        self.logger.debug(f"Saved memory for {agent_id}")
         
     | 
| 
      
 607 
     | 
    
         
            +
                        return True
         
     | 
| 
      
 608 
     | 
    
         
            +
                    except Exception as e:
         
     | 
| 
      
 609 
     | 
    
         
            +
                        self.logger.error(f"Error saving memory for {agent_id}: {e}")
         
     | 
| 
      
 610 
     | 
    
         
            +
                        return False
         
     | 
| 
      
 611 
     | 
    
         
            +
                
         
     | 
| 
      
 612 
     | 
    
         
            +
                def _ensure_memories_directory(self):
         
     | 
| 
      
 613 
     | 
    
         
            +
                    """Ensure memories directory exists with README.
         
     | 
| 
      
 614 
     | 
    
         
            +
                    
         
     | 
| 
      
 615 
     | 
    
         
            +
                    WHY: The memories directory needs clear documentation so developers
         
     | 
| 
      
 616 
     | 
    
         
            +
                    understand the purpose of these files and how to interact with them.
         
     | 
| 
      
 617 
     | 
    
         
            +
                    """
         
     | 
| 
      
 618 
     | 
    
         
            +
                    try:
         
     | 
| 
      
 619 
     | 
    
         
            +
                        self.memories_dir.mkdir(parents=True, exist_ok=True)
         
     | 
| 
      
 620 
     | 
    
         
            +
                        self.logger.debug(f"Ensured memories directory exists: {self.memories_dir}")
         
     | 
| 
      
 621 
     | 
    
         
            +
                        
         
     | 
| 
      
 622 
     | 
    
         
            +
                        readme_path = self.memories_dir / "README.md"
         
     | 
| 
      
 623 
     | 
    
         
            +
                        if not readme_path.exists():
         
     | 
| 
      
 624 
     | 
    
         
            +
                            readme_content = """# Agent Memory System
         
     | 
| 
      
 625 
     | 
    
         
            +
             
     | 
| 
      
 626 
     | 
    
         
            +
            ## Purpose
         
     | 
| 
      
 627 
     | 
    
         
            +
            Each agent maintains project-specific knowledge in these files. Agents read their memory file before tasks and update it when they learn something new.
         
     | 
| 
      
 628 
     | 
    
         
            +
             
     | 
| 
      
 629 
     | 
    
         
            +
            ## Manual Editing
         
     | 
| 
      
 630 
     | 
    
         
            +
            Feel free to edit these files to:
         
     | 
| 
      
 631 
     | 
    
         
            +
            - Add project-specific guidelines
         
     | 
| 
      
 632 
     | 
    
         
            +
            - Remove outdated information  
         
     | 
| 
      
 633 
     | 
    
         
            +
            - Reorganize for better clarity
         
     | 
| 
      
 634 
     | 
    
         
            +
            - Add domain-specific knowledge
         
     | 
| 
      
 635 
     | 
    
         
            +
             
     | 
| 
      
 636 
     | 
    
         
            +
            ## Memory Limits
         
     | 
| 
      
 637 
     | 
    
         
            +
            - Max file size: 8KB (~2000 tokens)
         
     | 
| 
      
 638 
     | 
    
         
            +
            - Max sections: 10
         
     | 
| 
      
 639 
     | 
    
         
            +
            - Max items per section: 15
         
     | 
| 
      
 640 
     | 
    
         
            +
            - Files auto-truncate when limits exceeded
         
     | 
| 
      
 641 
     | 
    
         
            +
             
     | 
| 
      
 642 
     | 
    
         
            +
            ## File Format
         
     | 
| 
      
 643 
     | 
    
         
            +
            Standard markdown with structured sections. Agents expect:
         
     | 
| 
      
 644 
     | 
    
         
            +
            - Project Architecture
         
     | 
| 
      
 645 
     | 
    
         
            +
            - Implementation Guidelines
         
     | 
| 
      
 646 
     | 
    
         
            +
            - Common Mistakes to Avoid
         
     | 
| 
      
 647 
     | 
    
         
            +
            - Current Technical Context
         
     | 
| 
      
 648 
     | 
    
         
            +
             
     | 
| 
      
 649 
     | 
    
         
            +
            ## How It Works
         
     | 
| 
      
 650 
     | 
    
         
            +
            1. Agents read their memory file before starting tasks
         
     | 
| 
      
 651 
     | 
    
         
            +
            2. Agents add learnings during or after task completion
         
     | 
| 
      
 652 
     | 
    
         
            +
            3. Files automatically enforce size limits
         
     | 
| 
      
 653 
     | 
    
         
            +
            4. Developers can manually edit for accuracy
         
     | 
| 
      
 654 
     | 
    
         
            +
             
     | 
| 
      
 655 
     | 
    
         
            +
            ## Memory File Lifecycle
         
     | 
| 
      
 656 
     | 
    
         
            +
            - Created automatically when agent first runs
         
     | 
| 
      
 657 
     | 
    
         
            +
            - Updated through hook system after delegations
         
     | 
| 
      
 658 
     | 
    
         
            +
            - Manually editable by developers
         
     | 
| 
      
 659 
     | 
    
         
            +
            - Version controlled with project
         
     | 
| 
      
 660 
     | 
    
         
            +
            """
         
     | 
| 
      
 661 
     | 
    
         
            +
                            readme_path.write_text(readme_content, encoding='utf-8')
         
     | 
| 
      
 662 
     | 
    
         
            +
                            self.logger.info("Created README.md in memories directory")
         
     | 
| 
      
 663 
     | 
    
         
            +
                            
         
     | 
| 
      
 664 
     | 
    
         
            +
                    except Exception as e:
         
     | 
| 
      
 665 
     | 
    
         
            +
                        self.logger.error(f"Error ensuring memories directory: {e}")
         
     | 
| 
      
 666 
     | 
    
         
            +
                        # Continue anyway - memory system should not block operations
         
     | 
| 
      
 667 
     | 
    
         
            +
             
     | 
| 
      
 668 
     | 
    
         
            +
             
     | 
| 
      
 669 
     | 
    
         
            +
            # Convenience functions for external use
         
     | 
| 
      
 670 
     | 
    
         
            +
            def get_memory_manager(config: Optional[Config] = None) -> AgentMemoryManager:
         
     | 
| 
      
 671 
     | 
    
         
            +
                """Get a singleton instance of the memory manager.
         
     | 
| 
      
 672 
     | 
    
         
            +
                
         
     | 
| 
      
 673 
     | 
    
         
            +
                WHY: The memory manager should be shared across the application to ensure
         
     | 
| 
      
 674 
     | 
    
         
            +
                consistent file access and avoid multiple instances managing the same files.
         
     | 
| 
      
 675 
     | 
    
         
            +
                
         
     | 
| 
      
 676 
     | 
    
         
            +
                Args:
         
     | 
| 
      
 677 
     | 
    
         
            +
                    config: Optional Config object. Only used on first instantiation.
         
     | 
| 
      
 678 
     | 
    
         
            +
                
         
     | 
| 
      
 679 
     | 
    
         
            +
                Returns:
         
     | 
| 
      
 680 
     | 
    
         
            +
                    AgentMemoryManager: The memory manager instance
         
     | 
| 
      
 681 
     | 
    
         
            +
                """
         
     | 
| 
      
 682 
     | 
    
         
            +
                if not hasattr(get_memory_manager, '_instance'):
         
     | 
| 
      
 683 
     | 
    
         
            +
                    get_memory_manager._instance = AgentMemoryManager(config)
         
     | 
| 
      
 684 
     | 
    
         
            +
                return get_memory_manager._instance
         
     |