claude-mpm 5.4.85__py3-none-any.whl → 5.6.1__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 (254) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +8 -5
  3. claude_mpm/agents/{CLAUDE_MPM_FOUNDERS_OUTPUT_STYLE.md → CLAUDE_MPM_RESEARCH_OUTPUT_STYLE.md} +14 -6
  4. claude_mpm/agents/PM_INSTRUCTIONS.md +101 -703
  5. claude_mpm/agents/WORKFLOW.md +2 -0
  6. claude_mpm/agents/templates/circuit-breakers.md +26 -17
  7. claude_mpm/cli/commands/autotodos.py +566 -0
  8. claude_mpm/cli/commands/commander.py +46 -0
  9. claude_mpm/cli/commands/hook_errors.py +60 -60
  10. claude_mpm/cli/commands/monitor.py +2 -2
  11. claude_mpm/cli/commands/mpm_init/core.py +2 -2
  12. claude_mpm/cli/commands/run.py +35 -3
  13. claude_mpm/cli/executor.py +119 -16
  14. claude_mpm/cli/parsers/base_parser.py +71 -1
  15. claude_mpm/cli/parsers/commander_parser.py +83 -0
  16. claude_mpm/cli/parsers/run_parser.py +10 -0
  17. claude_mpm/cli/startup.py +54 -16
  18. claude_mpm/cli/startup_display.py +72 -5
  19. claude_mpm/cli/startup_logging.py +2 -2
  20. claude_mpm/cli/utils.py +7 -3
  21. claude_mpm/commander/__init__.py +72 -0
  22. claude_mpm/commander/adapters/__init__.py +31 -0
  23. claude_mpm/commander/adapters/base.py +191 -0
  24. claude_mpm/commander/adapters/claude_code.py +361 -0
  25. claude_mpm/commander/adapters/communication.py +366 -0
  26. claude_mpm/commander/api/__init__.py +16 -0
  27. claude_mpm/commander/api/app.py +105 -0
  28. claude_mpm/commander/api/errors.py +112 -0
  29. claude_mpm/commander/api/routes/__init__.py +8 -0
  30. claude_mpm/commander/api/routes/events.py +184 -0
  31. claude_mpm/commander/api/routes/inbox.py +171 -0
  32. claude_mpm/commander/api/routes/messages.py +148 -0
  33. claude_mpm/commander/api/routes/projects.py +271 -0
  34. claude_mpm/commander/api/routes/sessions.py +215 -0
  35. claude_mpm/commander/api/routes/work.py +260 -0
  36. claude_mpm/commander/api/schemas.py +182 -0
  37. claude_mpm/commander/chat/__init__.py +7 -0
  38. claude_mpm/commander/chat/cli.py +107 -0
  39. claude_mpm/commander/chat/commands.py +96 -0
  40. claude_mpm/commander/chat/repl.py +310 -0
  41. claude_mpm/commander/config.py +49 -0
  42. claude_mpm/commander/config_loader.py +115 -0
  43. claude_mpm/commander/daemon.py +398 -0
  44. claude_mpm/commander/events/__init__.py +26 -0
  45. claude_mpm/commander/events/manager.py +332 -0
  46. claude_mpm/commander/frameworks/__init__.py +12 -0
  47. claude_mpm/commander/frameworks/base.py +143 -0
  48. claude_mpm/commander/frameworks/claude_code.py +58 -0
  49. claude_mpm/commander/frameworks/mpm.py +62 -0
  50. claude_mpm/commander/inbox/__init__.py +16 -0
  51. claude_mpm/commander/inbox/dedup.py +128 -0
  52. claude_mpm/commander/inbox/inbox.py +224 -0
  53. claude_mpm/commander/inbox/models.py +70 -0
  54. claude_mpm/commander/instance_manager.py +337 -0
  55. claude_mpm/commander/llm/__init__.py +6 -0
  56. claude_mpm/commander/llm/openrouter_client.py +167 -0
  57. claude_mpm/commander/llm/summarizer.py +70 -0
  58. claude_mpm/commander/models/__init__.py +18 -0
  59. claude_mpm/commander/models/events.py +121 -0
  60. claude_mpm/commander/models/project.py +162 -0
  61. claude_mpm/commander/models/work.py +214 -0
  62. claude_mpm/commander/parsing/__init__.py +20 -0
  63. claude_mpm/commander/parsing/extractor.py +132 -0
  64. claude_mpm/commander/parsing/output_parser.py +270 -0
  65. claude_mpm/commander/parsing/patterns.py +100 -0
  66. claude_mpm/commander/persistence/__init__.py +11 -0
  67. claude_mpm/commander/persistence/event_store.py +274 -0
  68. claude_mpm/commander/persistence/state_store.py +309 -0
  69. claude_mpm/commander/persistence/work_store.py +164 -0
  70. claude_mpm/commander/polling/__init__.py +13 -0
  71. claude_mpm/commander/polling/event_detector.py +104 -0
  72. claude_mpm/commander/polling/output_buffer.py +49 -0
  73. claude_mpm/commander/polling/output_poller.py +153 -0
  74. claude_mpm/commander/project_session.py +268 -0
  75. claude_mpm/commander/proxy/__init__.py +12 -0
  76. claude_mpm/commander/proxy/formatter.py +89 -0
  77. claude_mpm/commander/proxy/output_handler.py +191 -0
  78. claude_mpm/commander/proxy/relay.py +155 -0
  79. claude_mpm/commander/registry.py +404 -0
  80. claude_mpm/commander/runtime/__init__.py +10 -0
  81. claude_mpm/commander/runtime/executor.py +191 -0
  82. claude_mpm/commander/runtime/monitor.py +316 -0
  83. claude_mpm/commander/session/__init__.py +6 -0
  84. claude_mpm/commander/session/context.py +81 -0
  85. claude_mpm/commander/session/manager.py +59 -0
  86. claude_mpm/commander/tmux_orchestrator.py +361 -0
  87. claude_mpm/commander/web/__init__.py +1 -0
  88. claude_mpm/commander/work/__init__.py +30 -0
  89. claude_mpm/commander/work/executor.py +189 -0
  90. claude_mpm/commander/work/queue.py +405 -0
  91. claude_mpm/commander/workflow/__init__.py +27 -0
  92. claude_mpm/commander/workflow/event_handler.py +219 -0
  93. claude_mpm/commander/workflow/notifier.py +146 -0
  94. claude_mpm/commands/mpm-config.md +8 -0
  95. claude_mpm/commands/mpm-doctor.md +8 -0
  96. claude_mpm/commands/mpm-help.md +8 -0
  97. claude_mpm/commands/mpm-init.md +8 -0
  98. claude_mpm/commands/mpm-monitor.md +8 -0
  99. claude_mpm/commands/mpm-organize.md +8 -0
  100. claude_mpm/commands/mpm-postmortem.md +8 -0
  101. claude_mpm/commands/mpm-session-resume.md +9 -1
  102. claude_mpm/commands/mpm-status.md +8 -0
  103. claude_mpm/commands/mpm-ticket-view.md +8 -0
  104. claude_mpm/commands/mpm-version.md +8 -0
  105. claude_mpm/commands/mpm.md +8 -0
  106. claude_mpm/config/agent_presets.py +8 -7
  107. claude_mpm/core/config.py +5 -0
  108. claude_mpm/core/hook_manager.py +51 -3
  109. claude_mpm/core/logger.py +10 -7
  110. claude_mpm/core/logging_utils.py +4 -2
  111. claude_mpm/core/output_style_manager.py +15 -5
  112. claude_mpm/core/unified_config.py +10 -6
  113. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.C33zOoyM.css +1 -0
  114. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.CW1J-YuA.css +1 -0
  115. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cs_tUR18.js → 1WZnGYqX.js} +1 -1
  116. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CDuw-vjf.js → 67pF3qNn.js} +1 -1
  117. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bTOqqlTd.js → 6RxdMKe4.js} +1 -1
  118. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DwBR2MJi.js → 8cZrfX0h.js} +1 -1
  119. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{ZGh7QtNv.js → 9a6T2nm-.js} +1 -1
  120. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D9lljYKQ.js → B443AUzu.js} +1 -1
  121. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{RJiighC3.js → B8AwtY2H.js} +1 -1
  122. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{uuIeMWc-.js → BF15LAsF.js} +1 -1
  123. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D3k0OPJN.js → BRcwIQNr.js} +1 -1
  124. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CyWMqx4W.js → BV6nKitt.js} +1 -1
  125. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CiIAseT4.js → BViJ8lZt.js} +5 -5
  126. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CBBdVcY8.js → BcQ-Q0FE.js} +1 -1
  127. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BovzEFCE.js → Bpyvgze_.js} +1 -1
  128. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BzTRqg-z.js +1 -0
  129. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C0Fr8dve.js +1 -0
  130. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{eNVUfhuA.js → C3rbW_a-.js} +1 -1
  131. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{GYwsonyD.js → C8WYN38h.js} +1 -1
  132. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BIF9m_hv.js → C9I8FlXH.js} +1 -1
  133. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B0uc0UOD.js → CIQcWgO2.js} +3 -3
  134. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Be7GpZd6.js → CIctN7YN.js} +1 -1
  135. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Bh0LDWpI.js → CKrS_JZW.js} +2 -2
  136. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DUrLdbGD.js → CR6P9C4A.js} +1 -1
  137. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7xVLGWV.js → CRRR9MD_.js} +1 -1
  138. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
  139. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dhb8PKl3.js → CSXtMOf0.js} +1 -1
  140. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BPYeabCQ.js → CT-sbxSk.js} +1 -1
  141. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{sQeU3Y1z.js → CWm6DJsp.js} +1 -1
  142. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CnA0NrzZ.js → CpqQ1Kzn.js} +1 -1
  143. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4B-KCzX.js → D2nGpDRe.js} +1 -1
  144. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DGkLK5U1.js → D9iCMida.js} +1 -1
  145. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BofRWZRR.js → D9ykgMoY.js} +1 -1
  146. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DmxopI1J.js → DL2Ldur1.js} +1 -1
  147. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C30mlcqg.js → DPfltzjH.js} +1 -1
  148. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Vzk33B_K.js → DR8nis88.js} +2 -2
  149. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DI7hHRFL.js → DUliQN2b.js} +1 -1
  150. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4JcI4KD.js → DXlhR01x.js} +1 -1
  151. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bT1r9zLR.js → D_lyTybS.js} +1 -1
  152. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DZX00Y4g.js → DngoTTgh.js} +1 -1
  153. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzZX-COe.js → DqkmHtDC.js} +1 -1
  154. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7RN905-.js → DsDh8EYs.js} +1 -1
  155. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DLVjFsZ3.js → DypDmXgd.js} +1 -1
  156. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{iEWssX7S.js → IPYC-LnN.js} +1 -1
  157. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JTLiF7dt.js +24 -0
  158. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DaimHw_p.js → JpevfAFt.js} +1 -1
  159. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DY1XQ8fi.js → R8CEIRAd.js} +1 -1
  160. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dle-35c7.js → Zxy7qc-l.js} +2 -2
  161. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
  162. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C_Usid8X.js → qtd3IeO4.js} +2 -2
  163. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzeYkLYB.js → ulBFON_C.js} +2 -2
  164. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cfqx1Qun.js → wQVh1CoA.js} +1 -1
  165. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/{app.D6-I5TpK.js → app.Dr7t0z2J.js} +2 -2
  166. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
  167. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.m1gL8KXf.js → 0.RgBboRvH.js} +1 -1
  168. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{1.CgNOuw-d.js → 1.DG-KkbDf.js} +1 -1
  169. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.D_jnf-x6.js +1 -0
  170. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -1
  171. claude_mpm/dashboard/static/svelte-build/index.html +9 -9
  172. claude_mpm/experimental/cli_enhancements.py +2 -1
  173. claude_mpm/hooks/claude_hooks/INTEGRATION_EXAMPLE.md +243 -0
  174. claude_mpm/hooks/claude_hooks/README_AUTO_PAUSE.md +403 -0
  175. claude_mpm/hooks/claude_hooks/auto_pause_handler.py +486 -0
  176. claude_mpm/hooks/claude_hooks/event_handlers.py +250 -11
  177. claude_mpm/hooks/claude_hooks/hook_handler.py +106 -89
  178. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
  179. claude_mpm/hooks/claude_hooks/installer.py +69 -5
  180. claude_mpm/hooks/claude_hooks/response_tracking.py +3 -1
  181. claude_mpm/hooks/claude_hooks/services/connection_manager.py +20 -0
  182. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +14 -77
  183. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +30 -6
  184. claude_mpm/hooks/session_resume_hook.py +85 -1
  185. claude_mpm/init.py +1 -1
  186. claude_mpm/scripts/claude-hook-handler.sh +36 -10
  187. claude_mpm/services/agents/agent_recommendation_service.py +8 -8
  188. claude_mpm/services/agents/cache_git_manager.py +1 -1
  189. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +3 -0
  190. claude_mpm/services/agents/loading/framework_agent_loader.py +75 -2
  191. claude_mpm/services/cli/__init__.py +3 -0
  192. claude_mpm/services/cli/incremental_pause_manager.py +561 -0
  193. claude_mpm/services/cli/session_resume_helper.py +10 -2
  194. claude_mpm/services/delegation_detector.py +175 -0
  195. claude_mpm/services/diagnostics/checks/agent_sources_check.py +30 -0
  196. claude_mpm/services/diagnostics/checks/configuration_check.py +24 -0
  197. claude_mpm/services/diagnostics/checks/installation_check.py +22 -0
  198. claude_mpm/services/diagnostics/checks/mcp_services_check.py +23 -0
  199. claude_mpm/services/diagnostics/doctor_reporter.py +31 -1
  200. claude_mpm/services/diagnostics/models.py +14 -1
  201. claude_mpm/services/event_log.py +325 -0
  202. claude_mpm/services/infrastructure/__init__.py +4 -0
  203. claude_mpm/services/infrastructure/context_usage_tracker.py +291 -0
  204. claude_mpm/services/infrastructure/resume_log_generator.py +24 -5
  205. claude_mpm/services/monitor/daemon_manager.py +15 -4
  206. claude_mpm/services/monitor/management/lifecycle.py +8 -2
  207. claude_mpm/services/monitor/server.py +106 -16
  208. claude_mpm/services/pm_skills_deployer.py +259 -87
  209. claude_mpm/services/skills/git_skill_source_manager.py +51 -2
  210. claude_mpm/services/skills/selective_skill_deployer.py +114 -16
  211. claude_mpm/services/skills/skill_discovery_service.py +57 -3
  212. claude_mpm/services/socketio/handlers/hook.py +14 -7
  213. claude_mpm/services/socketio/server/main.py +12 -4
  214. claude_mpm/skills/bundled/pm/mpm/SKILL.md +38 -0
  215. claude_mpm/skills/bundled/pm/mpm-agent-update-workflow/SKILL.md +75 -0
  216. claude_mpm/skills/bundled/pm/mpm-circuit-breaker-enforcement/SKILL.md +476 -0
  217. claude_mpm/skills/bundled/pm/mpm-config/SKILL.md +29 -0
  218. claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
  219. claude_mpm/skills/bundled/pm/mpm-help/SKILL.md +35 -0
  220. claude_mpm/skills/bundled/pm/mpm-init/SKILL.md +125 -0
  221. claude_mpm/skills/bundled/pm/mpm-monitor/SKILL.md +32 -0
  222. claude_mpm/skills/bundled/pm/mpm-organize/SKILL.md +121 -0
  223. claude_mpm/skills/bundled/pm/mpm-postmortem/SKILL.md +22 -0
  224. claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
  225. claude_mpm/skills/bundled/pm/mpm-session-resume/SKILL.md +31 -0
  226. claude_mpm/skills/bundled/pm/mpm-status/SKILL.md +37 -0
  227. claude_mpm/skills/bundled/pm/{pm-teaching-mode → mpm-teaching-mode}/SKILL.md +2 -2
  228. claude_mpm/skills/bundled/pm/mpm-ticket-view/SKILL.md +110 -0
  229. claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
  230. claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
  231. claude_mpm/skills/skill_manager.py +4 -4
  232. claude_mpm-5.6.1.dist-info/METADATA +391 -0
  233. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/RECORD +244 -145
  234. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +0 -1
  235. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +0 -1
  236. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +0 -1
  237. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +0 -24
  238. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +0 -1
  239. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +0 -1
  240. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +0 -323
  241. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +0 -1
  242. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +0 -1
  243. claude_mpm-5.4.85.dist-info/METADATA +0 -1023
  244. /claude_mpm/skills/bundled/pm/{pm-bug-reporting/pm-bug-reporting.md → mpm-bug-reporting/SKILL.md} +0 -0
  245. /claude_mpm/skills/bundled/pm/{pm-delegation-patterns → mpm-delegation-patterns}/SKILL.md +0 -0
  246. /claude_mpm/skills/bundled/pm/{pm-git-file-tracking → mpm-git-file-tracking}/SKILL.md +0 -0
  247. /claude_mpm/skills/bundled/pm/{pm-pr-workflow → mpm-pr-workflow}/SKILL.md +0 -0
  248. /claude_mpm/skills/bundled/pm/{pm-ticketing-integration → mpm-ticketing-integration}/SKILL.md +0 -0
  249. /claude_mpm/skills/bundled/pm/{pm-verification-protocols → mpm-verification-protocols}/SKILL.md +0 -0
  250. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/WHEEL +0 -0
  251. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/entry_points.txt +0 -0
  252. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/licenses/LICENSE +0 -0
  253. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  254. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/top_level.txt +0 -0
