claude-mpm 3.4.27__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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/INSTRUCTIONS.md +182 -299
- claude_mpm/agents/agent_loader.py +283 -57
- claude_mpm/agents/agent_loader_integration.py +6 -9
- claude_mpm/agents/base_agent.json +2 -1
- claude_mpm/agents/base_agent_loader.py +1 -1
- claude_mpm/cli/__init__.py +5 -7
- claude_mpm/cli/commands/__init__.py +0 -2
- claude_mpm/cli/commands/agents.py +1 -1
- claude_mpm/cli/commands/memory.py +1 -1
- claude_mpm/cli/commands/run.py +12 -0
- claude_mpm/cli/parser.py +0 -13
- claude_mpm/cli/utils.py +1 -1
- claude_mpm/config/__init__.py +44 -2
- claude_mpm/config/agent_config.py +348 -0
- claude_mpm/config/paths.py +322 -0
- claude_mpm/constants.py +0 -1
- claude_mpm/core/__init__.py +2 -5
- claude_mpm/core/agent_registry.py +63 -17
- claude_mpm/core/claude_runner.py +354 -43
- claude_mpm/core/config.py +7 -1
- claude_mpm/core/config_aliases.py +4 -3
- claude_mpm/core/config_paths.py +151 -0
- claude_mpm/core/factories.py +4 -50
- claude_mpm/core/logger.py +11 -13
- claude_mpm/core/service_registry.py +2 -2
- claude_mpm/dashboard/static/js/components/agent-inference.js +101 -25
- claude_mpm/dashboard/static/js/components/event-processor.js +3 -2
- claude_mpm/hooks/claude_hooks/hook_handler.py +343 -83
- claude_mpm/hooks/memory_integration_hook.py +1 -1
- claude_mpm/init.py +37 -6
- claude_mpm/scripts/socketio_daemon.py +6 -2
- claude_mpm/services/__init__.py +71 -3
- claude_mpm/services/agents/__init__.py +85 -0
- claude_mpm/services/agents/deployment/__init__.py +21 -0
- claude_mpm/services/{agent_deployment.py → agents/deployment/agent_deployment.py} +192 -41
- claude_mpm/services/{agent_lifecycle_manager.py → agents/deployment/agent_lifecycle_manager.py} +11 -10
- claude_mpm/services/agents/loading/__init__.py +11 -0
- claude_mpm/services/{agent_profile_loader.py → agents/loading/agent_profile_loader.py} +9 -8
- claude_mpm/services/{base_agent_manager.py → agents/loading/base_agent_manager.py} +2 -2
- claude_mpm/services/{framework_agent_loader.py → agents/loading/framework_agent_loader.py} +116 -40
- claude_mpm/services/agents/management/__init__.py +9 -0
- claude_mpm/services/{agent_management_service.py → agents/management/agent_management_service.py} +6 -5
- claude_mpm/services/agents/memory/__init__.py +21 -0
- claude_mpm/services/{agent_memory_manager.py → agents/memory/agent_memory_manager.py} +3 -3
- claude_mpm/services/agents/registry/__init__.py +29 -0
- claude_mpm/services/{agent_registry.py → agents/registry/agent_registry.py} +101 -16
- claude_mpm/services/{deployed_agent_discovery.py → agents/registry/deployed_agent_discovery.py} +12 -2
- claude_mpm/services/{agent_modification_tracker.py → agents/registry/modification_tracker.py} +6 -5
- claude_mpm/services/async_session_logger.py +584 -0
- claude_mpm/services/claude_session_logger.py +299 -0
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +2 -2
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +17 -17
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +3 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +1 -1
- claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +1 -1
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +19 -24
- claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +1 -1
- claude_mpm/services/framework_claude_md_generator.py +4 -2
- claude_mpm/services/memory/__init__.py +17 -0
- claude_mpm/services/{memory_builder.py → memory/builder.py} +3 -3
- claude_mpm/services/memory/cache/__init__.py +14 -0
- claude_mpm/services/{shared_prompt_cache.py → memory/cache/shared_prompt_cache.py} +1 -1
- claude_mpm/services/memory/cache/simple_cache.py +317 -0
- claude_mpm/services/{memory_optimizer.py → memory/optimizer.py} +1 -1
- claude_mpm/services/{memory_router.py → memory/router.py} +1 -1
- claude_mpm/services/optimized_hook_service.py +542 -0
- claude_mpm/services/project_registry.py +14 -8
- claude_mpm/services/response_tracker.py +237 -0
- claude_mpm/services/ticketing_service_original.py +4 -2
- claude_mpm/services/version_control/branch_strategy.py +3 -1
- claude_mpm/utils/paths.py +12 -10
- claude_mpm/utils/session_logging.py +114 -0
- claude_mpm/validation/agent_validator.py +2 -1
- {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/METADATA +26 -20
- {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/RECORD +83 -106
- claude_mpm/cli/commands/ui.py +0 -57
- claude_mpm/core/simple_runner.py +0 -1046
- claude_mpm/hooks/builtin/__init__.py +0 -1
- claude_mpm/hooks/builtin/logging_hook_example.py +0 -165
- claude_mpm/hooks/builtin/memory_hooks_example.py +0 -67
- claude_mpm/hooks/builtin/mpm_command_hook.py +0 -125
- claude_mpm/hooks/builtin/post_delegation_hook_example.py +0 -124
- claude_mpm/hooks/builtin/pre_delegation_hook_example.py +0 -125
- claude_mpm/hooks/builtin/submit_hook_example.py +0 -100
- claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +0 -237
- claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +0 -240
- claude_mpm/hooks/builtin/workflow_start_hook.py +0 -181
- claude_mpm/orchestration/__init__.py +0 -6
- claude_mpm/orchestration/archive/direct_orchestrator.py +0 -195
- claude_mpm/orchestration/archive/factory.py +0 -215
- claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +0 -188
- claude_mpm/orchestration/archive/hook_integration_example.py +0 -178
- claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +0 -826
- claude_mpm/orchestration/archive/orchestrator.py +0 -501
- claude_mpm/orchestration/archive/pexpect_orchestrator.py +0 -252
- claude_mpm/orchestration/archive/pty_orchestrator.py +0 -270
- claude_mpm/orchestration/archive/simple_orchestrator.py +0 -82
- claude_mpm/orchestration/archive/subprocess_orchestrator.py +0 -801
- claude_mpm/orchestration/archive/system_prompt_orchestrator.py +0 -278
- claude_mpm/orchestration/archive/wrapper_orchestrator.py +0 -187
- claude_mpm/schemas/workflow_validator.py +0 -411
- claude_mpm/services/parent_directory_manager/__init__.py +0 -577
- claude_mpm/services/parent_directory_manager/backup_manager.py +0 -258
- claude_mpm/services/parent_directory_manager/config_manager.py +0 -210
- claude_mpm/services/parent_directory_manager/deduplication_manager.py +0 -279
- claude_mpm/services/parent_directory_manager/framework_protector.py +0 -143
- claude_mpm/services/parent_directory_manager/operations.py +0 -186
- claude_mpm/services/parent_directory_manager/state_manager.py +0 -624
- claude_mpm/services/parent_directory_manager/template_deployer.py +0 -579
- claude_mpm/services/parent_directory_manager/validation_manager.py +0 -378
- claude_mpm/services/parent_directory_manager/version_control_helper.py +0 -339
- claude_mpm/services/parent_directory_manager/version_manager.py +0 -222
- claude_mpm/ui/__init__.py +0 -1
- claude_mpm/ui/rich_terminal_ui.py +0 -295
- claude_mpm/ui/terminal_ui.py +0 -328
- /claude_mpm/services/{agent_versioning.py → agents/deployment/agent_versioning.py} +0 -0
- /claude_mpm/services/{agent_capabilities_generator.py → agents/management/agent_capabilities_generator.py} +0 -0
- /claude_mpm/services/{agent_persistence_service.py → agents/memory/agent_persistence_service.py} +0 -0
- {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/WHEEL +0 -0
- {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.4.27.dist-info → claude_mpm-3.5.0.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.4.27.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
|
|
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
|
-
|
|
97
|
-
|
|
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.
|
|
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
|
|
271
|
+
Discover and load all valid agents from all tier directories.
|
|
214
272
|
|
|
215
|
-
This method implements the agent discovery mechanism:
|
|
216
|
-
1. Scans
|
|
217
|
-
2.
|
|
218
|
-
3.
|
|
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
|
|
222
|
-
-
|
|
223
|
-
-
|
|
224
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
243
|
-
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
680
|
-
2.
|
|
681
|
-
3.
|
|
682
|
-
4.
|
|
683
|
-
5.
|
|
684
|
-
6.
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
44
|
-
agent_file_name =
|
|
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
|
-
|
|
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
|
+
"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
|
|
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__)
|
claude_mpm/cli/__init__.py
CHANGED
|
@@ -22,21 +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
26
|
manage_monitor
|
|
28
27
|
)
|
|
28
|
+
from claude_mpm.config.paths import paths
|
|
29
29
|
|
|
30
|
-
# Get version
|
|
30
|
+
# Get version using centralized path management
|
|
31
31
|
# Try package VERSION file first (for installed packages)
|
|
32
32
|
package_version_file = Path(__file__).parent.parent / "VERSION"
|
|
33
33
|
if package_version_file.exists():
|
|
34
34
|
__version__ = package_version_file.read_text().strip()
|
|
35
35
|
else:
|
|
36
|
-
#
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
__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()
|
|
40
39
|
else:
|
|
41
40
|
# Try to import from package as fallback
|
|
42
41
|
try:
|
|
@@ -180,7 +179,6 @@ def _execute_command(command: str, args) -> int:
|
|
|
180
179
|
CLICommands.TICKETS.value: list_tickets,
|
|
181
180
|
CLICommands.INFO.value: show_info,
|
|
182
181
|
CLICommands.AGENTS.value: manage_agents,
|
|
183
|
-
CLICommands.UI.value: run_terminal_ui,
|
|
184
182
|
CLICommands.MEMORY.value: manage_memory,
|
|
185
183
|
CLICommands.MONITOR.value: manage_monitor,
|
|
186
184
|
}
|
|
@@ -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
|
|
31
|
+
from ...services import AgentDeploymentService
|
|
32
32
|
deployment_service = AgentDeploymentService()
|
|
33
33
|
|
|
34
34
|
if not args.agents_command:
|
claude_mpm/cli/commands/run.py
CHANGED
|
@@ -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,
|