claude-mpm 3.9.11__py3-none-any.whl → 4.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/VERSION +1 -1
- claude_mpm/__init__.py +2 -2
- claude_mpm/__main__.py +3 -2
- claude_mpm/agents/__init__.py +85 -79
- claude_mpm/agents/agent_loader.py +464 -1003
- claude_mpm/agents/agent_loader_integration.py +45 -45
- claude_mpm/agents/agents_metadata.py +29 -30
- claude_mpm/agents/async_agent_loader.py +156 -138
- claude_mpm/agents/base_agent.json +1 -1
- claude_mpm/agents/base_agent_loader.py +179 -151
- claude_mpm/agents/frontmatter_validator.py +229 -130
- claude_mpm/agents/schema/agent_schema.json +1 -1
- claude_mpm/agents/system_agent_config.py +213 -147
- claude_mpm/agents/templates/__init__.py +13 -13
- claude_mpm/agents/templates/code_analyzer.json +2 -2
- claude_mpm/agents/templates/data_engineer.json +1 -1
- claude_mpm/agents/templates/documentation.json +23 -11
- claude_mpm/agents/templates/engineer.json +22 -6
- claude_mpm/agents/templates/memory_manager.json +1 -1
- claude_mpm/agents/templates/ops.json +2 -2
- claude_mpm/agents/templates/project_organizer.json +1 -1
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/agents/templates/refactoring_engineer.json +222 -0
- claude_mpm/agents/templates/research.json +20 -14
- claude_mpm/agents/templates/security.json +1 -1
- claude_mpm/agents/templates/ticketing.json +1 -1
- claude_mpm/agents/templates/version_control.json +1 -1
- claude_mpm/agents/templates/web_qa.json +3 -1
- claude_mpm/agents/templates/web_ui.json +2 -2
- claude_mpm/cli/__init__.py +79 -51
- claude_mpm/cli/__main__.py +3 -2
- claude_mpm/cli/commands/__init__.py +20 -20
- claude_mpm/cli/commands/agents.py +279 -247
- claude_mpm/cli/commands/aggregate.py +138 -157
- claude_mpm/cli/commands/cleanup.py +147 -147
- claude_mpm/cli/commands/config.py +93 -76
- claude_mpm/cli/commands/info.py +17 -16
- claude_mpm/cli/commands/mcp.py +140 -905
- claude_mpm/cli/commands/mcp_command_router.py +139 -0
- claude_mpm/cli/commands/mcp_config_commands.py +20 -0
- claude_mpm/cli/commands/mcp_install_commands.py +20 -0
- claude_mpm/cli/commands/mcp_server_commands.py +175 -0
- claude_mpm/cli/commands/mcp_tool_commands.py +34 -0
- claude_mpm/cli/commands/memory.py +239 -203
- claude_mpm/cli/commands/monitor.py +203 -81
- claude_mpm/cli/commands/run.py +380 -429
- claude_mpm/cli/commands/run_config_checker.py +160 -0
- claude_mpm/cli/commands/socketio_monitor.py +235 -0
- claude_mpm/cli/commands/tickets.py +305 -197
- claude_mpm/cli/parser.py +24 -1156
- claude_mpm/cli/parsers/__init__.py +29 -0
- claude_mpm/cli/parsers/agents_parser.py +136 -0
- claude_mpm/cli/parsers/base_parser.py +331 -0
- claude_mpm/cli/parsers/config_parser.py +85 -0
- claude_mpm/cli/parsers/mcp_parser.py +152 -0
- claude_mpm/cli/parsers/memory_parser.py +138 -0
- claude_mpm/cli/parsers/monitor_parser.py +104 -0
- claude_mpm/cli/parsers/run_parser.py +147 -0
- claude_mpm/cli/parsers/tickets_parser.py +203 -0
- claude_mpm/cli/ticket_cli.py +7 -3
- claude_mpm/cli/utils.py +55 -37
- claude_mpm/cli_module/__init__.py +6 -6
- claude_mpm/cli_module/args.py +188 -140
- claude_mpm/cli_module/commands.py +79 -70
- claude_mpm/cli_module/migration_example.py +38 -60
- claude_mpm/config/__init__.py +32 -25
- claude_mpm/config/agent_config.py +151 -119
- claude_mpm/config/experimental_features.py +71 -73
- claude_mpm/config/paths.py +94 -208
- claude_mpm/config/socketio_config.py +84 -73
- claude_mpm/constants.py +35 -18
- claude_mpm/core/__init__.py +9 -6
- claude_mpm/core/agent_name_normalizer.py +68 -71
- claude_mpm/core/agent_registry.py +372 -521
- claude_mpm/core/agent_session_manager.py +74 -63
- claude_mpm/core/base_service.py +116 -87
- claude_mpm/core/cache.py +119 -153
- claude_mpm/core/claude_runner.py +425 -1120
- claude_mpm/core/config.py +263 -168
- claude_mpm/core/config_aliases.py +69 -61
- claude_mpm/core/config_constants.py +292 -0
- claude_mpm/core/constants.py +57 -99
- claude_mpm/core/container.py +211 -178
- claude_mpm/core/exceptions.py +233 -89
- claude_mpm/core/factories.py +92 -54
- claude_mpm/core/framework_loader.py +378 -220
- claude_mpm/core/hook_manager.py +198 -83
- claude_mpm/core/hook_performance_config.py +136 -0
- claude_mpm/core/injectable_service.py +61 -55
- claude_mpm/core/interactive_session.py +165 -155
- claude_mpm/core/interfaces.py +221 -195
- claude_mpm/core/lazy.py +96 -96
- claude_mpm/core/logger.py +133 -107
- claude_mpm/core/logging_config.py +185 -157
- claude_mpm/core/minimal_framework_loader.py +20 -15
- claude_mpm/core/mixins.py +30 -29
- claude_mpm/core/oneshot_session.py +215 -181
- claude_mpm/core/optimized_agent_loader.py +134 -138
- claude_mpm/core/optimized_startup.py +159 -157
- claude_mpm/core/pm_hook_interceptor.py +85 -72
- claude_mpm/core/service_registry.py +103 -101
- claude_mpm/core/session_manager.py +97 -87
- claude_mpm/core/socketio_pool.py +212 -158
- claude_mpm/core/tool_access_control.py +58 -51
- claude_mpm/core/types.py +46 -24
- claude_mpm/core/typing_utils.py +166 -82
- claude_mpm/core/unified_agent_registry.py +721 -0
- claude_mpm/core/unified_config.py +550 -0
- claude_mpm/core/unified_paths.py +549 -0
- claude_mpm/dashboard/index.html +1 -1
- claude_mpm/dashboard/open_dashboard.py +51 -17
- claude_mpm/dashboard/static/css/dashboard.css +27 -8
- claude_mpm/dashboard/static/dist/components/agent-inference.js +2 -0
- claude_mpm/dashboard/static/dist/components/event-processor.js +2 -0
- claude_mpm/dashboard/static/dist/components/event-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/export-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-library-loader.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-visualizer.js +2 -0
- claude_mpm/dashboard/static/dist/components/module-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/session-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/socket-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/ui-state-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/working-directory.js +2 -0
- claude_mpm/dashboard/static/dist/dashboard.js +2 -0
- claude_mpm/dashboard/static/dist/socket-client.js +2 -0
- claude_mpm/dashboard/static/js/components/agent-inference.js +80 -76
- claude_mpm/dashboard/static/js/components/event-processor.js +71 -67
- claude_mpm/dashboard/static/js/components/event-viewer.js +74 -70
- claude_mpm/dashboard/static/js/components/export-manager.js +31 -28
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +106 -92
- claude_mpm/dashboard/static/js/components/hud-library-loader.js +11 -11
- claude_mpm/dashboard/static/js/components/hud-manager.js +73 -73
- claude_mpm/dashboard/static/js/components/hud-visualizer.js +163 -163
- claude_mpm/dashboard/static/js/components/module-viewer.js +305 -233
- claude_mpm/dashboard/static/js/components/session-manager.js +32 -29
- claude_mpm/dashboard/static/js/components/socket-manager.js +27 -20
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +21 -18
- claude_mpm/dashboard/static/js/components/working-directory.js +74 -71
- claude_mpm/dashboard/static/js/dashboard.js +178 -453
- claude_mpm/dashboard/static/js/extension-error-handler.js +164 -0
- claude_mpm/dashboard/static/js/socket-client.js +120 -54
- claude_mpm/dashboard/templates/index.html +40 -50
- claude_mpm/experimental/cli_enhancements.py +60 -58
- claude_mpm/generators/__init__.py +1 -1
- claude_mpm/generators/agent_profile_generator.py +75 -65
- claude_mpm/hooks/__init__.py +1 -1
- claude_mpm/hooks/base_hook.py +33 -28
- claude_mpm/hooks/claude_hooks/__init__.py +1 -1
- claude_mpm/hooks/claude_hooks/connection_pool.py +120 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +743 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +415 -1331
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +4 -4
- claude_mpm/hooks/claude_hooks/memory_integration.py +221 -0
- claude_mpm/hooks/claude_hooks/response_tracking.py +348 -0
- claude_mpm/hooks/claude_hooks/tool_analysis.py +230 -0
- claude_mpm/hooks/memory_integration_hook.py +140 -100
- claude_mpm/hooks/tool_call_interceptor.py +89 -76
- claude_mpm/hooks/validation_hooks.py +57 -49
- claude_mpm/init.py +145 -121
- claude_mpm/models/__init__.py +9 -9
- claude_mpm/models/agent_definition.py +33 -23
- claude_mpm/models/agent_session.py +228 -200
- claude_mpm/scripts/__init__.py +1 -1
- claude_mpm/scripts/socketio_daemon.py +192 -75
- claude_mpm/scripts/socketio_server_manager.py +328 -0
- claude_mpm/scripts/start_activity_logging.py +25 -22
- claude_mpm/services/__init__.py +68 -43
- claude_mpm/services/agent_capabilities_service.py +271 -0
- claude_mpm/services/agents/__init__.py +23 -32
- claude_mpm/services/agents/deployment/__init__.py +3 -3
- claude_mpm/services/agents/deployment/agent_config_provider.py +310 -0
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +359 -0
- claude_mpm/services/agents/deployment/agent_definition_factory.py +84 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +415 -2113
- claude_mpm/services/agents/deployment/agent_discovery_service.py +387 -0
- claude_mpm/services/agents/deployment/agent_environment_manager.py +293 -0
- claude_mpm/services/agents/deployment/agent_filesystem_manager.py +387 -0
- claude_mpm/services/agents/deployment/agent_format_converter.py +453 -0
- claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +161 -0
- claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +345 -495
- claude_mpm/services/agents/deployment/agent_metrics_collector.py +279 -0
- claude_mpm/services/agents/deployment/agent_restore_handler.py +88 -0
- claude_mpm/services/agents/deployment/agent_template_builder.py +406 -0
- claude_mpm/services/agents/deployment/agent_validator.py +352 -0
- claude_mpm/services/agents/deployment/agent_version_manager.py +313 -0
- claude_mpm/services/agents/deployment/agent_versioning.py +6 -9
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +79 -0
- claude_mpm/services/agents/deployment/async_agent_deployment.py +298 -234
- claude_mpm/services/agents/deployment/config/__init__.py +13 -0
- claude_mpm/services/agents/deployment/config/deployment_config.py +182 -0
- claude_mpm/services/agents/deployment/config/deployment_config_manager.py +200 -0
- claude_mpm/services/agents/deployment/deployment_config_loader.py +54 -0
- claude_mpm/services/agents/deployment/deployment_type_detector.py +124 -0
- claude_mpm/services/agents/deployment/facade/__init__.py +18 -0
- claude_mpm/services/agents/deployment/facade/async_deployment_executor.py +159 -0
- claude_mpm/services/agents/deployment/facade/deployment_executor.py +73 -0
- claude_mpm/services/agents/deployment/facade/deployment_facade.py +270 -0
- claude_mpm/services/agents/deployment/facade/sync_deployment_executor.py +178 -0
- claude_mpm/services/agents/deployment/interface_adapter.py +227 -0
- claude_mpm/services/agents/deployment/lifecycle_health_checker.py +85 -0
- claude_mpm/services/agents/deployment/lifecycle_performance_tracker.py +100 -0
- claude_mpm/services/agents/deployment/pipeline/__init__.py +32 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +158 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +159 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +169 -0
- claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +19 -0
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +195 -0
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +119 -0
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +79 -0
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +90 -0
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +100 -0
- claude_mpm/services/agents/deployment/processors/__init__.py +15 -0
- claude_mpm/services/agents/deployment/processors/agent_deployment_context.py +98 -0
- claude_mpm/services/agents/deployment/processors/agent_deployment_result.py +235 -0
- claude_mpm/services/agents/deployment/processors/agent_processor.py +258 -0
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +318 -0
- claude_mpm/services/agents/deployment/results/__init__.py +13 -0
- claude_mpm/services/agents/deployment/results/deployment_metrics.py +200 -0
- claude_mpm/services/agents/deployment/results/deployment_result_builder.py +249 -0
- claude_mpm/services/agents/deployment/strategies/__init__.py +25 -0
- claude_mpm/services/agents/deployment/strategies/base_strategy.py +119 -0
- claude_mpm/services/agents/deployment/strategies/project_strategy.py +150 -0
- claude_mpm/services/agents/deployment/strategies/strategy_selector.py +117 -0
- claude_mpm/services/agents/deployment/strategies/system_strategy.py +116 -0
- claude_mpm/services/agents/deployment/strategies/user_strategy.py +137 -0
- claude_mpm/services/agents/deployment/system_instructions_deployer.py +108 -0
- claude_mpm/services/agents/deployment/validation/__init__.py +19 -0
- claude_mpm/services/agents/deployment/validation/agent_validator.py +323 -0
- claude_mpm/services/agents/deployment/validation/deployment_validator.py +238 -0
- claude_mpm/services/agents/deployment/validation/template_validator.py +299 -0
- claude_mpm/services/agents/deployment/validation/validation_result.py +226 -0
- claude_mpm/services/agents/loading/__init__.py +2 -2
- claude_mpm/services/agents/loading/agent_profile_loader.py +259 -229
- claude_mpm/services/agents/loading/base_agent_manager.py +90 -81
- claude_mpm/services/agents/loading/framework_agent_loader.py +154 -129
- claude_mpm/services/agents/management/__init__.py +2 -2
- claude_mpm/services/agents/management/agent_capabilities_generator.py +72 -58
- claude_mpm/services/agents/management/agent_management_service.py +209 -156
- claude_mpm/services/agents/memory/__init__.py +9 -6
- claude_mpm/services/agents/memory/agent_memory_manager.py +218 -1152
- claude_mpm/services/agents/memory/agent_persistence_service.py +20 -16
- claude_mpm/services/agents/memory/analyzer.py +430 -0
- claude_mpm/services/agents/memory/content_manager.py +376 -0
- claude_mpm/services/agents/memory/template_generator.py +468 -0
- claude_mpm/services/agents/registry/__init__.py +7 -10
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +122 -97
- claude_mpm/services/agents/registry/modification_tracker.py +351 -285
- claude_mpm/services/async_session_logger.py +187 -153
- claude_mpm/services/claude_session_logger.py +87 -72
- claude_mpm/services/command_handler_service.py +217 -0
- claude_mpm/services/communication/__init__.py +3 -2
- claude_mpm/services/core/__init__.py +50 -97
- claude_mpm/services/core/base.py +60 -53
- claude_mpm/services/core/interfaces/__init__.py +188 -0
- claude_mpm/services/core/interfaces/agent.py +351 -0
- claude_mpm/services/core/interfaces/communication.py +343 -0
- claude_mpm/services/core/interfaces/infrastructure.py +413 -0
- claude_mpm/services/core/interfaces/service.py +434 -0
- claude_mpm/services/core/interfaces.py +19 -944
- claude_mpm/services/event_aggregator.py +208 -170
- claude_mpm/services/exceptions.py +387 -308
- claude_mpm/services/framework_claude_md_generator/__init__.py +75 -79
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +69 -60
- claude_mpm/services/framework_claude_md_generator/content_validator.py +65 -61
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +68 -49
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +34 -34
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +25 -22
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +10 -10
- claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +6 -5
- claude_mpm/services/framework_claude_md_generator/section_generators/header.py +8 -7
- claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +6 -5
- claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +9 -8
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +5 -4
- claude_mpm/services/framework_claude_md_generator/section_manager.py +28 -27
- claude_mpm/services/framework_claude_md_generator/version_manager.py +30 -28
- claude_mpm/services/hook_service.py +106 -114
- claude_mpm/services/infrastructure/__init__.py +7 -5
- claude_mpm/services/infrastructure/context_preservation.py +233 -199
- claude_mpm/services/infrastructure/daemon_manager.py +279 -0
- claude_mpm/services/infrastructure/logging.py +83 -76
- claude_mpm/services/infrastructure/monitoring.py +547 -404
- claude_mpm/services/mcp_gateway/__init__.py +30 -13
- claude_mpm/services/mcp_gateway/config/__init__.py +2 -2
- claude_mpm/services/mcp_gateway/config/config_loader.py +61 -56
- claude_mpm/services/mcp_gateway/config/config_schema.py +50 -41
- claude_mpm/services/mcp_gateway/config/configuration.py +82 -75
- claude_mpm/services/mcp_gateway/core/__init__.py +13 -20
- claude_mpm/services/mcp_gateway/core/base.py +80 -67
- claude_mpm/services/mcp_gateway/core/exceptions.py +60 -46
- claude_mpm/services/mcp_gateway/core/interfaces.py +87 -84
- claude_mpm/services/mcp_gateway/main.py +287 -137
- claude_mpm/services/mcp_gateway/registry/__init__.py +1 -1
- claude_mpm/services/mcp_gateway/registry/service_registry.py +97 -94
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +135 -126
- claude_mpm/services/mcp_gateway/server/__init__.py +2 -2
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +105 -110
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +105 -107
- claude_mpm/services/mcp_gateway/server/stdio_server.py +691 -0
- claude_mpm/services/mcp_gateway/tools/__init__.py +4 -2
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +109 -119
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +283 -215
- claude_mpm/services/mcp_gateway/tools/hello_world.py +122 -120
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +652 -0
- claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +606 -0
- claude_mpm/services/memory/__init__.py +2 -2
- claude_mpm/services/memory/builder.py +451 -362
- claude_mpm/services/memory/cache/__init__.py +2 -2
- claude_mpm/services/memory/cache/shared_prompt_cache.py +232 -194
- claude_mpm/services/memory/cache/simple_cache.py +107 -93
- claude_mpm/services/memory/indexed_memory.py +195 -193
- claude_mpm/services/memory/optimizer.py +267 -234
- claude_mpm/services/memory/router.py +571 -263
- claude_mpm/services/memory_hook_service.py +237 -0
- claude_mpm/services/port_manager.py +223 -0
- claude_mpm/services/project/__init__.py +3 -3
- claude_mpm/services/project/analyzer.py +451 -305
- claude_mpm/services/project/registry.py +262 -240
- claude_mpm/services/recovery_manager.py +287 -231
- claude_mpm/services/response_tracker.py +87 -67
- claude_mpm/services/runner_configuration_service.py +587 -0
- claude_mpm/services/session_management_service.py +304 -0
- claude_mpm/services/socketio/__init__.py +4 -4
- claude_mpm/services/socketio/client_proxy.py +174 -0
- claude_mpm/services/socketio/handlers/__init__.py +3 -3
- claude_mpm/services/socketio/handlers/base.py +44 -30
- claude_mpm/services/socketio/handlers/connection.py +145 -65
- claude_mpm/services/socketio/handlers/file.py +123 -108
- claude_mpm/services/socketio/handlers/git.py +607 -373
- claude_mpm/services/socketio/handlers/hook.py +170 -0
- claude_mpm/services/socketio/handlers/memory.py +4 -4
- claude_mpm/services/socketio/handlers/project.py +4 -4
- claude_mpm/services/socketio/handlers/registry.py +53 -38
- claude_mpm/services/socketio/server/__init__.py +18 -0
- claude_mpm/services/socketio/server/broadcaster.py +252 -0
- claude_mpm/services/socketio/server/core.py +399 -0
- claude_mpm/services/socketio/server/main.py +323 -0
- claude_mpm/services/socketio_client_manager.py +160 -133
- claude_mpm/services/socketio_server.py +36 -1885
- claude_mpm/services/subprocess_launcher_service.py +316 -0
- claude_mpm/services/system_instructions_service.py +258 -0
- claude_mpm/services/ticket_manager.py +19 -533
- claude_mpm/services/utility_service.py +285 -0
- claude_mpm/services/version_control/__init__.py +18 -21
- claude_mpm/services/version_control/branch_strategy.py +20 -10
- claude_mpm/services/version_control/conflict_resolution.py +37 -13
- claude_mpm/services/version_control/git_operations.py +52 -21
- claude_mpm/services/version_control/semantic_versioning.py +92 -53
- claude_mpm/services/version_control/version_parser.py +145 -125
- claude_mpm/services/version_service.py +270 -0
- claude_mpm/storage/__init__.py +2 -2
- claude_mpm/storage/state_storage.py +177 -181
- claude_mpm/ticket_wrapper.py +2 -2
- claude_mpm/utils/__init__.py +2 -2
- claude_mpm/utils/agent_dependency_loader.py +453 -243
- claude_mpm/utils/config_manager.py +157 -118
- claude_mpm/utils/console.py +1 -1
- claude_mpm/utils/dependency_cache.py +102 -107
- claude_mpm/utils/dependency_manager.py +52 -47
- claude_mpm/utils/dependency_strategies.py +131 -96
- claude_mpm/utils/environment_context.py +110 -102
- claude_mpm/utils/error_handler.py +75 -55
- claude_mpm/utils/file_utils.py +80 -67
- claude_mpm/utils/framework_detection.py +12 -11
- claude_mpm/utils/import_migration_example.py +12 -60
- claude_mpm/utils/imports.py +48 -45
- claude_mpm/utils/path_operations.py +100 -93
- claude_mpm/utils/robust_installer.py +172 -164
- claude_mpm/utils/session_logging.py +30 -23
- claude_mpm/utils/subprocess_utils.py +99 -61
- claude_mpm/validation/__init__.py +1 -1
- claude_mpm/validation/agent_validator.py +151 -111
- claude_mpm/validation/frontmatter_validator.py +92 -71
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/METADATA +27 -1
- claude_mpm-4.0.3.dist-info/RECORD +402 -0
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/entry_points.txt +1 -0
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/licenses/LICENSE +1 -1
- claude_mpm/cli/commands/run_guarded.py +0 -511
- claude_mpm/config/memory_guardian_config.py +0 -325
- claude_mpm/config/memory_guardian_yaml.py +0 -335
- claude_mpm/core/config_paths.py +0 -150
- claude_mpm/core/memory_aware_runner.py +0 -353
- claude_mpm/dashboard/static/js/dashboard-original.js +0 -4134
- claude_mpm/deployment_paths.py +0 -261
- claude_mpm/hooks/claude_hooks/hook_handler_fixed.py +0 -454
- claude_mpm/models/state_models.py +0 -433
- claude_mpm/services/agent/__init__.py +0 -24
- claude_mpm/services/agent/deployment.py +0 -2548
- claude_mpm/services/agent/management.py +0 -598
- claude_mpm/services/agent/registry.py +0 -813
- claude_mpm/services/agents/registry/agent_registry.py +0 -813
- claude_mpm/services/communication/socketio.py +0 -1935
- claude_mpm/services/communication/websocket.py +0 -479
- claude_mpm/services/framework_claude_md_generator.py +0 -624
- claude_mpm/services/health_monitor.py +0 -893
- claude_mpm/services/infrastructure/graceful_degradation.py +0 -616
- claude_mpm/services/infrastructure/health_monitor.py +0 -775
- claude_mpm/services/infrastructure/memory_dashboard.py +0 -479
- claude_mpm/services/infrastructure/memory_guardian.py +0 -944
- claude_mpm/services/infrastructure/restart_protection.py +0 -642
- claude_mpm/services/infrastructure/state_manager.py +0 -774
- claude_mpm/services/mcp_gateway/manager.py +0 -334
- claude_mpm/services/optimized_hook_service.py +0 -542
- claude_mpm/services/project_analyzer.py +0 -864
- claude_mpm/services/project_registry.py +0 -608
- claude_mpm/services/standalone_socketio_server.py +0 -1300
- claude_mpm/services/ticket_manager_di.py +0 -318
- claude_mpm/services/ticketing_service_original.py +0 -510
- claude_mpm/utils/paths.py +0 -395
- claude_mpm/utils/platform_memory.py +0 -524
- claude_mpm-3.9.11.dist-info/RECORD +0 -306
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/WHEEL +0 -0
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/top_level.txt +0 -0
| @@ -1,153 +1,132 @@ | |
| 1 1 | 
             
            #!/usr/bin/env python3
         | 
