claude-mpm 0.3.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.
Potentially problematic release.
This version of claude-mpm might be problematic. Click here for more details.
- claude_mpm/__init__.py +17 -0
- claude_mpm/__main__.py +14 -0
- claude_mpm/_version.py +32 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +88 -0
- claude_mpm/agents/INSTRUCTIONS.md +375 -0
- claude_mpm/agents/__init__.py +118 -0
- claude_mpm/agents/agent_loader.py +621 -0
- claude_mpm/agents/agent_loader_integration.py +229 -0
- claude_mpm/agents/agents_metadata.py +204 -0
- claude_mpm/agents/base_agent.json +27 -0
- claude_mpm/agents/base_agent_loader.py +519 -0
- claude_mpm/agents/schema/agent_schema.json +160 -0
- claude_mpm/agents/system_agent_config.py +587 -0
- claude_mpm/agents/templates/__init__.py +101 -0
- claude_mpm/agents/templates/data_engineer_agent.json +46 -0
- claude_mpm/agents/templates/documentation_agent.json +45 -0
- claude_mpm/agents/templates/engineer_agent.json +49 -0
- claude_mpm/agents/templates/ops_agent.json +46 -0
- claude_mpm/agents/templates/qa_agent.json +45 -0
- claude_mpm/agents/templates/research_agent.json +49 -0
- claude_mpm/agents/templates/security_agent.json +46 -0
- claude_mpm/agents/templates/update-optimized-specialized-agents.json +374 -0
- claude_mpm/agents/templates/version_control_agent.json +46 -0
- claude_mpm/agents/test_fix_deployment/.claude-pm/config/project.json +6 -0
- claude_mpm/cli.py +655 -0
- claude_mpm/cli_main.py +13 -0
- claude_mpm/cli_module/__init__.py +15 -0
- claude_mpm/cli_module/args.py +222 -0
- claude_mpm/cli_module/commands.py +203 -0
- claude_mpm/cli_module/migration_example.py +183 -0
- claude_mpm/cli_module/refactoring_guide.md +253 -0
- claude_mpm/cli_old/__init__.py +1 -0
- claude_mpm/cli_old/ticket_cli.py +102 -0
- claude_mpm/config/__init__.py +5 -0
- claude_mpm/config/hook_config.py +42 -0
- claude_mpm/constants.py +150 -0
- claude_mpm/core/__init__.py +45 -0
- claude_mpm/core/agent_name_normalizer.py +248 -0
- claude_mpm/core/agent_registry.py +627 -0
- claude_mpm/core/agent_registry.py.bak +312 -0
- claude_mpm/core/agent_session_manager.py +273 -0
- claude_mpm/core/base_service.py +747 -0
- claude_mpm/core/base_service.py.bak +406 -0
- claude_mpm/core/config.py +334 -0
- claude_mpm/core/config_aliases.py +292 -0
- claude_mpm/core/container.py +347 -0
- claude_mpm/core/factories.py +281 -0
- claude_mpm/core/framework_loader.py +472 -0
- claude_mpm/core/injectable_service.py +206 -0
- claude_mpm/core/interfaces.py +539 -0
- claude_mpm/core/logger.py +468 -0
- claude_mpm/core/minimal_framework_loader.py +107 -0
- claude_mpm/core/mixins.py +150 -0
- claude_mpm/core/service_registry.py +299 -0
- claude_mpm/core/session_manager.py +190 -0
- claude_mpm/core/simple_runner.py +511 -0
- claude_mpm/core/tool_access_control.py +173 -0
- claude_mpm/hooks/README.md +243 -0
- claude_mpm/hooks/__init__.py +5 -0
- claude_mpm/hooks/base_hook.py +154 -0
- claude_mpm/hooks/builtin/__init__.py +1 -0
- claude_mpm/hooks/builtin/logging_hook_example.py +165 -0
- claude_mpm/hooks/builtin/post_delegation_hook_example.py +124 -0
- claude_mpm/hooks/builtin/pre_delegation_hook_example.py +125 -0
- claude_mpm/hooks/builtin/submit_hook_example.py +100 -0
- claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +237 -0
- claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +239 -0
- claude_mpm/hooks/builtin/workflow_start_hook.py +181 -0
- claude_mpm/hooks/hook_client.py +264 -0
- claude_mpm/hooks/hook_runner.py +370 -0
- claude_mpm/hooks/json_rpc_executor.py +259 -0
- claude_mpm/hooks/json_rpc_hook_client.py +319 -0
- claude_mpm/hooks/tool_call_interceptor.py +204 -0
- claude_mpm/init.py +246 -0
- claude_mpm/orchestration/SUBPROCESS_DESIGN.md +66 -0
- claude_mpm/orchestration/__init__.py +6 -0
- claude_mpm/orchestration/archive/direct_orchestrator.py +195 -0
- claude_mpm/orchestration/archive/factory.py +215 -0
- claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +188 -0
- claude_mpm/orchestration/archive/hook_integration_example.py +178 -0
- claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +826 -0
- claude_mpm/orchestration/archive/orchestrator.py +501 -0
- claude_mpm/orchestration/archive/pexpect_orchestrator.py +252 -0
- claude_mpm/orchestration/archive/pty_orchestrator.py +270 -0
- claude_mpm/orchestration/archive/simple_orchestrator.py +82 -0
- claude_mpm/orchestration/archive/subprocess_orchestrator.py +801 -0
- claude_mpm/orchestration/archive/system_prompt_orchestrator.py +278 -0
- claude_mpm/orchestration/archive/wrapper_orchestrator.py +187 -0
- claude_mpm/scripts/__init__.py +1 -0
- claude_mpm/scripts/ticket.py +269 -0
- claude_mpm/services/__init__.py +10 -0
- claude_mpm/services/agent_deployment.py +955 -0
- claude_mpm/services/agent_lifecycle_manager.py +948 -0
- claude_mpm/services/agent_management_service.py +596 -0
- claude_mpm/services/agent_modification_tracker.py +841 -0
- claude_mpm/services/agent_profile_loader.py +606 -0
- claude_mpm/services/agent_registry.py +677 -0
- claude_mpm/services/base_agent_manager.py +380 -0
- claude_mpm/services/framework_agent_loader.py +337 -0
- claude_mpm/services/framework_claude_md_generator/README.md +92 -0
- claude_mpm/services/framework_claude_md_generator/__init__.py +206 -0
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +151 -0
- claude_mpm/services/framework_claude_md_generator/content_validator.py +126 -0
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +137 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +106 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +582 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +97 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +27 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +23 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +23 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +20 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/header.py +26 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +30 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +37 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +111 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +89 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +39 -0
- claude_mpm/services/framework_claude_md_generator/section_manager.py +106 -0
- claude_mpm/services/framework_claude_md_generator/version_manager.py +121 -0
- claude_mpm/services/framework_claude_md_generator.py +621 -0
- claude_mpm/services/hook_service.py +388 -0
- claude_mpm/services/hook_service_manager.py +223 -0
- claude_mpm/services/json_rpc_hook_manager.py +92 -0
- claude_mpm/services/parent_directory_manager/README.md +83 -0
- claude_mpm/services/parent_directory_manager/__init__.py +577 -0
- claude_mpm/services/parent_directory_manager/backup_manager.py +258 -0
- claude_mpm/services/parent_directory_manager/config_manager.py +210 -0
- claude_mpm/services/parent_directory_manager/deduplication_manager.py +279 -0
- claude_mpm/services/parent_directory_manager/framework_protector.py +143 -0
- claude_mpm/services/parent_directory_manager/operations.py +186 -0
- claude_mpm/services/parent_directory_manager/state_manager.py +624 -0
- claude_mpm/services/parent_directory_manager/template_deployer.py +579 -0
- claude_mpm/services/parent_directory_manager/validation_manager.py +378 -0
- claude_mpm/services/parent_directory_manager/version_control_helper.py +339 -0
- claude_mpm/services/parent_directory_manager/version_manager.py +222 -0
- claude_mpm/services/shared_prompt_cache.py +819 -0
- claude_mpm/services/ticket_manager.py +213 -0
- claude_mpm/services/ticket_manager_di.py +318 -0
- claude_mpm/services/ticketing_service_original.py +508 -0
- claude_mpm/services/version_control/VERSION +1 -0
- claude_mpm/services/version_control/__init__.py +70 -0
- claude_mpm/services/version_control/branch_strategy.py +670 -0
- claude_mpm/services/version_control/conflict_resolution.py +744 -0
- claude_mpm/services/version_control/git_operations.py +784 -0
- claude_mpm/services/version_control/semantic_versioning.py +703 -0
- claude_mpm/ui/__init__.py +1 -0
- claude_mpm/ui/rich_terminal_ui.py +295 -0
- claude_mpm/ui/terminal_ui.py +328 -0
- claude_mpm/utils/__init__.py +16 -0
- claude_mpm/utils/config_manager.py +468 -0
- claude_mpm/utils/import_migration_example.py +80 -0
- claude_mpm/utils/imports.py +182 -0
- claude_mpm/utils/path_operations.py +357 -0
- claude_mpm/utils/paths.py +289 -0
- claude_mpm-0.3.0.dist-info/METADATA +290 -0
- claude_mpm-0.3.0.dist-info/RECORD +159 -0
- claude_mpm-0.3.0.dist-info/WHEEL +5 -0
- claude_mpm-0.3.0.dist-info/entry_points.txt +4 -0
- claude_mpm-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Unified Agent Loader System
|
|
4
|
+
==========================
|
|
5
|
+
|
|
6
|
+
Provides unified loading of agent prompts from framework markdown files.
|
|
7
|
+
Integrates with SharedPromptCache for performance optimization.
|
|
8
|
+
|
|
9
|
+
Key Features:
|
|
10
|
+
- Loads agent prompts from framework/agent-roles/*.md files
|
|
11
|
+
- Handles base_agent.md prepending
|
|
12
|
+
- Provides backward-compatible get_*_agent_prompt() functions
|
|
13
|
+
- Uses SharedPromptCache for performance
|
|
14
|
+
- Special handling for ticketing agent's dynamic CLI help
|
|
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
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
from claude_pm.agents.agent_loader import get_documentation_agent_prompt
|
|
22
|
+
|
|
23
|
+
# Get agent prompt from MD file
|
|
24
|
+
prompt = get_documentation_agent_prompt()
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import logging
|
|
28
|
+
import os
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Optional, Dict, Any, Tuple, Union
|
|
31
|
+
|
|
32
|
+
from ..services.shared_prompt_cache import SharedPromptCache
|
|
33
|
+
from .base_agent_loader import prepend_base_instructions
|
|
34
|
+
# from ..services.task_complexity_analyzer import TaskComplexityAnalyzer, ComplexityLevel, ModelType
|
|
35
|
+
from ..utils.paths import PathResolver
|
|
36
|
+
|
|
37
|
+
# Temporary placeholders for missing module
|
|
38
|
+
class ComplexityLevel:
|
|
39
|
+
LOW = "LOW"
|
|
40
|
+
MEDIUM = "MEDIUM"
|
|
41
|
+
HIGH = "HIGH"
|
|
42
|
+
|
|
43
|
+
class ModelType:
|
|
44
|
+
HAIKU = "haiku"
|
|
45
|
+
SONNET = "sonnet"
|
|
46
|
+
OPUS = "opus"
|
|
47
|
+
|
|
48
|
+
# Module-level logger
|
|
49
|
+
logger = logging.getLogger(__name__)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _get_framework_agent_roles_dir() -> Path:
|
|
53
|
+
"""Get the framework agent-roles directory dynamically."""
|
|
54
|
+
# Use PathResolver for consistent path discovery
|
|
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
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# Framework agent-roles directory (dynamically determined)
|
|
92
|
+
FRAMEWORK_AGENT_ROLES_DIR = _get_framework_agent_roles_dir()
|
|
93
|
+
|
|
94
|
+
# 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
|
+
}
|
|
111
|
+
|
|
112
|
+
# Model configuration thresholds
|
|
113
|
+
MODEL_THRESHOLDS = {
|
|
114
|
+
ModelType.HAIKU: {"min_complexity": 0, "max_complexity": 30},
|
|
115
|
+
ModelType.SONNET: {"min_complexity": 31, "max_complexity": 70},
|
|
116
|
+
ModelType.OPUS: {"min_complexity": 71, "max_complexity": 100}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# Default model for each agent type (fallback when dynamic selection is disabled)
|
|
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
|
|
136
|
+
MODEL_NAME_MAPPINGS = {
|
|
137
|
+
ModelType.HAIKU: "claude-3-haiku-20240307",
|
|
138
|
+
ModelType.SONNET: "claude-sonnet-4-20250514",
|
|
139
|
+
ModelType.OPUS: "claude-4-opus"
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def load_agent_prompt_from_md(agent_name: str, force_reload: bool = False) -> Optional[str]:
|
|
144
|
+
"""
|
|
145
|
+
Load agent prompt from framework markdown file.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
agent_name: Agent name (e.g., 'documentation', 'ticketing')
|
|
149
|
+
force_reload: Force reload from file, bypassing cache
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
str: Agent prompt content from MD file, or None if not found
|
|
153
|
+
"""
|
|
154
|
+
try:
|
|
155
|
+
# Get cache instance
|
|
156
|
+
cache = SharedPromptCache.get_instance()
|
|
157
|
+
cache_key = f"{AGENT_CACHE_PREFIX}{agent_name}:md"
|
|
158
|
+
|
|
159
|
+
# Check cache first (unless force reload)
|
|
160
|
+
if not force_reload:
|
|
161
|
+
cached_content = cache.get(cache_key)
|
|
162
|
+
if cached_content is not None:
|
|
163
|
+
logger.debug(f"Agent prompt for '{agent_name}' loaded from cache")
|
|
164
|
+
return str(cached_content)
|
|
165
|
+
|
|
166
|
+
# Get MD file path
|
|
167
|
+
md_filename = AGENT_MAPPINGS.get(agent_name)
|
|
168
|
+
if not md_filename:
|
|
169
|
+
logger.warning(f"No MD file mapping found for agent: {agent_name}")
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
# Always get fresh framework directory path to ensure we're using the right location
|
|
173
|
+
framework_agent_roles_dir = _get_framework_agent_roles_dir()
|
|
174
|
+
md_path = framework_agent_roles_dir / md_filename
|
|
175
|
+
|
|
176
|
+
# Check if file exists
|
|
177
|
+
if not md_path.exists():
|
|
178
|
+
logger.warning(f"Agent MD file not found: {md_path}")
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
logger.debug(f"Loading agent prompt from: {md_path}")
|
|
182
|
+
content = md_path.read_text(encoding='utf-8')
|
|
183
|
+
|
|
184
|
+
# Cache the content with 1 hour TTL
|
|
185
|
+
cache.set(cache_key, content, ttl=3600)
|
|
186
|
+
logger.debug(f"Agent prompt for '{agent_name}' cached successfully")
|
|
187
|
+
|
|
188
|
+
return content
|
|
189
|
+
|
|
190
|
+
except Exception as e:
|
|
191
|
+
logger.error(f"Error loading agent prompt from MD for '{agent_name}': {e}")
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _analyze_task_complexity(task_description: str, context_size: int = 0, **kwargs: Any) -> Dict[str, Any]:
|
|
198
|
+
"""
|
|
199
|
+
Analyze task complexity using TaskComplexityAnalyzer.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
task_description: Description of the task
|
|
203
|
+
context_size: Size of context in characters
|
|
204
|
+
**kwargs: Additional parameters for complexity analysis
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Dictionary containing complexity analysis results
|
|
208
|
+
"""
|
|
209
|
+
# Temporary implementation until TaskComplexityAnalyzer is available
|
|
210
|
+
logger.warning("TaskComplexityAnalyzer not available, using default values")
|
|
211
|
+
return {
|
|
212
|
+
"complexity_score": 50,
|
|
213
|
+
"complexity_level": ComplexityLevel.MEDIUM,
|
|
214
|
+
"recommended_model": ModelType.SONNET,
|
|
215
|
+
"optimal_prompt_size": (700, 1000),
|
|
216
|
+
"error": "TaskComplexityAnalyzer module not available"
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _get_model_config(agent_name: str, complexity_analysis: Optional[Dict[str, Any]] = None) -> Tuple[str, Dict[str, Any]]:
|
|
221
|
+
"""
|
|
222
|
+
Get model configuration based on agent type and task complexity.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
agent_name: Name of the agent
|
|
226
|
+
complexity_analysis: Results from task complexity analysis
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Tuple of (selected_model, model_config)
|
|
230
|
+
"""
|
|
231
|
+
# Check if dynamic model selection is enabled
|
|
232
|
+
enable_dynamic_selection = os.getenv('ENABLE_DYNAMIC_MODEL_SELECTION', 'true').lower() == 'true'
|
|
233
|
+
|
|
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
|
+
# Check for per-agent override in environment
|
|
239
|
+
agent_override_key = f"CLAUDE_PM_{agent_name.upper()}_MODEL_SELECTION"
|
|
240
|
+
agent_override = os.getenv(agent_override_key, '').lower()
|
|
241
|
+
|
|
242
|
+
if agent_override == 'true':
|
|
243
|
+
enable_dynamic_selection = True
|
|
244
|
+
elif agent_override == 'false':
|
|
245
|
+
enable_dynamic_selection = False
|
|
246
|
+
|
|
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
|
+
# Dynamic model selection based on complexity
|
|
252
|
+
if enable_dynamic_selection and complexity_analysis:
|
|
253
|
+
recommended_model = complexity_analysis.get('recommended_model', ModelType.SONNET)
|
|
254
|
+
selected_model = MODEL_NAME_MAPPINGS.get(recommended_model, DEFAULT_AGENT_MODELS.get(agent_name, 'claude-sonnet-4-20250514'))
|
|
255
|
+
|
|
256
|
+
model_config = {
|
|
257
|
+
"selection_method": "dynamic_complexity_based",
|
|
258
|
+
"complexity_score": complexity_analysis.get('complexity_score', 50),
|
|
259
|
+
"complexity_level": complexity_analysis.get('complexity_level', ComplexityLevel.MEDIUM).value,
|
|
260
|
+
"optimal_prompt_size": complexity_analysis.get('optimal_prompt_size', (700, 1000)),
|
|
261
|
+
"scoring_breakdown": complexity_analysis.get('scoring_breakdown', {}),
|
|
262
|
+
"analysis_details": complexity_analysis.get('analysis_details', {})
|
|
263
|
+
}
|
|
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
|
+
else:
|
|
280
|
+
# Use default model mapping
|
|
281
|
+
selected_model = DEFAULT_AGENT_MODELS.get(agent_name, 'claude-sonnet-4-20250514')
|
|
282
|
+
model_config = {
|
|
283
|
+
"selection_method": "default_mapping",
|
|
284
|
+
"reason": "dynamic_selection_disabled" if not enable_dynamic_selection else "no_complexity_analysis"
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return selected_model, model_config
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
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
|
+
"""
|
|
292
|
+
Get agent prompt from MD file with optional dynamic model selection.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
agent_name: Agent name (e.g., 'documentation', 'ticketing')
|
|
296
|
+
force_reload: Force reload from source, bypassing cache
|
|
297
|
+
return_model_info: If True, returns tuple (prompt, model, config)
|
|
298
|
+
**kwargs: Additional arguments including:
|
|
299
|
+
- task_description: Description of the task for complexity analysis
|
|
300
|
+
- context_size: Size of context for complexity analysis
|
|
301
|
+
- enable_complexity_analysis: Override for complexity analysis
|
|
302
|
+
- Additional complexity factors (file_count, integration_points, etc.)
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
str or tuple: Complete agent prompt with base instructions prepended,
|
|
306
|
+
or tuple of (prompt, selected_model, model_config) if return_model_info=True
|
|
307
|
+
"""
|
|
308
|
+
# Load from MD file
|
|
309
|
+
prompt = load_agent_prompt_from_md(agent_name, force_reload)
|
|
310
|
+
|
|
311
|
+
if prompt is None:
|
|
312
|
+
raise ValueError(f"No agent prompt MD file found for: {agent_name}")
|
|
313
|
+
|
|
314
|
+
# Analyze task complexity if task description is provided
|
|
315
|
+
complexity_analysis = None
|
|
316
|
+
task_description = kwargs.get('task_description', '')
|
|
317
|
+
enable_analysis = kwargs.get('enable_complexity_analysis', True)
|
|
318
|
+
|
|
319
|
+
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
|
+
complexity_analysis = _analyze_task_complexity(
|
|
324
|
+
task_description=task_description,
|
|
325
|
+
context_size=kwargs.get('context_size', 0),
|
|
326
|
+
**analysis_kwargs
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
# Get model configuration (always happens, even without complexity analysis)
|
|
330
|
+
selected_model, model_config = _get_model_config(agent_name, complexity_analysis)
|
|
331
|
+
|
|
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
|
+
# Add model selection metadata to prompt if dynamic selection is enabled
|
|
353
|
+
if selected_model and model_config.get('selection_method') == 'dynamic_complexity_based':
|
|
354
|
+
model_metadata = f"\n<!-- Model Selection: {selected_model} (Complexity: {model_config.get('complexity_level', 'UNKNOWN')}) -->\n"
|
|
355
|
+
prompt = model_metadata + prompt
|
|
356
|
+
|
|
357
|
+
# Prepend base instructions with dynamic template based on complexity
|
|
358
|
+
complexity_score = model_config.get('complexity_score', 50) if model_config else 50
|
|
359
|
+
final_prompt = prepend_base_instructions(prompt, complexity_score=complexity_score)
|
|
360
|
+
|
|
361
|
+
# Return model info if requested
|
|
362
|
+
if return_model_info:
|
|
363
|
+
return final_prompt, selected_model, model_config
|
|
364
|
+
else:
|
|
365
|
+
return final_prompt
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
# Backward-compatible functions
|
|
369
|
+
def get_documentation_agent_prompt() -> str:
|
|
370
|
+
"""Get the complete Documentation Agent prompt with base instructions."""
|
|
371
|
+
prompt = get_agent_prompt("documentation", return_model_info=False)
|
|
372
|
+
assert isinstance(prompt, str), "Expected string when return_model_info=False"
|
|
373
|
+
return prompt
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def get_version_control_agent_prompt() -> str:
|
|
378
|
+
"""Get the complete Version Control Agent prompt with base instructions."""
|
|
379
|
+
prompt = get_agent_prompt("version_control", return_model_info=False)
|
|
380
|
+
assert isinstance(prompt, str), "Expected string when return_model_info=False"
|
|
381
|
+
return prompt
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def get_qa_agent_prompt() -> str:
|
|
385
|
+
"""Get the complete QA Agent prompt with base instructions."""
|
|
386
|
+
prompt = get_agent_prompt("qa", return_model_info=False)
|
|
387
|
+
assert isinstance(prompt, str), "Expected string when return_model_info=False"
|
|
388
|
+
return prompt
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def get_research_agent_prompt() -> str:
|
|
392
|
+
"""Get the complete Research Agent prompt with base instructions."""
|
|
393
|
+
prompt = get_agent_prompt("research", return_model_info=False)
|
|
394
|
+
assert isinstance(prompt, str), "Expected string when return_model_info=False"
|
|
395
|
+
return prompt
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def get_ops_agent_prompt() -> str:
|
|
399
|
+
"""Get the complete Ops Agent prompt with base instructions."""
|
|
400
|
+
prompt = get_agent_prompt("ops", return_model_info=False)
|
|
401
|
+
assert isinstance(prompt, str), "Expected string when return_model_info=False"
|
|
402
|
+
return prompt
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def get_security_agent_prompt() -> str:
|
|
406
|
+
"""Get the complete Security Agent prompt with base instructions."""
|
|
407
|
+
prompt = get_agent_prompt("security", return_model_info=False)
|
|
408
|
+
assert isinstance(prompt, str), "Expected string when return_model_info=False"
|
|
409
|
+
return prompt
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def get_engineer_agent_prompt() -> str:
|
|
413
|
+
"""Get the complete Engineer Agent prompt with base instructions."""
|
|
414
|
+
prompt = get_agent_prompt("engineer", return_model_info=False)
|
|
415
|
+
assert isinstance(prompt, str), "Expected string when return_model_info=False"
|
|
416
|
+
return prompt
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def get_data_engineer_agent_prompt() -> str:
|
|
420
|
+
"""Get the complete Data Engineer Agent prompt with base instructions."""
|
|
421
|
+
prompt = get_agent_prompt("data_engineer", return_model_info=False)
|
|
422
|
+
assert isinstance(prompt, str), "Expected string when return_model_info=False"
|
|
423
|
+
return prompt
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def get_agent_prompt_with_model_info(agent_name: str, force_reload: bool = False, **kwargs: Any) -> Tuple[str, str, Dict[str, Any]]:
|
|
427
|
+
"""
|
|
428
|
+
Get agent prompt with model selection information.
|
|
429
|
+
|
|
430
|
+
Args:
|
|
431
|
+
agent_name: Agent name (e.g., 'documentation', 'ticketing')
|
|
432
|
+
force_reload: Force reload from source, bypassing cache
|
|
433
|
+
**kwargs: Additional arguments for prompt generation and model selection
|
|
434
|
+
|
|
435
|
+
Returns:
|
|
436
|
+
Tuple of (prompt, selected_model, model_config)
|
|
437
|
+
"""
|
|
438
|
+
# Use get_agent_prompt with return_model_info=True
|
|
439
|
+
result = get_agent_prompt(agent_name, force_reload, return_model_info=True, **kwargs)
|
|
440
|
+
|
|
441
|
+
# If result is a tuple, return it directly
|
|
442
|
+
if isinstance(result, tuple):
|
|
443
|
+
return result
|
|
444
|
+
|
|
445
|
+
# Fallback (shouldn't happen)
|
|
446
|
+
return result, DEFAULT_AGENT_MODELS.get(agent_name, 'claude-sonnet-4-20250514'), {"selection_method": "default"}
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
# Utility functions
|
|
450
|
+
def list_available_agents() -> Dict[str, Dict[str, Any]]:
|
|
451
|
+
"""
|
|
452
|
+
List all available agents with their sources.
|
|
453
|
+
|
|
454
|
+
Returns:
|
|
455
|
+
dict: Agent information including MD file path
|
|
456
|
+
"""
|
|
457
|
+
agents = {}
|
|
458
|
+
|
|
459
|
+
# Get fresh framework directory path
|
|
460
|
+
framework_agent_roles_dir = _get_framework_agent_roles_dir()
|
|
461
|
+
|
|
462
|
+
for agent_name, md_filename in AGENT_MAPPINGS.items():
|
|
463
|
+
md_path = framework_agent_roles_dir / md_filename
|
|
464
|
+
|
|
465
|
+
agents[agent_name] = {
|
|
466
|
+
"md_file": md_filename if md_path.exists() else None,
|
|
467
|
+
"md_path": str(md_path) if md_path.exists() else None,
|
|
468
|
+
"has_md": md_path.exists(),
|
|
469
|
+
"default_model": DEFAULT_AGENT_MODELS.get(agent_name, 'claude-sonnet-4-20250514')
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return agents
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def clear_agent_cache(agent_name: Optional[str] = None) -> None:
|
|
476
|
+
"""
|
|
477
|
+
Clear cached agent prompts.
|
|
478
|
+
|
|
479
|
+
Args:
|
|
480
|
+
agent_name: Specific agent to clear, or None to clear all
|
|
481
|
+
"""
|
|
482
|
+
try:
|
|
483
|
+
cache = SharedPromptCache.get_instance()
|
|
484
|
+
|
|
485
|
+
if agent_name:
|
|
486
|
+
cache_key = f"{AGENT_CACHE_PREFIX}{agent_name}:md"
|
|
487
|
+
cache.invalidate(cache_key)
|
|
488
|
+
logger.debug(f"Cache cleared for agent: {agent_name}")
|
|
489
|
+
else:
|
|
490
|
+
# Clear all agent caches
|
|
491
|
+
for name in AGENT_MAPPINGS:
|
|
492
|
+
cache_key = f"{AGENT_CACHE_PREFIX}{name}:md"
|
|
493
|
+
cache.invalidate(cache_key)
|
|
494
|
+
logger.debug("All agent caches cleared")
|
|
495
|
+
|
|
496
|
+
except Exception as e:
|
|
497
|
+
logger.error(f"Error clearing agent cache: {e}")
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def validate_agent_files() -> Dict[str, Dict[str, Any]]:
|
|
501
|
+
"""
|
|
502
|
+
Validate that all expected agent files exist.
|
|
503
|
+
|
|
504
|
+
Returns:
|
|
505
|
+
dict: Validation results for each agent
|
|
506
|
+
"""
|
|
507
|
+
results = {}
|
|
508
|
+
|
|
509
|
+
# Get fresh framework directory path
|
|
510
|
+
framework_agent_roles_dir = _get_framework_agent_roles_dir()
|
|
511
|
+
|
|
512
|
+
for agent_name, md_filename in AGENT_MAPPINGS.items():
|
|
513
|
+
md_path = framework_agent_roles_dir / md_filename
|
|
514
|
+
results[agent_name] = {
|
|
515
|
+
"md_exists": md_path.exists(),
|
|
516
|
+
"md_path": str(md_path)
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return results
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def get_model_selection_metrics() -> Dict[str, Any]:
|
|
523
|
+
"""
|
|
524
|
+
Get metrics about model selection usage.
|
|
525
|
+
|
|
526
|
+
Returns:
|
|
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}")
|