@@ -7,9 +7,14 @@ This service manages:
7
7
  DESIGN DECISION: Use stateless HTTP POST instead of persistent SocketIO
8
8
  connections because hook handlers are ephemeral processes (< 1 second lifetime).
9
9
  This eliminates disconnection issues and matches the process lifecycle.
10
+
11
+ DESIGN DECISION: Synchronous HTTP POST only (no async)
12
+ Hook handlers are too short-lived (~25ms lifecycle) to benefit from async.
13
+ Using asyncio.run() creates event loops that close before HTTP operations complete,
14
+ causing "Event loop is closed" errors. Synchronous HTTP POST in a thread pool
15
+ is simpler and more reliable for ephemeral processes.
10
16
  """
11
17
 
12
- import asyncio
13
18
  import os
14
19
  import sys
15
20
  from concurrent.futures import ThreadPoolExecutor
@@ -27,9 +32,6 @@ except ImportError:
27
32
  REQUESTS_AVAILABLE = False
28
33
  requests = None
29
34
 
30
- # Import high-performance event emitter - lazy loaded in _async_emit()
31
- # to reduce hook handler initialization time by ~85% (792ms -> minimal)
32
-
33
35
  # Import EventNormalizer for consistent event formatting
34
36
  try:
35
37
  from claude_mpm.services.socketio.event_normalizer import EventNormalizer
@@ -55,10 +57,6 @@ except ImportError:
55
57
  )
56
58
 
57
59
 
58
- # EventBus removed - using direct HTTP POST only
59
- # This eliminates duplicate events and simplifies the architecture
60
-
61
-
62
60
  class ConnectionManagerService:
63
61
  """Manages connections for the Claude hook handler using HTTP POST."""
64
62
 
@@ -72,17 +70,9 @@ class ConnectionManagerService:
72
70
  self.server_port = int(os.environ.get("CLAUDE_MPM_SERVER_PORT", "8765"))
73
71
  self.http_endpoint = f"http://{self.server_host}:{self.server_port}/api/events"
74
72
 
75
- # EventBus removed - using direct HTTP POST only
76
-
77
- # For backward compatibility with tests
78
- self.connection_pool = None # No longer used
79
-
80
- # Track async emit tasks to prevent garbage collection
81
- self._emit_tasks: set = set()
82
-
83
73
  # Thread pool for non-blocking HTTP requests
84
74
  # WHY: Prevents HTTP POST from blocking hook processing (2s timeout → 0ms blocking)
85
- # max_workers=2: Sufficient for low-frequency HTTP fallback events
75
+ # max_workers=2: Sufficient for low-frequency hook events
86
76
  self._http_executor = ThreadPoolExecutor(
87
77
  max_workers=2, thread_name_prefix="http-emit"
88
78
  )
@@ -94,13 +84,13 @@ class ConnectionManagerService:
94
84
  )
95
85
 
96
86
  def emit_event(self, namespace: str, event: str, data: dict):
97
- """Emit event using high-performance async emitter with HTTP fallback.
87
+ """Emit event using HTTP POST.
98
88
 
99
- WHY Hybrid approach:
100
- - Direct async calls for ultra-low latency in-process events
101
- - HTTP POST fallback for cross-process communication
102
- - Connection pooling for memory protection
103
- - Automatic routing based on availability
89
+ WHY HTTP POST only:
90
+ - Hook handlers are ephemeral (~25ms lifecycle)
91
+ - Async emission causes "Event loop is closed" errors
92
+ - HTTP POST in thread pool is simpler and more reliable
93
+ - Completes in 20-50ms, which is acceptable for hook handlers
104
94
  """
