claude-mpm 3.4.26__py3-none-any.whl → 3.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/INSTRUCTIONS.md +182 -299
  3. claude_mpm/agents/agent_loader.py +283 -57
  4. claude_mpm/agents/agent_loader_integration.py +6 -9
  5. claude_mpm/agents/base_agent.json +2 -1
  6. claude_mpm/agents/base_agent_loader.py +1 -1
  7. claude_mpm/cli/__init__.py +6 -10
  8. claude_mpm/cli/commands/__init__.py +0 -2
  9. claude_mpm/cli/commands/agents.py +1 -1
  10. claude_mpm/cli/commands/memory.py +1 -1
  11. claude_mpm/cli/commands/run.py +12 -0
  12. claude_mpm/cli/parser.py +0 -13
  13. claude_mpm/cli/utils.py +1 -1
  14. claude_mpm/config/__init__.py +44 -2
  15. claude_mpm/config/agent_config.py +348 -0
  16. claude_mpm/config/paths.py +322 -0
  17. claude_mpm/constants.py +0 -1
  18. claude_mpm/core/__init__.py +2 -5
  19. claude_mpm/core/agent_registry.py +63 -17
  20. claude_mpm/core/claude_runner.py +354 -43
  21. claude_mpm/core/config.py +7 -1
  22. claude_mpm/core/config_aliases.py +4 -3
  23. claude_mpm/core/config_paths.py +151 -0
  24. claude_mpm/core/factories.py +4 -50
  25. claude_mpm/core/logger.py +11 -13
  26. claude_mpm/core/service_registry.py +2 -2
  27. claude_mpm/dashboard/static/js/components/agent-inference.js +101 -25
  28. claude_mpm/dashboard/static/js/components/event-processor.js +3 -2
  29. claude_mpm/hooks/claude_hooks/hook_handler.py +343 -83
  30. claude_mpm/hooks/memory_integration_hook.py +1 -1
  31. claude_mpm/init.py +37 -6
  32. claude_mpm/scripts/socketio_daemon.py +6 -2
  33. claude_mpm/services/__init__.py +71 -3
  34. claude_mpm/services/agents/__init__.py +85 -0
  35. claude_mpm/services/agents/deployment/__init__.py +21 -0
  36. claude_mpm/services/{agent_deployment.py → agents/deployment/agent_deployment.py} +192 -41
  37. claude_mpm/services/{agent_lifecycle_manager.py → agents/deployment/agent_lifecycle_manager.py} +11 -10
  38. claude_mpm/services/agents/loading/__init__.py +11 -0
  39. claude_mpm/services/{agent_profile_loader.py → agents/loading/agent_profile_loader.py} +9 -8
  40. claude_mpm/services/{base_agent_manager.py → agents/loading/base_agent_manager.py} +2 -2
  41. claude_mpm/services/{framework_agent_loader.py → agents/loading/framework_agent_loader.py} +116 -40
  42. claude_mpm/services/agents/management/__init__.py +9 -0
  43. claude_mpm/services/{agent_management_service.py → agents/management/agent_management_service.py} +6 -5
  44. claude_mpm/services/agents/memory/__init__.py +21 -0
  45. claude_mpm/services/{agent_memory_manager.py → agents/memory/agent_memory_manager.py} +3 -3
  46. claude_mpm/services/agents/registry/__init__.py +29 -0
  47. claude_mpm/services/{agent_registry.py → agents/registry/agent_registry.py} +101 -16
  48. claude_mpm/services/{deployed_agent_discovery.py → agents/registry/deployed_agent_discovery.py} +12 -2
  49. claude_mpm/services/{agent_modification_tracker.py → agents/registry/modification_tracker.py} +6 -5
  50. claude_mpm/services/async_session_logger.py +584 -0
  51. claude_mpm/services/claude_session_logger.py +299 -0
  52. claude_mpm/services/framework_claude_md_generator/content_assembler.py +2 -2
  53. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +17 -17
  54. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +3 -3
  55. claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +1 -1
  56. claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +1 -1
  57. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +19 -24
  58. claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +1 -1
  59. claude_mpm/services/framework_claude_md_generator.py +4 -2
  60. claude_mpm/services/memory/__init__.py +17 -0
  61. claude_mpm/services/{memory_builder.py → memory/builder.py} +3 -3
  62. claude_mpm/services/memory/cache/__init__.py +14 -0
  63. claude_mpm/services/{shared_prompt_cache.py → memory/cache/shared_prompt_cache.py} +1 -1
  64. claude_mpm/services/memory/cache/simple_cache.py +317 -0
  65. claude_mpm/services/{memory_optimizer.py → memory/optimizer.py} +1 -1
  66. claude_mpm/services/{memory_router.py → memory/router.py} +1 -1
  67. claude_mpm/services/optimized_hook_service.py +542 -0
  68. claude_mpm/services/project_registry.py +14 -8
  69. claude_mpm/services/response_tracker.py +237 -0
  70. claude_mpm/services/ticketing_service_original.py +4 -2
  71. claude_mpm/services/version_control/branch_strategy.py +3 -1
  72. claude_mpm/utils/paths.py +12 -10
  73. claude_mpm/utils/session_logging.py +114 -0
  74. claude_mpm/validation/agent_validator.py +2 -1
  75. {claude_mpm-3.4.26.dist-info → claude_mpm-3.5.0.dist-info}/METADATA +26 -20
  76. {claude_mpm-3.4.26.dist-info → claude_mpm-3.5.0.dist-info}/RECORD +83 -106
  77. claude_mpm/cli/commands/ui.py +0 -57
  78. claude_mpm/core/simple_runner.py +0 -1046
  79. claude_mpm/hooks/builtin/__init__.py +0 -1
  80. claude_mpm/hooks/builtin/logging_hook_example.py +0 -165
  81. claude_mpm/hooks/builtin/memory_hooks_example.py +0 -67
  82. claude_mpm/hooks/builtin/mpm_command_hook.py +0 -125
  83. claude_mpm/hooks/builtin/post_delegation_hook_example.py +0 -124
  84. claude_mpm/hooks/builtin/pre_delegation_hook_example.py +0 -125
  85. claude_mpm/hooks/builtin/submit_hook_example.py +0 -100
  86. claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +0 -237
  87. claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +0 -240
  88. claude_mpm/hooks/builtin/workflow_start_hook.py +0 -181
  89. claude_mpm/orchestration/__init__.py +0 -6
  90. claude_mpm/orchestration/archive/direct_orchestrator.py +0 -195
  91. claude_mpm/orchestration/archive/factory.py +0 -215
  92. claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +0 -188
  93. claude_mpm/orchestration/archive/hook_integration_example.py +0 -178
  94. claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +0 -826
  95. claude_mpm/orchestration/archive/orchestrator.py +0 -501
  96. claude_mpm/orchestration/archive/pexpect_orchestrator.py +0 -252
  97. claude_mpm/orchestration/archive/pty_orchestrator.py +0 -270
  98. claude_mpm/orchestration/archive/simple_orchestrator.py +0 -82
  99. claude_mpm/orchestration/archive/subprocess_orchestrator.py +0 -801
  100. claude_mpm/orchestration/archive/system_prompt_orchestrator.py +0 -278
  101. claude_mpm/orchestration/archive/wrapper_orchestrator.py +0 -187
  102. claude_mpm/schemas/workflow_validator.py +0 -411
  103. claude_mpm/services/parent_directory_manager/__init__.py +0 -577
  104. claude_mpm/services/parent_directory_manager/backup_manager.py +0 -258
  105. claude_mpm/services/parent_directory_manager/config_manager.py +0 -210
  106. claude_mpm/services/parent_directory_manager/deduplication_manager.py +0 -279
  107. claude_mpm/services/parent_directory_manager/framework_protector.py +0 -143
  108. claude_mpm/services/parent_directory_manager/operations.py +0 -186
  109. claude_mpm/services/parent_directory_manager/state_manager.py +0 -624
  110. claude_mpm/services/parent_directory_manager/template_deployer.py +0 -579
  111. claude_mpm/services/parent_directory_manager/validation_manager.py +0 -378
  112. claude_mpm/services/parent_directory_manager/version_control_helper.py +0 -339
  113. claude_mpm/services/parent_directory_manager/version_manager.py +0 -222
  114. claude_mpm/ui/__init__.py +0 -1
  115. claude_mpm/ui/rich_terminal_ui.py +0 -295
  116. claude_mpm/ui/terminal_ui.py +0 -328
  117. /claude_mpm/services/{agent_versioning.py → agents/deployment/agent_versioning.py} +0 -0
  118. /claude_mpm/services/{agent_capabilities_generator.py → agents/management/agent_capabilities_generator.py} +0 -0
  119. /claude_mpm/services/{agent_persistence_service.py → agents/memory/agent_persistence_service.py} +0 -0
  120. {claude_mpm-3.4.26.dist-info → claude_mpm-3.5.0.dist-info}/WHEEL +0 -0
  121. {claude_mpm-3.4.26.dist-info → claude_mpm-3.5.0.dist-info}/entry_points.txt +0 -0
  122. {claude_mpm-3.4.26.dist-info → claude_mpm-3.5.0.dist-info}/licenses/LICENSE +0 -0
  123. {claude_mpm-3.4.26.dist-info → claude_mpm-3.5.0.dist-info}/top_level.txt +0 -0
