macro-agent 0.0.10 → 0.0.12

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 (518) hide show
  1. package/.macro-agent/teams/self-driving/prompts/grinder.md +27 -0
  2. package/.macro-agent/teams/self-driving/prompts/judge.md +27 -0
  3. package/.macro-agent/teams/self-driving/prompts/planner.md +33 -0
  4. package/.macro-agent/teams/self-driving/roles/grinder.yaml +17 -0
  5. package/.macro-agent/teams/self-driving/roles/judge.yaml +24 -0
  6. package/.macro-agent/teams/self-driving/roles/planner.yaml +18 -0
  7. package/.macro-agent/teams/self-driving/team.yaml +103 -0
  8. package/.macro-agent/teams/structured/prompts/developer.md +26 -0
  9. package/.macro-agent/teams/structured/prompts/lead.md +25 -0
  10. package/.macro-agent/teams/structured/prompts/reviewer.md +24 -0
  11. package/.macro-agent/teams/structured/roles/developer.yaml +12 -0
  12. package/.macro-agent/teams/structured/roles/lead.yaml +11 -0
  13. package/.macro-agent/teams/structured/roles/reviewer.yaml +19 -0
  14. package/.macro-agent/teams/structured/team.yaml +89 -0
  15. package/.sudocode/issues.jsonl +56 -51
  16. package/.sudocode/specs.jsonl +8 -1
  17. package/CLAUDE.md +121 -30
  18. package/README.md +60 -3
  19. package/dist/acp/macro-agent.d.ts +4 -0
  20. package/dist/acp/macro-agent.d.ts.map +1 -1
  21. package/dist/acp/macro-agent.js +50 -4
  22. package/dist/acp/macro-agent.js.map +1 -1
  23. package/dist/acp/session-mapper.d.ts +20 -1
  24. package/dist/acp/session-mapper.d.ts.map +1 -1
  25. package/dist/acp/session-mapper.js +90 -1
  26. package/dist/acp/session-mapper.js.map +1 -1
  27. package/dist/acp/types.d.ts +24 -1
  28. package/dist/acp/types.d.ts.map +1 -1
  29. package/dist/acp/types.js.map +1 -1
  30. package/dist/agent/agent-manager.d.ts +40 -1
  31. package/dist/agent/agent-manager.d.ts.map +1 -1
  32. package/dist/agent/agent-manager.js +172 -8
  33. package/dist/agent/agent-manager.js.map +1 -1
  34. package/dist/agent/types.d.ts +22 -0
  35. package/dist/agent/types.d.ts.map +1 -1
  36. package/dist/agent/types.js.map +1 -1
  37. package/dist/agent/wake.d.ts +15 -0
  38. package/dist/agent/wake.d.ts.map +1 -1
  39. package/dist/agent/wake.js +15 -0
  40. package/dist/agent/wake.js.map +1 -1
  41. package/dist/agent-detection/command-builder.d.ts +30 -0
  42. package/dist/agent-detection/command-builder.d.ts.map +1 -0
  43. package/dist/agent-detection/command-builder.js +71 -0
  44. package/dist/agent-detection/command-builder.js.map +1 -0
  45. package/dist/agent-detection/detector.d.ts +84 -0
  46. package/dist/agent-detection/detector.d.ts.map +1 -0
  47. package/dist/agent-detection/detector.js +240 -0
  48. package/dist/agent-detection/detector.js.map +1 -0
  49. package/dist/agent-detection/index.d.ts +12 -0
  50. package/dist/agent-detection/index.d.ts.map +1 -0
  51. package/dist/agent-detection/index.js +14 -0
  52. package/dist/agent-detection/index.js.map +1 -0
  53. package/dist/agent-detection/registry.d.ts +53 -0
  54. package/dist/agent-detection/registry.d.ts.map +1 -0
  55. package/dist/agent-detection/registry.js +177 -0
  56. package/dist/agent-detection/registry.js.map +1 -0
  57. package/dist/agent-detection/types.d.ts +121 -0
  58. package/dist/agent-detection/types.d.ts.map +1 -0
  59. package/dist/agent-detection/types.js +20 -0
  60. package/dist/agent-detection/types.js.map +1 -0
  61. package/dist/api/server.d.ts +5 -1
  62. package/dist/api/server.d.ts.map +1 -1
  63. package/dist/api/server.js +362 -0
  64. package/dist/api/server.js.map +1 -1
  65. package/dist/api/types.d.ts +50 -1
  66. package/dist/api/types.d.ts.map +1 -1
  67. package/dist/cli/acp.d.ts +2 -0
  68. package/dist/cli/acp.d.ts.map +1 -1
  69. package/dist/cli/acp.js +8 -1
  70. package/dist/cli/acp.js.map +1 -1
  71. package/dist/cli/index.js +29 -0
  72. package/dist/cli/index.js.map +1 -1
  73. package/dist/cli/mcp.js +38 -0
  74. package/dist/cli/mcp.js.map +1 -1
  75. package/dist/config/index.d.ts +2 -0
  76. package/dist/config/index.d.ts.map +1 -0
  77. package/dist/config/index.js +2 -0
  78. package/dist/config/index.js.map +1 -0
  79. package/dist/config/project-config.d.ts +46 -0
  80. package/dist/config/project-config.d.ts.map +1 -0
  81. package/dist/config/project-config.js +68 -0
  82. package/dist/config/project-config.js.map +1 -0
  83. package/dist/lifecycle/cascade.d.ts +1 -1
  84. package/dist/lifecycle/cascade.d.ts.map +1 -1
  85. package/dist/lifecycle/handlers/index.d.ts +4 -0
  86. package/dist/lifecycle/handlers/index.d.ts.map +1 -1
  87. package/dist/lifecycle/handlers/index.js +2 -0
  88. package/dist/lifecycle/handlers/index.js.map +1 -1
  89. package/dist/lifecycle/handlers/worker.d.ts +4 -0
  90. package/dist/lifecycle/handlers/worker.d.ts.map +1 -1
  91. package/dist/lifecycle/handlers/worker.js +35 -3
  92. package/dist/lifecycle/handlers/worker.js.map +1 -1
  93. package/dist/mail/conversation-map.d.ts +33 -0
  94. package/dist/mail/conversation-map.d.ts.map +1 -0
  95. package/dist/mail/conversation-map.js +61 -0
  96. package/dist/mail/conversation-map.js.map +1 -0
  97. package/dist/mail/index.d.ts +11 -0
  98. package/dist/mail/index.d.ts.map +1 -0
  99. package/dist/mail/index.js +11 -0
  100. package/dist/mail/index.js.map +1 -0
  101. package/dist/mail/mail-service.d.ts +85 -0
  102. package/dist/mail/mail-service.d.ts.map +1 -0
  103. package/dist/mail/mail-service.js +121 -0
  104. package/dist/mail/mail-service.js.map +1 -0
  105. package/dist/mail/stores/eventstore-conversation-store.d.ts +40 -0
  106. package/dist/mail/stores/eventstore-conversation-store.d.ts.map +1 -0
  107. package/dist/mail/stores/eventstore-conversation-store.js +131 -0
  108. package/dist/mail/stores/eventstore-conversation-store.js.map +1 -0
  109. package/dist/mail/stores/eventstore-participant-store.d.ts +43 -0
  110. package/dist/mail/stores/eventstore-participant-store.d.ts.map +1 -0
  111. package/dist/mail/stores/eventstore-participant-store.js +145 -0
  112. package/dist/mail/stores/eventstore-participant-store.js.map +1 -0
  113. package/dist/mail/stores/eventstore-thread-store.d.ts +46 -0
  114. package/dist/mail/stores/eventstore-thread-store.d.ts.map +1 -0
  115. package/dist/mail/stores/eventstore-thread-store.js +118 -0
  116. package/dist/mail/stores/eventstore-thread-store.js.map +1 -0
  117. package/dist/mail/stores/eventstore-turn-store.d.ts +47 -0
  118. package/dist/mail/stores/eventstore-turn-store.d.ts.map +1 -0
  119. package/dist/mail/stores/eventstore-turn-store.js +153 -0
  120. package/dist/mail/stores/eventstore-turn-store.js.map +1 -0
  121. package/dist/mail/stores/index.d.ts +12 -0
  122. package/dist/mail/stores/index.d.ts.map +1 -0
  123. package/dist/mail/stores/index.js +12 -0
  124. package/dist/mail/stores/index.js.map +1 -0
  125. package/dist/mail/stores/types.d.ts +146 -0
  126. package/dist/mail/stores/types.d.ts.map +1 -0
  127. package/dist/mail/stores/types.js +13 -0
  128. package/dist/mail/stores/types.js.map +1 -0
  129. package/dist/mail/turn-recorder.d.ts +30 -0
  130. package/dist/mail/turn-recorder.d.ts.map +1 -0
  131. package/dist/mail/turn-recorder.js +98 -0
  132. package/dist/mail/turn-recorder.js.map +1 -0
  133. package/dist/map/adapter/acp-over-map.d.ts.map +1 -1
  134. package/dist/map/adapter/acp-over-map.js +32 -2
  135. package/dist/map/adapter/acp-over-map.js.map +1 -1
  136. package/dist/map/adapter/event-translator.d.ts.map +1 -1
  137. package/dist/map/adapter/event-translator.js +4 -0
  138. package/dist/map/adapter/event-translator.js.map +1 -1
  139. package/dist/map/adapter/extensions/agent-detection.d.ts +49 -0
  140. package/dist/map/adapter/extensions/agent-detection.d.ts.map +1 -0
  141. package/dist/map/adapter/extensions/agent-detection.js +91 -0
  142. package/dist/map/adapter/extensions/agent-detection.js.map +1 -0
  143. package/dist/map/adapter/extensions/index.d.ts +10 -1
  144. package/dist/map/adapter/extensions/index.d.ts.map +1 -1
  145. package/dist/map/adapter/extensions/index.js +39 -0
  146. package/dist/map/adapter/extensions/index.js.map +1 -1
  147. package/dist/map/adapter/extensions/resume.d.ts +47 -0
  148. package/dist/map/adapter/extensions/resume.d.ts.map +1 -0
  149. package/dist/map/adapter/extensions/resume.js +59 -0
  150. package/dist/map/adapter/extensions/resume.js.map +1 -0
  151. package/dist/map/adapter/extensions/workspace-files.d.ts +42 -0
  152. package/dist/map/adapter/extensions/workspace-files.d.ts.map +1 -0
  153. package/dist/map/adapter/extensions/workspace-files.js +338 -0
  154. package/dist/map/adapter/extensions/workspace-files.js.map +1 -0
  155. package/dist/map/adapter/mail-handler-adapter.d.ts +27 -0
  156. package/dist/map/adapter/mail-handler-adapter.d.ts.map +1 -0
  157. package/dist/map/adapter/mail-handler-adapter.js +292 -0
  158. package/dist/map/adapter/mail-handler-adapter.js.map +1 -0
  159. package/dist/map/adapter/map-adapter.d.ts +34 -10
  160. package/dist/map/adapter/map-adapter.d.ts.map +1 -1
  161. package/dist/map/adapter/map-adapter.js +110 -14
  162. package/dist/map/adapter/map-adapter.js.map +1 -1
  163. package/dist/map/adapter/rpc-handler.d.ts +4 -1
  164. package/dist/map/adapter/rpc-handler.d.ts.map +1 -1
  165. package/dist/map/adapter/rpc-handler.js +6 -0
  166. package/dist/map/adapter/rpc-handler.js.map +1 -1
  167. package/dist/map/index.d.ts +1 -0
  168. package/dist/map/index.d.ts.map +1 -1
  169. package/dist/map/index.js +2 -0
  170. package/dist/map/index.js.map +1 -1
  171. package/dist/map/types.d.ts +3 -1
  172. package/dist/map/types.d.ts.map +1 -1
  173. package/dist/map/types.js.map +1 -1
  174. package/dist/mcp/mcp-server.d.ts +6 -0
  175. package/dist/mcp/mcp-server.d.ts.map +1 -1
  176. package/dist/mcp/mcp-server.js +45 -0
  177. package/dist/mcp/mcp-server.js.map +1 -1
  178. package/dist/mcp/tools/claim_task.d.ts +35 -0
  179. package/dist/mcp/tools/claim_task.d.ts.map +1 -0
  180. package/dist/mcp/tools/claim_task.js +58 -0
  181. package/dist/mcp/tools/claim_task.js.map +1 -0
  182. package/dist/mcp/tools/done.d.ts +15 -2
  183. package/dist/mcp/tools/done.d.ts.map +1 -1
  184. package/dist/mcp/tools/done.js +45 -10
  185. package/dist/mcp/tools/done.js.map +1 -1
  186. package/dist/mcp/tools/list_claimable_tasks.d.ts +38 -0
  187. package/dist/mcp/tools/list_claimable_tasks.d.ts.map +1 -0
  188. package/dist/mcp/tools/list_claimable_tasks.js +63 -0
  189. package/dist/mcp/tools/list_claimable_tasks.js.map +1 -0
  190. package/dist/mcp/tools/unclaim_task.d.ts +31 -0
  191. package/dist/mcp/tools/unclaim_task.d.ts.map +1 -0
  192. package/dist/mcp/tools/unclaim_task.js +47 -0
  193. package/dist/mcp/tools/unclaim_task.js.map +1 -0
  194. package/dist/metrics/index.d.ts +2 -0
  195. package/dist/metrics/index.d.ts.map +1 -0
  196. package/dist/metrics/index.js +2 -0
  197. package/dist/metrics/index.js.map +1 -0
  198. package/dist/metrics/metrics.d.ts +79 -0
  199. package/dist/metrics/metrics.d.ts.map +1 -0
  200. package/dist/metrics/metrics.js +166 -0
  201. package/dist/metrics/metrics.js.map +1 -0
  202. package/dist/roles/capabilities.d.ts +1 -0
  203. package/dist/roles/capabilities.d.ts.map +1 -1
  204. package/dist/roles/capabilities.js +3 -0
  205. package/dist/roles/capabilities.js.map +1 -1
  206. package/dist/roles/types.d.ts +1 -1
  207. package/dist/roles/types.d.ts.map +1 -1
  208. package/dist/router/channels.d.ts +2 -4
  209. package/dist/router/channels.d.ts.map +1 -1
  210. package/dist/router/channels.js.map +1 -1
  211. package/dist/router/message-router.d.ts +85 -9
  212. package/dist/router/message-router.d.ts.map +1 -1
  213. package/dist/router/message-router.js +203 -14
  214. package/dist/router/message-router.js.map +1 -1
  215. package/dist/router/role-resolver.d.ts +10 -1
  216. package/dist/router/role-resolver.d.ts.map +1 -1
  217. package/dist/router/role-resolver.js +15 -1
  218. package/dist/router/role-resolver.js.map +1 -1
  219. package/dist/router/types.d.ts +30 -1
  220. package/dist/router/types.d.ts.map +1 -1
  221. package/dist/router/types.js.map +1 -1
  222. package/dist/server/combined-server.d.ts +6 -0
  223. package/dist/server/combined-server.d.ts.map +1 -1
  224. package/dist/server/combined-server.js +24 -2
  225. package/dist/server/combined-server.js.map +1 -1
  226. package/dist/store/event-store.d.ts +14 -1
  227. package/dist/store/event-store.d.ts.map +1 -1
  228. package/dist/store/event-store.js +456 -4
  229. package/dist/store/event-store.js.map +1 -1
  230. package/dist/store/types/agents.d.ts +1 -1
  231. package/dist/store/types/agents.d.ts.map +1 -1
  232. package/dist/store/types/conversations.d.ts +91 -0
  233. package/dist/store/types/conversations.d.ts.map +1 -0
  234. package/dist/store/types/conversations.js +8 -0
  235. package/dist/store/types/conversations.js.map +1 -0
  236. package/dist/store/types/events.d.ts +1 -1
  237. package/dist/store/types/events.d.ts.map +1 -1
  238. package/dist/store/types/events.js.map +1 -1
  239. package/dist/store/types/index.d.ts +2 -0
  240. package/dist/store/types/index.d.ts.map +1 -1
  241. package/dist/store/types/index.js +2 -0
  242. package/dist/store/types/index.js.map +1 -1
  243. package/dist/store/types/sessions.d.ts +44 -0
  244. package/dist/store/types/sessions.d.ts.map +1 -0
  245. package/dist/store/types/sessions.js +9 -0
  246. package/dist/store/types/sessions.js.map +1 -0
  247. package/dist/store/types/tasks.d.ts +2 -0
  248. package/dist/store/types/tasks.d.ts.map +1 -1
  249. package/dist/task/backend/memory.d.ts +4 -1
  250. package/dist/task/backend/memory.d.ts.map +1 -1
  251. package/dist/task/backend/memory.js +81 -0
  252. package/dist/task/backend/memory.js.map +1 -1
  253. package/dist/task/backend/types.d.ts +30 -0
  254. package/dist/task/backend/types.d.ts.map +1 -1
  255. package/dist/task/backend/types.js.map +1 -1
  256. package/dist/teams/index.d.ts +4 -0
  257. package/dist/teams/index.d.ts.map +1 -0
  258. package/dist/teams/index.js +4 -0
  259. package/dist/teams/index.js.map +1 -0
  260. package/dist/teams/team-loader.d.ts +20 -0
  261. package/dist/teams/team-loader.d.ts.map +1 -0
  262. package/dist/teams/team-loader.js +293 -0
  263. package/dist/teams/team-loader.js.map +1 -0
  264. package/dist/teams/team-runtime.d.ts +139 -0
  265. package/dist/teams/team-runtime.d.ts.map +1 -0
  266. package/dist/teams/team-runtime.js +613 -0
  267. package/dist/teams/team-runtime.js.map +1 -0
  268. package/dist/teams/types.d.ts +266 -0
  269. package/dist/teams/types.d.ts.map +1 -0
  270. package/dist/teams/types.js +20 -0
  271. package/dist/teams/types.js.map +1 -0
  272. package/dist/trigger/router/trigger-router.d.ts +30 -3
  273. package/dist/trigger/router/trigger-router.d.ts.map +1 -1
  274. package/dist/trigger/router/trigger-router.js +30 -3
  275. package/dist/trigger/router/trigger-router.js.map +1 -1
  276. package/dist/trigger/wake/types.d.ts +31 -5
  277. package/dist/trigger/wake/types.d.ts.map +1 -1
  278. package/dist/trigger/wake/types.js +19 -0
  279. package/dist/trigger/wake/types.js.map +1 -1
  280. package/dist/workspace/dataplane-adapter.d.ts +1 -1
  281. package/dist/workspace/dataplane-adapter.d.ts.map +1 -1
  282. package/dist/workspace/dataplane-adapter.js +1 -1
  283. package/dist/workspace/dataplane-adapter.js.map +1 -1
  284. package/dist/workspace/index.d.ts +1 -1
  285. package/dist/workspace/index.d.ts.map +1 -1
  286. package/dist/workspace/strategies/index.d.ts +6 -0
  287. package/dist/workspace/strategies/index.d.ts.map +1 -0
  288. package/dist/workspace/strategies/index.js +5 -0
  289. package/dist/workspace/strategies/index.js.map +1 -0
  290. package/dist/workspace/strategies/optimistic.d.ts +26 -0
  291. package/dist/workspace/strategies/optimistic.d.ts.map +1 -0
  292. package/dist/workspace/strategies/optimistic.js +121 -0
  293. package/dist/workspace/strategies/optimistic.js.map +1 -0
  294. package/dist/workspace/strategies/queue.d.ts +26 -0
  295. package/dist/workspace/strategies/queue.d.ts.map +1 -0
  296. package/dist/workspace/strategies/queue.js +67 -0
  297. package/dist/workspace/strategies/queue.js.map +1 -0
  298. package/dist/workspace/strategies/registry.d.ts +37 -0
  299. package/dist/workspace/strategies/registry.d.ts.map +1 -0
  300. package/dist/workspace/strategies/registry.js +63 -0
  301. package/dist/workspace/strategies/registry.js.map +1 -0
  302. package/dist/workspace/strategies/trunk.d.ts +20 -0
  303. package/dist/workspace/strategies/trunk.d.ts.map +1 -0
  304. package/dist/workspace/strategies/trunk.js +108 -0
  305. package/dist/workspace/strategies/trunk.js.map +1 -0
  306. package/dist/workspace/strategies/types.d.ts +104 -0
  307. package/dist/workspace/strategies/types.d.ts.map +1 -0
  308. package/dist/workspace/strategies/types.js +11 -0
  309. package/dist/workspace/strategies/types.js.map +1 -0
  310. package/dist/workspace/types.d.ts +1 -1
  311. package/dist/workspace/types.d.ts.map +1 -1
  312. package/dist/workspace/workspace-manager.d.ts +1 -1
  313. package/dist/workspace/workspace-manager.d.ts.map +1 -1
  314. package/docs/implementation-details.md +1127 -0
  315. package/docs/implementation-summary.md +448 -0
  316. package/docs/mail-integration.md +608 -0
  317. package/docs/plan-self-driving-support.md +433 -0
  318. package/docs/spec-self-driving-support.md +462 -0
  319. package/docs/team-templates.md +860 -0
  320. package/docs/teams.md +233 -0
  321. package/package.json +5 -3
  322. package/src/acp/__tests__/integration.test.ts +161 -1
  323. package/src/acp/__tests__/macro-agent.test.ts +95 -0
  324. package/src/acp/__tests__/session-persistence.test.ts +276 -0
  325. package/src/acp/macro-agent.ts +79 -7
  326. package/src/acp/session-mapper.ts +108 -1
  327. package/src/acp/types.ts +33 -1
  328. package/src/agent/agent-manager.ts +278 -6
  329. package/src/agent/types.ts +27 -0
  330. package/src/agent/wake.ts +15 -0
  331. package/src/agent-detection/__tests__/command-builder.test.ts +336 -0
  332. package/src/agent-detection/__tests__/detector.test.ts +768 -0
  333. package/src/agent-detection/__tests__/registry.test.ts +254 -0
  334. package/src/agent-detection/command-builder.ts +90 -0
  335. package/src/agent-detection/detector.ts +307 -0
  336. package/src/agent-detection/index.ts +36 -0
  337. package/src/agent-detection/registry.ts +200 -0
  338. package/src/agent-detection/types.ts +184 -0
  339. package/src/api/__tests__/conversation-api.test.ts +468 -0
  340. package/src/api/server.ts +425 -1
  341. package/src/api/types.ts +64 -1
  342. package/src/cli/acp.ts +9 -1
  343. package/src/cli/index.ts +44 -0
  344. package/src/cli/mcp.ts +47 -0
  345. package/src/config/index.ts +9 -0
  346. package/src/config/project-config.ts +107 -0
  347. package/src/lifecycle/cascade.ts +1 -1
  348. package/src/lifecycle/handlers/index.ts +8 -0
  349. package/src/lifecycle/handlers/worker.ts +48 -3
  350. package/src/mail/__tests__/conversation-lifecycle.test.ts +409 -0
  351. package/src/mail/__tests__/eventstore-stores.test.ts +1073 -0
  352. package/src/mail/__tests__/mail-full-agent.e2e.test.ts +575 -0
  353. package/src/mail/__tests__/mail-integration.test.ts +759 -0
  354. package/src/mail/__tests__/mail-map-protocol.e2e.test.ts +1068 -0
  355. package/src/mail/__tests__/mail-service.test.ts +506 -0
  356. package/src/mail/__tests__/turn-recorder.test.ts +328 -0
  357. package/src/mail/conversation-map.ts +107 -0
  358. package/src/mail/index.ts +25 -0
  359. package/src/mail/mail-service.ts +257 -0
  360. package/src/mail/stores/eventstore-conversation-store.ts +146 -0
  361. package/src/mail/stores/eventstore-participant-store.ts +172 -0
  362. package/src/mail/stores/eventstore-thread-store.ts +129 -0
  363. package/src/mail/stores/eventstore-turn-store.ts +173 -0
  364. package/src/mail/stores/index.ts +12 -0
  365. package/src/mail/stores/types.ts +160 -0
  366. package/src/mail/turn-recorder.ts +124 -0
  367. package/src/map/README.md +79 -0
  368. package/src/map/adapter/__tests__/extensions.test.ts +359 -0
  369. package/src/map/adapter/__tests__/map-adapter.test.ts +90 -0
  370. package/src/map/adapter/__tests__/workspace-files.test.ts +673 -0
  371. package/src/map/adapter/acp-over-map.ts +45 -2
  372. package/src/map/adapter/event-translator.ts +4 -0
  373. package/src/map/adapter/extensions/agent-detection.ts +201 -0
  374. package/src/map/adapter/extensions/index.ts +63 -0
  375. package/src/map/adapter/extensions/resume.ts +114 -0
  376. package/src/map/adapter/extensions/workspace-files.ts +449 -0
  377. package/src/map/adapter/mail-handler-adapter.ts +429 -0
  378. package/src/map/adapter/map-adapter.ts +173 -27
  379. package/src/map/adapter/rpc-handler.ts +8 -1
  380. package/src/map/index.ts +3 -0
  381. package/src/map/types.ts +3 -1
  382. package/src/mcp/mcp-server.ts +67 -0
  383. package/src/mcp/tools/claim_task.ts +86 -0
  384. package/src/mcp/tools/done.ts +59 -10
  385. package/src/mcp/tools/list_claimable_tasks.ts +93 -0
  386. package/src/mcp/tools/unclaim_task.ts +71 -0
  387. package/src/metrics/index.ts +9 -0
  388. package/src/metrics/metrics.ts +280 -0
  389. package/src/roles/capabilities.ts +3 -0
  390. package/src/roles/types.ts +2 -1
  391. package/src/router/README.md +120 -0
  392. package/src/router/__tests__/message-router.test.ts +561 -0
  393. package/src/router/channels.ts +3 -4
  394. package/src/router/message-router.ts +308 -22
  395. package/src/router/role-resolver.ts +22 -1
  396. package/src/router/types.ts +36 -1
  397. package/src/server/combined-server.ts +36 -2
  398. package/src/store/README.md +134 -0
  399. package/src/store/event-store.ts +546 -3
  400. package/src/store/types/agents.ts +1 -1
  401. package/src/store/types/conversations.ts +129 -0
  402. package/src/store/types/events.ts +5 -1
  403. package/src/store/types/index.ts +2 -0
  404. package/src/store/types/sessions.ts +53 -0
  405. package/src/store/types/tasks.ts +3 -0
  406. package/src/task/backend/memory.ts +116 -0
  407. package/src/task/backend/types.ts +43 -0
  408. package/src/teams/__tests__/cross-subsystem.integration.test.ts +983 -0
  409. package/src/teams/__tests__/e2e/team-runtime.e2e.test.ts +553 -0
  410. package/src/teams/__tests__/team-system.test.ts +1280 -0
  411. package/src/teams/index.ts +13 -0
  412. package/src/teams/team-loader.ts +434 -0
  413. package/src/teams/team-runtime.ts +727 -0
  414. package/src/teams/types.ts +377 -0
  415. package/src/trigger/router/trigger-router.ts +30 -3
  416. package/src/trigger/wake/types.ts +32 -5
  417. package/src/trigger/wake/wake-manager.ts +2 -2
  418. package/src/workspace/dataplane-adapter.ts +1 -1
  419. package/src/workspace/index.ts +1 -1
  420. package/src/workspace/strategies/index.ts +18 -0
  421. package/src/workspace/strategies/optimistic.ts +136 -0
  422. package/src/workspace/strategies/queue.ts +81 -0
  423. package/src/workspace/strategies/registry.ts +89 -0
  424. package/src/workspace/strategies/trunk.ts +123 -0
  425. package/src/workspace/strategies/types.ts +145 -0
  426. package/src/workspace/types.ts +1 -1
  427. package/src/workspace/workspace-manager.ts +1 -1
  428. package/.claude/settings.local.json +0 -59
  429. package/dist/map/utils/address-translation.d.ts +0 -99
  430. package/dist/map/utils/address-translation.d.ts.map +0 -1
  431. package/dist/map/utils/address-translation.js +0 -285
  432. package/dist/map/utils/address-translation.js.map +0 -1
  433. package/dist/map/utils/index.d.ts +0 -7
  434. package/dist/map/utils/index.d.ts.map +0 -1
  435. package/dist/map/utils/index.js +0 -7
  436. package/dist/map/utils/index.js.map +0 -1
  437. package/openspec/AGENTS.md +0 -456
  438. package/openspec/changes/archive/2025-12-21-add-mvp-foundation/design.md +0 -128
  439. package/openspec/changes/archive/2025-12-21-add-mvp-foundation/proposal.md +0 -49
  440. package/openspec/changes/archive/2025-12-21-add-mvp-foundation/specs/agent-manager/spec.md +0 -150
  441. package/openspec/changes/archive/2025-12-21-add-mvp-foundation/specs/cli-api/spec.md +0 -258
  442. package/openspec/changes/archive/2025-12-21-add-mvp-foundation/specs/event-store/spec.md +0 -160
  443. package/openspec/changes/archive/2025-12-21-add-mvp-foundation/specs/mcp-tools/spec.md +0 -224
  444. package/openspec/changes/archive/2025-12-21-add-mvp-foundation/specs/message-router/spec.md +0 -153
  445. package/openspec/changes/archive/2025-12-21-add-mvp-foundation/specs/task-manager/spec.md +0 -136
  446. package/openspec/changes/archive/2025-12-21-add-mvp-foundation/tasks.md +0 -147
  447. package/openspec/project.md +0 -31
  448. package/openspec/specs/agent-manager/spec.md +0 -154
  449. package/openspec/specs/cli-api/spec.md +0 -262
  450. package/openspec/specs/event-store/spec.md +0 -164
  451. package/openspec/specs/mcp-tools/spec.md +0 -228
  452. package/openspec/specs/message-router/spec.md +0 -157
  453. package/openspec/specs/task-manager/spec.md +0 -140
  454. package/references/acp-factory-ref/CHANGELOG.md +0 -33
  455. package/references/acp-factory-ref/LICENSE +0 -21
  456. package/references/acp-factory-ref/README.md +0 -341
  457. package/references/acp-factory-ref/package-lock.json +0 -3102
  458. package/references/acp-factory-ref/package.json +0 -96
  459. package/references/acp-factory-ref/python/CHANGELOG.md +0 -33
  460. package/references/acp-factory-ref/python/LICENSE +0 -21
  461. package/references/acp-factory-ref/python/Makefile +0 -57
  462. package/references/acp-factory-ref/python/README.md +0 -253
  463. package/references/acp-factory-ref/python/pyproject.toml +0 -73
  464. package/references/acp-factory-ref/python/tests/__init__.py +0 -0
  465. package/references/acp-factory-ref/python/tests/e2e/__init__.py +0 -1
  466. package/references/acp-factory-ref/python/tests/e2e/test_codex_e2e.py +0 -349
  467. package/references/acp-factory-ref/python/tests/e2e/test_gemini_e2e.py +0 -165
  468. package/references/acp-factory-ref/python/tests/e2e/test_opencode_e2e.py +0 -296
  469. package/references/acp-factory-ref/python/tests/test_client_handler.py +0 -543
  470. package/references/acp-factory-ref/python/tests/test_pushable.py +0 -199
  471. package/references/claude-code-acp/.github/workflows/ci.yml +0 -45
  472. package/references/claude-code-acp/.github/workflows/publish.yml +0 -34
  473. package/references/claude-code-acp/.prettierrc.json +0 -4
  474. package/references/claude-code-acp/CHANGELOG.md +0 -249
  475. package/references/claude-code-acp/LICENSE +0 -222
  476. package/references/claude-code-acp/README.md +0 -53
  477. package/references/claude-code-acp/docs/RELEASES.md +0 -24
  478. package/references/claude-code-acp/eslint.config.js +0 -48
  479. package/references/claude-code-acp/package-lock.json +0 -4570
  480. package/references/claude-code-acp/package.json +0 -88
  481. package/references/claude-code-acp/scripts/release.sh +0 -119
  482. package/references/claude-code-acp/src/acp-agent.ts +0 -2065
  483. package/references/claude-code-acp/src/index.ts +0 -26
  484. package/references/claude-code-acp/src/lib.ts +0 -38
  485. package/references/claude-code-acp/src/mcp-server.ts +0 -911
  486. package/references/claude-code-acp/src/settings.ts +0 -522
  487. package/references/claude-code-acp/src/tests/.claude/commands/quick-math.md +0 -5
  488. package/references/claude-code-acp/src/tests/.claude/commands/say-hello.md +0 -6
  489. package/references/claude-code-acp/src/tests/acp-agent-fork.test.ts +0 -479
  490. package/references/claude-code-acp/src/tests/acp-agent.test.ts +0 -1502
  491. package/references/claude-code-acp/src/tests/extract-lines.test.ts +0 -103
  492. package/references/claude-code-acp/src/tests/fork-session.test.ts +0 -335
  493. package/references/claude-code-acp/src/tests/replace-and-calculate-location.test.ts +0 -334
  494. package/references/claude-code-acp/src/tests/settings.test.ts +0 -617
  495. package/references/claude-code-acp/src/tests/skills-options.test.ts +0 -187
  496. package/references/claude-code-acp/src/tests/tools.test.ts +0 -318
  497. package/references/claude-code-acp/src/tests/typescript-declarations.test.ts +0 -558
  498. package/references/claude-code-acp/src/tools.ts +0 -819
  499. package/references/claude-code-acp/src/utils.ts +0 -171
  500. package/references/claude-code-acp/tsconfig.json +0 -18
  501. package/references/claude-code-acp/vitest.config.ts +0 -19
  502. package/references/multi-agent-protocol/.sudocode/issues.jsonl +0 -82
  503. package/references/multi-agent-protocol/.sudocode/specs.jsonl +0 -9
  504. package/references/multi-agent-protocol/LICENSE +0 -21
  505. package/references/multi-agent-protocol/README.md +0 -113
  506. package/references/multi-agent-protocol/docs/00-design-specification.md +0 -460
  507. package/references/multi-agent-protocol/docs/01-open-questions.md +0 -1050
  508. package/references/multi-agent-protocol/docs/02-wire-protocol.md +0 -296
  509. package/references/multi-agent-protocol/docs/03-streaming-semantics.md +0 -252
  510. package/references/multi-agent-protocol/docs/04-error-handling.md +0 -231
  511. package/references/multi-agent-protocol/docs/05-connection-model.md +0 -244
  512. package/references/multi-agent-protocol/docs/06-visibility-permissions.md +0 -243
  513. package/references/multi-agent-protocol/docs/07-federation.md +0 -259
  514. package/references/multi-agent-protocol/docs/08-macro-agent-migration.md +0 -253
  515. package/references/multi-agent-protocol/package-lock.json +0 -3239
  516. package/references/multi-agent-protocol/package.json +0 -56
  517. package/references/multi-agent-protocol/schema/meta.json +0 -337
  518. package/references/multi-agent-protocol/schema/schema.json +0 -1828