105
95
  # Create event data for normalization
106
96
  raw_event = {
@@ -132,62 +122,9 @@ class ConnectionManagerService:
132
122
  file=sys.stderr,
133
123
  )
134
124
 
135
- # Try high-performance async emitter first (direct calls)
136
- success = self._try_async_emit(namespace, event, claude_event_data)
137
- if success:
138
- return
139
-
140
- # Fallback to HTTP POST for cross-process communication
125
+ # Emit via HTTP POST (non-blocking, runs in thread pool)
141
126
  self._try_http_emit(namespace, event, claude_event_data)
142
127
 
143
- def _try_async_emit(self, namespace: str, event: str, data: dict) -> bool:
144
- """Try to emit event using high-performance async emitter."""
145
- try:
146
- # Run async emission in the current event loop or create one
147
- loop = None
148
- try:
149
- loop = asyncio.get_running_loop()
150
- except RuntimeError:
151
- # No running loop, create a new one
152
- pass
153
-
154
- if loop:
155
- # We're in an async context, create a task with tracking
156
- task = loop.create_task(self._async_emit(namespace, event, data))
157
- self._emit_tasks.add(task)
158
- task.add_done_callback(self._emit_tasks.discard)
159
- # Don't wait for completion to maintain low latency
160
- if DEBUG:
161
- print(f"✅ Async emit scheduled: {event}", file=sys.stderr)
162
- return True
163
- # No event loop, run synchronously
164
- success = asyncio.run(self._async_emit(namespace, event, data))
165
- if DEBUG and success:
166
- print(f"✅ Async emit successful: {event}", file=sys.stderr)
167
- return success
168
-
169
- except Exception as e:
170
- if DEBUG:
171
- print(f"⚠️ Async emit failed: {e}", file=sys.stderr)
172
- return False
173
-
174
- async def _async_emit(self, namespace: str, event: str, data: dict) -> bool:
175
- """Async helper for event emission."""
176
- try:
177
- # Lazy load event emitter to reduce initialization overhead
178
- from claude_mpm.services.monitor.event_emitter import get_event_emitter
179
-
180
- emitter = await get_event_emitter()
181
- return await emitter.emit_event(namespace, "claude_event", data)
182
- except ImportError:
183
- if DEBUG:
184
- print("⚠️ Event emitter not available", file=sys.stderr)
185
- return False
186
- except Exception as e:
187
- if DEBUG:
188
- print(f"⚠️ Async emitter error: {e}", file=sys.stderr)
189
- return False
190
-
191
128
  def _try_http_emit(self, namespace: str, event: str, data: dict):
