claude-mpm 1.1.0__py3-none-any.whl → 2.1.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.py +4 -33
- claude_mpm/agents/INSTRUCTIONS.md +109 -319
- claude_mpm/agents/agent_loader.py +184 -278
- claude_mpm/agents/base_agent.json +1 -1
- claude_mpm/agents/templates/backup/data_engineer_agent_20250726_234551.json +46 -0
- claude_mpm/agents/templates/{engineer_agent.json → backup/engineer_agent_20250726_234551.json} +1 -1
- claude_mpm/agents/templates/data_engineer.json +107 -0
- claude_mpm/agents/templates/documentation.json +106 -0
- claude_mpm/agents/templates/engineer.json +110 -0
- claude_mpm/agents/templates/ops.json +106 -0
- claude_mpm/agents/templates/qa.json +106 -0
- claude_mpm/agents/templates/research.json +75 -0
- claude_mpm/agents/templates/security.json +105 -0
- claude_mpm/agents/templates/version_control.json +103 -0
- claude_mpm/cli.py +80 -11
- claude_mpm/core/simple_runner.py +45 -5
- claude_mpm/hooks/claude_hooks/hook_handler.py +115 -1
- claude_mpm/schemas/agent_schema.json +328 -0
- claude_mpm/services/agent_capabilities_generator.py +182 -0
- claude_mpm/services/agent_deployment.py +228 -37
- claude_mpm/services/deployed_agent_discovery.py +222 -0
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +29 -0
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +29 -7
- claude_mpm/utils/framework_detection.py +39 -0
- claude_mpm/validation/agent_validator.py +252 -125
- {claude_mpm-1.1.0.dist-info → claude_mpm-2.1.0.dist-info}/METADATA +108 -26
- {claude_mpm-1.1.0.dist-info → claude_mpm-2.1.0.dist-info}/RECORD +36 -25
- claude_mpm/agents/templates/data_engineer_agent.json +0 -46
- claude_mpm/agents/templates/update-optimized-specialized-agents.json +0 -374
- /claude_mpm/agents/templates/{documentation_agent.json → backup/documentation_agent_20250726_234551.json} +0 -0
- /claude_mpm/agents/templates/{ops_agent.json → backup/ops_agent_20250726_234551.json} +0 -0
- /claude_mpm/agents/templates/{qa_agent.json → backup/qa_agent_20250726_234551.json} +0 -0
- /claude_mpm/agents/templates/{research_agent.json → backup/research_agent_20250726_234551.json} +0 -0
- /claude_mpm/agents/templates/{security_agent.json → backup/security_agent_20250726_234551.json} +0 -0
- /claude_mpm/agents/templates/{version_control_agent.json → backup/version_control_agent_20250726_234551.json} +0 -0
- {claude_mpm-1.1.0.dist-info → claude_mpm-2.1.0.dist-info}/WHEEL +0 -0
- {claude_mpm-1.1.0.dist-info → claude_mpm-2.1.0.dist-info}/entry_points.txt +0 -0
- {claude_mpm-1.1.0.dist-info → claude_mpm-2.1.0.dist-info}/top_level.txt +0 -0
|
@@ -3,19 +3,15 @@
|
|
|
3
3
|
Unified Agent Loader System
|
|
4
4
|
==========================
|
|
5
5
|
|
|
6
|
-
Provides unified loading of agent prompts from JSON template files
|
|
7
|
-
|
|
6
|
+
Provides unified loading of agent prompts from JSON template files using
|
|
7
|
+
the new standardized schema format.
|
|
8
8
|
|
|
9
9
|
Key Features:
|
|
10
10
|
- Loads agent prompts from src/claude_mpm/agents/templates/*.json files
|
|
11
11
|
- Handles base_agent.md prepending
|
|
12
12
|
- Provides backward-compatible get_*_agent_prompt() functions
|
|
13
13
|
- Uses SharedPromptCache for performance
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
For advanced agent management features (CRUD, versioning, section updates), use:
|
|
17
|
-
from claude_pm.agents.agent_loader_integration import get_enhanced_loader
|
|
18
|
-
from claude_pm.services.agent_management_service import AgentManager
|
|
14
|
+
- Validates agents against schema before loading
|
|
19
15
|
|
|
20
16
|
Usage:
|
|
21
17
|
from claude_pm.agents.agent_loader import get_documentation_agent_prompt
|
|
@@ -24,14 +20,15 @@ Usage:
|
|
|
24
20
|
prompt = get_documentation_agent_prompt()
|
|
25
21
|
"""
|
|
26
22
|
|
|
23
|
+
import json
|
|
27
24
|
import logging
|
|
28
25
|
import os
|
|
29
26
|
from pathlib import Path
|
|
30
|
-
from typing import Optional, Dict, Any, Tuple, Union
|
|
27
|
+
from typing import Optional, Dict, Any, Tuple, Union, List
|
|
31
28
|
|
|
32
29
|
from ..services.shared_prompt_cache import SharedPromptCache
|
|
33
30
|
from .base_agent_loader import prepend_base_instructions
|
|
34
|
-
|
|
31
|
+
from ..validation.agent_validator import AgentValidator, ValidationResult
|
|
35
32
|
from ..utils.paths import PathResolver
|
|
36
33
|
|
|
37
34
|
# Temporary placeholders for missing module
|
|
@@ -51,7 +48,6 @@ logger = logging.getLogger(__name__)
|
|
|
51
48
|
|
|
52
49
|
def _get_agent_templates_dir() -> Path:
|
|
53
50
|
"""Get the agent templates directory."""
|
|
54
|
-
# Agent templates are now in the agents/templates directory
|
|
55
51
|
return Path(__file__).parent / "templates"
|
|
56
52
|
|
|
57
53
|
|
|
@@ -59,20 +55,7 @@ def _get_agent_templates_dir() -> Path:
|
|
|
59
55
|
AGENT_TEMPLATES_DIR = _get_agent_templates_dir()
|
|
60
56
|
|
|
61
57
|
# Cache prefix for agent prompts
|
|
62
|
-
AGENT_CACHE_PREFIX = "agent_prompt:"
|
|
63
|
-
|
|
64
|
-
# Agent name mappings (agent name -> JSON file name)
|
|
65
|
-
AGENT_MAPPINGS = {
|
|
66
|
-
"documentation": "documentation_agent.json",
|
|
67
|
-
"version_control": "version_control_agent.json",
|
|
68
|
-
"qa": "qa_agent.json",
|
|
69
|
-
"research": "research_agent.json",
|
|
70
|
-
"ops": "ops_agent.json",
|
|
71
|
-
"security": "security_agent.json",
|
|
72
|
-
"engineer": "engineer_agent.json",
|
|
73
|
-
"data_engineer": "data_engineer_agent.json"
|
|
74
|
-
# Note: pm, orchestrator, and pm_orchestrator agents are handled separately
|
|
75
|
-
}
|
|
58
|
+
AGENT_CACHE_PREFIX = "agent_prompt:v2:"
|
|
76
59
|
|
|
77
60
|
# Model configuration thresholds
|
|
78
61
|
MODEL_THRESHOLDS = {
|
|
@@ -81,99 +64,138 @@ MODEL_THRESHOLDS = {
|
|
|
81
64
|
ModelType.OPUS: {"min_complexity": 71, "max_complexity": 100}
|
|
82
65
|
}
|
|
83
66
|
|
|
84
|
-
#
|
|
85
|
-
DEFAULT_AGENT_MODELS = {
|
|
86
|
-
'orchestrator': 'claude-4-opus',
|
|
87
|
-
'pm': 'claude-4-opus',
|
|
88
|
-
'pm_orchestrator': 'claude-4-opus',
|
|
89
|
-
'engineer': 'claude-4-opus',
|
|
90
|
-
'architecture': 'claude-4-opus',
|
|
91
|
-
'documentation': 'claude-sonnet-4-20250514',
|
|
92
|
-
'version_control': 'claude-sonnet-4-20250514',
|
|
93
|
-
'qa': 'claude-sonnet-4-20250514',
|
|
94
|
-
'research': 'claude-sonnet-4-20250514',
|
|
95
|
-
'ops': 'claude-sonnet-4-20250514',
|
|
96
|
-
'security': 'claude-sonnet-4-20250514',
|
|
97
|
-
'data_engineer': 'claude-sonnet-4-20250514'
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
# Model name mappings for Claude API
|
|
67
|
+
# Model name mappings for Claude API (updated for new schema)
|
|
101
68
|
MODEL_NAME_MAPPINGS = {
|
|
102
69
|
ModelType.HAIKU: "claude-3-haiku-20240307",
|
|
103
70
|
ModelType.SONNET: "claude-sonnet-4-20250514",
|
|
104
|
-
ModelType.OPUS: "claude-4-
|
|
71
|
+
ModelType.OPUS: "claude-opus-4-20250514"
|
|
105
72
|
}
|
|
106
73
|
|
|
107
74
|
|
|
108
|
-
|
|
109
|
-
"""
|
|
110
|
-
Load agent prompt from JSON template file.
|
|
75
|
+
class AgentLoader:
|
|
76
|
+
"""Loads and manages agent templates with schema validation."""
|
|
111
77
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
78
|
+
def __init__(self):
|
|
79
|
+
"""Initialize the agent loader."""
|
|
80
|
+
self.validator = AgentValidator()
|
|
81
|
+
self.cache = SharedPromptCache.get_instance()
|
|
82
|
+
self._agent_registry: Dict[str, Dict[str, Any]] = {}
|
|
83
|
+
self._load_agents()
|
|
84
|
+
|
|
85
|
+
def _load_agents(self) -> None:
|
|
86
|
+
"""Load all valid agents from the templates directory."""
|
|
87
|
+
logger.info(f"Loading agents from {AGENT_TEMPLATES_DIR}")
|
|
115
88
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
89
|
+
for json_file in AGENT_TEMPLATES_DIR.glob("*.json"):
|
|
90
|
+
if json_file.name == "agent_schema.json":
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
with open(json_file, 'r') as f:
|
|
95
|
+
agent_data = json.load(f)
|
|
96
|
+
|
|
97
|
+
# Validate against schema
|
|
98
|
+
validation_result = self.validator.validate_agent(agent_data)
|
|
99
|
+
|
|
100
|
+
if validation_result.is_valid:
|
|
101
|
+
agent_id = agent_data.get("id")
|
|
102
|
+
if agent_id:
|
|
103
|
+
self._agent_registry[agent_id] = agent_data
|
|
104
|
+
logger.debug(f"Loaded agent: {agent_id}")
|
|
105
|
+
else:
|
|
106
|
+
logger.warning(f"Invalid agent in {json_file.name}: {validation_result.errors}")
|
|
107
|
+
|
|
108
|
+
except Exception as e:
|
|
109
|
+
logger.error(f"Failed to load {json_file.name}: {e}")
|
|
110
|
+
|
|
111
|
+
def get_agent(self, agent_id: str) -> Optional[Dict[str, Any]]:
|
|
112
|
+
"""Get agent data by ID."""
|
|
113
|
+
return self._agent_registry.get(agent_id)
|
|
114
|
+
|
|
115
|
+
def list_agents(self) -> List[Dict[str, Any]]:
|
|
116
|
+
"""List all available agents."""
|
|
117
|
+
agents = []
|
|
118
|
+
for agent_id, agent_data in self._agent_registry.items():
|
|
119
|
+
agents.append({
|
|
120
|
+
"id": agent_id,
|
|
121
|
+
"name": agent_data.get("metadata", {}).get("name", agent_id),
|
|
122
|
+
"description": agent_data.get("metadata", {}).get("description", ""),
|
|
123
|
+
"category": agent_data.get("metadata", {}).get("category", ""),
|
|
124
|
+
"model": agent_data.get("capabilities", {}).get("model", ""),
|
|
125
|
+
"resource_tier": agent_data.get("capabilities", {}).get("resource_tier", "")
|
|
126
|
+
})
|
|
127
|
+
return sorted(agents, key=lambda x: x["id"])
|
|
128
|
+
|
|
129
|
+
def get_agent_prompt(self, agent_id: str, force_reload: bool = False) -> Optional[str]:
|
|
130
|
+
"""Get agent instructions by ID."""
|
|
131
|
+
cache_key = f"{AGENT_CACHE_PREFIX}{agent_id}"
|
|
123
132
|
|
|
124
|
-
# Check cache first
|
|
133
|
+
# Check cache first
|
|
125
134
|
if not force_reload:
|
|
126
|
-
cached_content = cache.get(cache_key)
|
|
135
|
+
cached_content = self.cache.get(cache_key)
|
|
127
136
|
if cached_content is not None:
|
|
128
|
-
logger.debug(f"Agent prompt for '{
|
|
137
|
+
logger.debug(f"Agent prompt for '{agent_id}' loaded from cache")
|
|
129
138
|
return str(cached_content)
|
|
130
139
|
|
|
131
|
-
# Get
|
|
132
|
-
|
|
133
|
-
if not
|
|
134
|
-
logger.warning(f"
|
|
135
|
-
return None
|
|
136
|
-
|
|
137
|
-
json_path = AGENT_TEMPLATES_DIR / json_filename
|
|
138
|
-
|
|
139
|
-
# Check if file exists
|
|
140
|
-
if not json_path.exists():
|
|
141
|
-
logger.warning(f"Agent JSON file not found: {json_path}")
|
|
140
|
+
# Get agent data
|
|
141
|
+
agent_data = self.get_agent(agent_id)
|
|
142
|
+
if not agent_data:
|
|
143
|
+
logger.warning(f"Agent not found: {agent_id}")
|
|
142
144
|
return None
|
|
143
|
-
|
|
144
|
-
logger.debug(f"Loading agent prompt from: {json_path}")
|
|
145
|
-
|
|
146
|
-
# Load and parse JSON
|
|
147
|
-
import json
|
|
148
|
-
with open(json_path, 'r', encoding='utf-8') as f:
|
|
149
|
-
data = json.load(f)
|
|
150
|
-
|
|
151
|
-
# Extract prompt content from JSON
|
|
152
|
-
# Check multiple possible locations for instructions/content
|
|
153
|
-
# Following the same pattern as AgentDeploymentService
|
|
154
|
-
content = (
|
|
155
|
-
data.get('instructions') or
|
|
156
|
-
data.get('narrative_fields', {}).get('instructions') or
|
|
157
|
-
data.get('content') or
|
|
158
|
-
''
|
|
159
|
-
)
|
|
160
145
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
146
|
+
# Extract instructions
|
|
147
|
+
instructions = agent_data.get("instructions", "")
|
|
148
|
+
if not instructions:
|
|
149
|
+
logger.warning(f"No instructions found for agent: {agent_id}")
|
|
164
150
|
return None
|
|
165
151
|
|
|
166
152
|
# Cache the content with 1 hour TTL
|
|
167
|
-
cache.set(cache_key,
|
|
168
|
-
logger.debug(f"Agent prompt for '{
|
|
153
|
+
self.cache.set(cache_key, instructions, ttl=3600)
|
|
154
|
+
logger.debug(f"Agent prompt for '{agent_id}' cached successfully")
|
|
169
155
|
|
|
170
|
-
return
|
|
156
|
+
return instructions
|
|
157
|
+
|
|
158
|
+
def get_agent_metadata(self, agent_id: str) -> Optional[Dict[str, Any]]:
|
|
159
|
+
"""Get agent metadata including capabilities and configuration."""
|
|
160
|
+
agent_data = self.get_agent(agent_id)
|
|
161
|
+
if not agent_data:
|
|
162
|
+
return None
|
|
171
163
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
164
|
+
return {
|
|
165
|
+
"id": agent_id,
|
|
166
|
+
"version": agent_data.get("version", "1.0.0"),
|
|
167
|
+
"metadata": agent_data.get("metadata", {}),
|
|
168
|
+
"capabilities": agent_data.get("capabilities", {}),
|
|
169
|
+
"knowledge": agent_data.get("knowledge", {}),
|
|
170
|
+
"interactions": agent_data.get("interactions", {})
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# Global loader instance
|
|
175
|
+
_loader: Optional[AgentLoader] = None
|
|
175
176
|
|
|
176
177
|
|
|
178
|
+
def _get_loader() -> AgentLoader:
|
|
179
|
+
"""Get or create the global agent loader instance."""
|
|
180
|
+
global _loader
|
|
181
|
+
if _loader is None:
|
|
182
|
+
_loader = AgentLoader()
|
|
183
|
+
return _loader
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def load_agent_prompt_from_md(agent_name: str, force_reload: bool = False) -> Optional[str]:
|
|
187
|
+
"""
|
|
188
|
+
Load agent prompt from new schema JSON template.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
agent_name: Agent name (matches agent ID in new schema)
|
|
192
|
+
force_reload: Force reload from file, bypassing cache
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
str: Agent instructions from JSON template, or None if not found
|
|
196
|
+
"""
|
|
197
|
+
loader = _get_loader()
|
|
198
|
+
return loader.get_agent_prompt(agent_name, force_reload)
|
|
177
199
|
|
|
178
200
|
|
|
179
201
|
def _analyze_task_complexity(task_description: str, context_size: int = 0, **kwargs: Any) -> Dict[str, Any]:
|
|
@@ -210,13 +232,19 @@ def _get_model_config(agent_name: str, complexity_analysis: Optional[Dict[str, A
|
|
|
210
232
|
Returns:
|
|
211
233
|
Tuple of (selected_model, model_config)
|
|
212
234
|
"""
|
|
235
|
+
loader = _get_loader()
|
|
236
|
+
agent_data = loader.get_agent(agent_name)
|
|
237
|
+
|
|
238
|
+
if not agent_data:
|
|
239
|
+
# Fallback for unknown agents
|
|
240
|
+
return "claude-sonnet-4-20250514", {"selection_method": "default"}
|
|
241
|
+
|
|
242
|
+
# Get model from agent capabilities
|
|
243
|
+
default_model = agent_data.get("capabilities", {}).get("model", "claude-sonnet-4-20250514")
|
|
244
|
+
|
|
213
245
|
# Check if dynamic model selection is enabled
|
|
214
246
|
enable_dynamic_selection = os.getenv('ENABLE_DYNAMIC_MODEL_SELECTION', 'true').lower() == 'true'
|
|
215
247
|
|
|
216
|
-
# Debug logging
|
|
217
|
-
logger.debug(f"Environment ENABLE_DYNAMIC_MODEL_SELECTION: {os.getenv('ENABLE_DYNAMIC_MODEL_SELECTION')}")
|
|
218
|
-
logger.debug(f"Enable dynamic selection: {enable_dynamic_selection}")
|
|
219
|
-
|
|
220
248
|
# Check for per-agent override in environment
|
|
221
249
|
agent_override_key = f"CLAUDE_PM_{agent_name.upper()}_MODEL_SELECTION"
|
|
222
250
|
agent_override = os.getenv(agent_override_key, '').lower()
|
|
@@ -226,44 +254,24 @@ def _get_model_config(agent_name: str, complexity_analysis: Optional[Dict[str, A
|
|
|
226
254
|
elif agent_override == 'false':
|
|
227
255
|
enable_dynamic_selection = False
|
|
228
256
|
|
|
229
|
-
# Log model selection decision
|
|
230
|
-
logger.info(f"Model selection for {agent_name}: dynamic={enable_dynamic_selection}, "
|
|
231
|
-
f"complexity_available={complexity_analysis is not None}")
|
|
232
|
-
|
|
233
257
|
# Dynamic model selection based on complexity
|
|
234
258
|
if enable_dynamic_selection and complexity_analysis:
|
|
235
259
|
recommended_model = complexity_analysis.get('recommended_model', ModelType.SONNET)
|
|
236
|
-
selected_model = MODEL_NAME_MAPPINGS.get(recommended_model,
|
|
260
|
+
selected_model = MODEL_NAME_MAPPINGS.get(recommended_model, default_model)
|
|
237
261
|
|
|
238
262
|
model_config = {
|
|
239
263
|
"selection_method": "dynamic_complexity_based",
|
|
240
264
|
"complexity_score": complexity_analysis.get('complexity_score', 50),
|
|
241
265
|
"complexity_level": complexity_analysis.get('complexity_level', ComplexityLevel.MEDIUM).value,
|
|
242
266
|
"optimal_prompt_size": complexity_analysis.get('optimal_prompt_size', (700, 1000)),
|
|
243
|
-
"
|
|
244
|
-
"analysis_details": complexity_analysis.get('analysis_details', {})
|
|
267
|
+
"default_model": default_model
|
|
245
268
|
}
|
|
246
|
-
|
|
247
|
-
# Log metrics
|
|
248
|
-
logger.info(f"Dynamic model selection for {agent_name}: "
|
|
249
|
-
f"model={selected_model}, "
|
|
250
|
-
f"complexity_score={model_config['complexity_score']}, "
|
|
251
|
-
f"complexity_level={model_config['complexity_level']}")
|
|
252
|
-
|
|
253
|
-
# Track model selection metrics
|
|
254
|
-
log_model_selection(
|
|
255
|
-
agent_name=agent_name,
|
|
256
|
-
selected_model=selected_model,
|
|
257
|
-
complexity_score=model_config['complexity_score'],
|
|
258
|
-
selection_method=model_config['selection_method']
|
|
259
|
-
)
|
|
260
|
-
|
|
261
269
|
else:
|
|
262
|
-
|
|
263
|
-
selected_model = DEFAULT_AGENT_MODELS.get(agent_name, 'claude-sonnet-4-20250514')
|
|
270
|
+
selected_model = default_model
|
|
264
271
|
model_config = {
|
|
265
|
-
"selection_method": "
|
|
266
|
-
"reason": "dynamic_selection_disabled" if not enable_dynamic_selection else "no_complexity_analysis"
|
|
272
|
+
"selection_method": "agent_default",
|
|
273
|
+
"reason": "dynamic_selection_disabled" if not enable_dynamic_selection else "no_complexity_analysis",
|
|
274
|
+
"default_model": default_model
|
|
267
275
|
}
|
|
268
276
|
|
|
269
277
|
return selected_model, model_config
|
|
@@ -274,24 +282,23 @@ def get_agent_prompt(agent_name: str, force_reload: bool = False, return_model_i
|
|
|
274
282
|
Get agent prompt from JSON template with optional dynamic model selection.
|
|
275
283
|
|
|
276
284
|
Args:
|
|
277
|
-
agent_name: Agent name (
|
|
285
|
+
agent_name: Agent name (agent ID in new schema)
|
|
278
286
|
force_reload: Force reload from source, bypassing cache
|
|
279
287
|
return_model_info: If True, returns tuple (prompt, model, config)
|
|
280
288
|
**kwargs: Additional arguments including:
|
|
281
289
|
- task_description: Description of the task for complexity analysis
|
|
282
290
|
- context_size: Size of context for complexity analysis
|
|
283
291
|
- enable_complexity_analysis: Override for complexity analysis
|
|
284
|
-
- Additional complexity factors (file_count, integration_points, etc.)
|
|
285
292
|
|
|
286
293
|
Returns:
|
|
287
294
|
str or tuple: Complete agent prompt with base instructions prepended,
|
|
288
295
|
or tuple of (prompt, selected_model, model_config) if return_model_info=True
|
|
289
296
|
"""
|
|
290
|
-
# Load from JSON template
|
|
297
|
+
# Load from new schema JSON template
|
|
291
298
|
prompt = load_agent_prompt_from_md(agent_name, force_reload)
|
|
292
299
|
|
|
293
300
|
if prompt is None:
|
|
294
|
-
raise ValueError(f"No agent
|
|
301
|
+
raise ValueError(f"No agent found with ID: {agent_name}")
|
|
295
302
|
|
|
296
303
|
# Analyze task complexity if task description is provided
|
|
297
304
|
complexity_analysis = None
|
|
@@ -299,38 +306,15 @@ def get_agent_prompt(agent_name: str, force_reload: bool = False, return_model_i
|
|
|
299
306
|
enable_analysis = kwargs.get('enable_complexity_analysis', True)
|
|
300
307
|
|
|
301
308
|
if task_description and enable_analysis:
|
|
302
|
-
# Remove already specified parameters from kwargs to avoid duplicates
|
|
303
|
-
analysis_kwargs = {k: v for k, v in kwargs.items()
|
|
304
|
-
if k not in ['task_description', 'context_size']}
|
|
305
309
|
complexity_analysis = _analyze_task_complexity(
|
|
306
310
|
task_description=task_description,
|
|
307
311
|
context_size=kwargs.get('context_size', 0),
|
|
308
|
-
**
|
|
312
|
+
**{k: v for k, v in kwargs.items() if k not in ['task_description', 'context_size']}
|
|
309
313
|
)
|
|
310
314
|
|
|
311
|
-
# Get model configuration
|
|
315
|
+
# Get model configuration
|
|
312
316
|
selected_model, model_config = _get_model_config(agent_name, complexity_analysis)
|
|
313
317
|
|
|
314
|
-
# Always store model selection info in kwargs for potential use by callers
|
|
315
|
-
kwargs['_selected_model'] = selected_model
|
|
316
|
-
kwargs['_model_config'] = model_config
|
|
317
|
-
|
|
318
|
-
# Handle dynamic template formatting if needed
|
|
319
|
-
if "{dynamic_help}" in prompt:
|
|
320
|
-
try:
|
|
321
|
-
# Import CLI helper module to get dynamic help
|
|
322
|
-
from ..orchestration.ai_trackdown_tools import CLIHelpFormatter
|
|
323
|
-
|
|
324
|
-
# Create a CLI helper instance
|
|
325
|
-
cli_helper = CLIHelpFormatter()
|
|
326
|
-
help_content, _ = cli_helper.get_cli_help()
|
|
327
|
-
dynamic_help = cli_helper.format_help_for_prompt(help_content)
|
|
328
|
-
prompt = prompt.format(dynamic_help=dynamic_help)
|
|
329
|
-
except Exception as e:
|
|
330
|
-
logger.warning(f"Could not format dynamic help for ticketing agent: {e}")
|
|
331
|
-
# Remove the placeholder if we can't fill it
|
|
332
|
-
prompt = prompt.replace("{dynamic_help}", "")
|
|
333
|
-
|
|
334
318
|
# Add model selection metadata to prompt if dynamic selection is enabled
|
|
335
319
|
if selected_model and model_config.get('selection_method') == 'dynamic_complexity_based':
|
|
336
320
|
model_metadata = f"\n<!-- Model Selection: {selected_model} (Complexity: {model_config.get('complexity_level', 'UNKNOWN')}) -->\n"
|
|
@@ -355,7 +339,6 @@ def get_documentation_agent_prompt() -> str:
|
|
|
355
339
|
return prompt
|
|
356
340
|
|
|
357
341
|
|
|
358
|
-
|
|
359
342
|
def get_version_control_agent_prompt() -> str:
|
|
360
343
|
"""Get the complete Version Control Agent prompt with base instructions."""
|
|
361
344
|
prompt = get_agent_prompt("version_control", return_model_info=False)
|
|
@@ -410,43 +393,54 @@ def get_agent_prompt_with_model_info(agent_name: str, force_reload: bool = False
|
|
|
410
393
|
Get agent prompt with model selection information.
|
|
411
394
|
|
|
412
395
|
Args:
|
|
413
|
-
agent_name: Agent name (
|
|
396
|
+
agent_name: Agent name (agent ID)
|
|
414
397
|
force_reload: Force reload from source, bypassing cache
|
|
415
398
|
**kwargs: Additional arguments for prompt generation and model selection
|
|
416
399
|
|
|
417
400
|
Returns:
|
|
418
401
|
Tuple of (prompt, selected_model, model_config)
|
|
419
402
|
"""
|
|
420
|
-
# Use get_agent_prompt with return_model_info=True
|
|
421
403
|
result = get_agent_prompt(agent_name, force_reload, return_model_info=True, **kwargs)
|
|
422
404
|
|
|
423
|
-
#
|
|
405
|
+
# Ensure we have a tuple
|
|
424
406
|
if isinstance(result, tuple):
|
|
425
407
|
return result
|
|
426
408
|
|
|
427
409
|
# Fallback (shouldn't happen)
|
|
428
|
-
|
|
410
|
+
loader = _get_loader()
|
|
411
|
+
agent_data = loader.get_agent(agent_name)
|
|
412
|
+
default_model = "claude-sonnet-4-20250514"
|
|
413
|
+
if agent_data:
|
|
414
|
+
default_model = agent_data.get("capabilities", {}).get("model", default_model)
|
|
415
|
+
|
|
416
|
+
return result, default_model, {"selection_method": "default"}
|
|
429
417
|
|
|
430
418
|
|
|
431
419
|
# Utility functions
|
|
432
420
|
def list_available_agents() -> Dict[str, Dict[str, Any]]:
|
|
433
421
|
"""
|
|
434
|
-
List all available agents with their
|
|
422
|
+
List all available agents with their metadata.
|
|
435
423
|
|
|
436
424
|
Returns:
|
|
437
|
-
dict: Agent information including
|
|
425
|
+
dict: Agent information including capabilities and metadata
|
|
438
426
|
"""
|
|
427
|
+
loader = _get_loader()
|
|
439
428
|
agents = {}
|
|
440
429
|
|
|
441
|
-
for
|
|
442
|
-
|
|
430
|
+
for agent_info in loader.list_agents():
|
|
431
|
+
agent_id = agent_info["id"]
|
|
432
|
+
metadata = loader.get_agent_metadata(agent_id)
|
|
443
433
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
434
|
+
if metadata:
|
|
435
|
+
agents[agent_id] = {
|
|
436
|
+
"name": metadata["metadata"].get("name", agent_id),
|
|
437
|
+
"description": metadata["metadata"].get("description", ""),
|
|
438
|
+
"category": metadata["metadata"].get("category", ""),
|
|
439
|
+
"version": metadata["version"],
|
|
440
|
+
"model": metadata["capabilities"].get("model", ""),
|
|
441
|
+
"resource_tier": metadata["capabilities"].get("resource_tier", ""),
|
|
442
|
+
"tools": metadata["capabilities"].get("tools", [])
|
|
443
|
+
}
|
|
450
444
|
|
|
451
445
|
return agents
|
|
452
446
|
|
|
@@ -462,13 +456,14 @@ def clear_agent_cache(agent_name: Optional[str] = None) -> None:
|
|
|
462
456
|
cache = SharedPromptCache.get_instance()
|
|
463
457
|
|
|
464
458
|
if agent_name:
|
|
465
|
-
cache_key = f"{AGENT_CACHE_PREFIX}{agent_name}
|
|
459
|
+
cache_key = f"{AGENT_CACHE_PREFIX}{agent_name}"
|
|
466
460
|
cache.invalidate(cache_key)
|
|
467
461
|
logger.debug(f"Cache cleared for agent: {agent_name}")
|
|
468
462
|
else:
|
|
469
463
|
# Clear all agent caches
|
|
470
|
-
|
|
471
|
-
|
|
464
|
+
loader = _get_loader()
|
|
465
|
+
for agent_id in loader._agent_registry.keys():
|
|
466
|
+
cache_key = f"{AGENT_CACHE_PREFIX}{agent_id}"
|
|
472
467
|
cache.invalidate(cache_key)
|
|
473
468
|
logger.debug("All agent caches cleared")
|
|
474
469
|
|
|
@@ -478,120 +473,31 @@ def clear_agent_cache(agent_name: Optional[str] = None) -> None:
|
|
|
478
473
|
|
|
479
474
|
def validate_agent_files() -> Dict[str, Dict[str, Any]]:
|
|
480
475
|
"""
|
|
481
|
-
Validate
|
|
476
|
+
Validate all agent files in the templates directory.
|
|
482
477
|
|
|
483
478
|
Returns:
|
|
484
479
|
dict: Validation results for each agent
|
|
485
480
|
"""
|
|
481
|
+
validator = AgentValidator()
|
|
486
482
|
results = {}
|
|
487
483
|
|
|
488
|
-
for
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
484
|
+
for json_file in AGENT_TEMPLATES_DIR.glob("*.json"):
|
|
485
|
+
if json_file.name == "agent_schema.json":
|
|
486
|
+
continue
|
|
487
|
+
|
|
488
|
+
validation_result = validator.validate_file(json_file)
|
|
489
|
+
results[json_file.stem] = {
|
|
490
|
+
"valid": validation_result.is_valid,
|
|
491
|
+
"errors": validation_result.errors,
|
|
492
|
+
"warnings": validation_result.warnings,
|
|
493
|
+
"file_path": str(json_file)
|
|
493
494
|
}
|
|
494
495
|
|
|
495
496
|
return results
|
|
496
497
|
|
|
497
498
|
|
|
498
|
-
def
|
|
499
|
-
"""
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
dict: Metrics including feature flag status and selection counts
|
|
504
|
-
"""
|
|
505
|
-
# Check feature flag status
|
|
506
|
-
global_enabled = os.getenv('ENABLE_DYNAMIC_MODEL_SELECTION', 'true').lower() == 'true'
|
|
507
|
-
|
|
508
|
-
# Check per-agent overrides
|
|
509
|
-
agent_overrides = {}
|
|
510
|
-
for agent_name in AGENT_MAPPINGS.keys():
|
|
511
|
-
override_key = f"CLAUDE_PM_{agent_name.upper()}_MODEL_SELECTION"
|
|
512
|
-
override_value = os.getenv(override_key, '')
|
|
513
|
-
if override_value:
|
|
514
|
-
agent_overrides[agent_name] = override_value.lower() == 'true'
|
|
515
|
-
|
|
516
|
-
# Get cache instance to check for cached metrics
|
|
517
|
-
try:
|
|
518
|
-
cache = SharedPromptCache.get_instance()
|
|
519
|
-
selection_stats = cache.get("agent_loader:model_selection_stats") or {}
|
|
520
|
-
except Exception:
|
|
521
|
-
selection_stats = {}
|
|
522
|
-
|
|
523
|
-
return {
|
|
524
|
-
"feature_flag": {
|
|
525
|
-
"global_enabled": global_enabled,
|
|
526
|
-
"agent_overrides": agent_overrides
|
|
527
|
-
},
|
|
528
|
-
"model_thresholds": {
|
|
529
|
-
model_type.value: thresholds
|
|
530
|
-
for model_type, thresholds in MODEL_THRESHOLDS.items()
|
|
531
|
-
},
|
|
532
|
-
"default_models": DEFAULT_AGENT_MODELS,
|
|
533
|
-
"selection_stats": selection_stats
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
def log_model_selection(agent_name: str, selected_model: str, complexity_score: int, selection_method: str) -> None:
|
|
538
|
-
"""
|
|
539
|
-
Log model selection for metrics tracking.
|
|
540
|
-
|
|
541
|
-
Args:
|
|
542
|
-
agent_name: Name of the agent
|
|
543
|
-
selected_model: Model that was selected
|
|
544
|
-
complexity_score: Complexity score from analysis
|
|
545
|
-
selection_method: Method used for selection
|
|
546
|
-
"""
|
|
547
|
-
try:
|
|
548
|
-
# Get cache instance
|
|
549
|
-
cache = SharedPromptCache.get_instance()
|
|
550
|
-
|
|
551
|
-
# Get existing stats
|
|
552
|
-
stats_key = "agent_loader:model_selection_stats"
|
|
553
|
-
stats = cache.get(stats_key) or {
|
|
554
|
-
"total_selections": 0,
|
|
555
|
-
"by_model": {},
|
|
556
|
-
"by_agent": {},
|
|
557
|
-
"by_method": {},
|
|
558
|
-
"complexity_distribution": {
|
|
559
|
-
"0-30": 0,
|
|
560
|
-
"31-70": 0,
|
|
561
|
-
"71-100": 0
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
# Update stats
|
|
566
|
-
stats["total_selections"] += 1
|
|
567
|
-
|
|
568
|
-
# By model
|
|
569
|
-
if selected_model not in stats["by_model"]:
|
|
570
|
-
stats["by_model"][selected_model] = 0
|
|
571
|
-
stats["by_model"][selected_model] += 1
|
|
572
|
-
|
|
573
|
-
# By agent
|
|
574
|
-
if agent_name not in stats["by_agent"]:
|
|
575
|
-
stats["by_agent"][agent_name] = {}
|
|
576
|
-
if selected_model not in stats["by_agent"][agent_name]:
|
|
577
|
-
stats["by_agent"][agent_name][selected_model] = 0
|
|
578
|
-
stats["by_agent"][agent_name][selected_model] += 1
|
|
579
|
-
|
|
580
|
-
# By method
|
|
581
|
-
if selection_method not in stats["by_method"]:
|
|
582
|
-
stats["by_method"][selection_method] = 0
|
|
583
|
-
stats["by_method"][selection_method] += 1
|
|
584
|
-
|
|
585
|
-
# Complexity distribution
|
|
586
|
-
if complexity_score <= 30:
|
|
587
|
-
stats["complexity_distribution"]["0-30"] += 1
|
|
588
|
-
elif complexity_score <= 70:
|
|
589
|
-
stats["complexity_distribution"]["31-70"] += 1
|
|
590
|
-
else:
|
|
591
|
-
stats["complexity_distribution"]["71-100"] += 1
|
|
592
|
-
|
|
593
|
-
# Store updated stats with 24 hour TTL
|
|
594
|
-
cache.set(stats_key, stats, ttl=86400)
|
|
595
|
-
|
|
596
|
-
except Exception as e:
|
|
597
|
-
logger.warning(f"Failed to log model selection metrics: {e}")
|
|
499
|
+
def reload_agents() -> None:
|
|
500
|
+
"""Force reload all agents from disk."""
|
|
501
|
+
global _loader
|
|
502
|
+
_loader = None
|
|
503
|
+
logger.info("Agent registry cleared, will reload on next access")
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"version": 2,
|
|
3
3
|
"agent_type": "base",
|
|
4
4
|
"narrative_fields": {
|
|
5
|
-
"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"
|
|
5
|
+
"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```"
|
|
6
6
|
},
|
|
7
7
|
"configuration_fields": {
|
|
8
8
|
"model": "claude-4-sonnet-20250514",
|