@@ -0,0 +1,1127 @@
1
+ # Implementation Details: Self-Driving Support
2
+
3
+ Detailed implementation plan for each phase, with resolved ambiguities, concrete interface designs, and precise code integration points.
4
+
5
+ Companion to [plan-self-driving-support.md](plan-self-driving-support.md) (high-level plan) and [team-templates.md](team-templates.md) (team template design).
6
+
7
+ ---
8
+
9
+ ## Ambiguities and Design Decisions
10
+
11
+ These are questions that arose when mapping the design documents to the actual codebase. Each is resolved here with rationale.
12
+
13
+ ### A1: Where does TeamRuntime sit in the dependency graph?
14
+
15
+ **Problem**: TeamRuntime needs access to RoleRegistry, AgentManager, MessageRouter, TaskBackend, and IntegrationStrategy. But these services are created independently in the CLI (`src/cli/index.ts`) and passed around via dependency injection. TeamRuntime isn't a service — it's a configuration layer that modifies how existing services behave.
16
+
17
+ **Resolution**: TeamRuntime is initialized *after* core services are created but *before* any agents are spawned. It receives service references and configures them:
18
+
19
+ ```typescript
20
+ // In CLI start/chat commands (or equivalent bootstrap path):
21
+ const eventStore = await createEventStore({ inMemory: false });
22
+ const messageRouter = createMessageRouter(eventStore);
23
+ const agentManager = createAgentManager(eventStore, messageRouter);
24
+ const taskBackend = createInMemoryTaskBackend(eventStore);
25
+
26
+ // NEW: Load and apply team configuration
27
+ let teamRuntime: TeamRuntime | null = null;
28
+ if (teamName) {
29
+ const manifest = await TeamLoader.load(teamName);
30
+ teamRuntime = new TeamRuntime(manifest, {
31
+ roleRegistry: agentManager.getRoleRegistry(), // exposed getter
32
+ messageRouter,
33
+ taskBackend,
34
+ eventStore,
35
+ });
36
+ await teamRuntime.initialize();
37
+ }
38
+ ```
39
+
40
+ TeamRuntime does three things at `initialize()`:
41
+ 1. Registers team roles into the RoleRegistry (team layer, highest priority)
42
+ 2. Sets up an IntegrationStrategy from the registry
43
+ 3. Stores team state (manifest, active strategy, task mode) for later use during spawns
44
+
45
+ TeamRuntime does **not** wrap or replace AgentManager. Instead, it hooks into the spawn flow via a **spawn interceptor** pattern (see A2).
46
+
47
+ ### A2: How does TeamRuntime intercept agent spawns?
48
+
49
+ **Problem**: When an agent is spawned within a team, the TeamRuntime needs to:
50
+ - Add topic subscriptions from the communication topology
51
+ - Set up peer routes
52
+ - Inject the role's static prompt
53
+ - Add MCP servers from the team config
54
+ - Set team environment variables
55
+
56
+ Currently, `AgentManager.spawn()` handles all of this. We need team context without a deep refactor.
57
+
58
+ **Resolution**: Add a `spawnInterceptor` hook to AgentManager. The interceptor receives the `SpawnAgentOptions` and returns modified options before the spawn proceeds.
59
+
60
+ ```typescript
61
+ // In agent-manager.ts
62
+ type SpawnInterceptor = (options: SpawnAgentOptions) => SpawnAgentOptions | Promise<SpawnAgentOptions>;
63
+
64
+ interface AgentManagerConfig {
65
+ // ... existing fields
66
+ spawnInterceptor?: SpawnInterceptor;
67
+ }
68
+ ```
69
+
70
+ TeamRuntime registers itself as the interceptor:
71
+
72
+ ```typescript
73
+ // In TeamRuntime.initialize()
74
+ agentManager.setSpawnInterceptor((options) => {
75
+ const roleManifest = this.manifest.roles.find(r => r.name === options.role);
76
+ if (!roleManifest) return options; // Unknown role, pass through
77
+
78
+ return {
79
+ ...options,
80
+ // Inject team topic subscriptions
81
+ topics: [
82
+ ...(options.topics ?? []),
83
+ ...this.getTopicsForRole(options.role),
84
+ ],
85
+ // Add team MCP servers
86
+ config: {
87
+ ...options.config,
88
+ mcpServers: [
89
+ ...(options.config?.mcpServers ?? []),
90
+ ...this.getMCPServersForRole(options.role),
91
+ ],
92
+ env: {
93
+ ...options.config?.env,
94
+ MACRO_TEAM_NAME: this.manifest.name,
95
+ MACRO_INTEGRATION_STRATEGY: this.manifest.macro_agent.integration.strategy,
96
+ MACRO_TASK_MODE: this.manifest.macro_agent.task_assignment.mode,
97
+ },
98
+ },
99
+ // Team's custom prompt replaces the default
100
+ customPrompt: roleManifest.prompt
101
+ ? this.loadedPrompts.get(roleManifest.prompt)
102
+ : options.customPrompt,
103
+ };
104
+ });
105
+ ```
106
+
107
+ The interceptor pattern is minimal — one new optional field on AgentManagerConfig, one check in spawn(). No existing code changes beyond the hook point.
108
+
109
+ **Where the hook goes in spawn()**: After options destructuring (line ~346), before capability checks (line ~379):
110
+
111
+ ```typescript
112
+ async function spawn(rawOptions: SpawnAgentOptions): Promise<SpawnedAgent> {
113
+ // Apply spawn interceptor if present
114
+ const options = spawnInterceptor
115
+ ? await spawnInterceptor(rawOptions)
116
+ : rawOptions;
117
+
118
+ const { task, task_id, parent, ... } = options;
119
+ // ... rest of spawn continues unchanged
120
+ }
121
+ ```
122
+
123
+ ### A3: How do companion agents work without a parent-child relationship?
124
+
125
+ **Problem**: The self-driving team has a judge companion — a peer of the planner, not its child. But `AgentManager.spawn()` assumes every agent has a parent (except the head manager). Companions need to be discoverable via role addressing (`{ role: "judge" }`) without being in anyone's subtree.
126
+
127
+ **Resolution**: Companions are spawned by the TeamRuntime bootstrap, not by any agent. They use `parent: null` (no parent, like a head manager) but are tagged with the team name for scoping.
128
+
129
+ ```typescript
130
+ // In TeamRuntime.bootstrap()
131
+ async bootstrap(): Promise<void> {
132
+ // 1. Spawn root agent
133
+ const root = await this.agentManager.spawn({
134
+ task: `Team root: ${this.manifest.name}`,
135
+ parent: null, // head-like agent
136
+ role: this.manifest.topology.root.role,
137
+ config: this.manifest.topology.root.config,
138
+ });
139
+ this.rootAgentId = root.id;
140
+
141
+ // 2. Spawn companions
142
+ for (const companion of this.manifest.topology.companions ?? []) {
143
+ const agent = await this.agentManager.spawn({
144
+ task: `Companion: ${companion.role}`,
145
+ parent: null, // peer, not child
146
+ role: companion.role,
147
+ config: companion.config,
148
+ });
149
+ this.companionAgentIds.push(agent.id);
150
+ }
151
+
152
+ // 3. Set up peer subscriptions between root and companions
153
+ // (neither is in the other's subtree, so explicit subscriptions needed)
154
+ for (const peerId of this.companionAgentIds) {
155
+ this.setupPeerSubscriptions(this.rootAgentId, peerId);
156
+ }
157
+ }
158
+ ```
159
+
160
+ **Peer discovery**: Companions are discoverable via `{ role: "judge" }` MAP addressing because `setupDefaultSubscriptions` already subscribes agents to their role channel when `role` is provided (line 1078-1081 in message-router.ts). No changes needed for role-based discovery.
161
+
162
+ **Subtree visibility**: Since the judge is not in the planner's subtree, it won't automatically receive worker status updates. The TeamRuntime's spawn interceptor adds explicit topic subscriptions (from the communication topology) to solve this — the judge subscribes to `work_coordination` and `health` topics.
163
+
164
+ ### A4: How does IntegrationStrategy inject into the done() handler?
165
+
166
+ **Problem**: The worker done() handler (in `src/lifecycle/handlers/worker.ts`) currently receives `WorkerHandlerDeps` which includes `mergeQueue?`. We need to replace (or augment) this with `integrationStrategy`. But `WorkerHandlerDeps` is used in `AllHandlerDeps` (in `handlers/index.ts`), which is constructed in `createDoneHandler` (in `mcp/tools/done.ts`) from `DoneToolDeps`.
167
+
168
+ The dependency chain is:
169
+ ```
170
+ DoneToolDeps → AllHandlerDeps → WorkerHandlerDeps → mergeQueue?
171
+ ```
172
+
173
+ **Resolution**: Add `integrationStrategy?` to `AllHandlerDeps` (and thus `WorkerHandlerDeps`). The worker handler checks for it first, falls back to mergeQueue:
174
+
175
+ ```typescript
176
+ // In handlers/index.ts
177
+ export interface AllHandlerDeps {
178
+ messageRouter: MessageRouter;
179
+ agentManager: AgentManager;
180
+ mergeQueue?: MergeQueueInterface; // existing
181
+ getWorkspacePath?: (agentId: string) => string | undefined;
182
+ integrationStrategy?: IntegrationStrategy; // NEW
183
+ taskMode?: 'push' | 'pull'; // NEW
184
+ }
185
+ ```
186
+
187
+ ```typescript
188
+ // In handlers/worker.ts, Step 4 (merge/integration)
189
+ if (args.status === 'completed' && context.workspacePath) {
190
+ if (deps.integrationStrategy) {
191
+ // NEW: use pluggable strategy
192
+ const result = await deps.integrationStrategy.land({
193
+ streamId: context.streamId ?? 'default',
194
+ workerBranch: sourceBranch,
195
+ integrationBranch: context.integrationBranch ?? 'main',
196
+ workerAgentId: context.agentId,
197
+ taskId: context.taskId ?? '',
198
+ workspacePath: context.workspacePath,
199
+ });
200
+ // Handle result...
201
+ } else if (deps.mergeQueue) {
202
+ // EXISTING: fall back to merge queue (backward compatible)
203
+ // ... current merge queue logic unchanged ...
204
+ }
205
+ }
206
+ ```
207
+
208
+ The `DoneToolDeps` gets a new optional field:
209
+
210
+ ```typescript
211
+ export interface DoneToolDeps {
212
+ // ... existing fields
213
+ integrationStrategy?: IntegrationStrategy;
214
+ taskMode?: 'push' | 'pull';
215
+ }
216
+ ```
217
+
218
+ And in `createDoneHandler`, it passes through to `AllHandlerDeps`:
219
+
220
+ ```typescript
221
+ const handlerDeps: AllHandlerDeps = {
222
+ messageRouter,
223
+ agentManager,
224
+ mergeQueue: workspaceManager?.getMergeQueue?.(),
225
+ getWorkspacePath: ...,
226
+ integrationStrategy: deps.integrationStrategy, // NEW
227
+ taskMode: deps.taskMode, // NEW
228
+ };
229
+ ```
230
+
231
+ **Where does the strategy come from at MCP server creation time?** The `MCPServices` type gets extended with optional team context:
232
+
233
+ ```typescript
234
+ // In mcp-server.ts
235
+ interface MCPServices {
236
+ // ... existing fields
237
+ integrationStrategy?: IntegrationStrategy;
238
+ taskMode?: 'push' | 'pull';
239
+ }
240
+ ```
241
+
242
+ TeamRuntime sets these on the MCPServices when it's active. When no team is loaded, these are undefined and the existing mergeQueue path is used.
243
+
244
+ ### A5: How does the pull-mode done() handler keep agents alive?
245
+
246
+ **Problem**: Currently, `handleWorkerDone` returns `{ shouldTerminate: true }` for completed/failed status. In pull mode, a completed worker should NOT terminate — it should continue its claim loop.
247
+
248
+ **Resolution**: The worker handler checks `deps.taskMode`:
249
+
250
+ ```typescript
251
+ // In handlers/worker.ts, final return
252
+ if (deps.taskMode === 'pull' && args.status === 'completed') {
253
+ return {
254
+ shouldTerminate: false, // Worker continues to claim next task
255
+ warnings: handlerWarnings,
256
+ };
257
+ }
258
+
259
+ return {
260
+ shouldTerminate: args.status === 'completed' || args.status === 'failed',
261
+ warnings: handlerWarnings,
262
+ };
263
+ ```
264
+
265
+ The worker agent's prompt (from the team template) instructs it to call `claim_task()` after `done()`. The `done()` tool returning `shouldTerminate: false` means the MCP server stays alive and the agent can continue.
266
+
267
+ **Idle timeout**: The agent is responsible for tracking idle time. If `claim_task()` returns empty repeatedly beyond the configured `idle_timeout_s`, the agent calls `done({ status: "completed", summary: "idle exit" })`. This is conveyed in the injected interaction pattern section of the system prompt (see team-templates.md "Interaction Pattern Injection").
268
+
269
+ ### A6: How does `claim_task` work with optimistic locking in InMemoryTaskBackend?
270
+
271
+ **Problem**: `InMemoryTaskBackend` wraps the EventStore. Tasks are materialized views from events. There's no explicit version field for CAS operations. Under concurrency (multiple MCP server processes calling claim), we need atomicity.
272
+
273
+ **Resolution**: The EventStore's SQLite backend serializes writes. Since all task state comes from events, a claim operation is:
274
+
275
+ 1. Read task state (from materialized view)
276
+ 2. Check status is `pending` or `ready` (claimable)
277
+ 3. Emit an `assign` event
278
+ 4. The materialized view updates the task to `assigned`
279
+
280
+ Since the EventStore is SQLite-backed and writes are serialized, two concurrent claims for the same task will be ordered — the second one will see the task is already assigned and fail.
281
+
282
+ ```typescript
283
+ // In memory.ts
284
+ async claim(agentId: string, filters?: ClaimFilters): Promise<ExtendedTask | null> {
285
+ // 1. Find claimable tasks matching filters
286
+ const candidates = this.eventStore.listTasks({
287
+ status: 'pending',
288
+ ...filters,
289
+ }).filter(t => !t.assigned_agent);
290
+
291
+ if (candidates.length === 0) return null;
292
+
293
+ // 2. Pick one (first match, or random for load distribution)
294
+ const target = candidates[0];
295
+
296
+ // 3. Try to assign — this emits an event through SQLite
297
+ // If another process claimed it first, the task status won't be 'pending'
298
+ // and assign() will throw (status transition validation)
299
+ try {
300
+ await this.assign(target.id, agentId);
301
+ await this.start(target.id);
302
+ return this.get(target.id);
303
+ } catch {
304
+ // Another agent claimed it — return null to retry
305
+ return null;
306
+ }
307
+ }
308
+ ```
309
+
310
+ This is not true CAS, but it works because:
311
+ - SQLite serializes writes across processes
312
+ - `assign()` validates status transitions (only `pending` → `assigned` is valid)
313
+ - The window between read and write is small (same process, synchronous event emit)
314
+ - Under contention, the failure mode is "try again" not "corrupt state"
315
+
316
+ For the InMemory backend this is sufficient. If a higher-concurrency backend is needed later (e.g., Sudocode), it can implement true CAS.
317
+
318
+ ### A7: How does the system prompt change for team-loaded roles?
319
+
320
+ **Problem**: Currently, `generateSystemPrompt()` in `system-prompt.ts` generates a fixed structure. Team templates provide static prompt files and interaction pattern injections. How do these compose?
321
+
322
+ **Resolution**: Extend `SystemPromptContext` with optional team fields:
323
+
324
+ ```typescript
325
+ interface SystemPromptContext {
326
+ // ... existing fields
327
+ teamPrompt?: string; // Static prompt from team template prompts/<role>.md
328
+ interactionPatterns?: string[]; // Auto-injected sections (pull mode, trunk integration, etc.)
329
+ }
330
+ ```
331
+
332
+ The prompt assembly order in `generateSystemPrompt()` becomes:
333
+
334
+ ```
335
+ 1. Identity section ← always (agent ID, task, lineage)
336
+ 2. Role section ← existing role guidance OR team prompt (mutually exclusive)
337
+ 3. Interaction patterns ← NEW: auto-injected operational sections
338
+ 4. MCP tools listing ← always (capability-filtered)
339
+ 5. Communication guidelines ← always
340
+ 6. Guidelines section ← always (execution, error handling)
341
+ ```
342
+
343
+ In `AgentManager.spawn()`, the team prompt replaces the default role section:
344
+
345
+ ```typescript
346
+ // Current code (line ~421-424):
347
+ const resolvedRole = roleRegistry.resolveRole(role ?? "worker");
348
+ if (resolvedRole.systemPrompt) {
349
+ systemPrompt += `\n\n# Role-Specific Instructions\n\n${resolvedRole.systemPrompt}`;
350
+ }
351
+
352
+ // With team support:
353
+ const resolvedRole = roleRegistry.resolveRole(role ?? "worker");
354
+ if (teamPrompt) {
355
+ // Team prompt replaces role.systemPrompt
356
+ systemPrompt += `\n\n# Role Instructions\n\n${teamPrompt}`;
357
+ } else if (resolvedRole.systemPrompt) {
358
+ systemPrompt += `\n\n# Role-Specific Instructions\n\n${resolvedRole.systemPrompt}`;
359
+ }
360
+
361
+ // Append interaction pattern sections
362
+ for (const pattern of interactionPatterns ?? []) {
363
+ systemPrompt += `\n\n${pattern}`;
364
+ }
365
+ ```
366
+
367
+ The spawn interceptor (A2) provides `teamPrompt` and `interactionPatterns` via the spawn options. This requires adding these fields to `SpawnAgentOptions` and `SystemPromptContext`.
368
+
369
+ ### A8: What happens to existing behavior when no team is loaded?
370
+
371
+ **Problem**: We need to ensure that without `--team`, everything works exactly as before.
372
+
373
+ **Resolution**: All new code paths are guarded by optional checks:
374
+
375
+ - No `spawnInterceptor`? Options pass through unchanged.
376
+ - No `integrationStrategy`? Worker handler falls back to `mergeQueue` (existing path).
377
+ - No `taskMode`? Done handler returns `shouldTerminate: true` (existing behavior).
378
+ - No `teamPrompt`? System prompt uses `resolvedRole.systemPrompt` (existing behavior).
379
+ - No team roles registered? RoleRegistry resolves to built-in roles (existing behavior).
380
+
381
+ The `queue` integration strategy wraps the existing merge queue with no behavioral change.
382
+
383
+ ### A9: How do signal channels map to topics at runtime?
384
+
385
+ **Problem**: The team manifest defines named channels (`task_updates`, `work_coordination`, `health`) with signal lists. The router has `topic` channels. How do we bridge these?
386
+
387
+ **Resolution**: Each named channel becomes a topic. When the TeamRuntime's spawn interceptor adds topics for a role, it uses the channel names:
388
+
389
+ ```yaml
390
+ # In team.yaml
391
+ communication:
392
+ channels:
393
+ task_updates:
394
+ signals: [TASK_CREATED, TASK_COMPLETED, TASK_FAILED]
395
+ subscriptions:
396
+ planner:
397
+ - channel: task_updates
398
+ ```
399
+
400
+ Maps to:
401
+
402
+ ```typescript
403
+ // In TeamRuntime.getTopicsForRole("planner")
404
+ return ["task_updates"]; // topic name = channel name
405
+ ```
406
+
407
+ When an agent emits a signal (e.g., `TASK_CREATED`), it publishes to the channel's topic:
408
+
409
+ ```typescript
410
+ // In a future emit_signal MCP tool (or via emit_status with signal details)
411
+ messageRouter.sendToAddress({
412
+ from: agentId,
413
+ to: { scope: "task_updates" }, // ScopeAddress → routes to topic subscribers
414
+ content: JSON.stringify({ signal: "TASK_CREATED", taskId, ... }),
415
+ });
416
+ ```
417
+
418
+ **Signal filtering**: For subscriptions with specific signals (e.g., `judge subscribes to task_updates but only TASK_FAILED`), filtering happens at message receive time. The agent receives all messages on the `task_updates` topic, but the MCP tool (or a thin filtering wrapper) only surfaces messages matching the subscribed signals. This avoids adding filtering complexity to the router.
419
+
420
+ Implementation options for filtering:
421
+ 1. **Filter in `check_messages`**: When the MCP tool retrieves messages, apply the signal filter from the team manifest. This is simplest.
422
+ 2. **Separate topics per signal**: `task_updates.TASK_CREATED`, `task_updates.TASK_FAILED`. More topics but no filtering needed. Verbose.
423
+ 3. **Router-level filter**: Add filter predicates to subscriptions. Most powerful but changes the router.
424
+
425
+ **Recommendation**: Option 1 (filter in check_messages) for Phase 1. It's the least invasive. The team manifest's signal filters are stored in TeamRuntime and applied when the agent reads messages. Can upgrade to option 3 later if performance requires it.
426
+
427
+ ### A10: Enforcement of emissions — where does it happen?
428
+
429
+ **Problem**: The team manifest declares what signals a role can emit. The current router has no emission restrictions.
430
+
431
+ **Resolution**: Enforcement happens at the MCP tool level, not the router level. When an agent calls `emit_status` (or a future `emit_signal` tool), the tool handler checks the team manifest's `emissions` declaration for the agent's role.
432
+
433
+ ```typescript
434
+ // In the emit_status tool handler (or a wrapper)
435
+ if (teamRuntime && enforcement === 'strict') {
436
+ const allowed = teamRuntime.getAllowedEmissions(context.role);
437
+ if (allowed && !allowed.includes(signal)) {
438
+ return { error: `Role ${context.role} cannot emit signal ${signal}` };
439
+ }
440
+ }
441
+ ```
442
+
443
+ For `permissive` mode, the check logs a warning but allows the emission. For `audit` mode, it records the emission in the EventStore for later analysis.
444
+
445
+ This keeps the router clean (it doesn't need to know about team-level enforcement) and is easy to implement as a thin layer in the MCP tool.
446
+
447
+ ---
448
+
449
+ ## Phase 1: Team Template System — Implementation Details
450
+
451
+ ### 1.1 TeamManifest Types (`src/teams/types.ts`)
452
+
453
+ ```typescript
454
+ // ─────────────────────────────────────────────────────────────
455
+ // Core manifest types (generic, portable)
456
+ // ─────────────────────────────────────────────────────────────
457
+
458
+ export interface TeamManifest {
459
+ /** Team name (directory name) */
460
+ name: string;
461
+ /** Human-readable description */
462
+ description: string;
463
+ /** Schema version */
464
+ version: number;
465
+
466
+ /** Role names used by this team */
467
+ roles: string[];
468
+
469
+ /** Agent spawn topology */
470
+ topology: TeamTopology;
471
+
472
+ /** Communication topology */
473
+ communication: TeamCommunication;
474
+
475
+ /** macro-agent specific extensions */
476
+ macro_agent: MacroAgentExtensions;
477
+
478
+ /** Resolved role definitions (populated by TeamLoader) */
479
+ _resolvedRoles: Map<string, ResolvedTeamRole>;
480
+ /** Loaded prompt contents (populated by TeamLoader) */
481
+ _loadedPrompts: Map<string, string>;
482
+ /** Loaded MCP server configs (populated by TeamLoader) */
483
+ _mcpServers: Map<string, McpServerConfig[]>;
484
+ }
485
+
486
+ // ─────────────────────────────────────────────────────────────
487
+ // Topology
488
+ // ─────────────────────────────────────────────────────────────
489
+
490
+ export interface TeamTopology {
491
+ /** The initial agent spawned when the team starts */
492
+ root: TopologyNode;
493
+ /** Agents spawned alongside root (peers, not children) */
494
+ companions?: TopologyNode[];
495
+ /** Which roles can spawn which other roles */
496
+ spawn_rules?: Record<string, string[]>;
497
+ }
498
+
499
+ export interface TopologyNode {
500
+ role: string;
501
+ prompt?: string; // path to prompt file relative to team dir
502
+ config?: {
503
+ model?: string;
504
+ [key: string]: unknown;
505
+ };
506
+ }
507
+
508
+ // ─────────────────────────────────────────────────────────────
509
+ // Communication
510
+ // ─────────────────────────────────────────────────────────────
511
+
512
+ export interface TeamCommunication {
513
+ /** Named signal channels */
514
+ channels?: Record<string, ChannelDefinition>;
515
+ /** Per-role subscription declarations */
516
+ subscriptions?: Record<string, ChannelSubscription[]>;
517
+ /** Per-role emission declarations */
518
+ emissions?: Record<string, string[]>;
519
+ /** Routing configuration */
520
+ routing?: CommunicationRouting;
521
+ /** Enforcement level */
522
+ enforcement?: 'strict' | 'permissive' | 'audit';
523
+ }
524
+
525
+ export interface ChannelDefinition {
526
+ description?: string;
527
+ signals: string[];
528
+ }
529
+
530
+ export interface ChannelSubscription {
531
+ channel: string;
532
+ /** If omitted, subscribes to all signals in the channel */
533
+ signals?: string[];
534
+ }
535
+
536
+ export interface CommunicationRouting {
537
+ /** Status flow direction */
538
+ status?: 'upstream';
539
+ /** Explicit peer connections */
540
+ peers?: PeerConnection[];
541
+ }
542
+
543
+ export interface PeerConnection {
544
+ from: string; // role name
545
+ to: string; // role name
546
+ via: 'direct' | 'topic' | 'scope';
547
+ signals?: string[];
548
+ }
549
+
550
+ // ─────────────────────────────────────────────────────────────
551
+ // macro-agent extensions
552
+ // ─────────────────────────────────────────────────────────────
553
+
554
+ export interface MacroAgentExtensions {
555
+ task_assignment?: {
556
+ mode: 'push' | 'pull';
557
+ pull?: {
558
+ idle_timeout_s?: number;
559
+ claim_retry_delay_ms?: number;
560
+ max_concurrent_per_agent?: number;
561
+ };
562
+ };
563
+
564
+ integration?: {
565
+ strategy: string; // 'queue' | 'trunk' | 'optimistic' | custom name
566
+ config?: Record<string, unknown>;
567
+ };
568
+
569
+ lifecycle?: {
570
+ continuations?: {
571
+ enabled: boolean;
572
+ max_history_messages?: number;
573
+ checkpoint_interval?: 'round_trip' | 'none';
574
+ };
575
+ scaling?: {
576
+ min_workers?: number;
577
+ max_workers?: number;
578
+ scale_on?: 'task_queue_depth' | 'manual';
579
+ idle_drain?: boolean;
580
+ };
581
+ };
582
+
583
+ observability?: {
584
+ metrics_window_s?: number;
585
+ snapshot_interval_s?: number;
586
+ };
587
+ }
588
+
589
+ // ─────────────────────────────────────────────────────────────
590
+ // Role definition within a team template
591
+ // ─────────────────────────────────────────────────────────────
592
+
593
+ export interface TeamRoleDefinition {
594
+ name: string;
595
+ extends?: string;
596
+ display_name?: string;
597
+ description?: string;
598
+
599
+ /** Full replacement capability list */
600
+ capabilities?: string[];
601
+
602
+ /** Additive/subtractive capabilities (relative to extends) */
603
+ capabilities_add?: string[];
604
+ capabilities_remove?: string[];
605
+
606
+ /** Path to prompt file (relative to team dir) */
607
+ prompt?: string;
608
+
609
+ /** macro-agent specific role config */
610
+ macro_agent?: {
611
+ workspace?: {
612
+ type?: string;
613
+ branch_pattern?: string;
614
+ cleanup_on_terminate?: boolean;
615
+ };
616
+ lifecycle?: {
617
+ type?: 'ephemeral' | 'persistent' | 'daemon' | 'event-driven';
618
+ cascade_terminate?: boolean;
619
+ self_cleanup?: boolean;
620
+ task_bound?: boolean;
621
+ parent_bound?: boolean;
622
+ max_duration_ms?: number;
623
+ };
624
+ };
625
+ }
626
+
627
+ /** Role definition with inheritance resolved and capabilities computed */
628
+ export interface ResolvedTeamRole {
629
+ name: string;
630
+ baseRole: string; // The built-in role this extends
631
+ capabilities: string[]; // Final computed capability set
632
+ prompt?: string; // Loaded prompt content
633
+ roleDefinition: import('../roles/types.js').RoleDefinition; // For RoleRegistry
634
+ }
635
+ ```
636
+
637
+ ### 1.2 TeamLoader (`src/teams/team-loader.ts`)
638
+
639
+ ```
640
+ TeamLoader.load(teamName: string, basePath?: string): Promise<TeamManifest>
641
+
642
+ Steps:
643
+ 1. Resolve directory: basePath ?? cwd / .macro-agent/teams/<teamName>/
644
+ 2. Read and parse team.yaml (use yaml library, already in deps or add)
645
+ 3. Validate manifest against TeamManifest schema (Zod validation)
646
+ 4. For each role name in manifest.roles:
647
+ a. Check roles/<name>.yaml exists → parse TeamRoleDefinition
648
+ b. If not found, check if it's a built-in role name → use as-is
649
+ c. Resolve extends chain:
650
+ - Load parent role from RoleRegistry
651
+ - Compute final capabilities:
652
+ - If TeamRoleDefinition.capabilities is set → full replacement
653
+ - If .capabilities_add/.capabilities_remove → parent.capabilities + add - remove
654
+ d. Build ResolvedTeamRole with final RoleDefinition
655
+ 5. For each prompt reference in topology and roles:
656
+ a. Read prompts/<name>.md → store in _loadedPrompts map
657
+ 6. If tools/mcp-servers.json exists:
658
+ a. Parse → store per-role MCP server configs in _mcpServers map
659
+ 7. Validate communication topology:
660
+ a. All subscription channel refs exist in channels
661
+ b. All emission signals exist in some channel
662
+ c. All peer connection roles exist in manifest.roles
663
+ 8. Return fully resolved TeamManifest
664
+ ```
665
+
666
+ **Dependencies**: `js-yaml` for YAML parsing (add to package.json), `zod` for validation (already used).
667
+
668
+ **Error handling**: TeamLoader throws typed errors (`TeamLoadError`) with specific codes: `MANIFEST_NOT_FOUND`, `INVALID_MANIFEST`, `ROLE_NOT_FOUND`, `PROMPT_NOT_FOUND`, `INVALID_COMMUNICATION`.
669
+
670
+ ### 1.3 TeamRuntime (`src/teams/team-runtime.ts`)
671
+
672
+ ```typescript
673
+ export class TeamRuntime {
674
+ private manifest: TeamManifest;
675
+ private services: TeamServices;
676
+ private integrationStrategy?: IntegrationStrategy;
677
+ private rootAgentId?: string;
678
+ private companionAgentIds: string[] = [];
679
+
680
+ constructor(manifest: TeamManifest, services: TeamServices);
681
+
682
+ /** Wire team config into running services */
683
+ async initialize(): Promise<void>;
684
+
685
+ /** Spawn root + companion agents */
686
+ async bootstrap(): Promise<{ rootId: string; companionIds: string[] }>;
687
+
688
+ /** Tear down team (terminate agents, clean up) */
689
+ async teardown(): Promise<void>;
690
+
691
+ /** Get integration strategy (for DoneToolDeps) */
692
+ getIntegrationStrategy(): IntegrationStrategy | undefined;
693
+
694
+ /** Get task mode (for DoneToolDeps) */
695
+ getTaskMode(): 'push' | 'pull';
696
+
697
+ /** Get topics a role should subscribe to (for spawn interceptor) */
698
+ getTopicsForRole(role: string): string[];
699
+
700
+ /** Get MCP servers for a role (for spawn interceptor) */
701
+ getMCPServersForRole(role: string): McpServerConfig[];
702
+
703
+ /** Get the loaded prompt for a role (for spawn interceptor) */
704
+ getPromptForRole(role: string): string | undefined;
705
+
706
+ /** Get interaction pattern injection sections (for spawn interceptor) */
707
+ getInteractionPatterns(): string[];
708
+
709
+ /** Check if a signal emission is allowed for a role */
710
+ isEmissionAllowed(role: string, signal: string): boolean;
711
+
712
+ /** Get active manifest (for API) */
713
+ getManifest(): TeamManifest;
714
+ }
715
+
716
+ interface TeamServices {
717
+ roleRegistry: DefaultRoleRegistry;
718
+ messageRouter: MessageRouter;
719
+ taskBackend: TaskBackend;
720
+ eventStore: EventStore;
721
+ agentManager: AgentManager;
722
+ }
723
+ ```
724
+
725
+ ### 1.4 SpawnInterceptor in AgentManager
726
+
727
+ Changes to `src/agent/agent-manager.ts`:
728
+
729
+ 1. Add `spawnInterceptor?: SpawnInterceptor` to `AgentManagerConfig`
730
+ 2. Add `setSpawnInterceptor(fn)` method to AgentManager
731
+ 3. In `spawn()`, call interceptor before proceeding (see A2 above)
732
+ 4. Add `customPrompt?: string` to `SpawnAgentOptions` for team prompt injection
733
+ 5. Expose `getRoleRegistry()` getter for TeamRuntime access
734
+
735
+ ### 1.5 CLI Integration
736
+
737
+ Changes to `src/cli/index.ts`:
738
+
739
+ ```typescript
740
+ program
741
+ .command("start")
742
+ .option("--team <name>", "Load team template")
743
+ .action(async (options) => {
744
+ const eventStore = await createEventStore({ inMemory: false });
745
+ const messageRouter = createMessageRouter(eventStore);
746
+ const agentManager = createAgentManager(eventStore, messageRouter);
747
+
748
+ // Load team if specified
749
+ let teamRuntime: TeamRuntime | null = null;
750
+ if (options.team) {
751
+ const manifest = await TeamLoader.load(options.team);
752
+ teamRuntime = new TeamRuntime(manifest, {
753
+ roleRegistry: agentManager.getRoleRegistry(),
754
+ messageRouter,
755
+ taskBackend: createInMemoryTaskBackend(eventStore),
756
+ eventStore,
757
+ agentManager,
758
+ });
759
+ await teamRuntime.initialize();
760
+ }
761
+
762
+ // Create API server (pass teamRuntime for API endpoints)
763
+ const server = createAPIServer(
764
+ { eventStore, agentManager, taskManager, messageRouter, teamRuntime },
765
+ { port: parseInt(options.port), host: options.host }
766
+ );
767
+
768
+ await server.start();
769
+
770
+ // Bootstrap team agents if team loaded
771
+ if (teamRuntime) {
772
+ const { rootId, companionIds } = await teamRuntime.bootstrap();
773
+ console.log(`Team '${options.team}' started: root=${rootId}, companions=${companionIds.join(', ')}`);
774
+ }
775
+ });
776
+ ```
777
+
778
+ ### 1.6 Testing Strategy
779
+
780
+ **Unit tests** (`src/teams/__tests__/`):
781
+ - `team-loader.test.ts`: Parse valid manifest, handle missing files, validate schema, resolve role inheritance, compute capabilities
782
+ - `team-runtime.test.ts`: Role registration, spawn interceptor behavior, topic computation, prompt resolution, integration strategy selection
783
+
784
+ **Integration test** (`src/teams/__tests__/team-integration.test.ts`):
785
+ - Load a fixture team template from `src/teams/__tests__/fixtures/test-team/`
786
+ - Initialize TeamRuntime with real services
787
+ - Spawn an agent and verify: correct topics subscribed, correct MCP servers, correct prompt, correct env vars
788
+
789
+ ---
790
+
791
+ ## Phase 2: Pluggable Integration Strategies — Implementation Details
792
+
793
+ ### New Module: `src/workspace/strategies/`
794
+
795
+ ```
796
+ src/workspace/strategies/
797
+ ├── types.ts # IntegrationStrategy, LandRequest, LandResult
798
+ ├── registry.ts # IntegrationStrategyRegistry
799
+ ├── queue.ts # QueueIntegrationStrategy (wraps merge queue)
800
+ ├── trunk.ts # TrunkIntegrationStrategy (push + rebase)
801
+ ├── optimistic.ts # OptimisticIntegrationStrategy (push + async validate)
802
+ └── index.ts # Re-exports
803
+ ```
804
+
805
+ ### IntegrationStrategy interface (`types.ts`)
806
+
807
+ As defined in plan-self-driving-support.md. No changes needed.
808
+
809
+ ### QueueIntegrationStrategy (`queue.ts`)
810
+
811
+ This wraps the existing merge queue behavior from `handleWorkerDone` Step 4 (worker.ts lines ~229-387). It extracts that logic into the strategy:
812
+
813
+ ```typescript
814
+ export class QueueIntegrationStrategy implements IntegrationStrategy {
815
+ readonly name = 'queue';
816
+ private mergeQueue?: MergeQueueInterface;
817
+
818
+ async initialize(streamId: string, config: Record<string, unknown>): Promise<void> {
819
+ // mergeQueue is injected via constructor or config
820
+ }
821
+
822
+ async land(request: LandRequest): Promise<LandResult> {
823
+ if (!this.mergeQueue) {
824
+ return { status: 'failed', error: 'No merge queue configured' };
825
+ }
826
+ // Extract logic from current worker.ts:
827
+ // 1. Detect source/target branch from workspace
828
+ // 2. Submit merge request to queue
829
+ // 3. Return result
830
+ const entry = await this.mergeQueue.submit({
831
+ streamId: request.streamId,
832
+ sourceBranch: request.workerBranch,
833
+ targetBranch: request.integrationBranch,
834
+ agentId: request.workerAgentId,
835
+ taskId: request.taskId,
836
+ });
837
+ return { status: 'landed', mergeCommit: entry.id };
838
+ }
839
+ }
840
+ ```
841
+
842
+ ### TrunkIntegrationStrategy (`trunk.ts`)
843
+
844
+ ```typescript
845
+ export class TrunkIntegrationStrategy implements IntegrationStrategy {
846
+ readonly name = 'trunk';
847
+
848
+ async land(request: LandRequest): Promise<LandResult> {
849
+ const { maxRetries = 3, conflictAction = 'abandon' } = request.options ?? {};
850
+
851
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
852
+ try {
853
+ // 1. Fetch latest integration branch
854
+ await git.fetch(request.workspacePath, 'origin', request.integrationBranch);
855
+
856
+ // 2. Rebase worker branch onto integration branch
857
+ await git.rebase(request.workspacePath, `origin/${request.integrationBranch}`);
858
+
859
+ // 3. Push to integration branch
860
+ await git.push(request.workspacePath, 'origin', request.integrationBranch);
861
+
862
+ // 4. Get merge commit SHA
863
+ const sha = await git.getHead(request.workspacePath);
864
+ return { status: 'landed', mergeCommit: sha };
865
+ } catch (error) {
866
+ if (isConflictError(error)) {
867
+ await git.rebaseAbort(request.workspacePath);
868
+ if (attempt === maxRetries) {
869
+ const conflictFiles = await git.getConflictFiles(request.workspacePath);
870
+ return {
871
+ status: 'conflict',
872
+ conflictFiles,
873
+ action: conflictAction === 'abandon' ? 'abandoned' : 'queued_for_resolution',
874
+ };
875
+ }
876
+ // Retry with fresh state
877
+ continue;
878
+ }
879
+ return { status: 'failed', error: String(error) };
880
+ }
881
+ }
882
+ return { status: 'retry_exhausted', attempts: maxRetries };
883
+ }
884
+ }
885
+ ```
886
+
887
+ **Git operations**: These will use the same git helpers used elsewhere in the codebase (check if `src/workspace/` has git utility functions, otherwise add a thin wrapper around child_process exec of git commands).
888
+
889
+ ### IntegrationStrategyRegistry (`registry.ts`)
890
+
891
+ ```typescript
892
+ type StrategyFactory = (config: Record<string, unknown>) => IntegrationStrategy;
893
+
894
+ export class IntegrationStrategyRegistry {
895
+ private factories = new Map<string, StrategyFactory>();
896
+
897
+ register(name: string, factory: StrategyFactory): void;
898
+ get(name: string, config?: Record<string, unknown>): IntegrationStrategy;
899
+ has(name: string): boolean;
900
+ list(): string[];
901
+ }
902
+
903
+ // Register built-ins at module load
904
+ export const defaultRegistry = new IntegrationStrategyRegistry();
905
+ defaultRegistry.register('queue', (config) => new QueueIntegrationStrategy(config));
906
+ defaultRegistry.register('trunk', (config) => new TrunkIntegrationStrategy(config));
907
+ defaultRegistry.register('optimistic', (config) => new OptimisticIntegrationStrategy(config));
908
+ ```
909
+
910
+ ### Worker handler refactor
911
+
912
+ The key change in `src/lifecycle/handlers/worker.ts` is replacing the direct merge queue calls in Step 4 with a strategy dispatch. The existing merge queue logic moves into `QueueIntegrationStrategy`. The handler becomes:
913
+
914
+ ```
915
+ Step 1: Commit uncommitted changes (unchanged)
916
+ Step 2: Create checkpoints (unchanged)
917
+ Step 3: Handle blocked/deferred (unchanged)
918
+ Step 4: Emit WORKER_DONE signal (unchanged)
919
+ Step 5: Land changes via strategy (NEW)
920
+ - if integrationStrategy: call strategy.land()
921
+ - else if mergeQueue: existing merge queue logic (backward compat)
922
+ - else: skip integration
923
+ Step 6: Signal descendants (unchanged)
924
+ Step 7: Return shouldTerminate based on taskMode (MODIFIED for pull mode)
925
+ ```
926
+
927
+ **Backward compatibility**: When no team is loaded and no integrationStrategy is set, the handler falls back to the existing mergeQueue path. Zero behavior change for existing users.
928
+
929
+ ---
930
+
931
+ ## Phase 3: Task Pull Model — Implementation Details
932
+
933
+ ### TaskBackend interface additions (`src/task/backend/types.ts`)
934
+
935
+ ```typescript
936
+ export interface TaskBackend {
937
+ // ... existing methods
938
+
939
+ // NEW: Pull model
940
+ claim(agentId: string, filters?: ClaimFilters): Promise<ExtendedTask | null>;
941
+ unclaim(taskId: TaskId, reason?: string): Promise<void>;
942
+ listClaimable(filters?: ClaimFilters): Promise<ExtendedTask[]>;
943
+ }
944
+
945
+ export interface ClaimFilters {
946
+ /** Only tasks with these tags */
947
+ tags?: string[];
948
+ /** Only tasks with these statuses (default: ['pending']) */
949
+ status?: string[];
950
+ /** Exclude tasks that were previously claimed by this agent and failed */
951
+ excludePreviousFails?: boolean;
952
+ }
953
+ ```
954
+
955
+ ### Tags on tasks (`src/store/types/tasks.ts`)
956
+
957
+ Add `tags?: string[]` to the task type. Tags are set at creation time and used for filtered claiming.
958
+
959
+ ### New MCP tools
960
+
961
+ **`src/mcp/tools/claim_task.ts`**:
962
+ ```
963
+ claim_task(filters?: { tags?: string[], status?: string[] })
964
+ → { task: ExtendedTask } | { empty: true, message: "No claimable tasks" }
965
+ ```
966
+
967
+ **`src/mcp/tools/unclaim_task.ts`**:
968
+ ```
969
+ unclaim_task(task_id: string, reason?: string)
970
+ → { success: true }
971
+ ```
972
+
973
+ **`src/mcp/tools/list_claimable_tasks.ts`**:
974
+ ```
975
+ list_claimable_tasks(filters?: { tags?: string[] }, limit?: number)
976
+ → { tasks: ExtendedTask[] }
977
+ ```
978
+
979
+ ### Capability gating
980
+
981
+ Add `task.claim` to `CAPABILITY_TOOL_MAP` in `src/roles/capabilities.ts`:
982
+
983
+ ```typescript
984
+ 'task.claim': ['claim_task', 'unclaim_task', 'list_claimable_tasks'],
985
+ ```
986
+
987
+ ### MCP registration
988
+
989
+ In `src/mcp/mcp-server.ts`, register the three new tools alongside existing task tools. They follow the same pattern as `create_task` — gated by capability, receive `taskBackend` from MCPServices.
990
+
991
+ ---
992
+
993
+ ## Phase 4: Session Continuations — Implementation Details
994
+
995
+ ### Session history storage
996
+
997
+ Add a new event type: `conversation` events already exist in the EventStore. Session continuations build on the existing conversation/turn infrastructure:
998
+
999
+ - When an agent's `done()` is called with continuation enabled, the agent's conversation transcript is already stored as turns in the EventStore's conversation view.
1000
+ - `AgentManager.resume(agentId)` loads the conversation turns, formats them as a resume context, and spawns a new agent with that context prepended.
1001
+
1002
+ ```typescript
1003
+ // In agent-manager.ts
1004
+ async function resume(agentId: string, options?: ResumeOptions): Promise<SpawnedAgent> {
1005
+ const agent = eventStore.getAgent(agentId);
1006
+ if (!agent) throw new Error(`Agent not found: ${agentId}`);
1007
+
1008
+ // Load conversation history
1009
+ const turns = eventStore.listTurns({ agent_id: agentId });
1010
+ const maxMessages = options?.maxMessages ?? 50;
1011
+ const recentTurns = turns.slice(-maxMessages);
1012
+
1013
+ // Build resume context
1014
+ const resumeContext = formatResumeContext(recentTurns, agent);
1015
+
1016
+ // Spawn new agent with same role, task, and resume context
1017
+ return spawn({
1018
+ task: agent.task,
1019
+ task_id: agent.task_id,
1020
+ parent: agent.parent,
1021
+ role: agent.role,
1022
+ customPrompt: resumeContext, // Prepended to system prompt
1023
+ });
1024
+ }
1025
+ ```
1026
+
1027
+ This is the simplest approach — no new event types, no new storage. We build on the existing conversation tracking. The main implementation work is:
1028
+ 1. Ensuring conversation turns are captured during agent operation (already happening via MailService)
1029
+ 2. Building `formatResumeContext()` to create a useful summary
1030
+ 3. Adding `resume()` to AgentManager
1031
+ 4. Adding periodic checkpointing if turns aren't already captured at each round-trip
1032
+
1033
+ ---
1034
+
1035
+ ## File Change Summary
1036
+
1037
+ ### New files
1038
+ | File | Description |
1039
+ |------|-------------|
1040
+ | `src/teams/types.ts` | TeamManifest, TeamTopology, TeamCommunication, MacroAgentExtensions types |
1041
+ | `src/teams/team-loader.ts` | TeamLoader — reads and validates team template directories |
1042
+ | `src/teams/team-runtime.ts` | TeamRuntime — wires team config into running services |
1043
+ | `src/teams/index.ts` | Re-exports |
1044
+ | `src/workspace/strategies/types.ts` | IntegrationStrategy, LandRequest, LandResult |
1045
+ | `src/workspace/strategies/registry.ts` | IntegrationStrategyRegistry |
1046
+ | `src/workspace/strategies/queue.ts` | QueueIntegrationStrategy |
1047
+ | `src/workspace/strategies/trunk.ts` | TrunkIntegrationStrategy |
1048
+ | `src/workspace/strategies/optimistic.ts` | OptimisticIntegrationStrategy |
1049
+ | `src/workspace/strategies/index.ts` | Re-exports |
1050
+ | `src/mcp/tools/claim_task.ts` | claim_task MCP tool |
1051
+ | `src/mcp/tools/unclaim_task.ts` | unclaim_task MCP tool |
1052
+ | `src/mcp/tools/list_claimable_tasks.ts` | list_claimable_tasks MCP tool |
1053
+ | `.macro-agent/teams/self-driving/` | Reference team template (team.yaml, roles/, prompts/) |
1054
+ | `.macro-agent/teams/structured/` | Backward-compat structured team template |
1055
+
1056
+ ### Modified files
1057
+ | File | Change |
1058
+ |------|--------|
1059
+ | `src/agent/agent-manager.ts` | Add spawnInterceptor hook, customPrompt support, getRoleRegistry(), resume() |
1060
+ | `src/agent/system-prompt.ts` | Add teamPrompt and interactionPatterns to SystemPromptContext |
1061
+ | `src/lifecycle/handlers/index.ts` | Add integrationStrategy and taskMode to AllHandlerDeps |
1062
+ | `src/lifecycle/handlers/worker.ts` | Add strategy dispatch (A4), pull mode shouldTerminate (A5) |
1063
+ | `src/mcp/tools/done.ts` | Pass integrationStrategy/taskMode through DoneToolDeps |
1064
+ | `src/mcp/mcp-server.ts` | Register claim_task/unclaim_task/list_claimable_tasks, add MCPServices fields |
1065
+ | `src/task/backend/types.ts` | Add claim(), unclaim(), listClaimable(), ClaimFilters, tags |
1066
+ | `src/task/backend/memory.ts` | Implement claim/unclaim/listClaimable |
1067
+ | `src/store/types/tasks.ts` | Add tags field to task type |
1068
+ | `src/roles/capabilities.ts` | Add task.claim capability mapping |
1069
+ | `src/cli/index.ts` | Add --team flag, TeamLoader/TeamRuntime initialization, bootstrap |
1070
+ | `src/api/server.ts` | Add GET /api/team endpoint |
1071
+
1072
+ ---
1073
+
1074
+ ## Implementation Order and Dependencies
1075
+
1076
+ ```
1077
+ Phase 1 (Foundation)
1078
+ 1.1 Types → 1.2 Loader → 1.3 Runtime → 1.4 Spawn interceptor
1079
+ 1.5 CLI integration → 1.6 Bootstrap → 1.7 API → 1.8-1.9 Tests → 1.10 Reference template
1080
+
1081
+ Phase 2 (Integration Strategies) ── can start after 1.3
1082
+ 2.1 Types → 2.2 Registry → 2.3 Queue → 2.4 Trunk → 2.5 Optimistic
1083
+ 2.6 Worker handler refactor → 2.7 Wire into TeamRuntime → 2.8-2.11 Tests
1084
+
1085
+ Phase 3 (Task Pull) ── can start after 1.3
1086
+ 3.1 Tags → 3.2-3.4 Backend methods → 3.5 Capabilities → 3.6-3.8 MCP tools
1087
+ 3.9 Registration → 3.10 Pull mode done → 3.11 Idle timeout → 3.12-3.13 Tests
1088
+
1089
+ Phase 4 (Session Continuations) ── can start after 1.3
1090
+ 4.1-4.2 Storage → 4.3-4.4 Persistence hooks → 4.5 Resume
1091
+ 4.6-4.7 Config → 4.8-4.9 Tests
1092
+
1093
+ Phase 5 (Observability) ── depends on 2 + 3
1094
+ 5.1-5.2 Metric events → 5.3-5.5 Views → 5.6 API → 5.7-5.8 Tests
1095
+
1096
+ Phase 6 (Templates + Docs) ── depends on all
1097
+ 6.1-6.4 Reference templates, docs, E2E test
1098
+ ```
1099
+
1100
+ Phases 2, 3, and 4 can be developed in parallel once Phase 1.3 (TeamRuntime) is done, since they each hook into different parts of the system. Phase 5 requires 2 and 3 for the metric events to be meaningful.
1101
+
1102
+ ---
1103
+
1104
+ ## Resolved Open Questions
1105
+
1106
+ These questions were resolved during spec review. See `docs/spec-self-driving-support.md` for the binding decisions (RD1-RD7).
1107
+
1108
+ 1. **YAML library**: `js-yaml` (RD7).
1109
+
1110
+ 2. **Git operations for trunk strategy**: Need a thin wrapper around child_process git commands. Check if `src/workspace/` has existing helpers to reuse.
1111
+
1112
+ 3. **Optimistic strategy validation**: The strategy is thin — push + emit event. Validation is the judge agent's responsibility via its role prompt (RD5).
1113
+
1114
+ 4. **MCP subprocess team context**: Store `team_config` event in EventStore. MCP subprocess reads it to reconstruct strategy and taskMode (RD2). No HTTP API between processes.
1115
+
1116
+ 5. **Team selection**: Via `.macro-agent/config.json` (new file), CLI `--team` overrides (RD6).
1117
+
1118
+ 6. **spawn_rules vs capabilities**: spawn_rules are syntactic sugar — TeamLoader translates them into capability additions (RD3).
1119
+
1120
+ 7. **Team prompt vs base role prompt**: Team prompt replaces base systemPrompt entirely (RD4).
1121
+
1122
+ 8. **done() hardcoded roles**: Must be fixed as prerequisite (RD1). Replace with RoleRegistry lookup.
1123
+
1124
+ ### Deferred
1125
+
1126
+ - **Team template hot-reloading**: Not needed for Phase 1. The `RoleRegistry` already supports file watching, so this could be added later.
1127
+ - **Multiple simultaneous teams**: Single team focus for now. Architecture supports it but CLI only accepts one `--team` flag.