192
129
  """Try to emit event using HTTP POST fallback (non-blocking).
193
130
 
@@ -67,7 +67,9 @@ class SubagentResponseProcessor:
67
67
  print(" - No stored sessions in delegation_requests!", file=sys.stderr)
68
68
 
69
69
  # Get agent type and other basic info
70
- agent_type, agent_id, reason = self._extract_basic_info(event, session_id)
70
+ agent_type, agent_id, reason, agent_type_inferred = self._extract_basic_info(
71
+ event, session_id
72
+ )
71
73
 
72
74
  # Always log SubagentStop events for debugging
73
75
  if DEBUG or agent_type != "unknown":
@@ -108,6 +110,7 @@ class SubagentResponseProcessor:
108
110
  working_dir,
109
111
  git_branch,
110
112
  structured_response,
113
+ agent_type_inferred,
111
114
  )
112
115
 
113
116
  # Debug log the processed data
@@ -117,11 +120,20 @@ class SubagentResponseProcessor:
117
120
  file=sys.stderr,
118
121
  )
119
122
 
120
- # Emit to /hook namespace with high priority
121
- self.connection_manager.emit_event("/hook", "subagent_stop", subagent_stop_data)
123
+ # Emit to default namespace (consistent with subagent_start)
124
+ self.connection_manager.emit_event("", "subagent_stop", subagent_stop_data)
125
+
126
+ def _extract_basic_info(
127
+ self, event: dict, session_id: str
128
+ ) -> Tuple[str, str, str, bool]:
129
+ """Extract basic info from the event.
130
+
131
+ Returns:
132
+ Tuple of (agent_type, agent_id, reason, agent_type_inferred)
133
+ - agent_type_inferred is True when defaulted to "pm"
134
+ """
135
+ agent_type_inferred = False
122
136
 
123
- def _extract_basic_info(self, event: dict, session_id: str) -> Tuple[str, str, str]:
124
- """Extract basic info from the event."""
125
137
  # First try to get agent type from our tracking
126
138
  agent_type = (
127
139
  self.state_manager.get_delegation_agent_type(session_id)
@@ -146,7 +158,17 @@ class SubagentResponseProcessor:
146
158
  elif "pm" in task_desc or "project" in task_desc:
147
159
  agent_type = "pm"
148
160
 
149
- return agent_type, agent_id, reason
161
+ # Default to "pm" if still unknown (main conversation doesn't use Task tool)
162
+ if agent_type == "unknown":
163
+ agent_type = "pm"
164
+ agent_type_inferred = True
165
+ if DEBUG:
166
+ print(
167
+ " - Inferred agent_type='pm' (no explicit type found)",
168
+ file=sys.stderr,
169
+ )
170
+
171
+ return agent_type, agent_id, reason, agent_type_inferred
150
172
 
151
173
  def _extract_structured_response(
152
174
  self, output: str, agent_type: str
@@ -338,10 +360,12 @@ class SubagentResponseProcessor:
338
360
  working_dir: str,
339
361
  git_branch: str,
340
362
  structured_response: Optional[dict],
363
+ agent_type_inferred: bool,
341
364
  ) -> dict:
342
365
  """Build the subagent stop data for event emission."""
343
366
  subagent_stop_data = {
344
367
  "agent_type": agent_type,
368
+ "agent_type_inferred": agent_type_inferred,
345
369
  "agent_id": agent_id,
346
370
  "reason": reason,
347
371
  "session_id": session_id,
@@ -8,8 +8,11 @@ DESIGN DECISIONS:
8
8
  - Non-blocking: doesn't prevent PM from starting if check fails
9
9
  - Displays context to stdout for user visibility
10
10
  - Integrates with existing session pause/resume infrastructure
11
+ - Checks for ACTIVE-PAUSE.jsonl (incremental auto-pause) before regular paused sessions
11
12
  """
