claude-mpm 1.0.0__py3-none-any.whl → 2.0.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 -2
- claude_mpm/agents/INSTRUCTIONS.md +117 -312
- claude_mpm/agents/__init__.py +2 -2
- claude_mpm/agents/agent-template.yaml +83 -0
- claude_mpm/agents/agent_loader.py +192 -310
- claude_mpm/agents/base_agent.json +1 -1
- claude_mpm/agents/base_agent_loader.py +10 -15
- 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 +107 -0
- claude_mpm/agents/templates/security.json +105 -0
- claude_mpm/agents/templates/version_control.json +103 -0
- claude_mpm/cli.py +41 -47
- claude_mpm/cli_enhancements.py +297 -0
- claude_mpm/core/factories.py +1 -46
- claude_mpm/core/service_registry.py +0 -8
- claude_mpm/core/simple_runner.py +43 -0
- claude_mpm/generators/__init__.py +5 -0
- claude_mpm/generators/agent_profile_generator.py +137 -0
- claude_mpm/hooks/README.md +75 -221
- claude_mpm/hooks/builtin/mpm_command_hook.py +125 -0
- claude_mpm/hooks/claude_hooks/__init__.py +5 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +399 -0
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +47 -0
- claude_mpm/hooks/validation_hooks.py +181 -0
- claude_mpm/schemas/agent_schema.json +328 -0
- claude_mpm/services/agent_management_service.py +4 -4
- claude_mpm/services/agent_profile_loader.py +1 -1
- claude_mpm/services/agent_registry.py +0 -1
- claude_mpm/services/base_agent_manager.py +3 -3
- claude_mpm/utils/error_handler.py +247 -0
- claude_mpm/validation/__init__.py +5 -0
- claude_mpm/validation/agent_validator.py +302 -0
- {claude_mpm-1.0.0.dist-info → claude_mpm-2.0.0.dist-info}/METADATA +133 -22
- {claude_mpm-1.0.0.dist-info → claude_mpm-2.0.0.dist-info}/RECORD +49 -37
- claude_mpm/agents/templates/data_engineer_agent.json +0 -46
- claude_mpm/agents/templates/update-optimized-specialized-agents.json +0 -374
- claude_mpm/config/hook_config.py +0 -42
- claude_mpm/hooks/hook_client.py +0 -264
- claude_mpm/hooks/hook_runner.py +0 -370
- claude_mpm/hooks/json_rpc_executor.py +0 -259
- claude_mpm/hooks/json_rpc_hook_client.py +0 -319
- claude_mpm/services/hook_service.py +0 -388
- claude_mpm/services/hook_service_manager.py +0 -223
- claude_mpm/services/json_rpc_hook_manager.py +0 -92
- /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.0.0.dist-info → claude_mpm-2.0.0.dist-info}/WHEEL +0 -0
- {claude_mpm-1.0.0.dist-info → claude_mpm-2.0.0.dist-info}/entry_points.txt +0 -0
- {claude_mpm-1.0.0.dist-info → claude_mpm-2.0.0.dist-info}/top_level.txt +0 -0
|
@@ -3,35 +3,32 @@
|
|
|
3
3
|
Unified Agent Loader System
|
|
4
4
|
==========================
|
|
5
5
|
|
|
6
|
-
Provides unified loading of agent prompts from
|
|
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
|
-
- Loads agent prompts from
|
|
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
|
|
22
18
|
|
|
23
|
-
# Get agent prompt from
|
|
19
|
+
# Get agent prompt from JSON template
|
|
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
|
|
@@ -49,65 +46,16 @@ class ModelType:
|
|
|
49
46
|
logger = logging.getLogger(__name__)
|
|
50
47
|
|
|
51
48
|
|
|
52
|
-
def
|
|
53
|
-
"""Get the
|
|
54
|
-
|
|
55
|
-
try:
|
|
56
|
-
framework_root = PathResolver.get_framework_root()
|
|
57
|
-
|
|
58
|
-
# Check if we're running from a wheel installation
|
|
59
|
-
try:
|
|
60
|
-
import claude_pm
|
|
61
|
-
package_path = Path(claude_pm.__file__).parent
|
|
62
|
-
path_str = str(package_path.resolve())
|
|
63
|
-
if 'site-packages' in path_str or 'dist-packages' in path_str:
|
|
64
|
-
# For wheel installations, check data directory
|
|
65
|
-
data_agent_roles = package_path / "data" / "framework" / "agent-roles"
|
|
66
|
-
if data_agent_roles.exists():
|
|
67
|
-
logger.debug(f"Using wheel installation agent-roles: {data_agent_roles}")
|
|
68
|
-
return data_agent_roles
|
|
69
|
-
except Exception:
|
|
70
|
-
pass
|
|
71
|
-
|
|
72
|
-
# Check framework structure
|
|
73
|
-
agent_roles_dir = framework_root / "framework" / "agent-roles"
|
|
74
|
-
if agent_roles_dir.exists():
|
|
75
|
-
logger.debug(f"Using framework agent-roles: {agent_roles_dir}")
|
|
76
|
-
return agent_roles_dir
|
|
77
|
-
|
|
78
|
-
# Try agents directory as fallback
|
|
79
|
-
agents_dir = PathResolver.get_agents_dir()
|
|
80
|
-
logger.debug(f"Using agents directory: {agents_dir}")
|
|
81
|
-
return agents_dir
|
|
82
|
-
|
|
83
|
-
except FileNotFoundError as e:
|
|
84
|
-
# Ultimate fallback
|
|
85
|
-
logger.warning(f"PathResolver could not find framework root: {e}")
|
|
86
|
-
fallback = Path(__file__).parent.parent.parent / "framework" / "agent-roles"
|
|
87
|
-
logger.warning(f"Using fallback agent-roles path: {fallback}")
|
|
88
|
-
return fallback
|
|
49
|
+
def _get_agent_templates_dir() -> Path:
|
|
50
|
+
"""Get the agent templates directory."""
|
|
51
|
+
return Path(__file__).parent / "templates"
|
|
89
52
|
|
|
90
53
|
|
|
91
|
-
#
|
|
92
|
-
|
|
54
|
+
# Agent templates directory
|
|
55
|
+
AGENT_TEMPLATES_DIR = _get_agent_templates_dir()
|
|
93
56
|
|
|
94
57
|
# Cache prefix for agent prompts
|
|
95
|
-
AGENT_CACHE_PREFIX = "agent_prompt:"
|
|
96
|
-
|
|
97
|
-
# Agent name mappings (agent name -> MD file name)
|
|
98
|
-
AGENT_MAPPINGS = {
|
|
99
|
-
"documentation": "documentation-agent.md",
|
|
100
|
-
"version_control": "version-control-agent.md",
|
|
101
|
-
"qa": "qa-agent.md",
|
|
102
|
-
"research": "research-agent.md",
|
|
103
|
-
"ops": "ops-agent.md",
|
|
104
|
-
"security": "security-agent.md",
|
|
105
|
-
"engineer": "engineer-agent.md",
|
|
106
|
-
"data_engineer": "data-agent.md", # Note: data-agent.md maps to data_engineer
|
|
107
|
-
"pm": "pm-orchestrator-agent.md",
|
|
108
|
-
"orchestrator": "pm-orchestrator-agent.md",
|
|
109
|
-
"pm_orchestrator": "pm-orchestrator-agent.md"
|
|
110
|
-
}
|
|
58
|
+
AGENT_CACHE_PREFIX = "agent_prompt:v2:"
|
|
111
59
|
|
|
112
60
|
# Model configuration thresholds
|
|
113
61
|
MODEL_THRESHOLDS = {
|
|
@@ -116,82 +64,138 @@ MODEL_THRESHOLDS = {
|
|
|
116
64
|
ModelType.OPUS: {"min_complexity": 71, "max_complexity": 100}
|
|
117
65
|
}
|
|
118
66
|
|
|
119
|
-
#
|
|
120
|
-
DEFAULT_AGENT_MODELS = {
|
|
121
|
-
'orchestrator': 'claude-4-opus',
|
|
122
|
-
'pm': 'claude-4-opus',
|
|
123
|
-
'pm_orchestrator': 'claude-4-opus',
|
|
124
|
-
'engineer': 'claude-4-opus',
|
|
125
|
-
'architecture': 'claude-4-opus',
|
|
126
|
-
'documentation': 'claude-sonnet-4-20250514',
|
|
127
|
-
'version_control': 'claude-sonnet-4-20250514',
|
|
128
|
-
'qa': 'claude-sonnet-4-20250514',
|
|
129
|
-
'research': 'claude-sonnet-4-20250514',
|
|
130
|
-
'ops': 'claude-sonnet-4-20250514',
|
|
131
|
-
'security': 'claude-sonnet-4-20250514',
|
|
132
|
-
'data_engineer': 'claude-sonnet-4-20250514'
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
# Model name mappings for Claude API
|
|
67
|
+
# Model name mappings for Claude API (updated for new schema)
|
|
136
68
|
MODEL_NAME_MAPPINGS = {
|
|
137
69
|
ModelType.HAIKU: "claude-3-haiku-20240307",
|
|
138
70
|
ModelType.SONNET: "claude-sonnet-4-20250514",
|
|
139
|
-
ModelType.OPUS: "claude-4-
|
|
71
|
+
ModelType.OPUS: "claude-opus-4-20250514"
|
|
140
72
|
}
|
|
141
73
|
|
|
142
74
|
|
|
143
|
-
|
|
144
|
-
"""
|
|
145
|
-
Load agent prompt from framework markdown file.
|
|
75
|
+
class AgentLoader:
|
|
76
|
+
"""Loads and manages agent templates with schema validation."""
|
|
146
77
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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}")
|
|
150
88
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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}"
|
|
158
132
|
|
|
159
|
-
# Check cache first
|
|
133
|
+
# Check cache first
|
|
160
134
|
if not force_reload:
|
|
161
|
-
cached_content = cache.get(cache_key)
|
|
135
|
+
cached_content = self.cache.get(cache_key)
|
|
162
136
|
if cached_content is not None:
|
|
163
|
-
logger.debug(f"Agent prompt for '{
|
|
137
|
+
logger.debug(f"Agent prompt for '{agent_id}' loaded from cache")
|
|
164
138
|
return str(cached_content)
|
|
165
139
|
|
|
166
|
-
# Get
|
|
167
|
-
|
|
168
|
-
if not
|
|
169
|
-
logger.warning(f"
|
|
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}")
|
|
170
144
|
return None
|
|
171
145
|
|
|
172
|
-
#
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
# Check if file exists
|
|
177
|
-
if not md_path.exists():
|
|
178
|
-
logger.warning(f"Agent MD file not found: {md_path}")
|
|
146
|
+
# Extract instructions
|
|
147
|
+
instructions = agent_data.get("instructions", "")
|
|
148
|
+
if not instructions:
|
|
149
|
+
logger.warning(f"No instructions found for agent: {agent_id}")
|
|
179
150
|
return None
|
|
180
|
-
|
|
181
|
-
logger.debug(f"Loading agent prompt from: {md_path}")
|
|
182
|
-
content = md_path.read_text(encoding='utf-8')
|
|
183
151
|
|
|
184
152
|
# Cache the content with 1 hour TTL
|
|
185
|
-
cache.set(cache_key,
|
|
186
|
-
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")
|
|
187
155
|
|
|
188
|
-
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
|
|
189
163
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
+
}
|
|
193
172
|
|
|
194
173
|
|
|
174
|
+
# Global loader instance
|
|
175
|
+
_loader: Optional[AgentLoader] = None
|
|
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)
|
|
195
199
|
|
|
196
200
|
|
|
197
201
|
def _analyze_task_complexity(task_description: str, context_size: int = 0, **kwargs: Any) -> Dict[str, Any]:
|
|
@@ -228,13 +232,19 @@ def _get_model_config(agent_name: str, complexity_analysis: Optional[Dict[str, A
|
|
|
228
232
|
Returns:
|
|
229
233
|
Tuple of (selected_model, model_config)
|
|
230
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
|
+
|
|
231
245
|
# Check if dynamic model selection is enabled
|
|
232
246
|
enable_dynamic_selection = os.getenv('ENABLE_DYNAMIC_MODEL_SELECTION', 'true').lower() == 'true'
|
|
233
247
|
|
|
234
|
-
# Debug logging
|
|
235
|
-
logger.debug(f"Environment ENABLE_DYNAMIC_MODEL_SELECTION: {os.getenv('ENABLE_DYNAMIC_MODEL_SELECTION')}")
|
|
236
|
-
logger.debug(f"Enable dynamic selection: {enable_dynamic_selection}")
|
|
237
|
-
|
|
238
248
|
# Check for per-agent override in environment
|
|
239
249
|
agent_override_key = f"CLAUDE_PM_{agent_name.upper()}_MODEL_SELECTION"
|
|
240
250
|
agent_override = os.getenv(agent_override_key, '').lower()
|
|
@@ -244,44 +254,24 @@ def _get_model_config(agent_name: str, complexity_analysis: Optional[Dict[str, A
|
|
|
244
254
|
elif agent_override == 'false':
|
|
245
255
|
enable_dynamic_selection = False
|
|
246
256
|
|
|
247
|
-
# Log model selection decision
|
|
248
|
-
logger.info(f"Model selection for {agent_name}: dynamic={enable_dynamic_selection}, "
|
|
249
|
-
f"complexity_available={complexity_analysis is not None}")
|
|
250
|
-
|
|
251
257
|
# Dynamic model selection based on complexity
|
|
252
258
|
if enable_dynamic_selection and complexity_analysis:
|
|
253
259
|
recommended_model = complexity_analysis.get('recommended_model', ModelType.SONNET)
|
|
254
|
-
selected_model = MODEL_NAME_MAPPINGS.get(recommended_model,
|
|
260
|
+
selected_model = MODEL_NAME_MAPPINGS.get(recommended_model, default_model)
|
|
255
261
|
|
|
256
262
|
model_config = {
|
|
257
263
|
"selection_method": "dynamic_complexity_based",
|
|
258
264
|
"complexity_score": complexity_analysis.get('complexity_score', 50),
|
|
259
265
|
"complexity_level": complexity_analysis.get('complexity_level', ComplexityLevel.MEDIUM).value,
|
|
260
266
|
"optimal_prompt_size": complexity_analysis.get('optimal_prompt_size', (700, 1000)),
|
|
261
|
-
"
|
|
262
|
-
"analysis_details": complexity_analysis.get('analysis_details', {})
|
|
267
|
+
"default_model": default_model
|
|
263
268
|
}
|
|
264
|
-
|
|
265
|
-
# Log metrics
|
|
266
|
-
logger.info(f"Dynamic model selection for {agent_name}: "
|
|
267
|
-
f"model={selected_model}, "
|
|
268
|
-
f"complexity_score={model_config['complexity_score']}, "
|
|
269
|
-
f"complexity_level={model_config['complexity_level']}")
|
|
270
|
-
|
|
271
|
-
# Track model selection metrics
|
|
272
|
-
log_model_selection(
|
|
273
|
-
agent_name=agent_name,
|
|
274
|
-
selected_model=selected_model,
|
|
275
|
-
complexity_score=model_config['complexity_score'],
|
|
276
|
-
selection_method=model_config['selection_method']
|
|
277
|
-
)
|
|
278
|
-
|
|
279
269
|
else:
|
|
280
|
-
|
|
281
|
-
selected_model = DEFAULT_AGENT_MODELS.get(agent_name, 'claude-sonnet-4-20250514')
|
|
270
|
+
selected_model = default_model
|
|
282
271
|
model_config = {
|
|
283
|
-
"selection_method": "
|
|
284
|
-
"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
|
|
285
275
|
}
|
|
286
276
|
|
|
287
277
|
return selected_model, model_config
|
|
@@ -289,27 +279,26 @@ def _get_model_config(agent_name: str, complexity_analysis: Optional[Dict[str, A
|
|
|
289
279
|
|
|
290
280
|
def get_agent_prompt(agent_name: str, force_reload: bool = False, return_model_info: bool = False, **kwargs: Any) -> Union[str, Tuple[str, str, Dict[str, Any]]]:
|
|
291
281
|
"""
|
|
292
|
-
Get agent prompt from
|
|
282
|
+
Get agent prompt from JSON template with optional dynamic model selection.
|
|
293
283
|
|
|
294
284
|
Args:
|
|
295
|
-
agent_name: Agent name (
|
|
285
|
+
agent_name: Agent name (agent ID in new schema)
|
|
296
286
|
force_reload: Force reload from source, bypassing cache
|
|
297
287
|
return_model_info: If True, returns tuple (prompt, model, config)
|
|
298
288
|
**kwargs: Additional arguments including:
|
|
299
289
|
- task_description: Description of the task for complexity analysis
|
|
300
290
|
- context_size: Size of context for complexity analysis
|
|
301
291
|
- enable_complexity_analysis: Override for complexity analysis
|
|
302
|
-
- Additional complexity factors (file_count, integration_points, etc.)
|
|
303
292
|
|
|
304
293
|
Returns:
|
|
305
294
|
str or tuple: Complete agent prompt with base instructions prepended,
|
|
306
295
|
or tuple of (prompt, selected_model, model_config) if return_model_info=True
|
|
307
296
|
"""
|
|
308
|
-
# Load from
|
|
297
|
+
# Load from new schema JSON template
|
|
309
298
|
prompt = load_agent_prompt_from_md(agent_name, force_reload)
|
|
310
299
|
|
|
311
300
|
if prompt is None:
|
|
312
|
-
raise ValueError(f"No agent
|
|
301
|
+
raise ValueError(f"No agent found with ID: {agent_name}")
|
|
313
302
|
|
|
314
303
|
# Analyze task complexity if task description is provided
|
|
315
304
|
complexity_analysis = None
|
|
@@ -317,38 +306,15 @@ def get_agent_prompt(agent_name: str, force_reload: bool = False, return_model_i
|
|
|
317
306
|
enable_analysis = kwargs.get('enable_complexity_analysis', True)
|
|
318
307
|
|
|
319
308
|
if task_description and enable_analysis:
|
|
320
|
-
# Remove already specified parameters from kwargs to avoid duplicates
|
|
321
|
-
analysis_kwargs = {k: v for k, v in kwargs.items()
|
|
322
|
-
if k not in ['task_description', 'context_size']}
|
|
323
309
|
complexity_analysis = _analyze_task_complexity(
|
|
324
310
|
task_description=task_description,
|
|
325
311
|
context_size=kwargs.get('context_size', 0),
|
|
326
|
-
**
|
|
312
|
+
**{k: v for k, v in kwargs.items() if k not in ['task_description', 'context_size']}
|
|
327
313
|
)
|
|
328
314
|
|
|
329
|
-
# Get model configuration
|
|
315
|
+
# Get model configuration
|
|
330
316
|
selected_model, model_config = _get_model_config(agent_name, complexity_analysis)
|
|
331
317
|
|
|
332
|
-
# Always store model selection info in kwargs for potential use by callers
|
|
333
|
-
kwargs['_selected_model'] = selected_model
|
|
334
|
-
kwargs['_model_config'] = model_config
|
|
335
|
-
|
|
336
|
-
# Handle dynamic template formatting if needed
|
|
337
|
-
if "{dynamic_help}" in prompt:
|
|
338
|
-
try:
|
|
339
|
-
# Import CLI helper module to get dynamic help
|
|
340
|
-
from ..orchestration.ai_trackdown_tools import CLIHelpFormatter
|
|
341
|
-
|
|
342
|
-
# Create a CLI helper instance
|
|
343
|
-
cli_helper = CLIHelpFormatter()
|
|
344
|
-
help_content, _ = cli_helper.get_cli_help()
|
|
345
|
-
dynamic_help = cli_helper.format_help_for_prompt(help_content)
|
|
346
|
-
prompt = prompt.format(dynamic_help=dynamic_help)
|
|
347
|
-
except Exception as e:
|
|
348
|
-
logger.warning(f"Could not format dynamic help for ticketing agent: {e}")
|
|
349
|
-
# Remove the placeholder if we can't fill it
|
|
350
|
-
prompt = prompt.replace("{dynamic_help}", "")
|
|
351
|
-
|
|
352
318
|
# Add model selection metadata to prompt if dynamic selection is enabled
|
|
353
319
|
if selected_model and model_config.get('selection_method') == 'dynamic_complexity_based':
|
|
354
320
|
model_metadata = f"\n<!-- Model Selection: {selected_model} (Complexity: {model_config.get('complexity_level', 'UNKNOWN')}) -->\n"
|
|
@@ -373,7 +339,6 @@ def get_documentation_agent_prompt() -> str:
|
|
|
373
339
|
return prompt
|
|
374
340
|
|
|
375
341
|
|
|
376
|
-
|
|
377
342
|
def get_version_control_agent_prompt() -> str:
|
|
378
343
|
"""Get the complete Version Control Agent prompt with base instructions."""
|
|
379
344
|
prompt = get_agent_prompt("version_control", return_model_info=False)
|
|
@@ -428,46 +393,54 @@ def get_agent_prompt_with_model_info(agent_name: str, force_reload: bool = False
|
|
|
428
393
|
Get agent prompt with model selection information.
|
|
429
394
|
|
|
430
395
|
Args:
|
|
431
|
-
agent_name: Agent name (
|
|
396
|
+
agent_name: Agent name (agent ID)
|
|
432
397
|
force_reload: Force reload from source, bypassing cache
|
|
433
398
|
**kwargs: Additional arguments for prompt generation and model selection
|
|
434
399
|
|
|
435
400
|
Returns:
|
|
436
401
|
Tuple of (prompt, selected_model, model_config)
|
|
437
402
|
"""
|
|
438
|
-
# Use get_agent_prompt with return_model_info=True
|
|
439
403
|
result = get_agent_prompt(agent_name, force_reload, return_model_info=True, **kwargs)
|
|
440
404
|
|
|
441
|
-
#
|
|
405
|
+
# Ensure we have a tuple
|
|
442
406
|
if isinstance(result, tuple):
|
|
443
407
|
return result
|
|
444
408
|
|
|
445
409
|
# Fallback (shouldn't happen)
|
|
446
|
-
|
|
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"}
|
|
447
417
|
|
|
448
418
|
|
|
449
419
|
# Utility functions
|
|
450
420
|
def list_available_agents() -> Dict[str, Dict[str, Any]]:
|
|
451
421
|
"""
|
|
452
|
-
List all available agents with their
|
|
422
|
+
List all available agents with their metadata.
|
|
453
423
|
|
|
454
424
|
Returns:
|
|
455
|
-
dict: Agent information including
|
|
425
|
+
dict: Agent information including capabilities and metadata
|
|
456
426
|
"""
|
|
427
|
+
loader = _get_loader()
|
|
457
428
|
agents = {}
|
|
458
429
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
for agent_name, md_filename in AGENT_MAPPINGS.items():
|
|
463
|
-
md_path = framework_agent_roles_dir / md_filename
|
|
430
|
+
for agent_info in loader.list_agents():
|
|
431
|
+
agent_id = agent_info["id"]
|
|
432
|
+
metadata = loader.get_agent_metadata(agent_id)
|
|
464
433
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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
|
+
}
|
|
471
444
|
|
|
472
445
|
return agents
|
|
473
446
|
|
|
@@ -483,13 +456,14 @@ def clear_agent_cache(agent_name: Optional[str] = None) -> None:
|
|
|
483
456
|
cache = SharedPromptCache.get_instance()
|
|
484
457
|
|
|
485
458
|
if agent_name:
|
|
486
|
-
cache_key = f"{AGENT_CACHE_PREFIX}{agent_name}
|
|
459
|
+
cache_key = f"{AGENT_CACHE_PREFIX}{agent_name}"
|
|
487
460
|
cache.invalidate(cache_key)
|
|
488
461
|
logger.debug(f"Cache cleared for agent: {agent_name}")
|
|
489
462
|
else:
|
|
490
463
|
# Clear all agent caches
|
|
491
|
-
|
|
492
|
-
|
|
464
|
+
loader = _get_loader()
|
|
465
|
+
for agent_id in loader._agent_registry.keys():
|
|
466
|
+
cache_key = f"{AGENT_CACHE_PREFIX}{agent_id}"
|
|
493
467
|
cache.invalidate(cache_key)
|
|
494
468
|
logger.debug("All agent caches cleared")
|
|
495
469
|
|
|
@@ -499,123 +473,31 @@ def clear_agent_cache(agent_name: Optional[str] = None) -> None:
|
|
|
499
473
|
|
|
500
474
|
def validate_agent_files() -> Dict[str, Dict[str, Any]]:
|
|
501
475
|
"""
|
|
502
|
-
Validate
|
|
476
|
+
Validate all agent files in the templates directory.
|
|
503
477
|
|
|
504
478
|
Returns:
|
|
505
479
|
dict: Validation results for each agent
|
|
506
480
|
"""
|
|
481
|
+
validator = AgentValidator()
|
|
507
482
|
results = {}
|
|
508
483
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
results[
|
|
515
|
-
"
|
|
516
|
-
"
|
|
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)
|
|
517
494
|
}
|
|
518
495
|
|
|
519
496
|
return results
|
|
520
497
|
|
|
521
498
|
|
|
522
|
-
def
|
|
523
|
-
"""
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
dict: Metrics including feature flag status and selection counts
|
|
528
|
-
"""
|
|
529
|
-
# Check feature flag status
|
|
530
|
-
global_enabled = os.getenv('ENABLE_DYNAMIC_MODEL_SELECTION', 'true').lower() == 'true'
|
|
531
|
-
|
|
532
|
-
# Check per-agent overrides
|
|
533
|
-
agent_overrides = {}
|
|
534
|
-
for agent_name in AGENT_MAPPINGS.keys():
|
|
535
|
-
override_key = f"CLAUDE_PM_{agent_name.upper()}_MODEL_SELECTION"
|
|
536
|
-
override_value = os.getenv(override_key, '')
|
|
537
|
-
if override_value:
|
|
538
|
-
agent_overrides[agent_name] = override_value.lower() == 'true'
|
|
539
|
-
|
|
540
|
-
# Get cache instance to check for cached metrics
|
|
541
|
-
try:
|
|
542
|
-
cache = SharedPromptCache.get_instance()
|
|
543
|
-
selection_stats = cache.get("agent_loader:model_selection_stats") or {}
|
|
544
|
-
except Exception:
|
|
545
|
-
selection_stats = {}
|
|
546
|
-
|
|
547
|
-
return {
|
|
548
|
-
"feature_flag": {
|
|
549
|
-
"global_enabled": global_enabled,
|
|
550
|
-
"agent_overrides": agent_overrides
|
|
551
|
-
},
|
|
552
|
-
"model_thresholds": {
|
|
553
|
-
model_type.value: thresholds
|
|
554
|
-
for model_type, thresholds in MODEL_THRESHOLDS.items()
|
|
555
|
-
},
|
|
556
|
-
"default_models": DEFAULT_AGENT_MODELS,
|
|
557
|
-
"selection_stats": selection_stats
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
def log_model_selection(agent_name: str, selected_model: str, complexity_score: int, selection_method: str) -> None:
|
|
562
|
-
"""
|
|
563
|
-
Log model selection for metrics tracking.
|
|
564
|
-
|
|
565
|
-
Args:
|
|
566
|
-
agent_name: Name of the agent
|
|
567
|
-
selected_model: Model that was selected
|
|
568
|
-
complexity_score: Complexity score from analysis
|
|
569
|
-
selection_method: Method used for selection
|
|
570
|
-
"""
|
|
571
|
-
try:
|
|
572
|
-
# Get cache instance
|
|
573
|
-
cache = SharedPromptCache.get_instance()
|
|
574
|
-
|
|
575
|
-
# Get existing stats
|
|
576
|
-
stats_key = "agent_loader:model_selection_stats"
|
|
577
|
-
stats = cache.get(stats_key) or {
|
|
578
|
-
"total_selections": 0,
|
|
579
|
-
"by_model": {},
|
|
580
|
-
"by_agent": {},
|
|
581
|
-
"by_method": {},
|
|
582
|
-
"complexity_distribution": {
|
|
583
|
-
"0-30": 0,
|
|
584
|
-
"31-70": 0,
|
|
585
|
-
"71-100": 0
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
# Update stats
|
|
590
|
-
stats["total_selections"] += 1
|
|
591
|
-
|
|
592
|
-
# By model
|
|
593
|
-
if selected_model not in stats["by_model"]:
|
|
594
|
-
stats["by_model"][selected_model] = 0
|
|
595
|
-
stats["by_model"][selected_model] += 1
|
|
596
|
-
|
|
597
|
-
# By agent
|
|
598
|
-
if agent_name not in stats["by_agent"]:
|
|
599
|
-
stats["by_agent"][agent_name] = {}
|
|
600
|
-
if selected_model not in stats["by_agent"][agent_name]:
|
|
601
|
-
stats["by_agent"][agent_name][selected_model] = 0
|
|
602
|
-
stats["by_agent"][agent_name][selected_model] += 1
|
|
603
|
-
|
|
604
|
-
# By method
|
|
605
|
-
if selection_method not in stats["by_method"]:
|
|
606
|
-
stats["by_method"][selection_method] = 0
|
|
607
|
-
stats["by_method"][selection_method] += 1
|
|
608
|
-
|
|
609
|
-
# Complexity distribution
|
|
610
|
-
if complexity_score <= 30:
|
|
611
|
-
stats["complexity_distribution"]["0-30"] += 1
|
|
612
|
-
elif complexity_score <= 70:
|
|
613
|
-
stats["complexity_distribution"]["31-70"] += 1
|
|
614
|
-
else:
|
|
615
|
-
stats["complexity_distribution"]["71-100"] += 1
|
|
616
|
-
|
|
617
|
-
# Store updated stats with 24 hour TTL
|
|
618
|
-
cache.set(stats_key, stats, ttl=86400)
|
|
619
|
-
|
|
620
|
-
except Exception as e:
|
|
621
|
-
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")
|