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.
Files changed (419) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +2 -2
  3. claude_mpm/__main__.py +3 -2
  4. claude_mpm/agents/__init__.py +85 -79
  5. claude_mpm/agents/agent_loader.py +464 -1003
  6. claude_mpm/agents/agent_loader_integration.py +45 -45
  7. claude_mpm/agents/agents_metadata.py +29 -30
  8. claude_mpm/agents/async_agent_loader.py +156 -138
  9. claude_mpm/agents/base_agent.json +1 -1
  10. claude_mpm/agents/base_agent_loader.py +179 -151
  11. claude_mpm/agents/frontmatter_validator.py +229 -130
  12. claude_mpm/agents/schema/agent_schema.json +1 -1
  13. claude_mpm/agents/system_agent_config.py +213 -147
  14. claude_mpm/agents/templates/__init__.py +13 -13
  15. claude_mpm/agents/templates/code_analyzer.json +2 -2
  16. claude_mpm/agents/templates/data_engineer.json +1 -1
  17. claude_mpm/agents/templates/documentation.json +23 -11
  18. claude_mpm/agents/templates/engineer.json +22 -6
  19. claude_mpm/agents/templates/memory_manager.json +1 -1
  20. claude_mpm/agents/templates/ops.json +2 -2
  21. claude_mpm/agents/templates/project_organizer.json +1 -1
  22. claude_mpm/agents/templates/qa.json +1 -1
  23. claude_mpm/agents/templates/refactoring_engineer.json +222 -0
  24. claude_mpm/agents/templates/research.json +20 -14
  25. claude_mpm/agents/templates/security.json +1 -1
  26. claude_mpm/agents/templates/ticketing.json +1 -1
  27. claude_mpm/agents/templates/version_control.json +1 -1
  28. claude_mpm/agents/templates/web_qa.json +3 -1
  29. claude_mpm/agents/templates/web_ui.json +2 -2
  30. claude_mpm/cli/__init__.py +79 -51
  31. claude_mpm/cli/__main__.py +3 -2
  32. claude_mpm/cli/commands/__init__.py +20 -20
  33. claude_mpm/cli/commands/agents.py +279 -247
  34. claude_mpm/cli/commands/aggregate.py +138 -157
  35. claude_mpm/cli/commands/cleanup.py +147 -147
  36. claude_mpm/cli/commands/config.py +93 -76
  37. claude_mpm/cli/commands/info.py +17 -16
  38. claude_mpm/cli/commands/mcp.py +140 -905
  39. claude_mpm/cli/commands/mcp_command_router.py +139 -0
  40. claude_mpm/cli/commands/mcp_config_commands.py +20 -0
  41. claude_mpm/cli/commands/mcp_install_commands.py +20 -0
  42. claude_mpm/cli/commands/mcp_server_commands.py +175 -0
  43. claude_mpm/cli/commands/mcp_tool_commands.py +34 -0
  44. claude_mpm/cli/commands/memory.py +239 -203
  45. claude_mpm/cli/commands/monitor.py +203 -81
  46. claude_mpm/cli/commands/run.py +380 -429
  47. claude_mpm/cli/commands/run_config_checker.py +160 -0
  48. claude_mpm/cli/commands/socketio_monitor.py +235 -0
  49. claude_mpm/cli/commands/tickets.py +305 -197
  50. claude_mpm/cli/parser.py +24 -1156
  51. claude_mpm/cli/parsers/__init__.py +29 -0
  52. claude_mpm/cli/parsers/agents_parser.py +136 -0
  53. claude_mpm/cli/parsers/base_parser.py +331 -0
  54. claude_mpm/cli/parsers/config_parser.py +85 -0
  55. claude_mpm/cli/parsers/mcp_parser.py +152 -0
  56. claude_mpm/cli/parsers/memory_parser.py +138 -0
  57. claude_mpm/cli/parsers/monitor_parser.py +104 -0
  58. claude_mpm/cli/parsers/run_parser.py +147 -0
  59. claude_mpm/cli/parsers/tickets_parser.py +203 -0
  60. claude_mpm/cli/ticket_cli.py +7 -3
  61. claude_mpm/cli/utils.py +55 -37
  62. claude_mpm/cli_module/__init__.py +6 -6
  63. claude_mpm/cli_module/args.py +188 -140
  64. claude_mpm/cli_module/commands.py +79 -70
  65. claude_mpm/cli_module/migration_example.py +38 -60
  66. claude_mpm/config/__init__.py +32 -25
  67. claude_mpm/config/agent_config.py +151 -119
  68. claude_mpm/config/experimental_features.py +71 -73
  69. claude_mpm/config/paths.py +94 -208
  70. claude_mpm/config/socketio_config.py +84 -73
  71. claude_mpm/constants.py +35 -18
  72. claude_mpm/core/__init__.py +9 -6
  73. claude_mpm/core/agent_name_normalizer.py +68 -71
  74. claude_mpm/core/agent_registry.py +372 -521
  75. claude_mpm/core/agent_session_manager.py +74 -63
  76. claude_mpm/core/base_service.py +116 -87
  77. claude_mpm/core/cache.py +119 -153
  78. claude_mpm/core/claude_runner.py +425 -1120
  79. claude_mpm/core/config.py +263 -168
  80. claude_mpm/core/config_aliases.py +69 -61
  81. claude_mpm/core/config_constants.py +292 -0
  82. claude_mpm/core/constants.py +57 -99
  83. claude_mpm/core/container.py +211 -178
  84. claude_mpm/core/exceptions.py +233 -89
  85. claude_mpm/core/factories.py +92 -54
  86. claude_mpm/core/framework_loader.py +378 -220
  87. claude_mpm/core/hook_manager.py +198 -83
  88. claude_mpm/core/hook_performance_config.py +136 -0
  89. claude_mpm/core/injectable_service.py +61 -55
  90. claude_mpm/core/interactive_session.py +165 -155
  91. claude_mpm/core/interfaces.py +221 -195
  92. claude_mpm/core/lazy.py +96 -96
  93. claude_mpm/core/logger.py +133 -107
  94. claude_mpm/core/logging_config.py +185 -157
  95. claude_mpm/core/minimal_framework_loader.py +20 -15
  96. claude_mpm/core/mixins.py +30 -29
  97. claude_mpm/core/oneshot_session.py +215 -181
  98. claude_mpm/core/optimized_agent_loader.py +134 -138
  99. claude_mpm/core/optimized_startup.py +159 -157
  100. claude_mpm/core/pm_hook_interceptor.py +85 -72
  101. claude_mpm/core/service_registry.py +103 -101
  102. claude_mpm/core/session_manager.py +97 -87
  103. claude_mpm/core/socketio_pool.py +212 -158
  104. claude_mpm/core/tool_access_control.py +58 -51
  105. claude_mpm/core/types.py +46 -24
  106. claude_mpm/core/typing_utils.py +166 -82
  107. claude_mpm/core/unified_agent_registry.py +721 -0
  108. claude_mpm/core/unified_config.py +550 -0
  109. claude_mpm/core/unified_paths.py +549 -0
  110. claude_mpm/dashboard/index.html +1 -1
  111. claude_mpm/dashboard/open_dashboard.py +51 -17
  112. claude_mpm/dashboard/static/css/dashboard.css +27 -8
  113. claude_mpm/dashboard/static/dist/components/agent-inference.js +2 -0
  114. claude_mpm/dashboard/static/dist/components/event-processor.js +2 -0
  115. claude_mpm/dashboard/static/dist/components/event-viewer.js +2 -0
  116. claude_mpm/dashboard/static/dist/components/export-manager.js +2 -0
  117. claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +2 -0
  118. claude_mpm/dashboard/static/dist/components/hud-library-loader.js +2 -0
  119. claude_mpm/dashboard/static/dist/components/hud-manager.js +2 -0
  120. claude_mpm/dashboard/static/dist/components/hud-visualizer.js +2 -0
  121. claude_mpm/dashboard/static/dist/components/module-viewer.js +2 -0
  122. claude_mpm/dashboard/static/dist/components/session-manager.js +2 -0
  123. claude_mpm/dashboard/static/dist/components/socket-manager.js +2 -0
  124. claude_mpm/dashboard/static/dist/components/ui-state-manager.js +2 -0
  125. claude_mpm/dashboard/static/dist/components/working-directory.js +2 -0
  126. claude_mpm/dashboard/static/dist/dashboard.js +2 -0
  127. claude_mpm/dashboard/static/dist/socket-client.js +2 -0
  128. claude_mpm/dashboard/static/js/components/agent-inference.js +80 -76
  129. claude_mpm/dashboard/static/js/components/event-processor.js +71 -67
  130. claude_mpm/dashboard/static/js/components/event-viewer.js +74 -70
  131. claude_mpm/dashboard/static/js/components/export-manager.js +31 -28
  132. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +106 -92
  133. claude_mpm/dashboard/static/js/components/hud-library-loader.js +11 -11
  134. claude_mpm/dashboard/static/js/components/hud-manager.js +73 -73
  135. claude_mpm/dashboard/static/js/components/hud-visualizer.js +163 -163
  136. claude_mpm/dashboard/static/js/components/module-viewer.js +305 -233
  137. claude_mpm/dashboard/static/js/components/session-manager.js +32 -29
  138. claude_mpm/dashboard/static/js/components/socket-manager.js +27 -20
  139. claude_mpm/dashboard/static/js/components/ui-state-manager.js +21 -18
  140. claude_mpm/dashboard/static/js/components/working-directory.js +74 -71
  141. claude_mpm/dashboard/static/js/dashboard.js +178 -453
  142. claude_mpm/dashboard/static/js/extension-error-handler.js +164 -0
  143. claude_mpm/dashboard/static/js/socket-client.js +120 -54
  144. claude_mpm/dashboard/templates/index.html +40 -50
  145. claude_mpm/experimental/cli_enhancements.py +60 -58
  146. claude_mpm/generators/__init__.py +1 -1
  147. claude_mpm/generators/agent_profile_generator.py +75 -65
  148. claude_mpm/hooks/__init__.py +1 -1
  149. claude_mpm/hooks/base_hook.py +33 -28
  150. claude_mpm/hooks/claude_hooks/__init__.py +1 -1
  151. claude_mpm/hooks/claude_hooks/connection_pool.py +120 -0
  152. claude_mpm/hooks/claude_hooks/event_handlers.py +743 -0
  153. claude_mpm/hooks/claude_hooks/hook_handler.py +415 -1331
  154. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +4 -4
  155. claude_mpm/hooks/claude_hooks/memory_integration.py +221 -0
  156. claude_mpm/hooks/claude_hooks/response_tracking.py +348 -0
  157. claude_mpm/hooks/claude_hooks/tool_analysis.py +230 -0
  158. claude_mpm/hooks/memory_integration_hook.py +140 -100
  159. claude_mpm/hooks/tool_call_interceptor.py +89 -76
  160. claude_mpm/hooks/validation_hooks.py +57 -49
  161. claude_mpm/init.py +145 -121
  162. claude_mpm/models/__init__.py +9 -9
  163. claude_mpm/models/agent_definition.py +33 -23
  164. claude_mpm/models/agent_session.py +228 -200
  165. claude_mpm/scripts/__init__.py +1 -1
  166. claude_mpm/scripts/socketio_daemon.py +192 -75
  167. claude_mpm/scripts/socketio_server_manager.py +328 -0
  168. claude_mpm/scripts/start_activity_logging.py +25 -22
  169. claude_mpm/services/__init__.py +68 -43
  170. claude_mpm/services/agent_capabilities_service.py +271 -0
  171. claude_mpm/services/agents/__init__.py +23 -32
  172. claude_mpm/services/agents/deployment/__init__.py +3 -3
  173. claude_mpm/services/agents/deployment/agent_config_provider.py +310 -0
  174. claude_mpm/services/agents/deployment/agent_configuration_manager.py +359 -0
  175. claude_mpm/services/agents/deployment/agent_definition_factory.py +84 -0
  176. claude_mpm/services/agents/deployment/agent_deployment.py +415 -2113
  177. claude_mpm/services/agents/deployment/agent_discovery_service.py +387 -0
  178. claude_mpm/services/agents/deployment/agent_environment_manager.py +293 -0
  179. claude_mpm/services/agents/deployment/agent_filesystem_manager.py +387 -0
  180. claude_mpm/services/agents/deployment/agent_format_converter.py +453 -0
  181. claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +161 -0
  182. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +345 -495
  183. claude_mpm/services/agents/deployment/agent_metrics_collector.py +279 -0
  184. claude_mpm/services/agents/deployment/agent_restore_handler.py +88 -0
  185. claude_mpm/services/agents/deployment/agent_template_builder.py +406 -0
  186. claude_mpm/services/agents/deployment/agent_validator.py +352 -0
  187. claude_mpm/services/agents/deployment/agent_version_manager.py +313 -0
  188. claude_mpm/services/agents/deployment/agent_versioning.py +6 -9
  189. claude_mpm/services/agents/deployment/agents_directory_resolver.py +79 -0
  190. claude_mpm/services/agents/deployment/async_agent_deployment.py +298 -234
  191. claude_mpm/services/agents/deployment/config/__init__.py +13 -0
  192. claude_mpm/services/agents/deployment/config/deployment_config.py +182 -0
  193. claude_mpm/services/agents/deployment/config/deployment_config_manager.py +200 -0
  194. claude_mpm/services/agents/deployment/deployment_config_loader.py +54 -0
  195. claude_mpm/services/agents/deployment/deployment_type_detector.py +124 -0
  196. claude_mpm/services/agents/deployment/facade/__init__.py +18 -0
  197. claude_mpm/services/agents/deployment/facade/async_deployment_executor.py +159 -0
  198. claude_mpm/services/agents/deployment/facade/deployment_executor.py +73 -0
  199. claude_mpm/services/agents/deployment/facade/deployment_facade.py +270 -0
  200. claude_mpm/services/agents/deployment/facade/sync_deployment_executor.py +178 -0
  201. claude_mpm/services/agents/deployment/interface_adapter.py +227 -0
  202. claude_mpm/services/agents/deployment/lifecycle_health_checker.py +85 -0
  203. claude_mpm/services/agents/deployment/lifecycle_performance_tracker.py +100 -0
  204. claude_mpm/services/agents/deployment/pipeline/__init__.py +32 -0
  205. claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +158 -0
  206. claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +159 -0
  207. claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +169 -0
  208. claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +19 -0
  209. claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +195 -0
  210. claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +119 -0
  211. claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +79 -0
  212. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +90 -0
  213. claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +100 -0
  214. claude_mpm/services/agents/deployment/processors/__init__.py +15 -0
  215. claude_mpm/services/agents/deployment/processors/agent_deployment_context.py +98 -0
  216. claude_mpm/services/agents/deployment/processors/agent_deployment_result.py +235 -0
  217. claude_mpm/services/agents/deployment/processors/agent_processor.py +258 -0
  218. claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +318 -0
  219. claude_mpm/services/agents/deployment/results/__init__.py +13 -0
  220. claude_mpm/services/agents/deployment/results/deployment_metrics.py +200 -0
  221. claude_mpm/services/agents/deployment/results/deployment_result_builder.py +249 -0
  222. claude_mpm/services/agents/deployment/strategies/__init__.py +25 -0
  223. claude_mpm/services/agents/deployment/strategies/base_strategy.py +119 -0
  224. claude_mpm/services/agents/deployment/strategies/project_strategy.py +150 -0
  225. claude_mpm/services/agents/deployment/strategies/strategy_selector.py +117 -0
  226. claude_mpm/services/agents/deployment/strategies/system_strategy.py +116 -0
  227. claude_mpm/services/agents/deployment/strategies/user_strategy.py +137 -0
  228. claude_mpm/services/agents/deployment/system_instructions_deployer.py +108 -0
  229. claude_mpm/services/agents/deployment/validation/__init__.py +19 -0
  230. claude_mpm/services/agents/deployment/validation/agent_validator.py +323 -0
  231. claude_mpm/services/agents/deployment/validation/deployment_validator.py +238 -0
  232. claude_mpm/services/agents/deployment/validation/template_validator.py +299 -0
  233. claude_mpm/services/agents/deployment/validation/validation_result.py +226 -0
  234. claude_mpm/services/agents/loading/__init__.py +2 -2
  235. claude_mpm/services/agents/loading/agent_profile_loader.py +259 -229
  236. claude_mpm/services/agents/loading/base_agent_manager.py +90 -81
  237. claude_mpm/services/agents/loading/framework_agent_loader.py +154 -129
  238. claude_mpm/services/agents/management/__init__.py +2 -2
  239. claude_mpm/services/agents/management/agent_capabilities_generator.py +72 -58
  240. claude_mpm/services/agents/management/agent_management_service.py +209 -156
  241. claude_mpm/services/agents/memory/__init__.py +9 -6
  242. claude_mpm/services/agents/memory/agent_memory_manager.py +218 -1152
  243. claude_mpm/services/agents/memory/agent_persistence_service.py +20 -16
  244. claude_mpm/services/agents/memory/analyzer.py +430 -0
  245. claude_mpm/services/agents/memory/content_manager.py +376 -0
  246. claude_mpm/services/agents/memory/template_generator.py +468 -0
  247. claude_mpm/services/agents/registry/__init__.py +7 -10
  248. claude_mpm/services/agents/registry/deployed_agent_discovery.py +122 -97
  249. claude_mpm/services/agents/registry/modification_tracker.py +351 -285
  250. claude_mpm/services/async_session_logger.py +187 -153
  251. claude_mpm/services/claude_session_logger.py +87 -72
  252. claude_mpm/services/command_handler_service.py +217 -0
  253. claude_mpm/services/communication/__init__.py +3 -2
  254. claude_mpm/services/core/__init__.py +50 -97
  255. claude_mpm/services/core/base.py +60 -53
  256. claude_mpm/services/core/interfaces/__init__.py +188 -0
  257. claude_mpm/services/core/interfaces/agent.py +351 -0
  258. claude_mpm/services/core/interfaces/communication.py +343 -0
  259. claude_mpm/services/core/interfaces/infrastructure.py +413 -0
  260. claude_mpm/services/core/interfaces/service.py +434 -0
  261. claude_mpm/services/core/interfaces.py +19 -944
  262. claude_mpm/services/event_aggregator.py +208 -170
  263. claude_mpm/services/exceptions.py +387 -308
  264. claude_mpm/services/framework_claude_md_generator/__init__.py +75 -79
  265. claude_mpm/services/framework_claude_md_generator/content_assembler.py +69 -60
  266. claude_mpm/services/framework_claude_md_generator/content_validator.py +65 -61
  267. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +68 -49
  268. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +34 -34
  269. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +25 -22
  270. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +10 -10
  271. claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +4 -3
  272. claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +4 -3
  273. claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +4 -3
  274. claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +6 -5
  275. claude_mpm/services/framework_claude_md_generator/section_generators/header.py +8 -7
  276. claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +4 -3
  277. claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +6 -5
  278. claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +9 -8
  279. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +4 -3
  280. claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +5 -4
  281. claude_mpm/services/framework_claude_md_generator/section_manager.py +28 -27
  282. claude_mpm/services/framework_claude_md_generator/version_manager.py +30 -28
  283. claude_mpm/services/hook_service.py +106 -114
  284. claude_mpm/services/infrastructure/__init__.py +7 -5
  285. claude_mpm/services/infrastructure/context_preservation.py +233 -199
  286. claude_mpm/services/infrastructure/daemon_manager.py +279 -0
  287. claude_mpm/services/infrastructure/logging.py +83 -76
  288. claude_mpm/services/infrastructure/monitoring.py +547 -404
  289. claude_mpm/services/mcp_gateway/__init__.py +30 -13
  290. claude_mpm/services/mcp_gateway/config/__init__.py +2 -2
  291. claude_mpm/services/mcp_gateway/config/config_loader.py +61 -56
  292. claude_mpm/services/mcp_gateway/config/config_schema.py +50 -41
  293. claude_mpm/services/mcp_gateway/config/configuration.py +82 -75
  294. claude_mpm/services/mcp_gateway/core/__init__.py +13 -20
  295. claude_mpm/services/mcp_gateway/core/base.py +80 -67
  296. claude_mpm/services/mcp_gateway/core/exceptions.py +60 -46
  297. claude_mpm/services/mcp_gateway/core/interfaces.py +87 -84
  298. claude_mpm/services/mcp_gateway/main.py +287 -137
  299. claude_mpm/services/mcp_gateway/registry/__init__.py +1 -1
  300. claude_mpm/services/mcp_gateway/registry/service_registry.py +97 -94
  301. claude_mpm/services/mcp_gateway/registry/tool_registry.py +135 -126
  302. claude_mpm/services/mcp_gateway/server/__init__.py +2 -2
  303. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +105 -110
  304. claude_mpm/services/mcp_gateway/server/stdio_handler.py +105 -107
  305. claude_mpm/services/mcp_gateway/server/stdio_server.py +691 -0
  306. claude_mpm/services/mcp_gateway/tools/__init__.py +4 -2
  307. claude_mpm/services/mcp_gateway/tools/base_adapter.py +109 -119
  308. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +283 -215
  309. claude_mpm/services/mcp_gateway/tools/hello_world.py +122 -120
  310. claude_mpm/services/mcp_gateway/tools/ticket_tools.py +652 -0
  311. claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +606 -0
  312. claude_mpm/services/memory/__init__.py +2 -2
  313. claude_mpm/services/memory/builder.py +451 -362
  314. claude_mpm/services/memory/cache/__init__.py +2 -2
  315. claude_mpm/services/memory/cache/shared_prompt_cache.py +232 -194
  316. claude_mpm/services/memory/cache/simple_cache.py +107 -93
  317. claude_mpm/services/memory/indexed_memory.py +195 -193
  318. claude_mpm/services/memory/optimizer.py +267 -234
  319. claude_mpm/services/memory/router.py +571 -263
  320. claude_mpm/services/memory_hook_service.py +237 -0
  321. claude_mpm/services/port_manager.py +223 -0
  322. claude_mpm/services/project/__init__.py +3 -3
  323. claude_mpm/services/project/analyzer.py +451 -305
  324. claude_mpm/services/project/registry.py +262 -240
  325. claude_mpm/services/recovery_manager.py +287 -231
  326. claude_mpm/services/response_tracker.py +87 -67
  327. claude_mpm/services/runner_configuration_service.py +587 -0
  328. claude_mpm/services/session_management_service.py +304 -0
  329. claude_mpm/services/socketio/__init__.py +4 -4
  330. claude_mpm/services/socketio/client_proxy.py +174 -0
  331. claude_mpm/services/socketio/handlers/__init__.py +3 -3
  332. claude_mpm/services/socketio/handlers/base.py +44 -30
  333. claude_mpm/services/socketio/handlers/connection.py +145 -65
  334. claude_mpm/services/socketio/handlers/file.py +123 -108
  335. claude_mpm/services/socketio/handlers/git.py +607 -373
  336. claude_mpm/services/socketio/handlers/hook.py +170 -0
  337. claude_mpm/services/socketio/handlers/memory.py +4 -4
  338. claude_mpm/services/socketio/handlers/project.py +4 -4
  339. claude_mpm/services/socketio/handlers/registry.py +53 -38
  340. claude_mpm/services/socketio/server/__init__.py +18 -0
  341. claude_mpm/services/socketio/server/broadcaster.py +252 -0
  342. claude_mpm/services/socketio/server/core.py +399 -0
  343. claude_mpm/services/socketio/server/main.py +323 -0
  344. claude_mpm/services/socketio_client_manager.py +160 -133
  345. claude_mpm/services/socketio_server.py +36 -1885
  346. claude_mpm/services/subprocess_launcher_service.py +316 -0
  347. claude_mpm/services/system_instructions_service.py +258 -0
  348. claude_mpm/services/ticket_manager.py +19 -533
  349. claude_mpm/services/utility_service.py +285 -0
  350. claude_mpm/services/version_control/__init__.py +18 -21
  351. claude_mpm/services/version_control/branch_strategy.py +20 -10
  352. claude_mpm/services/version_control/conflict_resolution.py +37 -13
  353. claude_mpm/services/version_control/git_operations.py +52 -21
  354. claude_mpm/services/version_control/semantic_versioning.py +92 -53
  355. claude_mpm/services/version_control/version_parser.py +145 -125
  356. claude_mpm/services/version_service.py +270 -0
  357. claude_mpm/storage/__init__.py +2 -2
  358. claude_mpm/storage/state_storage.py +177 -181
  359. claude_mpm/ticket_wrapper.py +2 -2
  360. claude_mpm/utils/__init__.py +2 -2
  361. claude_mpm/utils/agent_dependency_loader.py +453 -243
  362. claude_mpm/utils/config_manager.py +157 -118
  363. claude_mpm/utils/console.py +1 -1
  364. claude_mpm/utils/dependency_cache.py +102 -107
  365. claude_mpm/utils/dependency_manager.py +52 -47
  366. claude_mpm/utils/dependency_strategies.py +131 -96
  367. claude_mpm/utils/environment_context.py +110 -102
  368. claude_mpm/utils/error_handler.py +75 -55
  369. claude_mpm/utils/file_utils.py +80 -67
  370. claude_mpm/utils/framework_detection.py +12 -11
  371. claude_mpm/utils/import_migration_example.py +12 -60
  372. claude_mpm/utils/imports.py +48 -45
  373. claude_mpm/utils/path_operations.py +100 -93
  374. claude_mpm/utils/robust_installer.py +172 -164
  375. claude_mpm/utils/session_logging.py +30 -23
  376. claude_mpm/utils/subprocess_utils.py +99 -61
  377. claude_mpm/validation/__init__.py +1 -1
  378. claude_mpm/validation/agent_validator.py +151 -111
  379. claude_mpm/validation/frontmatter_validator.py +92 -71
  380. {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/METADATA +27 -1
  381. claude_mpm-4.0.3.dist-info/RECORD +402 -0
  382. {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/entry_points.txt +1 -0
  383. {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/licenses/LICENSE +1 -1
  384. claude_mpm/cli/commands/run_guarded.py +0 -511
  385. claude_mpm/config/memory_guardian_config.py +0 -325
  386. claude_mpm/config/memory_guardian_yaml.py +0 -335
  387. claude_mpm/core/config_paths.py +0 -150
  388. claude_mpm/core/memory_aware_runner.py +0 -353
  389. claude_mpm/dashboard/static/js/dashboard-original.js +0 -4134
  390. claude_mpm/deployment_paths.py +0 -261
  391. claude_mpm/hooks/claude_hooks/hook_handler_fixed.py +0 -454
  392. claude_mpm/models/state_models.py +0 -433
  393. claude_mpm/services/agent/__init__.py +0 -24
  394. claude_mpm/services/agent/deployment.py +0 -2548
  395. claude_mpm/services/agent/management.py +0 -598
  396. claude_mpm/services/agent/registry.py +0 -813
  397. claude_mpm/services/agents/registry/agent_registry.py +0 -813
  398. claude_mpm/services/communication/socketio.py +0 -1935
  399. claude_mpm/services/communication/websocket.py +0 -479
  400. claude_mpm/services/framework_claude_md_generator.py +0 -624
  401. claude_mpm/services/health_monitor.py +0 -893
  402. claude_mpm/services/infrastructure/graceful_degradation.py +0 -616
  403. claude_mpm/services/infrastructure/health_monitor.py +0 -775
  404. claude_mpm/services/infrastructure/memory_dashboard.py +0 -479
  405. claude_mpm/services/infrastructure/memory_guardian.py +0 -944
  406. claude_mpm/services/infrastructure/restart_protection.py +0 -642
  407. claude_mpm/services/infrastructure/state_manager.py +0 -774
  408. claude_mpm/services/mcp_gateway/manager.py +0 -334
  409. claude_mpm/services/optimized_hook_service.py +0 -542
  410. claude_mpm/services/project_analyzer.py +0 -864
  411. claude_mpm/services/project_registry.py +0 -608
  412. claude_mpm/services/standalone_socketio_server.py +0 -1300
  413. claude_mpm/services/ticket_manager_di.py +0 -318
  414. claude_mpm/services/ticketing_service_original.py +0 -510
  415. claude_mpm/utils/paths.py +0 -395
  416. claude_mpm/utils/platform_memory.py +0 -524
  417. claude_mpm-3.9.11.dist-info/RECORD +0 -306
  418. {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/WHEEL +0 -0
  419. {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 a unified system for loading and managing AI agent configurations
7
- from JSON template files. It serves as the central registry for all agent types in the
8
- Claude MPM system, handling discovery, validation, caching, and dynamic model selection.
9
-
10
- Architecture Overview:
11
- ----------------------
12
- The agent loader follows a plugin-like architecture where agents are discovered from
13
- JSON template files in a designated directory. Each agent is validated against a
14
- standardized schema before being registered for use.
15
-
16
- Key Features:
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 ..core.config_paths import ConfigPaths
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 AgentTier(Enum):
97
- """Agent precedence tiers."""
98
- PROJECT = "project" # Highest precedence - project-specific agents
99
- USER = "user" # User-level agents from ~/.claude-mpm
100
- SYSTEM = "system" # Lowest precedence - framework built-in agents
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() / ConfigPaths.CONFIG_DIR / "agents"
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 = ConfigPaths.get_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", # Fast, cost-effective
180
- ModelType.SONNET: "claude-sonnet-4-20250514", # Balanced performance
181
- ModelType.OPUS: "claude-opus-4-20250514" # Maximum capability
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
- Central registry for loading and managing agent configurations.
188
-
189
- This class implements the core agent discovery and management system. It:
190
- 1. Discovers agent JSON files from the agents directory
191
- 2. Validates each agent against the standardized schema
192
- 3. Maintains an in-memory registry of valid agents
193
- 4. Provides caching for performance optimization
194
- 5. Supports dynamic agent reloading
195
-
196
- METRICS COLLECTION OPPORTUNITIES:
197
- - Agent load times and cache hit rates
198
- - Validation performance by agent type
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 and discover available agents.
218
-
176
+ Initialize the agent loader with the registry.
177
+
219
178
  The initialization process:
220
- 1. Creates validator for schema checking
221
- 2. Gets shared cache instance for performance
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
- self._load_agents()
270
- self._metrics['initialization_time_ms'] = (time.time() - start_time) * 1000
271
- logger.debug(f"Agent loader initialized in {self._metrics['initialization_time_ms']:.2f}ms")
272
-
273
- def _load_agents(self, use_async: bool = True) -> None:
274
- """
275
- Discover and load all valid agents from all tier directories.
276
-
277
- This method implements the agent discovery mechanism with tier precedence:
278
- 1. Scans each tier directory (PROJECT → USER → SYSTEM)
279
- 2. Loads and validates each agent file
280
- 3. Registers agents with precedence (PROJECT overrides USER overrides SYSTEM)
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: Unique identifier for the agent (e.g., "research_agent")
491
-
200
+ agent_id: Agent identifier
201
+
492
202
  Returns:
493
- Dict containing the full agent configuration, or None if not found
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
- agent_data = self._agent_registry.get(agent_id)
499
- if agent_data and agent_id in self._agent_tiers:
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 containing key metadata fields
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
- agents = []
520
- for agent_id, agent_data in self._agent_registry.items():
521
- # Extract key fields from nested structure for easy consumption
522
- agents.append({
523
- "id": agent_id,
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 with caching support.
535
-
220
+ Retrieve agent instructions/prompt by ID.
221
+
536
222
  Args:
537
- agent_id: Unique identifier for the agent
538
- force_reload: If True, bypass cache and reload from registry
539
-
223
+ agent_id: Agent identifier
224
+ force_reload: Ignored (kept for API compatibility)
225
+
540
226
  Returns:
541
- The agent's instruction prompt, or None if not found
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
- cache_key = f"{AGENT_CACHE_PREFIX}{agent_id}"
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 from the agent configuration
232
+
233
+ # Extract instructions
587
234
  instructions = agent_data.get("instructions", "")
588
235
  if not instructions:
589
- logger.warning(f"No instructions found for agent: {agent_id}")
236
+ logger.warning(f"Agent '{agent_id}' has no instructions")
590
237
  return None
591
-
592
- # METRICS: Track prompt size for memory analysis
593
- self._metrics['prompt_sizes'][agent_id] = len(instructions)
594
-
595
- # METRICS: Record load time
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: Unique identifier for the agent
667
-
248
+ agent_id: Agent identifier
249
+
668
250
  Returns:
669
- Dictionary containing all agent metadata except instructions,
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
- return {
685
- "id": agent_id,
686
- "version": agent_data.get("version", "1.0.0"),
687
- "metadata": agent_data.get("metadata", {}), # Name, description, category
688
- "capabilities": agent_data.get("capabilities", {}), # Model, tools, features
689
- "knowledge": agent_data.get("knowledge", {}), # Domain expertise
690
- "interactions": agent_data.get("interactions", {}) # User interaction patterns
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 - singleton pattern for consistent state
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 single global instance
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
- def load_agent_prompt_from_md(agent_name: str, force_reload: bool = False) -> Optional[str]:
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(task_description: str, context_size: int = 0, **kwargs: Any) -> Dict[str, Any]:
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(agent_name: str, complexity_analysis: Optional[Dict[str, Any]] = None) -> Tuple[str, Dict[str, Any]]:
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("model", "claude-sonnet-4-20250514")
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 = os.getenv('ENABLE_DYNAMIC_MODEL_SELECTION', 'true').lower() == 'true'
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, '').lower()
837
-
838
- if agent_override == 'true':
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 == 'false':
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('recommended_model', ModelType.SONNET)
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('complexity_score', 50)
850
- if hasattr(loader, '_metrics'):
851
- loader._metrics['complexity_scores'].append(complexity_score)
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['complexity_scores']) > 1000:
854
- loader._metrics['complexity_scores'] = loader._metrics['complexity_scores'][-1000:]
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('complexity_level', ComplexityLevel.MEDIUM),
860
- "optimal_prompt_size": complexity_analysis.get('optimal_prompt_size', (700, 1000)),
861
- "default_model": default_model
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" if not enable_dynamic_selection else "no_complexity_analysis",
869
- "default_model": default_model
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, '_metrics'):
875
- loader._metrics['model_selections'][selected_model] = \
876
- loader._metrics['model_selections'].get(selected_model, 0) + 1
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(agent_name: str, force_reload: bool = False, return_model_info: bool = False, **kwargs: Any) -> Union[str, Tuple[str, str, Dict[str, Any]]]:
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(f"No agent found with name: {agent_name} (normalized to: {actual_agent_id})")
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('task_description', '')
974
- enable_analysis = kwargs.get('enable_complexity_analysis', True)
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 = {k: v for k, v in kwargs.items()
979
- if k not in ['task_description', 'context_size', 'enable_complexity_analysis']}
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('context_size', 0),
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(actual_agent_id, complexity_analysis)
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 selected_model and model_config.get('selection_method') == 'dynamic_complexity_based':
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('complexity_score', 50) if model_config else 50
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
- # Backward-compatible functions
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 get_ops_agent_prompt() -> str:
1073
- """
1074
- Get the complete Ops Agent prompt with base instructions.
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(agent_name, force_reload, return_model_info=True, **kwargs)
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
- def list_available_agents() -> Dict[str, Dict[str, Any]]:
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
- def list_agents_by_tier() -> Dict[str, List[str]]:
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
- def validate_agent_files() -> Dict[str, Dict[str, Any]]:
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
- def get_agent_tier(agent_name: str) -> Optional[str]:
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