12
13
 
14
+ import json
15
+ import sys
13
16
  from pathlib import Path
14
17
  from typing import Any, Dict, Optional
15
18
 
@@ -31,10 +34,83 @@ class SessionResumeStartupHook:
31
34
  self.project_path = project_path or Path.cwd()
32
35
  self.resume_helper = SessionResumeHelper(self.project_path)
33
36
  self._session_displayed = False
37
+ self.sessions_dir = self.project_path / ".claude-mpm" / "sessions"
38
+
39
+ def check_for_active_pause(self) -> Optional[Dict[str, Any]]:
40
+ """Check for an active incremental pause session.
41
+
42
+ Returns:
43
+ Pause session metadata if ACTIVE-PAUSE.jsonl exists, None otherwise
44
+ """
45
+ active_pause_path = self.sessions_dir / "ACTIVE-PAUSE.jsonl"
46
+
47
+ if not active_pause_path.exists():
48
+ logger.debug("No ACTIVE-PAUSE.jsonl found")
49
+ return None
50
+
51
+ try:
52
+ # Read JSONL file to get first and last actions
53
+ with active_pause_path.open("r") as f:
54
+ lines = f.readlines()
55
+
56
+ if not lines:
57
+ logger.warning("ACTIVE-PAUSE.jsonl is empty")
58
+ return None
59
+
60
+ # Parse first action (session start)
61
+ first_action = json.loads(lines[0])
62
+
63
+ # Parse last action (most recent)
64
+ last_action = json.loads(lines[-1]) if len(lines) > 1 else first_action
65
+
66
+ # Extract metadata
67
+ return {
68
+ "is_incremental": True,
69
+ "session_id": first_action.get("session_id"),
70
+ "started_at": first_action.get("timestamp"),
71
+ "context_at_start": first_action.get("data", {}).get(
72
+ "context_percentage", 0
73
+ ),
74
+ "current_context": last_action.get("context_percentage", 0),
75
+ "action_count": len(lines),
76
+ "file_path": str(active_pause_path),
77
+ }
78
+
79
+ except (json.JSONDecodeError, OSError, KeyError) as e:
80
+ logger.error(f"Failed to parse ACTIVE-PAUSE.jsonl: {e}", exc_info=True)
81
+ return None
82
+
83
+ def display_active_pause_warning(self, pause_info: Dict[str, Any]) -> None:
84
+ """Display warning about active incremental pause session.
85
+
86
+ Args:
87
+ pause_info: Pause session metadata from check_for_active_pause()
88
+ """
89
+ print("\n" + "=" * 60, file=sys.stderr)
90
+ print("⚠️ ACTIVE AUTO-PAUSE SESSION DETECTED", file=sys.stderr)
91
+ print("=" * 60, file=sys.stderr)
92
+ print(f"Session ID: {pause_info['session_id']}", file=sys.stderr)
93
+ print(f"Started at: {pause_info['started_at']}", file=sys.stderr)
94
+ print(
95
+ f"Context at pause: {pause_info['context_at_start']:.1%}", file=sys.stderr
96
+ )
97
+ print(f"Actions recorded: {pause_info['action_count']}", file=sys.stderr)
98
+ print(
99
+ "\nThis session was auto-paused due to high context usage.", file=sys.stderr
100
+ )
101
+ print("Options:", file=sys.stderr)
102
+ print(" 1. Continue (actions will be appended)", file=sys.stderr)
103
+ print(" 2. Use /mpm-init pause --finalize to create snapshot", file=sys.stderr)
104
+ print(" 3. Use /mpm-init pause --discard to abandon", file=sys.stderr)
105
+ print("=" * 60 + "\n", file=sys.stderr)
34
106
 