| 2 2 | 
             
            """
         | 
| 3 | 
            -
            Unified Agent Loader System
         | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
            This module provides  | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
            - Automatic agent discovery from JSON files in configured agent directories
         | 
| 19 | 
            -
            - Schema validation ensures all agents conform to the expected structure
         | 
| 20 | 
            -
            - Intelligent caching using SharedPromptCache for performance optimization
         | 
| 21 | 
            -
            - Dynamic model selection based on task complexity analysis
         | 
| 22 | 
            -
            - Backward compatibility with legacy get_*_agent_prompt() functions
         | 
| 23 | 
            -
            - Prepends base instructions to maintain consistency across all agents
         | 
| 24 | 
            -
             | 
| 25 | 
            -
            Design Decisions:
         | 
| 26 | 
            -
            -----------------
         | 
| 27 | 
            -
            1. JSON-based Configuration: We chose JSON over YAML or Python files for:
         | 
| 28 | 
            -
               - Schema validation support
         | 
| 29 | 
            -
               - Language-agnostic configuration
         | 
| 30 | 
            -
               - Easy parsing and generation by tools
         | 
| 31 | 
            -
             | 
| 32 | 
            -
            2. Lazy Loading with Caching: Agents are loaded on-demand and cached to:
         | 
| 33 | 
            -
               - Reduce startup time
         | 
| 34 | 
            -
               - Minimize memory usage for unused agents
         | 
| 35 | 
            -
               - Allow hot-reloading during development
         | 
| 36 | 
            -
             | 
| 37 | 
            -
            3. Dynamic Model Selection: The system can analyze task complexity to:
         | 
| 38 | 
            -
               - Optimize cost by using appropriate model tiers
         | 
| 39 | 
            -
               - Improve performance for simple tasks
         | 
| 40 | 
            -
               - Ensure complex tasks get sufficient model capabilities
         | 
| 3 | 
            +
            Unified Agent Loader System - Main Entry Point
         | 
| 4 | 
            +
            ==============================================
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            This module provides the main entry point for the unified agent loading system.
         | 
| 7 | 
            +
            The system has been refactored into smaller, focused modules for better maintainability:
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            - agent_registry.py: Core agent discovery and registry management
         | 
| 10 | 
            +
            - agent_cache.py: Caching mechanisms for performance optimization
         | 
| 11 | 
            +
            - agent_validator.py: Schema validation and error handling
         | 
| 12 | 
            +
            - model_selector.py: Dynamic model selection based on task complexity
         | 
| 13 | 
            +
            - legacy_support.py: Backward compatibility functions
         | 
| 14 | 
            +
            - async_loader.py: High-performance async loading operations
         | 
| 15 | 
            +
            - metrics_collector.py: Performance monitoring and telemetry
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            This main module provides the unified interface while delegating to specialized modules.
         | 
| 41 18 |  | 
| 42 19 | 
             
            Usage Examples:
         | 
| 43 20 | 
             
            --------------
         | 
| 44 21 | 
             
                from claude_mpm.agents.agent_loader import get_documentation_agent_prompt
         | 
| 45 | 
            -
             | 
| 22 | 
            +
             | 
| 46 23 | 
             
                # Get agent prompt using backward-compatible function
         | 
| 47 24 | 
             
                prompt = get_documentation_agent_prompt()
         | 
| 48 | 
            -
             | 
| 25 | 
            +
             | 
| 49 26 | 
             
                # Get agent with model selection info
         | 
| 50 | 
            -
                prompt, model, config = get_agent_prompt("research_agent", | 
| 27 | 
            +
                prompt, model, config = get_agent_prompt("research_agent",
         | 
| 51 28 | 
             
                                                        return_model_info=True,
         | 
| 52 29 | 
             
                                                        task_description="Analyze codebase")
         | 
| 53 | 
            -
             | 
| 30 | 
            +
             | 
| 54 31 | 
             
                # List all available agents
         | 
| 55 32 | 
             
                agents = list_available_agents()
         | 
| 56 33 | 
             
            """
         | 
| 57 34 |  | 
| 58 | 
            -
            import json
         | 
| 59 35 | 
             
            import logging
         | 
| 60 36 | 
             
            import os
         | 
| 61 37 | 
             
            import time
         | 
| 62 | 
            -
            import yaml
         | 
| 63 | 
            -
            from pathlib import Path
         | 
| 64 | 
            -
            from typing import Optional, Dict, Any, Tuple, Union, List
         | 
| 65 38 | 
             
            from enum import Enum
         | 
| 39 | 
            +
            from pathlib import Path
         | 
| 40 | 
            +
            from typing import Any, Dict, List, Optional, Tuple, Union
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            # Import modular components
         | 
| 43 | 
            +
            from claude_mpm.core.unified_agent_registry import AgentTier
         | 
| 44 | 
            +
            from claude_mpm.core.unified_agent_registry import UnifiedAgentRegistry as AgentRegistry
         | 
| 66 45 |  | 
| 67 | 
            -
            from claude_mpm.services.memory.cache.shared_prompt_cache import SharedPromptCache
         | 
| 68 | 
            -
            from .base_agent_loader import prepend_base_instructions
         | 
| 69 | 
            -
            from ..validation.agent_validator import AgentValidator, ValidationResult
         | 
| 70 | 
            -
            from ..utils.paths import PathResolver
         | 
| 71 46 | 
             
            from ..core.agent_name_normalizer import AgentNameNormalizer
         | 
| 72 | 
            -
            from  | 
| 73 | 
            -
            from .frontmatter_validator import FrontmatterValidator
         | 
| 74 | 
            -
             | 
| 75 | 
            -
            # Temporary placeholders for missing module
         | 
| 76 | 
            -
            # WHY: These classes would normally come from a task_complexity module, but
         | 
| 77 | 
            -
            # we've included them here temporarily to avoid breaking dependencies.
         | 
| 78 | 
            -
            # This allows the agent loader to function independently while the full
         | 
| 79 | 
            -
            # complexity analysis system is being developed.
         | 
| 80 | 
            -
            class ComplexityLevel:
         | 
| 81 | 
            -
                """Represents the complexity level of a task for model selection."""
         | 
| 82 | 
            -
                LOW = "LOW"      # Simple tasks suitable for fast, economical models
         | 
| 83 | 
            -
                MEDIUM = "MEDIUM"  # Standard tasks requiring balanced capabilities
         | 
| 84 | 
            -
                HIGH = "HIGH"    # Complex tasks needing advanced reasoning
         | 
| 85 | 
            -
             | 
| 86 | 
            -
            class ModelType:
         | 
| 87 | 
            -
                """Claude model tiers used for dynamic selection based on task complexity."""
         | 
| 88 | 
            -
                HAIKU = "haiku"    # Fast, economical model for simple tasks
         | 
| 89 | 
            -
                SONNET = "sonnet"  # Balanced model for general-purpose tasks
         | 
| 90 | 
            -
                OPUS = "opus"      # Most capable model for complex reasoning
         | 
| 47 | 
            +
            from .base_agent_loader import prepend_base_instructions
         | 
| 91 48 |  | 
| 92 49 | 
             
            # Module-level logger
         | 
| 93 50 | 
             
            logger = logging.getLogger(__name__)
         | 
| 94 51 |  | 
| 95 52 |  | 
| 96 | 
            -
            class  | 
| 97 | 
            -
                """ | 
| 98 | 
            -
             | 
| 99 | 
            -
                 | 
| 100 | 
            -
                 | 
| 53 | 
            +
            class ModelType(str, Enum):
         | 
| 54 | 
            +
                """Claude model types for agent configuration."""
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                HAIKU = "haiku"
         | 
| 57 | 
            +
                SONNET = "sonnet"
         | 
| 58 | 
            +
                OPUS = "opus"
         | 
| 59 | 
            +
             | 
| 60 | 
            +
             | 
| 61 | 
            +
            class ComplexityLevel(str, Enum):
         | 
| 62 | 
            +
                """Task complexity levels for model selection."""
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                LOW = "low"
         | 
| 65 | 
            +
                MEDIUM = "medium"
         | 
| 66 | 
            +
                HIGH = "high"
         | 
| 67 | 
            +
             | 
| 68 | 
            +
             | 
| 69 | 
            +
            # Re-export key classes and functions
         | 
| 70 | 
            +
            __all__ = [
         | 
| 71 | 
            +
                "AgentLoader",
         | 
| 72 | 
            +
                "AgentTier",
         | 
| 73 | 
            +
                "get_agent_prompt",
         | 
| 74 | 
            +
                "list_available_agents",
         | 
| 75 | 
            +
                "validate_agent_files",
         | 
| 76 | 
            +
                "reload_agents",
         | 
| 77 | 
            +
                "get_agent_tier",
         | 
| 78 | 
            +
                "list_agents_by_tier",
         | 
| 79 | 
            +
            ]
         | 
| 101 80 |  | 
| 102 81 |  | 
| 103 82 | 
             
            def _get_agent_templates_dirs() -> Dict[AgentTier, Optional[Path]]:
         | 
| 104 83 | 
             
                """
         | 
| 105 84 | 
             
                Get directories containing agent JSON files across all tiers.
         | 
| 106 | 
            -
             | 
| 85 | 
            +
             | 
| 107 86 | 
             
                Returns a dictionary mapping tiers to their agent directories:
         | 
| 108 87 | 
             
                - PROJECT: .claude-mpm/agents in the current working directory
         | 
| 109 | 
            -
                - USER: ~/.claude-mpm/agents | 
| 88 | 
            +
                - USER: ~/.claude-mpm/agents
         | 
| 110 89 | 
             
                - SYSTEM: Built-in agents relative to this module
         | 
| 111 | 
            -
             | 
| 90 | 
            +
             | 
| 112 91 | 
             
                WHY: We support multiple tiers to allow project-specific customization
         | 
| 113 92 | 
             
                while maintaining backward compatibility with system agents.
         | 
| 114 | 
            -
             | 
| 93 | 
            +
             | 
| 115 94 | 
             
                Returns:
         | 
| 116 95 | 
             
                    Dict mapping AgentTier to Path (or None if not available)
         | 
| 117 96 | 
             
                """
         | 
| 118 97 | 
             
                dirs = {}
         | 
| 119 | 
            -
             | 
| 98 | 
            +
             | 
| 120 99 | 
             
                # PROJECT tier - ALWAYS check current working directory dynamically
         | 
| 121 100 | 
             
                # This ensures we pick up project agents even if CWD changes
         | 
| 122 | 
            -
                project_dir = Path.cwd() /  | 
| 101 | 
            +
                project_dir = Path.cwd() / get_path_manager().CONFIG_DIR / "agents"
         | 
| 123 102 | 
             
                if project_dir.exists():
         | 
| 124 103 | 
             
                    dirs[AgentTier.PROJECT] = project_dir
         | 
| 125 104 | 
             
                    logger.debug(f"Found PROJECT agents at: {project_dir}")
         | 
| 126 | 
            -
             | 
| 105 | 
            +
             | 
| 127 106 | 
             
                # USER tier - check user home directory
         | 
| 128 | 
            -
                user_config_dir =  | 
| 107 | 
            +
                user_config_dir = get_path_manager().get_user_config_dir()
         | 
| 129 108 | 
             
                if user_config_dir:
         | 
| 130 109 | 
             
                    user_agents_dir = user_config_dir / "agents"
         | 
| 131 110 | 
             
                    if user_agents_dir.exists():
         | 
| 132 111 | 
             
                        dirs[AgentTier.USER] = user_agents_dir
         | 
| 133 112 | 
             
                        logger.debug(f"Found USER agents at: {user_agents_dir}")
         | 
| 134 | 
            -
             | 
| 113 | 
            +
             | 
| 135 114 | 
             
                # SYSTEM tier - built-in agents
         | 
| 136 115 | 
             
                system_dir = Path(__file__).parent / "templates"
         | 
| 137 116 | 
             
                if system_dir.exists():
         | 
| 138 117 | 
             
                    dirs[AgentTier.SYSTEM] = system_dir
         | 
| 139 118 | 
             
                    logger.debug(f"Found SYSTEM agents at: {system_dir}")
         | 
| 140 | 
            -
             | 
| 119 | 
            +
             | 
| 141 120 | 
             
                return dirs
         | 
| 142 121 |  | 
| 143 122 |  | 
| 144 123 | 
             
            def _get_agent_templates_dir() -> Path:
         | 
| 145 124 | 
             
                """
         | 