@@ -62,11 +62,14 @@ import time
62
62
  import yaml
63
63
  from pathlib import Path
64
64
  from typing import Optional, Dict, Any, Tuple, Union, List
65
+ from enum import Enum
65
66
 
66
- from ..services.shared_prompt_cache import SharedPromptCache
67
+ from claude_mpm.services.memory.cache.shared_prompt_cache import SharedPromptCache
67
68
  from .base_agent_loader import prepend_base_instructions
68
69
  from ..validation.agent_validator import AgentValidator, ValidationResult
69
70
  from ..utils.paths import PathResolver
71
+ from ..core.agent_name_normalizer import AgentNameNormalizer
72
+ from ..core.config_paths import ConfigPaths
70
73
 
71
74
  # Temporary placeholders for missing module
72
75
  # WHY: These classes would normally come from a task_complexity module, but
@@ -89,16 +92,63 @@ class ModelType:
89
92
  logger = logging.getLogger(__name__)
90
93
 
91
94
 
95
+ class AgentTier(Enum):
96
+ """Agent precedence tiers."""
97
+ PROJECT = "project" # Highest precedence - project-specific agents
98
+ USER = "user" # User-level agents from ~/.claude-mpm
99
+ SYSTEM = "system" # Lowest precedence - framework built-in agents
100
+
101
+
102
+ def _get_agent_templates_dirs() -> Dict[AgentTier, Optional[Path]]:
103
+ """
104
+ Get directories containing agent template JSON files across all tiers.
105
+
106
+ Returns a dictionary mapping tiers to their template directories:
107
+ - PROJECT: .claude-mpm/agents/templates in the current working directory
108
+ - USER: ~/.claude-mpm/agents/templates
109
+ - SYSTEM: Built-in templates relative to this module
110
+
111
+ WHY: We support multiple tiers to allow project-specific customization
112
+ while maintaining backward compatibility with system agents.
113
+
114
+ Returns:
115
+ Dict mapping AgentTier to Path (or None if not available)
116
+ """
117
+ dirs = {}
118
+
119
+ # PROJECT tier - ALWAYS check current working directory dynamically
120
+ # This ensures we pick up project agents even if CWD changes
121
+ project_dir = Path.cwd() / ConfigPaths.CONFIG_DIR / "agents" / "templates"
122
+ if project_dir.exists():
123
+ dirs[AgentTier.PROJECT] = project_dir
124
+ logger.debug(f"Found PROJECT agents at: {project_dir}")
125
+
126
+ # USER tier - check user home directory
127
+ user_config_dir = ConfigPaths.get_user_config_dir()
128
+ if user_config_dir:
129
+ user_agents_dir = user_config_dir / "agents" / "templates"
130
+ if user_agents_dir.exists():
131
+ dirs[AgentTier.USER] = user_agents_dir
132
+ logger.debug(f"Found USER agents at: {user_agents_dir}")
133
+
134
+ # SYSTEM tier - built-in templates
135
+ system_dir = Path(__file__).parent / "templates"
136
+ if system_dir.exists():
137
+ dirs[AgentTier.SYSTEM] = system_dir
138
+ logger.debug(f"Found SYSTEM agents at: {system_dir}")
139
+
140
+ return dirs
141
+
142
+
92
143
  def _get_agent_templates_dir() -> Path:
93
144
  """
94
- Get the directory containing agent template JSON files.
145
+ Get the primary directory containing agent template JSON files.
95
146
 
96
- WHY: We use a function instead of a direct constant to ensure the path
97
- is always resolved relative to this module's location, making the code
98
- portable across different installation methods (pip install, development mode, etc.).
147
+ DEPRECATED: Use _get_agent_templates_dirs() for tier-aware loading.
148
+ This function is kept for backward compatibility.
99
149
 
100
150
  Returns:
101
- Path: Absolute path to the templates directory
151
+ Path: Absolute path to the system templates directory
102
152
  """
103
153
  return Path(__file__).parent / "templates"
104
154
 
@@ -169,7 +219,8 @@ class AgentLoader:
169
219
  1. Creates validator for schema checking
170
220
  2. Gets shared cache instance for performance
171
221
  3. Initializes empty agent registry
172
- 4. Triggers agent discovery and loading
222
+ 4. Discovers template directories across all tiers
223
+ 5. Triggers agent discovery and loading
173
224
 
174
225
  METRICS OPPORTUNITIES:
175
226
  - Track initialization time
@@ -186,10 +237,17 @@ class AgentLoader:
186
237
  self.cache = SharedPromptCache.get_instance()
187
238
  self._agent_registry: Dict[str, Dict[str, Any]] = {}
188
239
 
240
+ # Template directories will be discovered dynamically during loading
241
+ self._template_dirs = {}
242
+
243
+ # Track which tier each agent came from for debugging
244
+ self._agent_tiers: Dict[str, AgentTier] = {}
245
+
189
246
  # METRICS: Initialize performance tracking
190
247
  # This structure collects valuable telemetry for AI agent performance