35
107
  def on_pm_startup(self) -> Optional[Dict[str, Any]]:
36
108
  """Execute on PM startup to check for paused sessions.
37
109
 
110
+ Checks in priority order:
111
+ 1. ACTIVE-PAUSE.jsonl (incremental auto-pause)
112
+ 2. Regular paused sessions (session-*.json)
113
+
38
114
  Returns:
39
115
  Session data if paused session found, None otherwise
40
116
  """
@@ -44,7 +120,15 @@ class SessionResumeStartupHook:
44
120
  logger.debug("Session already displayed, skipping")
45
121
  return None
46
122
 
47
- # Check for paused sessions
123
+ # PRIORITY 1: Check for active incremental pause FIRST
124
+ active_pause_info = self.check_for_active_pause()
125
+ if active_pause_info:
126
+ self.display_active_pause_warning(active_pause_info)
127
+ self._session_displayed = True
128
+ logger.info("Active pause session detected and displayed")
129
+ return active_pause_info
130
+
131
+ # PRIORITY 2: Fall back to regular paused sessions
48
132
  session_data = self.resume_helper.check_and_display_resume_prompt()
49
133
 
50
134
  if session_data:
claude_mpm/init.py CHANGED
@@ -216,7 +216,7 @@ class ProjectInitializer:
216
216
  if deploy_result.deployed:
217
217
  print(
218
218
  f"✓ Deployed {len(deploy_result.deployed)} PM skill(s) "
219
- f"to .claude-mpm/skills/pm/"
219
+ f"to .claude/skills/"
220
220
  )
221
221
  else:
222
222
  self.logger.warning(
@@ -62,8 +62,13 @@ set -e
62
62
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
63
63
 
64
64
  # Determine the claude-mpm root based on installation type
65
+ # Check if we're in a UV tools installation
66
+ if [[ "$SCRIPT_DIR" == *"/.local/share/uv/tools/"* ]]; then
67
+ # UV tools installation - script is at lib/python*/site-packages/claude_mpm/scripts/
68
+ # The tool root is what we need for Python detection
69
+ CLAUDE_MPM_ROOT="$(echo "$SCRIPT_DIR" | sed 's|/lib/python.*/site-packages.*||')"
65
70
  # Check if we're in a pipx installation
66
- if [[ "$SCRIPT_DIR" == *"/.local/pipx/venvs/claude-mpm/"* ]]; then
71
+ elif [[ "$SCRIPT_DIR" == *"/.local/pipx/venvs/claude-mpm/"* ]]; then
67
72
  # pipx installation - script is at lib/python*/site-packages/claude_mpm/scripts/
68
73
  # The venv root is what we need for Python detection
69
74
  CLAUDE_MPM_ROOT="$(echo "$SCRIPT_DIR" | sed 's|/lib/python.*/site-packages/.*||')"
@@ -89,11 +94,12 @@ fi
89
94
  # STRATEGY:
90
95
  # This function implements a fallback chain to find Python with claude-mpm dependencies:
91
96
  # 1. UV-managed projects (uv.lock detected) - uses "uv run python"
92
- # 2. pipx installations - uses pipx venv Python
93
- # 3. Project-specific virtual environments (venv, .venv)
94
- # 4. Currently active virtual environment ($VIRTUAL_ENV)
95
- # 5. System python3 (may lack dependencies)
96
- # 6. System python (last resort)
97
+ # 2. UV tools installations (~/.local/share/uv/tools/) - uses tool's venv Python
98
+ # 3. pipx installations - uses pipx venv Python
99
+ # 4. Project-specific virtual environments (venv, .venv)
100
+ # 5. Currently active virtual environment ($VIRTUAL_ENV)
101
+ # 6. System python3 (may lack dependencies)
102
+ # 7. System python (last resort)
97
103
  #
98
104
  # WHY THIS APPROACH:
99
105
  # - Claude MPM requires specific packages (socketio, eventlet) not in system Python
@@ -124,7 +130,21 @@ find_python_command() {
124
130
  fi
125
131
  fi
126
132
 
127
- # 2. Check if we're in a pipx installation
133
+ # 2. Check if we're in a UV tools installation
134
+ if [[ "$SCRIPT_DIR" == *"/.local/share/uv/tools/"* ]]; then
135
+ # UV tools installation - extract the tool root directory
136
+ CLAUDE_MPM_ROOT="$(echo "$SCRIPT_DIR" | sed 's|/lib/python.*/site-packages.*||')"
137
+ local uv_python="$CLAUDE_MPM_ROOT/bin/python"
138
+ if [ -x "$uv_python" ]; then
139
+ if [ "${CLAUDE_MPM_HOOK_DEBUG}" = "true" ]; then
140
+ echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] UV tools Python found: $uv_python" >> /tmp/claude-mpm-hook.log
141
+ fi
142
+ echo "$uv_python"
143
+ return
144
+ fi
145
+ fi
146
+
147
+ # 3. Check if we're in a pipx installation
128
148
  if [[ "$SCRIPT_DIR" == *"/.local/pipx/venvs/claude-mpm/"* ]]; then
129
149
  # pipx installation - use the pipx venv's Python directly
130
150
  if [ -f "$CLAUDE_MPM_ROOT/bin/python" ]; then
@@ -133,7 +153,7 @@ find_python_command() {
133
153
  fi
134
154
  fi
135
155
 
136
- # 3. Check for project-local virtual environment (common in development)
156
+ # 4. Check for project-local virtual environment (common in development)
137
157
  if [ -f "$CLAUDE_MPM_ROOT/venv/bin/activate" ]; then
138
158
  source "$CLAUDE_MPM_ROOT/venv/bin/activate"
139
159
  echo "$CLAUDE_MPM_ROOT/venv/bin/python"
@@ -154,7 +174,13 @@ find_python_command() {
154
174
  PYTHON_CMD=$(find_python_command)
155
175
 
156
176
  # Check installation type and set PYTHONPATH accordingly
157
- if [[ "$SCRIPT_DIR" == *"/.local/pipx/venvs/claude-mpm/"* ]]; then
177
+ if [[ "$SCRIPT_DIR" == *"/.local/share/uv/tools/"* ]]; then
178
+ # UV tools installation - claude_mpm is already in the tool's site-packages
179
+ # No need to modify PYTHONPATH
180
+ if [ "${CLAUDE_MPM_HOOK_DEBUG}" = "true" ]; then
181
+ echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] UV tools installation detected" >> /tmp/claude-mpm-hook.log
182
+ fi
183
+ elif [[ "$SCRIPT_DIR" == *"/.local/pipx/venvs/claude-mpm/"* ]]; then
158
184
  # pipx installation - claude_mpm is already in the venv's site-packages
159
185
  # No need to modify PYTHONPATH
160
186
  if [ "${CLAUDE_MPM_HOOK_DEBUG}" = "true" ]; then
@@ -224,4 +250,4 @@ if [ "${CLAUDE_MPM_HOOK_DEBUG}" = "true" ]; then
224
250
  fi
225
251
  # Return continue action to prevent blocking Claude Code
226
252
  echo '{"action": "continue"}'
227
- exit 0
253
+ exit 0
@@ -30,16 +30,16 @@ class AgentRecommendationService:
30
30
  Can be used by CLI, API, or future auto-configuration features.
31
31
  """
32
32
 
33
- # Core agents always included - matches ToolchainDetector.CORE_AGENTS
33
+ # Core agents always included - Standard 6 core agents for essential PM workflow
34
+ # These agents are auto-deployed when no configuration exists
34
35
  # Uses exact agent IDs from repository for consistency
35
36
  CORE_AGENTS = {
36
- "engineer",
37
- "qa-agent",
38
- "memory-manager-agent",
39
- "local-ops-agent",
40
- "research-agent",
41
- "documentation-agent",
42
- "security-agent",
37
+ "engineer", # General-purpose implementation
38
+ "research", # Codebase exploration and analysis
39
+ "qa", # Testing and quality assurance
40
+ "documentation", # Documentation generation
41
+ "ops", # Basic deployment operations
42
+ "ticketing", # Ticket tracking (essential for PM workflow)
43
43
  }
44
44
 
45
45
  # Map detected languages to recommended engineer agents
@@ -88,7 +88,7 @@ class CacheGitManager:
88
88
  if self.repo_path:
89
89
  logger.debug(f"Initialized CacheGitManager for repo: {self.repo_path}")
90
90
  else:
91
- logger.warning(f"Cache path is not a git repository: {cache_path}")
91
+ logger.debug(f"Cache path is not a git repository: {cache_path}")
92
92
 
93
93
  def _find_git_root(self) -> Optional[Path]:
94
94
  """
@@ -491,6 +491,9 @@ class RemoteAgentDiscoveryService:
491
491
  "SKILL.md",
492
492
  "SKILLS.md",
493
493
  "skill-template.md",
494
+ # Legacy agents superseded by newer versions
495
+ # TODO: Remove after bobmatnyc/claude-mpm-agents#XXX is merged
496
+ "memory-manager.md", # Superseded by memory-manager-agent.md (v1.2.0)
494
497
  }
495
498
  md_files = [f for f in md_files if f.name not in excluded_files]
496
499
 
@@ -10,10 +10,19 @@ Loading precedence: Project → User → System
10
10
 
11
11
  This service integrates with the main agent_loader.py to provide
12
12
  markdown-based agent profiles alongside JSON-based templates.
13
+
14
+ Auto-Deployment: When no agents are configured, the standard 6 core agents
15
+ are automatically deployed:
16
+ - engineer: General-purpose implementation
17
+ - research: Codebase exploration and analysis
18
+ - qa: Testing and quality assurance
19
+ - documentation: Documentation generation
20
+ - ops: Basic deployment operations
21
+ - ticketing: Ticket tracking (essential for PM workflow)
13
22
  """
14
23
 
15
24
  from pathlib import Path
16
- from typing import Any, Dict, Optional
25
+ from typing import Any, Dict, List, Optional
17
26
 
18
27
  from claude_mpm.agents.agent_loader import AgentTier, list_agents_by_tier
19
28
  from claude_mpm.core.logging_utils import get_logger
@@ -21,6 +30,17 @@ from claude_mpm.core.unified_paths import get_path_manager
21
30
 
22
31
  logger = get_logger(__name__)
23
32
 
33
+ # Standard 6 core agents that are auto-deployed when no agents are specified
34
+ # This list is the canonical source - other modules should import from here
35
+ CORE_AGENTS: List[str] = [
36
+ "engineer", # General-purpose implementation
37
+ "research", # Codebase exploration and analysis
38
+ "qa", # Testing and quality assurance
39
+ "documentation", # Documentation generation
40
+ "ops", # Basic deployment operations
41
+ "ticketing", # Ticket tracking (essential for PM workflow)
42
+ ]
43
+
24
44
 
25
45
  class FrameworkAgentLoader:
26
46
  """Loads agent profiles from project, user, and system directories with proper precedence"""
@@ -86,7 +106,7 @@ class FrameworkAgentLoader:
86
106
  data_claude = package_path / "data" / "agents" / "CLAUDE.md"
87
107
  if data_instructions.exists() or data_claude.exists():
88
108
  return package_path / "data"
89
- except Exception:
109
+ except Exception: # nosec B110 - intentional fallthrough to next location
90
110
  pass
91
111
 
92
112
  current = Path.cwd()
@@ -431,3 +451,56 @@ Please operate according to your profile specifications and maintain quality sta
431
451
  """
432
452
 
433
453
  return instruction.strip()
454
+
455
+ def get_core_agents(self) -> List[str]:
456
+ """
457
+ Get the standard 6 core agents for auto-deployment.
458
+
459
+ These agents are automatically deployed when no agents are specified
460
+ in the configuration. They provide essential PM workflow functionality.
461
+
462
+ Returns:
463
+ List of core agent IDs
464
+
465
+ Example:
466
+ >>> loader = FrameworkAgentLoader()
467
+ >>> core = loader.get_core_agents()
468
+ >>> 'engineer' in core
469
+ True
470
+ >>> len(core)
471
+ 6
472
+ """
473
+ return CORE_AGENTS.copy()
474
+
475
+ def get_agents_with_fallback(self) -> Dict[str, list]:
476
+ """
477
+ Get available agents, falling back to core agents if none found.
478
+
479
+ This method implements the auto-deployment logic: when no agents
480
+ are found in any tier (project, user, system), it returns the
481
+ standard 6 core agents as a fallback.
482
+
483
+ Returns:
484
+ Dictionary with agent lists by tier. If no agents found in any tier,
485
+ returns core agents under 'fallback' key.
486
+
487
+ Example:
488
+ >>> loader = FrameworkAgentLoader()
489
+ >>> loader.initialize()
490
+ >>> agents = loader.get_agents_with_fallback()
491
+ >>> if 'fallback' in agents:
492
+ ... print("Using core agents as fallback")
493
+ """
494
+ available = self.get_available_agents()
495
+
496
+ # Check if any agents are found
497
+ total_agents = sum(len(agents) for agents in available.values())
498
+
499
+ if total_agents == 0:
500
+ logger.info(
501
+ "No agents found in configuration. "
502
+ "Auto-deploying standard 6 core agents."
503
+ )
504
+ return {"fallback": CORE_AGENTS.copy()}
505
+
506
+ return available
@@ -5,6 +5,7 @@ Services specifically for CLI command support and utilities.
5
5
 
6
6
  from .agent_dependency_service import AgentDependencyService, IAgentDependencyService
7
7
  from .agent_validation_service import AgentValidationService, IAgentValidationService
8
+ from .incremental_pause_manager import IncrementalPauseManager, PauseAction
8
9
  from .startup_checker import IStartupChecker, StartupCheckerService, StartupWarning
9
10
 
10
11
  __all__ = [
@@ -13,6 +14,8 @@ __all__ = [
13
14
  "IAgentDependencyService",
14
15
  "IAgentValidationService",
15
16
  "IStartupChecker",
17
+ "IncrementalPauseManager",
18
+ "PauseAction",
16
19
  "StartupCheckerService",
17
20
  "StartupWarning",
18
21
  ]