| 146 125 | 
             
                Get the primary directory containing agent JSON files.
         | 
| 147 | 
            -
             | 
| 126 | 
            +
             | 
| 148 127 | 
             
                DEPRECATED: Use _get_agent_templates_dirs() for tier-aware loading.
         | 
| 149 128 | 
             
                This function is kept for backward compatibility.
         | 
| 150 | 
            -
             | 
| 129 | 
            +
             | 
| 151 130 | 
             
                Returns:
         | 
| 152 131 | 
             
                    Path: Absolute path to the system agents directory
         | 
| 153 132 | 
             
                """
         | 
| @@ -157,9 +136,6 @@ def _get_agent_templates_dir() -> Path: | |
| 157 136 | 
             
            # Agent directory - where all agent JSON files are stored
         | 
| 158 137 | 
             
            AGENT_TEMPLATES_DIR = _get_agent_templates_dir()
         | 
| 159 138 |  | 
| 160 | 
            -
            # Cache prefix for agent prompts - versioned to allow cache invalidation on schema changes
         | 
| 161 | 
            -
            # WHY: The "v2:" suffix allows us to invalidate all cached prompts when we make
         | 
| 162 | 
            -
            # breaking changes to the agent schema format
         | 
| 163 139 | 
             
            AGENT_CACHE_PREFIX = "agent_prompt:v2:"
         | 
| 164 140 |  | 
| 165 141 | 
             
            # Model configuration thresholds for dynamic selection
         | 
| @@ -169,573 +145,342 @@ AGENT_CACHE_PREFIX = "agent_prompt:v2:" | |
| 169 145 | 
             
            MODEL_THRESHOLDS = {
         | 
| 170 146 | 
             
                ModelType.HAIKU: {"min_complexity": 0, "max_complexity": 30},
         | 
| 171 147 | 
             
                ModelType.SONNET: {"min_complexity": 31, "max_complexity": 70},
         | 
| 172 | 
            -
                ModelType.OPUS: {"min_complexity": 71, "max_complexity": 100}
         | 
| 148 | 
            +
                ModelType.OPUS: {"min_complexity": 71, "max_complexity": 100},
         | 
| 173 149 | 
             
            }
         | 
| 174 150 |  | 
| 175 | 
            -
            # Model name mappings for Claude API
         | 
| 176 | 
            -
            # WHY: These map our internal model types to the actual API model identifiers.
         | 
| 177 | 
            -
            # The specific versions are chosen for their stability and feature completeness.
         | 
| 178 151 | 
             
            MODEL_NAME_MAPPINGS = {
         | 
| 179 | 
            -
                ModelType.HAIKU: "claude-3-haiku-20240307", | 
| 180 | 
            -
                ModelType.SONNET: "claude-sonnet-4-20250514", | 
| 181 | 
            -
                ModelType.OPUS: "claude-opus-4-20250514" | 
| 152 | 
            +
                ModelType.HAIKU: "claude-3-haiku-20240307",  # Fast, cost-effective
         | 
| 153 | 
            +
                ModelType.SONNET: "claude-sonnet-4-20250514",  # Balanced performance
         | 
| 154 | 
            +
                ModelType.OPUS: "claude-opus-4-20250514",  # Maximum capability
         | 
| 182 155 | 
             
            }
         | 
| 183 156 |  | 
| 184 157 |  | 
| 185 158 | 
             
            class AgentLoader:
         | 
| 186 159 | 
             
                """
         | 
| 187 | 
            -
                 | 
| 188 | 
            -
             | 
| 189 | 
            -
                This class  | 
| 190 | 
            -
                 | 
| 191 | 
            -
                 | 
| 192 | 
            -
                 | 
| 193 | 
            -
             | 
| 194 | 
            -
                 | 
| 195 | 
            -
                
         | 
| 196 | 
            -
                 | 
| 197 | 
            -
                -  | 
| 198 | 
            -
                -  | 
| 199 | 
            -
                - Agent usage frequency and patterns
         | 
| 200 | 
            -
                - Model selection distribution
         | 
| 201 | 
            -
                - Task complexity analysis results
         | 
| 202 | 
            -
                - Memory usage for agent definitions
         | 
| 203 | 
            -
                - Error rates during loading/validation
         | 
| 204 | 
            -
                - Agent prompt size distributions
         | 
| 205 | 
            -
                
         | 
| 206 | 
            -
                The loader follows a singleton-like pattern through the module-level
         | 
| 207 | 
            -
                _loader instance to ensure consistent state across the application.
         | 
| 208 | 
            -
                
         | 
| 209 | 
            -
                Attributes:
         | 
| 210 | 
            -
                    validator: AgentValidator instance for schema validation
         | 
| 211 | 
            -
                    cache: SharedPromptCache instance for performance optimization
         | 
| 212 | 
            -
                    _agent_registry: Internal dictionary mapping agent IDs to their configurations
         | 
| 160 | 
            +
                Simplified Agent Loader - Clean interface to agent registry.
         | 
| 161 | 
            +
             | 
| 162 | 
            +
                This class provides a simple, focused interface for agent loading:
         | 
| 163 | 
            +
                - AgentRegistry: Core agent discovery and registry management
         | 
| 164 | 
            +
                - Direct file access (no caching complexity)
         | 
| 165 | 
            +
                - Simple, testable design
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                The simplified design provides:
         | 
| 168 | 
            +
                - Clean separation of concerns
         | 
| 169 | 
            +
                - Easy testability
         | 
| 170 | 
            +
                - Minimal complexity
         | 
| 171 | 
            +
                - Fast, direct file access
         | 
| 213 172 | 
             
                """
         | 
| 214 | 
            -
             | 
| 173 | 
            +
             | 
| 215 174 | 
             
                def __init__(self):
         | 
| 216 175 | 
             
                    """
         | 
| 217 | 
            -
                    Initialize the agent loader  | 
| 218 | 
            -
             | 
| 176 | 
            +
                    Initialize the agent loader with the registry.
         | 
| 177 | 
            +
             | 
| 219 178 | 
             
                    The initialization process:
         | 
| 220 | 
            -
                    1. Creates  | 
| 221 | 
            -
                    2.  | 
| 222 | 
            -
                    3. Initializes empty agent registry
         | 
| 223 | 
            -
                    4. Discovers template directories across all tiers
         | 
| 224 | 
            -
                    5. Triggers agent discovery and loading
         | 
| 225 | 
            -
                    
         | 
| 226 | 
            -
                    METRICS OPPORTUNITIES:
         | 
| 227 | 
            -
                    - Track initialization time
         | 
| 228 | 
            -
                    - Monitor agent discovery performance
         | 
| 229 | 
            -
                    - Count total agents loaded vs validation failures
         | 
| 230 | 
            -
                    - Measure memory footprint of loaded agents
         | 
| 231 | 
            -
                    
         | 
| 232 | 
            -
                    WHY: We load agents eagerly during initialization to:
         | 
| 233 | 
            -
                    - Detect configuration errors early
         | 
| 234 | 
            -
                    - Build the registry once for efficient access
         | 
| 235 | 
            -
                    - Validate all agents before the system starts using them
         | 
| 179 | 
            +
                    1. Creates the agent registry
         | 
| 180 | 
            +
                    2. Loads agents from all tiers
         | 
| 236 181 | 
             
                    """
         | 
| 237 | 
            -
                    self.validator = AgentValidator()
         | 
| 238 | 
            -
                    self.cache = SharedPromptCache.get_instance()
         | 
| 239 | 
            -
                    self._agent_registry: Dict[str, Dict[str, Any]] = {}
         | 
| 240 | 
            -
                    
         | 
| 241 | 
            -
                    # Template directories will be discovered dynamically during loading
         | 
| 242 | 
            -
                    self._template_dirs = {}
         | 
| 243 | 
            -
                    
         | 
| 244 | 
            -
                    # Track which tier each agent came from for debugging
         | 
| 245 | 
            -
                    self._agent_tiers: Dict[str, AgentTier] = {}
         | 
| 246 | 
            -
                    
         | 
| 247 | 
            -
                    # Initialize frontmatter validator for .md agent files
         | 
| 248 | 
            -
                    self.frontmatter_validator = FrontmatterValidator()
         | 
| 249 | 
            -
                    
         | 
| 250 | 
            -
                    # METRICS: Initialize performance tracking
         | 
| 251 | 
            -
                    # This structure collects valuable telemetry for AI agent performance
         | 
| 252 | 
            -
                    self._metrics = {
         | 
| 253 | 
            -
                        'agents_loaded': 0,
         | 
| 254 | 
            -
                        'agents_by_tier': {tier.value: 0 for tier in AgentTier},
         | 
| 255 | 
            -
                        'validation_failures': 0,
         | 
| 256 | 
            -
                        'cache_hits': 0,
         | 
| 257 | 
            -
                        'cache_misses': 0,
         | 
| 258 | 
            -
                        'load_times': {},  # agent_id -> load time ms
         | 
| 259 | 
            -
                        'usage_counts': {},  # agent_id -> usage count
         | 
| 260 | 
            -
                        'model_selections': {},  # model -> count
         | 
| 261 | 
            -
                        'complexity_scores': [],  # Distribution of task complexity
         | 
| 262 | 
            -
                        'prompt_sizes': {},  # agent_id -> prompt size in chars
         | 
| 263 | 
            -
                        'error_types': {},  # error_type -> count
         | 
| 264 | 
            -
                        'initialization_time_ms': 0
         | 
| 265 | 
            -
                    }
         | 
| 266 | 
            -
                    
         | 
| 267 | 
            -
                    # METRICS: Track initialization performance
         | 
| 268 182 | 
             
                    start_time = time.time()
         | 
| 269 | 
            -
             | 
| 270 | 
            -
                     | 
| 271 | 
            -
                     | 
| 272 | 
            -
             | 
| 273 | 
            -
             | 
| 274 | 
            -
                     | 
| 275 | 
            -
             | 
| 276 | 
            -
                    
         | 
| 277 | 
            -
                     | 
| 278 | 
            -
             | 
| 279 | 
            -
                     | 
| 280 | 
            -
             | 
| 281 | 
            -
                    
         | 
| 282 | 
            -
                    WHY: We use tier-based discovery to allow:
         | 
| 283 | 
            -
                    - Project-specific agent customization
         | 
| 284 | 
            -
                    - User-level agent modifications
         | 
| 285 | 
            -
                    - Fallback to system defaults
         | 
| 286 | 
            -
                    
         | 
| 287 | 
            -
                    Performance:
         | 
| 288 | 
            -
                    - Async loading (default) provides 60-80% faster startup
         | 
| 289 | 
            -
                    - Falls back to sync loading if async unavailable
         | 
| 290 | 
            -
                    
         | 
| 291 | 
            -
                    Error Handling:
         | 
| 292 | 
            -
                    - Invalid JSON files are logged but don't stop the loading process
         | 
| 293 | 
            -
                    - Schema validation failures are logged with details
         | 
| 294 | 
            -
                    - The system continues to function with whatever valid agents it finds
         | 
| 295 | 
            -
                    """
         | 
| 296 | 
            -
                    # Try async loading for better performance
         | 
| 297 | 
            -
                    if use_async:
         | 
| 298 | 
            -
                        try:
         | 
| 299 | 
            -
                            from .async_agent_loader import load_agents_async
         | 
| 300 | 
            -
                            logger.info("Using async agent loading for improved performance")
         | 
| 301 | 
            -
                            
         | 
| 302 | 
            -
                            # Load agents asynchronously
         | 
| 303 | 
            -
                            agents = load_agents_async()
         | 
| 304 | 
            -
                            
         | 
| 305 | 
            -
                            # Update registry
         | 
| 306 | 
            -
                            self._agent_registry = agents
         | 
| 307 | 
            -
                            
         | 
| 308 | 
            -
                            # Update metrics
         | 
| 309 | 
            -
                            self._metrics['agents_loaded'] = len(agents)
         | 
| 310 | 
            -
                            
         | 
| 311 | 
            -
                            # Extract tier information
         | 
| 312 | 
            -
                            for agent_id, agent_data in agents.items():
         | 
| 313 | 
            -
                                tier_str = agent_data.get('_tier', 'system')
         | 
| 314 | 
            -
                                self._agent_tiers[agent_id] = AgentTier(tier_str)
         | 
| 315 | 
            -
                                
         | 
| 316 | 
            -
                            logger.info(f"Async loaded {len(agents)} agents successfully")
         | 
| 317 | 
            -
                            return
         | 
| 318 | 
            -
                            
         | 
| 319 | 
            -
                        except ImportError:
         | 
| 320 | 
            -
                            logger.warning("Async loading not available, falling back to sync")
         | 
| 321 | 
            -
                        except Exception as e:
         | 
| 322 | 
            -
                            logger.warning(f"Async loading failed, falling back to sync: {e}")
         | 
| 323 | 
            -
                    
         | 
| 324 | 
            -
                    # Fall back to synchronous loading
         | 
| 325 | 
            -
                    logger.info("Using synchronous agent loading")
         | 
| 326 | 
            -
                    
         | 
| 327 | 
            -
                    # Dynamically discover agent directories at load time
         | 
| 328 | 
            -
                    self._template_dirs = _get_agent_templates_dirs()
         | 
| 329 | 
            -
                    
         | 
| 330 | 
            -
                    logger.info(f"Loading agents from {len(self._template_dirs)} tier(s)")
         | 
| 331 | 
            -
                    
         | 
| 332 | 
            -
                    # Perform startup validation check for .md agent files
         | 
| 333 | 
            -
                    self._validate_markdown_agents()
         | 
| 334 | 
            -
                    
         | 
| 335 | 
            -
                    # Process tiers in REVERSE precedence order (SYSTEM first, PROJECT last)
         | 
| 336 | 
            -
                    # This ensures PROJECT agents override USER/SYSTEM agents
         | 
| 337 | 
            -
                    for tier in [AgentTier.SYSTEM, AgentTier.USER, AgentTier.PROJECT]:
         | 
| 338 | 
            -
                        if tier not in self._template_dirs:
         | 
| 339 | 
            -
                            continue
         | 
| 340 | 
            -
                            
         | 
| 341 | 
            -
                        templates_dir = self._template_dirs[tier]
         | 
| 342 | 
            -
                        logger.debug(f"Loading {tier.value} agents from {templates_dir}")
         | 
| 343 | 
            -
                        
         | 
| 344 | 
            -
                        for json_file in templates_dir.glob("*.json"):
         | 
| 345 | 
            -
                            # Skip the schema definition file itself
         | 
| 346 | 
            -
                            if json_file.name == "agent_schema.json":
         | 
| 347 | 
            -
                                continue
         | 
| 348 | 
            -
                            
         | 
| 349 | 
            -
                            try:
         | 
| 350 | 
            -
                                with open(json_file, 'r') as f:
         | 
| 351 | 
            -
                                    agent_data = json.load(f)
         | 
| 352 | 
            -
                                
         | 
| 353 | 
            -
                                # For files without _agent suffix, use the filename as agent_id
         | 
| 354 | 
            -
                                if "agent_id" not in agent_data:
         | 
| 355 | 
            -
                                    agent_data["agent_id"] = json_file.stem
         | 
| 356 | 
            -
                                
         | 
| 357 | 
            -
                                # Validate against schema to ensure consistency
         | 
| 358 | 
            -
                                # Skip validation for now if instructions are plain text (not in expected format)
         | 
| 359 | 
            -
                                if "instructions" in agent_data and isinstance(agent_data["instructions"], str) and len(agent_data["instructions"]) > 10000:
         | 
| 360 | 
            -
                                    # For very long instructions, skip validation but log warning
         | 
| 361 | 
            -
                                    logger.warning(f"Skipping validation for {json_file.name} due to long instructions")
         | 
| 362 | 
            -
                                    validation_result = ValidationResult(is_valid=True, warnings=["Validation skipped due to long instructions"])
         | 
| 363 | 
            -
                                else:
         | 
| 364 | 
            -
                                    validation_result = self.validator.validate_agent(agent_data)
         | 
| 365 | 
            -
                                
         | 
| 366 | 
            -
                                if validation_result.is_valid:
         | 
| 367 | 
            -
                                    agent_id = agent_data.get("agent_id")
         | 
| 368 | 
            -
                                    if agent_id:
         | 
| 369 | 
            -
                                        # Check if this agent was already loaded from a higher-precedence tier
         | 
| 370 | 
            -
                                        if agent_id in self._agent_registry:
         | 
| 371 | 
            -
                                            existing_tier = self._agent_tiers.get(agent_id)
         | 
| 372 | 
            -
                                            # Only override if current tier has higher precedence
         | 
| 373 | 
            -
                                            if tier == AgentTier.PROJECT or \
         | 
| 374 | 
            -
                                               (tier == AgentTier.USER and existing_tier == AgentTier.SYSTEM):
         | 
| 375 | 
            -
                                                logger.info(f"Overriding {existing_tier.value} agent '{agent_id}' with {tier.value} version")
         | 
| 376 | 
            -
                                            else:
         | 
| 377 | 
            -
                                                logger.debug(f"Skipping {tier.value} agent '{agent_id}' - already loaded from {existing_tier.value}")
         | 
| 378 | 
            -
                                                continue
         | 
| 379 | 
            -
                                        
         | 
| 380 | 
            -
                                        # Register the agent
         | 
| 381 | 
            -
                                        self._agent_registry[agent_id] = agent_data
         | 
| 382 | 
            -
                                        self._agent_tiers[agent_id] = tier
         | 
| 383 | 
            -
                                        
         | 
| 384 | 
            -
                                        # METRICS: Track successful agent load
         | 
| 385 | 
            -
                                        self._metrics['agents_loaded'] += 1
         | 
| 386 | 
            -
                                        self._metrics['agents_by_tier'][tier.value] += 1
         | 
| 387 | 
            -
                                        logger.debug(f"Loaded {tier.value} agent: {agent_id}")
         | 