191
248
  self._metrics = {
192
249
  'agents_loaded': 0,
250
+ 'agents_by_tier': {tier.value: 0 for tier in AgentTier},
193
251
  'validation_failures': 0,
194
252
  'cache_hits': 0,
195
253
  'cache_misses': 0,
@@ -210,54 +268,90 @@ class AgentLoader:
210
268
 
211
269
  def _load_agents(self) -> None:
212
270
  """
213
- Discover and load all valid agents from the templates directory.
271
+ Discover and load all valid agents from all tier directories.
214
272
 
215
- This method implements the agent discovery mechanism:
216
- 1. Scans the templates directory for JSON files
217
- 2. Skips the schema definition file
218
- 3. Loads and validates each potential agent file
219
- 4. Registers valid agents in the internal registry
273
+ This method implements the agent discovery mechanism with tier precedence:
274
+ 1. Scans each tier directory (PROJECT USER → SYSTEM)
275
+ 2. Loads and validates each agent file
276
+ 3. Registers agents with precedence (PROJECT overrides USER overrides SYSTEM)
220
277
 
221
- WHY: We use a file-based discovery mechanism because:
222
- - It allows easy addition of new agents without code changes
223
- - Agents can be distributed as simple JSON files
224
- - The system remains extensible and maintainable
278
+ WHY: We use tier-based discovery to allow:
279
+ - Project-specific agent customization
280
+ - User-level agent modifications
281
+ - Fallback to system defaults
225
282
 
226
283
  Error Handling:
227
284
  - Invalid JSON files are logged but don't stop the loading process
228
285
  - Schema validation failures are logged with details
229
286
  - The system continues to function with whatever valid agents it finds
230
287
  """
231
- logger.info(f"Loading agents from {AGENT_TEMPLATES_DIR}")
288
+ # Dynamically discover template directories at load time
289
+ self._template_dirs = _get_agent_templates_dirs()
290
+
291
+ logger.info(f"Loading agents from {len(self._template_dirs)} tier(s)")
232
292
 
233
- for json_file in AGENT_TEMPLATES_DIR.glob("*.json"):
234
- # Skip the schema definition file itself
235
- if json_file.name == "agent_schema.json":
293
+ # Process tiers in REVERSE precedence order (SYSTEM first, PROJECT last)
294
+ # This ensures PROJECT agents override USER/SYSTEM agents
295
+ for tier in [AgentTier.SYSTEM, AgentTier.USER, AgentTier.PROJECT]:
296
+ if tier not in self._template_dirs:
236
297
  continue
237
-
238
- try:
239
- with open(json_file, 'r') as f:
240
- agent_data = json.load(f)
241
298
 
242
- # Validate against schema to ensure consistency
243
- validation_result = self.validator.validate_agent(agent_data)
299
+ templates_dir = self._template_dirs[tier]
300
+ logger.debug(f"Loading {tier.value} agents from {templates_dir}")
301
+
302
+ for json_file in templates_dir.glob("*.json"):
303
+ # Skip the schema definition file itself
304
+ if json_file.name == "agent_schema.json":
305
+ continue
244
306
 
245
- if validation_result.is_valid:
246
- agent_id = agent_data.get("agent_id")
247
- if agent_id:
248
- self._agent_registry[agent_id] = agent_data
249
- # METRICS: Track successful agent load
250
- self._metrics['agents_loaded'] += 1
251
- logger.debug(f"Loaded agent: {agent_id}")
252
- else:
253
- # Log validation errors but continue loading other agents
254
- # METRICS: Track validation failure
255
- self._metrics['validation_failures'] += 1
256
- logger.warning(f"Invalid agent in {json_file.name}: {validation_result.errors}")
307
+ try:
308
+ with open(json_file, 'r') as f:
309
+ agent_data = json.load(f)
310
+
311
+ # For files without _agent suffix, use the filename as agent_id
312
+ if "agent_id" not in agent_data:
313
+ agent_data["agent_id"] = json_file.stem
257
314
 
258
- except Exception as e:
259
- # Log loading errors but don't crash - system should be resilient
260
- logger.error(f"Failed to load {json_file.name}: {e}")
315
+ # Validate against schema to ensure consistency
316
+ # Skip validation for now if instructions are plain text (not in expected format)
317
+ if "instructions" in agent_data and isinstance(agent_data["instructions"], str) and len(agent_data["instructions"]) > 10000:
318
+ # For very long instructions, skip validation but log warning
319
+ logger.warning(f"Skipping validation for {json_file.name} due to long instructions")
320
+ validation_result = ValidationResult(is_valid=True, warnings=["Validation skipped due to long instructions"])
321
+ else:
322
+ validation_result = self.validator.validate_agent(agent_data)
323
+
324
+ if validation_result.is_valid:
325
+ agent_id = agent_data.get("agent_id")
326
+ if agent_id:
327
+ # Check if this agent was already loaded from a higher-precedence tier
328
+ if agent_id in self._agent_registry:
329
+ existing_tier = self._agent_tiers.get(agent_id)
330
+ # Only override if current tier has higher precedence
331
+ if tier == AgentTier.PROJECT or \
332
+ (tier == AgentTier.USER and existing_tier == AgentTier.SYSTEM):
333
+ logger.info(f"Overriding {existing_tier.value} agent '{agent_id}' with {tier.value} version")
334
+ else:
335
+ logger.debug(f"Skipping {tier.value} agent '{agent_id}' - already loaded from {existing_tier.value}")
336
+ continue
337
+
338
+ # Register the agent
339
+ self._agent_registry[agent_id] = agent_data
340
+ self._agent_tiers[agent_id] = tier
341
+
342
+ # METRICS: Track successful agent load
343
+ self._metrics['agents_loaded'] += 1
344
+ self._metrics['agents_by_tier'][tier.value] += 1
345
+ logger.debug(f"Loaded {tier.value} agent: {agent_id}")
346
+ else:
347
+ # Log validation errors but continue loading other agents
348
+ # METRICS: Track validation failure
349
+ self._metrics['validation_failures'] += 1
350
+ logger.warning(f"Invalid agent in {json_file.name}: {validation_result.errors}")
351
+
352
+ except Exception as e:
353
+ # Log loading errors but don't crash - system should be resilient
354
+ logger.error(f"Failed to load {json_file.name}: {e}")
261
355
 
262
356
  def get_agent(self, agent_id: str) -> Optional[Dict[str, Any]]:
263
357
  """
@@ -272,7 +366,12 @@ class AgentLoader:
272
366
  WHY: Direct dictionary lookup for O(1) performance, essential for
273
367
  frequently accessed agents during runtime.
274
368
  """
275
- return self._agent_registry.get(agent_id)
369
+ agent_data = self._agent_registry.get(agent_id)
370
+ if agent_data and agent_id in self._agent_tiers:
371
+ # Add tier information to the agent data for debugging
372
+ agent_data = agent_data.copy()
373
+ agent_data['_tier'] = self._agent_tiers[agent_id].value
374
+ return agent_data
276
375
 
277
376
  def list_agents(self) -> List[Dict[str, Any]]:
278
377
  """
@@ -385,6 +484,7 @@ class AgentLoader:
385
484
  - Load time analysis
386
485
  - Memory usage patterns
387
486
  - Error tracking
487
+ - Tier distribution
388
488
 
389
489
  This data could be:
390
490
  - Exposed via monitoring endpoints
@@ -412,6 +512,7 @@ class AgentLoader:
412
512
  return {
413
513
  'initialization_time_ms': self._metrics['initialization_time_ms'],
414
514
  'agents_loaded': self._metrics['agents_loaded'],
515
+ 'agents_by_tier': self._metrics['agents_by_tier'].copy(),
415
516
  'validation_failures': self._metrics['validation_failures'],
416
517
  'cache_hit_rate_percent': cache_hit_rate,
417
518
  'cache_hits': self._metrics['cache_hits'],
@@ -564,7 +665,7 @@ def _get_model_config(agent_name: str, complexity_analysis: Optional[Dict[str, A
564
665
  - Dynamic vs static selection rates
565
666
 
566
667
  Args:
567
- agent_name: Name of the agent requesting model selection
668
+ agent_name: Name of the agent requesting model selection (already normalized to agent_id format)
568
669
  complexity_analysis: Results from task complexity analysis (if available)
569
670
 
570
671
  Returns:
@@ -659,7 +760,7 @@ def get_agent_prompt(agent_name: str, force_reload: bool = False, return_model_i
659
760
  4. Adding metadata about model selection decisions
660
761
 
661
762
  Args:
662
- agent_name: Agent ID (e.g., "research_agent", "qa_agent")
763
+ agent_name: Agent name in any format (e.g., "Engineer", "research_agent", "QA")
663
764
  force_reload: Force reload from source, bypassing cache
664
765
  return_model_info: If True, returns extended info tuple
665
766
  **kwargs: Additional arguments:
@@ -676,12 +777,13 @@ def get_agent_prompt(agent_name: str, force_reload: bool = False, return_model_i
676
777
  ValueError: If the requested agent is not found
677
778
 
678
779
  Processing Flow:
679
- 1. Load agent instructions (with caching)
680
- 2. Analyze task complexity (if enabled and task_description provided)
681
- 3. Determine optimal model based on complexity
682
- 4. Add model selection metadata to prompt
683
- 5. Prepend base instructions
684
- 6. Return appropriate format based on return_model_info
780
+ 1. Normalize agent name to correct agent ID
781
+ 2. Load agent instructions (with caching)
782
+ 3. Analyze task complexity (if enabled and task_description provided)
783
+ 4. Determine optimal model based on complexity
784
+ 5. Add model selection metadata to prompt
785
+ 6. Prepend base instructions
786
+ 7. Return appropriate format based on return_model_info
685
787
 
686
788
  WHY: This comprehensive approach ensures:
687
789
  - Consistent prompt structure across all agents
@@ -689,11 +791,53 @@ def get_agent_prompt(agent_name: str, force_reload: bool = False, return_model_i
689
791
  - Transparency in model selection decisions
690
792
  - Flexibility for different use cases
691
793
  """
794
+ # Normalize the agent name to handle various formats
795
+ # Convert names like "Engineer", "Research", "QA" to the correct agent IDs
796
+ normalizer = AgentNameNormalizer()
797
+ loader = _get_loader()
798
+
799
+ # First check if agent exists with exact name
800
+ if loader.get_agent(agent_name):
801
+ actual_agent_id = agent_name
802
+ # Then check with _agent suffix
803
+ elif loader.get_agent(f"{agent_name}_agent"):
804
+ actual_agent_id = f"{agent_name}_agent"
805
+ # Check if this looks like it might already be an agent ID
806
+ elif agent_name.endswith("_agent"):
807
+ actual_agent_id = agent_name
808
+ else:
809
+ # Get the normalized key (e.g., "engineer", "research", "qa")
810
+ # First check if the agent name is recognized by the normalizer
811
+ cleaned = agent_name.strip().lower().replace("-", "_")
812
+
813
+ # Check if this is a known alias or canonical name
814
+ if cleaned in normalizer.ALIASES or cleaned in normalizer.CANONICAL_NAMES:
815
+ agent_key = normalizer.to_key(agent_name)
816
+ # Try both with and without _agent suffix
817
+ if loader.get_agent(agent_key):
818
+ actual_agent_id = agent_key
819
+ elif loader.get_agent(f"{agent_key}_agent"):
820
+ actual_agent_id = f"{agent_key}_agent"
821
+ else:
822
+ actual_agent_id = agent_key # Use normalized key
823
+ else:
824
+ # Unknown agent name - check both variations
825
+ if loader.get_agent(cleaned):
826
+ actual_agent_id = cleaned
827
+ elif loader.get_agent(f"{cleaned}_agent"):
828
+ actual_agent_id = f"{cleaned}_agent"
829
+ else:
830
+ actual_agent_id = cleaned # Use cleaned name
831
+
832
+ # Log the normalization for debugging
833
+ if agent_name != actual_agent_id:
834
+ logger.debug(f"Normalized agent name '{agent_name}' to '{actual_agent_id}'")
835
+
692
836
  # Load from JSON template via the loader
693
- prompt = load_agent_prompt_from_md(agent_name, force_reload)
837
+ prompt = load_agent_prompt_from_md(actual_agent_id, force_reload)
694
838
 
695
839
  if prompt is None:
696
- raise ValueError(f"No agent found with ID: {agent_name}")
840
+ raise ValueError(f"No agent found with name: {agent_name} (normalized to: {actual_agent_id})")
697
841
 
698
842
  # Analyze task complexity if task description is provided
699
843
  complexity_analysis = None
@@ -711,7 +855,8 @@ def get_agent_prompt(agent_name: str, force_reload: bool = False, return_model_i
711
855
  )
712
856
 
713
857
  # Get model configuration based on agent and complexity
714
- selected_model, model_config = _get_model_config(agent_name, complexity_analysis)
858
+ # Pass the normalized agent ID to _get_model_config
859
+ selected_model, model_config = _get_model_config(actual_agent_id, complexity_analysis)
715
860
 
716
861
  # Add model selection metadata to prompt for transparency
717
862
  # This helps with debugging and understanding model choices
@@ -1002,6 +1147,39 @@ def clear_agent_cache(agent_name: Optional[str] = None) -> None:
1002
1147
  logger.error(f"Error clearing agent cache: {e}")
1003
1148
 
1004
1149
 
1150
+ def list_agents_by_tier() -> Dict[str, List[str]]:
1151
+ """
1152
+ List available agents organized by their tier.
1153
+
1154
+ Returns:
1155
+ Dictionary mapping tier names to lists of agent IDs available in that tier
1156
+
1157
+ Example:
1158
+ {
1159
+ "project": ["engineer_agent", "custom_agent"],
1160
+ "user": ["research_agent"],
1161
+ "system": ["engineer_agent", "research_agent", "qa_agent", ...]
1162
+ }
1163
+
1164
+ This is useful for:
1165
+ - Understanding which agents are available at each level
1166
+ - Debugging agent precedence issues
1167
+ - UI display of agent sources
1168
+ """
1169
+ loader = _get_loader()
1170
+ result = {"project": [], "user": [], "system": []}
1171
+
1172
+ # Group agents by their loaded tier
1173
+ for agent_id, tier in loader._agent_tiers.items():
1174
+ result[tier.value].append(agent_id)
1175
+
1176
+ # Sort each list for consistent output
1177
+ for tier in result:
1178
+ result[tier].sort()
1179
+
1180
+ return result
1181
+
1182
+
1005
1183
  def validate_agent_files() -> Dict[str, Dict[str, Any]]:
1006
1184
  """
1007
1185
  Validate all agent template files against the schema.
@@ -1064,21 +1242,69 @@ def reload_agents() -> None:
1064
1242
  This function completely resets the agent loader state, causing:
1065
1243
  1. The global loader instance to be destroyed
1066
1244
  2. All cached agent prompts to be invalidated
1067
- 3. Fresh agent discovery on next access
1245
+ 3. Fresh agent discovery on next access across all tiers
1068
1246
 
1069
1247
  Use Cases:
1070
1248
  - Hot-reloading during development
1071
1249
  - Picking up new agent files without restart
1072
1250
  - Recovering from corrupted state
1073
1251
  - Testing agent loading logic
1252
+ - Switching between projects with different agents
1074
1253
 
1075
1254
  WHY: Hot-reloading is essential for development productivity and
1076
1255
  allows dynamic agent updates in production without service restart.
1077
1256
 
1078
1257
  Implementation Note: We simply clear the global loader reference.
1079
1258
  The next call to _get_loader() will create a fresh instance that
1080
- re-discovers and re-validates all agents.
1259
+ re-discovers and re-validates all agents across all tiers.
1081
1260
  """
1082
1261
  global _loader
1083
1262
  _loader = None
1084
- logger.info("Agent registry cleared, will reload on next access")
1263
+ logger.info("Agent registry cleared, will reload on next access")
1264
+
1265
+
1266
+ def get_agent_tier(agent_name: str) -> Optional[str]:
1267
+ """
1268
+ Get the tier from which an agent was loaded.
1269
+
1270
+ Args:
1271
+ agent_name: Agent name or ID
1272
+
1273
+ Returns:
1274
+ Tier name ("project", "user", or "system") or None if not found
1275
+
1276
+ This is useful for debugging and understanding which version of an
1277
+ agent is being used when multiple versions exist across tiers.
1278
+ """
1279
+ loader = _get_loader()
1280
+
1281
+ # Check if agent exists with exact name
1282
+ if agent_name in loader._agent_tiers:
1283
+ tier = loader._agent_tiers[agent_name]
1284
+ return tier.value if tier else None
1285
+
1286
+ # Try with _agent suffix
1287
+ agent_with_suffix = f"{agent_name}_agent"
1288
+ if agent_with_suffix in loader._agent_tiers:
1289
+ tier = loader._agent_tiers[agent_with_suffix]
1290
+ return tier.value if tier else None
1291
+
1292
+ # Try normalizing the name
1293
+ normalizer = AgentNameNormalizer()
1294
+ cleaned = agent_name.strip().lower().replace("-", "_")
1295
+
1296
+ if cleaned in normalizer.ALIASES or cleaned in normalizer.CANONICAL_NAMES:
1297
+ agent_key = normalizer.to_key(agent_name)
1298
+ # Try both with and without suffix
1299
+ for candidate in [agent_key, f"{agent_key}_agent"]:
1300
+ if candidate in loader._agent_tiers:
1301
+ tier = loader._agent_tiers[candidate]
1302
+ return tier.value if tier else None
1303
+
1304
+ # Try cleaned name with and without suffix
1305
+ for candidate in [cleaned, f"{cleaned}_agent"]:
1306
+ if candidate in loader._agent_tiers:
1307
+ tier = loader._agent_tiers[candidate]
1308
+ return tier.value if tier else None
1309
+
1310
+ return None
@@ -12,11 +12,9 @@ from typing import Optional, Dict, Any
12
12
 
13
13
  from .agent_loader import (
14
14
  load_agent_prompt_from_md,
15
- get_agent_prompt,
16
- AGENT_MAPPINGS,
17
- FRAMEWORK_AGENT_ROLES_DIR
15
+ get_agent_prompt
18
16
  )
19
- from ..services.agent_management_service import AgentManager
17
+ from ..services import AgentManager
20
18
  from ..models.agent_definition import AgentDefinition
21
19
 
22
20
  logger = logging.getLogger(__name__)
@@ -40,8 +38,8 @@ class EnhancedAgentLoader:
40
38
  AgentDefinition or None
41
39
  """
42
40
  # Map from old naming to new naming if needed
43
- md_filename = AGENT_MAPPINGS.get(agent_name, f"{agent_name}.md")
44
- agent_file_name = md_filename.replace('.md', '')
41
+ # Since AGENT_MAPPINGS no longer exists, use direct naming
42
+ agent_file_name = agent_name
45
43
 
46
44
  return self.manager.read_agent(agent_file_name)
47
45
 
@@ -127,9 +125,8 @@ class EnhancedAgentLoader:
127
125
  Returns:
128
126
  True if updated, False otherwise
129
127
  """
130
- # Map name
131
- md_filename = AGENT_MAPPINGS.get(agent_name, f"{agent_name}.md")
132
- agent_file_name = md_filename.replace('.md', '')
128
+ # Map name - since AGENT_MAPPINGS no longer exists, use direct naming
129
+ agent_file_name = agent_name
133
130
 
134
131
  result = self.manager.update_agent(agent_file_name, {}, increment_version=True)
135
132
  return result is not None
@@ -1,5 +1,6 @@
1
1
  {
2
- "version": 2,
2
+ "version": 3,
3
+ "base_version": "0.3.0",
3
4
  "agent_type": "base",
4
5
  "narrative_fields": {
5
6
  "instructions": "# Claude MPM Framework Agent\n\nYou are a specialized agent in the Claude MPM framework. Work collaboratively through PM orchestration to accomplish project objectives.\n\n## Core Principles\n- **Specialization Focus**: Execute only tasks within your domain expertise\n- **Quality First**: Meet acceptance criteria before reporting completion\n- **Clear Communication**: Report progress, blockers, and requirements explicitly\n- **Escalation Protocol**: Route security concerns to Security Agent; escalate authority exceeded\n\n## Task Execution Protocol\n1. **Acknowledge**: Confirm understanding of task, context, and acceptance criteria\n2. **Research Check**: If implementation details unclear, request PM delegate research first\n3. **Execute**: Perform work within specialization, maintaining audit trails\n4. **Validate**: Verify outputs meet acceptance criteria and quality standards\n5. **Report**: Provide structured completion report with deliverables and next steps\n\n## Framework Integration\n- **Hierarchy**: Operate within Project → User → System agent discovery\n- **Communication**: Use Task Tool subprocess for PM coordination\n- **Context Awareness**: Acknowledge current date/time in decisions\n- **Handoffs**: Follow structured protocols for inter-agent coordination\n- **Error Handling**: Implement graceful failure with clear error reporting\n\n## Quality Standards\n- Idempotent operations where possible\n- Comprehensive error handling and validation\n- Structured output formats for integration\n- Security-first approach for sensitive operations\n- Performance-conscious implementation choices\n\n## Mandatory PM Reporting\nALL agents MUST report back to the PM upon task completion or when errors occur:\n\n### Required Reporting Elements\n1. **Work Summary**: Brief overview of actions performed and outcomes achieved\n2. **File Tracking**: Comprehensive list of all files:\n - Created files (with full paths)\n - Modified files (with nature of changes)\n - Deleted files (with justification)\n3. **Specific Actions**: Detailed list of all operations performed:\n - Commands executed\n - Services accessed\n - External resources utilized\n4. **Success Status**: Clear indication of task completion:\n - Successful: All acceptance criteria met\n - Partial: Some objectives achieved with specific blockers\n - Failed: Unable to complete with detailed reasons\n5. **Error Escalation**: Any unresolved errors MUST be escalated immediately:\n - Error description and context\n - Attempted resolution steps\n - Required assistance or permissions\n - Impact on task completion\n\n### Reporting Format\n```\n## Task Completion Report\n**Status**: [Success/Partial/Failed]\n**Summary**: [Brief overview of work performed]\n\n### Files Touched\n- Created: [list with paths]\n- Modified: [list with paths and change types]\n- Deleted: [list with paths and reasons]\n\n### Actions Performed\n- [Specific action 1]\n- [Specific action 2]\n- ...\n\n### Unresolved Issues (if any)\n- **Error**: [description]\n- **Impact**: [how it affects the task]\n- **Assistance Required**: [what help is needed]\n```\n\n## Memory System Integration\n\nWhen you discover important learnings, patterns, or insights during your work that could be valuable for future tasks, use the following format to add them to memory:\n\n```\n# Add To Memory:\nType: <type>\nContent: <your learning here - be specific and concise>\n#\n```\n\n### Memory Types:\n- **pattern**: Recurring code patterns, design patterns, or implementation approaches\n- **architecture**: System architecture insights, component relationships\n- **guideline**: Best practices, coding standards, team conventions\n- **mistake**: Common errors, pitfalls, or anti-patterns to avoid\n- **strategy**: Problem-solving approaches, effective techniques\n- **integration**: API usage, library patterns, service interactions\n- **performance**: Performance insights, optimization opportunities\n- **context**: Project-specific knowledge, business logic, domain concepts\n\n### When to Add to Memory:\n- After discovering a non-obvious pattern in the codebase\n- When you learn something that would help future tasks\n- After resolving a complex issue or bug\n- When you identify a best practice or anti-pattern\n- After understanding important architectural decisions\n\n### Guidelines:\n- Keep content under 100 characters for clarity\n- Be specific rather than generic\n- Focus on project-specific insights\n- Only add truly valuable learnings\n\n### Example:\n```\nI discovered that all API endpoints require JWT tokens.\n\n# Add To Memory:\nType: pattern\nContent: All API endpoints use JWT bearer tokens with 24-hour expiration\n#\n```"
@@ -28,7 +28,7 @@ from pathlib import Path
28
28
  from typing import Optional, Dict, Any
29
29
  from enum import Enum
30
30
 
31
- from ..services.shared_prompt_cache import SharedPromptCache
31
+ from claude_mpm.services.memory.cache.shared_prompt_cache import SharedPromptCache
32
32
 
33
33
  # Module-level logger
34
34
  logger = logging.getLogger(__name__)
@@ -22,22 +22,20 @@ from .commands import (
22
22
  list_tickets,
23
23
  show_info,
24
24
  manage_agents,
25
- run_terminal_ui,
26
25
  manage_memory,
27
- manage_monitor,
28
- run_manager
26
+ manage_monitor
29
27
  )
28
+ from claude_mpm.config.paths import paths
30
29
 
31
- # Get version from VERSION file - single source of truth
30
+ # Get version using centralized path management
32
31
  # Try package VERSION file first (for installed packages)
33
32
  package_version_file = Path(__file__).parent.parent / "VERSION"
34
33
  if package_version_file.exists():
35
34
  __version__ = package_version_file.read_text().strip()
36
35
  else:
37
- # Fall back to project root VERSION file (for development)
38
- root_version_file = Path(__file__).parent.parent.parent.parent / "VERSION"
39
- if root_version_file.exists():
40
- __version__ = root_version_file.read_text().strip()
36
+ # Use centralized path management for VERSION file
37
+ if paths.version_file.exists():
38
+ __version__ = paths.version_file.read_text().strip()
41
39
  else:
42
40
  # Try to import from package as fallback
43
41
  try:
@@ -181,10 +179,8 @@ def _execute_command(command: str, args) -> int:
181
179
  CLICommands.TICKETS.value: list_tickets,
182
180
  CLICommands.INFO.value: show_info,
183
181
  CLICommands.AGENTS.value: manage_agents,
184
- CLICommands.UI.value: run_terminal_ui,
185
182
  CLICommands.MEMORY.value: manage_memory,
186
183
  CLICommands.MONITOR.value: manage_monitor,
187
- CLICommands.MANAGER.value: run_manager,
188
184
  }
189
185
 
190
186
  # Execute command if found
@@ -9,7 +9,6 @@ from .run import run_session
9
9
  from .tickets import list_tickets
10
10
  from .info import show_info
11
11
  from .agents import manage_agents
12
- from .ui import run_terminal_ui
13
12
  from .memory import manage_memory
14
13
  from .monitor import manage_monitor
15
14
 
@@ -18,7 +17,6 @@ __all__ = [
18
17
  'list_tickets',
19
18
  'show_info',
20
19
  'manage_agents',
21
- 'run_terminal_ui',
22
20
  'manage_memory',
23
21
  'manage_monitor'
24
22
  ]
@@ -28,7 +28,7 @@ def manage_agents(args):
28
28
  logger = get_logger("cli")
29
29
 
30
30
  try:
31
- from ...services.agent_deployment import AgentDeploymentService
31
+ from ...services import AgentDeploymentService
32
32
  deployment_service = AgentDeploymentService()
33
33
 
34
34
  if not args.agents_command:
@@ -18,7 +18,7 @@ import click
18
18
 
19
19
  from ...core.logger import get_logger
20
20
  from ...core.config import Config
21
- from ...services.agent_memory_manager import AgentMemoryManager
21
+ from ...services.agents.memory import AgentMemoryManager
22
22
 
23
23
 
24
24
  def manage_memory(args):
@@ -281,6 +281,18 @@ def run_session(args):
281
281
  websocket_port=websocket_port
282
282
  )
283
283
 
284
+ # Ensure project agents are available if we're in a project directory
285
+ # This deploys system agents to .claude-mpm/agents/ for local customization
286
+ if not hasattr(args, 'no_native_agents') or not args.no_native_agents:
287
+ # Check if we're in a project directory (has .git or other markers)
288
+ project_markers = ['.git', 'pyproject.toml', 'package.json', 'requirements.txt']
289
+ cwd = Path.cwd()
290
+ is_project = any((cwd / marker).exists() for marker in project_markers)
291
+
292
+ if is_project:
293
+ logger.debug("Detected project directory, ensuring agents are available locally")
294
+ runner.ensure_project_agents()
295
+
284
296
  # Set browser opening flag for monitor mode
285
297
  if monitor_mode:
286
298
  runner._should_open_monitor_browser = True
claude_mpm/cli/parser.py CHANGED
@@ -278,19 +278,6 @@ def create_parser(prog_name: str = "claude-mpm", version: str = "0.0.0") -> argp
278
278
  )
279
279
  add_common_arguments(info_parser)
280
280
 
281
- # UI command
282
- ui_parser = subparsers.add_parser(
283
- CLICommands.UI.value,
284
- help="Launch terminal UI with multiple panes"
285
- )
286
- add_common_arguments(ui_parser)
287
- ui_parser.add_argument(
288
- "--mode",
289
- choices=["terminal", "curses"],
290
- default="terminal",
291
- help="UI mode to launch (default: terminal)"
292
- )
293
-
294
281
  # Agents command with subcommands
295
282
  agents_parser = subparsers.add_parser(
296
283
  CLICommands.AGENTS.value,