| 388 | 
            -
                                else:
         | 
| 389 | 
            -
                                    # Log validation errors but continue loading other agents
         | 
| 390 | 
            -
                                    # METRICS: Track validation failure
         | 
| 391 | 
            -
                                    self._metrics['validation_failures'] += 1
         | 
| 392 | 
            -
                                    logger.warning(f"Invalid agent in {json_file.name}: {validation_result.errors}")
         | 
| 393 | 
            -
                                    
         | 
| 394 | 
            -
                            except Exception as e:
         | 
| 395 | 
            -
                                # Log loading errors but don't crash - system should be resilient
         | 
| 396 | 
            -
                                logger.error(f"Failed to load {json_file.name}: {e}")
         | 
| 397 | 
            -
                
         | 
| 398 | 
            -
                def _validate_markdown_agents(self) -> None:
         | 
| 399 | 
            -
                    """
         | 
| 400 | 
            -
                    Validate frontmatter in all .md agent files at startup.
         | 
| 401 | 
            -
                    
         | 
| 402 | 
            -
                    This method performs validation and reports issues found in agent files.
         | 
| 403 | 
            -
                    It checks all tiers and provides a summary of validation results.
         | 
| 404 | 
            -
                    Auto-correction is applied in memory but not written to files.
         | 
| 405 | 
            -
                    """
         | 
| 406 | 
            -
                    validation_summary = {
         | 
| 407 | 
            -
                        'total_checked': 0,
         | 
| 408 | 
            -
                        'valid': 0,
         | 
| 409 | 
            -
                        'corrected': 0,
         | 
| 410 | 
            -
                        'errors': 0,
         | 
| 411 | 
            -
                        'by_tier': {}
         | 
| 412 | 
            -
                    }
         | 
| 413 | 
            -
                    
         | 
| 414 | 
            -
                    # Check the .claude/agents directory for .md files
         | 
| 415 | 
            -
                    claude_agents_dir = Path.cwd() / ".claude" / "agents"
         | 
| 416 | 
            -
                    if claude_agents_dir.exists():
         | 
| 417 | 
            -
                        logger.info("Validating agent files in .claude/agents directory...")
         | 
| 418 | 
            -
                        
         | 
| 419 | 
            -
                        for md_file in claude_agents_dir.glob("*.md"):
         | 
| 420 | 
            -
                            validation_summary['total_checked'] += 1
         | 
| 421 | 
            -
                            
         | 
| 422 | 
            -
                            # Validate the file
         | 
| 423 | 
            -
                            result = self.frontmatter_validator.validate_file(md_file)
         | 
| 424 | 
            -
                            
         | 
| 425 | 
            -
                            if result.is_valid and not result.corrections:
         | 
| 426 | 
            -
                                validation_summary['valid'] += 1
         | 
| 427 | 
            -
                            elif result.corrections:
         | 
| 428 | 
            -
                                validation_summary['corrected'] += 1
         | 
| 429 | 
            -
                                logger.info(f"Auto-corrected frontmatter in {md_file.name}:")
         | 
| 430 | 
            -
                                for correction in result.corrections:
         | 
| 431 | 
            -
                                    logger.info(f"  - {correction}")
         | 
| 432 | 
            -
                            
         | 
| 433 | 
            -
                            if result.errors:
         | 
| 434 | 
            -
                                validation_summary['errors'] += 1
         | 
| 435 | 
            -
                                logger.warning(f"Validation errors in {md_file.name}:")
         | 
| 436 | 
            -
                                for error in result.errors:
         | 
| 437 | 
            -
                                    logger.warning(f"  - {error}")
         | 
| 438 | 
            -
                            
         | 
| 439 | 
            -
                            if result.warnings:
         | 
| 440 | 
            -
                                for warning in result.warnings:
         | 
| 441 | 
            -
                                    logger.debug(f"  Warning in {md_file.name}: {warning}")
         | 
| 442 | 
            -
                    
         | 
| 443 | 
            -
                    # Check template directories for .md files
         | 
| 444 | 
            -
                    for tier, templates_dir in self._template_dirs.items():
         | 
| 445 | 
            -
                        if not templates_dir:
         | 
| 446 | 
            -
                            continue
         | 
| 447 | 
            -
                            
         | 
| 448 | 
            -
                        tier_stats = {'checked': 0, 'valid': 0, 'corrected': 0, 'errors': 0}
         | 
| 449 | 
            -
                        
         | 
| 450 | 
            -
                        for md_file in templates_dir.glob("*.md"):
         | 
| 451 | 
            -
                            validation_summary['total_checked'] += 1
         | 
| 452 | 
            -
                            tier_stats['checked'] += 1
         | 
| 453 | 
            -
                            
         | 
| 454 | 
            -
                            # Validate the file
         | 
| 455 | 
            -
                            result = self.frontmatter_validator.validate_file(md_file)
         | 
| 456 | 
            -
                            
         | 
| 457 | 
            -
                            if result.is_valid and not result.corrections:
         | 
| 458 | 
            -
                                validation_summary['valid'] += 1
         | 
| 459 | 
            -
                                tier_stats['valid'] += 1
         | 
| 460 | 
            -
                            elif result.corrections:
         | 
| 461 | 
            -
                                validation_summary['corrected'] += 1
         | 
| 462 | 
            -
                                tier_stats['corrected'] += 1
         | 
| 463 | 
            -
                                logger.debug(f"Auto-corrected {tier.value} agent {md_file.name}")
         | 
| 464 | 
            -
                            
         | 
| 465 | 
            -
                            if result.errors:
         | 
| 466 | 
            -
                                validation_summary['errors'] += 1
         | 
| 467 | 
            -
                                tier_stats['errors'] += 1
         | 
| 468 | 
            -
                        
         | 
| 469 | 
            -
                        if tier_stats['checked'] > 0:
         | 
| 470 | 
            -
                            validation_summary['by_tier'][tier.value] = tier_stats
         | 
| 471 | 
            -
                    
         | 
| 472 | 
            -
                    # Log validation summary
         | 
| 473 | 
            -
                    if validation_summary['total_checked'] > 0:
         | 
| 474 | 
            -
                        logger.info(
         | 
| 475 | 
            -
                            f"Agent validation summary: "
         | 
| 476 | 
            -
                            f"{validation_summary['total_checked']} files checked, "
         | 
| 477 | 
            -
                            f"{validation_summary['valid']} valid, "
         | 
| 478 | 
            -
                            f"{validation_summary['corrected']} auto-corrected, "
         | 
| 479 | 
            -
                            f"{validation_summary['errors']} with errors"
         | 
| 480 | 
            -
                        )
         | 
| 481 | 
            -
                        
         | 
| 482 | 
            -
                        # Store in metrics for reporting
         | 
| 483 | 
            -
                        self._metrics['validation_summary'] = validation_summary
         | 
| 484 | 
            -
                
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                    # Initialize the agent registry
         | 
| 185 | 
            +
                    self.registry = get_agent_registry()
         | 
| 186 | 
            +
             | 
| 187 | 
            +
                    # Load agents through registry
         | 
| 188 | 
            +
                    self.registry.load_agents()
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                    init_time = (time.time() - start_time) * 1000
         | 
| 191 | 
            +
                    logger.info(
         | 
| 192 | 
            +
                        f"AgentLoader initialized in {init_time:.2f}ms with {len(self.registry._agent_registry)} agents"
         | 
| 193 | 
            +
                    )
         | 
| 194 | 
            +
             | 
| 485 195 | 
             
                def get_agent(self, agent_id: str) -> Optional[Dict[str, Any]]:
         | 
| 486 196 | 
             
                    """
         | 
| 487 197 | 
             
                    Retrieve agent configuration by ID.
         | 
| 488 | 
            -
             | 
| 198 | 
            +
             | 
| 489 199 | 
             
                    Args:
         | 
| 490 | 
            -
                        agent_id:  | 
| 491 | 
            -
             | 
| 200 | 
            +
                        agent_id: Agent identifier
         | 
| 201 | 
            +
             | 
| 492 202 | 
             
                    Returns:
         | 
| 493 | 
            -
                         | 
| 494 | 
            -
                        
         | 
| 495 | 
            -
                    WHY: Direct dictionary lookup for O(1) performance, essential for
         | 
| 496 | 
            -
                    frequently accessed agents during runtime.
         | 
| 203 | 
            +
                        Agent configuration or None if not found
         | 
| 497 204 | 
             
                    """
         | 
| 498 | 
            -
                     | 
| 499 | 
            -
             | 
| 500 | 
            -
                        # Add tier information to the agent data for debugging
         | 
| 501 | 
            -
                        agent_data = agent_data.copy()
         | 
| 502 | 
            -
                        agent_data['_tier'] = self._agent_tiers[agent_id].value
         | 
| 503 | 
            -
                    return agent_data
         | 
| 504 | 
            -
                
         | 
| 205 | 
            +
                    return self.registry.get_agent(agent_id)
         | 
| 206 | 
            +
             | 
| 505 207 | 
             
                def list_agents(self) -> List[Dict[str, Any]]:
         | 
| 506 208 | 
             
                    """
         | 
| 507 209 | 
             
                    Get a summary list of all available agents.
         | 
| 508 | 
            -
             | 
| 210 | 
            +
             | 
| 509 211 | 
             
                    Returns:
         | 
| 510 | 
            -
                        List of agent summaries  | 
| 511 | 
            -
                        
         | 
| 512 | 
            -
                    WHY: We return a summary instead of full configurations to:
         | 
| 513 | 
            -
                    - Reduce memory usage when listing many agents
         | 
| 514 | 
            -
                    - Provide only the information needed for agent selection
         | 
| 515 | 
            -
                    - Keep the API response size manageable
         | 
| 516 | 
            -
                    
         | 
| 517 | 
            -
                    The returned list is sorted by ID for consistent ordering across calls.
         | 
| 212 | 
            +
                        List of agent summaries with key metadata
         | 
| 518 213 | 
             
                    """
         | 
| 519 | 
            -
                     | 
| 520 | 
            -
             | 
| 521 | 
            -
             | 
| 522 | 
            -
             | 
| 523 | 
            -
             | 
| 524 | 
            -
                            "name": agent_data.get("metadata", {}).get("name", agent_id),
         | 
| 525 | 
            -
                            "description": agent_data.get("metadata", {}).get("description", ""),
         | 
| 526 | 
            -
                            "category": agent_data.get("metadata", {}).get("category", ""),
         | 
| 527 | 
            -
                            "model": agent_data.get("capabilities", {}).get("model", ""),
         | 
| 528 | 
            -
                            "resource_tier": agent_data.get("capabilities", {}).get("resource_tier", "")
         | 
| 529 | 
            -
                        })
         | 
| 530 | 
            -
                    return sorted(agents, key=lambda x: x["id"])
         | 
| 531 | 
            -
                
         | 
| 532 | 
            -
                def get_agent_prompt(self, agent_id: str, force_reload: bool = False) -> Optional[str]:
         | 
| 214 | 
            +
                    return self.registry.list_agents()
         | 
| 215 | 
            +
             | 
| 216 | 
            +
                def get_agent_prompt(
         | 
| 217 | 
            +
                    self, agent_id: str, force_reload: bool = False
         | 
| 218 | 
            +
                ) -> Optional[str]:
         | 
| 533 219 | 
             
                    """
         | 
| 534 | 
            -
                    Retrieve agent instructions/prompt by ID | 
| 535 | 
            -
             | 
| 220 | 
            +
                    Retrieve agent instructions/prompt by ID.
         | 
| 221 | 
            +
             | 
| 536 222 | 
             
                    Args:
         | 
| 537 | 
            -
                        agent_id:  | 
| 538 | 
            -
                        force_reload:  | 
| 539 | 
            -
             | 
| 223 | 
            +
                        agent_id: Agent identifier
         | 
| 224 | 
            +
                        force_reload: Ignored (kept for API compatibility)
         | 
| 225 | 
            +
             | 
| 540 226 | 
             
                    Returns:
         | 
| 541 | 
            -
                         | 
| 542 | 
            -
                        
         | 
| 543 | 
            -
                    Caching Strategy:
         | 
| 544 | 
            -
                    - Prompts are cached for 1 hour (3600 seconds) by default
         | 
| 545 | 
            -
                    - Cache keys are versioned (v2:) to allow bulk invalidation
         | 
| 546 | 
            -
                    - Force reload bypasses cache for development/debugging
         | 
| 547 | 
            -
                    
         | 
| 548 | 
            -
                    METRICS TRACKED:
         | 
| 549 | 
            -
                    - Cache hit/miss rates for optimization
         | 
| 550 | 
            -
                    - Agent usage frequency for popular agents
         | 
| 551 | 
            -
                    - Prompt loading times for performance
         | 
| 552 | 
            -
                    - Prompt sizes for memory analysis
         | 
| 553 | 
            -
                    
         | 
| 554 | 
            -
                    WHY: Caching is critical here because:
         | 
| 555 | 
            -
                    - Agent prompts can be large (several KB)
         | 
| 556 | 
            -
                    - They're accessed frequently during agent execution
         | 
| 557 | 
            -
                    - They rarely change in production
         | 
| 558 | 
            -
                    - The 1-hour TTL balances freshness with performance
         | 
| 227 | 
            +
                        Agent prompt string or None if not found
         | 
| 559 228 | 
             
                    """
         | 
| 560 | 
            -
                     | 
| 561 | 
            -
                    
         | 
| 562 | 
            -
                    # METRICS: Track usage count for this agent
         | 
| 563 | 
            -
                    self._metrics['usage_counts'][agent_id] = self._metrics['usage_counts'].get(agent_id, 0) + 1
         | 
| 564 | 
            -
                    
         | 
| 565 | 
            -
                    # METRICS: Track load time
         | 
| 566 | 
            -
                    load_start = time.time()
         | 
| 567 | 
            -
                    
         | 
| 568 | 
            -
                    # Check cache first unless force reload is requested
         | 
| 569 | 
            -
                    if not force_reload:
         | 
| 570 | 
            -
                        cached_content = self.cache.get(cache_key)
         | 
| 571 | 
            -
                        if cached_content is not None:
         | 
| 572 | 
            -
                            # METRICS: Track cache hit
         | 
| 573 | 
            -
                            self._metrics['cache_hits'] += 1
         | 
| 574 | 
            -
                            logger.debug(f"Agent prompt for '{agent_id}' loaded from cache")
         | 
| 575 | 
            -
                            return str(cached_content)
         | 
| 576 | 
            -
                    
         | 
| 577 | 
            -
                    # METRICS: Track cache miss
         | 
| 578 | 
            -
                    self._metrics['cache_misses'] += 1
         | 
| 579 | 
            -
                    
         | 
| 580 | 
            -
                    # Get agent data from registry
         | 
| 581 | 
            -
                    agent_data = self.get_agent(agent_id)
         | 
| 229 | 
            +
                    agent_data = self.registry.get_agent(agent_id)
         | 
| 582 230 | 
             
                    if not agent_data:
         | 
| 583 | 
            -
                        logger.warning(f"Agent not found: {agent_id}")
         | 
| 584 231 | 
             
                        return None
         | 
| 585 | 
            -
             | 
| 586 | 
            -
                    # Extract instructions | 
| 232 | 
            +
             | 
| 233 | 
            +
                    # Extract instructions
         | 
| 587 234 | 
             
                    instructions = agent_data.get("instructions", "")
         | 
| 588 235 | 
             
                    if not instructions:
         | 
| 589 | 
            -
                        logger.warning(f" | 
| 236 | 
            +
                        logger.warning(f"Agent '{agent_id}' has no instructions")
         | 
| 590 237 | 
             
                        return None
         | 
| 591 | 
            -
             | 
| 592 | 
            -
                    #  | 
| 593 | 
            -
                     | 
| 594 | 
            -
                    
         | 
| 595 | 
            -
             | 
| 596 | 
            -
                    load_time_ms = (time.time() - load_start) * 1000
         | 
| 597 | 
            -
                    self._metrics['load_times'][agent_id] = load_time_ms
         | 
| 598 | 
            -
                    
         | 
| 599 | 
            -
                    # Cache the content with 1 hour TTL for performance
         | 
| 600 | 
            -
                    self.cache.set(cache_key, instructions, ttl=3600)
         | 
| 601 | 
            -
                    logger.debug(f"Agent prompt for '{agent_id}' cached successfully")
         | 
| 602 | 
            -
                    
         | 
| 603 | 
            -
                    return instructions
         | 
| 604 | 
            -
                
         | 
| 605 | 
            -
                def get_metrics(self) -> Dict[str, Any]:
         | 
| 606 | 
            -
                    """
         | 
| 607 | 
            -
                    Get collected performance metrics.
         | 
| 608 | 
            -
                    
         | 
| 609 | 
            -
                    Returns:
         | 
| 610 | 
            -
                        Dictionary containing:
         | 
| 611 | 
            -
                        - Cache performance (hit rate, miss rate)
         | 
| 612 | 
            -
                        - Agent usage statistics
         | 
| 613 | 
            -
                        - Load time analysis
         | 
| 614 | 
            -
                        - Memory usage patterns
         | 
| 615 | 
            -
                        - Error tracking
         | 
| 616 | 
            -
                        - Tier distribution
         | 
| 617 | 
            -
                        
         | 
| 618 | 
            -
                    This data could be:
         | 
| 619 | 
            -
                    - Exposed via monitoring endpoints
         | 
| 620 | 
            -
                    - Logged periodically for analysis
         | 
| 621 | 
            -
                    - Used for capacity planning
         | 
| 622 | 
            -
                    - Fed to AI operations platforms
         | 
| 623 | 
            -
                    """
         | 
| 624 | 
            -
                    cache_total = self._metrics['cache_hits'] + self._metrics['cache_misses']
         | 
| 625 | 
            -
                    cache_hit_rate = 0.0
         | 
| 626 | 
            -
                    if cache_total > 0:
         | 
| 627 | 
            -
                        cache_hit_rate = (self._metrics['cache_hits'] / cache_total) * 100
         | 
| 628 | 
            -
                    
         | 
| 629 | 
            -
                    # Calculate average load times
         | 
| 630 | 
            -
                    avg_load_time = 0.0
         | 
| 631 | 
            -
                    if self._metrics['load_times']:
         | 
| 632 | 
            -
                        avg_load_time = sum(self._metrics['load_times'].values()) / len(self._metrics['load_times'])
         | 
| 633 | 
            -
                    
         | 
| 634 | 
            -
                    # Find most used agents
         | 
| 635 | 
            -
                    top_agents = sorted(
         | 
| 636 | 
            -
                        self._metrics['usage_counts'].items(),
         | 
| 637 | 
            -
                        key=lambda x: x[1],
         | 
| 638 | 
            -
                        reverse=True
         | 
| 639 | 
            -
                    )[:5]
         | 
| 640 | 
            -
                    
         | 
| 641 | 
            -
                    return {
         | 
| 642 | 
            -
                        'initialization_time_ms': self._metrics['initialization_time_ms'],
         | 
| 643 | 
            -
                        'agents_loaded': self._metrics['agents_loaded'],
         | 
| 644 | 
            -
                        'agents_by_tier': self._metrics['agents_by_tier'].copy(),
         | 
| 645 | 
            -
                        'validation_failures': self._metrics['validation_failures'],
         | 
| 646 | 
            -
                        'cache_hit_rate_percent': cache_hit_rate,
         | 
| 647 | 
            -
                        'cache_hits': self._metrics['cache_hits'],
         | 
| 648 | 
            -
                        'cache_misses': self._metrics['cache_misses'],
         | 
| 649 | 
            -
                        'average_load_time_ms': avg_load_time,
         | 
| 650 | 
            -
                        'top_agents_by_usage': dict(top_agents),
         | 
| 651 | 
            -
                        'model_selection_distribution': self._metrics['model_selections'].copy(),
         | 
| 652 | 
            -
                        'prompt_size_stats': {
         | 
| 653 | 
            -
                            'total_agents': len(self._metrics['prompt_sizes']),
         | 
| 654 | 
            -
                            'average_size': sum(self._metrics['prompt_sizes'].values()) / len(self._metrics['prompt_sizes']) if self._metrics['prompt_sizes'] else 0,
         | 
| 655 | 
            -
                            'max_size': max(self._metrics['prompt_sizes'].values()) if self._metrics['prompt_sizes'] else 0,
         | 
| 656 | 
            -
                            'min_size': min(self._metrics['prompt_sizes'].values()) if self._metrics['prompt_sizes'] else 0
         | 
| 657 | 
            -
                        },
         | 
| 658 | 
            -
                        'error_types': self._metrics['error_types'].copy()
         | 
| 659 | 
            -
                    }
         | 
| 660 | 
            -
                
         | 
| 238 | 
            +
             | 
| 239 | 
            +
                    # Prepend base instructions
         | 
| 240 | 
            +
                    full_prompt = prepend_base_instructions(instructions)
         | 
| 241 | 
            +
                    return full_prompt
         | 
| 242 | 
            +
             | 
| 661 243 | 
             
                def get_agent_metadata(self, agent_id: str) -> Optional[Dict[str, Any]]:
         | 
| 662 244 | 
             
                    """
         | 
| 663 245 | 
             
                    Get comprehensive agent metadata including capabilities and configuration.
         | 
| 664 | 
            -
             | 
| 246 | 
            +
             | 
| 665 247 | 
             
                    Args:
         | 
| 666 | 
            -
                        agent_id:  | 
| 667 | 
            -
             | 
| 248 | 
            +
                        agent_id: Agent identifier
         | 
| 249 | 
            +
             | 
| 668 250 | 
             
                    Returns:
         | 
| 669 | 
            -
                         | 
| 670 | 
            -
                        or None if agent not found
         | 
| 671 | 
            -
                        
         | 
| 672 | 
            -
                    WHY: This method provides access to agent configuration without
         | 
| 673 | 
            -
                    including the potentially large instruction text. This is useful for:
         | 
| 674 | 
            -
                    - UI displays showing agent capabilities
         | 
| 675 | 
            -
                    - Programmatic agent selection based on features
         | 
| 676 | 
            -
                    - Debugging and introspection
         | 
| 677 | 
            -
                    
         | 
| 678 | 
            -
                    The returned structure mirrors the JSON schema sections for consistency.
         | 
| 251 | 
            +
                        Agent metadata dictionary or None if not found
         | 
| 679 252 | 
             
                    """
         | 
| 680 | 
            -
                    agent_data = self.get_agent(agent_id)
         | 
| 253 | 
            +
                    agent_data = self.registry.get_agent(agent_id)
         | 
| 681 254 | 
             
                    if not agent_data:
         | 
| 682 255 | 
             
                        return None
         | 
| 683 | 
            -
             | 
| 684 | 
            -
                     | 
| 685 | 
            -
             | 
| 686 | 
            -
             | 
| 687 | 
            -
             | 
| 688 | 
            -
             | 
| 689 | 
            -
             | 
| 690 | 
            -
             | 
| 256 | 
            +
             | 
| 257 | 
            +
                    metadata = agent_data.get("metadata", {})
         | 
| 258 | 
            +
                    capabilities = agent_data.get("capabilities", {})
         | 
| 259 | 
            +
                    tier = self.registry.get_agent_tier(agent_id)
         | 
| 260 | 
            +
             | 
| 261 | 
            +
                    # Check for project memory
         | 
| 262 | 
            +
                    has_memory = capabilities.get("has_project_memory", False)
         | 
| 263 | 
            +
             | 
| 264 | 
            +
                    result = {
         | 
| 265 | 
            +
                        "agent_id": agent_id,
         | 
| 266 | 
            +
                        "name": metadata.get("name", agent_id),
         | 
| 267 | 
            +
                        "description": metadata.get("description", ""),
         | 
| 268 | 
            +
                        "category": metadata.get("category", "general"),
         | 
| 269 | 
            +
                        "version": metadata.get("version", "1.0.0"),
         | 
| 270 | 
            +
                        "model": agent_data.get("model", "claude-sonnet-4-20250514"),
         | 
| 271 | 
            +
                        "resource_tier": agent_data.get("resource_tier", "standard"),
         | 
| 272 | 
            +
                        "tier": tier.value if tier else "unknown",
         | 
| 273 | 
            +
                        "tools": agent_data.get("tools", []),
         | 
| 274 | 
            +
                        "capabilities": capabilities,
         | 
| 275 | 
            +
                        "source_file": agent_data.get("_source_file", "unknown"),
         | 
| 276 | 
            +
                        "has_project_memory": has_memory,
         | 
| 691 277 | 
             
                    }
         | 
| 692 278 |  | 
| 279 | 
            +
                    # Add memory-specific information if present
         | 
| 280 | 
            +
                    if has_memory:
         | 
| 281 | 
            +
                        result["memory_size_kb"] = capabilities.get("memory_size_kb", 0)
         | 
| 282 | 
            +
                        result["memory_file"] = capabilities.get("memory_file", "")
         | 
| 283 | 
            +
                        result["memory_lines"] = capabilities.get("memory_lines", 0)
         | 
| 284 | 
            +
                        result["memory_enhanced"] = True
         | 
| 285 | 
            +
             | 
| 286 | 
            +
                    return result
         | 
| 287 | 
            +
             | 
| 288 | 
            +
                def reload(self) -> None:
         | 
| 289 | 
            +
                    """
         | 
| 290 | 
            +
                    Reload all agents from disk, clearing the registry.
         | 
| 291 | 
            +
                    """
         | 
| 292 | 
            +
                    logger.info("Reloading agent system...")
         | 
| 293 | 
            +
             | 
| 294 | 
            +
                    # Reload registry
         | 
| 295 | 
            +
                    self.registry.reload()
         | 
| 296 | 
            +
             | 
| 297 | 
            +
                    logger.info(
         | 
| 298 | 
            +
                        f"Agent system reloaded with {len(self.registry._agent_registry)} agents"
         | 
| 299 | 
            +
                    )
         | 
| 300 | 
            +
             | 
| 693 301 |  | 
| 694 | 
            -
            # Global loader instance  | 
| 695 | 
            -
            # WHY: We use a module-level singleton because:
         | 
| 696 | 
            -
            # - Agent configurations should be consistent across the application
         | 
| 697 | 
            -
            # - Loading and validation only needs to happen once
         | 
| 698 | 
            -
            # - Multiple loaders would lead to cache inconsistencies
         | 
| 302 | 
            +
            # Global loader instance (singleton pattern)
         | 
| 699 303 | 
             
            _loader: Optional[AgentLoader] = None
         | 
| 700 304 |  | 
| 701 305 |  | 
| 702 306 | 
             
            def _get_loader() -> AgentLoader:
         | 
| 703 307 | 
             
                """
         | 
| 704 308 | 
             
                Get or create the global agent loader instance (singleton pattern).
         | 
| 705 | 
            -
             | 
| 309 | 
            +
             | 
| 706 310 | 
             
                Returns:
         | 
| 707 | 
            -
                    AgentLoader: The  | 
| 708 | 
            -
                    
         | 
| 709 | 
            -
                WHY: The singleton pattern ensures:
         | 
| 710 | 
            -
                - Agents are loaded and validated only once
         | 
| 711 | 
            -
                - All parts of the application see the same agent registry
         | 
| 712 | 
            -
                - Cache state remains consistent
         | 
| 713 | 
            -
                - Memory usage is minimized
         | 
| 714 | 
            -
                
         | 
| 715 | 
            -
                Thread Safety: Python's GIL makes this simple implementation thread-safe
         | 
| 716 | 
            -
                for the single assignment operation.
         | 
| 311 | 
            +
                    AgentLoader: The global agent loader instance
         | 
| 717 312 | 
             
                """
         | 
| 718 313 | 
             
                global _loader
         | 
| 719 314 | 
             
                if _loader is None:
         | 
| 315 | 
            +
                    logger.debug("Initializing global agent loader")
         | 
| 720 316 | 
             
                    _loader = AgentLoader()
         | 
| 721 317 | 
             
                return _loader
         | 
| 722 318 |  | 
| 723 319 |  | 
| 724 | 
            -
             | 
| 320 | 
            +
            # Removed duplicate get_agent_prompt function - using the comprehensive version below
         | 
| 321 | 
            +
             | 
| 322 | 
            +
             | 
| 323 | 
            +
            def list_available_agents() -> Dict[str, Dict[str, Any]]:
         | 
| 324 | 
            +
                """
         | 
| 325 | 
            +
                List all available agents with their key metadata including memory information.
         | 
| 326 | 
            +
             | 
| 327 | 
            +
                Returns:
         | 
| 328 | 
            +
                    Dictionary mapping agent IDs to their metadata
         | 
| 329 | 
            +
                """
         | 
| 330 | 
            +
                loader = _get_loader()
         | 
| 331 | 
            +
                agents_list = loader.list_agents()
         | 
| 332 | 
            +
             | 
| 333 | 
            +
                # Convert list to dictionary for easier access
         | 
| 334 | 
            +
                agents_dict = {}
         | 
| 335 | 
            +
                for agent in agents_list:
         | 
| 336 | 
            +
                    agent_id = agent["id"]
         | 
| 337 | 
            +
                    agent_info = {
         | 
| 338 | 
            +
                        "name": agent["name"],
         | 
| 339 | 
            +
                        "description": agent["description"],
         | 
| 340 | 
            +
                        "category": agent["category"],
         | 
| 341 | 
            +
                        "version": agent.get("version", "1.0.0"),
         | 
| 342 | 
            +
                        "model": agent.get("model", "claude-sonnet-4-20250514"),
         | 
| 343 | 
            +
                        "resource_tier": agent.get("resource_tier", "standard"),
         | 
| 344 | 
            +
                        "tier": agent.get("tier", "system"),
         | 
| 345 | 
            +
                        "has_project_memory": agent.get("has_project_memory", False),
         | 
| 346 | 
            +
                    }
         | 
| 347 | 
            +
             | 
| 348 | 
            +
                    # Add memory details if present
         | 
| 349 | 
            +
                    if agent.get("has_project_memory", False):
         | 
| 350 | 
            +
                        agent_info["memory_size_kb"] = agent.get("memory_size_kb", 0)
         | 
| 351 | 
            +
                        agent_info["memory_file"] = agent.get("memory_file", "")
         | 
| 352 | 
            +
                        agent_info["memory_lines"] = agent.get("memory_lines", 0)
         | 
| 353 | 
            +
             | 
| 354 | 
            +
                    agents_dict[agent_id] = agent_info
         | 
| 355 | 
            +
             | 
| 356 | 
            +
                return agents_dict
         | 
| 357 | 
            +
             | 
| 358 | 
            +
             | 
| 359 | 
            +
            def validate_agent_files() -> Dict[str, Dict[str, Any]]:
         | 
| 360 | 
            +
                """
         | 
| 361 | 
            +
                Validate all agent template files against the schema.
         | 
| 362 | 
            +
             | 
| 363 | 
            +
                Returns:
         | 
| 364 | 
            +
                    Dictionary mapping agent names to validation results
         | 
| 365 | 
            +
                """
         | 
| 366 | 
            +
                loader = _get_loader()
         | 
| 367 | 
            +
                agents = loader.list_agents()
         | 
| 368 | 
            +
                results = {}
         | 
| 369 | 
            +
             | 
| 370 | 
            +
                for agent in agents:
         | 
| 371 | 
            +
                    agent_id = agent["id"]
         | 
| 372 | 
            +
                    agent_data = loader.get_agent(agent_id)
         | 
| 373 | 
            +
                    if agent_data:
         | 
| 374 | 
            +
                        results[agent_id] = {"valid": True, "errors": [], "warnings": []}
         | 
| 375 | 
            +
                    else:
         | 
| 376 | 
            +
                        results[agent_id] = {
         | 
| 377 | 
            +
                            "valid": False,
         | 
| 378 | 
            +
                            "errors": ["Agent not found"],
         | 
| 379 | 
            +
                            "warnings": [],
         | 
| 380 | 
            +
                        }
         | 
| 381 | 
            +
             | 
| 382 | 
            +
                return results
         | 
| 383 | 
            +
             | 
| 384 | 
            +
             | 
| 385 | 
            +
            def reload_agents() -> None:
         | 
| 386 | 
            +
                """
         | 
| 387 | 
            +
                Force reload all agents from disk, clearing the registry.
         | 
| 388 | 
            +
                """
         | 
| 389 | 
            +
                global _loader
         | 
| 390 | 
            +
                if _loader:
         | 
| 391 | 
            +
                    _loader.reload()
         | 
| 392 | 
            +
                else:
         | 
| 393 | 
            +
                    # Clear the global instance to force reinitialization
         | 
| 394 | 
            +
                    _loader = None
         | 
| 395 | 
            +
             | 
| 396 | 
            +
                logger.info("Agent registry cleared, will reload on next access")
         | 
| 397 | 
            +
             | 
| 398 | 
            +
             | 
| 399 | 
            +
            def get_agent_tier(agent_name: str) -> Optional[str]:
         | 
| 400 | 
            +
                """
         | 
| 401 | 
            +
                Get the tier from which an agent was loaded.
         | 
| 402 | 
            +
             | 
| 403 | 
            +
                Args:
         | 
| 404 | 
            +
                    agent_name: Agent identifier
         | 
| 405 | 
            +
             | 
| 406 | 
            +
                Returns:
         | 
| 407 | 
            +
                    Tier name or None if agent not found
         | 
| 408 | 
            +
                """
         | 
| 409 | 
            +
                loader = _get_loader()
         | 
| 410 | 
            +
                tier = loader.registry.get_agent_tier(agent_name)
         | 
| 411 | 
            +
                return tier.value if tier else None
         | 
| 412 | 
            +
             | 
| 413 | 
            +
             | 
| 414 | 
            +
            def list_agents_by_tier() -> Dict[str, List[str]]:
         | 
| 415 | 
            +
                """
         | 
| 416 | 
            +
                List available agents organized by their tier.
         | 
| 417 | 
            +
             | 
| 418 | 
            +
                Returns:
         | 
| 419 | 
            +
                    Dictionary mapping tier names to lists of agent IDs
         | 
| 420 | 
            +
                """
         | 
| 421 | 
            +
                loader = _get_loader()
         | 
| 422 | 
            +
                agents = loader.list_agents()
         | 
| 423 | 
            +
             | 
| 424 | 
            +
                result = {tier.value: [] for tier in AgentTier}
         | 
| 425 | 
            +
             | 
| 426 | 
            +
                for agent in agents:
         | 
| 427 | 
            +
                    tier = agent.get("tier", "system")
         | 
| 428 | 
            +
                    if tier in result:
         | 
| 429 | 
            +
                        result[tier].append(agent["id"])
         | 
| 430 | 
            +
             | 
| 431 | 
            +
                return result
         | 
| 432 | 
            +
             | 
| 433 | 
            +
             | 
| 434 | 
            +
            # Duplicate functions removed - using the ones defined earlier
         | 
| 435 | 
            +
             | 
| 436 | 
            +
             | 
| 437 | 
            +
            def get_agent_metadata(agent_id: str) -> Optional[Dict[str, Any]]:
         | 
| 438 | 
            +
                """
         | 
| 439 | 
            +
                Get agent metadata without instruction content.
         | 
| 440 | 
            +
             | 
| 441 | 
            +
                WHY: This method provides access to agent configuration without
         | 
| 442 | 
            +
                including the potentially large instruction text. This is useful for:
         | 
| 443 | 
            +
                - UI displays showing agent capabilities
         | 
| 444 | 
            +
                - Programmatic agent selection based on features
         | 
| 445 | 
            +
                - Debugging and introspection
         | 
| 446 | 
            +
             | 
| 447 | 
            +
                The returned structure mirrors the JSON schema sections for consistency.
         | 
| 448 | 
            +
                """
         | 
| 449 | 
            +
                loader = AgentLoader()
         | 
| 450 | 
            +
                agent_data = loader.get_agent(agent_id)
         | 
| 451 | 
            +
                if not agent_data:
         | 
| 452 | 
            +
                    return None
         | 
| 453 | 
            +
             | 
| 454 | 
            +
                return {
         | 
| 455 | 
            +
                    "id": agent_id,
         | 
| 456 | 
            +
                    "version": agent_data.get("version", "1.0.0"),
         | 
| 457 | 
            +
                    "metadata": agent_data.get("metadata", {}),  # Name, description, category
         | 
| 458 | 
            +
                    "capabilities": agent_data.get("capabilities", {}),  # Model, tools, features
         | 
| 459 | 
            +
                    "knowledge": agent_data.get("knowledge", {}),  # Domain expertise
         | 
| 460 | 
            +
                    "interactions": agent_data.get("interactions", {}),  # User interaction patterns
         | 
| 461 | 
            +
                }
         | 
| 462 | 
            +
             | 
| 463 | 
            +
             | 
| 464 | 
            +
            # Duplicate _get_loader function removed - using the one defined earlier
         | 
| 465 | 
            +
             | 
| 466 | 
            +
             | 
| 467 | 
            +
            def load_agent_prompt_from_md(
         | 
| 468 | 
            +
                agent_name: str, force_reload: bool = False
         | 
| 469 | 
            +
            ) -> Optional[str]:
         | 
| 725 470 | 
             
                """
         | 
| 726 471 | 
             
                Load agent prompt from JSON template (legacy function name).
         | 
| 727 | 
            -
             | 
| 472 | 
            +
             | 
| 728 473 | 
             
                Args:
         | 
| 729 474 | 
             
                    agent_name: Agent name (matches agent ID in new schema)
         | 
| 730 475 | 
             
                    force_reload: Force reload from file, bypassing cache
         | 
| 731 | 
            -
             | 
| 476 | 
            +
             | 
| 732 477 | 
             
                Returns:
         | 
| 733 478 | 
             
                    str: Agent instructions from JSON template, or None if not found
         | 
| 734 | 
            -
             | 
| 479 | 
            +
             | 
| 735 480 | 
             
                NOTE: Despite the "md" in the function name, this loads from JSON files.
         | 
| 736 481 | 
             
                The name is kept for backward compatibility with existing code that
         | 
| 737 482 | 
             
                expects this interface. New code should use get_agent_prompt() directly.
         | 
| 738 | 
            -
             | 
| 483 | 
            +
             | 
| 739 484 | 
             
                WHY: This wrapper exists to maintain backward compatibility during the
         | 
| 740 485 | 
             
                migration from markdown-based agents to JSON-based agents.
         | 
| 741 486 | 
             
                """
         | 
| @@ -743,10 +488,12 @@ def load_agent_prompt_from_md(agent_name: str, force_reload: bool = False) -> Op | |
| 743 488 | 
             
                return loader.get_agent_prompt(agent_name, force_reload)
         | 
| 744 489 |  | 
| 745 490 |  | 
| 746 | 
            -
            def _analyze_task_complexity( | 
| 491 | 
            +
            def _analyze_task_complexity(
         | 
| 492 | 
            +
                task_description: str, context_size: int = 0, **kwargs: Any
         | 
| 493 | 
            +
            ) -> Dict[str, Any]:
         | 
| 747 494 | 
             
                """
         | 
| 748 495 | 
             
                Analyze task complexity to determine optimal model selection.
         | 
| 749 | 
            -
             | 
| 496 | 
            +
             | 
| 750 497 | 
             
                Args:
         | 
| 751 498 | 
             
                    task_description: Description of the task to analyze
         | 
| 752 499 | 
             
                    context_size: Size of context in characters (affects complexity)
         | 
| @@ -754,7 +501,7 @@ def _analyze_task_complexity(task_description: str, context_size: int = 0, **kwa | |
| 754 501 | 
             
                        - code_analysis: Whether code analysis is required
         | 
| 755 502 | 
             
                        - multi_step: Whether the task involves multiple steps
         | 
| 756 503 | 
             
                        - domain_expertise: Required domain knowledge level
         | 
| 757 | 
            -
             | 
| 504 | 
            +
             | 
| 758 505 | 
             
                Returns:
         | 
| 759 506 | 
             
                    Dictionary containing:
         | 
| 760 507 | 
             
                        - complexity_score: Numeric score 0-100
         | 
| @@ -762,14 +509,14 @@ def _analyze_task_complexity(task_description: str, context_size: int = 0, **kwa | |
| 762 509 | 
             
                        - recommended_model: Suggested Claude model tier
         | 
| 763 510 | 
             
                        - optimal_prompt_size: Recommended prompt size range
         | 
| 764 511 | 
             
                        - error: Error message if analysis fails
         | 
| 765 | 
            -
             | 
| 512 | 
            +
             | 
| 766 513 | 
             
                WHY: This is a placeholder implementation that returns sensible defaults.
         | 
| 767 514 | 
             
                The actual TaskComplexityAnalyzer module would use NLP techniques to:
         | 
| 768 515 | 
             
                - Analyze task description for complexity indicators
         | 
| 769 516 | 
             
                - Consider context size and memory requirements
         | 
| 770 517 | 
             
                - Factor in domain-specific requirements
         | 
| 771 518 | 
             
                - Optimize for cost vs capability trade-offs
         | 
| 772 | 
            -
             | 
| 519 | 
            +
             | 
| 773 520 | 
             
                Current Implementation: Returns medium complexity as a safe default that
         | 
| 774 521 | 
             
                works well for most tasks while the full analyzer is being developed.
         | 
| 775 522 | 
             
                """
         | 
| @@ -780,37 +527,39 @@ def _analyze_task_complexity(task_description: str, context_size: int = 0, **kwa | |
| 780 527 | 
             
                    "complexity_level": ComplexityLevel.MEDIUM,
         | 
| 781 528 | 
             
                    "recommended_model": ModelType.SONNET,
         | 
| 782 529 | 
             
                    "optimal_prompt_size": (700, 1000),
         | 
| 783 | 
            -
                    "error": "TaskComplexityAnalyzer module not available"
         | 
| 530 | 
            +
                    "error": "TaskComplexityAnalyzer module not available",
         | 
| 784 531 | 
             
                }
         | 
| 785 532 |  | 
| 786 533 |  | 
| 787 | 
            -
            def _get_model_config( | 
| 534 | 
            +
            def _get_model_config(
         | 
| 535 | 
            +
                agent_name: str, complexity_analysis: Optional[Dict[str, Any]] = None
         | 
| 536 | 
            +
            ) -> Tuple[str, Dict[str, Any]]:
         | 
| 788 537 | 
             
                """
         | 
| 789 538 | 
             
                Determine optimal model configuration based on agent type and task complexity.
         | 
| 790 | 
            -
             | 
| 539 | 
            +
             | 
| 791 540 | 
             
                METRICS TRACKED:
         | 
| 792 541 | 
             
                - Model selection distribution
         | 
| 793 542 | 
             
                - Complexity score distribution
         | 
| 794 543 | 
             
                - Dynamic vs static selection rates
         | 
| 795 | 
            -
             | 
| 544 | 
            +
             | 
| 796 545 | 
             
                Args:
         | 
| 797 546 | 
             
                    agent_name: Name of the agent requesting model selection (already normalized to agent_id format)
         | 
| 798 547 | 
             
                    complexity_analysis: Results from task complexity analysis (if available)
         | 
| 799 | 
            -
             | 
| 548 | 
            +
             | 
| 800 549 | 
             
                Returns:
         | 
| 801 550 | 
             
                    Tuple of (selected_model, model_config) where:
         | 
| 802 551 | 
             
                        - selected_model: Claude API model identifier
         | 
| 803 552 | 
             
                        - model_config: Dictionary with selection metadata
         | 
| 804 | 
            -
             | 
| 553 | 
            +
             | 
| 805 554 | 
             
                Model Selection Strategy:
         | 
| 806 555 | 
             
                1. Each agent has a default model defined in its capabilities
         | 
| 807 556 | 
             
                2. Dynamic selection can override based on task complexity
         | 
| 808 557 | 
             
                3. Environment variables can control selection behavior
         | 
| 809 | 
            -
             | 
| 558 | 
            +
             | 
| 810 559 | 
             
                Environment Variables:
         | 
| 811 560 | 
             
                - ENABLE_DYNAMIC_MODEL_SELECTION: Global toggle (default: true)
         | 
| 812 561 | 
             
                - CLAUDE_PM_{AGENT}_MODEL_SELECTION: Per-agent override
         | 
| 813 | 
            -
             | 
| 562 | 
            +
             | 
| 814 563 | 
             
                WHY: This flexible approach allows:
         | 
| 815 564 | 
             
                - Cost optimization by using cheaper models for simple tasks
         | 
| 816 565 | 
             
                - Performance optimization by using powerful models only when needed
         | 
| @@ -819,75 +568,95 @@ def _get_model_config(agent_name: str, complexity_analysis: Optional[Dict[str, A | |
| 819 568 | 
             
                """
         | 
| 820 569 | 
             
                loader = _get_loader()
         | 
| 821 570 | 
             
                agent_data = loader.get_agent(agent_name)
         | 
| 822 | 
            -
             | 
| 571 | 
            +
             | 
| 823 572 | 
             
                if not agent_data:
         | 
| 824 573 | 
             
                    # Fallback for unknown agents - use Sonnet as safe default
         | 
| 825 574 | 
             
                    return "claude-sonnet-4-20250514", {"selection_method": "default"}
         | 
| 826 | 
            -
             | 
| 575 | 
            +
             | 
| 827 576 | 
             
                # Get model from agent capabilities (agent's preferred model)
         | 
| 828 | 
            -
                default_model = agent_data.get("capabilities", {}).get( | 
| 829 | 
            -
             | 
| 577 | 
            +
                default_model = agent_data.get("capabilities", {}).get(
         | 
| 578 | 
            +
                    "model", "claude-sonnet-4-20250514"
         | 
| 579 | 
            +
                )
         | 
| 580 | 
            +
             | 
| 830 581 | 
             
                # Check if dynamic model selection is enabled globally
         | 
| 831 | 
            -
                enable_dynamic_selection =  | 
| 832 | 
            -
             | 
| 582 | 
            +
                enable_dynamic_selection = (
         | 
| 583 | 
            +
                    os.getenv("ENABLE_DYNAMIC_MODEL_SELECTION", "true").lower() == "true"
         | 
| 584 | 
            +
                )
         | 
| 585 | 
            +
             | 
| 833 586 | 
             
                # Check for per-agent override in environment
         | 
| 834 587 | 
             
                # This allows fine-grained control over specific agents
         | 
| 835 588 | 
             
                agent_override_key = f"CLAUDE_PM_{agent_name.upper()}_MODEL_SELECTION"
         | 
| 836 | 
            -
                agent_override = os.getenv(agent_override_key,  | 
| 837 | 
            -
             | 
| 838 | 
            -
                if agent_override ==  | 
| 589 | 
            +
                agent_override = os.getenv(agent_override_key, "").lower()
         | 
| 590 | 
            +
             | 
| 591 | 
            +
                if agent_override == "true":
         | 
| 839 592 | 
             
                    enable_dynamic_selection = True
         | 
| 840 | 
            -
                elif agent_override ==  | 
| 593 | 
            +
                elif agent_override == "false":
         | 
| 841 594 | 
             
                    enable_dynamic_selection = False
         | 
| 842 | 
            -
             | 
| 595 | 
            +
             | 
| 843 596 | 
             
                # Apply dynamic model selection based on task complexity
         | 
| 844 597 | 
             
                if enable_dynamic_selection and complexity_analysis:
         | 
| 845 | 
            -
                    recommended_model = complexity_analysis.get( | 
| 598 | 
            +
                    recommended_model = complexity_analysis.get(
         | 
| 599 | 
            +
                        "recommended_model", ModelType.SONNET
         | 
| 600 | 
            +
                    )
         | 
| 846 601 | 
             
                    selected_model = MODEL_NAME_MAPPINGS.get(recommended_model, default_model)
         | 
| 847 | 
            -
             | 
| 602 | 
            +
             | 
| 848 603 | 
             
                    # METRICS: Track complexity scores for distribution analysis
         | 
| 849 | 
            -
                    complexity_score = complexity_analysis.get( | 
| 850 | 
            -
                    if hasattr(loader,  | 
| 851 | 
            -
                        loader._metrics[ | 
| 604 | 
            +
                    complexity_score = complexity_analysis.get("complexity_score", 50)
         | 
| 605 | 
            +
                    if hasattr(loader, "_metrics"):
         | 
| 606 | 
            +
                        loader._metrics["complexity_scores"].append(complexity_score)
         | 
| 852 607 | 
             
                        # Keep only last 1000 scores for memory efficiency
         | 
| 853 | 
            -
                        if len(loader._metrics[ | 
| 854 | 
            -
                            loader._metrics[ | 
| 855 | 
            -
             | 
| 608 | 
            +
                        if len(loader._metrics["complexity_scores"]) > 1000:
         | 
| 609 | 
            +
                            loader._metrics["complexity_scores"] = loader._metrics[
         | 
| 610 | 
            +
                                "complexity_scores"
         | 
| 611 | 
            +
                            ][-1000:]
         | 
| 612 | 
            +
             | 
| 856 613 | 
             
                    model_config = {
         | 
| 857 614 | 
             
                        "selection_method": "dynamic_complexity_based",
         | 
| 858 615 | 
             
                        "complexity_score": complexity_score,
         | 
| 859 | 
            -
                        "complexity_level": complexity_analysis.get( | 
| 860 | 
            -
             | 
| 861 | 
            -
                         | 
| 616 | 
            +
                        "complexity_level": complexity_analysis.get(
         | 
| 617 | 
            +
                            "complexity_level", ComplexityLevel.MEDIUM
         | 
| 618 | 
            +
                        ),
         | 
| 619 | 
            +
                        "optimal_prompt_size": complexity_analysis.get(
         | 
| 620 | 
            +
                            "optimal_prompt_size", (700, 1000)
         | 
| 621 | 
            +
                        ),
         | 
| 622 | 
            +
                        "default_model": default_model,
         | 
| 862 623 | 
             
                    }
         | 
| 863 624 | 
             
                else:
         | 
| 864 625 | 
             
                    # Use agent's default model when dynamic selection is disabled
         | 
| 865 626 | 
             
                    selected_model = default_model
         | 
| 866 627 | 
             
                    model_config = {
         | 
| 867 628 | 
             
                        "selection_method": "agent_default",
         | 
| 868 | 
            -
                        "reason": "dynamic_selection_disabled" | 
| 869 | 
            -
                         | 
| 629 | 
            +
                        "reason": "dynamic_selection_disabled"
         | 
| 630 | 
            +
                        if not enable_dynamic_selection
         | 
| 631 | 
            +
                        else "no_complexity_analysis",
         | 
| 632 | 
            +
                        "default_model": default_model,
         | 
| 870 633 | 
             
                    }
         | 
| 871 | 
            -
             | 
| 634 | 
            +
             | 
| 872 635 | 
             
                # METRICS: Track model selection distribution
         | 
| 873 636 | 
             
                # This helps understand model usage patterns and costs
         | 
| 874 | 
            -
                if hasattr(loader,  | 
| 875 | 
            -
                    loader._metrics[ | 
| 876 | 
            -
                        loader._metrics[ | 
| 877 | 
            -
             | 
| 637 | 
            +
                if hasattr(loader, "_metrics"):
         | 
| 638 | 
            +
                    loader._metrics["model_selections"][selected_model] = (
         | 
| 639 | 
            +
                        loader._metrics["model_selections"].get(selected_model, 0) + 1
         | 
| 640 | 
            +
                    )
         | 
| 641 | 
            +
             | 
| 878 642 | 
             
                return selected_model, model_config
         | 
| 879 643 |  | 
| 880 644 |  | 
| 881 | 
            -
            def get_agent_prompt( | 
| 645 | 
            +
            def get_agent_prompt(
         | 
| 646 | 
            +
                agent_name: str,
         | 
| 647 | 
            +
                force_reload: bool = False,
         | 
| 648 | 
            +
                return_model_info: bool = False,
         | 
| 649 | 
            +
                **kwargs: Any,
         | 
| 650 | 
            +
            ) -> Union[str, Tuple[str, str, Dict[str, Any]]]:
         | 
| 882 651 | 
             
                """
         | 
| 883 652 | 
             
                Get agent prompt with optional dynamic model selection and base instructions.
         | 
| 884 | 
            -
             | 
| 653 | 
            +
             | 
| 885 654 | 
             
                This is the primary interface for retrieving agent prompts. It handles:
         | 
| 886 655 | 
             
                1. Loading the agent's instructions from the registry
         | 
| 887 656 | 
             
                2. Optionally analyzing task complexity for model selection
         | 
| 888 657 | 
             
                3. Prepending base instructions for consistency
         | 
| 889 658 | 
             
                4. Adding metadata about model selection decisions
         | 
| 890 | 
            -
             | 
| 659 | 
            +
             | 
| 891 660 | 
             
                Args:
         | 
| 892 661 | 
             
                    agent_name: Agent name in any format (e.g., "Engineer", "research_agent", "QA")
         | 
| 893 662 | 
             
                    force_reload: Force reload from source, bypassing cache
         | 
| @@ -897,14 +666,14 @@ def get_agent_prompt(agent_name: str, force_reload: bool = False, return_model_i | |
| 897 666 | 
             
                        - context_size: Size of context in characters
         | 
| 898 667 | 
             
                        - enable_complexity_analysis: Toggle complexity analysis (default: True)
         | 
| 899 668 | 
             
                        - Additional task-specific parameters
         | 
| 900 | 
            -
             | 
| 669 | 
            +
             | 
| 901 670 | 
             
                Returns:
         | 
| 902 671 | 
             
                    If return_model_info=False: Complete agent prompt string
         | 
| 903 672 | 
             
                    If return_model_info=True: Tuple of (prompt, selected_model, model_config)
         | 
| 904 | 
            -
             | 
| 673 | 
            +
             | 
| 905 674 | 
             
                Raises:
         | 
| 906 675 | 
             
                    ValueError: If the requested agent is not found
         | 
| 907 | 
            -
             | 
| 676 | 
            +
             | 
| 908 677 | 
             
                Processing Flow:
         | 
| 909 678 | 
             
                1. Normalize agent name to correct agent ID
         | 
| 910 679 | 
             
                2. Load agent instructions (with caching)
         | 
| @@ -913,7 +682,7 @@ def get_agent_prompt(agent_name: str, force_reload: bool = False, return_model_i | |
| 913 682 | 
             
                5. Add model selection metadata to prompt
         | 
| 914 683 | 
             
                6. Prepend base instructions
         | 
| 915 684 | 
             
                7. Return appropriate format based on return_model_info
         | 
| 916 | 
            -
             | 
| 685 | 
            +
             | 
| 917 686 | 
             
                WHY: This comprehensive approach ensures:
         | 
| 918 687 | 
             
                - Consistent prompt structure across all agents
         | 
| 919 688 | 
             
                - Optimal model selection for cost/performance
         | 
| @@ -924,7 +693,7 @@ def get_agent_prompt(agent_name: str, force_reload: bool = False, return_model_i | |
| 924 693 | 
             
                # Convert names like "Engineer", "Research", "QA" to the correct agent IDs
         | 
| 925 694 | 
             
                normalizer = AgentNameNormalizer()
         | 
| 926 695 | 
             
                loader = _get_loader()
         | 
| 927 | 
            -
             | 
| 696 | 
            +
             | 
| 928 697 | 
             
                # First check if agent exists with exact name
         | 
| 929 698 | 
             
                if loader.get_agent(agent_name):
         | 
| 930 699 | 
             
                    actual_agent_id = agent_name
         | 
| @@ -938,7 +707,7 @@ def get_agent_prompt(agent_name: str, force_reload: bool = False, return_model_i | |
| 938 707 | 
             
                    # Get the normalized key (e.g., "engineer", "research", "qa")
         | 
| 939 708 | 
             
                    # First check if the agent name is recognized by the normalizer
         | 
| 940 709 | 
             
                    cleaned = agent_name.strip().lower().replace("-", "_")
         | 
| 941 | 
            -
             | 
| 710 | 
            +
             | 
| 942 711 | 
             
                    # Check if this is a known alias or canonical name
         | 
| 943 712 | 
             
                    if cleaned in normalizer.ALIASES or cleaned in normalizer.CANONICAL_NAMES:
         | 
| 944 713 | 
             
                        agent_key = normalizer.to_key(agent_name)
         | 
| @@ -957,47 +726,58 @@ def get_agent_prompt(agent_name: str, force_reload: bool = False, return_model_i | |
| 957 726 | 
             
                            actual_agent_id = f"{cleaned}_agent"
         | 
| 958 727 | 
             
                        else:
         | 
| 959 728 | 
             
                            actual_agent_id = cleaned  # Use cleaned name
         | 
| 960 | 
            -
             | 
| 729 | 
            +
             | 
| 961 730 | 
             
                # Log the normalization for debugging
         | 
| 962 731 | 
             
                if agent_name != actual_agent_id:
         | 
| 963 732 | 
             
                    logger.debug(f"Normalized agent name '{agent_name}' to '{actual_agent_id}'")
         | 
| 964 | 
            -
             | 
| 733 | 
            +
             | 
| 965 734 | 
             
                # Load from JSON template via the loader
         | 
| 966 735 | 
             
                prompt = load_agent_prompt_from_md(actual_agent_id, force_reload)
         | 
| 967 | 
            -
             | 
| 736 | 
            +
             | 
| 968 737 | 
             
                if prompt is None:
         | 
| 969 | 
            -
                    raise ValueError( | 
| 970 | 
            -
             | 
| 738 | 
            +
                    raise ValueError(
         | 
| 739 | 
            +
                        f"No agent found with name: {agent_name} (normalized to: {actual_agent_id})"
         | 
| 740 | 
            +
                    )
         | 
| 741 | 
            +
             | 
| 971 742 | 
             
                # Analyze task complexity if task description is provided
         | 
| 972 743 | 
             
                complexity_analysis = None
         | 
| 973 | 
            -
                task_description = kwargs.get( | 
| 974 | 
            -
                enable_analysis = kwargs.get( | 
| 975 | 
            -
             | 
| 744 | 
            +
                task_description = kwargs.get("task_description", "")
         | 
| 745 | 
            +
                enable_analysis = kwargs.get("enable_complexity_analysis", True)
         | 
| 746 | 
            +
             | 
| 976 747 | 
             
                if task_description and enable_analysis:
         | 
| 977 748 | 
             
                    # Extract relevant kwargs for complexity analysis
         | 
| 978 | 
            -
                    complexity_kwargs = { | 
| 979 | 
            -
             | 
| 749 | 
            +
                    complexity_kwargs = {
         | 
| 750 | 
            +
                        k: v
         | 
| 751 | 
            +
                        for k, v in kwargs.items()
         | 
| 752 | 
            +
                        if k
         | 
| 753 | 
            +
                        not in ["task_description", "context_size", "enable_complexity_analysis"]
         | 
| 754 | 
            +
                    }
         | 
| 980 755 | 
             
                    complexity_analysis = _analyze_task_complexity(
         | 
| 981 756 | 
             
                        task_description=task_description,
         | 
| 982 | 
            -
                        context_size=kwargs.get( | 
| 983 | 
            -
                        **complexity_kwargs
         | 
| 757 | 
            +
                        context_size=kwargs.get("context_size", 0),
         | 
| 758 | 
            +
                        **complexity_kwargs,
         | 
| 984 759 | 
             
                    )
         | 
| 985 | 
            -
             | 
| 760 | 
            +
             | 
| 986 761 | 
             
                # Get model configuration based on agent and complexity
         | 
| 987 762 | 
             
                # Pass the normalized agent ID to _get_model_config
         | 
| 988 | 
            -
                selected_model, model_config = _get_model_config( | 
| 989 | 
            -
             | 
| 763 | 
            +
                selected_model, model_config = _get_model_config(
         | 
| 764 | 
            +
                    actual_agent_id, complexity_analysis
         | 
| 765 | 
            +
                )
         | 
| 766 | 
            +
             | 
| 990 767 | 
             
                # Add model selection metadata to prompt for transparency
         | 
| 991 768 | 
             
                # This helps with debugging and understanding model choices
         | 
| 992 | 
            -
                if  | 
| 769 | 
            +
                if (
         | 
| 770 | 
            +
                    selected_model
         | 
| 771 | 
            +
                    and model_config.get("selection_method") == "dynamic_complexity_based"
         | 
| 772 | 
            +
                ):
         | 
| 993 773 | 
             
                    model_metadata = f"\n<!-- Model Selection: {selected_model} (Complexity: {model_config.get('complexity_level', 'UNKNOWN')}) -->\n"
         | 
| 994 774 | 
             
                    prompt = model_metadata + prompt
         | 
| 995 | 
            -
             | 
| 775 | 
            +
             | 
| 996 776 | 
             
                # Prepend base instructions with dynamic template based on complexity
         | 
| 997 777 | 
             
                # The base instructions provide common guidelines all agents should follow
         | 
| 998 | 
            -
                complexity_score = model_config.get( | 
| 778 | 
            +
                complexity_score = model_config.get("complexity_score", 50) if model_config else 50
         | 
| 999 779 | 
             
                final_prompt = prepend_base_instructions(prompt, complexity_score=complexity_score)
         | 
| 1000 | 
            -
             | 
| 780 | 
            +
             | 
| 1001 781 | 
             
                # Return format based on caller's needs
         | 
| 1002 782 | 
             
                if return_model_info:
         | 
| 1003 783 | 
             
                    return final_prompt, selected_model, model_config
         | 
| @@ -1005,130 +785,15 @@ def get_agent_prompt(agent_name: str, force_reload: bool = False, return_model_i | |
| 1005 785 | 
             
                    return final_prompt
         | 
| 1006 786 |  | 
| 1007 787 |  | 
| 1008 | 
            -
            #  | 
| 1009 | 
            -
            # WHY: These functions exist to maintain backward compatibility with existing code
         | 
| 1010 | 
            -
            # that expects agent-specific getter functions. New code should use get_agent_prompt()
         | 
| 1011 | 
            -
            # directly with the agent_id parameter for more flexibility.
         | 
| 1012 | 
            -
            #
         | 
| 1013 | 
            -
            # DEPRECATION NOTE: These functions may be removed in a future major version.
         | 
| 1014 | 
            -
            # They add maintenance overhead and limit extensibility compared to the generic interface.
         | 
| 1015 | 
            -
             | 
| 1016 | 
            -
            def get_documentation_agent_prompt() -> str:
         | 
| 1017 | 
            -
                """
         | 
| 1018 | 
            -
                Get the complete Documentation Agent prompt with base instructions.
         | 
| 1019 | 
            -
                
         | 
| 1020 | 
            -
                Returns:
         | 
| 1021 | 
            -
                    Complete prompt string ready for use with Claude API
         | 
| 1022 | 
            -
                    
         | 
| 1023 | 
            -
                DEPRECATED: Use get_agent_prompt("documentation_agent") instead
         | 
| 1024 | 
            -
                """
         | 
| 1025 | 
            -
                prompt = get_agent_prompt("documentation_agent", return_model_info=False)
         | 
| 1026 | 
            -
                assert isinstance(prompt, str), "Expected string when return_model_info=False"
         | 
| 1027 | 
            -
                return prompt
         | 
| 1028 | 
            -
             | 
| 1029 | 
            -
             | 
| 1030 | 
            -
            def get_version_control_agent_prompt() -> str:
         | 
| 1031 | 
            -
                """
         | 
| 1032 | 
            -
                Get the complete Version Control Agent prompt with base instructions.
         | 
| 1033 | 
            -
                
         | 
| 1034 | 
            -
                Returns:
         | 
| 1035 | 
            -
                    Complete prompt string ready for use with Claude API
         | 
| 1036 | 
            -
                    
         | 
| 1037 | 
            -
                DEPRECATED: Use get_agent_prompt("version_control_agent") instead
         | 
| 1038 | 
            -
                """
         | 
| 1039 | 
            -
                prompt = get_agent_prompt("version_control_agent", return_model_info=False)
         | 
| 1040 | 
            -
                assert isinstance(prompt, str), "Expected string when return_model_info=False"
         | 
| 1041 | 
            -
                return prompt
         | 
| 1042 | 
            -
             | 
| 1043 | 
            -
             | 
| 1044 | 
            -
            def get_qa_agent_prompt() -> str:
         | 
| 1045 | 
            -
                """
         | 
| 1046 | 
            -
                Get the complete QA Agent prompt with base instructions.
         | 
| 1047 | 
            -
                
         | 
| 1048 | 
            -
                Returns:
         | 
| 1049 | 
            -
                    Complete prompt string ready for use with Claude API
         | 
| 1050 | 
            -
                    
         | 
| 1051 | 
            -
                DEPRECATED: Use get_agent_prompt("qa_agent") instead
         | 
| 1052 | 
            -
                """
         | 
| 1053 | 
            -
                prompt = get_agent_prompt("qa_agent", return_model_info=False)
         | 
| 1054 | 
            -
                assert isinstance(prompt, str), "Expected string when return_model_info=False"
         | 
| 1055 | 
            -
                return prompt
         | 
| 1056 | 
            -
             | 
| 1057 | 
            -
             | 
| 1058 | 
            -
            def get_research_agent_prompt() -> str:
         | 
| 1059 | 
            -
                """
         | 
| 1060 | 
            -
                Get the complete Research Agent prompt with base instructions.
         | 
| 1061 | 
            -
                
         | 
| 1062 | 
            -
                Returns:
         | 
| 1063 | 
            -
                    Complete prompt string ready for use with Claude API
         | 
| 1064 | 
            -
                    
         | 
| 1065 | 
            -
                DEPRECATED: Use get_agent_prompt("research_agent") instead
         | 
| 1066 | 
            -
                """
         | 
| 1067 | 
            -
                prompt = get_agent_prompt("research_agent", return_model_info=False)
         | 
| 1068 | 
            -
                assert isinstance(prompt, str), "Expected string when return_model_info=False"
         | 
| 1069 | 
            -
                return prompt
         | 
| 788 | 
            +
            # Legacy hardcoded agent functions removed - use get_agent_prompt(agent_id) instead
         | 
| 1070 789 |  | 
| 1071 790 |  | 
| 1072 | 
            -
            def  | 
| 1073 | 
            -
                 | 
| 1074 | 
            -
             | 
| 1075 | 
            -
                
         | 
| 1076 | 
            -
                Returns:
         | 
| 1077 | 
            -
                    Complete prompt string ready for use with Claude API
         | 
| 1078 | 
            -
                    
         | 
| 1079 | 
            -
                DEPRECATED: Use get_agent_prompt("ops_agent") instead
         | 
| 1080 | 
            -
                """
         | 
| 1081 | 
            -
                prompt = get_agent_prompt("ops_agent", return_model_info=False)
         | 
| 1082 | 
            -
                assert isinstance(prompt, str), "Expected string when return_model_info=False"
         | 
| 1083 | 
            -
                return prompt
         | 
| 1084 | 
            -
             | 
| 1085 | 
            -
             | 
| 1086 | 
            -
            def get_security_agent_prompt() -> str:
         | 
| 1087 | 
            -
                """
         | 
| 1088 | 
            -
                Get the complete Security Agent prompt with base instructions.
         | 
| 1089 | 
            -
                
         | 
| 1090 | 
            -
                Returns:
         | 
| 1091 | 
            -
                    Complete prompt string ready for use with Claude API
         | 
| 1092 | 
            -
                    
         | 
| 1093 | 
            -
                DEPRECATED: Use get_agent_prompt("security_agent") instead
         | 
| 1094 | 
            -
                """
         | 
| 1095 | 
            -
                prompt = get_agent_prompt("security_agent", return_model_info=False)
         | 
| 1096 | 
            -
                assert isinstance(prompt, str), "Expected string when return_model_info=False"
         | 
| 1097 | 
            -
                return prompt
         | 
| 1098 | 
            -
             | 
| 1099 | 
            -
             | 
| 1100 | 
            -
            def get_engineer_agent_prompt() -> str:
         | 
| 1101 | 
            -
                """
         | 
| 1102 | 
            -
                Get the complete Engineer Agent prompt with base instructions.
         | 
| 1103 | 
            -
                
         | 
| 1104 | 
            -
                Returns:
         | 
| 1105 | 
            -
                    Complete prompt string ready for use with Claude API
         | 
| 1106 | 
            -
                    
         | 
| 1107 | 
            -
                DEPRECATED: Use get_agent_prompt("engineer_agent") instead
         | 
| 1108 | 
            -
                """
         | 
| 1109 | 
            -
                prompt = get_agent_prompt("engineer_agent", return_model_info=False)
         | 
| 1110 | 
            -
                assert isinstance(prompt, str), "Expected string when return_model_info=False"
         | 
| 1111 | 
            -
                return prompt
         | 
| 1112 | 
            -
             | 
| 1113 | 
            -
             | 
| 1114 | 
            -
            def get_data_engineer_agent_prompt() -> str:
         | 
| 1115 | 
            -
                """
         | 
| 1116 | 
            -
                Get the complete Data Engineer Agent prompt with base instructions.
         | 
| 1117 | 
            -
                
         | 
| 1118 | 
            -
                Returns:
         | 
| 1119 | 
            -
                    Complete prompt string ready for use with Claude API
         | 
| 1120 | 
            -
                    
         | 
| 1121 | 
            -
                DEPRECATED: Use get_agent_prompt("data_engineer_agent") instead
         | 
| 1122 | 
            -
                """
         | 
| 1123 | 
            -
                prompt = get_agent_prompt("data_engineer_agent", return_model_info=False)
         | 
| 1124 | 
            -
                assert isinstance(prompt, str), "Expected string when return_model_info=False"
         | 
| 1125 | 
            -
                return prompt
         | 
| 1126 | 
            -
             | 
| 1127 | 
            -
             | 
| 1128 | 
            -
            def get_agent_prompt_with_model_info(agent_name: str, force_reload: bool = False, **kwargs: Any) -> Tuple[str, str, Dict[str, Any]]:
         | 
| 791 | 
            +
            def get_agent_prompt_with_model_info(
         | 
| 792 | 
            +
                agent_name: str, force_reload: bool = False, **kwargs: Any
         | 
| 793 | 
            +
            ) -> Tuple[str, str, Dict[str, Any]]:
         | 
| 1129 794 | 
             
                """
         | 
| 1130 795 | 
             
                Convenience wrapper to always get agent prompt with model selection information.
         | 
| 1131 | 
            -
             | 
| 796 | 
            +
             | 
| 1132 797 | 
             
                Args:
         | 
| 1133 798 | 
             
                    agent_name: Agent ID (e.g., "research_agent")
         | 
| 1134 799 | 
             
                    force_reload: Force reload from source, bypassing cache
         | 
| @@ -1136,16 +801,16 @@ def get_agent_prompt_with_model_info(agent_name: str, force_reload: bool = False | |
| 1136 801 | 
             
                        - task_description: For complexity analysis
         | 
| 1137 802 | 
             
                        - context_size: For complexity scoring
         | 
| 1138 803 | 
             
                        - Other task-specific parameters
         | 
| 1139 | 
            -
             | 
| 804 | 
            +
             | 
| 1140 805 | 
             
                Returns:
         | 
| 1141 806 | 
             
                    Tuple of (prompt, selected_model, model_config) where:
         | 
| 1142 807 | 
             
                        - prompt: Complete agent prompt with base instructions
         | 
| 1143 808 | 
             
                        - selected_model: Claude API model identifier
         | 
| 1144 809 | 
             
                        - model_config: Dictionary with selection metadata
         | 
| 1145 | 
            -
             | 
| 810 | 
            +
             | 
| 1146 811 | 
             
                WHY: This dedicated function ensures type safety for callers that always
         | 
| 1147 812 | 
             
                need model information, avoiding the need to handle Union types.
         | 
| 1148 | 
            -
             | 
| 813 | 
            +
             | 
| 1149 814 | 
             
                Example:
         | 
| 1150 815 | 
             
                    prompt, model, config = get_agent_prompt_with_model_info(
         | 
| 1151 816 | 
             
                        "research_agent",
         | 
| @@ -1153,12 +818,14 @@ def get_agent_prompt_with_model_info(agent_name: str, force_reload: bool = False | |
| 1153 818 | 
             
                    )
         | 
| 1154 819 | 
             
                    print(f"Using model: {model} (method: {config['selection_method']})")
         | 
| 1155 820 | 
             
                """
         | 
| 1156 | 
            -
                result = get_agent_prompt( | 
| 1157 | 
            -
             | 
| 821 | 
            +
                result = get_agent_prompt(
         | 
| 822 | 
            +
                    agent_name, force_reload, return_model_info=True, **kwargs
         | 
| 823 | 
            +
                )
         | 
| 824 | 
            +
             | 
| 1158 825 | 
             
                # Type narrowing - we know this returns a tuple when return_model_info=True
         | 
| 1159 826 | 
             
                if isinstance(result, tuple):
         | 
| 1160 827 | 
             
                    return result
         | 
| 1161 | 
            -
             | 
| 828 | 
            +
             | 
| 1162 829 | 
             
                # Fallback (shouldn't happen with current implementation)
         | 
| 1163 830 | 
             
                # This defensive code ensures we always return the expected tuple format
         | 
| 1164 831 | 
             
                loader = _get_loader()
         | 
| @@ -1166,98 +833,46 @@ def get_agent_prompt_with_model_info(agent_name: str, force_reload: bool = False | |
| 1166 833 | 
             
                default_model = "claude-sonnet-4-20250514"
         | 
| 1167 834 | 
             
                if agent_data:
         | 
| 1168 835 | 
             
                    default_model = agent_data.get("capabilities", {}).get("model", default_model)
         | 
| 1169 | 
            -
             | 
| 836 | 
            +
             | 
| 1170 837 | 
             
                return result, default_model, {"selection_method": "default"}
         | 
| 1171 838 |  | 
| 1172 839 |  | 
| 1173 840 | 
             
            # Utility functions for agent management
         | 
| 1174 841 |  | 
| 1175 | 
            -
             | 
| 1176 | 
            -
                """
         | 
| 1177 | 
            -
                List all available agents with their key metadata.
         | 
| 1178 | 
            -
                
         | 
| 1179 | 
            -
                Returns:
         | 
| 1180 | 
            -
                    Dictionary mapping agent IDs to their metadata summaries
         | 
| 1181 | 
            -
                    
         | 
| 1182 | 
            -
                The returned dictionary provides a comprehensive view of all registered
         | 
| 1183 | 
            -
                agents, useful for:
         | 
| 1184 | 
            -
                - UI agent selection interfaces
         | 
| 1185 | 
            -
                - Documentation generation
         | 
| 1186 | 
            -
                - System introspection and debugging
         | 
| 1187 | 
            -
                - Programmatic agent discovery
         | 
| 1188 | 
            -
                
         | 
| 1189 | 
            -
                Example Return Value:
         | 
| 1190 | 
            -
                    {
         | 
| 1191 | 
            -
                        "research_agent": {
         | 
| 1192 | 
            -
                            "name": "Research Agent",
         | 
| 1193 | 
            -
                            "description": "Analyzes codebases...",
         | 
| 1194 | 
            -
                            "category": "analysis",
         | 
| 1195 | 
            -
                            "version": "1.0.0",
         | 
| 1196 | 
            -
                            "model": "claude-opus-4-20250514",
         | 
| 1197 | 
            -
                            "resource_tier": "standard",
         | 
| 1198 | 
            -
                            "tools": ["code_analysis", "search"]
         | 
| 1199 | 
            -
                        },
         | 
| 1200 | 
            -
                        ...
         | 
| 1201 | 
            -
                    }
         | 
| 1202 | 
            -
                    
         | 
| 1203 | 
            -
                WHY: This aggregated view is more useful than raw agent data because:
         | 
| 1204 | 
            -
                - It provides a consistent interface regardless of schema changes
         | 
| 1205 | 
            -
                - It includes only the fields relevant for agent selection
         | 
| 1206 | 
            -
                - It's optimized for UI display and decision making
         | 
| 1207 | 
            -
                """
         | 
| 1208 | 
            -
                loader = _get_loader()
         | 
| 1209 | 
            -
                agents = {}
         | 
| 1210 | 
            -
                
         | 
| 1211 | 
            -
                for agent_info in loader.list_agents():
         | 
| 1212 | 
            -
                    agent_id = agent_info["id"]
         | 
| 1213 | 
            -
                    metadata = loader.get_agent_metadata(agent_id)
         | 
| 1214 | 
            -
                    
         | 
| 1215 | 
            -
                    if metadata:
         | 
| 1216 | 
            -
                        # Extract and flatten key information for easy consumption
         | 
| 1217 | 
            -
                        agents[agent_id] = {
         | 
| 1218 | 
            -
                            "name": metadata["metadata"].get("name", agent_id),
         | 
| 1219 | 
            -
                            "description": metadata["metadata"].get("description", ""),
         | 
| 1220 | 
            -
                            "category": metadata["metadata"].get("category", ""),
         | 
| 1221 | 
            -
                            "version": metadata["version"],
         | 
| 1222 | 
            -
                            "model": metadata["capabilities"].get("model", ""),
         | 
| 1223 | 
            -
                            "resource_tier": metadata["capabilities"].get("resource_tier", ""),
         | 
| 1224 | 
            -
                            "tools": metadata["capabilities"].get("tools", [])
         | 
| 1225 | 
            -
                        }
         | 
| 1226 | 
            -
                
         | 
| 1227 | 
            -
                return agents
         | 
| 842 | 
            +
            # Duplicate list_available_agents function removed
         | 
| 1228 843 |  | 
| 1229 844 |  | 
| 1230 845 | 
             
            def clear_agent_cache(agent_name: Optional[str] = None) -> None:
         | 
| 1231 846 | 
             
                """
         | 
| 1232 847 | 
             
                Clear cached agent prompts for development or after updates.
         | 
| 1233 | 
            -
             | 
| 848 | 
            +
             | 
| 1234 849 | 
             
                Args:
         | 
| 1235 850 | 
             
                    agent_name: Specific agent ID to clear, or None to clear all agents
         | 
| 1236 | 
            -
             | 
| 851 | 
            +
             | 
| 1237 852 | 
             
                This function is useful for:
         | 
| 1238 853 | 
             
                - Development when modifying agent prompts
         | 
| 1239 854 | 
             
                - Forcing reload after agent template updates
         | 
| 1240 855 | 
             
                - Troubleshooting caching issues
         | 
| 1241 856 | 
             
                - Memory management in long-running processes
         | 
| 1242 | 
            -
             | 
| 857 | 
            +
             | 
| 1243 858 | 
             
                Examples:
         | 
| 1244 859 | 
             
                    # Clear specific agent cache
         | 
| 1245 860 | 
             
                    clear_agent_cache("research_agent")
         | 
| 1246 | 
            -
             | 
| 861 | 
            +
             | 
| 1247 862 | 
             
                    # Clear all agent caches
         | 
| 1248 863 | 
             
                    clear_agent_cache()
         | 
| 1249 | 
            -
             | 
| 864 | 
            +
             | 
| 1250 865 | 
             
                WHY: Manual cache management is important because:
         | 
| 1251 866 | 
             
                - Agent prompts have a 1-hour TTL but may need immediate refresh
         | 
| 1252 867 | 
             
                - Development requires seeing changes without waiting for TTL
         | 
| 1253 868 | 
             
                - System administrators need cache control for troubleshooting
         | 
| 1254 | 
            -
             | 
| 869 | 
            +
             | 
| 1255 870 | 
             
                Error Handling: Failures are logged but don't raise exceptions to ensure
         | 
| 1256 871 | 
             
                the system remains operational even if cache clearing fails.
         | 
| 1257 872 | 
             
                """
         | 
| 1258 873 | 
             
                try:
         | 
| 1259 874 | 
             
                    cache = SharedPromptCache.get_instance()
         | 
| 1260 | 
            -
             | 
| 875 | 
            +
             | 
| 1261 876 | 
             
                    if agent_name:
         | 
| 1262 877 | 
             
                        # Clear specific agent's cache entry
         | 
| 1263 878 | 
             
                        cache_key = f"{AGENT_CACHE_PREFIX}{agent_name}"
         | 
| @@ -1270,170 +885,16 @@ def clear_agent_cache(agent_name: Optional[str] = None) -> None: | |
| 1270 885 | 
             
                            cache_key = f"{AGENT_CACHE_PREFIX}{agent_id}"
         | 
| 1271 886 | 
             
                            cache.invalidate(cache_key)
         | 
| 1272 887 | 
             
                        logger.debug("All agent caches cleared")
         | 
| 1273 | 
            -
             | 
| 888 | 
            +
             | 
| 1274 889 | 
             
                except Exception as e:
         | 
| 1275 890 | 
             
                    # Log but don't raise - cache clearing shouldn't break the system
         | 
| 1276 891 | 
             
                    logger.error(f"Error clearing agent cache: {e}")
         | 
| 1277 892 |  | 
| 1278 893 |  | 
| 1279 | 
            -
             | 
| 1280 | 
            -
                """
         | 
| 1281 | 
            -
                List available agents organized by their tier.
         | 
| 1282 | 
            -
                
         | 
| 1283 | 
            -
                Returns:
         | 
| 1284 | 
            -
                    Dictionary mapping tier names to lists of agent IDs available in that tier
         | 
| 1285 | 
            -
                    
         | 
| 1286 | 
            -
                Example:
         | 
| 1287 | 
            -
                    {
         | 
| 1288 | 
            -
                        "project": ["engineer_agent", "custom_agent"],
         | 
| 1289 | 
            -
                        "user": ["research_agent"],
         | 
| 1290 | 
            -
                        "system": ["engineer_agent", "research_agent", "qa_agent", ...]
         | 
| 1291 | 
            -
                    }
         | 
| 1292 | 
            -
                
         | 
| 1293 | 
            -
                This is useful for:
         | 
| 1294 | 
            -
                - Understanding which agents are available at each level
         | 
| 1295 | 
            -
                - Debugging agent precedence issues
         | 
| 1296 | 
            -
                - UI display of agent sources
         | 
| 1297 | 
            -
                """
         | 
| 1298 | 
            -
                loader = _get_loader()
         | 
| 1299 | 
            -
                result = {"project": [], "user": [], "system": []}
         | 
| 1300 | 
            -
                
         | 
| 1301 | 
            -
                # Group agents by their loaded tier
         | 
| 1302 | 
            -
                for agent_id, tier in loader._agent_tiers.items():
         | 
| 1303 | 
            -
                    result[tier.value].append(agent_id)
         | 
| 1304 | 
            -
                
         | 
| 1305 | 
            -
                # Sort each list for consistent output
         | 
| 1306 | 
            -
                for tier in result:
         | 
| 1307 | 
            -
                    result[tier].sort()
         | 
| 1308 | 
            -
                
         | 
| 1309 | 
            -
                return result
         | 
| 894 | 
            +
            # Duplicate list_agents_by_tier function removed
         | 
| 1310 895 |  | 
| 896 | 
            +
            # Duplicate validate_agent_files function removed
         | 
| 1311 897 |  | 
| 1312 | 
            -
             | 
| 1313 | 
            -
                """
         | 
| 1314 | 
            -
                Validate all agent template files against the schema.
         | 
| 1315 | 
            -
                
         | 
| 1316 | 
            -
                Returns:
         | 
| 1317 | 
            -
                    Dictionary mapping agent names to validation results
         | 
| 1318 | 
            -
                    
         | 
| 1319 | 
            -
                This function performs comprehensive validation of all agent files,
         | 
| 1320 | 
            -
                checking for:
         | 
| 1321 | 
            -
                - JSON syntax errors
         | 
| 1322 | 
            -
                - Schema compliance
         | 
| 1323 | 
            -
                - Required fields presence
         | 
| 1324 | 
            -
                - Data type correctness
         | 
| 1325 | 
            -
                - Constraint violations
         | 
| 1326 | 
            -
                
         | 
| 1327 | 
            -
                Return Format:
         | 
| 1328 | 
            -
                    {
         | 
| 1329 | 
            -
                        "agent_name": {
         | 
| 1330 | 
            -
                            "valid": bool,
         | 
| 1331 | 
            -
                            "errors": [list of error messages],
         | 
| 1332 | 
            -
                            "warnings": [list of warning messages],
         | 
| 1333 | 
            -
                            "file_path": "/full/path/to/file.json"
         | 
| 1334 | 
            -
                        },
         | 
| 1335 | 
            -
                        ...
         | 
| 1336 | 
            -
                    }
         | 
| 1337 | 
            -
                    
         | 
| 1338 | 
            -
                Use Cases:
         | 
| 1339 | 
            -
                - Pre-deployment validation in CI/CD
         | 
| 1340 | 
            -
                - Development-time agent verification  
         | 
| 1341 | 
            -
                - Troubleshooting agent loading issues
         | 
| 1342 | 
            -
                - Automated testing of agent configurations
         | 
| 1343 | 
            -
                
         | 
| 1344 | 
            -
                WHY: Separate validation allows checking agents without loading them,
         | 
| 1345 | 
            -
                useful for CI/CD pipelines and development workflows where we want to
         | 
| 1346 | 
            -
                catch errors before runtime.
         | 
| 1347 | 
            -
                """
         | 
| 1348 | 
            -
                validator = AgentValidator()
         | 
| 1349 | 
            -
                results = {}
         | 
| 1350 | 
            -
                
         | 
| 1351 | 
            -
                for json_file in AGENT_TEMPLATES_DIR.glob("*.json"):
         | 
| 1352 | 
            -
                    # Skip the schema definition file itself
         | 
| 1353 | 
            -
                    if json_file.name == "agent_schema.json":
         | 
| 1354 | 
            -
                        continue
         | 
| 1355 | 
            -
                    
         | 
| 1356 | 
            -
                    validation_result = validator.validate_file(json_file)
         | 
| 1357 | 
            -
                    results[json_file.stem] = {
         | 
| 1358 | 
            -
                        "valid": validation_result.is_valid,
         | 
| 1359 | 
            -
                        "errors": validation_result.errors,
         | 
| 1360 | 
            -
                        "warnings": validation_result.warnings,
         | 
| 1361 | 
            -
                        "file_path": str(json_file)
         | 
| 1362 | 
            -
                    }
         | 
| 1363 | 
            -
                
         | 
| 1364 | 
            -
                return results
         | 
| 1365 | 
            -
             | 
| 1366 | 
            -
             | 
| 1367 | 
            -
            def reload_agents() -> None:
         | 
| 1368 | 
            -
                """
         | 
| 1369 | 
            -
                Force reload all agents from disk, clearing the registry and cache.
         | 
| 1370 | 
            -
                
         | 
| 1371 | 
            -
                This function completely resets the agent loader state, causing:
         | 
| 1372 | 
            -
                1. The global loader instance to be destroyed
         | 
| 1373 | 
            -
                2. All cached agent prompts to be invalidated
         | 
| 1374 | 
            -
                3. Fresh agent discovery on next access across all tiers
         | 
| 1375 | 
            -
                
         | 
| 1376 | 
            -
                Use Cases:
         | 
| 1377 | 
            -
                - Hot-reloading during development
         | 
| 1378 | 
            -
                - Picking up new agent files without restart
         | 
| 1379 | 
            -
                - Recovering from corrupted state
         | 
| 1380 | 
            -
                - Testing agent loading logic
         | 
| 1381 | 
            -
                - Switching between projects with different agents
         | 
| 1382 | 
            -
                
         | 
| 1383 | 
            -
                WHY: Hot-reloading is essential for development productivity and
         | 
| 1384 | 
            -
                allows dynamic agent updates in production without service restart.
         | 
| 1385 | 
            -
                
         | 
| 1386 | 
            -
                Implementation Note: We simply clear the global loader reference.
         | 
| 1387 | 
            -
                The next call to _get_loader() will create a fresh instance that
         | 
| 1388 | 
            -
                re-discovers and re-validates all agents across all tiers.
         | 
| 1389 | 
            -
                """
         | 
| 1390 | 
            -
                global _loader
         | 
| 1391 | 
            -
                _loader = None
         | 
| 1392 | 
            -
                logger.info("Agent registry cleared, will reload on next access")
         | 
| 1393 | 
            -
             | 
| 898 | 
            +
            # Duplicate reload_agents function removed
         | 
| 1394 899 |  | 
| 1395 | 
            -
             | 
| 1396 | 
            -
                """
         | 
| 1397 | 
            -
                Get the tier from which an agent was loaded.
         | 
| 1398 | 
            -
                
         | 
| 1399 | 
            -
                Args:
         | 
| 1400 | 
            -
                    agent_name: Agent name or ID
         | 
| 1401 | 
            -
                    
         | 
| 1402 | 
            -
                Returns:
         | 
| 1403 | 
            -
                    Tier name ("project", "user", or "system") or None if not found
         | 
| 1404 | 
            -
                    
         | 
| 1405 | 
            -
                This is useful for debugging and understanding which version of an
         | 
| 1406 | 
            -
                agent is being used when multiple versions exist across tiers.
         | 
| 1407 | 
            -
                """
         | 
| 1408 | 
            -
                loader = _get_loader()
         | 
| 1409 | 
            -
                
         | 
| 1410 | 
            -
                # Check if agent exists with exact name
         | 
| 1411 | 
            -
                if agent_name in loader._agent_tiers:
         | 
| 1412 | 
            -
                    tier = loader._agent_tiers[agent_name]
         | 
| 1413 | 
            -
                    return tier.value if tier else None
         | 
| 1414 | 
            -
                
         | 
| 1415 | 
            -
                # Try with _agent suffix
         | 
| 1416 | 
            -
                agent_with_suffix = f"{agent_name}_agent"
         | 
| 1417 | 
            -
                if agent_with_suffix in loader._agent_tiers:
         | 
| 1418 | 
            -
                    tier = loader._agent_tiers[agent_with_suffix]
         | 
| 1419 | 
            -
                    return tier.value if tier else None
         | 
| 1420 | 
            -
                
         | 
| 1421 | 
            -
                # Try normalizing the name
         | 
| 1422 | 
            -
                normalizer = AgentNameNormalizer()
         | 
| 1423 | 
            -
                cleaned = agent_name.strip().lower().replace("-", "_")
         | 
| 1424 | 
            -
                
         | 
| 1425 | 
            -
                if cleaned in normalizer.ALIASES or cleaned in normalizer.CANONICAL_NAMES:
         | 
| 1426 | 
            -
                    agent_key = normalizer.to_key(agent_name)
         | 
| 1427 | 
            -
                    # Try both with and without suffix
         | 
| 1428 | 
            -
                    for candidate in [agent_key, f"{agent_key}_agent"]:
         | 
| 1429 | 
            -
                        if candidate in loader._agent_tiers:
         | 
| 1430 | 
            -
                            tier = loader._agent_tiers[candidate]
         | 
| 1431 | 
            -
                            return tier.value if tier else None
         | 
| 1432 | 
            -
                
         | 
| 1433 | 
            -
                # Try cleaned name with and without suffix
         | 
| 1434 | 
            -
                for candidate in [cleaned, f"{cleaned}_agent"]:
         | 
| 1435 | 
            -
                    if candidate in loader._agent_tiers:
         | 
| 1436 | 
            -
                        tier = loader._agent_tiers[candidate]
         | 
| 1437 | 
            -
                        return tier.value if tier else None
         | 
| 1438 | 
            -
                
         | 
| 1439 | 
            -
                return None
         | 
| 900 | 
            +
            # Duplicate get_agent_tier function removed - using the one defined earlier
         |