claude-code-swarm 0.3.2 → 0.3.4

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 (1129) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CLAUDE.md +4 -0
  4. package/README.md +65 -0
  5. package/package.json +4 -4
  6. package/src/__tests__/config.test.mjs +128 -0
  7. package/src/__tests__/index.test.mjs +2 -0
  8. package/src/__tests__/paths.test.mjs +13 -0
  9. package/src/config.mjs +46 -16
  10. package/src/index.mjs +3 -1
  11. package/src/map-connection.mjs +3 -3
  12. package/src/paths.mjs +5 -0
  13. package/src/sidecar-client.mjs +2 -2
  14. package/references/multi-agent-protocol/.sudocode/issues.jsonl +0 -120
  15. package/references/multi-agent-protocol/.sudocode/specs.jsonl +0 -15
  16. package/references/multi-agent-protocol/LICENSE +0 -21
  17. package/references/multi-agent-protocol/README.md +0 -113
  18. package/references/multi-agent-protocol/docs/00-design-specification.md +0 -496
  19. package/references/multi-agent-protocol/docs/01-open-questions.md +0 -1050
  20. package/references/multi-agent-protocol/docs/02-wire-protocol.md +0 -296
  21. package/references/multi-agent-protocol/docs/03-streaming-semantics.md +0 -252
  22. package/references/multi-agent-protocol/docs/04-error-handling.md +0 -231
  23. package/references/multi-agent-protocol/docs/05-connection-model.md +0 -244
  24. package/references/multi-agent-protocol/docs/06-visibility-permissions.md +0 -243
  25. package/references/multi-agent-protocol/docs/07-federation.md +0 -335
  26. package/references/multi-agent-protocol/docs/08-macro-agent-migration.md +0 -253
  27. package/references/multi-agent-protocol/docs/09-authentication.md +0 -748
  28. package/references/multi-agent-protocol/docs/10-environment-awareness.md +0 -242
  29. package/references/multi-agent-protocol/docs/10-mail-protocol.md +0 -553
  30. package/references/multi-agent-protocol/docs/11-anp-inspired-improvements.md +0 -1079
  31. package/references/multi-agent-protocol/docs/11-trajectory-protocol.md +0 -292
  32. package/references/multi-agent-protocol/docs/12-anp-implementation-plan.md +0 -641
  33. package/references/multi-agent-protocol/docs/agent-iam-integration.md +0 -877
  34. package/references/multi-agent-protocol/docs/agentic-mesh-integration-draft.md +0 -459
  35. package/references/multi-agent-protocol/docs/git-transport-draft.md +0 -251
  36. package/references/multi-agent-protocol/docs-site/Gemfile +0 -22
  37. package/references/multi-agent-protocol/docs-site/README.md +0 -82
  38. package/references/multi-agent-protocol/docs-site/_config.yml +0 -91
  39. package/references/multi-agent-protocol/docs-site/_includes/head_custom.html +0 -20
  40. package/references/multi-agent-protocol/docs-site/_sass/color_schemes/map.scss +0 -42
  41. package/references/multi-agent-protocol/docs-site/_sass/custom/custom.scss +0 -34
  42. package/references/multi-agent-protocol/docs-site/examples/full-integration.md +0 -510
  43. package/references/multi-agent-protocol/docs-site/examples/index.md +0 -138
  44. package/references/multi-agent-protocol/docs-site/examples/simple-chat.md +0 -282
  45. package/references/multi-agent-protocol/docs-site/examples/task-queue.md +0 -399
  46. package/references/multi-agent-protocol/docs-site/getting-started/index.md +0 -98
  47. package/references/multi-agent-protocol/docs-site/getting-started/installation.md +0 -219
  48. package/references/multi-agent-protocol/docs-site/getting-started/overview.md +0 -172
  49. package/references/multi-agent-protocol/docs-site/getting-started/quickstart.md +0 -237
  50. package/references/multi-agent-protocol/docs-site/index.md +0 -136
  51. package/references/multi-agent-protocol/docs-site/protocol/authentication.md +0 -391
  52. package/references/multi-agent-protocol/docs-site/protocol/connection-model.md +0 -376
  53. package/references/multi-agent-protocol/docs-site/protocol/design.md +0 -284
  54. package/references/multi-agent-protocol/docs-site/protocol/error-handling.md +0 -312
  55. package/references/multi-agent-protocol/docs-site/protocol/federation.md +0 -449
  56. package/references/multi-agent-protocol/docs-site/protocol/index.md +0 -129
  57. package/references/multi-agent-protocol/docs-site/protocol/permissions.md +0 -398
  58. package/references/multi-agent-protocol/docs-site/protocol/streaming.md +0 -353
  59. package/references/multi-agent-protocol/docs-site/protocol/wire-protocol.md +0 -369
  60. package/references/multi-agent-protocol/docs-site/sdk/api/agent.md +0 -357
  61. package/references/multi-agent-protocol/docs-site/sdk/api/client.md +0 -380
  62. package/references/multi-agent-protocol/docs-site/sdk/api/index.md +0 -62
  63. package/references/multi-agent-protocol/docs-site/sdk/api/server.md +0 -453
  64. package/references/multi-agent-protocol/docs-site/sdk/api/types.md +0 -468
  65. package/references/multi-agent-protocol/docs-site/sdk/guides/agent.md +0 -375
  66. package/references/multi-agent-protocol/docs-site/sdk/guides/authentication.md +0 -405
  67. package/references/multi-agent-protocol/docs-site/sdk/guides/client.md +0 -352
  68. package/references/multi-agent-protocol/docs-site/sdk/guides/index.md +0 -89
  69. package/references/multi-agent-protocol/docs-site/sdk/guides/server.md +0 -360
  70. package/references/multi-agent-protocol/docs-site/sdk/guides/testing.md +0 -446
  71. package/references/multi-agent-protocol/docs-site/sdk/guides/transports.md +0 -363
  72. package/references/multi-agent-protocol/docs-site/sdk/index.md +0 -206
  73. package/references/multi-agent-protocol/package-lock.json +0 -4230
  74. package/references/multi-agent-protocol/package.json +0 -56
  75. package/references/multi-agent-protocol/schema/meta.json +0 -584
  76. package/references/multi-agent-protocol/schema/schema.json +0 -3067
  77. package/references/openhive/.claude/settings.json +0 -6
  78. package/references/openhive/.dockerignore +0 -54
  79. package/references/openhive/.github/workflows/docker.yml +0 -52
  80. package/references/openhive/.sudocode/issues.jsonl +0 -24
  81. package/references/openhive/.sudocode/specs.jsonl +0 -4
  82. package/references/openhive/CLAUDE.md +0 -88
  83. package/references/openhive/Dockerfile +0 -105
  84. package/references/openhive/README.md +0 -745
  85. package/references/openhive/bin/openhive.js +0 -6
  86. package/references/openhive/cloudbuild.yaml +0 -80
  87. package/references/openhive/deploy/cloud-run.sh +0 -106
  88. package/references/openhive/deploy/openhive.env.example +0 -80
  89. package/references/openhive/deploy/openhive.service +0 -91
  90. package/references/openhive/docker-compose.yml +0 -67
  91. package/references/openhive/docker-entrypoint.sh +0 -117
  92. package/references/openhive/docs/API_MIGRATION.md +0 -176
  93. package/references/openhive/docs/DEPLOYMENT.md +0 -847
  94. package/references/openhive/docs/DESIGN_v1.md +0 -489
  95. package/references/openhive/docs/DESIGN_v2.md +0 -564
  96. package/references/openhive/docs/HEADSCALE_HOSTING_SPEC.md +0 -513
  97. package/references/openhive/docs/HIVE_SYNC_DESIGN.md +0 -2362
  98. package/references/openhive/docs/HIVE_SYNC_IMPLEMENTATION_PLAN.md +0 -1169
  99. package/references/openhive/docs/HOSTING.md +0 -601
  100. package/references/openhive/docs/IMPLEMENTATION_PLAN.md +0 -428
  101. package/references/openhive/docs/LOCAL_SETUP.md +0 -506
  102. package/references/openhive/docs/MACRO_AGENT_ATLAS_EXTENSION.md +0 -351
  103. package/references/openhive/docs/MEMORY_BANK_SYNC_SPEC.md +0 -909
  104. package/references/openhive/docs/PLAN_v1.md +0 -471
  105. package/references/openhive/docs/PLAN_v2.md +0 -623
  106. package/references/openhive/docs/WEBSOCKET.md +0 -267
  107. package/references/openhive/docs/openswarm-bootstrap-token-spec.md +0 -240
  108. package/references/openhive/ecosystem.config.cjs +0 -76
  109. package/references/openhive/fly.toml +0 -63
  110. package/references/openhive/package-lock.json +0 -17640
  111. package/references/openhive/package.json +0 -128
  112. package/references/openhive/packages/openhive-types/package-lock.json +0 -1473
  113. package/references/openhive/packages/openhive-types/package.json +0 -42
  114. package/references/openhive/packages/openhive-types/src/index.ts +0 -36
  115. package/references/openhive/packages/openhive-types/src/map-coordination.ts +0 -92
  116. package/references/openhive/packages/openhive-types/src/map-session-sync.ts +0 -50
  117. package/references/openhive/packages/openhive-types/src/map-sync.ts +0 -68
  118. package/references/openhive/packages/openhive-types/tsconfig.json +0 -15
  119. package/references/openhive/packages/openhive-types/tsconfig.tsbuildinfo +0 -1
  120. package/references/openhive/packages/openhive-types/tsup.config.ts +0 -12
  121. package/references/openhive/railway.json +0 -13
  122. package/references/openhive/railway.toml +0 -24
  123. package/references/openhive/render.yaml +0 -51
  124. package/references/openhive/src/__tests__/auth.test.ts +0 -148
  125. package/references/openhive/src/__tests__/bridge/credentials.test.ts +0 -65
  126. package/references/openhive/src/__tests__/bridge/dal.test.ts +0 -279
  127. package/references/openhive/src/__tests__/bridge/inbound.test.ts +0 -349
  128. package/references/openhive/src/__tests__/bridge/manager.test.ts +0 -419
  129. package/references/openhive/src/__tests__/bridge/mentions.test.ts +0 -83
  130. package/references/openhive/src/__tests__/bridge/outbound.test.ts +0 -209
  131. package/references/openhive/src/__tests__/bridge/slack-adapter.test.ts +0 -276
  132. package/references/openhive/src/__tests__/cli.test.ts +0 -342
  133. package/references/openhive/src/__tests__/config.test.ts +0 -205
  134. package/references/openhive/src/__tests__/coordination/coordination.test.ts +0 -1072
  135. package/references/openhive/src/__tests__/coordination/cross-instance.test.ts +0 -540
  136. package/references/openhive/src/__tests__/coordination/e2e.test.ts +0 -780
  137. package/references/openhive/src/__tests__/data-dir.test.ts +0 -332
  138. package/references/openhive/src/__tests__/db.test.ts +0 -258
  139. package/references/openhive/src/__tests__/discovery.test.ts +0 -288
  140. package/references/openhive/src/__tests__/events/dal.test.ts +0 -371
  141. package/references/openhive/src/__tests__/events/dispatch.test.ts +0 -202
  142. package/references/openhive/src/__tests__/events/e2e.test.ts +0 -528
  143. package/references/openhive/src/__tests__/events/normalizers.test.ts +0 -263
  144. package/references/openhive/src/__tests__/events/router.test.ts +0 -314
  145. package/references/openhive/src/__tests__/events/routes.test.ts +0 -407
  146. package/references/openhive/src/__tests__/follows.test.ts +0 -328
  147. package/references/openhive/src/__tests__/helpers/test-dirs.ts +0 -44
  148. package/references/openhive/src/__tests__/ingest-keys.test.ts +0 -925
  149. package/references/openhive/src/__tests__/map/sync-client-content.test.ts +0 -288
  150. package/references/openhive/src/__tests__/map/sync-client.test.ts +0 -500
  151. package/references/openhive/src/__tests__/map/sync-listener.test.ts +0 -504
  152. package/references/openhive/src/__tests__/middleware/hostname-guard.test.ts +0 -73
  153. package/references/openhive/src/__tests__/migrations.test.ts +0 -260
  154. package/references/openhive/src/__tests__/opentasks/client.test.ts +0 -497
  155. package/references/openhive/src/__tests__/opentasks/discovery.test.ts +0 -283
  156. package/references/openhive/src/__tests__/opentasks/e2e.test.ts +0 -767
  157. package/references/openhive/src/__tests__/routes/agents.test.ts +0 -417
  158. package/references/openhive/src/__tests__/routes/opentasks-content.test.ts +0 -493
  159. package/references/openhive/src/__tests__/routes/resource-content.test.ts +0 -1741
  160. package/references/openhive/src/__tests__/sessions/adapters.test.ts +0 -524
  161. package/references/openhive/src/__tests__/sessions/routes.test.ts +0 -1053
  162. package/references/openhive/src/__tests__/sessions/storage.test.ts +0 -545
  163. package/references/openhive/src/__tests__/sessions/trajectory-checkpoints.test.ts +0 -349
  164. package/references/openhive/src/__tests__/sessions/trajectory-routes.test.ts +0 -290
  165. package/references/openhive/src/__tests__/swarm/config.test.ts +0 -125
  166. package/references/openhive/src/__tests__/swarm/credentials.test.ts +0 -254
  167. package/references/openhive/src/__tests__/swarm/dal.test.ts +0 -290
  168. package/references/openhive/src/__tests__/swarm/e2e.test.ts +0 -827
  169. package/references/openhive/src/__tests__/swarm/fixtures/exit-immediately.js +0 -3
  170. package/references/openhive/src/__tests__/swarm/fixtures/map-server.js +0 -147
  171. package/references/openhive/src/__tests__/swarm/fixtures/sleep-server.js +0 -52
  172. package/references/openhive/src/__tests__/swarm/local-provider.test.ts +0 -279
  173. package/references/openhive/src/__tests__/swarm/manager.test.ts +0 -305
  174. package/references/openhive/src/__tests__/swarm/routes.test.ts +0 -396
  175. package/references/openhive/src/__tests__/swarm/workspace.test.ts +0 -257
  176. package/references/openhive/src/__tests__/swarmhub/client.test.ts +0 -324
  177. package/references/openhive/src/__tests__/swarmhub/config.test.ts +0 -213
  178. package/references/openhive/src/__tests__/swarmhub/connector.test.ts +0 -581
  179. package/references/openhive/src/__tests__/swarmhub/routes.test.ts +0 -639
  180. package/references/openhive/src/__tests__/swarmhub/slack-client.test.ts +0 -164
  181. package/references/openhive/src/__tests__/swarmhub/slack-connector.test.ts +0 -164
  182. package/references/openhive/src/__tests__/swarmhub/slack-routes.test.ts +0 -373
  183. package/references/openhive/src/__tests__/swarmhub/webhook-handler.test.ts +0 -295
  184. package/references/openhive/src/__tests__/sync/resource-sync.test.ts +0 -1418
  185. package/references/openhive/src/__tests__/sync/sync.test.ts +0 -800
  186. package/references/openhive/src/api/index.ts +0 -65
  187. package/references/openhive/src/api/middleware/auth.ts +0 -227
  188. package/references/openhive/src/api/middleware/hostname-guard.ts +0 -38
  189. package/references/openhive/src/api/routes/admin.ts +0 -366
  190. package/references/openhive/src/api/routes/agents.ts +0 -223
  191. package/references/openhive/src/api/routes/auth.ts +0 -164
  192. package/references/openhive/src/api/routes/bridges.ts +0 -384
  193. package/references/openhive/src/api/routes/comments.ts +0 -294
  194. package/references/openhive/src/api/routes/coordination.ts +0 -312
  195. package/references/openhive/src/api/routes/events.ts +0 -158
  196. package/references/openhive/src/api/routes/federation.ts +0 -367
  197. package/references/openhive/src/api/routes/feed.ts +0 -212
  198. package/references/openhive/src/api/routes/hives.ts +0 -264
  199. package/references/openhive/src/api/routes/map.ts +0 -674
  200. package/references/openhive/src/api/routes/memory-banks.ts +0 -971
  201. package/references/openhive/src/api/routes/posts.ts +0 -342
  202. package/references/openhive/src/api/routes/resource-content.ts +0 -727
  203. package/references/openhive/src/api/routes/resources.ts +0 -1013
  204. package/references/openhive/src/api/routes/search.ts +0 -45
  205. package/references/openhive/src/api/routes/sessions.ts +0 -1187
  206. package/references/openhive/src/api/routes/swarm-hosting.ts +0 -313
  207. package/references/openhive/src/api/routes/sync-protocol.ts +0 -168
  208. package/references/openhive/src/api/routes/sync.ts +0 -279
  209. package/references/openhive/src/api/routes/uploads.ts +0 -174
  210. package/references/openhive/src/api/routes/webhooks.ts +0 -603
  211. package/references/openhive/src/api/schemas/agents.ts +0 -26
  212. package/references/openhive/src/api/schemas/comments.ts +0 -22
  213. package/references/openhive/src/api/schemas/hives.ts +0 -33
  214. package/references/openhive/src/api/schemas/posts.ts +0 -37
  215. package/references/openhive/src/api/schemas/sync.ts +0 -56
  216. package/references/openhive/src/auth/index.ts +0 -2
  217. package/references/openhive/src/auth/jwks.ts +0 -58
  218. package/references/openhive/src/bridge/adapters/slack.ts +0 -306
  219. package/references/openhive/src/bridge/credentials.ts +0 -72
  220. package/references/openhive/src/bridge/inbound.ts +0 -288
  221. package/references/openhive/src/bridge/index.ts +0 -42
  222. package/references/openhive/src/bridge/manager.ts +0 -425
  223. package/references/openhive/src/bridge/mentions.ts +0 -42
  224. package/references/openhive/src/bridge/outbound.ts +0 -103
  225. package/references/openhive/src/bridge/schema.ts +0 -82
  226. package/references/openhive/src/bridge/types.ts +0 -238
  227. package/references/openhive/src/cli/network.ts +0 -480
  228. package/references/openhive/src/cli.ts +0 -620
  229. package/references/openhive/src/config.ts +0 -611
  230. package/references/openhive/src/coordination/index.ts +0 -43
  231. package/references/openhive/src/coordination/listener.ts +0 -92
  232. package/references/openhive/src/coordination/schema.ts +0 -79
  233. package/references/openhive/src/coordination/service.ts +0 -233
  234. package/references/openhive/src/coordination/types.ts +0 -177
  235. package/references/openhive/src/data-dir.ts +0 -105
  236. package/references/openhive/src/db/adapters/index.ts +0 -21
  237. package/references/openhive/src/db/adapters/postgres.ts +0 -310
  238. package/references/openhive/src/db/adapters/sqlite.ts +0 -56
  239. package/references/openhive/src/db/adapters/types.ts +0 -65
  240. package/references/openhive/src/db/dal/agents.ts +0 -430
  241. package/references/openhive/src/db/dal/bridge.ts +0 -336
  242. package/references/openhive/src/db/dal/comments.ts +0 -213
  243. package/references/openhive/src/db/dal/coordination.ts +0 -361
  244. package/references/openhive/src/db/dal/events.ts +0 -381
  245. package/references/openhive/src/db/dal/follows.ts +0 -96
  246. package/references/openhive/src/db/dal/hives.ts +0 -198
  247. package/references/openhive/src/db/dal/ingest-keys.ts +0 -176
  248. package/references/openhive/src/db/dal/instances.ts +0 -196
  249. package/references/openhive/src/db/dal/invites.ts +0 -123
  250. package/references/openhive/src/db/dal/map.ts +0 -750
  251. package/references/openhive/src/db/dal/posts.ts +0 -274
  252. package/references/openhive/src/db/dal/remote-agents.ts +0 -56
  253. package/references/openhive/src/db/dal/search.ts +0 -238
  254. package/references/openhive/src/db/dal/sync-events.ts +0 -160
  255. package/references/openhive/src/db/dal/sync-groups.ts +0 -100
  256. package/references/openhive/src/db/dal/sync-peer-configs.ts +0 -216
  257. package/references/openhive/src/db/dal/sync-peers.ts +0 -145
  258. package/references/openhive/src/db/dal/syncable-resources.ts +0 -888
  259. package/references/openhive/src/db/dal/trajectory-checkpoints.ts +0 -291
  260. package/references/openhive/src/db/dal/uploads.ts +0 -124
  261. package/references/openhive/src/db/dal/votes.ts +0 -124
  262. package/references/openhive/src/db/index.ts +0 -293
  263. package/references/openhive/src/db/providers/index.ts +0 -75
  264. package/references/openhive/src/db/providers/postgres.ts +0 -529
  265. package/references/openhive/src/db/providers/sqlite.ts +0 -1383
  266. package/references/openhive/src/db/providers/turso.ts +0 -1360
  267. package/references/openhive/src/db/providers/types.ts +0 -516
  268. package/references/openhive/src/db/schema.ts +0 -641
  269. package/references/openhive/src/discovery/index.ts +0 -403
  270. package/references/openhive/src/events/dispatch.ts +0 -106
  271. package/references/openhive/src/events/index.ts +0 -17
  272. package/references/openhive/src/events/normalizers/github.ts +0 -133
  273. package/references/openhive/src/events/normalizers/index.ts +0 -62
  274. package/references/openhive/src/events/normalizers/slack.ts +0 -50
  275. package/references/openhive/src/events/router.ts +0 -156
  276. package/references/openhive/src/events/schema.ts +0 -66
  277. package/references/openhive/src/events/types.ts +0 -130
  278. package/references/openhive/src/federation/index.ts +0 -1
  279. package/references/openhive/src/federation/service.ts +0 -776
  280. package/references/openhive/src/headscale/client.ts +0 -256
  281. package/references/openhive/src/headscale/config.ts +0 -212
  282. package/references/openhive/src/headscale/index.ts +0 -23
  283. package/references/openhive/src/headscale/manager.ts +0 -249
  284. package/references/openhive/src/headscale/sync.ts +0 -272
  285. package/references/openhive/src/headscale/types.ts +0 -231
  286. package/references/openhive/src/index.ts +0 -225
  287. package/references/openhive/src/map/client-entry.ts +0 -26
  288. package/references/openhive/src/map/index.ts +0 -76
  289. package/references/openhive/src/map/schema.ts +0 -119
  290. package/references/openhive/src/map/service.ts +0 -323
  291. package/references/openhive/src/map/sync-client.ts +0 -696
  292. package/references/openhive/src/map/sync-listener.ts +0 -409
  293. package/references/openhive/src/map/types.ts +0 -290
  294. package/references/openhive/src/network/factory.ts +0 -118
  295. package/references/openhive/src/network/headscale-provider.ts +0 -437
  296. package/references/openhive/src/network/index.ts +0 -43
  297. package/references/openhive/src/network/tailscale-client.ts +0 -289
  298. package/references/openhive/src/network/tailscale-provider.ts +0 -287
  299. package/references/openhive/src/network/types.ts +0 -178
  300. package/references/openhive/src/opentasks-client/client.ts +0 -374
  301. package/references/openhive/src/opentasks-client/index.ts +0 -7
  302. package/references/openhive/src/realtime/index.ts +0 -282
  303. package/references/openhive/src/server.ts +0 -1069
  304. package/references/openhive/src/services/email.ts +0 -177
  305. package/references/openhive/src/services/sitemap.ts +0 -135
  306. package/references/openhive/src/sessions/adapters/claude.ts +0 -466
  307. package/references/openhive/src/sessions/adapters/codex.ts +0 -265
  308. package/references/openhive/src/sessions/adapters/index.ts +0 -263
  309. package/references/openhive/src/sessions/adapters/raw.ts +0 -144
  310. package/references/openhive/src/sessions/adapters/types.ts +0 -83
  311. package/references/openhive/src/sessions/index.ts +0 -50
  312. package/references/openhive/src/sessions/storage/adapters/gcs.ts +0 -277
  313. package/references/openhive/src/sessions/storage/adapters/local.ts +0 -240
  314. package/references/openhive/src/sessions/storage/adapters/s3.ts +0 -321
  315. package/references/openhive/src/sessions/storage/index.ts +0 -231
  316. package/references/openhive/src/sessions/storage/types.ts +0 -189
  317. package/references/openhive/src/sessions/types.ts +0 -415
  318. package/references/openhive/src/shared/types/index.ts +0 -45
  319. package/references/openhive/src/shared/types/map-coordination.ts +0 -92
  320. package/references/openhive/src/shared/types/map-session-sync.ts +0 -170
  321. package/references/openhive/src/shared/types/map-sync.ts +0 -68
  322. package/references/openhive/src/skill.ts +0 -203
  323. package/references/openhive/src/storage/adapters/local.ts +0 -169
  324. package/references/openhive/src/storage/adapters/s3.ts +0 -195
  325. package/references/openhive/src/storage/index.ts +0 -64
  326. package/references/openhive/src/storage/types.ts +0 -69
  327. package/references/openhive/src/swarm/credentials.ts +0 -98
  328. package/references/openhive/src/swarm/dal.ts +0 -206
  329. package/references/openhive/src/swarm/index.ts +0 -28
  330. package/references/openhive/src/swarm/manager.ts +0 -917
  331. package/references/openhive/src/swarm/providers/local.ts +0 -338
  332. package/references/openhive/src/swarm/providers/sandboxed-local.ts +0 -478
  333. package/references/openhive/src/swarm/providers/workspace.ts +0 -52
  334. package/references/openhive/src/swarm/schema.ts +0 -43
  335. package/references/openhive/src/swarm/types.ts +0 -333
  336. package/references/openhive/src/swarmhub/client.ts +0 -279
  337. package/references/openhive/src/swarmhub/connector.ts +0 -463
  338. package/references/openhive/src/swarmhub/index.ts +0 -43
  339. package/references/openhive/src/swarmhub/routes.ts +0 -296
  340. package/references/openhive/src/swarmhub/types.ts +0 -213
  341. package/references/openhive/src/swarmhub/webhook-handler.ts +0 -126
  342. package/references/openhive/src/sync/compaction.ts +0 -193
  343. package/references/openhive/src/sync/coordination-hooks.ts +0 -154
  344. package/references/openhive/src/sync/crypto.ts +0 -79
  345. package/references/openhive/src/sync/gossip.ts +0 -136
  346. package/references/openhive/src/sync/hooks.ts +0 -202
  347. package/references/openhive/src/sync/materializer-repo.ts +0 -256
  348. package/references/openhive/src/sync/materializer.ts +0 -682
  349. package/references/openhive/src/sync/middleware.ts +0 -140
  350. package/references/openhive/src/sync/peer-resolver.ts +0 -157
  351. package/references/openhive/src/sync/resource-hooks.ts +0 -161
  352. package/references/openhive/src/sync/schema.ts +0 -158
  353. package/references/openhive/src/sync/service.ts +0 -990
  354. package/references/openhive/src/sync/types.ts +0 -369
  355. package/references/openhive/src/terminal/index.ts +0 -4
  356. package/references/openhive/src/terminal/pty-manager.ts +0 -337
  357. package/references/openhive/src/terminal/resolve-tui.ts +0 -44
  358. package/references/openhive/src/terminal/terminal-ws.ts +0 -251
  359. package/references/openhive/src/types.ts +0 -442
  360. package/references/openhive/src/utils/git-remote.ts +0 -329
  361. package/references/openhive/src/web/App.tsx +0 -77
  362. package/references/openhive/src/web/__tests__/components/dashboard/RecentActivity.test.tsx +0 -77
  363. package/references/openhive/src/web/__tests__/components/dashboard/StatsOverview.test.tsx +0 -62
  364. package/references/openhive/src/web/__tests__/components/dashboard/SwarmStatusSummary.test.tsx +0 -122
  365. package/references/openhive/src/web/__tests__/components/dashboard/SyncResourcesStatus.test.tsx +0 -104
  366. package/references/openhive/src/web/__tests__/components/layout/Sidebar.test.tsx +0 -110
  367. package/references/openhive/src/web/__tests__/components/swarm/StatusBadges.test.tsx +0 -65
  368. package/references/openhive/src/web/__tests__/components/terminal/query-responses.test.ts +0 -143
  369. package/references/openhive/src/web/__tests__/components/terminal/terminal-mouse.test.ts +0 -509
  370. package/references/openhive/src/web/__tests__/hooks/useEventsApi.test.ts +0 -378
  371. package/references/openhive/src/web/__tests__/pages/Dashboard.test.tsx +0 -57
  372. package/references/openhive/src/web/__tests__/pages/Events.test.tsx +0 -886
  373. package/references/openhive/src/web/__tests__/pages/Explore.test.tsx +0 -63
  374. package/references/openhive/src/web/__tests__/routing.test.tsx +0 -79
  375. package/references/openhive/src/web/__tests__/setup.ts +0 -37
  376. package/references/openhive/src/web/__tests__/stores/dashboard.test.ts +0 -49
  377. package/references/openhive/src/web/components/common/AgentBadge.tsx +0 -58
  378. package/references/openhive/src/web/components/common/Avatar.tsx +0 -78
  379. package/references/openhive/src/web/components/common/ErrorBoundary.tsx +0 -76
  380. package/references/openhive/src/web/components/common/Highlight.tsx +0 -79
  381. package/references/openhive/src/web/components/common/ImageUpload.tsx +0 -209
  382. package/references/openhive/src/web/components/common/LoadingSpinner.tsx +0 -37
  383. package/references/openhive/src/web/components/common/Logo.tsx +0 -21
  384. package/references/openhive/src/web/components/common/Markdown.tsx +0 -53
  385. package/references/openhive/src/web/components/common/ProtectedRoute.tsx +0 -18
  386. package/references/openhive/src/web/components/common/ThemeToggle.tsx +0 -38
  387. package/references/openhive/src/web/components/common/TimeAgo.tsx +0 -17
  388. package/references/openhive/src/web/components/common/Toast.tsx +0 -70
  389. package/references/openhive/src/web/components/common/VoteButtons.tsx +0 -100
  390. package/references/openhive/src/web/components/dashboard/RecentActivity.tsx +0 -100
  391. package/references/openhive/src/web/components/dashboard/StatsOverview.tsx +0 -40
  392. package/references/openhive/src/web/components/dashboard/SwarmStatusSummary.tsx +0 -89
  393. package/references/openhive/src/web/components/dashboard/SyncResourcesStatus.tsx +0 -81
  394. package/references/openhive/src/web/components/feed/FeedControls.tsx +0 -38
  395. package/references/openhive/src/web/components/feed/NewPostsIndicator.tsx +0 -75
  396. package/references/openhive/src/web/components/feed/PostCard.tsx +0 -129
  397. package/references/openhive/src/web/components/feed/PostList.tsx +0 -83
  398. package/references/openhive/src/web/components/layout/Footer.tsx +0 -5
  399. package/references/openhive/src/web/components/layout/Layout.tsx +0 -29
  400. package/references/openhive/src/web/components/layout/Sidebar.tsx +0 -348
  401. package/references/openhive/src/web/components/post/CommentForm.tsx +0 -59
  402. package/references/openhive/src/web/components/post/CommentTree.tsx +0 -145
  403. package/references/openhive/src/web/components/resources/MemoryBrowser.tsx +0 -208
  404. package/references/openhive/src/web/components/resources/OpenTasksSummary.tsx +0 -138
  405. package/references/openhive/src/web/components/resources/SkillBrowser.tsx +0 -284
  406. package/references/openhive/src/web/components/swarm/StatusBadges.tsx +0 -56
  407. package/references/openhive/src/web/components/terminal/TerminalPanel.tsx +0 -485
  408. package/references/openhive/src/web/components/terminal/index.ts +0 -2
  409. package/references/openhive/src/web/components/terminal/query-responses.ts +0 -70
  410. package/references/openhive/src/web/components/terminal/terminal-mouse.ts +0 -222
  411. package/references/openhive/src/web/hooks/useApi.ts +0 -740
  412. package/references/openhive/src/web/hooks/useDocumentTitle.ts +0 -49
  413. package/references/openhive/src/web/hooks/useInfiniteScroll.ts +0 -58
  414. package/references/openhive/src/web/hooks/useRealtimeUpdates.ts +0 -154
  415. package/references/openhive/src/web/hooks/useWebSocket.ts +0 -225
  416. package/references/openhive/src/web/index.html +0 -73
  417. package/references/openhive/src/web/lib/api.ts +0 -518
  418. package/references/openhive/src/web/main.tsx +0 -32
  419. package/references/openhive/src/web/pages/About.tsx +0 -131
  420. package/references/openhive/src/web/pages/Agent.tsx +0 -130
  421. package/references/openhive/src/web/pages/Agents.tsx +0 -69
  422. package/references/openhive/src/web/pages/AuthCallback.tsx +0 -75
  423. package/references/openhive/src/web/pages/Dashboard.tsx +0 -41
  424. package/references/openhive/src/web/pages/Events.tsx +0 -1025
  425. package/references/openhive/src/web/pages/Explore.tsx +0 -43
  426. package/references/openhive/src/web/pages/Hive.tsx +0 -134
  427. package/references/openhive/src/web/pages/Hives.tsx +0 -64
  428. package/references/openhive/src/web/pages/Home.tsx +0 -43
  429. package/references/openhive/src/web/pages/Login.tsx +0 -122
  430. package/references/openhive/src/web/pages/Post.tsx +0 -216
  431. package/references/openhive/src/web/pages/ResourceDetail.tsx +0 -426
  432. package/references/openhive/src/web/pages/Resources.tsx +0 -276
  433. package/references/openhive/src/web/pages/Search.tsx +0 -234
  434. package/references/openhive/src/web/pages/SessionDetail.tsx +0 -703
  435. package/references/openhive/src/web/pages/Sessions.tsx +0 -129
  436. package/references/openhive/src/web/pages/Settings.tsx +0 -826
  437. package/references/openhive/src/web/pages/SwarmCraft.tsx +0 -16
  438. package/references/openhive/src/web/pages/Swarms.tsx +0 -981
  439. package/references/openhive/src/web/pages/Terminal.tsx +0 -69
  440. package/references/openhive/src/web/postcss.config.js +0 -5
  441. package/references/openhive/src/web/public/favicon.svg +0 -11
  442. package/references/openhive/src/web/public/manifest.json +0 -21
  443. package/references/openhive/src/web/stores/auth.ts +0 -207
  444. package/references/openhive/src/web/stores/dashboard.ts +0 -23
  445. package/references/openhive/src/web/stores/realtime.ts +0 -90
  446. package/references/openhive/src/web/stores/theme.ts +0 -70
  447. package/references/openhive/src/web/stores/toast.ts +0 -63
  448. package/references/openhive/src/web/styles/globals.css +0 -503
  449. package/references/openhive/src/web/sw.ts +0 -228
  450. package/references/openhive/src/web/utils/serviceWorker.ts +0 -86
  451. package/references/openhive/src/web/vite.config.ts +0 -81
  452. package/references/openhive/tsconfig.json +0 -32
  453. package/references/openhive/tsup.config.ts +0 -17
  454. package/references/openhive/vitest.config.ts +0 -30
  455. package/references/openhive/vitest.web.config.ts +0 -20
  456. package/references/opentasks/.claude/settings.json +0 -6
  457. package/references/opentasks/.claude-plugin/plugin.json +0 -20
  458. package/references/opentasks/.lintstagedrc.json +0 -4
  459. package/references/opentasks/.prettierignore +0 -4
  460. package/references/opentasks/.prettierrc.json +0 -11
  461. package/references/opentasks/.sudocode/issues.jsonl +0 -89
  462. package/references/opentasks/.sudocode/specs.jsonl +0 -24
  463. package/references/opentasks/README.md +0 -401
  464. package/references/opentasks/docs/ARCHITECTURE.md +0 -841
  465. package/references/opentasks/docs/DESIGN.md +0 -689
  466. package/references/opentasks/docs/INTERFACE.md +0 -670
  467. package/references/opentasks/docs/PERSISTENCE.md +0 -1638
  468. package/references/opentasks/docs/PROVIDERS.md +0 -1412
  469. package/references/opentasks/docs/SCHEMA.md +0 -815
  470. package/references/opentasks/docs/TESTING.md +0 -1081
  471. package/references/opentasks/eslint.config.js +0 -58
  472. package/references/opentasks/package-lock.json +0 -4348
  473. package/references/opentasks/package.json +0 -81
  474. package/references/opentasks/skills/opentasks/SKILL.md +0 -139
  475. package/references/opentasks/skills/opentasks/dependency-management.md +0 -119
  476. package/references/opentasks/skills/opentasks/feedback-and-review.md +0 -100
  477. package/references/opentasks/skills/opentasks/linking-external-data.md +0 -103
  478. package/references/opentasks/skills/opentasks/spec-to-implementation.md +0 -98
  479. package/references/opentasks/src/__tests__/cli-tools.test.ts +0 -800
  480. package/references/opentasks/src/__tests__/cli.test.ts +0 -97
  481. package/references/opentasks/src/__tests__/p1-p3-gaps.test.ts +0 -635
  482. package/references/opentasks/src/cli.ts +0 -929
  483. package/references/opentasks/src/client/__tests__/client-crud.test.ts +0 -546
  484. package/references/opentasks/src/client/__tests__/client.test.ts +0 -658
  485. package/references/opentasks/src/client/__tests__/socket-discovery.test.ts +0 -122
  486. package/references/opentasks/src/client/client.ts +0 -560
  487. package/references/opentasks/src/client/index.ts +0 -32
  488. package/references/opentasks/src/config/__tests__/defaults.test.ts +0 -66
  489. package/references/opentasks/src/config/__tests__/env.test.ts +0 -155
  490. package/references/opentasks/src/config/__tests__/index.test.ts +0 -148
  491. package/references/opentasks/src/config/__tests__/loader.test.ts +0 -173
  492. package/references/opentasks/src/config/__tests__/merge.test.ts +0 -121
  493. package/references/opentasks/src/config/__tests__/schema.test.ts +0 -446
  494. package/references/opentasks/src/config/defaults.ts +0 -18
  495. package/references/opentasks/src/config/env.ts +0 -170
  496. package/references/opentasks/src/config/errors.ts +0 -33
  497. package/references/opentasks/src/config/index.ts +0 -63
  498. package/references/opentasks/src/config/loader.ts +0 -90
  499. package/references/opentasks/src/config/merge.ts +0 -64
  500. package/references/opentasks/src/config/schema.ts +0 -767
  501. package/references/opentasks/src/core/__tests__/conditional-redirects.test.ts +0 -116
  502. package/references/opentasks/src/core/__tests__/connections.test.ts +0 -194
  503. package/references/opentasks/src/core/__tests__/hash.test.ts +0 -161
  504. package/references/opentasks/src/core/__tests__/id.test.ts +0 -175
  505. package/references/opentasks/src/core/__tests__/init.test.ts +0 -115
  506. package/references/opentasks/src/core/__tests__/location.test.ts +0 -94
  507. package/references/opentasks/src/core/__tests__/merge-driver.test.ts +0 -300
  508. package/references/opentasks/src/core/__tests__/redirects.test.ts +0 -169
  509. package/references/opentasks/src/core/__tests__/resolve-location-target.test.ts +0 -468
  510. package/references/opentasks/src/core/__tests__/uri.test.ts +0 -228
  511. package/references/opentasks/src/core/__tests__/worktree.test.ts +0 -160
  512. package/references/opentasks/src/core/conditional-redirects.ts +0 -100
  513. package/references/opentasks/src/core/connections.ts +0 -217
  514. package/references/opentasks/src/core/discover.ts +0 -195
  515. package/references/opentasks/src/core/hash.ts +0 -74
  516. package/references/opentasks/src/core/id.ts +0 -174
  517. package/references/opentasks/src/core/index.ts +0 -108
  518. package/references/opentasks/src/core/init.ts +0 -66
  519. package/references/opentasks/src/core/location.ts +0 -139
  520. package/references/opentasks/src/core/merge-driver.ts +0 -280
  521. package/references/opentasks/src/core/redirects.ts +0 -182
  522. package/references/opentasks/src/core/uri.ts +0 -270
  523. package/references/opentasks/src/core/worktree.ts +0 -504
  524. package/references/opentasks/src/daemon/__tests__/e2e-live-agent.test.ts +0 -344
  525. package/references/opentasks/src/daemon/__tests__/e2e-session-pipeline.test.ts +0 -447
  526. package/references/opentasks/src/daemon/__tests__/e2e-watch.test.ts +0 -279
  527. package/references/opentasks/src/daemon/__tests__/entire-linker.test.ts +0 -1074
  528. package/references/opentasks/src/daemon/__tests__/entire-watcher.test.ts +0 -659
  529. package/references/opentasks/src/daemon/__tests__/flush.test.ts +0 -306
  530. package/references/opentasks/src/daemon/__tests__/integration.test.ts +0 -338
  531. package/references/opentasks/src/daemon/__tests__/ipc.test.ts +0 -406
  532. package/references/opentasks/src/daemon/__tests__/lifecycle.test.ts +0 -378
  533. package/references/opentasks/src/daemon/__tests__/lock.test.ts +0 -240
  534. package/references/opentasks/src/daemon/__tests__/methods/graph.test.ts +0 -372
  535. package/references/opentasks/src/daemon/__tests__/methods/provider.test.ts +0 -238
  536. package/references/opentasks/src/daemon/__tests__/methods/tools.test.ts +0 -690
  537. package/references/opentasks/src/daemon/__tests__/multi-location.test.ts +0 -945
  538. package/references/opentasks/src/daemon/__tests__/registry.test.ts +0 -268
  539. package/references/opentasks/src/daemon/__tests__/watcher.test.ts +0 -329
  540. package/references/opentasks/src/daemon/entire-linker.ts +0 -615
  541. package/references/opentasks/src/daemon/entire-watcher.ts +0 -415
  542. package/references/opentasks/src/daemon/factory.ts +0 -133
  543. package/references/opentasks/src/daemon/flush.ts +0 -168
  544. package/references/opentasks/src/daemon/index.ts +0 -120
  545. package/references/opentasks/src/daemon/ipc.ts +0 -491
  546. package/references/opentasks/src/daemon/lifecycle.ts +0 -1106
  547. package/references/opentasks/src/daemon/location-state.ts +0 -481
  548. package/references/opentasks/src/daemon/lock.ts +0 -168
  549. package/references/opentasks/src/daemon/methods/__tests__/graph.test.ts +0 -359
  550. package/references/opentasks/src/daemon/methods/__tests__/provider.test.ts +0 -227
  551. package/references/opentasks/src/daemon/methods/__tests__/tools.test.ts +0 -360
  552. package/references/opentasks/src/daemon/methods/__tests__/watch.test.ts +0 -656
  553. package/references/opentasks/src/daemon/methods/archive.ts +0 -193
  554. package/references/opentasks/src/daemon/methods/graph.ts +0 -274
  555. package/references/opentasks/src/daemon/methods/lifecycle.ts +0 -112
  556. package/references/opentasks/src/daemon/methods/location.ts +0 -118
  557. package/references/opentasks/src/daemon/methods/provider.ts +0 -159
  558. package/references/opentasks/src/daemon/methods/tools.ts +0 -221
  559. package/references/opentasks/src/daemon/methods/watch.ts +0 -206
  560. package/references/opentasks/src/daemon/registry.ts +0 -244
  561. package/references/opentasks/src/daemon/types.ts +0 -163
  562. package/references/opentasks/src/daemon/watcher.ts +0 -248
  563. package/references/opentasks/src/entire/__tests__/agent-registry.test.ts +0 -127
  564. package/references/opentasks/src/entire/__tests__/claude-generator.test.ts +0 -49
  565. package/references/opentasks/src/entire/__tests__/commit-msg.test.ts +0 -89
  566. package/references/opentasks/src/entire/__tests__/cursor-agent.test.ts +0 -224
  567. package/references/opentasks/src/entire/__tests__/flush-sentinel.test.ts +0 -93
  568. package/references/opentasks/src/entire/__tests__/gemini-agent.test.ts +0 -375
  569. package/references/opentasks/src/entire/__tests__/git-hooks.test.ts +0 -85
  570. package/references/opentasks/src/entire/__tests__/hook-managers.test.ts +0 -128
  571. package/references/opentasks/src/entire/__tests__/opencode-agent.test.ts +0 -329
  572. package/references/opentasks/src/entire/__tests__/redaction.test.ts +0 -143
  573. package/references/opentasks/src/entire/__tests__/session-store.test.ts +0 -83
  574. package/references/opentasks/src/entire/__tests__/summarize.test.ts +0 -346
  575. package/references/opentasks/src/entire/__tests__/transcript-timestamp.test.ts +0 -127
  576. package/references/opentasks/src/entire/__tests__/types.test.ts +0 -112
  577. package/references/opentasks/src/entire/__tests__/utils.test.ts +0 -296
  578. package/references/opentasks/src/entire/__tests__/validation.test.ts +0 -103
  579. package/references/opentasks/src/entire/__tests__/worktree.test.ts +0 -66
  580. package/references/opentasks/src/entire/agent/registry.ts +0 -143
  581. package/references/opentasks/src/entire/agent/session-types.ts +0 -117
  582. package/references/opentasks/src/entire/agent/types.ts +0 -217
  583. package/references/opentasks/src/entire/commands/clean.ts +0 -134
  584. package/references/opentasks/src/entire/commands/disable.ts +0 -85
  585. package/references/opentasks/src/entire/commands/doctor.ts +0 -152
  586. package/references/opentasks/src/entire/commands/enable.ts +0 -149
  587. package/references/opentasks/src/entire/commands/explain.ts +0 -271
  588. package/references/opentasks/src/entire/commands/reset.ts +0 -105
  589. package/references/opentasks/src/entire/commands/resume.ts +0 -194
  590. package/references/opentasks/src/entire/commands/rewind.ts +0 -204
  591. package/references/opentasks/src/entire/commands/status.ts +0 -150
  592. package/references/opentasks/src/entire/config.ts +0 -153
  593. package/references/opentasks/src/entire/git-operations.ts +0 -485
  594. package/references/opentasks/src/entire/hooks/git-hooks.ts +0 -171
  595. package/references/opentasks/src/entire/hooks/lifecycle.ts +0 -224
  596. package/references/opentasks/src/entire/index.ts +0 -644
  597. package/references/opentasks/src/entire/security/redaction.ts +0 -263
  598. package/references/opentasks/src/entire/session/state-machine.ts +0 -463
  599. package/references/opentasks/src/entire/store/checkpoint-store.ts +0 -489
  600. package/references/opentasks/src/entire/store/native-store.ts +0 -178
  601. package/references/opentasks/src/entire/store/provider-types.ts +0 -99
  602. package/references/opentasks/src/entire/store/session-store.ts +0 -233
  603. package/references/opentasks/src/entire/strategy/attribution.ts +0 -300
  604. package/references/opentasks/src/entire/strategy/common.ts +0 -222
  605. package/references/opentasks/src/entire/strategy/content-overlap.ts +0 -242
  606. package/references/opentasks/src/entire/strategy/manual-commit.ts +0 -1008
  607. package/references/opentasks/src/entire/strategy/types.ts +0 -285
  608. package/references/opentasks/src/entire/summarize/claude-generator.ts +0 -119
  609. package/references/opentasks/src/entire/summarize/summarize.ts +0 -432
  610. package/references/opentasks/src/entire/types.ts +0 -408
  611. package/references/opentasks/src/entire/utils/chunk-files.ts +0 -49
  612. package/references/opentasks/src/entire/utils/commit-message.ts +0 -65
  613. package/references/opentasks/src/entire/utils/detect-agent.ts +0 -36
  614. package/references/opentasks/src/entire/utils/hook-managers.ts +0 -118
  615. package/references/opentasks/src/entire/utils/ide-tags.ts +0 -32
  616. package/references/opentasks/src/entire/utils/paths.ts +0 -59
  617. package/references/opentasks/src/entire/utils/preview-rewind.ts +0 -86
  618. package/references/opentasks/src/entire/utils/rewind-conflict.ts +0 -121
  619. package/references/opentasks/src/entire/utils/shadow-branch.ts +0 -113
  620. package/references/opentasks/src/entire/utils/string-utils.ts +0 -46
  621. package/references/opentasks/src/entire/utils/todo-extract.ts +0 -193
  622. package/references/opentasks/src/entire/utils/trailers.ts +0 -190
  623. package/references/opentasks/src/entire/utils/transcript-parse.ts +0 -177
  624. package/references/opentasks/src/entire/utils/transcript-timestamp.ts +0 -61
  625. package/references/opentasks/src/entire/utils/tree-ops.ts +0 -227
  626. package/references/opentasks/src/entire/utils/tty.ts +0 -72
  627. package/references/opentasks/src/entire/utils/validation.ts +0 -67
  628. package/references/opentasks/src/entire/utils/worktree.ts +0 -58
  629. package/references/opentasks/src/graph/EdgeTypeRegistry.ts +0 -330
  630. package/references/opentasks/src/graph/FederatedGraph.ts +0 -796
  631. package/references/opentasks/src/graph/GraphologyAdapter.ts +0 -374
  632. package/references/opentasks/src/graph/HydratingFederatedGraph.ts +0 -533
  633. package/references/opentasks/src/graph/__tests__/EdgeTypeRegistry.test.ts +0 -263
  634. package/references/opentasks/src/graph/__tests__/FederatedGraph.test.ts +0 -821
  635. package/references/opentasks/src/graph/__tests__/GraphologyAdapter.test.ts +0 -408
  636. package/references/opentasks/src/graph/__tests__/HydratingFederatedGraph.test.ts +0 -735
  637. package/references/opentasks/src/graph/__tests__/debounce.test.ts +0 -276
  638. package/references/opentasks/src/graph/__tests__/e2e-store-roundtrip.test.ts +0 -349
  639. package/references/opentasks/src/graph/__tests__/edge-cases.test.ts +0 -595
  640. package/references/opentasks/src/graph/__tests__/expansion.test.ts +0 -304
  641. package/references/opentasks/src/graph/__tests__/git-graph-syncer.test.ts +0 -572
  642. package/references/opentasks/src/graph/__tests__/provider-store.test.ts +0 -1091
  643. package/references/opentasks/src/graph/__tests__/query.test.ts +0 -991
  644. package/references/opentasks/src/graph/__tests__/store.test.ts +0 -998
  645. package/references/opentasks/src/graph/__tests__/sync.test.ts +0 -178
  646. package/references/opentasks/src/graph/__tests__/validation.test.ts +0 -657
  647. package/references/opentasks/src/graph/coordination.ts +0 -454
  648. package/references/opentasks/src/graph/debounce.ts +0 -154
  649. package/references/opentasks/src/graph/expansion.ts +0 -364
  650. package/references/opentasks/src/graph/git-graph-syncer.ts +0 -321
  651. package/references/opentasks/src/graph/history.ts +0 -438
  652. package/references/opentasks/src/graph/index.ts +0 -145
  653. package/references/opentasks/src/graph/provider-store.ts +0 -1077
  654. package/references/opentasks/src/graph/query.ts +0 -651
  655. package/references/opentasks/src/graph/store.ts +0 -861
  656. package/references/opentasks/src/graph/sync.ts +0 -116
  657. package/references/opentasks/src/graph/types.ts +0 -420
  658. package/references/opentasks/src/graph/validation.ts +0 -520
  659. package/references/opentasks/src/index.ts +0 -270
  660. package/references/opentasks/src/materialization/CLAUDE.md +0 -88
  661. package/references/opentasks/src/materialization/README.md +0 -187
  662. package/references/opentasks/src/materialization/__tests__/archive-methods.test.ts +0 -194
  663. package/references/opentasks/src/materialization/__tests__/archiver.test.ts +0 -528
  664. package/references/opentasks/src/materialization/__tests__/config.test.ts +0 -123
  665. package/references/opentasks/src/materialization/__tests__/git-remote-store.test.ts +0 -533
  666. package/references/opentasks/src/materialization/__tests__/graph-id.test.ts +0 -82
  667. package/references/opentasks/src/materialization/__tests__/http-remote-store.test.ts +0 -263
  668. package/references/opentasks/src/materialization/__tests__/materialize-before-archive.test.ts +0 -246
  669. package/references/opentasks/src/materialization/__tests__/remote-store-factory.test.ts +0 -152
  670. package/references/opentasks/src/materialization/__tests__/snapshot.test.ts +0 -209
  671. package/references/opentasks/src/materialization/archiver.ts +0 -318
  672. package/references/opentasks/src/materialization/git-archive-store.ts +0 -568
  673. package/references/opentasks/src/materialization/git-remote-store.ts +0 -551
  674. package/references/opentasks/src/materialization/graph-id.ts +0 -173
  675. package/references/opentasks/src/materialization/http-remote-store.ts +0 -190
  676. package/references/opentasks/src/materialization/index.ts +0 -62
  677. package/references/opentasks/src/materialization/remote-store-factory.ts +0 -55
  678. package/references/opentasks/src/materialization/snapshot.ts +0 -230
  679. package/references/opentasks/src/materialization/types.ts +0 -410
  680. package/references/opentasks/src/providers/__tests__/beads.test.ts +0 -752
  681. package/references/opentasks/src/providers/__tests__/claude-tasks.test.ts +0 -485
  682. package/references/opentasks/src/providers/__tests__/entire-e2e.test.ts +0 -692
  683. package/references/opentasks/src/providers/__tests__/entire-sessionlog-e2e.test.ts +0 -1113
  684. package/references/opentasks/src/providers/__tests__/entire.test.ts +0 -1016
  685. package/references/opentasks/src/providers/__tests__/from-config.test.ts +0 -183
  686. package/references/opentasks/src/providers/__tests__/global.test.ts +0 -515
  687. package/references/opentasks/src/providers/__tests__/materialization.test.ts +0 -567
  688. package/references/opentasks/src/providers/__tests__/native.test.ts +0 -693
  689. package/references/opentasks/src/providers/__tests__/registry.test.ts +0 -232
  690. package/references/opentasks/src/providers/beads.ts +0 -1155
  691. package/references/opentasks/src/providers/claude-tasks.ts +0 -402
  692. package/references/opentasks/src/providers/entire.ts +0 -608
  693. package/references/opentasks/src/providers/from-config.ts +0 -210
  694. package/references/opentasks/src/providers/global.ts +0 -460
  695. package/references/opentasks/src/providers/index.ts +0 -147
  696. package/references/opentasks/src/providers/location.ts +0 -237
  697. package/references/opentasks/src/providers/materialization.ts +0 -346
  698. package/references/opentasks/src/providers/native.ts +0 -725
  699. package/references/opentasks/src/providers/registry.ts +0 -114
  700. package/references/opentasks/src/providers/sudocode.ts +0 -1292
  701. package/references/opentasks/src/providers/sync.ts +0 -485
  702. package/references/opentasks/src/providers/traits/RelationshipQueryable.ts +0 -169
  703. package/references/opentasks/src/providers/traits/TaskManageable.ts +0 -211
  704. package/references/opentasks/src/providers/traits/Watchable.ts +0 -260
  705. package/references/opentasks/src/providers/traits/__tests__/RelationshipQueryable.test.ts +0 -217
  706. package/references/opentasks/src/providers/traits/__tests__/TaskManageable.test.ts +0 -241
  707. package/references/opentasks/src/providers/traits/index.ts +0 -42
  708. package/references/opentasks/src/providers/types.ts +0 -439
  709. package/references/opentasks/src/schema/__tests__/validation.test.ts +0 -283
  710. package/references/opentasks/src/schema/base.ts +0 -88
  711. package/references/opentasks/src/schema/edges.ts +0 -78
  712. package/references/opentasks/src/schema/index.ts +0 -37
  713. package/references/opentasks/src/schema/nodes.ts +0 -119
  714. package/references/opentasks/src/schema/storage.ts +0 -130
  715. package/references/opentasks/src/schema/validation.ts +0 -209
  716. package/references/opentasks/src/storage/__tests__/atomic-write.test.ts +0 -227
  717. package/references/opentasks/src/storage/__tests__/file-lock.test.ts +0 -120
  718. package/references/opentasks/src/storage/__tests__/jsonl.test.ts +0 -267
  719. package/references/opentasks/src/storage/__tests__/locked-writer.test.ts +0 -134
  720. package/references/opentasks/src/storage/__tests__/sqlite.test.ts +0 -572
  721. package/references/opentasks/src/storage/atomic-write.ts +0 -86
  722. package/references/opentasks/src/storage/file-lock.ts +0 -215
  723. package/references/opentasks/src/storage/index.ts +0 -24
  724. package/references/opentasks/src/storage/interface.ts +0 -289
  725. package/references/opentasks/src/storage/jsonl.ts +0 -264
  726. package/references/opentasks/src/storage/locked-writer.ts +0 -140
  727. package/references/opentasks/src/storage/sqlite-schema.ts +0 -177
  728. package/references/opentasks/src/storage/sqlite.ts +0 -791
  729. package/references/opentasks/src/tools/__tests__/annotate.test.ts +0 -381
  730. package/references/opentasks/src/tools/__tests__/link.test.ts +0 -299
  731. package/references/opentasks/src/tools/__tests__/query.test.ts +0 -350
  732. package/references/opentasks/src/tools/__tests__/task.test.ts +0 -218
  733. package/references/opentasks/src/tools/annotate.ts +0 -277
  734. package/references/opentasks/src/tools/index.ts +0 -57
  735. package/references/opentasks/src/tools/link.ts +0 -163
  736. package/references/opentasks/src/tools/query.ts +0 -468
  737. package/references/opentasks/src/tools/task.ts +0 -213
  738. package/references/opentasks/src/tools/types.ts +0 -451
  739. package/references/opentasks/src/tracking/__tests__/claude-tool-categorizer.test.ts +0 -223
  740. package/references/opentasks/src/tracking/__tests__/transcript-extractor.test.ts +0 -262
  741. package/references/opentasks/src/tracking/claude-tool-categorizer.ts +0 -155
  742. package/references/opentasks/src/tracking/index.ts +0 -32
  743. package/references/opentasks/src/tracking/skill-tracker.ts +0 -322
  744. package/references/opentasks/src/tracking/transcript-extractor.ts +0 -225
  745. package/references/opentasks/tests/e2e/helpers/assertions.ts +0 -211
  746. package/references/opentasks/tests/e2e/helpers/beads-helpers.ts +0 -487
  747. package/references/opentasks/tests/e2e/helpers/fixtures.ts +0 -236
  748. package/references/opentasks/tests/e2e/helpers/index.ts +0 -122
  749. package/references/opentasks/tests/e2e/helpers/sudocode-helpers.ts +0 -341
  750. package/references/opentasks/tests/e2e/helpers/system-setup.ts +0 -504
  751. package/references/opentasks/tests/e2e/helpers/test-agent.ts +0 -504
  752. package/references/opentasks/tests/e2e/infrastructure.e2e.test.ts +0 -521
  753. package/references/opentasks/tests/e2e/skill-tracking.e2e.test.ts +0 -625
  754. package/references/opentasks/tests/e2e/workflows/feedback-loop.e2e.test.ts +0 -279
  755. package/references/opentasks/tests/e2e/workflows/multi-agent.e2e.test.ts +0 -304
  756. package/references/opentasks/tests/e2e/workflows/provider-sync/background-sync.e2e.test.ts +0 -292
  757. package/references/opentasks/tests/e2e/workflows/provider-sync/beads-provider-compat.e2e.test.ts +0 -249
  758. package/references/opentasks/tests/e2e/workflows/provider-sync/cross-provider-edges.e2e.test.ts +0 -407
  759. package/references/opentasks/tests/e2e/workflows/provider-sync/federated-ready.e2e.test.ts +0 -504
  760. package/references/opentasks/tests/e2e/workflows/provider-sync/hydration.e2e.test.ts +0 -340
  761. package/references/opentasks/tests/e2e/workflows/provider-sync/materialization.e2e.test.ts +0 -370
  762. package/references/opentasks/tests/e2e/workflows/provider-sync/sudocode-provider-compat.e2e.test.ts +0 -683
  763. package/references/opentasks/tests/e2e/workflows/provider-sync/watchable-beads.e2e.test.ts +0 -573
  764. package/references/opentasks/tests/e2e/workflows/spec-driven.e2e.test.ts +0 -244
  765. package/references/opentasks/tests/e2e/worktree-location.e2e.test.ts +0 -699
  766. package/references/opentasks/tests/integration/daemon/helpers.ts +0 -147
  767. package/references/opentasks/tests/integration/daemon/ipc.integration.test.ts +0 -343
  768. package/references/opentasks/tests/integration/daemon/lifecycle.integration.test.ts +0 -407
  769. package/references/opentasks/tests/integration/graph/federated-graph.integration.test.ts +0 -660
  770. package/references/opentasks/tests/integration/helpers/flags.ts +0 -28
  771. package/references/opentasks/tests/integration/helpers/index.ts +0 -47
  772. package/references/opentasks/tests/integration/helpers/process.ts +0 -133
  773. package/references/opentasks/tests/integration/helpers/temp.ts +0 -105
  774. package/references/opentasks/tests/integration/helpers/wait.ts +0 -133
  775. package/references/opentasks/tests/integration/helpers.test.ts +0 -120
  776. package/references/opentasks/tests/integration/providers/beads-task-manageable.integration.test.ts +0 -450
  777. package/references/opentasks/tests/integration/providers/beads.integration.test.ts +0 -388
  778. package/references/opentasks/tests/integration/providers/native-task-manageable.integration.test.ts +0 -667
  779. package/references/opentasks/tests/integration/providers/sudocode-task-manageable.integration.test.ts +0 -406
  780. package/references/opentasks/tests/integration/providers/sudocode.integration.test.ts +0 -342
  781. package/references/opentasks/tests/integration/storage/jsonl-durability.integration.test.ts +0 -390
  782. package/references/opentasks/tests/integration/storage/sqlite-durability.integration.test.ts +0 -527
  783. package/references/opentasks/tests/integration/worktree/redirect-location-resolution.integration.test.ts +0 -578
  784. package/references/opentasks/tests/integration/worktree/worktree-flow.integration.test.ts +0 -656
  785. package/references/opentasks/tsconfig.json +0 -18
  786. package/references/opentasks/vitest.config.ts +0 -27
  787. package/references/opentasks/vitest.e2e.config.ts +0 -35
  788. package/references/opentasks/vitest.integration.config.ts +0 -19
  789. package/references/openteams/.claude/settings.json +0 -6
  790. package/references/openteams/CLAUDE.md +0 -98
  791. package/references/openteams/README.md +0 -508
  792. package/references/openteams/SKILL.md +0 -198
  793. package/references/openteams/design.md +0 -250
  794. package/references/openteams/docs/visual-editor-design.md +0 -1225
  795. package/references/openteams/editor/index.html +0 -15
  796. package/references/openteams/editor/package.json +0 -39
  797. package/references/openteams/editor/src/App.tsx +0 -48
  798. package/references/openteams/editor/src/components/canvas/Canvas.tsx +0 -131
  799. package/references/openteams/editor/src/components/canvas/QuickAddMenu.tsx +0 -134
  800. package/references/openteams/editor/src/components/edges/PeerRouteEdge.tsx +0 -82
  801. package/references/openteams/editor/src/components/edges/SignalFlowEdge.tsx +0 -77
  802. package/references/openteams/editor/src/components/edges/SpawnEdge.tsx +0 -54
  803. package/references/openteams/editor/src/components/inspector/ChannelInspector.tsx +0 -158
  804. package/references/openteams/editor/src/components/inspector/EdgeInspector.tsx +0 -168
  805. package/references/openteams/editor/src/components/inspector/Inspector.tsx +0 -46
  806. package/references/openteams/editor/src/components/inspector/RoleInspector.tsx +0 -508
  807. package/references/openteams/editor/src/components/inspector/TeamInspector.tsx +0 -126
  808. package/references/openteams/editor/src/components/nodes/ChannelNode.tsx +0 -103
  809. package/references/openteams/editor/src/components/nodes/RoleNode.tsx +0 -157
  810. package/references/openteams/editor/src/components/nodes/node-styles.ts +0 -101
  811. package/references/openteams/editor/src/components/sidebar/Sidebar.tsx +0 -227
  812. package/references/openteams/editor/src/components/toolbar/ExportModal.tsx +0 -110
  813. package/references/openteams/editor/src/components/toolbar/ImportModal.tsx +0 -139
  814. package/references/openteams/editor/src/components/toolbar/Toolbar.tsx +0 -190
  815. package/references/openteams/editor/src/hooks/use-autosave.ts +0 -126
  816. package/references/openteams/editor/src/hooks/use-keyboard.ts +0 -106
  817. package/references/openteams/editor/src/hooks/use-validation.ts +0 -45
  818. package/references/openteams/editor/src/index.css +0 -245
  819. package/references/openteams/editor/src/lib/auto-layout.ts +0 -51
  820. package/references/openteams/editor/src/lib/bundled-templates.ts +0 -42
  821. package/references/openteams/editor/src/lib/compiler.ts +0 -75
  822. package/references/openteams/editor/src/lib/load-template.ts +0 -103
  823. package/references/openteams/editor/src/lib/rebuild-edges.ts +0 -104
  824. package/references/openteams/editor/src/lib/serializer.ts +0 -408
  825. package/references/openteams/editor/src/lib/signal-catalog.ts +0 -50
  826. package/references/openteams/editor/src/lib/validator.ts +0 -172
  827. package/references/openteams/editor/src/main.tsx +0 -10
  828. package/references/openteams/editor/src/stores/canvas-store.ts +0 -80
  829. package/references/openteams/editor/src/stores/config-store.ts +0 -243
  830. package/references/openteams/editor/src/stores/history-store.ts +0 -143
  831. package/references/openteams/editor/src/stores/theme-store.ts +0 -66
  832. package/references/openteams/editor/src/stores/ui-store.ts +0 -46
  833. package/references/openteams/editor/src/stores/validation-store.ts +0 -27
  834. package/references/openteams/editor/src/types/editor.ts +0 -74
  835. package/references/openteams/editor/src/vite-env.d.ts +0 -1
  836. package/references/openteams/editor/tests/compiler.test.ts +0 -151
  837. package/references/openteams/editor/tests/e2e-add-remove.test.ts +0 -386
  838. package/references/openteams/editor/tests/e2e-components.test.tsx +0 -424
  839. package/references/openteams/editor/tests/e2e-export-roundtrip.test.ts +0 -299
  840. package/references/openteams/editor/tests/e2e-template-load.test.ts +0 -204
  841. package/references/openteams/editor/tests/e2e-ui-store.test.ts +0 -126
  842. package/references/openteams/editor/tests/e2e-undo-redo.test.ts +0 -203
  843. package/references/openteams/editor/tests/e2e-validation.test.ts +0 -307
  844. package/references/openteams/editor/tests/serializer.test.ts +0 -142
  845. package/references/openteams/editor/tests/setup.ts +0 -52
  846. package/references/openteams/editor/tests/validator.test.ts +0 -92
  847. package/references/openteams/editor/tsconfig.json +0 -21
  848. package/references/openteams/editor/tsconfig.tsbuildinfo +0 -1
  849. package/references/openteams/editor/vite.config.ts +0 -28
  850. package/references/openteams/examples/bmad-method/prompts/analyst/ROLE.md +0 -16
  851. package/references/openteams/examples/bmad-method/prompts/analyst/SOUL.md +0 -5
  852. package/references/openteams/examples/bmad-method/prompts/architect/ROLE.md +0 -24
  853. package/references/openteams/examples/bmad-method/prompts/architect/SOUL.md +0 -5
  854. package/references/openteams/examples/bmad-method/prompts/developer/ROLE.md +0 -25
  855. package/references/openteams/examples/bmad-method/prompts/developer/SOUL.md +0 -5
  856. package/references/openteams/examples/bmad-method/prompts/master/ROLE.md +0 -21
  857. package/references/openteams/examples/bmad-method/prompts/master/SOUL.md +0 -5
  858. package/references/openteams/examples/bmad-method/prompts/pm/ROLE.md +0 -20
  859. package/references/openteams/examples/bmad-method/prompts/pm/SOUL.md +0 -5
  860. package/references/openteams/examples/bmad-method/prompts/qa/ROLE.md +0 -17
  861. package/references/openteams/examples/bmad-method/prompts/qa/SOUL.md +0 -5
  862. package/references/openteams/examples/bmad-method/prompts/quick-flow-dev/ROLE.md +0 -23
  863. package/references/openteams/examples/bmad-method/prompts/quick-flow-dev/SOUL.md +0 -5
  864. package/references/openteams/examples/bmad-method/prompts/scrum-master/ROLE.md +0 -27
  865. package/references/openteams/examples/bmad-method/prompts/scrum-master/SOUL.md +0 -5
  866. package/references/openteams/examples/bmad-method/prompts/tech-writer/ROLE.md +0 -21
  867. package/references/openteams/examples/bmad-method/prompts/tech-writer/SOUL.md +0 -5
  868. package/references/openteams/examples/bmad-method/prompts/ux-designer/ROLE.md +0 -16
  869. package/references/openteams/examples/bmad-method/prompts/ux-designer/SOUL.md +0 -5
  870. package/references/openteams/examples/bmad-method/roles/analyst.yaml +0 -9
  871. package/references/openteams/examples/bmad-method/roles/architect.yaml +0 -9
  872. package/references/openteams/examples/bmad-method/roles/developer.yaml +0 -8
  873. package/references/openteams/examples/bmad-method/roles/master.yaml +0 -8
  874. package/references/openteams/examples/bmad-method/roles/pm.yaml +0 -9
  875. package/references/openteams/examples/bmad-method/roles/qa.yaml +0 -8
  876. package/references/openteams/examples/bmad-method/roles/quick-flow-dev.yaml +0 -8
  877. package/references/openteams/examples/bmad-method/roles/scrum-master.yaml +0 -9
  878. package/references/openteams/examples/bmad-method/roles/tech-writer.yaml +0 -8
  879. package/references/openteams/examples/bmad-method/roles/ux-designer.yaml +0 -8
  880. package/references/openteams/examples/bmad-method/team.yaml +0 -161
  881. package/references/openteams/examples/bug-fix-pipeline/roles/fixer.yaml +0 -9
  882. package/references/openteams/examples/bug-fix-pipeline/roles/investigator.yaml +0 -8
  883. package/references/openteams/examples/bug-fix-pipeline/roles/pr-creator.yaml +0 -6
  884. package/references/openteams/examples/bug-fix-pipeline/roles/triager.yaml +0 -7
  885. package/references/openteams/examples/bug-fix-pipeline/roles/verifier.yaml +0 -8
  886. package/references/openteams/examples/bug-fix-pipeline/team.yaml +0 -88
  887. package/references/openteams/examples/codebase-migration/roles/assessor.yaml +0 -7
  888. package/references/openteams/examples/codebase-migration/roles/migrator.yaml +0 -9
  889. package/references/openteams/examples/codebase-migration/roles/planner.yaml +0 -5
  890. package/references/openteams/examples/codebase-migration/roles/test-extractor.yaml +0 -9
  891. package/references/openteams/examples/codebase-migration/roles/validator.yaml +0 -7
  892. package/references/openteams/examples/codebase-migration/team.yaml +0 -81
  893. package/references/openteams/examples/docs-sync/roles/adr-writer.yaml +0 -7
  894. package/references/openteams/examples/docs-sync/roles/api-doc-writer.yaml +0 -7
  895. package/references/openteams/examples/docs-sync/roles/change-detector.yaml +0 -7
  896. package/references/openteams/examples/docs-sync/roles/doc-reviewer.yaml +0 -7
  897. package/references/openteams/examples/docs-sync/roles/guide-writer.yaml +0 -7
  898. package/references/openteams/examples/docs-sync/team.yaml +0 -84
  899. package/references/openteams/examples/gsd/prompts/codebase-mapper/ROLE.md +0 -17
  900. package/references/openteams/examples/gsd/prompts/codebase-mapper/SOUL.md +0 -5
  901. package/references/openteams/examples/gsd/prompts/debugger/ROLE.md +0 -25
  902. package/references/openteams/examples/gsd/prompts/debugger/SOUL.md +0 -5
  903. package/references/openteams/examples/gsd/prompts/executor/ROLE.md +0 -34
  904. package/references/openteams/examples/gsd/prompts/executor/SOUL.md +0 -5
  905. package/references/openteams/examples/gsd/prompts/integration-checker/ROLE.md +0 -18
  906. package/references/openteams/examples/gsd/prompts/integration-checker/SOUL.md +0 -3
  907. package/references/openteams/examples/gsd/prompts/orchestrator/ROLE.md +0 -42
  908. package/references/openteams/examples/gsd/prompts/orchestrator/SOUL.md +0 -5
  909. package/references/openteams/examples/gsd/prompts/phase-researcher/ROLE.md +0 -15
  910. package/references/openteams/examples/gsd/prompts/phase-researcher/SOUL.md +0 -3
  911. package/references/openteams/examples/gsd/prompts/plan-checker/ROLE.md +0 -17
  912. package/references/openteams/examples/gsd/prompts/plan-checker/SOUL.md +0 -3
  913. package/references/openteams/examples/gsd/prompts/planner/ROLE.md +0 -28
  914. package/references/openteams/examples/gsd/prompts/planner/SOUL.md +0 -5
  915. package/references/openteams/examples/gsd/prompts/project-researcher/ROLE.md +0 -16
  916. package/references/openteams/examples/gsd/prompts/project-researcher/SOUL.md +0 -3
  917. package/references/openteams/examples/gsd/prompts/research-synthesizer/ROLE.md +0 -13
  918. package/references/openteams/examples/gsd/prompts/research-synthesizer/SOUL.md +0 -3
  919. package/references/openteams/examples/gsd/prompts/roadmapper/ROLE.md +0 -14
  920. package/references/openteams/examples/gsd/prompts/roadmapper/SOUL.md +0 -3
  921. package/references/openteams/examples/gsd/prompts/verifier/ROLE.md +0 -19
  922. package/references/openteams/examples/gsd/prompts/verifier/SOUL.md +0 -5
  923. package/references/openteams/examples/gsd/roles/codebase-mapper.yaml +0 -8
  924. package/references/openteams/examples/gsd/roles/debugger.yaml +0 -8
  925. package/references/openteams/examples/gsd/roles/executor.yaml +0 -8
  926. package/references/openteams/examples/gsd/roles/integration-checker.yaml +0 -8
  927. package/references/openteams/examples/gsd/roles/orchestrator.yaml +0 -9
  928. package/references/openteams/examples/gsd/roles/phase-researcher.yaml +0 -7
  929. package/references/openteams/examples/gsd/roles/plan-checker.yaml +0 -8
  930. package/references/openteams/examples/gsd/roles/planner.yaml +0 -8
  931. package/references/openteams/examples/gsd/roles/project-researcher.yaml +0 -8
  932. package/references/openteams/examples/gsd/roles/research-synthesizer.yaml +0 -7
  933. package/references/openteams/examples/gsd/roles/roadmapper.yaml +0 -7
  934. package/references/openteams/examples/gsd/roles/verifier.yaml +0 -8
  935. package/references/openteams/examples/gsd/team.yaml +0 -154
  936. package/references/openteams/examples/incident-response/roles/communicator.yaml +0 -5
  937. package/references/openteams/examples/incident-response/roles/fix-proposer.yaml +0 -7
  938. package/references/openteams/examples/incident-response/roles/incident-triager.yaml +0 -8
  939. package/references/openteams/examples/incident-response/roles/investigator.yaml +0 -8
  940. package/references/openteams/examples/incident-response/team.yaml +0 -68
  941. package/references/openteams/examples/pr-review-checks/roles/code-reviewer.yaml +0 -7
  942. package/references/openteams/examples/pr-review-checks/roles/security-scanner.yaml +0 -6
  943. package/references/openteams/examples/pr-review-checks/roles/summarizer.yaml +0 -6
  944. package/references/openteams/examples/pr-review-checks/roles/test-checker.yaml +0 -8
  945. package/references/openteams/examples/pr-review-checks/team.yaml +0 -64
  946. package/references/openteams/examples/security-audit/roles/code-analyzer.yaml +0 -6
  947. package/references/openteams/examples/security-audit/roles/dep-scanner.yaml +0 -7
  948. package/references/openteams/examples/security-audit/roles/fixer.yaml +0 -9
  949. package/references/openteams/examples/security-audit/roles/pr-creator.yaml +0 -6
  950. package/references/openteams/examples/security-audit/roles/prioritizer.yaml +0 -6
  951. package/references/openteams/examples/security-audit/roles/secrets-scanner.yaml +0 -6
  952. package/references/openteams/examples/security-audit/roles/verifier.yaml +0 -8
  953. package/references/openteams/examples/security-audit/team.yaml +0 -102
  954. package/references/openteams/media/banner.png +0 -0
  955. package/references/openteams/media/editor.png +0 -0
  956. package/references/openteams/package-lock.json +0 -4804
  957. package/references/openteams/package.json +0 -58
  958. package/references/openteams/schema/role.schema.json +0 -147
  959. package/references/openteams/schema/team.schema.json +0 -311
  960. package/references/openteams/src/cli/editor.ts +0 -170
  961. package/references/openteams/src/cli/generate.test.ts +0 -191
  962. package/references/openteams/src/cli/generate.ts +0 -220
  963. package/references/openteams/src/cli/prompt-utils.ts +0 -42
  964. package/references/openteams/src/cli/template.test.ts +0 -365
  965. package/references/openteams/src/cli/template.ts +0 -205
  966. package/references/openteams/src/cli.ts +0 -22
  967. package/references/openteams/src/generators/agent-prompt-generator.test.ts +0 -332
  968. package/references/openteams/src/generators/agent-prompt-generator.ts +0 -527
  969. package/references/openteams/src/generators/package-generator.test.ts +0 -129
  970. package/references/openteams/src/generators/package-generator.ts +0 -102
  971. package/references/openteams/src/generators/skill-generator.test.ts +0 -246
  972. package/references/openteams/src/generators/skill-generator.ts +0 -388
  973. package/references/openteams/src/index.ts +0 -84
  974. package/references/openteams/src/template/builtins.test.ts +0 -74
  975. package/references/openteams/src/template/builtins.ts +0 -108
  976. package/references/openteams/src/template/install-service.test.ts +0 -452
  977. package/references/openteams/src/template/install-service.ts +0 -332
  978. package/references/openteams/src/template/loader.test.ts +0 -1696
  979. package/references/openteams/src/template/loader.ts +0 -804
  980. package/references/openteams/src/template/resolver.test.ts +0 -304
  981. package/references/openteams/src/template/resolver.ts +0 -251
  982. package/references/openteams/src/template/types.ts +0 -229
  983. package/references/openteams/tsconfig.cjs.json +0 -7
  984. package/references/openteams/tsconfig.esm.json +0 -8
  985. package/references/openteams/tsconfig.json +0 -16
  986. package/references/openteams/vitest.config.ts +0 -9
  987. package/references/sessionlog/.husky/pre-commit +0 -1
  988. package/references/sessionlog/.lintstagedrc.json +0 -4
  989. package/references/sessionlog/.prettierignore +0 -4
  990. package/references/sessionlog/.prettierrc.json +0 -11
  991. package/references/sessionlog/LICENSE +0 -21
  992. package/references/sessionlog/README.md +0 -453
  993. package/references/sessionlog/eslint.config.js +0 -58
  994. package/references/sessionlog/package-lock.json +0 -3672
  995. package/references/sessionlog/package.json +0 -65
  996. package/references/sessionlog/src/__tests__/agent-hooks.test.ts +0 -570
  997. package/references/sessionlog/src/__tests__/agent-registry.test.ts +0 -127
  998. package/references/sessionlog/src/__tests__/claude-code-hooks.test.ts +0 -225
  999. package/references/sessionlog/src/__tests__/claude-generator.test.ts +0 -46
  1000. package/references/sessionlog/src/__tests__/commit-msg.test.ts +0 -86
  1001. package/references/sessionlog/src/__tests__/cursor-agent.test.ts +0 -224
  1002. package/references/sessionlog/src/__tests__/e2e-live.test.ts +0 -890
  1003. package/references/sessionlog/src/__tests__/event-log.test.ts +0 -183
  1004. package/references/sessionlog/src/__tests__/flush-sentinel.test.ts +0 -105
  1005. package/references/sessionlog/src/__tests__/gemini-agent.test.ts +0 -375
  1006. package/references/sessionlog/src/__tests__/git-hooks.test.ts +0 -78
  1007. package/references/sessionlog/src/__tests__/hook-managers.test.ts +0 -121
  1008. package/references/sessionlog/src/__tests__/lifecycle-tasks.test.ts +0 -759
  1009. package/references/sessionlog/src/__tests__/opencode-agent.test.ts +0 -338
  1010. package/references/sessionlog/src/__tests__/redaction.test.ts +0 -136
  1011. package/references/sessionlog/src/__tests__/session-repo.test.ts +0 -353
  1012. package/references/sessionlog/src/__tests__/session-store.test.ts +0 -166
  1013. package/references/sessionlog/src/__tests__/setup-ccweb.test.ts +0 -466
  1014. package/references/sessionlog/src/__tests__/skill-live.test.ts +0 -461
  1015. package/references/sessionlog/src/__tests__/summarize.test.ts +0 -348
  1016. package/references/sessionlog/src/__tests__/task-plan-e2e.test.ts +0 -610
  1017. package/references/sessionlog/src/__tests__/task-plan-live.test.ts +0 -632
  1018. package/references/sessionlog/src/__tests__/transcript-timestamp.test.ts +0 -121
  1019. package/references/sessionlog/src/__tests__/types.test.ts +0 -166
  1020. package/references/sessionlog/src/__tests__/utils.test.ts +0 -333
  1021. package/references/sessionlog/src/__tests__/validation.test.ts +0 -103
  1022. package/references/sessionlog/src/__tests__/worktree.test.ts +0 -57
  1023. package/references/sessionlog/src/agent/registry.ts +0 -143
  1024. package/references/sessionlog/src/agent/session-types.ts +0 -113
  1025. package/references/sessionlog/src/agent/types.ts +0 -220
  1026. package/references/sessionlog/src/cli.ts +0 -597
  1027. package/references/sessionlog/src/commands/clean.ts +0 -133
  1028. package/references/sessionlog/src/commands/disable.ts +0 -84
  1029. package/references/sessionlog/src/commands/doctor.ts +0 -145
  1030. package/references/sessionlog/src/commands/enable.ts +0 -202
  1031. package/references/sessionlog/src/commands/explain.ts +0 -261
  1032. package/references/sessionlog/src/commands/reset.ts +0 -105
  1033. package/references/sessionlog/src/commands/resume.ts +0 -180
  1034. package/references/sessionlog/src/commands/rewind.ts +0 -195
  1035. package/references/sessionlog/src/commands/setup-ccweb.ts +0 -275
  1036. package/references/sessionlog/src/commands/status.ts +0 -172
  1037. package/references/sessionlog/src/config.ts +0 -165
  1038. package/references/sessionlog/src/events/event-log.ts +0 -126
  1039. package/references/sessionlog/src/git-operations.ts +0 -558
  1040. package/references/sessionlog/src/hooks/git-hooks.ts +0 -165
  1041. package/references/sessionlog/src/hooks/lifecycle.ts +0 -391
  1042. package/references/sessionlog/src/index.ts +0 -650
  1043. package/references/sessionlog/src/security/redaction.ts +0 -283
  1044. package/references/sessionlog/src/session/state-machine.ts +0 -452
  1045. package/references/sessionlog/src/store/checkpoint-store.ts +0 -509
  1046. package/references/sessionlog/src/store/native-store.ts +0 -173
  1047. package/references/sessionlog/src/store/provider-types.ts +0 -99
  1048. package/references/sessionlog/src/store/session-store.ts +0 -266
  1049. package/references/sessionlog/src/strategy/attribution.ts +0 -296
  1050. package/references/sessionlog/src/strategy/common.ts +0 -207
  1051. package/references/sessionlog/src/strategy/content-overlap.ts +0 -228
  1052. package/references/sessionlog/src/strategy/manual-commit.ts +0 -988
  1053. package/references/sessionlog/src/strategy/types.ts +0 -279
  1054. package/references/sessionlog/src/summarize/claude-generator.ts +0 -115
  1055. package/references/sessionlog/src/summarize/summarize.ts +0 -432
  1056. package/references/sessionlog/src/types.ts +0 -508
  1057. package/references/sessionlog/src/utils/chunk-files.ts +0 -49
  1058. package/references/sessionlog/src/utils/commit-message.ts +0 -65
  1059. package/references/sessionlog/src/utils/detect-agent.ts +0 -36
  1060. package/references/sessionlog/src/utils/hook-managers.ts +0 -125
  1061. package/references/sessionlog/src/utils/ide-tags.ts +0 -32
  1062. package/references/sessionlog/src/utils/paths.ts +0 -79
  1063. package/references/sessionlog/src/utils/preview-rewind.ts +0 -80
  1064. package/references/sessionlog/src/utils/rewind-conflict.ts +0 -121
  1065. package/references/sessionlog/src/utils/shadow-branch.ts +0 -109
  1066. package/references/sessionlog/src/utils/string-utils.ts +0 -46
  1067. package/references/sessionlog/src/utils/todo-extract.ts +0 -188
  1068. package/references/sessionlog/src/utils/trailers.ts +0 -187
  1069. package/references/sessionlog/src/utils/transcript-parse.ts +0 -177
  1070. package/references/sessionlog/src/utils/transcript-timestamp.ts +0 -59
  1071. package/references/sessionlog/src/utils/tree-ops.ts +0 -219
  1072. package/references/sessionlog/src/utils/tty.ts +0 -72
  1073. package/references/sessionlog/src/utils/validation.ts +0 -65
  1074. package/references/sessionlog/src/utils/worktree.ts +0 -58
  1075. package/references/sessionlog/src/wire-types.ts +0 -59
  1076. package/references/sessionlog/templates/setup-env.sh +0 -153
  1077. package/references/sessionlog/tsconfig.json +0 -18
  1078. package/references/sessionlog/vitest.config.ts +0 -12
  1079. package/references/swarmkit/LICENSE +0 -21
  1080. package/references/swarmkit/README.md +0 -130
  1081. package/references/swarmkit/docs/design.md +0 -453
  1082. package/references/swarmkit/docs/package-setup-reference.md +0 -519
  1083. package/references/swarmkit/package-lock.json +0 -1938
  1084. package/references/swarmkit/package.json +0 -43
  1085. package/references/swarmkit/src/cli.ts +0 -41
  1086. package/references/swarmkit/src/commands/add.ts +0 -126
  1087. package/references/swarmkit/src/commands/doctor.ts +0 -117
  1088. package/references/swarmkit/src/commands/hive.ts +0 -279
  1089. package/references/swarmkit/src/commands/init/phases/configure.ts +0 -96
  1090. package/references/swarmkit/src/commands/init/phases/global-setup.ts +0 -102
  1091. package/references/swarmkit/src/commands/init/phases/packages.ts +0 -44
  1092. package/references/swarmkit/src/commands/init/phases/project.ts +0 -81
  1093. package/references/swarmkit/src/commands/init/phases/use-case.ts +0 -47
  1094. package/references/swarmkit/src/commands/init/state.test.ts +0 -23
  1095. package/references/swarmkit/src/commands/init/state.ts +0 -22
  1096. package/references/swarmkit/src/commands/init/wizard.ts +0 -160
  1097. package/references/swarmkit/src/commands/init.ts +0 -17
  1098. package/references/swarmkit/src/commands/login.ts +0 -106
  1099. package/references/swarmkit/src/commands/logout.ts +0 -22
  1100. package/references/swarmkit/src/commands/remove.ts +0 -72
  1101. package/references/swarmkit/src/commands/status.ts +0 -101
  1102. package/references/swarmkit/src/commands/update.ts +0 -62
  1103. package/references/swarmkit/src/commands/whoami.ts +0 -41
  1104. package/references/swarmkit/src/config/global.test.ts +0 -258
  1105. package/references/swarmkit/src/config/global.ts +0 -141
  1106. package/references/swarmkit/src/config/keys.test.ts +0 -109
  1107. package/references/swarmkit/src/config/keys.ts +0 -49
  1108. package/references/swarmkit/src/doctor/checks.test.ts +0 -366
  1109. package/references/swarmkit/src/doctor/checks.ts +0 -292
  1110. package/references/swarmkit/src/doctor/types.ts +0 -33
  1111. package/references/swarmkit/src/hub/auth-flow.test.ts +0 -127
  1112. package/references/swarmkit/src/hub/auth-flow.ts +0 -144
  1113. package/references/swarmkit/src/hub/client.test.ts +0 -224
  1114. package/references/swarmkit/src/hub/client.ts +0 -185
  1115. package/references/swarmkit/src/hub/credentials.test.ts +0 -132
  1116. package/references/swarmkit/src/hub/credentials.ts +0 -51
  1117. package/references/swarmkit/src/index.ts +0 -116
  1118. package/references/swarmkit/src/packages/installer.test.ts +0 -365
  1119. package/references/swarmkit/src/packages/installer.ts +0 -206
  1120. package/references/swarmkit/src/packages/plugin.test.ts +0 -141
  1121. package/references/swarmkit/src/packages/plugin.ts +0 -46
  1122. package/references/swarmkit/src/packages/registry.test.ts +0 -235
  1123. package/references/swarmkit/src/packages/registry.ts +0 -209
  1124. package/references/swarmkit/src/packages/setup.test.ts +0 -1349
  1125. package/references/swarmkit/src/packages/setup.ts +0 -635
  1126. package/references/swarmkit/src/utils/ui.test.ts +0 -115
  1127. package/references/swarmkit/src/utils/ui.ts +0 -62
  1128. package/references/swarmkit/tsconfig.json +0 -17
  1129. package/references/swarmkit/vitest.config.ts +0 -9
@@ -1,15 +0,0 @@
1
- {"id":"s-4cts","uuid":"d1212471-4731-4b05-8ab3-cc79b931bede","title":"P1 Implementation: Streaming, Permissions, Federation","file_path":"specs/s-4cts_p1_implementation_streaming_permissions_federation.md","content":"# P1 Implementation: Streaming, Permissions, Federation\n\nThis spec captures all design decisions, requirements, and implementation details for P1 gaps in the MAP SDK.\n\n**Status**: Approved for Implementation\n**Design Doc**: `ts-sdk/docs/p1-implementation-plan.md`\n\n---\n\n## Table of Contents\n\n1. [Overview](#overview)\n2. [Streaming: Pause/Resume, Overflow, Backpressure](#streaming)\n3. [Permissions: Agent-Level Permissions](#agent-permissions)\n4. [Permissions: Dynamic Updates](#dynamic-permissions)\n5. [Federation: Envelope & Routing](#federation-envelope)\n6. [Federation: Reconnection & Recovery](#federation-reconnection)\n7. [Implementation Phases](#implementation-phases)\n8. [Breaking Changes](#breaking-changes)\n\n---\n\n## Overview\n\n### Gaps Addressed\n\n| ID | Gap | Area |\n|----|-----|------|\n| STREAM-004 | Pause/resume subscriptions | Streaming |\n| STREAM-005 | Overflow notifications | Streaming |\n| STREAM-006 | Backpressure acknowledgment | Streaming |\n| PERM-004 | Agent-level permissions | Permissions |\n| PERM-005 | Dynamic permission updates | Permissions |\n| FED-001 | Federation envelope | Federation |\n| FED-003 | Message queuing during outages | Federation |\n| FED-004 | Federation auto-reconnect | Federation |\n\n### Key Design Decisions\n\n1. **Backpressure**: Optionally implemented on both client and server\n2. **Agent Permissions**: Hybrid approach (role defaults + per-agent overrides)\n3. **Permission Updates**: Flow 1 (system→client) + Flow 4 (owner→agent)\n4. **Federation Envelope**: Full routing metadata, signing deferred\n5. **Federation Recovery**: Outage buffer + event store replay by timestamp\n6. **Error Handling**: `PERMISSION_DENIED` for direct messages, silent drop for broadcasts\n\n---\n\n## Streaming\n\n### Requirements\n\n| ID | Requirement | Priority |\n|----|-------------|----------|\n| S1 | Client can explicitly pause/resume event consumption | Must |\n| S2 | Client receives overflow notifications when events are dropped | Must |\n| S3 | Server can optionally implement flow control via acknowledgments | Should |\n| S4 | Client can optionally send acknowledgments if server supports it | Should |\n| S5 | Backpressure is opt-in for both client and server | Must |\n\n### Type Definitions\n\n```typescript\n// Server capability advertisement\ninterface StreamingCapabilities {\n supportsAck?: boolean;\n supportsFlowControl?: boolean;\n supportsPause?: boolean;\n}\n\n// Add to ParticipantCapabilities\ninterface ParticipantCapabilities {\n // ... existing ...\n streaming?: StreamingCapabilities;\n}\n\n// Subscription state\ntype SubscriptionState = 'active' | 'paused' | 'closed';\n\n// Overflow information\ninterface OverflowInfo {\n eventsDropped: number;\n oldestDroppedId?: string;\n newestDroppedId?: string;\n timestamp: number;\n totalDropped: number;\n}\n\ntype OverflowHandler = (info: OverflowInfo) => void;\n\n// Acknowledgment (optional)\ninterface SubscriptionAckParams {\n subscriptionId: SubscriptionId;\n upToSequence: number;\n}\n```\n\n### Updated Subscription API\n\n```typescript\nclass Subscription implements AsyncIterable<Event> {\n // Existing\n readonly id: SubscriptionId;\n readonly isClosed: boolean;\n readonly bufferedCount: number;\n\n // New properties\n readonly state: SubscriptionState;\n readonly isPaused: boolean;\n readonly totalDropped: number;\n\n // New methods\n pause(): void;\n resume(): void;\n on(type: 'overflow', handler: OverflowHandler): this;\n off(type: 'overflow', handler: OverflowHandler): this;\n ack(upToSequence?: number): void;\n}\n```\n\n### Implementation Tasks\n\n**Phase 1**: Type definitions\n- Add `StreamingCapabilities` to types\n- Add `SubscriptionState`, `OverflowInfo` types\n- Add `SubscriptionAckParams` type\n\n**Phase 2**: Core logic\n- Add `#state`, `#totalDropped` fields to Subscription\n- Implement `pause()`, `resume()` methods\n- Modify async iterator to check `isPaused`\n- Track dropped event IDs for overflow info\n- Add overflow handler support\n\n**Phase 3**: Integration\n- Server advertises capabilities in connect response\n- Add `ack()` method to Subscription\n- TestServer receives and tracks acks\n\n**Phase 4**: Testing\n- Pause/resume tests\n- Overflow handler tests\n- Ack flow tests\n\n---\n\n## Agent Permissions\n\n### Requirements\n\n| ID | Requirement | Priority |\n|----|-------------|----------|\n| P1 | Agents can have visibility rules (who can see them) | Must |\n| P2 | Agents can have messaging rules (who they can message) | Must |\n| P3 | Agents can have acceptance rules (who they accept messages from) | Must |\n| P4 | Role-based default permissions | Must |\n| P5 | Per-agent permission overrides | Must |\n| P6 | System-wide default permissions | Should |\n\n### Design Decision: Hybrid Approach\n\nResolution order:\n1. Start with system default permissions\n2. If agent has a role, deep merge role permissions\n3. Deep merge agent's `permissionOverrides`\n\nDeep merge at field level within `canSee`, `canMessage`, `acceptsFrom`.\n\n### Type Definitions\n\n```typescript\n// Permission rule types\ntype AgentVisibilityRule =\n | 'all' | 'hierarchy' | 'scoped' | 'direct'\n | { include: AgentId[] };\n\ntype ScopeVisibilityRule =\n | 'all' | 'member'\n | { include: ScopeId[] };\n\ntype StructureVisibilityRule = 'full' | 'local' | 'none';\n\ntype AgentMessagingRule =\n | 'all' | 'hierarchy' | 'scoped'\n | { include: AgentId[] };\n\ntype ScopeMessagingRule =\n | 'all' | 'member'\n | { include: ScopeId[] };\n\ntype AgentAcceptanceRule =\n | 'all' | 'hierarchy' | 'scoped'\n | { include: AgentId[] };\n\ntype ClientAcceptanceRule =\n | 'all' | 'none'\n | { include: ParticipantId[] };\n\ntype SystemAcceptanceRule =\n | 'all' | 'none'\n | { include: string[] };\n\n// Main permissions interface\ninterface AgentPermissions {\n canSee?: {\n agents?: AgentVisibilityRule;\n scopes?: ScopeVisibilityRule;\n structure?: StructureVisibilityRule;\n };\n canMessage?: {\n agents?: AgentMessagingRule;\n scopes?: ScopeMessagingRule;\n };\n acceptsFrom?: {\n agents?: AgentAcceptanceRule;\n clients?: ClientAcceptanceRule;\n systems?: SystemAcceptanceRule;\n };\n}\n\n// Agent interface update\ninterface Agent {\n // ... existing fields ...\n permissionOverrides?: Partial<AgentPermissions>;\n}\n\n// System configuration\ninterface AgentPermissionConfig {\n defaultPermissions: AgentPermissions;\n rolePermissions: { [role: string]: AgentPermissions };\n}\n```\n\n### Permission Resolution Function\n\n```typescript\nfunction resolveAgentPermissions(\n agent: Agent,\n config: AgentPermissionConfig\n): AgentPermissions {\n let permissions = deepClone(config.defaultPermissions);\n\n if (agent.role && config.rolePermissions[agent.role]) {\n permissions = deepMerge(permissions, config.rolePermissions[agent.role]);\n }\n\n if (agent.permissionOverrides) {\n permissions = deepMerge(permissions, agent.permissionOverrides);\n }\n\n // Backwards compat: map visibility to canSee.agents\n if (agent.visibility && !agent.permissionOverrides?.canSee?.agents) {\n permissions.canSee = permissions.canSee ?? {};\n permissions.canSee.agents = mapVisibilityToRule(agent.visibility);\n }\n\n return permissions;\n}\n```\n\n### Implementation Tasks\n\n**Phase 1**: Type definitions\n- Add permission rule types\n- Add `AgentPermissions` interface\n- Add `permissionOverrides` to Agent\n- Add `AgentPermissionConfig`\n\n**Phase 2**: Core logic\n- Implement `resolveAgentPermissions()`\n- Implement `deepMerge()` utility\n- Add `canAgentAcceptMessage()` function\n- Update existing permission check functions\n\n**Phase 3**: Integration\n- Add config to `MAPRouterConfig`\n- TestServer resolves permissions\n- Check `acceptsFrom` before message delivery\n- Update `filterVisibleAgents()`\n\n**Phase 4**: Testing\n- Role-based permission tests\n- Override merge tests\n- Acceptance rule tests\n- Backwards compatibility tests\n\n---\n\n## Dynamic Permissions\n\n### Requirements\n\n| ID | Requirement | Priority |\n|----|-------------|----------|\n| D1 | System can update client permissions at runtime | Must |\n| D2 | Agent owner can update agent permissions at runtime | Must |\n| D3 | Permission changes emit events | Should |\n| D4 | Permission updates are immediate | Must |\n\n### Supported Flows\n\n**Flow 1: System updates client**\n```\nSystem ── map/permissions/update ──► Router ── event ──► Client\n```\n\n**Flow 4: Owner updates agent**\n```\nClient ── map/agents/update ──► Router ── event ──► Subscribers\n```\n\n### Type Definitions\n\n```typescript\n// Client permission update\ninterface PermissionsUpdateRequestParams {\n clientId?: ParticipantId;\n permissions: Partial<ParticipantCapabilities>;\n _meta?: Meta;\n}\n\ninterface PermissionsUpdateResponseResult {\n success: boolean;\n effectivePermissions: ParticipantCapabilities;\n _meta?: Meta;\n}\n\n// Permission events\nconst PERMISSION_EVENT_TYPES = {\n PERMISSIONS_CLIENT_UPDATED: 'permissions_client_updated',\n PERMISSIONS_AGENT_UPDATED: 'permissions_agent_updated',\n} as const;\n\ninterface PermissionsClientUpdatedEventData {\n clientId: ParticipantId;\n changes: Partial<ParticipantCapabilities>;\n effectivePermissions: ParticipantCapabilities;\n updatedBy: ParticipantId;\n}\n\ninterface PermissionsAgentUpdatedEventData {\n agentId: AgentId;\n changes: Partial<AgentPermissions>;\n effectivePermissions: AgentPermissions;\n updatedBy: ParticipantId;\n}\n```\n\n### Authorization Rules\n\n- Only system can update client permissions\n- Owner can update their own agents\n- Owner of parent can update child agents\n\n### Implementation Tasks\n\n**Phase 1**: Type definitions\n- Add request/response types\n- Add event types\n- Register `map/permissions/update` method\n\n**Phase 2**: Core logic\n- Authorization check functions\n\n**Phase 3**: Integration\n- Add `permissionOverrides` to `UpdateAgentRequestParams`\n- TestServer handler for `map/permissions/update`\n- Emit permission events\n\n**Phase 4**: Testing\n- Authorization tests\n- Event emission tests\n\n---\n\n## Federation Envelope\n\n### Requirements\n\n| ID | Requirement | Priority |\n|----|-------------|----------|\n| F1 | Messages include source system identifier | Must |\n| F2 | Messages include target system identifier | Must |\n| F3 | Hop count tracking for loop prevention | Must |\n| F4 | Routing path tracking for debugging | Should |\n| F5 | Correlation ID for cross-system tracing | Should |\n| F6 | Message signing for integrity | Deferred |\n\n### Type Definitions\n\n```typescript\ninterface FederationMetadata {\n sourceSystem: string;\n targetSystem: string;\n hopCount: number;\n maxHops?: number;\n path?: string[];\n originTimestamp: Timestamp;\n correlationId?: string;\n signature?: string; // TODO: Define signing later\n}\n\ninterface FederationEnvelope<T = unknown> {\n payload: T;\n federation: FederationMetadata;\n}\n\ninterface FederationRoutingConfig {\n systemId: string;\n maxHops?: number;\n trackPath?: boolean;\n allowedTargets?: string[];\n allowedSources?: string[];\n}\n\n// Updated request params\ninterface FederationRouteRequestParams {\n systemId: string;\n envelope: FederationEnvelope<Message>;\n /** @deprecated Use envelope instead */\n message?: Message;\n _meta?: Meta;\n}\n\n// New error codes\nFEDERATION_LOOP_DETECTED: 5010,\nFEDERATION_MAX_HOPS_EXCEEDED: 5011,\n```\n\n### Routing Logic\n\n```typescript\nfunction processFederationEnvelope<T>(\n envelope: FederationEnvelope<T>,\n config: FederationRoutingConfig\n): FederationEnvelope<T> | null {\n const { federation } = envelope;\n const maxHops = federation.maxHops ?? config.maxHops ?? 10;\n\n // Check hop count\n if (federation.hopCount >= maxHops) return null;\n\n // Check for loops\n if (federation.path?.includes(config.systemId)) return null;\n\n // Check source allowlist\n if (config.allowedSources && !config.allowedSources.includes(federation.sourceSystem)) {\n return null;\n }\n\n // Update for forwarding\n return {\n payload: envelope.payload,\n federation: {\n ...federation,\n hopCount: federation.hopCount + 1,\n path: config.trackPath\n ? [...(federation.path ?? []), config.systemId]\n : undefined,\n },\n };\n}\n\nfunction createFederationEnvelope<T>(\n payload: T,\n sourceSystem: string,\n targetSystem: string,\n options?: { correlationId?: string; maxHops?: number; trackPath?: boolean }\n): FederationEnvelope<T> {\n return {\n payload,\n federation: {\n sourceSystem,\n targetSystem,\n hopCount: 0,\n maxHops: options?.maxHops,\n path: options?.trackPath ? [sourceSystem] : undefined,\n originTimestamp: Date.now(),\n correlationId: options?.correlationId,\n },\n };\n}\n```\n\n### Implementation Tasks\n\n**Phase 1**: Type definitions\n- Add `FederationMetadata`, `FederationEnvelope<T>`\n- Add `FederationRoutingConfig`\n- Update `FederationRouteRequestParams`\n- Add error codes\n\n**Phase 2**: Core logic\n- Create `ts-sdk/src/federation/envelope.ts`\n- Implement `createFederationEnvelope()`\n- Implement `processFederationEnvelope()`\n- Loop detection logic\n\n**Phase 3**: Integration\n- Add routing config to `GatewayConnectionOptions`\n- Update `routeToSystem()` to wrap in envelope\n- Add `routeEnvelope()` method\n- TestServer handles envelope format\n\n**Phase 4**: Testing\n- Envelope creation tests\n- Hop count tests\n- Loop detection tests\n- Backwards compatibility tests\n\n---\n\n## Federation Reconnection\n\n### Requirements\n\n| ID | Requirement | Priority |\n|----|-------------|----------|\n| R1 | GatewayConnection supports auto-reconnect | Must |\n| R2 | Consistent with ClientConnection pattern | Must |\n| R3 | Buffer outbound messages during brief outages | Should |\n| R4 | Replay missed events on reconnect | Should |\n| R5 | Configurable buffer size and TTL | Should |\n| R6 | Replay by timestamp | Must |\n\n### Design: Hybrid Recovery\n\n1. **Outage Buffer** (in-memory, short-term): Messages that failed during brief outage\n2. **Event Store Replay** (persisted, longer-term): State-changing events\n\nBuffer drained first, then events replayed.\n\n### Type Definitions\n\n```typescript\ninterface GatewayReconnectionOptions {\n enabled: boolean;\n maxRetries?: number;\n baseDelayMs?: number;\n maxDelayMs?: number;\n jitter?: boolean;\n}\n\ninterface FederationBufferConfig {\n enabled?: boolean;\n maxQueueSize?: number; // default: 1000\n maxQueueDuration?: number; // default: 60000ms\n overflowPolicy?: 'drop-oldest' | 'drop-newest' | 'reject-new';\n}\n\ninterface FederationReplayConfig {\n enabled?: boolean;\n eventTypes?: EventType[];\n maxEvents?: number; // default: 1000\n maxAgeMs?: number; // default: 300000ms (5 min)\n}\n\ninterface GatewayConnectionOptions extends BaseConnectionOptions {\n name?: string;\n capabilities?: ParticipantCapabilities;\n routing?: FederationRoutingConfig;\n reconnection?: GatewayReconnectionOptions;\n buffer?: FederationBufferConfig;\n replay?: FederationReplayConfig;\n createStream?: () => Promise<Stream>;\n}\n\ntype GatewayReconnectionEventType =\n | 'disconnected'\n | 'reconnecting'\n | 'reconnected'\n | 'reconnectFailed'\n | 'bufferOverflow'\n | 'replayStarted'\n | 'replayCompleted';\n\ninterface GatewayReconnectionEvent {\n type: GatewayReconnectionEventType;\n timestamp: number;\n attempt?: number;\n peerId?: string;\n eventsReplayed?: number;\n messagesBuffered?: number;\n error?: Error;\n}\n```\n\n### Outage Buffer\n\n```typescript\nclass FederationOutageBuffer {\n enqueue(peerId: string, envelope: FederationEnvelope<Message>): boolean;\n drain(peerId: string): FederationEnvelope<Message>[];\n stats(): Map<string, { count: number; oldestAge: number }>;\n}\n```\n\n### Implementation Tasks\n\n**Phase 1**: Type definitions\n- Add all config interfaces\n- Update `GatewayConnectionOptions`\n- Add event types\n\n**Phase 2**: Core logic\n- Create `ts-sdk/src/federation/buffer.ts`\n- Implement `FederationOutageBuffer`\n\n**Phase 3**: Integration\n- Add reconnection to `GatewayConnection`\n- Integrate buffer\n- Track `lastSyncTimestamp` per peer\n- Replay events on reconnect\n\n**Phase 4**: Testing\n- Reconnection tests\n- Buffer overflow tests\n- Replay tests\n- Combined recovery flow tests\n\n---\n\n## Implementation Phases\n\n### Phase 1: Type Definitions (Week 1)\n\n| Issue | Title |\n|-------|-------|\n| [[i-3f48]] | Add streaming backpressure type definitions |\n| [[i-2fao]] | Add agent permission type definitions |\n| [[i-1ha9]] | Add dynamic permission update type definitions |\n| [[i-6yme]] | Add federation envelope type definitions |\n| [[i-9xsr]] | Add federation reconnection type definitions |\n\n**Parallelizable**: i-3f48, i-2fao, i-6yme\n**Dependencies**: i-1ha9 → i-2fao, i-9xsr → i-6yme\n\n### Phase 2: Core Logic (Week 2)\n\n- Subscription pause/resume/overflow implementation\n- `resolveAgentPermissions()` and acceptance checks\n- Federation envelope utilities and routing logic\n- `FederationOutageBuffer` class\n\n### Phase 3: Integration (Week 3)\n\n- Server capability advertisement\n- Subscription ack support\n- Permission update handlers and events\n- Gateway reconnection with buffer/replay\n\n### Phase 4: Testing & Polish (Week 4)\n\n- Comprehensive test coverage for all features\n- Documentation updates\n- Migration guide for breaking changes\n\n---\n\n## Breaking Changes\n\n### Streaming\n- None (additive only)\n\n### Permissions\n- `Agent.visibility` deprecated in favor of `permissionOverrides.canSee.agents`\n- Still supported for backwards compatibility\n\n### Federation\n- `FederationRouteRequestParams.message` deprecated in favor of `envelope`\n- Old format supported temporarily for migration\n\n---\n\n## Open Items / TODOs\n\n1. **Signing**: Define algorithm and key management for `FederationMetadata.signature`\n2. **Policy Entities**: Consider adding policy CRUD for P2 if hybrid approach insufficient\n3. **Permission Request Flow**: Design approval workflow for capability upgrade requests\n4. **Server Flow Control**: Implement send pausing based on ack lag (optional enhancement)\n","priority":1,"archived":0,"archived_at":null,"created_at":"2026-01-29 04:25:28","updated_at":"2026-01-29 04:31:11","parent_id":null,"parent_uuid":null,"relationships":[],"tags":["approved","federation","p1","permissions","streaming"]}
2
- {"id":"s-10j2","uuid":"c6344426-5210-44e6-93ce-1f6731307522","title":"MAP Server SDK - Building Blocks Architecture","file_path":"specs/s-10j2_map_server_sdk_building_blocks_architecture.md","content":"# MAP Server SDK - Building Blocks Architecture\n\n## Overview\n\nThis spec defines the server-side SDK components for the Multi-Agent Protocol (MAP). The SDK provides composable building blocks that allow server implementers to build MAP-compliant routers with varying levels of customization.\n\n## Design Philosophy\n\n### Goals\n1. **Composable** - Building blocks work independently and together\n2. **Pluggable** - Storage, permissions, and federation are swappable\n3. **Optional Persistence** - Works in-memory by default, persistence opt-in\n4. **Handler-First** - Handlers pattern as primitive, interface pattern built on top\n5. **Federation as Decorator** - Core blocks stay simple, federation wraps them\n\n### Non-Goals\n- Providing a single \"MAP Server\" class that does everything\n- Forcing a specific storage backend\n- Requiring federation support\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│ RouterConnection │\n│ (JSON-RPC routing, validation, middleware) │\n└─────────────────────────────────────────────────────────────┘\n │\n ┌────────────────────┼────────────────────┐\n ▼ ▼ ▼\n┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐\n│ AgentRegistry │ │ ScopeManager │ │ SessionManager │\n│ │◄─┤ │ │ │\n│ - agents CRUD │ │ - scope CRUD │ │ - connections │\n│ - state machine │ │ - membership │ │ - cleanup │\n└────────┬────────┘ └────────┬────────┘ └────────┬────────┘\n │ │ │\n └────────────────────┼────────────────────┘\n ▼\n ┌─────────────────┐\n │ EventBus │\n │ │\n │ - emit/subscribe│\n │ - history │\n └────────┬────────┘\n │\n ▼\n ┌───────────────────────┐\n │ SubscriptionManager │\n │ │\n │ - client subscriptions│\n │ - filtering & replay │\n │ - causal ordering │\n └───────────────────────┘\n```\n\n## Design Decisions\n\n### 1. Handler Pattern as Primitive\n\nThe SDK uses a handlers pattern (like Express/Hono) as the core abstraction, with an optional interface adapter for those who prefer OOP.\n\n**Rationale:**\n- MAP servers vary wildly (in-memory test server vs distributed production)\n- Federation is optional - shouldn't force implementing it\n- Middleware is genuinely useful for auth, logging, metrics\n- Easier to provide composable \"starter\" handler sets\n- Interface pattern is trivially built on top\n\n### 2. Constructor Injection for Dependencies\n\nBuilding blocks take dependencies explicitly in their constructor.\n\n**Rationale:**\n- Explicit dependencies - clear what each component needs\n- No magic - easy to understand and debug\n- Tree-shakeable - only import what you use\n- Easy to test - pass mocks directly\n- TypeScript catches missing dependencies at compile time\n\n### 3. Per-Building-Block Storage Interfaces\n\nEach building block that needs persistence defines its own storage interface.\n\n**Rationale:**\n- Query patterns differ significantly (events need time-range, agents need state filter)\n- Allows specialized implementations per data type\n- Users only implement stores for what they need to persist\n- Can mix backends (agents in Postgres, events in Redis)\n\n### 4. Layered Permissions\n\nMiddleware handles coarse-grained (method-level) permissions, injected checker handles fine-grained (resource-level).\n\n**Rationale:**\n- Centralized policy for method access is easy to audit\n- Fine-grained checks (can agent A message agent B?) need handler context\n- Handlers stay clean - permission logic is separated\n- Flexible enough for simple and complex use cases\n\n### 5. Federation as Decorator\n\nFederation wraps core building blocks rather than being integrated into them.\n\n**Rationale:**\n- Core building blocks stay simple and testable\n- Federation is truly optional - don't pay for what you don't use\n- Users can choose what to federate (agents yes, scopes no)\n- Clear separation of concerns\n- Easy to test each layer independently\n\n### 6. Optional OOP Interface\n\nProvide a `MAPRouter` interface with `routerToHandlers()` adapter for users who prefer class-based implementations.\n\n**Rationale:**\n- Some teams prefer OOP patterns\n- TypeScript enforces all methods are implemented\n- Easy to migrate between patterns\n- Interface is trivially built on handler primitives\n\n### 7. Connection Resume Support\n\n`RouterConnection` supports resuming sessions after disconnect/reconnect.\n\n**Rationale:**\n- Network interruptions are common\n- Agents shouldn't lose state on brief disconnects\n- Subscriptions can resume from last event ID\n- Better UX for long-running agents\n\n### 8. Causal Event Ordering\n\n`SubscriptionManager` delivers events respecting causal dependencies by default.\n\n**Rationale:**\n- Events often have dependencies (agent.registered before agent.state.changed)\n- Out-of-order delivery causes bugs in subscribers\n- Can be disabled for performance-critical cases\n- Uses `causedBy` field in events\n\n### 9. Nested Scopes\n\nScopes support parent-child hierarchy.\n\n**Rationale:**\n- Natural for organizational structures (org > team > project)\n- Permissions can cascade down\n- Membership queries can include descendants\n- Optional - flat scopes still work\n\n### 10. Message Queuing\n\n`MessageRouter` queues messages for temporarily offline agents.\n\n**Rationale:**\n- Brief disconnects shouldn't lose messages\n- Configurable queue size and TTL\n- Fail-fast option still available\n- Essential for reliable delivery\n\n### 11. Flexible Cleanup Component\n\n`ResourceCleaner` handles stale resources with pluggable strategies.\n\n**Rationale:**\n- Sessions can disconnect without cleanup\n- Orphaned agents/subscriptions waste resources\n- Different deployments need different strategies\n- Configurable thresholds and behaviors\n\n---\n\n## Core Interfaces\n\n### Events\n\n```typescript\n/**\n * A MAP event that can be emitted, stored, and delivered to subscribers.\n */\ninterface MAPEvent {\n /** ULID for ordering and deduplication */\n id: string;\n /** Event type (e.g., 'agent.registered', 'scope.agent.joined') */\n type: string;\n /** Unix timestamp in milliseconds */\n timestamp: number;\n /** Event-specific payload */\n data: unknown;\n /** Source of the event */\n source?: {\n agentId?: string;\n scopeId?: string;\n sessionId?: string;\n };\n /** Parent event ID for causal ordering */\n causedBy?: string;\n}\n\n/**\n * Filter criteria for querying events.\n */\ninterface EventFilter {\n /** Filter by event types */\n types?: string[];\n /** Return events after this event ID */\n since?: string;\n /** Return events before this event ID */\n until?: string;\n /** Maximum number of events to return */\n limit?: number;\n}\n```\n\n### EventStore (Pluggable Backend)\n\n```typescript\n/**\n * Storage backend for events. Implement this interface to provide\n * custom persistence (Redis, Postgres, etc.).\n */\ninterface EventStore {\n /** Append an event to storage */\n append(event: MAPEvent): void;\n \n /** Query events matching filter criteria */\n query(filter: EventFilter): MAPEvent[];\n \n /** Get a specific event by ID */\n getById(id: string): MAPEvent | undefined;\n \n /** Clear all events (useful for testing) */\n clear(): void;\n}\n```\n\n### EventBus\n\n```typescript\n/**\n * Central event dispatcher. All building blocks emit events through this.\n */\ninterface EventBus {\n /**\n * Emit an event. Assigns ID and timestamp automatically.\n * @returns The complete event with ID and timestamp\n */\n emit(event: Omit<MAPEvent, 'id' | 'timestamp'>): MAPEvent;\n \n /**\n * Subscribe to events by type.\n * @param types Event type(s) to listen for, or '*' for all\n * @param handler Callback invoked for each matching event\n * @returns Unsubscribe function\n */\n on(types: string | string[], handler: (event: MAPEvent) => void): () => void;\n \n /**\n * Query historical events.\n */\n getEvents(filter: EventFilter): MAPEvent[];\n \n /** The underlying storage backend */\n readonly store: EventStore;\n}\n\ninterface EventBusOptions {\n /** Custom event store (defaults to InMemoryEventStore) */\n store?: EventStore;\n}\n```\n\n### Agents\n\n```typescript\n/** Valid agent states */\ntype AgentState = 'idle' | 'busy' | 'suspended' | 'stopped';\n\n/**\n * A registered agent in the system.\n */\ninterface RegisteredAgent {\n /** Unique agent identifier */\n id: string;\n /** Human-readable name */\n name: string;\n /** Optional role identifier */\n role?: string;\n /** Current agent state */\n state: AgentState;\n /** Arbitrary metadata */\n metadata: Record<string, unknown>;\n /** Session that owns this agent */\n sessionId: string;\n /** Registration timestamp */\n registeredAt: number;\n /** Last state change timestamp */\n lastStateChange: number;\n}\n\n/**\n * Filter criteria for listing agents.\n */\ninterface AgentFilter {\n /** Filter by state */\n state?: AgentState;\n /** Filter by role */\n role?: string;\n /** Filter by session */\n sessionId?: string;\n /** Filter by scope membership */\n scopeId?: string;\n}\n```\n\n### AgentStore (Pluggable Backend)\n\n```typescript\n/**\n * Storage backend for agents. Implement for persistence.\n */\ninterface AgentStore {\n save(agent: RegisteredAgent): void;\n get(id: string): RegisteredAgent | undefined;\n list(filter?: AgentFilter): RegisteredAgent[];\n delete(id: string): boolean;\n clear(): void;\n}\n```\n\n### AgentRegistry\n\n```typescript\n/**\n * Manages agent lifecycle and state.\n * \n * Events emitted:\n * - agent.registered\n * - agent.unregistered\n * - agent.state.changed\n * - agent.metadata.changed\n */\ninterface AgentRegistry {\n /**\n * Register a new agent.\n */\n register(params: {\n name: string;\n role?: string;\n metadata?: Record<string, unknown>;\n sessionId: string;\n }): RegisteredAgent;\n \n /** Get agent by ID */\n get(id: string): RegisteredAgent | undefined;\n \n /** List agents matching filter */\n list(filter?: AgentFilter): RegisteredAgent[];\n \n /** Unregister an agent */\n unregister(id: string): boolean;\n \n /** Update agent state */\n updateState(id: string, state: AgentState): RegisteredAgent;\n \n /** Update agent metadata (merges with existing) */\n updateMetadata(id: string, metadata: Record<string, unknown>): RegisteredAgent;\n \n /** Unregister all agents for a session (cleanup on disconnect) */\n unregisterBySession(sessionId: string): string[];\n}\n\ninterface AgentRegistryOptions {\n eventBus: EventBus;\n store?: AgentStore;\n}\n```\n\n### Scopes (with Hierarchy)\n\n```typescript\n/**\n * A scope for grouping agents. Supports parent-child hierarchy.\n */\ninterface Scope {\n /** Unique scope identifier */\n id: string;\n /** Human-readable name */\n name: string;\n /** Arbitrary metadata */\n metadata: Record<string, unknown>;\n /** Creation timestamp */\n createdAt: number;\n /** Creator (agent or session ID) */\n createdBy?: string;\n /** Parent scope ID for hierarchy (null for root scopes) */\n parentId?: string;\n}\n\n/**\n * Filter criteria for listing scopes.\n */\ninterface ScopeFilter {\n /** Filter by parent (null for root scopes only) */\n parentId?: string | null;\n /** Include all descendants of this scope */\n ancestorId?: string;\n}\n```\n\n### ScopeStore (Pluggable Backend)\n\n```typescript\n/**\n * Storage backend for scopes. Implement for persistence.\n */\ninterface ScopeStore {\n saveScope(scope: Scope): void;\n getScope(id: string): Scope | undefined;\n listScopes(filter?: ScopeFilter): Scope[];\n deleteScope(id: string): boolean;\n \n /** Get all ancestor scope IDs (parent chain) */\n getAncestors(scopeId: string): string[];\n \n /** Get all descendant scope IDs */\n getDescendants(scopeId: string): string[];\n \n // Membership\n addMember(scopeId: string, agentId: string): void;\n removeMember(scopeId: string, agentId: string): void;\n getMembers(scopeId: string, includeDescendants?: boolean): string[];\n getScopesForAgent(agentId: string): string[];\n \n clear(): void;\n}\n```\n\n### ScopeManager\n\n```typescript\n/**\n * Manages scopes and membership with hierarchy support.\n * \n * Events emitted:\n * - scope.created\n * - scope.deleted\n * - scope.agent.joined\n * - scope.agent.left\n */\ninterface ScopeManager {\n /** Create a new scope */\n create(params: {\n name: string;\n metadata?: Record<string, unknown>;\n createdBy?: string;\n parentId?: string;\n }): Scope;\n \n /** Get scope by ID */\n get(id: string): Scope | undefined;\n \n /** List scopes with optional filter */\n list(filter?: ScopeFilter): Scope[];\n \n /** Delete a scope (and optionally its descendants) */\n delete(id: string, opts?: { deleteDescendants?: boolean }): boolean;\n \n /** Get parent scope */\n getParent(scopeId: string): Scope | undefined;\n \n /** Get child scopes */\n getChildren(scopeId: string): Scope[];\n \n /** Get all ancestor scopes (parent chain to root) */\n getAncestors(scopeId: string): Scope[];\n \n /** Get all descendant scopes */\n getDescendants(scopeId: string): Scope[];\n \n /** Add agent to scope */\n join(scopeId: string, agentId: string): void;\n \n /** Remove agent from scope */\n leave(scopeId: string, agentId: string): void;\n \n /** Get all agents in a scope (optionally including descendants) */\n getMembers(scopeId: string, opts?: { includeDescendants?: boolean }): string[];\n \n /** Get all scopes an agent belongs to */\n getScopesForAgent(agentId: string): string[];\n \n /** Check if agent is in scope (optionally checking ancestors) */\n isMember(scopeId: string, agentId: string, opts?: { checkAncestors?: boolean }): boolean;\n \n /** Remove agent from all scopes (cleanup) */\n leaveAll(agentId: string): string[];\n}\n\ninterface ScopeManagerOptions {\n eventBus: EventBus;\n store?: ScopeStore;\n}\n```\n\n### Sessions (with Resume Support)\n\n```typescript\n/** Connection role */\ntype SessionRole = 'client' | 'agent' | 'gateway';\n\n/** Session status */\ntype SessionStatus = 'connected' | 'disconnected' | 'expired';\n\n/**\n * A connected session (client, agent, or gateway).\n * Supports disconnect/resume lifecycle.\n */\ninterface Session {\n /** Unique session identifier */\n id: string;\n /** Connection role */\n role: SessionRole;\n /** Human-readable name */\n name?: string;\n /** Current status */\n status: SessionStatus;\n /** Initial connection timestamp */\n connectedAt: number;\n /** Last activity timestamp */\n lastActivity: number;\n /** Last disconnect timestamp (if disconnected) */\n disconnectedAt?: number;\n /** Arbitrary metadata */\n metadata: Record<string, unknown>;\n /** Agents registered via this session */\n agentIds: string[];\n /** Active subscriptions */\n subscriptionIds: string[];\n /** Resume token for reconnection */\n resumeToken?: string;\n}\n\n/**\n * Resume result when reconnecting.\n */\ninterface ResumeResult {\n /** Whether resume succeeded */\n success: boolean;\n /** The resumed session (if successful) */\n session?: Session;\n /** Reason for failure (if unsuccessful) */\n reason?: 'expired' | 'invalid_token' | 'not_found';\n}\n```\n\n### SessionStore (Pluggable Backend)\n\n```typescript\n/**\n * Storage backend for sessions. Implement for persistence.\n */\ninterface SessionStore {\n save(session: Session): void;\n get(id: string): Session | undefined;\n getByResumeToken(token: string): Session | undefined;\n list(filter?: { role?: SessionRole; status?: SessionStatus }): Session[];\n delete(id: string): boolean;\n clear(): void;\n}\n```\n\n### SessionManager\n\n```typescript\n/**\n * Tracks connections and their resources with resume support.\n * \n * Events emitted:\n * - session.connected\n * - session.disconnected\n * - session.resumed\n * - session.expired\n */\ninterface SessionManager {\n /** Create a new session */\n create(params: {\n role: SessionRole;\n name?: string;\n metadata?: Record<string, unknown>;\n }): Session;\n \n /** Get session by ID */\n get(id: string): Session | undefined;\n \n /** List sessions */\n list(filter?: { role?: SessionRole; status?: SessionStatus }): Session[];\n \n /**\n * Mark session as disconnected (but resumable).\n * @returns Resume token for reconnection\n */\n disconnect(id: string): string | undefined;\n \n /**\n * Resume a disconnected session.\n */\n resume(resumeToken: string): ResumeResult;\n \n /**\n * Permanently close a session (not resumable).\n * @returns Session for cleanup\n */\n close(id: string): Session | undefined;\n \n /**\n * Expire sessions that have been disconnected too long.\n * @returns Expired session IDs\n */\n expireStale(maxDisconnectMs: number): string[];\n \n /** Track agent registration */\n addAgent(sessionId: string, agentId: string): void;\n removeAgent(sessionId: string, agentId: string): void;\n \n /** Track subscriptions */\n addSubscription(sessionId: string, subscriptionId: string): void;\n removeSubscription(sessionId: string, subscriptionId: string): void;\n \n /** Update last activity timestamp */\n touch(id: string): void;\n}\n\ninterface SessionManagerOptions {\n eventBus: EventBus;\n store?: SessionStore;\n /** How long disconnected sessions remain resumable (default: 5 minutes) */\n resumeWindowMs?: number;\n}\n```\n\n### Subscriptions (with Causal Ordering)\n\n```typescript\n/**\n * Filter for subscription events.\n */\ninterface SubscriptionFilter {\n /** Event types to receive */\n eventTypes?: string[];\n /** Agents to watch */\n agents?: string[];\n /** Scopes to watch (includes nested scopes by default) */\n scopes?: string[];\n}\n\n/**\n * An active subscription.\n */\ninterface Subscription {\n /** Unique subscription identifier */\n id: string;\n /** Owning session */\n sessionId: string;\n /** Filter criteria */\n filter: SubscriptionFilter;\n /** Creation timestamp */\n createdAt: number;\n /** Last delivered event ID (for replay/resume) */\n lastEventId?: string;\n /** Whether delivery is paused */\n paused: boolean;\n}\n\n/**\n * Causal ordering configuration.\n */\ninterface CausalOrderingOptions {\n /** Enable causal ordering (default: true) */\n enabled?: boolean;\n /** Max time to wait for dependencies (default: 5000ms) */\n maxWaitMs?: number;\n /** Max events to buffer while waiting (default: 1000) */\n maxBufferSize?: number;\n}\n```\n\n### SubscriptionStore (Pluggable Backend)\n\n```typescript\n/**\n * Storage backend for subscriptions. Implement for persistence.\n */\ninterface SubscriptionStore {\n save(subscription: Subscription): void;\n get(id: string): Subscription | undefined;\n list(filter?: { sessionId?: string }): Subscription[];\n delete(id: string): boolean;\n clear(): void;\n}\n```\n\n### SubscriptionManager\n\n```typescript\n/**\n * Manages client subscriptions to events with causal ordering.\n */\ninterface SubscriptionManager {\n /** Create a new subscription */\n create(params: {\n sessionId: string;\n filter: SubscriptionFilter;\n startAfter?: string;\n }): Subscription;\n \n /** Get subscription by ID */\n get(id: string): Subscription | undefined;\n \n /** Cancel a subscription */\n cancel(id: string): boolean;\n \n /** Cancel all subscriptions for a session */\n cancelBySession(sessionId: string): string[];\n \n /** Pause event delivery */\n pause(id: string): void;\n \n /** Resume event delivery */\n resume(id: string): void;\n \n /** Update last delivered event ID */\n acknowledge(id: string, eventId: string): void;\n \n /**\n * Find subscriptions that should receive an event.\n * @returns Subscription IDs that match\n */\n match(event: MAPEvent): string[];\n \n /**\n * Get ordered event stream for a subscription.\n * Handles causal ordering internally.\n */\n getEventStream(id: string): AsyncIterable<MAPEvent>;\n}\n\ninterface SubscriptionManagerOptions {\n eventBus: EventBus;\n store?: SubscriptionStore;\n scopes?: ScopeManager;\n causalOrdering?: CausalOrderingOptions;\n}\n```\n\n### Messages (with Queuing)\n\n```typescript\n/**\n * A message between agents.\n */\ninterface Message {\n /** Unique message identifier */\n id: string;\n /** Sender (agent or session ID) */\n from: string;\n /** Recipient(s) - agent ID, scope ID, or array */\n to: string | string[];\n /** Message content */\n payload: unknown;\n /** Send timestamp */\n timestamp: number;\n /** For request/response patterns */\n replyTo?: string;\n /** Message priority (lower = higher priority) */\n priority?: number;\n /** TTL in milliseconds (for queued messages) */\n ttlMs?: number;\n}\n\n/**\n * Queued message awaiting delivery.\n */\ninterface QueuedMessage {\n message: Message;\n targetAgentId: string;\n queuedAt: number;\n expiresAt: number;\n attempts: number;\n}\n\n/**\n * Callback for message delivery.\n */\ntype DeliveryHandler = (agentId: string, message: Message) => void;\n\n/**\n * Message queue configuration.\n */\ninterface MessageQueueOptions {\n /** Enable queuing for offline agents (default: true) */\n enabled?: boolean;\n /** Max messages per agent (default: 100) */\n maxPerAgent?: number;\n /** Default TTL in milliseconds (default: 60000) */\n defaultTtlMs?: number;\n /** Max total queued messages (default: 10000) */\n maxTotal?: number;\n}\n```\n\n### MessageStore (Pluggable Backend)\n\n```typescript\n/**\n * Storage backend for message queue. Implement for persistence.\n */\ninterface MessageQueueStore {\n enqueue(msg: QueuedMessage): void;\n dequeue(agentId: string, limit?: number): QueuedMessage[];\n peek(agentId: string, limit?: number): QueuedMessage[];\n remove(messageId: string): boolean;\n getQueueSize(agentId: string): number;\n getTotalSize(): number;\n expireOld(): number;\n clear(): void;\n}\n```\n\n### MessageRouter\n\n```typescript\n/**\n * Routes messages between agents with queuing support.\n * \n * Events emitted:\n * - message.sent\n * - message.delivered\n * - message.queued\n * - message.expired\n */\ninterface MessageRouter {\n /** Send to a specific agent */\n sendToAgent(params: {\n from: string;\n to: string;\n payload: unknown;\n replyTo?: string;\n priority?: number;\n ttlMs?: number;\n }): Message;\n \n /** Broadcast to all agents in a scope */\n sendToScope(params: {\n from: string;\n scopeId: string;\n payload: unknown;\n excludeSender?: boolean;\n includeDescendants?: boolean;\n }): Message;\n \n /** Set delivery callback */\n onDeliver(handler: DeliveryHandler): void;\n \n /**\n * Flush queued messages for an agent (call when agent reconnects).\n * @returns Number of messages delivered\n */\n flushQueue(agentId: string): number;\n \n /** Get queue stats */\n getQueueStats(): {\n total: number;\n byAgent: Record<string, number>;\n };\n}\n\ninterface MessageRouterOptions {\n eventBus: EventBus;\n agents: AgentRegistry;\n scopes: ScopeManager;\n queue?: MessageQueueOptions;\n queueStore?: MessageQueueStore;\n}\n```\n\n---\n\n## Router Connection\n\n### Handler Types\n\n```typescript\n/**\n * Context passed to handlers.\n */\ninterface HandlerContext {\n /** Current session */\n session: Session;\n /** Request metadata */\n requestId: string;\n /** Abort signal for cancellation */\n signal: AbortSignal;\n}\n\n/**\n * A request handler function.\n */\ntype Handler<TParams = unknown, TResult = unknown> = (\n params: TParams,\n ctx: HandlerContext\n) => Promise<TResult>;\n\n/**\n * Registry of method handlers.\n */\ntype HandlerRegistry = Record<string, Handler>;\n\n/**\n * Middleware function.\n */\ntype Middleware = (\n method: string,\n params: unknown,\n ctx: HandlerContext,\n next: () => Promise<unknown>\n) => Promise<unknown>;\n```\n\n### RouterConnection\n\n```typescript\n/**\n * JSON-RPC router that dispatches to handlers.\n * Supports session resume on reconnect.\n */\ninterface RouterConnection {\n /** Start processing messages */\n start(): Promise<void>;\n \n /** Stop processing and close */\n close(): Promise<void>;\n \n /** Promise that resolves when connection closes */\n readonly closed: Promise<void>;\n \n /** Current session */\n readonly session: Session;\n \n /** Send notification to connected peer */\n notify(method: string, params: unknown): Promise<void>;\n}\n\ninterface RouterConnectionOptions {\n /** Bidirectional stream */\n stream: Stream;\n \n /** Method handlers */\n handlers: HandlerRegistry;\n \n /** Middleware chain (executed in order) */\n middleware?: Middleware[];\n \n /** Session manager for connection tracking */\n sessions: SessionManager;\n \n /** Resume token for reconnection (optional) */\n resumeToken?: string;\n \n /** Role for new sessions */\n role: SessionRole;\n \n /** Name for new sessions */\n name?: string;\n}\n```\n\n### Handler Factories\n\n```typescript\n/**\n * Create handlers for agent-related methods.\n */\nfunction createAgentHandlers(opts: {\n agents: AgentRegistry;\n sessions: SessionManager;\n permissions?: PermissionChecker;\n}): HandlerRegistry;\n\n/**\n * Create handlers for scope-related methods.\n */\nfunction createScopeHandlers(opts: {\n scopes: ScopeManager;\n agents: AgentRegistry;\n permissions?: PermissionChecker;\n}): HandlerRegistry;\n\n/**\n * Create handlers for subscription-related methods.\n */\nfunction createSubscriptionHandlers(opts: {\n subscriptions: SubscriptionManager;\n eventBus: EventBus;\n permissions?: PermissionChecker;\n}): HandlerRegistry;\n\n/**\n * Create handlers for message-related methods.\n */\nfunction createMessageHandlers(opts: {\n messages: MessageRouter;\n permissions?: PermissionChecker;\n}): HandlerRegistry;\n\n/**\n * Create handlers for connection lifecycle.\n */\nfunction createConnectionHandlers(opts: {\n sessions: SessionManager;\n}): HandlerRegistry;\n```\n\n---\n\n## Optional OOP Interface\n\n### MAPRouter Interface\n\n```typescript\n/**\n * OOP interface for implementing a MAP router.\n * All methods correspond to protocol methods.\n */\ninterface MAPRouter {\n // Connection\n connect(params: ConnectRequest, ctx: HandlerContext): Promise<ConnectResponse>;\n disconnect(params: DisconnectRequest, ctx: HandlerContext): Promise<void>;\n resume(params: ResumeRequest, ctx: HandlerContext): Promise<ResumeResponse>;\n \n // Agents\n registerAgent(params: RegisterAgentRequest, ctx: HandlerContext): Promise<RegisterAgentResponse>;\n unregisterAgent(params: UnregisterAgentRequest, ctx: HandlerContext): Promise<void>;\n listAgents(params: ListAgentsRequest, ctx: HandlerContext): Promise<Agent[]>;\n getAgent(params: GetAgentRequest, ctx: HandlerContext): Promise<Agent>;\n updateAgentState(params: UpdateAgentStateRequest, ctx: HandlerContext): Promise<Agent>;\n updateAgentMetadata(params: UpdateAgentMetadataRequest, ctx: HandlerContext): Promise<Agent>;\n \n // Scopes\n createScope(params: CreateScopeRequest, ctx: HandlerContext): Promise<Scope>;\n deleteScope(params: DeleteScopeRequest, ctx: HandlerContext): Promise<void>;\n listScopes(params: ListScopesRequest, ctx: HandlerContext): Promise<Scope[]>;\n getScope(params: GetScopeRequest, ctx: HandlerContext): Promise<Scope>;\n joinScope(params: JoinScopeRequest, ctx: HandlerContext): Promise<void>;\n leaveScope(params: LeaveScopeRequest, ctx: HandlerContext): Promise<void>;\n \n // Messages\n send(params: SendRequest, ctx: HandlerContext): Promise<SendResponse>;\n \n // Subscriptions\n subscribe(params: SubscribeRequest, ctx: HandlerContext): Promise<SubscribeResponse>;\n unsubscribe(params: UnsubscribeRequest, ctx: HandlerContext): Promise<void>;\n replay(params: ReplayRequest, ctx: HandlerContext): Promise<ReplayResponse>;\n}\n```\n\n### Router to Handlers Adapter\n\n```typescript\n/**\n * Convert a MAPRouter instance to a HandlerRegistry.\n */\nfunction routerToHandlers(router: MAPRouter): HandlerRegistry;\n\n/**\n * Example usage:\n * \n * class MyRouter implements MAPRouter {\n * // ... implement all methods\n * }\n * \n * const router = new MyRouter();\n * const connection = new RouterConnection(stream, {\n * handlers: routerToHandlers(router),\n * sessions,\n * });\n */\n```\n\n### Abstract Base Router\n\n```typescript\n/**\n * Abstract base class with default implementations using building blocks.\n * Extend and override specific methods as needed.\n */\nabstract class BaseMAPRouter implements MAPRouter {\n constructor(protected opts: {\n agents: AgentRegistry;\n scopes: ScopeManager;\n sessions: SessionManager;\n subscriptions: SubscriptionManager;\n messages: MessageRouter;\n eventBus: EventBus;\n });\n \n // Default implementations that delegate to building blocks\n async registerAgent(params, ctx) {\n return this.opts.agents.register({\n ...params,\n sessionId: ctx.session.id,\n });\n }\n \n // ... other defaults\n \n // Override in subclass for custom behavior\n}\n```\n\n---\n\n## Permissions\n\n### PermissionChecker\n\n```typescript\n/**\n * Permission check result.\n */\ninterface PermissionResult {\n allowed: boolean;\n reason?: string;\n}\n\n/**\n * Checks permissions at various levels.\n */\ninterface PermissionChecker {\n /** Check if method is allowed for session role */\n canCallMethod(session: Session, method: string): PermissionResult;\n \n /** Check if session can access a scope */\n canAccessScope(session: Session, scopeId: string, action: string): PermissionResult;\n \n /** Check if agent can perform action on target */\n canAgentPerform(agentId: string, action: string, targetId: string): PermissionResult;\n}\n\n/**\n * Permission rule definition.\n */\ninterface PermissionRule {\n /** Method pattern (glob supported) */\n method?: string;\n /** Required session role(s) */\n roles?: SessionRole[];\n /** Custom check function */\n check?: (session: Session, params: unknown) => PermissionResult;\n}\n\ninterface PermissionCheckerOptions {\n rules: PermissionRule[];\n /** Default behavior when no rule matches */\n defaultAllow?: boolean;\n}\n```\n\n### Permission Middleware\n\n```typescript\n/**\n * Middleware that checks method-level permissions.\n */\nfunction permissionMiddleware(checker: PermissionChecker): Middleware;\n```\n\n---\n\n## Resource Cleanup\n\n### ResourceCleaner\n\n```typescript\n/**\n * Strategy for handling cleanup.\n */\ninterface CleanupStrategy {\n /** Called for each stale session */\n onStaleSession?(session: Session): void;\n \n /** Called for each orphaned agent */\n onOrphanedAgent?(agent: RegisteredAgent): void;\n \n /** Called for each orphaned subscription */\n onOrphanedSubscription?(subscription: Subscription): void;\n \n /** Called for expired queued messages */\n onExpiredMessages?(count: number): void;\n}\n\n/**\n * Cleanup thresholds configuration.\n */\ninterface CleanupThresholds {\n /** Max time a session can be disconnected before expiring */\n sessionDisconnectMs?: number;\n /** Max time since last activity before session is stale */\n sessionInactiveMs?: number;\n /** Run cleanup every N milliseconds (0 = manual only) */\n intervalMs?: number;\n}\n\n/**\n * Handles cleanup of stale resources.\n */\ninterface ResourceCleaner {\n /**\n * Run cleanup cycle.\n * @returns Stats about what was cleaned\n */\n run(): Promise<CleanupStats>;\n \n /** Start automatic cleanup interval */\n start(): void;\n \n /** Stop automatic cleanup */\n stop(): void;\n \n /** Check if automatic cleanup is running */\n readonly running: boolean;\n}\n\ninterface CleanupStats {\n sessionsExpired: number;\n agentsUnregistered: number;\n subscriptionsCancelled: number;\n messagesExpired: number;\n}\n\ninterface ResourceCleanerOptions {\n sessions: SessionManager;\n agents: AgentRegistry;\n subscriptions: SubscriptionManager;\n messages: MessageRouter;\n thresholds?: CleanupThresholds;\n strategy?: CleanupStrategy;\n}\n```\n\n---\n\n## Federation (Decorator Pattern)\n\n### FederationGateway\n\n```typescript\n/**\n * Peer system connection.\n */\ninterface PeerConnection {\n systemId: string;\n endpoint: string;\n status: 'connected' | 'disconnected' | 'connecting';\n connectedAt?: number;\n lastActivity?: number;\n}\n\n/**\n * Federation envelope for cross-system messages.\n */\ninterface FederationEnvelope {\n /** Original message */\n payload: unknown;\n /** Routing metadata */\n routing: {\n from: string;\n to: string;\n hops: string[];\n maxHops: number;\n };\n /** Timestamp */\n timestamp: number;\n}\n\n/**\n * Gateway for federation between MAP systems.\n */\ninterface FederationGateway {\n /** Connect to a peer system */\n connectPeer(params: {\n systemId: string;\n endpoint: string;\n credentials?: unknown;\n }): Promise<PeerConnection>;\n \n /** Route message to peer */\n routeToPeer(systemId: string, envelope: FederationEnvelope): Promise<void>;\n \n /** Handle incoming messages from peers */\n onPeerMessage(handler: (from: string, envelope: FederationEnvelope) => void): void;\n \n /** List connected peers */\n listPeers(): PeerConnection[];\n \n /** Disconnect from peer */\n disconnectPeer(systemId: string): void;\n \n /** Outage buffer for resilience */\n readonly buffer: OutageBuffer;\n}\n\n/**\n * Buffers messages during peer outages.\n */\ninterface OutageBuffer {\n /** Add message to buffer */\n add(systemId: string, envelope: FederationEnvelope): void;\n \n /** Flush buffered messages (on reconnect) */\n flush(systemId: string): FederationEnvelope[];\n \n /** Get buffer stats */\n stats(): { total: number; bySystem: Record<string, number> };\n}\n```\n\n### Federated Decorators\n\n```typescript\n/**\n * Wraps AgentRegistry to sync agents across systems.\n */\nclass FederatedAgentRegistry implements AgentRegistry {\n constructor(opts: {\n local: AgentRegistry;\n gateway: FederationGateway;\n sync?: {\n /** Sync local registrations to peers */\n onRegister?: boolean;\n /** Sync state changes to peers */\n onStateChange?: boolean;\n /** Include remote agents in list() */\n includeRemote?: boolean;\n };\n });\n}\n\n/**\n * Wraps ScopeManager to sync scopes across systems.\n */\nclass FederatedScopeManager implements ScopeManager {\n constructor(opts: {\n local: ScopeManager;\n gateway: FederationGateway;\n sync?: {\n onCreateScope?: boolean;\n onMembershipChange?: boolean;\n includeRemote?: boolean;\n };\n });\n}\n\n/**\n * Wraps MessageRouter to route messages across systems.\n */\nclass FederatedMessageRouter implements MessageRouter {\n constructor(opts: {\n local: MessageRouter;\n gateway: FederationGateway;\n agents: AgentRegistry;\n });\n}\n```\n\n---\n\n## Built-in Implementations\n\n### In-Memory Stores (Default)\n\n```typescript\nclass InMemoryEventStore implements EventStore { /* ... */ }\nclass InMemoryAgentStore implements AgentStore { /* ... */ }\nclass InMemoryScopeStore implements ScopeStore { /* ... */ }\nclass InMemorySessionStore implements SessionStore { /* ... */ }\nclass InMemorySubscriptionStore implements SubscriptionStore { /* ... */ }\nclass InMemoryMessageQueueStore implements MessageQueueStore { /* ... */ }\n```\n\n### Core Building Blocks\n\n```typescript\nclass EventBusImpl implements EventBus { /* ... */ }\nclass AgentRegistryImpl implements AgentRegistry { /* ... */ }\nclass ScopeManagerImpl implements ScopeManager { /* ... */ }\nclass SessionManagerImpl implements SessionManager { /* ... */ }\nclass SubscriptionManagerImpl implements SubscriptionManager { /* ... */ }\nclass MessageRouterImpl implements MessageRouter { /* ... */ }\nclass ResourceCleanerImpl implements ResourceCleaner { /* ... */ }\n```\n\n---\n\n## Usage Examples\n\n### Minimal Server (In-Memory)\n\n```typescript\nimport {\n EventBus,\n AgentRegistry,\n ScopeManager,\n SessionManager,\n SubscriptionManager,\n MessageRouter,\n RouterConnection,\n createAgentHandlers,\n createScopeHandlers,\n createSubscriptionHandlers,\n createMessageHandlers,\n createConnectionHandlers,\n} from '@anthropic/multi-agent-protocol/server';\n\n// Create building blocks with defaults\nconst eventBus = new EventBus();\nconst sessions = new SessionManager({ eventBus });\nconst agents = new AgentRegistry({ eventBus });\nconst scopes = new ScopeManager({ eventBus });\nconst subscriptions = new SubscriptionManager({ eventBus, scopes });\nconst messages = new MessageRouter({ eventBus, agents, scopes });\n\n// Wire up handlers\nconst handlers = {\n ...createConnectionHandlers({ sessions }),\n ...createAgentHandlers({ agents, sessions }),\n ...createScopeHandlers({ scopes, agents }),\n ...createSubscriptionHandlers({ subscriptions, eventBus }),\n ...createMessageHandlers({ messages }),\n};\n\n// Create router for each connection\nfunction handleConnection(stream: Stream) {\n const router = new RouterConnection(stream, {\n handlers,\n sessions,\n role: 'agent',\n });\n router.start();\n}\n```\n\n### With OOP Interface\n\n```typescript\nimport { BaseMAPRouter, routerToHandlers } from '@anthropic/multi-agent-protocol/server';\n\nclass MyRouter extends BaseMAPRouter {\n // Override specific methods\n async registerAgent(params, ctx) {\n // Custom logic before registration\n console.log(`Registering agent: ${params.name}`);\n \n // Call default implementation\n const agent = await super.registerAgent(params, ctx);\n \n // Custom logic after registration\n await this.notifyAdmins(agent);\n \n return agent;\n }\n}\n\nconst router = new MyRouter({ agents, scopes, sessions, subscriptions, messages, eventBus });\nconst connection = new RouterConnection(stream, {\n handlers: routerToHandlers(router),\n sessions,\n role: 'agent',\n});\n```\n\n### With Session Resume\n\n```typescript\nfunction handleConnection(stream: Stream, resumeToken?: string) {\n const router = new RouterConnection(stream, {\n handlers,\n sessions,\n role: 'agent',\n resumeToken, // Pass token from client's reconnect request\n });\n \n router.start().then(() => {\n if (resumeToken) {\n // Flush any queued messages\n const agentIds = router.session.agentIds;\n for (const agentId of agentIds) {\n messages.flushQueue(agentId);\n }\n }\n });\n}\n```\n\n### With Cleanup\n\n```typescript\nimport { ResourceCleaner } from '@anthropic/multi-agent-protocol/server';\n\nconst cleaner = new ResourceCleaner({\n sessions,\n agents,\n subscriptions,\n messages,\n thresholds: {\n sessionDisconnectMs: 5 * 60 * 1000, // 5 minutes\n sessionInactiveMs: 30 * 60 * 1000, // 30 minutes\n intervalMs: 60 * 1000, // Run every minute\n },\n strategy: {\n onStaleSession(session) {\n console.log(`Cleaning up stale session: ${session.id}`);\n },\n },\n});\n\n// Start automatic cleanup\ncleaner.start();\n\n// Or run manually\nconst stats = await cleaner.run();\nconsole.log(`Cleaned: ${stats.sessionsExpired} sessions, ${stats.agentsUnregistered} agents`);\n```\n\n### With Custom Storage\n\n```typescript\nimport { RedisEventStore, PostgresAgentStore } from './my-stores';\n\nconst eventBus = new EventBus({\n store: new RedisEventStore({ url: process.env.REDIS_URL }),\n});\n\nconst agents = new AgentRegistry({\n eventBus,\n store: new PostgresAgentStore({ connectionString: process.env.DATABASE_URL }),\n});\n```\n\n### With Permissions\n\n```typescript\nimport { PermissionChecker, permissionMiddleware } from '@anthropic/multi-agent-protocol/server';\n\nconst permissions = new PermissionChecker({\n rules: [\n { method: 'map/agents/register', roles: ['agent'] },\n { method: 'map/agents/list', roles: ['client', 'agent'] },\n { method: 'map/send', roles: ['agent', 'client'] },\n { method: 'map/scopes/create', roles: ['agent'] },\n ],\n defaultAllow: false,\n});\n\nconst router = new RouterConnection(stream, {\n handlers,\n sessions,\n role: 'agent',\n middleware: [\n permissionMiddleware(permissions),\n ],\n});\n```\n\n### With Federation\n\n```typescript\nimport {\n FederationGateway,\n FederatedAgentRegistry,\n FederatedMessageRouter,\n} from '@anthropic/multi-agent-protocol/server/federation';\n\nconst gateway = new FederationGateway({\n systemId: 'system-a',\n buffer: { maxMessages: 10000 },\n});\n\n// Connect to peer\nawait gateway.connectPeer({\n systemId: 'system-b',\n endpoint: 'wss://system-b.example.com/federation',\n});\n\n// Wrap local building blocks\nconst federatedAgents = new FederatedAgentRegistry({\n local: agents,\n gateway,\n sync: { onRegister: true, includeRemote: true },\n});\n\nconst federatedMessages = new FederatedMessageRouter({\n local: messages,\n gateway,\n agents: federatedAgents,\n});\n\n// Use federated versions in handlers\nconst handlers = {\n ...createAgentHandlers({ agents: federatedAgents, sessions }),\n ...createMessageHandlers({ messages: federatedMessages }),\n};\n```\n\n---\n\n## File Structure\n\n```\nts-sdk/src/\n├── server/\n│ ├── index.ts # Main exports\n│ ├── types.ts # All interfaces and types\n│ │\n│ ├── events/\n│ │ ├── index.ts\n│ │ ├── event-bus.ts # EventBus implementation\n│ │ └── stores/\n│ │ └── in-memory.ts # InMemoryEventStore\n│ │\n│ ├── agents/\n│ │ ├── index.ts\n│ │ ├── registry.ts # AgentRegistry implementation\n│ │ ├── handlers.ts # createAgentHandlers\n│ │ └── stores/\n│ │ └── in-memory.ts # InMemoryAgentStore\n│ │\n│ ├── scopes/\n│ │ ├── index.ts\n│ │ ├── manager.ts # ScopeManager implementation\n│ │ ├── handlers.ts # createScopeHandlers\n│ │ └── stores/\n│ │ └── in-memory.ts # InMemoryScopeStore\n│ │\n│ ├── sessions/\n│ │ ├── index.ts\n│ │ ├── manager.ts # SessionManager implementation\n│ │ └── stores/\n│ │ └── in-memory.ts # InMemorySessionStore\n│ │\n│ ├── subscriptions/\n│ │ ├── index.ts\n│ │ ├── manager.ts # SubscriptionManager implementation\n│ │ ├── handlers.ts # createSubscriptionHandlers\n│ │ ├── causal-buffer.ts # CausalEventBuffer for ordering\n│ │ └── stores/\n│ │ └── in-memory.ts # InMemorySubscriptionStore\n│ │\n│ ├── messages/\n│ │ ├── index.ts\n│ │ ├── router.ts # MessageRouter implementation\n│ │ ├── handlers.ts # createMessageHandlers\n│ │ └── stores/\n│ │ └── in-memory.ts # InMemoryMessageQueueStore\n│ │\n│ ├── router/\n│ │ ├── index.ts\n│ │ ├── connection.ts # RouterConnection implementation\n│ │ ├── middleware.ts # Built-in middleware\n│ │ ├── interface.ts # MAPRouter interface\n│ │ ├── base-router.ts # BaseMAPRouter abstract class\n│ │ └── adapter.ts # routerToHandlers\n│ │\n│ ├── permissions/\n│ │ ├── index.ts\n│ │ ├── checker.ts # PermissionChecker implementation\n│ │ └── middleware.ts # permissionMiddleware\n│ │\n│ ├── cleanup/\n│ │ ├── index.ts\n│ │ └── cleaner.ts # ResourceCleaner implementation\n│ │\n│ └── federation/\n│ ├── index.ts\n│ ├── gateway.ts # FederationGateway\n│ ├── buffer.ts # OutageBuffer\n│ ├── decorators/\n│ │ ├── agents.ts # FederatedAgentRegistry\n│ │ ├── scopes.ts # FederatedScopeManager\n│ │ └── messages.ts # FederatedMessageRouter\n│ └── handlers.ts # createFederationHandlers\n```\n","priority":1,"archived":0,"archived_at":null,"created_at":"2026-01-29 19:27:32","updated_at":"2026-01-29 19:38:46","parent_id":null,"parent_uuid":null,"relationships":[],"tags":["architecture","building-blocks","map","server-sdk"]}
3
- {"id":"s-1x3s","uuid":"fc0cd465-2f0f-4ac9-af94-2ee1a7979a4f","title":"MAP Protocol Improvements - Server SDK Implementation Feedback","file_path":"specs/s-1x3s_map_protocol_improvements_server_sdk_implementatio.md","content":"# MAP Protocol Improvements\n\nFriction points and improvement suggestions discovered during the Server SDK implementation.\n\n## Overview\n\nThis spec captures protocol-level issues that create friction for implementers or break interoperability between MAP systems. Items are prioritized by impact on the ecosystem.\n\n---\n\n## High Priority\n\n### 1. Federation Identity Format\n\n**Problem:** Remote agent/scope IDs have no standardized format. Each implementation invents its own.\n\n**Current State:**\n```typescript\n// Our implementation\n\"remote:system-west:agent-123\"\n\n// Other implementations might use\n\"system-west/agent-123\"\n\"system-west::agent-123\"\n{ system: \"system-west\", id: \"agent-123\" }\n```\n\n**Impact:** Federation between different MAP implementations will fail due to ID parsing incompatibility.\n\n**Proposed Solution:**\n```typescript\n// Standardize on URI-like format\n\"map://{systemId}/{entityType}/{entityId}\"\n\n// Examples\n\"map://system-west/agent/agent-123\"\n\"map://system-east/scope/room-456\"\n\n// Or simpler colon-delimited format\n\"{systemId}:{entityType}:{entityId}\"\n\"system-west:agent:agent-123\"\n```\n\n**Spec Change:** Add \"Federated Identity\" section defining canonical format.\n\n---\n\n### 2. Session Resume Semantics\n\n**Problem:** Protocol doesn't specify what state is preserved on resume.\n\n**Current State:** Each implementation decides independently:\n- Subscriptions preserved? (usually yes)\n- Agent registrations preserved? (usually yes)\n- Agent states preserved? (varies)\n- Queued messages delivered? (varies)\n- Scope memberships preserved? (varies)\n\n**Impact:** Clients can't rely on consistent behavior across MAP servers.\n\n**Proposed Solution:**\n```typescript\ninterface ResumeGuarantees {\n // MUST be preserved\n sessionId: true;\n agentRegistrations: true;\n subscriptions: true;\n scopeMemberships: true;\n \n // SHOULD be preserved (with limits)\n queuedMessages: {\n preserved: true;\n maxAge: \"5 minutes\";\n maxCount: 1000;\n };\n \n // MAY be preserved\n agentMetadata: \"implementation-defined\";\n}\n```\n\n**Spec Change:** Add \"Session Resume\" section with explicit guarantees.\n\n---\n\n### 3. Baseline Permission Model\n\n**Problem:** Protocol defines roles but no permission semantics.\n\n**Current State:**\n- Roles exist: `client`, `agent`, `gateway`\n- No standard for what each role can do\n- No resource-level permissions defined\n\n**Impact:** Security model varies wildly between implementations. Can't build secure multi-tenant systems reliably.\n\n**Proposed Solution:**\n```typescript\n// Baseline permissions (implementations MAY be more restrictive)\nconst baselinePermissions = {\n client: {\n \"map/agents/list\": true,\n \"map/agents/get\": true,\n \"map/scopes/list\": true,\n \"map/scopes/get\": true,\n \"map/subscribe\": true,\n \"map/send\": \"to-joined-scopes-only\",\n },\n agent: {\n \"map/agents/*\": true,\n \"map/scopes/*\": true,\n \"map/send\": true,\n \"map/subscribe\": true,\n },\n gateway: {\n \"*\": true, // Full access for federation\n },\n};\n\n// Resource-level rules\nconst resourcePermissions = {\n \"map/send\": {\n // Agents can message:\n // - Any agent in a shared scope\n // - Any agent that messaged them first\n rule: \"shared-scope OR prior-contact\"\n },\n \"map/scopes/delete\": {\n // Only creator or gateway can delete\n rule: \"creator OR gateway\"\n },\n};\n```\n\n**Spec Change:** Add \"Permission Model\" section with baseline and extension points.\n\n---\n\n## Medium Priority\n\n### 4. Message Recipient Disambiguation\n\n**Problem:** `to` field can be agent ID or scope ID with no way to distinguish.\n\n**Current State:**\n```typescript\n// Implementation must check both registries\nsend({ to: \"abc123\", payload: {} })\n// Is \"abc123\" an agent or scope?\n```\n\n**Proposed Solutions:**\n\nOption A - Prefix convention:\n```typescript\nsend({ to: \"agent:abc123\", payload: {} })\nsend({ to: \"scope:xyz789\", payload: {} })\n```\n\nOption B - Explicit field:\n```typescript\nsend({ toAgent: \"abc123\", payload: {} })\n// or\nsend({ toScope: \"xyz789\", payload: {} })\n```\n\nOption C - Type field:\n```typescript\nsend({ to: \"abc123\", toType: \"agent\", payload: {} })\n```\n\n**Recommendation:** Option A (prefix) - backward compatible, self-describing.\n\n---\n\n### 5. Agent State Machine Extensibility\n\n**Problem:** Fixed states (`idle`, `busy`, `suspended`, `stopped`) don't cover all use cases.\n\n**Current State:**\n```typescript\ntype AgentState = \"idle\" | \"busy\" | \"suspended\" | \"stopped\";\n// No room for: \"initializing\", \"error\", \"thinking\", \"waiting_for_human\"\n```\n\n**Proposed Solution:**\n```typescript\n// Standard states remain\ntype StandardState = \"idle\" | \"busy\" | \"suspended\" | \"stopped\";\n\n// Allow custom states with prefix\ntype CustomState = `custom:${string}`;\n\ntype AgentState = StandardState | CustomState;\n\n// Examples\nagent.state = \"custom:thinking\";\nagent.state = \"custom:awaiting_approval\";\n```\n\n**Spec Change:** Allow `custom:*` states, document standard state semantics.\n\n---\n\n### 6. Subscription Filter Expressiveness\n\n**Problem:** Filters only support implicit AND, no OR or complex conditions.\n\n**Current State:**\n```typescript\n// All criteria are AND'd together\nfilter: {\n eventTypes: [\"a\", \"b\"], // type is \"a\" OR \"b\" (within field)\n scopes: [\"s1\"], // AND scope is \"s1\"\n agents: [\"a1\"], // AND agent is \"a1\"\n}\n// Can't express: \"events in scope-1 OR events about agent-123\"\n```\n\n**Proposed Solution:**\n```typescript\n// Add explicit operators\nfilter: {\n $or: [\n { scopes: [\"scope-1\"] },\n { agents: [\"agent-123\"] },\n ]\n}\n\n// Or keep simple with \"any\" modifier\nfilter: {\n eventTypes: [\"agent.registered\"],\n match: \"any\", // instead of default \"all\"\n scopes: [\"s1\", \"s2\"],\n agents: [\"a1\"],\n}\n```\n\n**Recommendation:** Start with `match: \"any\" | \"all\"` for simplicity.\n\n---\n\n### 7. Scope Hierarchy Semantics\n\n**Problem:** Edge cases for hierarchy operations aren't specified.\n\n**Questions:**\n1. Delete parent with children - error, orphan, or cascade?\n2. Does membership inherit down the hierarchy?\n3. Do permissions cascade?\n4. Can a scope be reparented?\n\n**Proposed Solution:**\n```typescript\n// Explicit options on operations\nscopes.delete(id, {\n onChildren: \"error\" | \"orphan\" | \"cascade\", // default: \"error\"\n});\n\nscopes.getMembers(id, {\n includeDescendants: boolean, // default: false\n});\n\nscopes.isMember(scopeId, agentId, {\n checkAncestors: boolean, // default: false\n});\n\n// Reparenting\nscopes.update(id, {\n parentId: newParentId | null, // null = make root\n});\n```\n\n**Spec Change:** Document default behaviors and available options.\n\n---\n\n## Low Priority\n\n### 8. Multi-Cause Event Dependencies\n\n**Problem:** `causedBy` only allows one parent event.\n\n**Current State:**\n```typescript\n// Can only reference one cause\nevent.causedBy = \"event-123\";\n\n// But scope.agent.joined is caused by BOTH:\n// - agent.registered\n// - scope.created\n```\n\n**Proposed Solution:**\n```typescript\n// Allow array\ncausedBy?: string | string[];\n\n// Example\n{\n type: \"scope.agent.joined\",\n causedBy: [\"agent-registered-event\", \"scope-created-event\"],\n}\n```\n\n**Consideration:** Complicates causal ordering algorithm. May be better to keep single-cause and document as \"primary cause\".\n\n---\n\n### 9. Message Type Field\n\n**Problem:** No way to filter/route messages by type without inspecting payload.\n\n**Current State:**\n```typescript\n// Type buried in payload\n{ from: \"a\", to: \"b\", payload: { type: \"chat\", text: \"hi\" } }\n```\n\n**Proposed Solution:**\n```typescript\n// Top-level optional field\ninterface Message {\n id: string;\n from: string;\n to: string;\n messageType?: string; // NEW\n payload: unknown;\n}\n\n// Enables filtering\nsubscriptions.create({\n filter: { messageTypes: [\"chat\", \"command\"] }\n});\n```\n\n---\n\n### 10. Capability Discovery\n\n**Problem:** No way to discover agent capabilities without external knowledge.\n\n**Current State:**\n```typescript\n// Must know agent IDs in advance\n// No way to ask \"who can translate French?\"\n```\n\n**Proposed Solution:**\n```typescript\n// Add capabilities to agent registration\nagents.register({\n name: \"Translator\",\n capabilities: [\"translate:fr\", \"translate:de\"],\n});\n\n// Query by capability\nagents.list({ capability: \"translate:fr\" });\n\n// Or dedicated discovery method\nagents.discover({ capability: \"translate:*\" });\n```\n\n---\n\n## Summary\n\n| # | Issue | Priority | Breaking Change |\n|---|-------|----------|-----------------|\n| 1 | Federation ID format | High | Yes (federation) |\n| 2 | Resume semantics | High | No (documentation) |\n| 3 | Permission model | High | No (additive) |\n| 4 | Recipient disambiguation | Medium | Yes (wire format) |\n| 5 | Agent state extensibility | Medium | No (additive) |\n| 6 | Filter expressiveness | Medium | No (additive) |\n| 7 | Hierarchy semantics | Medium | No (documentation) |\n| 8 | Multi-cause events | Low | No (additive) |\n| 9 | Message type field | Low | No (additive) |\n| 10 | Capability discovery | Low | No (additive) |\n\n## Implementation Path\n\n**Phase 1 - Documentation (No Breaking Changes):**\n- Resume semantics (#2)\n- Permission model baseline (#3)\n- Hierarchy semantics (#7)\n\n**Phase 2 - Additive Features:**\n- Agent state extensibility (#5)\n- Filter expressiveness (#6)\n- Message type field (#9)\n- Multi-cause events (#8)\n- Capability discovery (#10)\n\n**Phase 3 - Breaking Changes (Major Version):**\n- Federation ID format (#1)\n- Recipient disambiguation (#4)\n","priority":1,"archived":0,"archived_at":null,"created_at":"2026-01-29 21:20:42","updated_at":"2026-01-29 21:20:42","parent_id":null,"parent_uuid":null,"relationships":[{"from":"s-1x3s","from_type":"spec","to":"s-10j2","to_type":"spec","type":"discovered-from"}],"tags":["breaking-changes","feedback","improvements","protocol","server-sdk"]}
4
- {"id":"s-2uns","uuid":"8e6f784a-b340-4215-b9e3-38a1ffbbb24a","title":"Client-Server SDK Integration Tests","file_path":"specs/s-2uns_client_server_sdk_integration_tests.md","content":"# Client-Server SDK Integration Tests\n\n## Overview\n\nCreate integration tests that verify the client SDK and server SDK work together correctly. Currently, client SDK tests use a separate `TestServer` mock implementation, while server SDK tests exercise building blocks in isolation. This spec defines tests that wire both SDKs together to validate end-to-end protocol compliance.\n\n## Motivation\n\n### Current State\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│ Client SDK Tests (integration.test.ts) │\n│ │\n│ ClientConnection ──────► TestServer (ad-hoc mock) │\n│ AgentConnection ──────► TestServer │\n└─────────────────────────────────────────────────────────────┘\n\n┌─────────────────────────────────────────────────────────────┐\n│ Server SDK Tests (server-sdk-integration.test.ts) │\n│ │\n│ Direct API calls ──────► Building blocks │\n│ (No JSON-RPC layer) │\n└─────────────────────────────────────────────────────────────┘\n```\n\n### Problems\n\n1. **Protocol Drift**: Client and server could implement protocol differently\n2. **Missing Coverage**: JSON-RPC serialization/deserialization between SDKs untested\n3. **Type Mismatches**: Request/response types could diverge\n4. **Edge Cases**: Error handling across the wire untested\n5. **Duplicate Code**: TestServer reimplements what server SDK already provides\n\n### Desired State\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│ Client-Server Integration Tests │\n│ │\n│ ClientConnection ──┐ │\n│ AgentConnection ──┼──► StreamPair ──► RouterConnectionImpl│\n│ GatewayConnection ─┘ │ │ │\n│ │ ▼ │\n│ │ Server SDK Building │\n│ │ Blocks (real impl) │\n└─────────────────────────────────────────────────────────────┘\n```\n\n## Goals\n\n1. **Validate Protocol Compliance**: Both SDKs speak the same protocol\n2. **Test Real Integration**: No mocks between client and server\n3. **Cover All Protocol Methods**: Every MAP method tested end-to-end\n4. **Test Event Flow**: Subscriptions receive events from server actions\n5. **Test Error Propagation**: Server errors reach client correctly\n6. **Test Session Resume**: Reconnection works across SDKs\n\n## Non-Goals\n\n- Testing client SDK in isolation (existing tests cover this)\n- Testing server SDK in isolation (existing tests cover this)\n- Performance/load testing\n- Testing with real network (streams are sufficient)\n\n## Test Architecture\n\n### Test Harness\n\nCreate a reusable test harness that wires client and server SDKs:\n\n```typescript\ninterface IntegrationTestHarness {\n // Server-side building blocks\n eventBus: EventBusImpl;\n agents: AgentRegistryImpl;\n scopes: ScopeManagerImpl;\n sessions: SessionManagerImpl;\n subscriptions: SubscriptionManagerImpl;\n messages: MessageRouterImpl;\n handlers: HandlerRegistry;\n\n // Create a connected client\n createClient(name?: string): Promise<{\n connection: ClientConnection;\n disconnect: () => Promise<void>;\n }>;\n\n // Create a connected agent\n createAgent(name: string): Promise<{\n connection: AgentConnection;\n agent: RegisteredAgent;\n disconnect: () => Promise<void>;\n }>;\n\n // Cleanup all connections\n cleanup(): Promise<void>;\n}\n\nfunction createIntegrationHarness(): IntegrationTestHarness;\n```\n\n### Stream Pair Connection\n\n```typescript\nfunction connectClientToServer(\n harness: IntegrationTestHarness,\n clientConnection: BaseConnection\n): void {\n const [clientStream, serverStream] = createStreamPair();\n \n // Client uses one end\n clientConnection.setStream(clientStream);\n \n // Server uses the other end\n const router = new RouterConnectionImpl({\n stream: serverStream,\n handlers: harness.handlers,\n sessions: harness.sessions,\n role: \"agent\", // or \"client\"\n });\n router.start();\n}\n```\n\n## Test Categories\n\n### 1. Connection Lifecycle\n\n| Test | Description |\n|------|-------------|\n| Client connect | ClientConnection.connect() creates server session |\n| Agent connect | AgentConnection.connect() creates server session |\n| Client disconnect | Clean disconnect removes session |\n| Disconnect policy | `sessionPolicy: \"end\"` vs `\"pause\"` |\n| Session resume | Reconnect with resumeToken restores session |\n| Resume with agents | Resumed session retains registered agents |\n\n### 2. Agent Operations\n\n| Test | Description |\n|------|-------------|\n| Register agent | AgentConnection.register() creates agent on server |\n| List agents | ClientConnection.listAgents() returns registered agents |\n| Get agent | ClientConnection.getAgent(id) returns specific agent |\n| Update state | AgentConnection.updateState() changes server state |\n| Update metadata | AgentConnection.updateMetadata() merges metadata |\n| Unregister | AgentConnection.unregister() removes from server |\n| Multiple agents | Same session can register multiple agents |\n\n### 3. Scope Operations\n\n| Test | Description |\n|------|-------------|\n| Create scope | AgentConnection.createScope() creates on server |\n| List scopes | ClientConnection.listScopes() returns scopes |\n| Join scope | AgentConnection.joinScope() adds membership |\n| Leave scope | AgentConnection.leaveScope() removes membership |\n| Scope hierarchy | Parent/child scopes work correctly |\n| Scope members | getScope() includes member list |\n\n### 4. Messaging\n\n| Test | Description |\n|------|-------------|\n| Agent to agent | sendMessage() delivers to target agent |\n| Agent to scope | sendToScope() broadcasts to members |\n| Client to agent | Client can send messages to agents |\n| Message delivery | onMessage handler receives sent messages |\n| Message fields | id, from, to, timestamp preserved |\n| Exclude sender | excludeSender option works |\n\n### 5. Subscriptions & Events\n\n| Test | Description |\n|------|-------------|\n| Subscribe to events | subscribe() returns subscription |\n| Receive agent events | agent.registered triggers event |\n| Receive scope events | scope.created triggers event |\n| Filter by type | eventTypes filter works |\n| Filter by scope | scopes filter works |\n| Unsubscribe | unsubscribe() stops events |\n| Event ordering | Events arrive in causal order |\n\n### 6. Error Handling\n\n| Test | Description |\n|------|-------------|\n| Invalid method | Unknown method returns error |\n| Invalid params | Bad parameters return validation error |\n| Not found | Getting nonexistent agent returns error |\n| Permission denied | Unauthorized action returns error |\n| Error codes | JSON-RPC error codes are correct |\n\n### 7. Session Resume\n\n| Test | Description |\n|------|-------------|\n| Get resume token | Disconnect returns resumeToken |\n| Resume session | Connect with token restores session |\n| Agent preservation | Agents survive disconnect/resume |\n| Subscription restoration | Subscriptions survive resume |\n| Message queue flush | Queued messages delivered on resume |\n| Expired token | Old token fails gracefully |\n\n### 8. Concurrent Operations\n\n| Test | Description |\n|------|-------------|\n| Multiple clients | Several clients connect simultaneously |\n| Multiple agents | Several agents in same session |\n| Cross-agent messaging | Agent A messages Agent B |\n| Broadcast to scope | Message reaches all scope members |\n| Event fanout | Event reaches all subscribers |\n\n## Implementation Strategy\n\n### Phase 1: Test Harness\n\n1. Create `createIntegrationHarness()` function\n2. Wire all server SDK building blocks\n3. Implement `createClient()` and `createAgent()` helpers\n4. Add cleanup and teardown logic\n\n### Phase 2: Core Tests\n\n1. Connection lifecycle tests\n2. Agent registration tests\n3. Basic messaging tests\n4. Basic subscription tests\n\n### Phase 3: Advanced Tests\n\n1. Scope hierarchy tests\n2. Session resume tests\n3. Error handling tests\n4. Concurrent operation tests\n\n### Phase 4: Edge Cases\n\n1. Large message payloads\n2. Rapid connect/disconnect\n3. Many simultaneous subscriptions\n4. Deep scope hierarchies\n\n## File Structure\n\n```\nsrc/__tests__/\n├── client-server-integration.test.ts # Main integration tests\n└── helpers/\n └── integration-harness.ts # Test harness utilities\n```\n\n## Success Criteria\n\n- [ ] All protocol methods tested end-to-end\n- [ ] Event delivery verified for all event types\n- [ ] Session resume works across disconnect\n- [ ] Error codes match between client and server\n- [ ] No type mismatches discovered\n- [ ] Tests run in < 10 seconds\n\n## Dependencies\n\n- Server SDK building blocks (complete)\n- Client SDK connections (complete)\n- Stream pair utilities (complete)\n\n## Risks\n\n1. **Stream timing**: Async streams may need careful synchronization\n2. **Event ordering**: Causal ordering adds complexity to assertions\n3. **Cleanup**: Must ensure all connections close to avoid test hangs\n\n## Open Questions\n\n1. Should we deprecate `TestServer` after this? Or keep both?\n2. Should integration harness be exported for external use?\n3. How to handle federation integration tests (needs two server instances)?\n","priority":1,"archived":0,"archived_at":null,"created_at":"2026-01-29 21:21:32","updated_at":"2026-01-29 21:21:32","parent_id":null,"parent_uuid":null,"relationships":[],"tags":["client-sdk","integration","server-sdk","testing"]}
5
- {"id":"s-7hnb","uuid":"abc49215-6278-4cf2-bec7-6fc09e8d5f32","title":"MAP Server SDK - MAPServer Convenience Layer","file_path":"specs/s-7hnb_map_server_sdk_mapserver_convenience_layer.md","content":"# MAP Server SDK - MAPServer Convenience Layer\n\n## Overview\n\nThis spec defines a convenience layer for the MAP Server SDK that reduces boilerplate while preserving full access to the underlying building blocks. The goal is **progressive disclosure**: simple things simple, complex things possible.\n\n**Parent spec:** [[s-10j2]] (MAP Server SDK - Building Blocks Architecture)\n\n## Problem Statement\n\nThe current building blocks architecture is powerful and composable, but requires significant boilerplate for common use cases:\n\n1. **~50 lines to create a basic server** - Must instantiate 6+ building blocks, combine 5+ handler factories, and wire event delivery manually\n2. **Event delivery is manual** - Users must wire `eventBus.on(\"*\")` → `subscriptions.match()` → `router.notify()` themselves\n3. **No \"just works\" option** - Users must understand the full dependency graph before writing any code\n4. **Connection tracking is DIY** - Users must track active connections for cleanup and event delivery\n\nThe integration test harness (`integration-harness.ts`) is ~400 lines, demonstrating the complexity.\n\n## Design Philosophy\n\n### Core Principles\n\n1. **Thin wrapper, not replacement** - `MAPServer` wires building blocks together; all internals remain accessible\n2. **Progressive disclosure** - Start with defaults, customize as needed\n3. **Escape hatches everywhere** - Replace any component or hook into any lifecycle point\n4. **No magic** - Behavior is explicit and predictable\n\n### What MAPServer Is NOT\n\n- NOT a replacement for building blocks (they're still the primitive)\n- NOT required (advanced users can still wire manually)\n- NOT opinionated about transport (accepts any Stream)\n\n## API Design\n\n### MAPServerOptions\n\n```typescript\ninterface MAPServerOptions {\n // === Basic Config ===\n /** Server name for connect responses */\n name?: string;\n /** Server version for connect responses */\n version?: string;\n /** Server capabilities advertised to clients */\n capabilities?: ParticipantCapabilities;\n \n // === Building Block Overrides ===\n /** \n * Replace EventBus entirely.\n * NOTE: If provided, stores.events is ignored.\n */\n eventBus?: EventBus;\n /** \n * Replace AgentRegistry entirely.\n * NOTE: If provided, stores.agents is ignored.\n */\n agents?: AgentRegistry;\n /** \n * Replace ScopeManager entirely.\n * NOTE: If provided, stores.scopes is ignored.\n */\n scopes?: ScopeManager;\n /** \n * Replace SessionManager entirely.\n * NOTE: If provided, stores.sessions is ignored.\n */\n sessions?: SessionManager;\n /** \n * Replace SubscriptionManager entirely.\n * NOTE: If provided, stores.subscriptions is ignored.\n */\n subscriptions?: SubscriptionManager;\n /** \n * Replace MessageRouter entirely.\n * NOTE: If provided, stores.messages is ignored.\n */\n messages?: MessageRouter;\n \n // === Storage Overrides ===\n /** \n * Custom stores (uses default impl with your store).\n * NOTE: Only applies to building blocks NOT explicitly provided above.\n */\n stores?: {\n events?: EventStore;\n agents?: AgentStore;\n sessions?: SessionStore;\n scopes?: ScopeStore;\n subscriptions?: SubscriptionStore;\n messages?: MessageQueueStore;\n };\n \n // === Handler Customization ===\n /** Replace all handlers entirely */\n handlers?: HandlerRegistry;\n /** Add handlers to defaults (merged after built-in handlers) */\n additionalHandlers?: HandlerRegistry;\n /** Middleware chain (applied to all requests) */\n middleware?: Middleware[];\n \n // === Event Delivery ===\n /** Event delivery configuration */\n eventDelivery?: {\n /** Enable automatic event delivery (default: true) */\n enabled?: boolean;\n /** Custom filter for event delivery */\n filter?: (event: MAPEvent, subscription: ServerSubscription) => boolean;\n };\n \n // === Session/Cleanup Config ===\n /** Session resume window in ms (default: 300000 = 5 minutes) */\n resumeWindowMs?: number;\n}\n```\n\n### MAPServer Class\n\n```typescript\nclass MAPServer {\n // === Building Blocks (readonly access) ===\n readonly eventBus: EventBus;\n readonly agents: AgentRegistry;\n readonly scopes: ScopeManager;\n readonly sessions: SessionManager;\n readonly subscriptions: SubscriptionManager;\n readonly messages: MessageRouter;\n readonly handlers: HandlerRegistry;\n \n // === Connection Tracking ===\n /** \n * Active connections keyed by session ID.\n * Connections are added on accept() and removed when closed.\n */\n readonly connections: ReadonlyMap<string, RouterConnection>;\n \n // === Constructor ===\n constructor(options?: MAPServerOptions);\n \n // === Connection Lifecycle ===\n /**\n * Accept a new connection.\n * Creates RouterConnection, tracks it, and wires up cleanup.\n * \n * NOTE: Caller must call router.start() to begin processing.\n * This allows pre-start configuration if needed.\n */\n accept(stream: Stream, options?: AcceptOptions): RouterConnection;\n \n /**\n * Close the server.\n * Gracefully disconnects all clients with optional timeout.\n * \n * NOTE: Queued messages for offline agents are lost on shutdown.\n * NOTE: Disconnected-but-resumable sessions are force-expired.\n */\n close(options?: CloseOptions): Promise<void>;\n \n // === Convenience Methods (delegate to building blocks) ===\n /** Subscribe to events (delegates to eventBus.on) */\n on(type: string | string[], handler: (event: MAPEvent) => void): () => void;\n \n /** Emit an event (delegates to eventBus.emit) */\n emit(event: Omit<MAPEvent, 'id' | 'timestamp'>): MAPEvent;\n}\n\ninterface AcceptOptions {\n /** \n * Connection role (default: 'agent').\n * - 'client': Observer that queries and subscribes\n * - 'agent': Active participant that registers and sends messages\n * - 'gateway': Federation peer\n */\n role?: 'client' | 'agent' | 'gateway';\n /** Name for the session */\n name?: string;\n /** Resume token for reconnection */\n resumeToken?: string;\n}\n\ninterface CloseOptions {\n /** Timeout for graceful shutdown in ms (default: 5000) */\n timeout?: number;\n /** Force close without waiting for graceful disconnect */\n force?: boolean;\n}\n```\n\n## Behavior\n\n### Construction\n\nWhen `new MAPServer(options)` is called:\n\n1. **Create building blocks** in dependency order, respecting user overrides:\n ```typescript\n // User-provided blocks take precedence over stores\n // Auto-created blocks use user-provided blocks as dependencies\n \n const eventBus = options.eventBus ?? new EventBusImpl({\n store: options.stores?.events // Only used if eventBus not provided\n });\n \n const sessions = options.sessions ?? new SessionManagerImpl({ \n eventBus, // Uses user's eventBus if provided\n store: options.stores?.sessions,\n resumeWindowMs: options.resumeWindowMs\n });\n \n const agents = options.agents ?? new AgentRegistryImpl({ \n eventBus, // Uses user's eventBus if provided\n store: options.stores?.agents \n });\n \n const scopes = options.scopes ?? new ScopeManagerImpl({ \n eventBus,\n store: options.stores?.scopes \n });\n \n const subscriptions = options.subscriptions ?? new SubscriptionManagerImpl({ \n eventBus, \n scopes, // Uses user's scopes if provided\n store: options.stores?.subscriptions \n });\n \n const messages = options.messages ?? new MessageRouterImpl({ \n eventBus, \n agents, // Uses user's agents if provided\n scopes,\n queueStore: options.stores?.messages \n });\n ```\n\n2. **Compose handlers**:\n ```typescript\n const handlers = options.handlers ?? combineHandlers(\n createConnectionHandlers({ \n sessions, \n serverName: options.name ?? 'MAPServer',\n serverVersion: options.version ?? '1.0.0',\n capabilities: options.capabilities\n }),\n createAgentHandlers({ agents }),\n createScopeHandlers({ scopes }),\n createMessageHandlers({ messages, scopes }),\n createSubscriptionHandlers({ subscriptions, eventBus }),\n options.additionalHandlers ?? {}\n );\n ```\n\n3. **Wire event delivery** (if `eventDelivery.enabled !== false`):\n ```typescript\n // Track sequence numbers per subscription\n const subscriptionSequences = new Map<string, number>();\n \n eventBus.on('*', (event) => {\n const matchingSubs = subscriptions.match(event);\n \n for (const subId of matchingSubs) {\n const sub = subscriptions.get(subId);\n if (!sub || sub.paused) continue;\n \n // Apply custom filter if provided\n if (options.eventDelivery?.filter) {\n if (!options.eventDelivery.filter(event, sub)) continue;\n }\n \n const router = this.#findRouterForSession(sub.sessionId);\n if (!router) continue;\n \n // Increment sequence number for this subscription\n const seq = (subscriptionSequences.get(subId) ?? 0) + 1;\n subscriptionSequences.set(subId, seq);\n \n // Deliver event, handle errors gracefully\n router.notify(NOTIFICATION_METHODS.EVENT, {\n subscriptionId: subId,\n sequenceNumber: seq,\n eventId: event.id,\n timestamp: event.timestamp,\n event,\n }).catch((error) => {\n // Connection likely closed - remove from tracking\n console.warn(`Event delivery failed for session ${sub.sessionId}:`, error.message);\n this.#removeConnection(sub.sessionId);\n });\n }\n });\n ```\n\n### Connection Acceptance\n\nWhen `server.accept(stream, options)` is called:\n\n1. Create `RouterConnectionImpl`:\n ```typescript\n const router = new RouterConnectionImpl({\n stream,\n handlers: this.handlers,\n sessions: this.sessions,\n middleware: this.#middleware,\n role: options?.role ?? 'agent', // Default to 'agent'\n name: options?.name,\n resumeToken: options?.resumeToken,\n });\n ```\n\n2. Track connection by session ID:\n ```typescript\n // Session is created when router.start() processes map/connect\n // We track the router, then update the key once session is established\n router.closed.then(() => {\n if (router.session) {\n this.#connections.delete(router.session.id);\n }\n });\n ```\n\n3. Return router (caller calls `start()`):\n ```typescript\n // Not auto-starting allows:\n // - Pre-start configuration\n // - Synchronous setup before async processing begins\n // - Consistent with RouterConnectionImpl behavior\n return router;\n ```\n\n**Why not auto-start?**\n- Consistency with underlying `RouterConnectionImpl` API\n- Allows attaching event handlers before processing starts\n- Explicit is better than implicit for connection lifecycle\n\n### Server Shutdown\n\nWhen `server.close(options)` is called:\n\n1. **Force-expire disconnected sessions** (within resume window):\n ```typescript\n // Clean up sessions that are disconnected but resumable\n this.sessions.expireStale(0); // Expire all disconnected immediately\n ```\n\n2. **Close active connections**:\n ```typescript\n const { timeout = 5000, force = false } = options ?? {};\n \n if (force) {\n // Immediately close all\n await Promise.all([...this.#connections.values()].map(r => r.close()));\n return;\n }\n \n // Graceful: attempt close with timeout\n const closePromises = [...this.#connections.values()].map(async (router) => {\n try {\n await Promise.race([\n router.close(),\n new Promise((_, reject) => \n setTimeout(() => reject(new Error('Timeout')), timeout)\n )\n ]);\n } catch {\n // Force close on timeout\n await router.close();\n }\n });\n \n await Promise.all(closePromises);\n ```\n\n3. **Note:** Queued messages (in `MessageRouter`) are lost on shutdown. This is intentional - a clean shutdown shouldn't leave state that can't be recovered.\n\n## Usage Examples\n\n### Simplest Case (3 lines)\n\n```typescript\nimport { MAPServer } from '@multi-agent-protocol/sdk/server';\n\nconst server = new MAPServer({ name: 'MyServer' });\n\n// Accept connections (e.g., from WebSocket)\nwss.on('connection', (ws) => {\n const stream = websocketToStream(ws);\n server.accept(stream).start();\n});\n```\n\n### With Custom Storage\n\n```typescript\nconst server = new MAPServer({\n name: 'PersistentServer',\n stores: {\n agents: new PostgresAgentStore(db),\n events: new RedisEventStore(redis),\n }\n});\n```\n\n### With Custom Handler\n\n```typescript\nconst server = new MAPServer({\n name: 'ExtendedServer',\n additionalHandlers: {\n 'myapp/custom/method': async (params, ctx) => {\n return { result: 'ok' };\n }\n }\n});\n```\n\n### With Middleware\n\n```typescript\nimport { permissionMiddleware, secureDefaults } from '@multi-agent-protocol/sdk/server';\n\nconst server = new MAPServer({\n name: 'SecureServer',\n middleware: [\n loggingMiddleware(),\n secureDefaults(),\n ]\n});\n```\n\n### Partial Building Block Override\n\n```typescript\n// Custom agent registry with special validation\nconst myAgents = new MyAgentRegistry({ eventBus: new EventBusImpl() });\n\nconst server = new MAPServer({\n agents: myAgents,\n // MessageRouter will be auto-created AND will use myAgents\n // This is the expected behavior - partial overrides compose correctly\n});\n\n// Verify: server.messages uses server.agents internally\n```\n\n### Full Control (escape hatch)\n\n```typescript\n// Create all custom building blocks\nconst eventBus = new EventBusImpl();\nconst agents = new MyCustomAgentRegistry({ eventBus });\n\n// Pass to MAPServer - other blocks use these as dependencies\nconst server = new MAPServer({\n eventBus,\n agents,\n // scopes, sessions, subscriptions, messages created automatically\n // and will use the provided eventBus and agents\n});\n\n// Direct access still works\nserver.agents.register({ name: 'SystemAgent', sessionId: 'system' });\nserver.eventBus.on('agent.registered', console.log);\n```\n\n### Disable Auto Event Delivery\n\n```typescript\nconst server = new MAPServer({\n name: 'CustomDelivery',\n eventDelivery: { enabled: false }\n});\n\n// Wire your own delivery logic\nserver.eventBus.on('*', (event) => {\n // Custom routing with custom sequence tracking...\n});\n```\n\n### Graceful Shutdown\n\n```typescript\n// On SIGTERM\nprocess.on('SIGTERM', async () => {\n console.log('Shutting down gracefully...');\n await server.close({ timeout: 10000 });\n console.log('All connections closed');\n process.exit(0);\n});\n\n// Emergency shutdown\nawait server.close({ force: true });\n```\n\n## Design Decisions\n\n### 1. Track Connections by Session ID\n\n**Decision:** MAPServer tracks active `RouterConnection`s in a Map keyed by session ID.\n\n**Rationale:**\n- Event delivery needs to find routers by session ID (subscriptions reference sessions)\n- `server.close()` needs to know what to disconnect\n- Users don't have to maintain this map themselves\n- Auto-cleanup on connection close prevents memory leaks\n- Session ID is the natural key (already unique, already tracked)\n\n### 2. Building Block Override Precedence\n\n**Decision:** If user provides a building block directly (e.g., `agents`), the corresponding store option (e.g., `stores.agents`) is ignored.\n\n**Rationale:**\n- Clear precedence avoids confusion\n- User-provided blocks are fully configured already\n- Stores are only for customizing default implementations\n- Documented in JSDoc comments\n\n### 3. Partial Overrides Use Correct Dependencies\n\n**Decision:** When user provides some blocks but not others, auto-created blocks use the user-provided blocks as dependencies.\n\n**Rationale:**\n- Expected behavior for dependency injection\n- Enables incremental customization\n- Example: custom `AgentRegistry` should be used by auto-created `MessageRouter`\n\n### 4. Default Role is 'agent'\n\n**Decision:** `accept()` defaults to `role: 'agent'` if not specified.\n\n**Rationale:**\n- Most connections are agents (active participants)\n- Clients (observers) are less common and can specify explicitly\n- Gateways (federation) definitely need explicit configuration\n- Matches the common use case\n\n### 5. Manual Start (Not Auto-Start)\n\n**Decision:** `accept()` returns a `RouterConnection` that the caller must `start()`.\n\n**Rationale:**\n- Consistency with underlying `RouterConnectionImpl` API\n- Allows attaching event handlers before processing\n- Explicit lifecycle is easier to reason about\n- No hidden async behavior in a synchronous-looking method\n\n### 6. Graceful Shutdown with Timeout\n\n**Decision:** `server.close()` attempts graceful disconnect with configurable timeout, then force closes.\n\n**Rationale:**\n- Expected behavior for \"close\" is clean shutdown\n- Timeout prevents hanging on stuck connections\n- `force: true` option for emergency shutdown\n- Default 5s timeout is reasonable for most cases\n\n### 7. Force-Expire Disconnected Sessions on Shutdown\n\n**Decision:** `close()` immediately expires all disconnected-but-resumable sessions.\n\n**Rationale:**\n- Clean shutdown should leave no dangling state\n- Resumable sessions won't be resumed after server stops\n- Prevents confusion about state after restart\n\n### 8. Queued Messages Lost on Shutdown\n\n**Decision:** Messages queued for offline agents are lost when `close()` is called.\n\n**Rationale:**\n- Clean shutdown semantics\n- No durable queue (that's a different feature)\n- Documented clearly so users can drain queues before shutdown if needed\n\n### 9. Event Delivery Error Handling\n\n**Decision:** If `router.notify()` fails during event delivery, log a warning and remove the connection from tracking.\n\n**Rationale:**\n- Failed notify usually means connection is dead\n- Prevents repeated failures to same dead connection\n- Warning helps debugging without crashing the server\n- Graceful degradation over hard failure\n\n### 10. Federation as Add-on\n\n**Decision:** Federation is NOT included in `MAPServer` by default.\n\n**Rationale:**\n- Federation has different lifecycle (peer connections, not client connections)\n- Most servers don't need federation\n- Keeps core simple\n- Can be enabled via wrapper or separate helper\n\nExample federation add-on:\n```typescript\nimport { enableFederation } from '@multi-agent-protocol/sdk/server/federation';\n\nconst federatedServer = enableFederation(server, {\n systemId: 'system-a',\n peers: [{ systemId: 'system-b', endpoint: 'ws://...' }]\n});\n```\n\n### 11. Permissions as Opt-in Middleware\n\n**Decision:** Permissions middleware is NOT auto-included.\n\n**Rationale:**\n- Default permissions that are too restrictive frustrate users\n- Default permissions that are too permissive aren't \"secure by default\"\n- Explicit opt-in is clearer than magic defaults\n- Provide presets for common patterns\n\nExample:\n```typescript\nimport { restrictedPermissions } from '@multi-agent-protocol/sdk/server';\n\nconst server = new MAPServer({\n middleware: [restrictedPermissions()]\n});\n```\n\n### 12. Transport Agnostic\n\n**Decision:** `MAPServer` accepts `Stream`, doesn't include transport.\n\n**Rationale:**\n- Transports vary (WebSocket, HTTP/SSE, stdio, in-memory)\n- Users often integrate with existing HTTP servers\n- Keep core focused on protocol, not I/O\n- Transport helpers can be separate exports\n\n## File Structure\n\n```\nts-sdk/src/server/\n├── index.ts # Add MAPServer export\n├── server.ts # NEW: MAPServer implementation\n├── types.ts # Add MAPServerOptions, AcceptOptions, CloseOptions\n└── ...existing files...\n```\n\n## Testing Strategy\n\n1. **Unit tests** for MAPServer:\n - Constructor creates all building blocks with defaults\n - Custom stores are passed to building blocks (when block not provided)\n - Custom building blocks are used when provided\n - Custom building blocks are used as dependencies for auto-created blocks\n - Handler composition works correctly\n - Event delivery wiring works with sequence numbers\n - Event delivery errors remove dead connections\n\n2. **Integration tests**:\n - Full client → MAPServer → response flow\n - Multiple concurrent connections\n - Graceful shutdown with active connections\n - Force shutdown\n - Reconnection with resume token\n - Partial building block override (verify dependencies work)\n\n3. **Comparison test**:\n - Verify MAPServer produces identical behavior to manual wiring\n - Side-by-side test with integration-harness.ts\n\n## Success Criteria\n\n- [ ] `MAPServer` can be instantiated with zero options\n- [ ] All building blocks accessible via readonly properties\n- [ ] Custom stores work correctly (only when block not provided)\n- [ ] Custom building blocks are used when provided\n- [ ] Partial overrides use correct dependencies (custom block used by auto-created blocks)\n- [ ] Additional handlers are merged with defaults\n- [ ] Middleware is applied to all requests\n- [ ] Event delivery works automatically with sequence numbers\n- [ ] Event delivery errors are handled gracefully (log + remove connection)\n- [ ] Event delivery can be disabled\n- [ ] Connection tracking works correctly (keyed by session ID)\n- [ ] Default role is 'agent'\n- [ ] Graceful shutdown closes all connections with timeout\n- [ ] Graceful shutdown force-expires disconnected sessions\n- [ ] Force shutdown closes immediately\n- [ ] Server capabilities can be configured\n- [ ] Existing building blocks tests still pass\n- [ ] Integration harness can be simplified to use MAPServer\n\n## Non-Goals (Deferred)\n\n- Transport helpers (WebSocket adapter, HTTP/SSE handler) - separate spec\n- Client SDK convenience layer (e.g., `ClientConnection.connectWebSocket()`) - separate spec\n- Permission presets (`secureDefaults`, `restrictedPermissions`) - separate spec\n- Federation helper (`enableFederation()`) - separate spec\n- Automatic ResourceCleaner integration - users can create their own if needed\n","priority":1,"archived":0,"archived_at":null,"created_at":"2026-01-29 23:24:56","updated_at":"2026-01-29 23:50:11","parent_id":null,"parent_uuid":null,"relationships":[{"from":"s-7hnb","from_type":"spec","to":"s-10j2","to_type":"spec","type":"depends-on"}],"tags":["convenience-layer","developer-experience","map","server-sdk"]}
6
- {"id":"s-243e","uuid":"4b9066b5-f4f7-4d0e-81eb-fd0fd867f52d","title":"MAP Client SDK - Transport Convenience Layer","file_path":"specs/s-243e_map_client_sdk_transport_convenience_layer.md","content":"# MAP Client SDK - Transport Convenience Layer\n\n## Overview\n\nThis spec defines convenience methods for the MAP Client SDK that simplify transport setup while preserving access to the low-level Stream-based API. The goal is to reduce boilerplate for the common case (WebSocket connections) without sacrificing flexibility.\n\n## Problem Statement\n\nThe current client SDK has a good API surface, but connecting to a MAP server requires verbose transport setup:\n\n```typescript\n// Current: 15+ lines just to connect with reconnection\nconst ws = new WebSocket('ws://localhost:8080');\nconst stream = websocketStream(ws);\nconst client = new ClientConnection(stream, {\n name: 'MyClient',\n createStream: async () => {\n const newWs = new WebSocket('ws://localhost:8080');\n return new Promise((resolve, reject) => {\n newWs.onopen = () => resolve(websocketStream(newWs));\n newWs.onerror = () => reject(new Error('Failed'));\n });\n },\n reconnection: { enabled: true }\n});\nawait client.connect();\n```\n\nThis is error-prone because:\n1. Users must manually wire WebSocket → Stream conversion\n2. Reconnection requires implementing a `createStream` factory that recreates the WebSocket\n3. The pattern is repeated for every client/agent instantiation\n4. Easy to forget error handling on WebSocket connection\n\n## Design Philosophy\n\n1. **Additive, not replacement** - Add static factory methods; don't change existing constructor\n2. **URL-first** - Most users just want to connect to a URL\n3. **Reconnection by default** - Production apps need reconnection; make it easy\n4. **Escape hatch preserved** - Custom streams still work via constructor\n\n## API Design\n\n### ClientConnection Static Methods\n\n```typescript\nclass ClientConnection {\n // === Existing (unchanged) ===\n constructor(stream: Stream, options?: ClientConnectionOptions);\n \n // === NEW: Static factory for URL-based connection ===\n /**\n * Connect to a MAP server via WebSocket URL.\n * \n * Handles:\n * - WebSocket creation and connection\n * - Stream wrapping\n * - Auto-configuration of createStream for reconnection\n * - Initial MAP protocol connect handshake\n * \n * @example\n * ```typescript\n * const client = await ClientConnection.connect('ws://localhost:8080', {\n * name: 'MyClient',\n * reconnection: true\n * });\n * \n * // Already connected, ready to use\n * const agents = await client.listAgents();\n * ```\n */\n static async connect(\n url: string,\n options?: ClientConnectOptions\n ): Promise<ClientConnection>;\n}\n```\n\n### AgentConnection Static Methods\n\n```typescript\nclass AgentConnection {\n // === Existing (unchanged) ===\n constructor(stream: Stream, options?: AgentConnectionOptions);\n \n // === NEW: Static factory for URL-based connection ===\n /**\n * Connect and register an agent via WebSocket URL.\n * \n * Handles:\n * - WebSocket creation and connection\n * - Stream wrapping \n * - Auto-configuration of createStream for reconnection\n * - Initial MAP protocol connect handshake\n * - Agent registration\n * \n * @example\n * ```typescript\n * const agent = await AgentConnection.connect('ws://localhost:8080', {\n * name: 'Worker',\n * role: 'processor',\n * reconnection: true\n * });\n * \n * // Already registered, ready to work\n * agent.onMessage(handleMessage);\n * await agent.busy();\n * ```\n */\n static async connect(\n url: string,\n options?: AgentConnectOptions\n ): Promise<AgentConnection>;\n}\n```\n\n### Options Types\n\n```typescript\n/**\n * Options for ClientConnection.connect()\n */\ninterface ClientConnectOptions {\n /** Client name for identification */\n name?: string;\n /** Client capabilities to advertise */\n capabilities?: ParticipantCapabilities;\n /** Authentication credentials */\n auth?: {\n method: 'bearer' | 'api-key' | 'mtls' | 'none';\n token?: string;\n };\n /** \n * Reconnection configuration.\n * - `true` = enable with defaults\n * - `false` or omitted = disabled\n * - `ReconnectionOptions` = enable with custom settings\n */\n reconnection?: boolean | ReconnectionOptions;\n /** Connection timeout in ms (default: 10000) */\n connectTimeout?: number;\n}\n\n/**\n * Options for AgentConnection.connect()\n */\ninterface AgentConnectOptions extends ClientConnectOptions {\n /** Agent role */\n role?: string;\n /** Agent visibility settings */\n visibility?: AgentVisibility;\n /** Parent agent ID (for child agents) */\n parent?: AgentId;\n /** Initial scopes to join */\n scopes?: ScopeId[];\n /** Initial metadata */\n metadata?: Record<string, unknown>;\n}\n```\n\n## Behavior\n\n### `ClientConnection.connect(url, options)`\n\n1. **Parse URL** and validate protocol (must be `ws:` or `wss:`)\n2. **Create WebSocket** to the URL\n3. **Wait for connection** with timeout (default 10s)\n4. **Wrap in Stream** using existing `websocketStream()` helper\n5. **Configure reconnection** if enabled:\n - Create `createStream` factory that recreates WebSocket to same URL\n - Normalize `reconnection: true` to `{ enabled: true }`\n6. **Create ClientConnection** with configured options\n7. **Call `connect()`** to perform MAP protocol handshake\n8. **Return** the connected client\n\n```typescript\nstatic async connect(url: string, options?: ClientConnectOptions): Promise<ClientConnection> {\n const parsedUrl = new URL(url);\n if (!['ws:', 'wss:'].includes(parsedUrl.protocol)) {\n throw new Error(`Unsupported protocol: ${parsedUrl.protocol}. Use ws: or wss:`);\n }\n \n const timeout = options?.connectTimeout ?? 10000;\n \n // Create and connect WebSocket\n const ws = new WebSocket(url);\n await waitForOpen(ws, timeout);\n const stream = websocketStream(ws);\n \n // Configure createStream for reconnection\n const createStream = async () => {\n const newWs = new WebSocket(url);\n await waitForOpen(newWs, timeout);\n return websocketStream(newWs);\n };\n \n // Normalize reconnection option\n const reconnection = options?.reconnection === true\n ? { enabled: true }\n : typeof options?.reconnection === 'object'\n ? options.reconnection\n : undefined;\n \n // Create connection\n const client = new ClientConnection(stream, {\n name: options?.name,\n capabilities: options?.capabilities,\n createStream,\n reconnection,\n });\n \n // Perform MAP handshake\n await client.connect({ auth: options?.auth });\n \n return client;\n}\n```\n\n### `AgentConnection.connect(url, options)`\n\nSame as ClientConnection, plus:\n- Passes `role`, `visibility`, `parent`, `scopes`, `metadata` to AgentConnectionOptions\n- Returns after both connection AND registration complete\n\n### Error Handling\n\n```typescript\n// Connection timeout\nawait ClientConnection.connect('ws://localhost:8080', { connectTimeout: 5000 });\n// Throws: Error('WebSocket connection timeout after 5000ms')\n\n// Invalid URL\nawait ClientConnection.connect('http://localhost:8080');\n// Throws: Error('Unsupported protocol: http:. Use ws: or wss:')\n\n// Server unreachable\nawait ClientConnection.connect('ws://localhost:9999');\n// Throws: Error('WebSocket connection failed')\n```\n\n## Helper Function\n\nAdd a utility for waiting on WebSocket open:\n\n```typescript\n// In src/stream/index.ts\n\n/**\n * Wait for a WebSocket to open with timeout.\n */\nexport function waitForOpen(ws: WebSocket, timeoutMs = 10000): Promise<void> {\n return new Promise((resolve, reject) => {\n if (ws.readyState === WebSocket.OPEN) {\n resolve();\n return;\n }\n \n const timeout = setTimeout(() => {\n ws.close();\n reject(new Error(`WebSocket connection timeout after ${timeoutMs}ms`));\n }, timeoutMs);\n \n const onOpen = () => {\n clearTimeout(timeout);\n ws.removeEventListener('error', onError);\n resolve();\n };\n \n const onError = (event: Event) => {\n clearTimeout(timeout);\n ws.removeEventListener('open', onOpen);\n reject(new Error('WebSocket connection failed'));\n };\n \n ws.addEventListener('open', onOpen, { once: true });\n ws.addEventListener('error', onError, { once: true });\n });\n}\n```\n\n## Usage Examples\n\n### Simple Client\n\n```typescript\nimport { ClientConnection } from '@multi-agent-protocol/sdk';\n\n// Connect with reconnection\nconst client = await ClientConnection.connect('ws://localhost:8080', {\n name: 'Dashboard',\n reconnection: true\n});\n\n// Subscribe to events\nconst sub = await client.subscribe({ eventTypes: ['agent.registered'] });\nfor await (const event of sub) {\n console.log('Agent registered:', event.data);\n}\n```\n\n### Agent with Custom Reconnection\n\n```typescript\nimport { AgentConnection } from '@multi-agent-protocol/sdk';\n\nconst agent = await AgentConnection.connect('wss://prod.example.com/map', {\n name: 'DataProcessor',\n role: 'etl',\n reconnection: {\n enabled: true,\n maxRetries: 20,\n maxDelayMs: 60000,\n restoreScopeMemberships: true\n }\n});\n\nagent.onMessage(async (msg) => {\n await agent.busy();\n // Process message...\n await agent.idle();\n});\n```\n\n### Using Low-Level API (unchanged)\n\n```typescript\nimport { ClientConnection, ndJsonStream } from '@multi-agent-protocol/sdk';\n\n// Custom transport (e.g., stdio)\nconst stream = ndJsonStream(process.stdin, process.stdout);\nconst client = new ClientConnection(stream, { name: 'CLI' });\nawait client.connect();\n```\n\n## Comparison: Before vs After\n\n### Connecting a Client\n\n| Aspect | Before | After |\n|--------|--------|-------|\n| Lines of code | ~15 | 3-5 |\n| WebSocket handling | Manual | Automatic |\n| Reconnection setup | Manual factory | `reconnection: true` |\n| Error handling | Manual | Built-in timeout |\n\n### Code Reduction\n\n**Before:**\n```typescript\nconst ws = new WebSocket('ws://localhost:8080');\nconst stream = websocketStream(ws);\nconst client = new ClientConnection(stream, {\n name: 'MyClient',\n createStream: async () => {\n const newWs = new WebSocket('ws://localhost:8080');\n return new Promise((resolve, reject) => {\n newWs.onopen = () => resolve(websocketStream(newWs));\n newWs.onerror = () => reject(new Error('Failed'));\n });\n },\n reconnection: { enabled: true }\n});\nawait client.connect();\n```\n\n**After:**\n```typescript\nconst client = await ClientConnection.connect('ws://localhost:8080', {\n name: 'MyClient',\n reconnection: true\n});\n```\n\n## File Changes\n\n```\nts-sdk/src/\n├── connection/\n│ ├── client.ts # Add static connect() method\n│ └── agent.ts # Add static connect() method\n├── stream/\n│ └── index.ts # Add waitForOpen() helper\n└── index.ts # Export new types\n```\n\n## Success Criteria\n\n- [ ] `ClientConnection.connect(url)` works with ws: and wss: URLs\n- [ ] `AgentConnection.connect(url)` works and auto-registers\n- [ ] `reconnection: true` enables reconnection with sensible defaults\n- [ ] Custom `ReconnectionOptions` are respected\n- [ ] Connection timeout is configurable\n- [ ] Invalid URLs throw clear error messages\n- [ ] Existing constructor API still works unchanged\n- [ ] All existing tests pass\n\n## Non-Goals\n\n- HTTP/SSE transport (separate spec if needed)\n- Connection pooling\n- Load balancing across multiple URLs\n- Custom WebSocket implementations (use constructor for that)\n","priority":2,"archived":0,"archived_at":null,"created_at":"2026-01-29 23:47:43","updated_at":"2026-01-29 23:47:43","parent_id":null,"parent_uuid":null,"relationships":[{"from":"s-243e","from_type":"spec","to":"s-7hnb","to_type":"spec","type":"related"}],"tags":["client-sdk","convenience-layer","developer-experience","transport"]}
7
- {"id":"s-9kpn","uuid":"f64172e0-8eed-4f14-a17e-4398b6582294","title":"ACP-over-MAP Tunneling","file_path":"specs/s-9kpn_acp_over_map_tunneling.md","content":"# ACP-over-MAP Tunneling\n\n## Overview\n\nThis spec defines how the Agent Client Protocol (ACP) can be tunneled through the Multi-Agent Protocol (MAP), enabling clients to interact with ACP-compatible agents within a MAP system while preserving all ACP semantics and features.\n\n## Goals\n\n1. **Full ACP Compatibility**: Preserve 100% of ACP features so existing client logic patterns work\n2. **Multiplexed Streams**: Support multiple concurrent ACP sessions over a single MAP connection\n3. **Observability**: Gain MAP's visibility into ACP interactions (events, message tracing)\n4. **Multi-Agent Routing**: Route ACP requests to any ACP-compatible agent in the MAP system\n5. **Federation Ready**: ACP messages can traverse federated MAP systems\n6. **Session Continuity**: ACP sessions can survive agent restarts via MAP's event replay\n\n## Non-Goals\n\n- Modifying the ACP specification\n- Supporting legacy ACP clients without code changes (use Gateway pattern for that)\n- Replacing direct ACP connections (this is an optional capability)\n\n---\n\n## Architecture\n\n### Multiplexed ACP Streams over MAP\n\n```\n┌───────────────────────────────────────────────────────────────────────────┐\n│ MAP Client Connection │\n│ │\n│ ┌─────────────────────────────────────────────────────────────────────┐ │\n│ │ Standard MAP Interface │ │\n│ │ send(), subscribe(), listAgents(), structureGraph(), etc. │ │\n│ └─────────────────────────────────────────────────────────────────────┘ │\n│ │ │\n│ ┌─────────────────────────────────┼─────────────────────────────────┐ │\n│ │ ACP Stream Multiplexer │ │\n│ │ │ │\n│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │\n│ │ │ ACPStream #1 │ │ ACPStream #2 │ │ ACPStream #3 │ │ │\n│ │ │ → Agent A │ │ → Agent B │ │ → Agent A │ │ │\n│ │ │ session: s1 │ │ session: s2 │ │ session: s3 │ │ │\n│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │\n│ │ │ │ │ │ │\n│ └─────────┼──────────────────┼──────────────────┼───────────────────┘ │\n│ │ │ │ │\n└────────────┼──────────────────┼──────────────────┼─────────────────────────┘\n │ │ │\n ▼ ▼ ▼\n ┌────────────┐ ┌────────────┐ ┌────────────┐\n │ Agent A │ │ Agent B │ │ Agent A │\n │ (ACP-cap) │ │ (ACP-cap) │ │ (2nd sess) │\n └────────────┘ └────────────┘ └────────────┘\n```\n\n### Key Design Decisions\n\n1. **Single MAP connection, multiple ACP streams** - Network efficient, simple debugging\n2. **Virtual ACP connection per target agent** - Each stream manages its own ACP session\n3. **Client-side session management** - The ACPStreamConnection owns the session ID\n4. **Agent-side ACP compatibility required** - Target agents must understand ACP envelope format\n5. **Standard MAP messaging underneath** - ACP messages wrapped in MAP message payloads\n\n---\n\n## Design Decisions\n\nThis section documents key design decisions and their rationale.\n\n### 1. Request/Response Correlation\n\n**Decision**: ACP stream manages its own request/response correlation using ACP's existing request IDs.\n\nMAP's `send()` is fire-and-forget from the transport perspective. The `ACPStreamConnection` maintains a pending requests map and correlates responses received via subscription.\n\n```typescript\nclass ACPStreamConnection {\n #pendingRequests: Map<string, { resolve, reject, timeout }> = new Map();\n \n async #sendACPRequest(method: string, params: unknown): Promise<unknown> {\n const correlationId = generateId();\n \n // Send via MAP (fire-and-forget)\n await this.#mapClient.send(\n { agent: this.#targetAgent },\n { acp: { id: correlationId, method, params }, acpContext: {...} },\n { protocol: 'acp', correlationId }\n );\n \n // Wait for response via subscription\n return new Promise((resolve, reject) => {\n this.#pendingRequests.set(correlationId, { \n resolve, \n reject, \n timeout: setTimeout(() => reject(new Error('Timeout')), 30000) \n });\n });\n }\n}\n```\n\n**Rationale**: Keeps MAP simple, reuses ACP's existing correlation mechanism, matches how the ACP SDK works internally.\n\n---\n\n### 2. Message Delivery: Subscriptions vs Direct\n\n**Decision**: Hybrid approach - notifications via subscriptions, requests via direct messaging.\n\n| Message Type | Delivery | Rationale |\n|-------------|----------|-----------|\n| `session/update` (notification) | Subscription | High volume, one-way, benefits from filtering |\n| Agent→Client requests | Direct message | Needs response, lower volume, simpler correlation |\n\n```typescript\n// Agent sends notification (one-way, high volume)\nawait this.#mapAgent.send(\n { participant: clientId },\n { acp: { method: 'session/update', params: update }, acpContext },\n { protocol: 'acp' }\n);\n\n// Agent sends request (needs response)\nawait this.#mapAgent.send(\n { participant: clientId },\n { acp: { id: reqId, method: 'request_permission', params }, acpContext },\n { protocol: 'acp', expectsResponse: true }\n);\n```\n\n**Rationale**: Separates high-volume streaming from interactive requests. Client can prioritize permission requests over buffered updates.\n\n---\n\n### 3. Session Relationship (MAP vs ACP)\n\n**Decision**: MAP sessions and ACP sessions are independent concepts.\n\n| Concept | Scope | Lifecycle |\n|---------|-------|-----------|\n| MAP Session | Connection-level | Created on `map/connect`, survives reconnection |\n| ACP Session | Application-level | Created on `session/new`, tied to conversation |\n| ACP Stream | Bridge-level | Created on `createACPStream()`, ties client to agent |\n\n**Rationale**: Simplest mental model, no coupling between protocols. MAP handles transport, ACP handles conversation state.\n\n---\n\n### 4. Agent Discovery for ACP\n\n**Decision**: Agents advertise ACP capability via metadata.\n\n```typescript\n// Agent registers with ACP capability\nawait mapAgent.connect({\n name: 'CodingAgent',\n capabilities: {\n protocols: ['acp'],\n acp: {\n version: '2024-10-07',\n features: ['loadSession', 'modes', 'terminals']\n }\n }\n});\n\n// Client discovers ACP-capable agents\nconst { agents } = await mapClient.listAgents();\nconst acpAgents = agents.filter(a => a.capabilities?.protocols?.includes('acp'));\n```\n\n**Rationale**: Explicit capability advertisement, filterable via existing `listAgents`, includes version and feature support.\n\n---\n\n### 5. Terminal/FS Resource Handling\n\n**Decision**: Implementation-defined - agent decides whether to use client resources or handle server-side.\n\nThe ACP tunneling layer passes through terminal/FS requests unchanged. The agent implementation decides:\n\n- **Forward to client**: Agent calls `terminal/create` which routes to user's IDE\n- **Handle server-side**: Agent uses its own execution environment\n\n```typescript\nclass MyACPAgent {\n async handleToolCall(tool: string, params: unknown, ctx: ACPContext) {\n if (tool === 'bash') {\n // Option A: Use client's terminal (user visibility)\n const terminal = await this.acpAdapter.createTerminal(ctx.streamId, {\n command: params.command\n });\n \n // Option B: Server-side execution (headless)\n const result = await this.serverSideExec(params.command);\n }\n }\n}\n```\n\n**Rationale**: Flexibility for different deployment scenarios. IDE-connected agents may want user terminals; headless agents handle everything server-side.\n\n---\n\n### 6. Reconnection & Session Resume\n\n**Decision**: Auto-restore ACP streams using MAP's subscription restoration, verify ACP session validity.\n\n```typescript\nclass ACPStreamConnection {\n #lastEventId: string | null = null;\n \n async #onReconnect(): Promise<void> {\n // Restore subscription with replay from last known event\n this.#subscription = await this.#mapClient.subscribe({\n fromAgents: [this.#targetAgent],\n filters: { 'metadata.protocol': 'acp', 'acpContext.streamId': this.#streamId },\n resumeFrom: this.#lastEventId\n });\n \n // Verify ACP session is still valid on agent\n if (this.#sessionId) {\n try {\n await this.#sendACPRequest('session/status', { sessionId: this.#sessionId });\n } catch {\n this.emit('sessionLost', { sessionId: this.#sessionId });\n }\n }\n }\n}\n```\n\n**Rationale**: Leverages MAP's built-in reconnection. Transparent for short disconnects, emits event for session loss so client can use `session/load`.\n\n---\n\n### 7. Backpressure Handling\n\n**Decision**: Use MAP's existing backpressure mechanisms (pause/resume, overflow callbacks).\n\n```typescript\nconst subscription = await mapClient.subscribe({...});\n\nsubscription.on('overflow', (info) => {\n console.warn(`Dropped ${info.eventsDropped} session updates`);\n});\n\n// Manual flow control\nsubscription.pause();\n// ... process backlog\nsubscription.resume();\n```\n\n**Rationale**: MAP already has robust backpressure. `session/update` is incremental (missing some is recoverable). Keep simple, add ACP-specific flow control only if needed.\n\n---\n\n### 8. Error Code Handling\n\n**Decision**: Pass through ACP errors unchanged.\n\n```typescript\n// ACP error propagates directly\nclass ACPError extends Error {\n constructor(public code: number, message: string, public data?: unknown) {\n super(message);\n }\n}\n\n// Client catches ACP errors as-is\ntry {\n await acp.newSession({...});\n} catch (e) {\n if (e instanceof ACPError && e.code === -32000) {\n // Handle ACP auth_required\n }\n}\n```\n\n**Rationale**: Preserves ACP semantics exactly, no lossy translation. MAP transport errors are separate (connection failures).\n\n---\n\n### 9. Federation Support\n\n**Decision**: Defer federation support, but design for compatibility.\n\nCurrent design doesn't preclude federation - the ACP envelope can travel through federation routing. However:\n- Federation adds latency (bad for streaming prompts)\n- Focus on single-system first\n- Document as future extension\n\n---\n\n### 10. Unstable ACP Methods\n\n**Decision**: Start with stable ACP methods only.\n\n**Stable (implement first):**\n- `initialize`, `authenticate`\n- `session/new`, `session/load`, `session/set_mode`\n- `session/prompt`, `session/cancel`\n- `session/update`, `session/request_permission`\n- All `fs/*` and `terminal/*` methods\n\n**Unstable (defer):**\n- `session/list`, `session/fork`, `session/resume`\n- `session/set_model`, `session/set_config_option`\n\n```typescript\ninterface ACPStreamConnection {\n // Stable methods\n initialize(...): Promise<...>;\n newSession(...): Promise<...>;\n prompt(...): Promise<...>;\n \n // Unstable - optional, prefixed\n unstable_listSessions?(...): Promise<...>;\n unstable_forkSession?(...): Promise<...>;\n}\n```\n\n**Rationale**: Stable methods cover core use cases. Unstable methods may change in ACP spec. Add incrementally.\n\n---\n\n### 11. Testing Strategy\n\n**Decision**: Provide both `TestACPAgent` mock and TestServer integration.\n\n```typescript\n// Unit testing - lightweight mock\nimport { TestACPAgent } from '@anthropic/map-sdk/testing';\n\nconst mockAgent = new TestACPAgent({\n onPrompt: async (params) => {\n await mockAgent.sendUpdate({ type: 'message_start' });\n return { stopReason: 'end_turn' };\n }\n});\n\n// Integration testing - full server\nconst server = new TestServer();\nserver.registerACPAgent('test-agent', mockAgent);\nconst client = await ClientConnection.connect(server.url);\n```\n\n**Rationale**: Unit tests need lightweight mocks, integration tests need full behavior. TestServer already exists.\n\n---\n\n### 12. Type Packaging\n\n**Decision**: Bundle ACP types directly in MAP SDK.\n\n```typescript\n// All from @anthropic/map-sdk\nimport { \n ClientConnection,\n ACPStreamConnection,\n // ACP types bundled\n type ACPInitializeRequest,\n type ACPPromptRequest,\n type ACPSessionNotification,\n} from '@anthropic/map-sdk';\n\n// Or namespaced\nimport { ClientConnection, ACP } from '@anthropic/map-sdk';\nconst req: ACP.InitializeRequest = {...};\n```\n\n**Rationale**: Simpler dependency management, single package to install. Types are lightweight (no runtime cost). Can split later if needed.\n\n---\n\n## Protocol Translation\n\n### ACP Message Envelope in MAP\n\nAll ACP messages are wrapped in a standard envelope carried as MAP message payload:\n\n```typescript\ninterface ACPEnvelope {\n // The original ACP JSON-RPC message\n acp: {\n jsonrpc: '2.0';\n id?: string | number; // Present for requests\n method?: string; // Present for requests/notifications\n params?: unknown; // Method parameters\n result?: unknown; // Present for responses\n error?: ACPError; // Present for error responses\n };\n \n // ACP-specific routing context\n acpContext: {\n streamId: string; // Identifies the virtual ACP stream\n sessionId: string | null; // ACP session (null before session/new)\n direction: 'client-to-agent' | 'agent-to-client';\n \n // For agent→client requests (permissions, fs, terminal)\n pendingClientRequest?: {\n requestId: string | number;\n method: string;\n timeout?: number;\n };\n };\n}\n```\n\n### MAP Message Structure\n\n```typescript\n// Client→Agent ACP request via MAP\nconst mapMessage: SendRequestParams = {\n to: { agent: targetAgentId },\n payload: ACPEnvelope,\n meta: {\n protocol: 'acp',\n expectsResponse: true, // For ACP requests\n correlationId: string, // Links request/response\n }\n};\n```\n\n### MAP Metadata Conventions\n\n| Field | Purpose |\n|-------|---------|\n| `protocol: 'acp'` | Identifies this as an ACP-tunneled message |\n| `expectsResponse` | True for ACP requests, false for notifications |\n| `correlationId` | Links ACP request/response pairs |\n\n---\n\n## Client SDK Interface\n\n### Creating ACP Streams\n\n```typescript\n// Extend ClientConnection\nclass ClientConnection {\n /**\n * Create a virtual ACP stream connection to a specific agent.\n * Multiple ACP streams can coexist over a single MAP connection.\n */\n createACPStream(options: ACPStreamOptions): ACPStreamConnection;\n}\n\ninterface ACPStreamOptions {\n /** Target agent that will handle ACP requests */\n targetAgent: AgentId;\n \n /** Client-side handlers for agent→client requests */\n client: ACPClientHandlers;\n \n /** Optional: receive MAP events alongside ACP (for observability) */\n exposeMapEvents?: boolean;\n}\n\ninterface ACPClientHandlers {\n // Required\n requestPermission(params: ACP.RequestPermissionRequest): Promise<ACP.RequestPermissionResponse>;\n sessionUpdate(params: ACP.SessionNotification): Promise<void>;\n \n // Optional (based on advertised capabilities)\n readTextFile?(params: ACP.ReadTextFileRequest): Promise<ACP.ReadTextFileResponse>;\n writeTextFile?(params: ACP.WriteTextFileRequest): Promise<ACP.WriteTextFileResponse>;\n createTerminal?(params: ACP.CreateTerminalRequest): Promise<ACP.CreateTerminalResponse>;\n terminalOutput?(params: ACP.TerminalOutputRequest): Promise<ACP.TerminalOutputResponse>;\n releaseTerminal?(params: ACP.ReleaseTerminalRequest): Promise<ACP.ReleaseTerminalResponse>;\n waitForTerminalExit?(params: ACP.WaitForTerminalExitRequest): Promise<ACP.WaitForTerminalExitResponse>;\n killTerminal?(params: ACP.KillTerminalCommandRequest): Promise<ACP.KillTerminalCommandResponse>;\n}\n```\n\n### ACPStreamConnection Interface\n\n```typescript\n/**\n * Virtual ACP connection over MAP.\n * Implements the full ACP Agent interface.\n */\nclass ACPStreamConnection {\n /** Unique identifier for this stream */\n readonly streamId: string;\n \n /** Target agent this stream connects to */\n readonly targetAgent: AgentId;\n \n /** Current ACP session ID (null until newSession called) */\n readonly sessionId: string | null;\n \n /** Whether initialize() has been called */\n readonly initialized: boolean;\n \n /** Agent capabilities from initialize response */\n readonly capabilities: ACP.AgentCapabilities | null;\n\n // ===== ACP Lifecycle Methods =====\n \n initialize(params: ACP.InitializeRequest): Promise<ACP.InitializeResponse>;\n authenticate(params: ACP.AuthenticateRequest): Promise<ACP.AuthenticateResponse>;\n \n // ===== ACP Session Methods =====\n \n newSession(params: ACP.NewSessionRequest): Promise<ACP.NewSessionResponse>;\n loadSession(params: ACP.LoadSessionRequest): Promise<ACP.LoadSessionResponse>;\n setSessionMode(params: ACP.SetSessionModeRequest): Promise<ACP.SetSessionModeResponse>;\n \n // ===== ACP Prompt Methods =====\n \n prompt(params: ACP.PromptRequest): Promise<ACP.PromptResponse>;\n cancel(params: ACP.CancelNotification): Promise<void>;\n \n // ===== MAP Observability (optional) =====\n \n /** Subscribe to MAP events from the target agent (if exposeMapEvents enabled) */\n onMapEvent?(handler: (event: MAPEvent) => void): () => void;\n \n // ===== Lifecycle =====\n \n /** Close this ACP stream and clean up resources */\n close(): Promise<void>;\n \n // ===== Events =====\n \n /** Emitted when ACP session is lost after reconnection */\n on(event: 'sessionLost', handler: (info: { sessionId: string }) => void): void;\n}\n```\n\n### Usage Example\n\n```typescript\n// Connect to MAP system\nconst mapClient = await ClientConnection.connect('ws://localhost:8080', {\n name: 'MyIDE'\n});\n\n// Discover ACP-capable agents\nconst { agents } = await mapClient.listAgents();\nconst acpAgents = agents.filter(a => a.capabilities?.protocols?.includes('acp'));\n\n// Create virtual ACP stream to a specific agent\nconst acp = mapClient.createACPStream({\n targetAgent: acpAgents[0].id,\n client: {\n requestPermission: async (req) => {\n const allowed = await showPermissionDialog(req.toolCall);\n return { outcome: allowed ? 'allowed' : 'denied' };\n },\n sessionUpdate: async (update) => {\n renderUpdate(update);\n },\n readTextFile: async (req) => {\n const content = await fs.readFile(req.path, 'utf-8');\n return { content };\n },\n createTerminal: async (req) => {\n const terminal = await ide.createTerminal(req.command);\n return { terminalId: terminal.id };\n }\n }\n});\n\n// Handle session loss on reconnection\nacp.on('sessionLost', async ({ sessionId }) => {\n console.log('Session lost, attempting reload...');\n await acp.loadSession({ sessionId });\n});\n\n// Standard ACP workflow\nconst initResult = await acp.initialize({\n protocolVersion: '2024-10-07',\n clientInfo: { name: 'MyIDE', version: '1.0.0' },\n capabilities: {\n fs: { readTextFile: true, writeTextFile: true },\n terminal: true\n }\n});\n\nconst { sessionId } = await acp.newSession({\n workingDirectory: '/path/to/project'\n});\n\n// Prompt with streaming via sessionUpdate handler\nconst result = await acp.prompt({\n sessionId,\n messages: [{\n role: 'user',\n content: [{ type: 'text', text: 'Fix the authentication bug' }]\n }]\n});\n\nconsole.log('Stop reason:', result.stopReason);\n\n// Can create multiple streams to different agents\nconst acp2 = mapClient.createACPStream({\n targetAgent: 'research-agent-1',\n client: { /* handlers */ }\n});\n\n// Clean up\nawait acp.close();\nawait acp2.close();\nawait mapClient.disconnect();\n```\n\n---\n\n## Agent SDK Interface\n\n### ACP-Compatible Agent\n\nAgents that want to receive ACP-tunneled messages must:\n1. Advertise ACP capability during registration\n2. Handle the ACP envelope format\n\n```typescript\n// Register with ACP capability\nconst mapAgent = await AgentConnection.connect('ws://localhost:8080', {\n name: 'CodingAgent',\n capabilities: {\n protocols: ['acp'],\n acp: { version: '2024-10-07', features: ['loadSession', 'modes'] }\n }\n});\n\n// Handle incoming messages\nmapAgent.onMessage(async (message) => {\n if (message.metadata?.protocol === 'acp') {\n await handleACPMessage(message);\n }\n});\n```\n\n### Helper: ACPAgentAdapter\n\nTo simplify agent implementation, use the adapter:\n\n```typescript\nimport { ACPAgentAdapter } from '@anthropic/map-sdk';\n\nconst adapter = new ACPAgentAdapter(mapAgent, {\n initialize: async (params, ctx) => {\n return {\n protocolVersion: '2024-10-07',\n agentInfo: { name: 'CodingAgent', version: '1.0.0' },\n capabilities: { loadSession: true }\n };\n },\n \n newSession: async (params, ctx) => {\n const sessionId = generateSessionId();\n return { sessionId };\n },\n \n prompt: async (params, ctx) => {\n // Stream updates to client\n await adapter.sendSessionUpdate(ctx.streamId, {\n sessionId: params.sessionId,\n update: { type: 'message_start', message: {...} }\n });\n \n // Request permission if needed\n const permission = await adapter.requestPermission(ctx.streamId, {\n sessionId: params.sessionId,\n toolCall: { id: '1', name: 'bash', input: { command: 'npm test' } }\n });\n \n if (permission.outcome === 'allowed') {\n // Option A: Use client's terminal\n const terminal = await adapter.createTerminal(ctx.streamId, {\n sessionId: params.sessionId,\n command: 'npm test'\n });\n \n // Option B: Execute server-side (implementation choice)\n // const result = await exec('npm test');\n }\n \n return { stopReason: 'end_turn' };\n },\n \n cancel: async (params, ctx) => {\n // Abort any in-progress work\n }\n});\n```\n\n### ACPAgentAdapter Interface\n\n```typescript\nclass ACPAgentAdapter {\n constructor(mapAgent: AgentConnection, handler: ACPAgentHandler);\n \n /** Send session update notification to client */\n sendSessionUpdate(streamId: string, update: ACP.SessionNotification): Promise<void>;\n \n /** Request permission from client (blocks until user responds) */\n requestPermission(streamId: string, request: ACP.RequestPermissionRequest): Promise<ACP.RequestPermissionResponse>;\n \n /** Read file from client filesystem */\n readTextFile(streamId: string, request: ACP.ReadTextFileRequest): Promise<ACP.ReadTextFileResponse>;\n \n /** Write file to client filesystem */\n writeTextFile(streamId: string, request: ACP.WriteTextFileRequest): Promise<ACP.WriteTextFileResponse>;\n \n /** Create terminal on client */\n createTerminal(streamId: string, request: ACP.CreateTerminalRequest): Promise<ACP.CreateTerminalResponse>;\n \n /** Get terminal output from client */\n terminalOutput(streamId: string, request: ACP.TerminalOutputRequest): Promise<ACP.TerminalOutputResponse>;\n \n /** Release terminal on client */\n releaseTerminal(streamId: string, request: ACP.ReleaseTerminalRequest): Promise<ACP.ReleaseTerminalResponse>;\n \n /** Wait for terminal exit on client */\n waitForTerminalExit(streamId: string, request: ACP.WaitForTerminalExitRequest): Promise<ACP.WaitForTerminalExitResponse>;\n \n /** Kill terminal command on client */\n killTerminal(streamId: string, request: ACP.KillTerminalCommandRequest): Promise<ACP.KillTerminalCommandResponse>;\n}\n\ninterface ACPAgentHandler {\n initialize(params: ACP.InitializeRequest, ctx: ACPContext): Promise<ACP.InitializeResponse>;\n authenticate?(params: ACP.AuthenticateRequest, ctx: ACPContext): Promise<ACP.AuthenticateResponse>;\n newSession(params: ACP.NewSessionRequest, ctx: ACPContext): Promise<ACP.NewSessionResponse>;\n loadSession?(params: ACP.LoadSessionRequest, ctx: ACPContext): Promise<ACP.LoadSessionResponse>;\n setSessionMode?(params: ACP.SetSessionModeRequest, ctx: ACPContext): Promise<ACP.SetSessionModeResponse>;\n prompt(params: ACP.PromptRequest, ctx: ACPContext): Promise<ACP.PromptResponse>;\n cancel(params: ACP.CancelNotification, ctx: ACPContext): Promise<void>;\n}\n\ninterface ACPContext {\n streamId: string;\n sessionId: string | null;\n clientParticipantId: string;\n}\n```\n\n---\n\n## Method Mapping\n\n### Client→Agent Methods\n\n| ACP Method | Handling |\n|------------|----------|\n| `initialize` | Forward to agent, agent returns capabilities |\n| `authenticate` | Forward to agent |\n| `session/new` | Forward to agent, client stores returned sessionId |\n| `session/load` | Forward to agent, may trigger event replay |\n| `session/set_mode` | Forward to agent |\n| `session/prompt` | Forward to agent, streaming via session/update events |\n| `session/cancel` | Forward immediately as notification |\n\n### Agent→Client Methods\n\n| ACP Method | Handling |\n|------------|----------|\n| `session/update` | Agent sends MAP notification, client routes to sessionUpdate handler |\n| `session/request_permission` | Agent sends MAP request, client shows UI, returns decision |\n| `fs/read_text_file` | Agent sends MAP request, client reads local file |\n| `fs/write_text_file` | Agent sends MAP request, client writes local file |\n| `terminal/create` | Agent sends MAP request, client spawns terminal |\n| `terminal/output` | Agent sends MAP request, client returns terminal output |\n| `terminal/release` | Agent sends MAP request, client releases terminal |\n| `terminal/wait_for_exit` | Agent sends MAP request, client awaits terminal exit |\n| `terminal/kill` | Agent sends MAP request, client kills terminal process |\n\n---\n\n## Event Flow\n\n### Prompt Turn Sequence\n\n```\nClient MAP Agent\n │ │ │\n │ createACPStream() │ │\n │───────────────────────►│ │\n │ │ subscribe(fromAgent) │\n │ │───────────────────────►│\n │ │ │\n │ acp.initialize() │ │\n │───────────────────────►│ MAP send (ACP envelope)│\n │ │───────────────────────►│\n │ │◄───────────────────────│\n │◄───────────────────────│ MAP response │\n │ InitializeResponse │ │\n │ │ │\n │ acp.newSession() │ │\n │───────────────────────►│───────────────────────►│\n │◄───────────────────────│◄───────────────────────│\n │ { sessionId } │ │\n │ │ │\n │ acp.prompt() │ │\n │───────────────────────►│───────────────────────►│\n │ │ │\n │ │ session/update │\n │ sessionUpdate() ◄───│◄───────────────────────│\n │ │ session/update │\n │ sessionUpdate() ◄───│◄───────────────────────│\n │ │ │\n │ │ request_permission │\n │ requestPermission()◄──│◄───────────────────────│\n │ (show UI) │ │\n │───────────────────────►│───────────────────────►│\n │ { allowed } │ │\n │ │ │\n │ │ session/update │\n │ sessionUpdate() ◄───│◄───────────────────────│\n │ │ │\n │◄───────────────────────│◄───────────────────────│\n │ PromptResponse │ (prompt complete) │\n │ │ │\n```\n\n---\n\n## Error Handling\n\n### ACP Errors Through MAP\n\nACP errors are passed through unchanged:\n\n```typescript\n// ACP error in MAP response payload\n{\n payload: {\n acp: {\n jsonrpc: '2.0',\n id: originalRequestId,\n error: {\n code: -32000, // ACP error code preserved\n message: 'Authentication required',\n data: { ... }\n }\n },\n acpContext: { streamId, sessionId, direction: 'agent-to-client' }\n }\n}\n\n// Client receives as ACPError\nclass ACPError extends Error {\n constructor(public code: number, message: string, public data?: unknown);\n}\n\ntry {\n await acp.newSession({...});\n} catch (e) {\n if (e instanceof ACPError && e.code === -32000) {\n // Handle ACP auth_required\n }\n}\n```\n\n### Error Scenarios\n\n| Scenario | Handling |\n|----------|----------|\n| Target agent unavailable | ACPStreamConnection throws with agent unavailable error |\n| MAP message timeout | ACPStreamConnection throws with timeout error |\n| Agent returns ACP error | ACPError propagated to caller unchanged |\n| Client disconnects during agent request | Agent receives disconnect, should abort |\n| Stream closed during operation | Pending operations reject with stream closed error |\n| Session lost after reconnection | `sessionLost` event emitted, client can use `loadSession` |\n\n---\n\n## Capability Negotiation\n\n### Client Capabilities\n\n```typescript\nawait acp.initialize({\n protocolVersion: '2024-10-07',\n clientInfo: { name: 'MyIDE', version: '1.0.0' },\n capabilities: {\n fs: {\n readTextFile: !!clientHandlers.readTextFile,\n writeTextFile: !!clientHandlers.writeTextFile\n },\n terminal: !!clientHandlers.createTerminal,\n _meta: {\n map: { observability: options.exposeMapEvents ?? false }\n }\n }\n});\n```\n\n### Agent Capabilities (Registration)\n\n```typescript\nawait mapAgent.connect({\n name: 'CodingAgent',\n capabilities: {\n protocols: ['acp'],\n acp: {\n version: '2024-10-07',\n features: ['loadSession', 'modes', 'terminals']\n }\n }\n});\n```\n\n### Agent Capabilities (ACP Initialize Response)\n\n```typescript\n{\n protocolVersion: '2024-10-07',\n agentInfo: { name: 'CodingAgent', version: '1.0.0' },\n capabilities: {\n loadSession: true,\n sessionCapabilities: { modes: true },\n _meta: {\n map: {\n multiSession: true,\n federation: true\n }\n }\n }\n}\n```\n\n---\n\n## Comparison with Alternatives\n\n| Aspect | Multiplexed ACP Streams | Gateway Agent | Native ACP |\n|--------|------------------------|---------------|------------|\n| **Client SDK** | MAP SDK (bundled ACP types) | ACP SDK (unchanged) | ACP SDK |\n| **Network** | Single MAP connection | Two connections | Direct connection |\n| **Multiple agents** | ✅ Multiple streams | ❌ Single gateway | ❌ One connection |\n| **Observability** | ✅ Full MAP events | ❌ Limited | ❌ None |\n| **Legacy clients** | ❌ Need migration | ✅ Works unchanged | ✅ Native |\n| **Deployment** | Simple | Gateway process needed | Simple |\n| **Debugging** | Single connection trace | Two connection traces | Single trace |\n| **Federation** | ✅ Via MAP (future) | ✅ Via MAP | ❌ No |\n\n---\n\n## Future Extensions\n\n### 1. Gateway Agent (for legacy ACP clients)\n\nFor clients that cannot migrate to the MAP SDK:\n\n```\nACP Client (unchanged) → ACP Gateway Agent → MAP System → Target Agents\n```\n\nUses the same envelope format internally.\n\n### 2. Multi-Agent Sessions\n\nAllow an ACP session to span multiple agents:\n\n```typescript\nconst acp = mapClient.createACPStream({\n targetScope: 'project-scope',\n routingStrategy: 'round-robin' | 'broadcast' | 'leader',\n client: {...}\n});\n```\n\n### 3. Session Migration\n\nMove an ACP session from one agent to another:\n\n```typescript\nawait acp.migrateSession({\n newTargetAgent: 'agent-2',\n preserveHistory: true\n});\n```\n\n### 4. Federation\n\nRoute ACP streams to agents in federated MAP systems:\n\n```typescript\nconst acp = mapClient.createACPStream({\n targetAgent: 'agent-1',\n targetSystem: 'remote-system',\n client: {...}\n});\n```\n\n---\n\n## Testing Strategy\n\n### Unit Testing\n\n```typescript\nimport { TestACPAgent } from '@anthropic/map-sdk/testing';\n\nconst mockAgent = new TestACPAgent({\n capabilities: { loadSession: true },\n onPrompt: async (params) => {\n await mockAgent.sendUpdate({ type: 'message_start', ... });\n await mockAgent.sendUpdate({ type: 'content_delta', ... });\n return { stopReason: 'end_turn' };\n }\n});\n\n// Test client code against mock\nconst acp = createTestACPStream(mockAgent);\nawait acp.initialize({...});\nconst result = await acp.prompt({...});\nexpect(result.stopReason).toBe('end_turn');\n```\n\n### Integration Testing\n\n```typescript\nimport { TestServer } from '@anthropic/map-sdk/testing';\n\nconst server = new TestServer();\nserver.registerACPAgent('test-agent', mockAgent);\n\nconst client = await ClientConnection.connect(server.url);\nconst acp = client.createACPStream({\n targetAgent: 'test-agent',\n client: {...}\n});\n\n// Full round-trip test\nawait acp.initialize({...});\nconst { sessionId } = await acp.newSession({...});\nconst result = await acp.prompt({ sessionId, messages: [...] });\n```\n\n### Test Coverage\n\n1. **Unit tests**: Message encoding/decoding, correlation logic\n2. **Integration tests**: Full round-trip through TestServer\n3. **Multi-stream tests**: Multiple concurrent ACP streams\n4. **Error handling tests**: Disconnection, timeout, agent unavailable\n5. **Backpressure tests**: Slow client handling rapid session/update\n6. **Reconnection tests**: Session restoration after disconnect\n\n---\n\n## References\n\n- [ACP Specification](https://agentclientprotocol.com/)\n- [ACP TypeScript SDK](../references/typescript-sdk)\n- [MAP Protocol Specification](../docs/00-design-specification.md)\n","priority":1,"archived":0,"archived_at":null,"created_at":"2026-01-30 10:18:54","updated_at":"2026-01-30 18:52:27","parent_id":null,"parent_uuid":null,"relationships":[],"tags":["acp","architecture","integration","protocol"]}
8
- {"id":"s-65aw","uuid":"4a6b6165-7c8a-4184-893f-3fe3f047e5b4","title":"MAP Extension System","file_path":"specs/s-65aw_map_extension_system.md","content":"# MAP Extension System\n\n> **Status: Designed, implementation deferred.** The extension system is designed but not yet needed. The first candidate (conversations/mail) was promoted to a core MAP feature (see [[s-1bob]]). This spec will be implemented when truly optional extensions are needed (e.g., streaming, transactions, domain-specific features).\n\n## Overview\n\nThe MAP Extension System provides a standardized way to add optional functionality to the Multi-Agent Protocol without breaking backward compatibility. Extensions are self-contained feature sets that can be negotiated at connection time, allowing servers and clients to progressively adopt new capabilities.\n\n## Design Principles\n\n### 1. Opt-In Adoption\nExtensions are optional. A minimal MAP implementation only needs to support core methods (`map/connect`, `map/send`, `map/subscribe`, etc.). Extensions can be added incrementally without breaking existing clients.\n\n### 2. Graceful Degradation\nWhen an extension-aware client talks to a non-extension server, or vice versa, the system should degrade gracefully. Extension metadata in messages should be ignorable by non-aware participants.\n\n### 3. Capability Discovery\nClients and servers discover each other's extension support during the `map/connect` handshake. This allows runtime decisions about what features to use.\n\n### 4. Namespace Isolation\nEach extension owns a method namespace (e.g., `stream/*` for streaming). This prevents collisions between extensions and with core MAP methods.\n\n### 5. Backward-Compatible Data\nExtension data piggybacks on existing structures via the `meta` field using `x-{extension}` prefixed keys. Non-aware participants can safely ignore this data.\n\n### 6. Protocol vs Implementation Separation\nThe extension **protocol** defines methods, events, capabilities, and schemas. It is stateless. The **implementation** maintains state, handles lifecycle, and manages storage. This separation allows different implementations of the same extension protocol.\n\n---\n\n## Extension Manifest\n\nEvery extension is described by a manifest that declares its capabilities:\n\n```typescript\ninterface ExtensionManifest {\n id: string; // e.g., 'streaming', 'transactions'\n version: string; // e.g., '1.0.0'\n namespace: string; // e.g., 'stream' → methods are 'stream/start'\n description?: string;\n \n requires: {\n mapVersion: string; // semver range, e.g., '>=1.0.0'\n capabilities?: string[];\n };\n \n provides: {\n methods: MethodDefinition[];\n events: EventDefinition[];\n capabilities?: CapabilityDefinition[];\n \n // Message interception for observability (optional)\n intercepts?: {\n messages?: {\n metaPrefix: string; // e.g., 'x-stream'\n onError: 'log-continue' | 'warn-in-response' | 'fail';\n };\n };\n \n // Event filter schema for map/subscribe integration (optional)\n eventFilter?: {\n properties: Record<string, { \n type: string; \n description?: string;\n required?: boolean;\n }>;\n };\n };\n \n dependencies?: ExtensionDependency[];\n}\n\ninterface MethodDefinition {\n name: string;\n description: string;\n type: 'request' | 'notification';\n requiredCapability?: string;\n}\n\ninterface EventDefinition {\n type: string;\n description: string;\n filterableProperties?: string[];\n}\n\ninterface CapabilityDefinition {\n path: string;\n description: string;\n}\n\ninterface ExtensionDependency {\n id: string;\n version: string;\n}\n```\n\n---\n\n## Capability Negotiation\n\nExtension support is negotiated during the `map/connect` handshake.\n\n### Connect Request (Client/Agent → Server)\n\n```typescript\ninterface ConnectRequestParams {\n // ... existing fields ...\n capabilities: {\n // ... existing capability groups ...\n extensions?: {\n supported?: ExtensionSupport[];\n required?: string[];\n };\n };\n}\n\ninterface ExtensionSupport {\n id: string;\n version: string;\n capabilities?: string[];\n}\n```\n\n### Connect Response (Server → Client/Agent)\n\n```typescript\ninterface ConnectResponseResult {\n // ... existing fields ...\n capabilities: {\n // ... existing capability groups ...\n extensions?: {\n available: ExtensionAvailability[];\n };\n };\n}\n\ninterface ExtensionAvailability {\n id: string;\n version: string;\n enabled: boolean;\n capabilities?: string[];\n disabledReason?: 'not-supported' | 'not-authorized' | 'disabled-by-config';\n}\n```\n\n### Negotiation Semantics\n\n1. Client/Agent sends list of supported extensions with versions\n2. Server checks each against its available extensions\n3. For each extension:\n - If server doesn't support it: `enabled: false, disabledReason: 'not-supported'`\n - If versions are incompatible: `enabled: false, disabledReason: 'not-supported'`\n - If participant lacks required capabilities: `enabled: false, disabledReason: 'not-authorized'`\n - Otherwise: `enabled: true` with effective capabilities\n4. If participant required an extension that's not enabled: connection fails with error\n5. Participants can use server extensions they didn't advertise; they just won't receive events unless subscribed\n\n---\n\n## Method Namespacing\n\n### Namespace Rules\n\n1. Extension namespace is a short identifier (2-10 lowercase chars)\n2. Methods are `{namespace}/{method-path}`\n3. Reserved namespaces: `map` (core), `mail` (core mail protocol)\n4. Nested paths are allowed\n\n### Method Registration\n\nExtensions explicitly register all methods in the manifest for:\n- **Discoverability**: Manifest documents what methods exist\n- **Type safety**: Each method can have typed schemas\n- **Better errors**: Clear \"method not found\" messages\n- **Per-method capabilities**: Different permissions per method\n\n### Method Not Found\n\n```json\n{\n \"jsonrpc\": \"2.0\",\n \"id\": 1,\n \"error\": {\n \"code\": -32601,\n \"message\": \"Method not found: stream/start\",\n \"data\": {\n \"category\": \"protocol\",\n \"details\": {\n \"extension\": \"streaming\",\n \"reason\": \"extension-not-enabled\"\n }\n }\n }\n}\n```\n\n---\n\n## Message Interception\n\nExtensions can observe core MAP messages for tracking and observability without modifying routing behavior.\n\n### Interception Semantics\n\n1. **Non-blocking**: Message routing proceeds normally regardless of interception\n2. **Observability**: Extensions observe messages, they don't gate them\n3. **Error handling**: Configurable per-extension via `onError`\n4. **Ordering**: When multiple extensions intercept the same message, they run **in sequence** in extension registration order\n\n### Handler Interface\n\n```typescript\ninterface ExtensionHandler {\n handleRequest(method: string, params: unknown, ctx: RequestContext): Promise<unknown>;\n onMessage?(message: Message, extensionMeta: Record<string, unknown>, ctx: RequestContext): Promise<void>;\n checkAccess?(participant: Participant): ExtensionAccessResult;\n matchesFilter?(event: Event, filter: Record<string, unknown>): boolean;\n}\n\ninterface ExtensionAccessResult {\n allowed: boolean;\n capabilities?: string[];\n reason?: string;\n}\n```\n\n---\n\n## Event Namespacing\n\n- Core events: `{entity}_{action}` (e.g., `agent_registered`)\n- Mail events: `mail.{entity}.{action}` (e.g., `mail.turn.added`) — core, not extension\n- Extension events: `{extension}.{entity}.{action}` (e.g., `stream.started`)\n\n### Extension Filters in map/subscribe\n\n```typescript\ninterface SubscriptionFilter {\n eventTypes?: string[];\n agentIds?: AgentId[];\n scopeIds?: ScopeId[];\n mail?: { ... }; // Core mail filter\n extensions?: { // Extension-specific filters\n [extensionId: string]: Record<string, unknown>;\n };\n}\n```\n\n---\n\n## Extension Data in Core Messages\n\nExtensions use `x-{extension-id}` keys in the `meta` field:\n\n```typescript\nmeta: {\n correlationId: 'abc',\n mail: { ... }, // Core (not prefixed)\n 'x-stream': { ... }, // Extension (x- prefixed)\n 'x-audit': { ... }, // Extension (x- prefixed)\n}\n```\n\n**Note:** Core features like `mail` use unprefixed keys. Extensions use the `x-` prefix convention.\n\n### Interoperability\n\n1. Non-aware participants MUST ignore unrecognized `x-*` keys\n2. Extension-aware participants SHOULD process relevant `x-*` keys\n3. Forwarding SHOULD preserve `x-*` keys (pass-through)\n\n---\n\n## Extension Registration (Server-Side)\n\n```typescript\ninterface ExtensionRegistry {\n register(manifest: ExtensionManifest, handler: ExtensionHandler): void;\n isAvailable(extensionId: string): boolean;\n getManifest(extensionId: string): ExtensionManifest | undefined;\n listExtensions(): ExtensionManifest[];\n getExtensionForMethod(method: string): string | undefined;\n getInterceptors(metaPrefix: string): ExtensionHandler[];\n}\n```\n\n---\n\n## Error Handling\n\nExtensions define error codes in reserved ranges:\n\n| Range | Purpose |\n|-------|---------|\n| -32700 to -32600 | JSON-RPC standard errors |\n| 1000-4999 | MAP core errors |\n| 5000-5999 | Federation errors |\n| 10000-10099 | Mail protocol (core) |\n| 10100-19999 | Reserved for extensions |\n\n---\n\n## Resolved Design Decisions\n\n1. **Protocol vs Implementation**: Protocol is stateless. Implementation handles state, storage, lifecycle.\n2. **Method Registration**: Explicit in manifest; single handler function internally is acceptable.\n3. **Message Interception**: Non-blocking observability. Extensions observe, don't gate.\n4. **Interception Error Handling**: Configurable per-extension.\n5. **Multiple Interceptors**: Run in sequence in registration order.\n6. **Event Filtering**: Extensions integrate with `map/subscribe` via `extensions` filter.\n7. **Core vs Extension**: Features fundamental to MAP (like mail) are core with capability gating. Extensions are for truly optional features.\n8. **Meta key convention**: Core features use unprefixed keys (`mail`). Extensions use `x-{id}` prefix.\n","priority":3,"archived":0,"archived_at":null,"created_at":"2026-01-31 05:08:06","updated_at":"2026-02-05 19:26:28","parent_id":null,"parent_uuid":null,"relationships":[],"tags":["architecture","deferred","extensions","protocol"]}
9
- {"id":"s-1bob","uuid":"96dd7cfe-bf89-4aa1-9aeb-5576e54a471a","title":"MAP Mail Protocol","file_path":"specs/s-1bob_map_conversation_extension.md","content":"# MAP Mail Protocol\n\n## Overview\n\nThe Mail Protocol (`mail/`) is a core MAP feature that adds **persistence and structure** to MAP's existing message routing. It provides conversations as containers for tracking interactions, making them queryable, replayable, and observable.\n\nMail is purely additive - it doesn't replace or duplicate `map/send`. It layers on top.\n\n### Design Philosophy\n\n1. **Mail is for communication, not computation.** Turns record what participants intentionally communicate. Agent internal work (tool calls, reasoning) is not tracked in mail unless explicitly shared.\n\n2. **Routing and recording are orthogonal.** `map/send` routes messages (transport). `mail/turn` records turns (persistence). Adding `mail` meta to `map/send` does both. They compose cleanly.\n\n3. **Generic content model.** The protocol defines a small set of well-known content types. Custom types use `x-` prefixes. No protocol changes needed for new content types.\n\n4. **Capability-gated.** Mail is a core feature gated behind the `mail` capability group. Implementations that don't support it simply omit the capability.\n\n---\n\n## Architecture: How Mail Layers on MAP\n\n### What MAP Has Today (Transport Layer)\n\n- `map/send` routes messages between agents, to scopes, by role, hierarchy, broadcast\n- `MessageMeta` carries context: `correlationId`, `relationship`, `priority`, `expectsResponse`\n- `map/subscribe` lets clients observe message events (metadata only, no payload)\n- Scopes are named agent groups with join policies and visibility rules\n- Messages are **ephemeral** - delivered and forgotten\n\n### What Mail Adds (Persistence Layer)\n\n- **Conversations** - containers that group related interactions\n- **Turns** - ordered records of communication within a conversation\n- **Threading** - sub-conversations for focused discussion\n- **Participants** - who's in the conversation with roles and permissions\n- **History** - catch-up, replay, and summary for late joiners\n- **Queryable** - list, filter, paginate turns and conversations\n\n### The Three Operations\n\n```\n┌──────────────────────────────────────────────────────────────┐\n│ │\n│ map/send Route a message (transport) │\n│ map/send + mail meta Route AND record (both) │\n│ mail/turn Record a turn only (persistence)│\n│ │\n└──────────────────────────────────────────────────────────────┘\n```\n\n**`map/send` without `mail` meta** - Pure routing. Message delivered to recipient. No conversation involvement. Observable via `map/subscribe` as `message_sent` event. This is what exists today.\n\n**`map/send` with `mail` meta** - Routes the message via normal `map/send` path, AND records a turn in the specified conversation. Used for inter-agent communication within a conversation.\n\n**`mail/turn`** - Records a turn in a conversation without routing any message. Used for non-routed content: user messages, status events, observations, summaries. Does NOT deliver anything to anyone.\n\n### What Goes Into a Conversation vs Not\n\n| Activity | In mail? | How |\n|----------|----------|-----|\n| Agent sends message to another agent (in conversation) | Yes | `map/send` + `mail` meta |\n| Agent sends message outside any conversation | No | `map/send` (no mail) |\n| User message | Yes | `mail/turn` (contentType: text) |\n| Orchestrator assigns task | Yes | `mail/turn` (contentType: event) |\n| Agent calls a tool | No | Agent internal work, observed via MAP events |\n| Agent internal reasoning | No | Agent internal work |\n| Agent shares results with conversation | Yes | `map/send` + `mail` meta, or `mail/turn` |\n| System lifecycle event | Yes | `mail/turn` (contentType: event) |\n\n**Key principle:** If an agent's internal tool calls need to be tracked, create a child conversation for the agent's trajectory. The parent conversation stays clean with only inter-participant communication.\n\n---\n\n## Capability Negotiation\n\nMail capabilities are negotiated during `map/connect` as part of the core capability system.\n\n### Connect Request (Client/Agent → Server)\n\n```typescript\ninterface ConnectRequestParams {\n protocolVersion: ProtocolVersion;\n participantType: ParticipantType;\n // ... existing fields ...\n \n capabilities: {\n // Existing core capabilities\n messaging: { canSend: boolean; canBroadcast: boolean };\n observation: { canObserve: boolean; canSubscribe: boolean };\n \n // Mail capabilities (optional - omit if not needed)\n mail?: {\n canCreate?: boolean;\n canJoin?: boolean;\n canInvite?: boolean;\n canViewHistory?: boolean;\n canCreateThreads?: boolean;\n };\n };\n}\n```\n\n### Connect Response (Server → Client/Agent)\n\n```typescript\ninterface ConnectResponseResult {\n protocolVersion: ProtocolVersion;\n sessionId: SessionId;\n // ... existing fields ...\n \n capabilities: {\n messaging: { canSend: boolean; canBroadcast: boolean };\n observation: { canObserve: boolean; canSubscribe: boolean };\n \n // Mail capabilities (absent = not supported by server)\n mail?: {\n enabled: boolean;\n canCreate: boolean;\n canJoin: boolean;\n canInvite: boolean;\n canViewHistory: boolean;\n canCreateThreads: boolean;\n };\n };\n}\n```\n\n### Behavior When Not Supported\n\n- **Server doesn't support mail**: `mail` absent from response. Calling `mail/*` methods returns method-not-found error. `map/send` with `mail` meta still routes the message but ignores the `mail` meta.\n- **Client doesn't request mail**: Server omits `mail` from response. Turn tracking for intercepted messages still works if server supports it.\n- **Partial capabilities**: Server may grant `canJoin` but not `canCreate` based on participant role.\n\n---\n\n## Method Namespace\n\nAll mail methods use the `mail/` namespace:\n\n```\nmail/create - Create a new conversation\nmail/get - Get conversation details\nmail/list - List conversations\nmail/close - Close a conversation\nmail/join - Join an existing conversation\nmail/leave - Leave a conversation\nmail/invite - Invite a participant\nmail/turn - Record a turn (no routing)\nmail/turns/list - List turns in conversation\nmail/thread/create - Create a thread within conversation\nmail/thread/list - List threads in conversation\nmail/summary - Get or generate conversation summary\nmail/replay - Replay turns from a point\n```\n\n---\n\n## Core Types\n\n### Identifiers\n\n```typescript\ntype ConversationId = string; // Format: 'conv-{ulid}'\ntype TurnId = string; // Format: 'turn-{ulid}'\ntype ThreadId = string; // Format: 'thread-{ulid}'\n```\n\n### Conversation\n\n```typescript\ntype ConversationType = \n | 'user-session' // User interacting with agent(s)\n | 'agent-task' // Agent trajectory (child of another conversation)\n | 'multi-agent' // Agent-to-agent coordination\n | 'mixed'; // Any combination\n\ntype ConversationStatus = \n | 'active'\n | 'paused'\n | 'completed'\n | 'failed'\n | 'archived';\n\ninterface Conversation {\n id: ConversationId;\n type: ConversationType;\n status: ConversationStatus;\n subject?: string;\n participantCount: number;\n parentConversationId?: ConversationId;\n parentTurnId?: TurnId;\n createdAt: Timestamp;\n updatedAt: Timestamp;\n closedAt?: Timestamp;\n createdBy: ParticipantId;\n metadata?: Record<string, unknown>;\n}\n```\n\n### Conversation Participant\n\n```typescript\ntype ParticipantRole = \n | 'initiator'\n | 'assistant'\n | 'worker'\n | 'observer'\n | 'moderator';\n\ninterface ConversationParticipant {\n id: ParticipantId;\n type: 'user' | 'agent' | 'system';\n role: ParticipantRole;\n joinedAt: Timestamp;\n leftAt?: Timestamp;\n permissions: ConversationPermissions;\n agentInfo?: {\n agentId: AgentId;\n name?: string;\n role?: string;\n };\n}\n\ninterface ConversationPermissions {\n canSend: boolean;\n canObserve: boolean;\n canInvite: boolean;\n canRemove: boolean;\n canCreateThreads: boolean;\n historyAccess: 'none' | 'from-join' | 'full';\n canSeeInternal: boolean;\n}\n```\n\n### Thread\n\n```typescript\ninterface Thread {\n id: ThreadId;\n conversationId: ConversationId;\n parentThreadId?: ThreadId;\n subject?: string;\n rootTurnId: TurnId;\n turnCount: number;\n participantCount: number;\n createdAt: Timestamp;\n updatedAt: Timestamp;\n createdBy: ParticipantId;\n}\n```\n\n### Turn\n\nA turn is the atomic unit of conversation - any interaction a participant intentionally communicates.\n\n```typescript\ninterface Turn {\n id: TurnId;\n conversationId: ConversationId;\n participant: ParticipantId;\n timestamp: Timestamp;\n\n // Content - generic model\n contentType: string; // Well-known or custom (x-prefixed)\n content: unknown; // Shape determined by contentType\n\n // Threading\n threadId?: ThreadId;\n inReplyTo?: TurnId;\n\n // How this turn was created\n source: TurnSource;\n\n // Optional\n visibility?: TurnVisibility;\n status?: 'pending' | 'streaming' | 'complete' | 'failed';\n metadata?: Record<string, unknown>;\n}\n\ntype TurnSource = \n | { type: 'explicit'; method: 'mail/turn' } // Direct mail/turn call\n | { type: 'intercepted'; messageId: MessageId }; // Auto-recorded from map/send + mail meta\n```\n\n### Content Types\n\n#### Well-Known Types (Protocol-Defined)\n\n| contentType | content shape | Purpose |\n|-------------|---------------|---------|\n| `text` | `{ text: string }` | Human-readable message |\n| `data` | `{ [key: string]: unknown }` | Structured JSON payload |\n| `event` | `{ event: string, data?: unknown }` | Lifecycle/system event |\n| `reference` | `{ type: string, id: string, uri?: string }` | Pointer to another entity |\n\nThese are the only types the protocol defines. Clients that understand only these 4 types can still render any conversation meaningfully.\n\n#### Custom Types (Application-Defined)\n\nCustom types use `x-` prefix. These are defined by frameworks and implementations, not the protocol:\n\n| contentType | Example content | Defined by |\n|-------------|----------------|------------|\n| `x-tool-call` | `{ tool: 'search', input: {...}, status: 'running' }` | Agent framework |\n| `x-tool-result` | `{ output: {...}, durationMs: 450 }` | Agent framework |\n| `x-thought` | `{ reasoning: '...', confidence: 0.8 }` | Agent framework |\n| `x-handoff` | `{ from: 'agent-a', to: 'agent-b', reason: '...' }` | Orchestrator |\n| `x-code-diff` | `{ file: '...', patch: '...' }` | Dev tools |\n\nA generic client renders unknown types as raw JSON. A rich client with knowledge of custom types renders them with specialized UI.\n\n### Turn Visibility\n\n```typescript\ntype TurnVisibility =\n | { type: 'all' }\n | { type: 'participants'; ids: ParticipantId[] }\n | { type: 'role'; roles: ParticipantRole[] }\n | { type: 'private' };\n```\n\n---\n\n## Message Turn Tracking\n\n### `mail` Meta Key in map/send\n\nWhen an agent wants to send a message AND record it as a conversation turn, it includes the `mail` key in `map/send` meta:\n\n```typescript\ninterface MailMessageMeta {\n conversationId: ConversationId;\n threadId?: ThreadId;\n inReplyTo?: TurnId;\n visibility?: TurnVisibility;\n}\n\n// Usage\n{\n method: 'map/send',\n params: {\n to: { agent: 'agent-b' },\n payload: { text: 'Here are the Q4 results' },\n meta: {\n correlationId: 'task-123',\n mail: {\n conversationId: 'conv-001',\n threadId: 'thread-002'\n }\n }\n }\n}\n```\n\n### Turn Recording Flow\n\n1. **Message arrives** at server via `map/send`\n2. **Server routes message** normally via existing routing (transport layer)\n3. **If mail enabled**, server checks for `mail` key in `meta`\n4. **If `mail` meta present**:\n - Validates conversationId exists and sender is a participant\n - Creates turn with `source: { type: 'intercepted', messageId }`\n - `contentType` is `data` (the message payload becomes the turn content)\n - Emits `mail.turn.added` event\n5. **Message delivery succeeds** regardless of turn recording outcome\n\n### Error Handling\n\nTurn recording is non-blocking:\n- If turn recording fails, the error is logged server-side\n- Message delivery completes normally\n- A warning may be included in the send response (implementation choice)\n\n---\n\n## Trajectory Tracking Pattern\n\nAgent internal work (tool calls, reasoning) is NOT automatically tracked in mail. If trajectory tracking is desired, use child conversations:\n\n```\nconv-001: \"Answer user question\" (user-session)\n├── turn-001 [text] user \"What were Q4 sales?\"\n├── turn-002 [event] orchestrator { event: 'task.assigned', agent: 'agent-a' }\n├── turn-003 [data] orchestrator { task: 'research Q4 sales' } ← map/send + mail\n├── turn-004 [data] agent-a { results: { growth: '15%' } } ← map/send + mail\n├── turn-005 [text] orchestrator \"Q4 sales were up 15%\"\n│\n└── conv-002: \"Agent A work log\" (agent-task, parent: conv-001)\n ├── turn [x-tool-call] search(\"Q4 revenue\")\n ├── turn [x-tool-result] { rows: [...] }\n ├── turn [x-tool-call] analyze(data)\n └── turn [x-tool-result] { trend: \"up 15%\" }\n```\n\nThe parent conversation (conv-001) contains only inter-participant communication. The child conversation (conv-002) contains the agent's detailed trajectory. They're linked via `parentConversationId`.\n\nCreating trajectory conversations is an orchestrator/framework concern, not a protocol requirement.\n\n---\n\n## Methods\n\n### mail/create\n\nCreate a new conversation.\n\n**Request:**\n```typescript\ninterface MailCreateRequest {\n type?: ConversationType;\n subject?: string;\n parentConversationId?: ConversationId;\n parentTurnId?: TurnId;\n initialParticipants?: Array<{\n id: ParticipantId;\n role?: ParticipantRole;\n permissions?: Partial<ConversationPermissions>;\n }>;\n initialTurn?: {\n contentType: string;\n content: unknown;\n visibility?: TurnVisibility;\n };\n metadata?: Record<string, unknown>;\n}\n```\n\n**Response:**\n```typescript\ninterface MailCreateResponse {\n conversation: Conversation;\n participant: ConversationParticipant;\n initialTurn?: Turn;\n}\n```\n\n### mail/get\n\nGet conversation details.\n\n**Request:**\n```typescript\ninterface MailGetRequest {\n conversationId: ConversationId;\n include?: {\n participants?: boolean;\n threads?: boolean;\n recentTurns?: number;\n stats?: boolean;\n };\n}\n```\n\n**Response:**\n```typescript\ninterface MailGetResponse {\n conversation: Conversation;\n participants?: ConversationParticipant[];\n threads?: Thread[];\n recentTurns?: Turn[];\n stats?: {\n totalTurns: number;\n turnsByContentType: Record<string, number>;\n activeParticipants: number;\n threadCount: number;\n };\n}\n```\n\n### mail/list\n\nList conversations.\n\n**Request:**\n```typescript\ninterface MailListRequest {\n filter?: {\n type?: ConversationType[];\n status?: ConversationStatus[];\n participantId?: ParticipantId;\n createdAfter?: Timestamp;\n createdBefore?: Timestamp;\n parentConversationId?: ConversationId;\n };\n limit?: number;\n cursor?: string;\n}\n```\n\n**Response:**\n```typescript\ninterface MailListResponse {\n conversations: Conversation[];\n nextCursor?: string;\n hasMore: boolean;\n}\n```\n\n### mail/join\n\nJoin an existing conversation.\n\n**Request:**\n```typescript\ninterface MailJoinRequest {\n conversationId: ConversationId;\n role?: ParticipantRole;\n catchUp?: {\n from: 'beginning' | 'recent' | TurnId | Timestamp;\n limit?: number;\n includeSummary?: boolean;\n };\n}\n```\n\n**Response:**\n```typescript\ninterface MailJoinResponse {\n conversation: Conversation;\n participant: ConversationParticipant;\n history?: Turn[];\n historyCursor?: string;\n summary?: string;\n}\n```\n\n### mail/leave\n\nLeave a conversation.\n\n**Request:**\n```typescript\ninterface MailLeaveRequest {\n conversationId: ConversationId;\n reason?: string;\n}\n```\n\n**Response:**\n```typescript\ninterface MailLeaveResponse {\n success: boolean;\n leftAt: Timestamp;\n}\n```\n\n### mail/invite\n\nInvite a participant.\n\n**Request:**\n```typescript\ninterface MailInviteRequest {\n conversationId: ConversationId;\n participant: {\n id: ParticipantId;\n role?: ParticipantRole;\n permissions?: Partial<ConversationPermissions>;\n };\n message?: string;\n}\n```\n\n**Response:**\n```typescript\ninterface MailInviteResponse {\n invited: boolean;\n participant?: ConversationParticipant;\n invitationId?: string;\n pending?: boolean;\n}\n```\n\n### mail/turn\n\nRecord a turn in a conversation. Does NOT route any message.\n\n**Request:**\n```typescript\ninterface MailTurnRequest {\n conversationId: ConversationId;\n contentType: string;\n content: unknown;\n threadId?: ThreadId;\n inReplyTo?: TurnId;\n visibility?: TurnVisibility;\n metadata?: Record<string, unknown>;\n}\n```\n\n**Response:**\n```typescript\ninterface MailTurnResponse {\n turn: Turn;\n}\n```\n\n### mail/turns/list\n\nList turns in a conversation.\n\n**Request:**\n```typescript\ninterface MailTurnsListRequest {\n conversationId: ConversationId;\n filter?: {\n threadId?: ThreadId;\n includeAllThreads?: boolean;\n contentTypes?: string[];\n participantId?: ParticipantId;\n afterTurnId?: TurnId;\n beforeTurnId?: TurnId;\n afterTimestamp?: Timestamp;\n beforeTimestamp?: Timestamp;\n };\n limit?: number;\n order?: 'asc' | 'desc';\n}\n```\n\n**Response:**\n```typescript\ninterface MailTurnsListResponse {\n turns: Turn[];\n hasMore: boolean;\n nextCursor?: string;\n}\n```\n\n### mail/thread/create\n\nCreate a thread.\n\n**Request:**\n```typescript\ninterface MailThreadCreateRequest {\n conversationId: ConversationId;\n rootTurnId: TurnId;\n subject?: string;\n parentThreadId?: ThreadId;\n}\n```\n\n**Response:**\n```typescript\ninterface MailThreadCreateResponse {\n thread: Thread;\n}\n```\n\n### mail/thread/list\n\nList threads.\n\n**Request:**\n```typescript\ninterface MailThreadListRequest {\n conversationId: ConversationId;\n parentThreadId?: ThreadId;\n limit?: number;\n cursor?: string;\n}\n```\n\n**Response:**\n```typescript\ninterface MailThreadListResponse {\n threads: Thread[];\n hasMore: boolean;\n nextCursor?: string;\n}\n```\n\n### mail/summary\n\nGet or generate a conversation summary.\n\n**Request:**\n```typescript\ninterface MailSummaryRequest {\n conversationId: ConversationId;\n scope?: {\n fromTurnId?: TurnId;\n toTurnId?: TurnId;\n threadId?: ThreadId;\n };\n regenerate?: boolean;\n include?: {\n keyPoints?: boolean;\n keyDecisions?: boolean;\n openQuestions?: boolean;\n participants?: boolean;\n };\n}\n```\n\n**Response:**\n```typescript\ninterface MailSummaryResponse {\n summary: string;\n keyPoints?: string[];\n keyDecisions?: string[];\n openQuestions?: string[];\n generated: boolean;\n cachedAt?: Timestamp;\n}\n```\n\n### mail/replay\n\nReplay turns from a specific point. Used for catch-up after disconnection or late joining.\n\n**Request:**\n```typescript\ninterface MailReplayRequest {\n conversationId: ConversationId;\n fromTurnId?: TurnId;\n fromTimestamp?: Timestamp;\n threadId?: ThreadId;\n limit?: number;\n contentTypes?: string[];\n}\n```\n\n**Response:**\n```typescript\ninterface MailReplayResponse {\n turns: Turn[];\n hasMore: boolean;\n nextCursor?: string;\n missedCount: number;\n}\n```\n\n---\n\n## Events\n\nMail events use the `mail.*` prefix and integrate with core `map/subscribe`.\n\n### Event Types\n\n| Event | Description | Filterable Properties |\n|-------|-------------|----------------------|\n| `mail.created` | Conversation created | `conversationId` |\n| `mail.closed` | Conversation closed | `conversationId` |\n| `mail.participant.joined` | Participant joined | `conversationId`, `participantId` |\n| `mail.participant.left` | Participant left | `conversationId`, `participantId` |\n| `mail.turn.added` | Turn recorded | `conversationId`, `threadId`, `contentType` |\n| `mail.turn.updated` | Turn updated | `conversationId`, `turnId` |\n| `mail.thread.created` | Thread created | `conversationId`, `threadId` |\n| `mail.summary.generated` | Summary generated | `conversationId` |\n\n### Subscribing to Mail Events\n\n```typescript\n// Subscribe to all events for a conversation\n{\n method: 'map/subscribe',\n params: {\n filter: {\n eventTypes: ['mail.turn.added', 'mail.participant.joined'],\n mail: { conversationId: 'conv-123' }\n }\n }\n}\n\n// Subscribe to a specific thread\n{\n method: 'map/subscribe',\n params: {\n filter: {\n eventTypes: ['mail.turn.added'],\n mail: { conversationId: 'conv-123', threadId: 'thread-456' }\n }\n }\n}\n\n// Subscribe to all mail events system-wide\n{\n method: 'map/subscribe',\n params: {\n filter: {\n eventTypes: ['mail.created', 'mail.turn.added', 'mail.closed']\n }\n }\n}\n```\n\n### SubscriptionFilter\n\nThe `mail` field is added to the core `SubscriptionFilter`:\n\n```typescript\ninterface SubscriptionFilter {\n eventTypes?: string[];\n agentIds?: AgentId[];\n scopeIds?: ScopeId[];\n \n // Mail-specific filter\n mail?: {\n conversationId?: ConversationId;\n threadId?: ThreadId;\n participantId?: ParticipantId;\n contentType?: string;\n };\n}\n```\n\n---\n\n## Error Codes\n\n| Code | Name | Description |\n|------|------|-------------|\n| 10000 | CONVERSATION_NOT_FOUND | Conversation ID doesn't exist |\n| 10001 | CONVERSATION_CLOSED | Cannot modify a closed conversation |\n| 10002 | NOT_A_PARTICIPANT | Caller is not a participant |\n| 10003 | PERMISSION_DENIED | Lacks required permission |\n| 10004 | TURN_NOT_FOUND | Turn ID doesn't exist |\n| 10005 | THREAD_NOT_FOUND | Thread ID doesn't exist |\n| 10006 | INVALID_TURN_CONTENT | Turn content validation failed |\n| 10007 | PARTICIPANT_ALREADY_JOINED | Already a participant |\n| 10008 | INVITATION_REQUIRED | Cannot join without invitation |\n| 10009 | HISTORY_ACCESS_DENIED | Cannot access requested history |\n| 10010 | PARENT_CONVERSATION_NOT_FOUND | Parent conversation doesn't exist |\n\n---\n\n## Implementation Considerations\n\n### Storage\n\nConversations and turns need persistent storage:\n- **Conversations table**: id, type, status, subject, parent, timestamps, metadata\n- **Participants table**: conversation_id, participant_id, role, permissions, joined_at, left_at\n- **Turns table**: id, conversation_id, thread_id, participant_id, content_type, content (JSONB), visibility, source, timestamps\n- **Threads table**: id, conversation_id, parent_thread_id, root_turn_id, subject\n\n### Integration with map/send\n\nServer implementations that support mail add turn tracking to the `map/send` handler:\n\n```typescript\nasync handleSend(message: Message, ctx: RequestContext) {\n // 1. Route message normally (existing behavior)\n const result = await this.router.route(message);\n \n // 2. If mail enabled and mail meta present, record turn\n if (this.capabilities.mail?.enabled && message.meta?.mail) {\n try {\n await this.mail.recordTurn({\n conversationId: message.meta.mail.conversationId,\n participant: message.from,\n contentType: 'data',\n content: message.payload,\n threadId: message.meta.mail.threadId,\n inReplyTo: message.meta.mail.inReplyTo,\n visibility: message.meta.mail.visibility,\n source: { type: 'intercepted', messageId: message.id }\n });\n } catch (err) {\n // Log error, don't fail the send\n this.logger.warn('Failed to record mail turn', { err, messageId: message.id });\n }\n }\n \n return result;\n}\n```\n\n### Privacy\n\n- **Turn visibility**: Enforce at query time, not just at delivery\n- **History access**: Check permissions before returning history on join\n- **Agent thoughts**: Only visible in trajectory child conversations if `canSeeInternal` is true\n- **Consistency with MAP**: Mail respects the existing 4-layer permission model\n\n---\n\n## Resolved Design Decisions\n\n1. **Core feature, capability-gated**: Mail is a core MAP feature gated behind the `mail` capability group, not an extension.\n\n2. **`mail/` namespace**: All methods use `mail/` prefix. Capability key is `mail`. Meta key in `map/send` is `mail`.\n\n3. **Routing and recording are orthogonal**: `map/send` routes. `mail/turn` records. `map/send` + `mail` meta does both. They don't duplicate each other.\n\n4. **Mail is for communication, not computation**: Turns record what participants communicate. Tool calls and internal reasoning are agent implementation details, not mail turns.\n\n5. **Trajectory tracking via child conversations**: If detailed agent work logs are needed, create a child conversation with `type: 'agent-task'`. This is a framework pattern, not a protocol requirement.\n\n6. **Generic content model**: 4 well-known content types (`text`, `data`, `event`, `reference`). Custom types use `x-` prefix. Protocol doesn't need to change for new content types.\n\n7. **Non-blocking recording**: Turn recording failures don't affect message delivery.\n\n8. **No correlation inference**: Only messages with explicit `mail` meta are recorded as turns. No magic inference from `correlationId`.\n\n9. **Event integration**: Mail events use `mail.*` prefix and are filterable via `mail` field in `SubscriptionFilter`.\n\n---\n\n## Open Questions\n\n1. **Turn immutability**: Should turns be immutable after creation, or allow edits with version history?\n\n2. **Conversation forking**: Can a conversation be forked/branched for \"what-if\" scenarios?\n\n3. **Cross-conversation references**: How do turns reference turns in other conversations? The `reference` content type covers this partially.\n\n4. **Retention policies**: How long are conversations/turns retained? Per-conversation settings?\n\n5. **Scope-conversation binding**: Should a MAP scope be associable with a conversation, so all scope messages auto-record as turns?\n","priority":2,"archived":0,"archived_at":null,"created_at":"2026-01-31 05:09:59","updated_at":"2026-02-06T00:05:21.493Z","parent_id":null,"parent_uuid":null,"relationships":[{"from":"s-1bob","from_type":"spec","to":"s-65aw","to_type":"spec","type":"depends-on"}],"tags":["conversations","core","mail","p0","protocol"]}
10
- {"id":"s-37t6","uuid":"4b176ad8-e702-4515-9bc9-f3ebd8b123ff","title":"Agentic-Mesh Transport & Git Sync Integration","file_path":"specs/s-37t6_agentic_mesh_transport_git_sync_integration.md","content":"# Agentic-Mesh Transport & Git Sync Integration\n\n## Overview\n\nThis spec defines the integration of agentic-mesh as a transport option for the MAP TypeScript SDK, enabling:\n\n1. **AgenticMeshTransport** - Connect MAP clients/agents over encrypted mesh tunnels (Nebula/Tailscale/Headscale)\n2. **Git Sync** - Allow agents to synchronize code/filesystems using git over MAP protocol\n\n## Problem Statement\n\nThe current MAP SDK supports WebSocket and stdio transports, but lacks support for:\n\n- **Encrypted P2P mesh networks** - No way to connect MAP participants over Nebula/Tailscale networks\n- **File/code synchronization** - No built-in mechanism for agents to sync repositories or filesystems\n\nAgentic-mesh already provides:\n- `TunnelStream` for NDJSON framing over mesh transports\n- `TransportAdapter` interface with Nebula/Tailscale implementations\n- `GitTransportService` and `GitSyncClient` for git operations over MAP\n\nThe integration is purely additive to the MAP SDK - agentic-mesh already exports everything needed.\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ multi-agent-protocol/ts-sdk │\n├─────────────────────────────────────────────────────────────────┤\n│ │\n│ ┌─────────────────┐ │\n│ │ ClientConnection │ Standard MAP SDK client/agent │\n│ │ AgentConnection │ │\n│ └────────┬────────┘ │\n│ │ │\n│ ▼ │\n│ ┌─────────────────────────────────────────────────────────┐ │\n│ │ Stream Interface (pluggable) │ │\n│ ├─────────────────────────────────────────────────────────┤ │\n│ │ websocketStream │ ndJsonStream │ agenticMeshStream (NEW) │ │\n│ └─────────────────────────────────────────────────────────┘ │\n│ │ │\n│ ▼ │\n│ ┌─────────────────────────────────────────────────────────┐ │\n│ │ AgenticMeshTransport (NEW) │ │\n│ │ - Wraps agentic-mesh TunnelStream │ │\n│ │ - Adapts to MAP SDK Stream interface │ │\n│ │ - Manages transport lifecycle │ │\n│ └─────────────────────────────────────────────────────────┘ │\n│ │ │\n└─────────────────────────────────────────┼────────────────────────┘\n │\n ▼\n ┌────────────────────────────┐\n │ agentic-mesh (peer dep) │\n │ - TunnelStream │\n │ - TransportAdapter │\n │ - GitSyncClient │\n └────────────────────────────┘\n```\n\n## Design Philosophy\n\n1. **Thin wrapper, not reimplementation** - Reuse agentic-mesh components, don't duplicate\n2. **Optional peer dependency** - agentic-mesh only required if using mesh transport\n3. **Consistent with existing patterns** - Follow `websocketStream()` / `ndJsonStream()` patterns\n4. **Git sync as opt-in capability** - Separate from core transport\n\n---\n\n## Part 1: AgenticMeshTransport\n\n### Stream Adapter\n\nCreate a stream adapter that converts agentic-mesh's `TunnelStream` to MAP SDK's `Stream` interface.\n\n**File:** `ts-sdk/src/stream/agentic-mesh.ts`\n\n```typescript\nimport type { Stream, AnyMessage } from './index';\n\n// Types from agentic-mesh (peer dependency)\nimport type { \n TunnelStream,\n TransportAdapter, \n PeerEndpoint,\n} from 'agentic-mesh';\n\n/**\n * Configuration for agentic-mesh stream.\n */\nexport interface AgenticMeshStreamConfig {\n /** The agentic-mesh transport adapter (Nebula, Tailscale, etc.) */\n transport: TransportAdapter;\n \n /** Remote peer to connect to */\n peer: PeerEndpoint;\n \n /** Local peer ID for identification */\n localPeerId: string;\n \n /** Connection timeout in milliseconds (default: 10000) */\n timeout?: number;\n}\n\n/**\n * Creates a MAP Stream over an agentic-mesh tunnel.\n * \n * This wraps agentic-mesh's TunnelStream (NDJSON over encrypted transport)\n * and adapts it to the MAP SDK's Stream interface.\n * \n * @example\n * ```typescript\n * import { createNebulaTransport } from 'agentic-mesh';\n * import { agenticMeshStream } from '@anthropic/multi-agent-protocol';\n * \n * const transport = createNebulaTransport({\n * configPath: '/etc/nebula/config.yml',\n * });\n * \n * const stream = await agenticMeshStream({\n * transport,\n * peer: { id: 'server', nebulaIp: '10.0.0.1', port: 4242 },\n * localPeerId: 'my-client',\n * });\n * \n * const client = new ClientConnection(stream, { name: 'MeshClient' });\n * await client.connect();\n * ```\n */\nexport async function agenticMeshStream(\n config: AgenticMeshStreamConfig\n): Promise<Stream> {\n const { TunnelStream } = await import('agentic-mesh');\n \n // Start transport if needed\n if (!config.transport.active) {\n await config.transport.start();\n }\n \n // Connect to peer\n const connected = await config.transport.connect(config.peer);\n if (!connected) {\n throw new Error(`Failed to connect to peer: ${config.peer.id}`);\n }\n \n // Create tunnel stream\n const tunnelStream = new TunnelStream({\n transport: config.transport,\n peerId: config.peer.id,\n streamId: `map-${config.localPeerId}-${Date.now()}`,\n });\n \n await tunnelStream.open();\n \n // Adapt to MAP Stream interface\n return tunnelStreamToMapStream(tunnelStream);\n}\n\n/**\n * Adapts a TunnelStream to the MAP SDK Stream interface.\n */\nfunction tunnelStreamToMapStream(tunnel: TunnelStream): Stream {\n const readable = new ReadableStream<AnyMessage>({\n async start(controller) {\n try {\n for await (const frame of tunnel) {\n controller.enqueue(frame as AnyMessage);\n }\n controller.close();\n } catch (error) {\n controller.error(error);\n }\n },\n });\n \n const writable = new WritableStream<AnyMessage>({\n async write(message) {\n await tunnel.write(message);\n },\n async close() {\n await tunnel.close();\n },\n abort(reason) {\n tunnel.close().catch(() => {});\n },\n });\n \n return { readable, writable };\n}\n```\n\n### Static Factory Methods\n\nAdd convenience methods to `ClientConnection` and `AgentConnection` for mesh connections.\n\n**File:** `ts-sdk/src/connection/client.ts` (additions)\n\n```typescript\n/**\n * Options for connecting via agentic-mesh transport.\n */\nexport interface MeshConnectOptions extends ClientConnectOptions {\n /** The agentic-mesh transport adapter */\n transport: TransportAdapter;\n \n /** Remote peer endpoint */\n peer: PeerEndpoint;\n \n /** Local peer ID */\n localPeerId: string;\n}\n\nclass ClientConnection {\n // ... existing code ...\n \n /**\n * Connect to a MAP server via agentic-mesh transport.\n * \n * @example\n * ```typescript\n * import { createNebulaTransport } from 'agentic-mesh';\n * \n * const transport = createNebulaTransport({ configPath: '...' });\n * \n * const client = await ClientConnection.connectMesh({\n * transport,\n * peer: { id: 'server', nebulaIp: '10.0.0.1', port: 4242 },\n * localPeerId: 'dashboard',\n * name: 'Dashboard',\n * reconnection: true,\n * });\n * ```\n */\n static async connectMesh(options: MeshConnectOptions): Promise<ClientConnection> {\n const { agenticMeshStream } = await import('../stream/agentic-mesh');\n \n const stream = await agenticMeshStream({\n transport: options.transport,\n peer: options.peer,\n localPeerId: options.localPeerId,\n timeout: options.connectTimeout,\n });\n \n // Configure createStream for reconnection\n const createStream = async () => {\n return agenticMeshStream({\n transport: options.transport,\n peer: options.peer,\n localPeerId: options.localPeerId,\n timeout: options.connectTimeout,\n });\n };\n \n const reconnection = options.reconnection === true\n ? { enabled: true }\n : typeof options.reconnection === 'object'\n ? options.reconnection\n : undefined;\n \n const client = new ClientConnection(stream, {\n name: options.name,\n capabilities: options.capabilities,\n createStream,\n reconnection,\n });\n \n await client.connect({ auth: options.auth });\n \n return client;\n }\n}\n```\n\nSimilar addition for `AgentConnection.connectMesh()`.\n\n---\n\n## Part 2: Git Sync Integration\n\n### GitSyncCapability\n\nProvide git sync as an optional capability that can be attached to connections.\n\n**File:** `ts-sdk/src/git/sync.ts`\n\n```typescript\nimport type { AgentConnection } from '../connection/agent';\n\n// Types from agentic-mesh\nimport type { \n GitSyncClient,\n SyncOptions,\n SyncResult,\n CloneOptions,\n PullOptions,\n PushOptions,\n TransportAdapter,\n} from 'agentic-mesh';\n\n/**\n * Configuration for git sync capability.\n */\nexport interface GitSyncConfig {\n /** The mesh transport to use for git operations */\n transport: TransportAdapter;\n \n /** Local repository path */\n repoPath: string;\n \n /** HTTP port for git helper (default: 3456) */\n httpPort?: number;\n}\n\n/**\n * Git sync capability for MAP agents.\n * \n * Allows agents to synchronize repositories with peers over MAP.\n * \n * @example\n * ```typescript\n * // Attach git sync to an agent\n * const gitSync = await createGitSync({\n * transport: meshTransport,\n * repoPath: '/my/repo',\n * });\n * \n * // Sync with another agent\n * const result = await gitSync.sync('peer-agent-id', {\n * branch: 'main',\n * bidirectional: true,\n * });\n * \n * // Or individual operations\n * await gitSync.pull('peer-agent-id', 'main');\n * await gitSync.push('peer-agent-id', 'feature-branch');\n * ```\n */\nexport interface GitSync {\n /** Bidirectional sync with a peer */\n sync(peerId: string, options?: SyncOptions): Promise<SyncResult>;\n \n /** Pull from a peer */\n pull(peerId: string, branch?: string, options?: PullOptions): Promise<SyncResult>;\n \n /** Push to a peer */\n push(peerId: string, branch?: string, options?: PushOptions): Promise<SyncResult>;\n \n /** Clone from a peer */\n clone(peerId: string, destPath: string, options?: CloneOptions): Promise<void>;\n \n /** Stop the git sync service */\n stop(): Promise<void>;\n}\n\n/**\n * Creates a git sync capability.\n */\nexport async function createGitSync(config: GitSyncConfig): Promise<GitSync> {\n const { \n GitTransportService, \n createGitSyncClient,\n } = await import('agentic-mesh');\n \n // Create git transport service\n const gitService = new GitTransportService({\n httpPort: config.httpPort ?? 3456,\n git: {\n repoPath: config.repoPath,\n },\n });\n \n // Create peer sender that uses the transport\n const peerSender = {\n sendToPeer: async (peerId: string, message: unknown) => {\n const conn = config.transport.getConnection(peerId);\n if (!conn) throw new Error(`No connection to peer: ${peerId}`);\n await conn.sendGitMessage(message);\n },\n isConnected: (peerId: string) => config.transport.isConnected(peerId),\n };\n \n gitService.setPeerSender(peerSender);\n await gitService.start();\n \n // Create sync client\n const syncClient = createGitSyncClient(gitService, config.repoPath);\n \n return {\n sync: (peerId, options) => syncClient.sync(peerId, options),\n pull: (peerId, branch, options) => syncClient.pull(peerId, branch, options),\n push: (peerId, branch, options) => syncClient.push(peerId, branch, options),\n clone: (peerId, destPath, options) => syncClient.clone(peerId, destPath, options),\n stop: () => gitService.stop(),\n };\n}\n```\n\n### MeshPeer Convenience Class\n\nFor users who want the full agentic-mesh experience (MAP + Git + transport management), provide a convenience wrapper.\n\n**File:** `ts-sdk/src/mesh/peer.ts`\n\n```typescript\nimport type { AgentConnection } from '../connection/agent';\nimport type { GitSync } from '../git/sync';\nimport type { TransportAdapter, PeerEndpoint, MeshPeerConfig } from 'agentic-mesh';\n\n/**\n * Configuration for MAPMeshPeer.\n */\nexport interface MAPMeshPeerConfig {\n /** Unique peer ID */\n peerId: string;\n \n /** Display name */\n name?: string;\n \n /** Transport configuration */\n transport: {\n type: 'nebula' | 'tailscale' | 'headscale';\n config: Record<string, unknown>;\n };\n \n /** Git sync configuration (optional) */\n git?: {\n enabled: boolean;\n repoPath: string;\n httpPort?: number;\n };\n \n /** Initial peers to connect to */\n peers?: PeerEndpoint[];\n}\n\n/**\n * High-level mesh peer for MAP agents.\n * \n * Combines transport management, MAP connection, and git sync\n * into a single convenient interface.\n * \n * @example\n * ```typescript\n * const peer = await MAPMeshPeer.create({\n * peerId: 'worker-1',\n * name: 'DataProcessor',\n * transport: {\n * type: 'nebula',\n * config: { configPath: '/etc/nebula/config.yml' },\n * },\n * git: {\n * enabled: true,\n * repoPath: '/workspace',\n * },\n * peers: [\n * { id: 'server', nebulaIp: '10.0.0.1', port: 4242 },\n * ],\n * });\n * \n * // Register as MAP agent\n * const agent = await peer.registerAgent({\n * role: 'processor',\n * });\n * \n * // Sync code with another peer\n * await peer.git.sync('worker-2', { branch: 'main' });\n * \n * // Handle messages\n * agent.onMessage(async (msg) => {\n * // Process...\n * });\n * ```\n */\nexport class MAPMeshPeer {\n readonly peerId: string;\n readonly transport: TransportAdapter;\n readonly git: GitSync | null;\n \n private constructor(\n peerId: string,\n transport: TransportAdapter,\n git: GitSync | null,\n ) {\n this.peerId = peerId;\n this.transport = transport;\n this.git = git;\n }\n \n static async create(config: MAPMeshPeerConfig): Promise<MAPMeshPeer> {\n const { createMeshPeer } = await import('agentic-mesh');\n \n // Create agentic-mesh peer (handles transport + git internally)\n const meshPeer = createMeshPeer({\n peerId: config.peerId,\n peerName: config.name,\n transport: config.transport,\n git: config.git,\n peers: config.peers,\n });\n \n await meshPeer.start();\n \n return new MAPMeshPeer(\n config.peerId,\n meshPeer.transport,\n meshPeer.git ?? null,\n );\n }\n \n /**\n * Register a MAP agent over this mesh peer's transport.\n */\n async registerAgent(options: {\n role?: string;\n name?: string;\n serverPeerId: string;\n }): Promise<AgentConnection> {\n const { AgentConnection } = await import('../connection/agent');\n \n return AgentConnection.connectMesh({\n transport: this.transport,\n peer: { id: options.serverPeerId } as PeerEndpoint,\n localPeerId: this.peerId,\n name: options.name ?? this.peerId,\n role: options.role,\n reconnection: true,\n });\n }\n \n /**\n * Stop the mesh peer and all services.\n */\n async stop(): Promise<void> {\n if (this.git) {\n await this.git.stop();\n }\n await this.transport.stop();\n }\n}\n```\n\n---\n\n## File Changes Summary\n\n### New Files\n\n| File | Purpose |\n|------|---------|\n| `ts-sdk/src/stream/agentic-mesh.ts` | Stream adapter for agentic-mesh TunnelStream |\n| `ts-sdk/src/git/index.ts` | Git sync capability exports |\n| `ts-sdk/src/git/sync.ts` | GitSync wrapper for agentic-mesh |\n| `ts-sdk/src/mesh/index.ts` | Mesh peer exports |\n| `ts-sdk/src/mesh/peer.ts` | MAPMeshPeer convenience class |\n\n### Modified Files\n\n| File | Change |\n|------|--------|\n| `ts-sdk/src/connection/client.ts` | Add `connectMesh()` static method |\n| `ts-sdk/src/connection/agent.ts` | Add `connectMesh()` static method |\n| `ts-sdk/src/stream/index.ts` | Re-export agentic-mesh stream |\n| `ts-sdk/src/index.ts` | Export new modules |\n| `ts-sdk/package.json` | Add `agentic-mesh` as optional peer dependency |\n\n### Package.json Changes\n\n```json\n{\n \"peerDependencies\": {\n \"agentic-mesh\": \"^0.1.0\"\n },\n \"peerDependenciesMeta\": {\n \"agentic-mesh\": {\n \"optional\": true\n }\n }\n}\n```\n\n---\n\n## Usage Examples\n\n### Example 1: Client over Nebula Mesh\n\n```typescript\nimport { ClientConnection } from '@anthropic/multi-agent-protocol';\nimport { createNebulaTransport } from 'agentic-mesh';\n\n// Create Nebula transport\nconst transport = createNebulaTransport({\n configPath: '/etc/nebula/config.yml',\n certPath: '/etc/nebula/host.crt',\n keyPath: '/etc/nebula/host.key',\n});\n\n// Connect client over mesh\nconst client = await ClientConnection.connectMesh({\n transport,\n peer: { id: 'map-server', nebulaIp: '10.0.0.1', port: 4242 },\n localPeerId: 'dashboard-1',\n name: 'Dashboard',\n reconnection: true,\n});\n\n// Use as normal MAP client\nconst agents = await client.listAgents();\nconst sub = await client.subscribe({ eventTypes: ['agent.registered'] });\n```\n\n### Example 2: Agent with Git Sync\n\n```typescript\nimport { MAPMeshPeer } from '@anthropic/multi-agent-protocol/mesh';\n\n// Create mesh peer with git enabled\nconst peer = await MAPMeshPeer.create({\n peerId: 'worker-1',\n name: 'CodeProcessor',\n transport: {\n type: 'tailscale',\n config: { authKey: process.env.TAILSCALE_AUTH_KEY },\n },\n git: {\n enabled: true,\n repoPath: '/workspace/project',\n },\n});\n\n// Register as MAP agent\nconst agent = await peer.registerAgent({\n serverPeerId: 'coordinator',\n role: 'processor',\n});\n\n// Pull latest code from another worker\nawait peer.git!.pull('worker-2', 'main');\n\n// Process work...\nagent.onMessage(async (msg) => {\n await agent.busy();\n // Do work...\n \n // Push results\n await peer.git!.push('worker-2', 'results');\n await agent.idle();\n});\n```\n\n### Example 3: Standard Git CLI (via mesh://)\n\n```bash\n# With git-remote-mesh in PATH (from agentic-mesh)\ngit remote add worker-2 mesh://worker-2-id/\ngit fetch worker-2\ngit merge worker-2/main\ngit push worker-2 feature-branch\n```\n\n---\n\n## Success Criteria\n\n### Transport Integration\n- [ ] `agenticMeshStream()` creates valid MAP Stream from TunnelStream\n- [ ] `ClientConnection.connectMesh()` works with Nebula transport\n- [ ] `AgentConnection.connectMesh()` works with Tailscale transport\n- [ ] Reconnection works correctly over mesh transport\n- [ ] agentic-mesh is optional - SDK works without it installed\n\n### Git Sync\n- [ ] `createGitSync()` creates working GitSync instance\n- [ ] `sync()`, `pull()`, `push()`, `clone()` operations work\n- [ ] Large pack files stream correctly (>1MB threshold)\n- [ ] Pack integrity verified via SHA256 checksum\n\n### MAPMeshPeer\n- [ ] `MAPMeshPeer.create()` initializes transport and git service\n- [ ] `registerAgent()` creates functional AgentConnection\n- [ ] `stop()` cleanly shuts down all services\n\n### Documentation\n- [ ] JSDoc comments on all public APIs\n- [ ] README section on mesh transport usage\n- [ ] Example code in examples/ directory\n\n---\n\n## Non-Goals\n\n- **Transport implementation** - Use agentic-mesh's transports, don't reimplement\n- **Git protocol implementation** - Use agentic-mesh's GitTransportService\n- **Certificate management** - Use agentic-mesh's CertManager if needed\n- **Server-side mesh support** - Focus on client/agent side first\n\n---\n\n## Open Questions\n\n1. **Should MAPMeshPeer wrap or extend agentic-mesh's MeshPeer?**\n - Option A: Thin wrapper (current design) - less coupling\n - Option B: Direct re-export - simpler but tighter coupling\n\n2. **How to handle transport failures during git sync?**\n - Should git sync have its own retry logic?\n - Or rely on transport-level reconnection?\n\n3. **Should we support WebSocket fallback when mesh is unavailable?**\n - Could detect and fall back automatically\n - Or require explicit configuration\n\n---\n\n## Related Specs\n\n- [[s-243e]] MAP Client SDK - Transport Convenience Layer\n- [[s-7hnb]] MAP Server SDK - MAPServer Convenience Layer\n","priority":1,"archived":0,"archived_at":null,"created_at":"2026-01-31 06:42:25","updated_at":"2026-01-31 06:42:25","parent_id":null,"parent_uuid":null,"relationships":[{"from":"s-37t6","from_type":"spec","to":"s-243e","to_type":"spec","type":"related"},{"from":"s-37t6","from_type":"spec","to":"s-7hnb","to_type":"spec","type":"related"}],"tags":["agentic-mesh","federation","git-sync","p2p","transport"]}
11
- {"id":"s-6kja","uuid":"d25fa9e3-b547-45b7-bbe3-497540012d1e","title":"Pluggable Auth Providers & Agent-IAM Integration","file_path":"specs/s-6kja_pluggable_auth_providers_agent_iam_integration.md","content":"# Pluggable Auth Providers & Agent-IAM Integration\n\n## Overview\n\nThis spec defines a standardized **Auth Provider** abstraction layer for MAP that enables pluggable authentication systems beyond the existing built-in authenticators (JWT, API key, mTLS). The first implementation is **agent-iam** integration, which provides capability-based tokens with hierarchical delegation, identity binding, and federation support.\n\n### Goals\n\n1. **Standardize auth provider interface** - Define a higher-level abstraction for auth systems that manage their own token lifecycle\n2. **Integrate agent-iam** - Use agent-iam as the reference implementation for capability-based auth\n3. **Capability mapping** - Bridge external auth systems' permission models to MAP's 4-layer permission system\n4. **Federation support** - Enable cross-system authentication with token translation\n5. **Spawn delegation** - Support automatic token delegation when agents spawn children\n\n### Non-Goals\n\n- Replacing existing authenticators (JWT, API key, mTLS remain as-is)\n- Implementing a central identity provider\n- Modifying the core MAP protocol\n- Credential brokering (getting GitHub/AWS tokens) - see [[s-2w2c]] for that\n\n### Scope Boundary: Auth vs Credentials\n\nThis spec covers **authentication and capability mapping only** - i.e., verifying agent identity and determining what the agent can do within MAP. It does NOT cover credential issuance (getting actual GitHub tokens, AWS credentials, etc.), which is a separate concern handled by the Credential Extension ([[s-2w2c]]).\n\nThe two layers are connected by the agent-iam token stored on the session:\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ MAP Server │\n│ │\n│ Auth Layer (this spec) Credential Layer ([[s-2w2c]]) │\n│ ┌─────────────────────┐ ┌─────────────────────┐ │\n│ │ AgentIAMProvider │ │ CredentialExtension │ │\n│ │ │ token │ │ │\n│ │ • verify token │─────────▶│ • cred/get │ │\n│ │ • map capabilities │ (session)│ • cred/list │ │\n│ │ • delegate on spawn │ │ • cred/status │ │\n│ └─────────────────────┘ └─────────────────────┘ │\n│ │ │ │\n│ │ consumes │ consumes │\n│ ▼ ▼ │\n│ ┌─────────────────────────────────────────────────────────┐ │\n│ │ agent-iam (no changes needed) │ │\n│ │ TokenService: verify, deserialize, delegate │ │\n│ │ Broker: checkPermission, getCredential │ │\n│ │ Providers: GitHub, AWS, API Key │ │\n│ └─────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────┘\n```\n\n### agent-iam: No Changes Required\n\nagent-iam already exports everything MAP needs:\n\n| MAP needs | agent-iam already has |\n|-----------|----------------------|\n| Verify token signatures | `TokenService.verify()` |\n| Deserialize tokens from wire | `TokenService.deserialize()` |\n| Delegate tokens on spawn | `TokenService.delegate()` |\n| Token types, identity, federation | All exported from `types.ts` |\n\nThis spec is purely MAP-side work consuming agent-iam as a dependency.\n\n---\n\n## Background\n\n### Existing Auth Architecture\n\nMAP's current auth system (`ts-sdk/src/server/auth/`) provides:\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ AuthManager │\n│ - Coordinates multiple Authenticators │\n│ - Method → Authenticator routing │\n│ - Transport bypass rules │\n└─────────────────────────────────────────────────────────────────┘\n │\n ┌─────────────────────┼─────────────────────┐\n ▼ ▼ ▼\n┌──────────────┐ ┌──────────────┐ ┌──────────────┐\n│ JWT │ │ API Key │ │ mTLS │\n│Authenticator │ │Authenticator │ │Authenticator │\n└──────────────┘ └──────────────┘ └──────────────┘\n```\n\n**Key interfaces:**\n- `Authenticator` - Validates credentials, returns `AuthResult` with `AuthPrincipal`\n- `AuthManager` - Orchestrates authenticators, provides capabilities to clients\n- `AuthContext` - Transport metadata (TLS cert, remote address, etc.)\n- `AuthPrincipal` - Authenticated identity with `id`, `issuer`, `claims`, `expiresAt`\n\n### Limitation: Stateless Authenticators\n\nCurrent authenticators are stateless validators. They verify credentials but don't:\n- Map external permission models to MAP capabilities\n- Handle cross-system federation\n- Integrate with spawn/delegation workflows\n\n### agent-iam Overview\n\nagent-iam (`references/agent-iam/`) is a capability-based credential broker:\n\n```typescript\ninterface AgentToken {\n agentId: string;\n parentId?: string; // Delegation chain\n scopes: string[]; // Capability scopes\n constraints: Constraints; // Per-scope restrictions\n delegatable: boolean;\n maxDelegationDepth: number;\n currentDepth: number;\n expiresAt?: string;\n \n // MAP integration fields (optional, ignored in standalone mode)\n identity?: IdentityBinding; // Principal/tenant binding\n federation?: FederationMetadata; // Cross-system controls\n agentCapabilities?: AgentCapabilities; // Spawn, message, observe, etc.\n}\n```\n\n---\n\n## Resolved Design Decisions\n\n### 1. Token Refresh Strategy: Reconnection Only\n\nClients disconnect and reconnect with fresh credentials when tokens expire. This matches MAP's existing connection model.\n\n**Rationale:**\n- Simpler model with no additional protocol methods needed\n- MAP already supports `map/connect` with credentials\n- agent-iam tokens have explicit TTLs; clients can monitor expiry and reconnect proactively\n\n### 2. Revocation Propagation: Local Only (for now)\n\nToken revocation only affects the local system. Federated tokens remain valid until natural expiry (short TTL mitigates risk).\n\n**Rationale:**\n- Federated tokens already use short TTLs (1 day default)\n- Avoids need for a revocation sync protocol between peers\n\n**Future work:** Push-based revocation to trusted peers via a revocation sync protocol.\n\n### 3. Capability Conflict Resolution: Most Restrictive (Intersection)\n\nWhen provider-mapped capabilities conflict with server defaults, take the **intersection** (AND). If either server or provider denies a capability, the result is denied.\n\n```\nServer default: canSpawn=true, canFederate=true\nProvider maps: canSpawn=true, canFederate=false\nResult: canSpawn=true, canFederate=false\n```\n\n**Rationale:**\n- Defense in depth - two layers of restriction\n- Matches agent-iam's own delegation model (child can only narrow, never widen)\n- Server admin can enforce global policies (e.g., \"no federation on this server\")\n- Fails closed - safe default\n\n### 4. Multi-Provider Sessions: Allowed\n\nA session can authenticate with multiple providers. Capabilities are merged using the same intersection strategy.\n\n**Rationale:**\n- Enables layered auth (e.g., agent-iam for capabilities + API key for identity)\n- Each provider's capabilities are intersected with the running total\n- Primary provider (first authenticated) is used for spawn delegation\n\n**Implementation:**\n- `session.providers: Map<string, { principal, providerData }>`\n- Capability mapping merges across all providers via intersection\n\n### 5. Auth vs Credential Separation\n\nAuthentication (verifying tokens, mapping capabilities) and credential brokering (issuing GitHub/AWS tokens) are separate concerns with separate interfaces.\n\n**Rationale:**\n- Not all auth providers broker credentials (JWT, mTLS don't)\n- Not all credential brokers need MAP auth (agent-iam works standalone)\n- Different lifecycle: auth at connect time, credentials repeatedly during session\n- Composable: agent-iam auth + custom credential broker, or OAuth2 auth + agent-iam credentials\n- The token stored on the session is the bridge between the two layers\n\n---\n\n## Design\n\n### 1. Auth Provider Interface\n\n```typescript\ninterface AuthProvider extends Authenticator {\n readonly providerId: string;\n mapCapabilities?(principal: AuthPrincipal, providerData: unknown): CapabilityMapping;\n delegateForSpawn?(parentPrincipal: AuthPrincipal, parentProviderData: unknown, spawnRequest: SpawnDelegationRequest): Promise<DelegatedCredentials>;\n handleFederatedToken?(sourceSystemId: string, token: unknown, context: FederationContext): Promise<FederationResult>;\n prepareFederatedToken?(principal: AuthPrincipal, providerData: unknown, targetSystemId: string): Promise<{ token: string; allowed: boolean; reason?: string }>;\n}\n\ninterface CapabilityMapping {\n participantCapabilities: Partial<ParticipantCapabilities>;\n defaultAgentPermissions?: Partial<AgentPermissions>;\n additionalClaims?: Record<string, unknown>;\n}\n\ninterface SpawnDelegationRequest {\n childAgentId: string;\n requestedScopes?: string[];\n requestedCapabilities?: Partial<AgentCapabilities>;\n ttlMinutes?: number;\n inheritIdentity?: boolean;\n}\n\ninterface DelegatedCredentials {\n method: AuthMethod;\n credentials: Record<string, unknown>;\n env?: Record<string, string>;\n}\n\ninterface FederationContext { localSystemId: string; trustConfig?: FederationTrustConfig; }\ninterface FederationResult { allowed: boolean; principal?: AuthPrincipal; providerData?: unknown; reason?: string; }\n```\n\n### 2. Agent-IAM Auth Provider\n\n```typescript\n// ts-sdk/src/server/auth/providers/agent-iam.ts\n// Consumes: TokenService from agent-iam (verify, deserialize, delegate)\n\nexport class AgentIAMProvider implements AuthProvider {\n readonly providerId = 'agent-iam';\n readonly methods = ['x-agent-iam'] as const;\n \n // authenticate(): deserialize → verify → validate identity/tenant/federation → build principal\n // mapCapabilities(): delegates to AgentIAMCapabilityMapper\n // delegateForSpawn(): tokenService.delegate() with attenuated scopes → serialize → return credentials + env\n // handleFederatedToken(): delegates to AgentIAMFederationGateway\n}\n```\n\n### 3. Capability Mapper\n\n```typescript\n// ts-sdk/src/server/auth/providers/agent-iam-mapper.ts\n\nexport class AgentIAMCapabilityMapper {\n mapToParticipantCapabilities(token: AgentToken): Partial<ParticipantCapabilities>;\n mapToAgentPermissions(token: AgentToken): Partial<AgentPermissions>;\n // Configurable scope mappings (e.g., 'map:observe:*' → observation capability)\n // agentCapabilities override scope-based inference when present\n}\n```\n\n### 4. Capability Intersection\n\n```typescript\n// ts-sdk/src/server/auth/capabilities.ts\n\nfunction and(a?: boolean, b?: boolean): boolean; // undefined = \"no opinion\"\nfunction intersectCapabilities(base: ParticipantCapabilities, overlay: Partial<ParticipantCapabilities>): ParticipantCapabilities;\nfunction intersectAgentPermissions(base: AgentPermissions, overlay: Partial<AgentPermissions>): AgentPermissions;\n```\n\n### 5. AuthManager Updates\n\n- Support `providers[]` in options alongside `authenticators[]`\n- Providers register as authenticators for their methods\n- `getCapabilityMapping(authResult)` → looks up provider, calls `mapCapabilities()`\n- `delegateForSpawn(session, request)` → uses primary provider\n- Multi-provider: `session.providers` map\n\n### 6. Handler Integration\n\n- **map/connect**: store provider data → get capability mapping → intersect with server defaults\n- **map/agents/spawn**: check canSpawn → delegate credentials → pass env to subprocess\n\n### 7. Federation Gateway\n\n```typescript\n// ts-sdk/src/server/auth/providers/agent-iam-federation.ts\n\nexport class AgentIAMFederationGateway {\n // Verify incoming tokens from trusted peers\n // Check federation metadata (crossSystemAllowed, hopCount, allowedSystems)\n // Translate scopes via peer scope mapping\n // Mint short-TTL local token with federated identity\n}\n```\n\n---\n\n## File Changes\n\n```\nts-sdk/src/server/auth/\n ├── types.ts ← modify: add AuthProvider, CapabilityMapping, etc.\n ├── capabilities.ts ← new: intersectCapabilities(), intersectAgentPermissions()\n ├── manager.ts ← modify: support providers[], multi-provider sessions\n └── providers/\n ├── agent-iam.ts ← new: AgentIAMProvider\n ├── agent-iam-mapper.ts ← new: AgentIAMCapabilityMapper\n └── agent-iam-federation.ts ← new: AgentIAMFederationGateway\n\nts-sdk/src/server/router/\n └── handlers.ts ← modify: connect + spawn handler integration\n```\n\n---\n\n## E2E Lifecycle\n\n```\nHuman creates root token (via agent-iam Broker)\n │ scopes: ['map:*', 'github:repo:write'], identity: alice@acme.com\n ▼\nCoordinator connects ── map/connect { method: 'x-agent-iam', token: '...' }\n │ → verify → map capabilities → intersect with server defaults → store on session\n ▼\nCoordinator spawns ── map/agents/spawn { agentId: 'git-worker', scopes: [...] }\n │ → delegate token (attenuated) → serialize → pass via AGENT_TOKEN env\n ▼\nGit Worker connects with delegated token (narrower capabilities)\n │ → same auth flow, fewer permissions\n ▼\nGit Worker needs credentials → cred/get (see [[s-2w2c]])\n```\n\n---\n\n## Security Considerations\n\n- **Token validation**: Always verify HMAC signature before trusting claims; check expiry\n- **Capability intersection**: Most restrictive (AND) ensures defense in depth\n- **Federation**: Separate signing keys per system; hop count limits; short TTLs\n- **Multi-provider**: Each provider's capabilities independently intersected; no escalation possible\n\n---\n\n## Implementation Phases\n\n### Phase 1: Auth Provider Interface & Capability Intersection\n- [ ] Define `AuthProvider` interface, `CapabilityMapping`, `SpawnDelegationRequest`, `DelegatedCredentials`, `FederationContext`, `FederationResult` types\n- [ ] Implement `intersectCapabilities()` and `intersectAgentPermissions()`\n- [ ] Update `AuthManager` to support `providers[]` with multi-provider sessions\n\n### Phase 2: Agent-IAM Provider\n- [ ] Implement `AgentIAMProvider` (authenticate, mapCapabilities, delegateForSpawn)\n- [ ] Implement `AgentIAMCapabilityMapper`\n- [ ] Token validation: signature, expiry, identity, tenant, federation\n\n### Phase 3: Handler Integration\n- [ ] Update `map/connect` handler: capability mapping + intersection\n- [ ] Update `map/agents/spawn` handler: delegate credentials to child\n- [ ] Add `session.providers` map\n\n### Phase 4: Federation Gateway\n- [ ] Implement `AgentIAMFederationGateway`\n- [ ] Trusted peer config, scope translation, short-TTL local tokens\n\n### Phase 5: Testing\n- [ ] Unit: intersection logic, provider auth, capability mapper, federation gateway\n- [ ] Integration: connect → capabilities, spawn → delegation, multi-provider, federation\n\n---\n\n## Future Work\n\n- **Revocation sync**: Push-based revocation to trusted peers\n- **Configurable resolution**: `'most-restrictive' | 'provider-wins' | 'server-wins'`\n- **Audit logging**: Structured logging for auth/federation events\n\n---\n\n## References\n\n- [[s-65aw]] MAP Extension System\n- [[s-2w2c]] Credential Brokering Extension (companion spec)\n- `references/agent-iam/` - agent-iam (consumed as dependency, no changes)\n- `docs/agent-iam-integration.md` - Original draft spec\n- `ts-sdk/src/server/auth/` - Existing MAP auth","priority":1,"archived":0,"archived_at":null,"created_at":"2026-02-04 03:34:46","updated_at":"2026-02-05 20:14:47","parent_id":null,"parent_uuid":null,"relationships":[{"from":"s-6kja","from_type":"spec","to":"s-2w2c","to_type":"spec","type":"related"},{"from":"s-6kja","from_type":"spec","to":"s-65aw","to_type":"spec","type":"references"}],"tags":["agent-iam","auth","federation","p1","security"]}
12
- {"id":"s-2w2c","uuid":"41285f26-9e0e-466d-bdb9-ea29b9f74a77","title":"Credential Brokering (Core Optional Capability)","file_path":"specs/s-2w2c_credential_brokering_extension.md","content":"# Credential Brokering (Core Optional Capability)\n\n## Overview\n\nThis spec defines **credential brokering as a core but optional MAP capability** - following the same pattern as mail. Server operators enable it via configuration, and the capability is advertised in the connect response. Agents can then request external service credentials (GitHub tokens, AWS credentials, API keys) through `cred/*` method calls.\n\nThe capability wraps agent-iam's `Broker` and provider system, exposing it as `cred/*` methods that leverage the authenticated session for authorization.\n\n### Why Core-but-Optional (Not Extension)\n\n| Aspect | Extension | Core-Optional |\n|--------|-----------|---------------|\n| Discovery | Negotiation protocol | Advertised in connect |\n| Handler registration | Dynamic at runtime | Static handler factory |\n| Type safety | Looser | Full TypeScript coverage |\n| Implementation complexity | Higher | Lower (mail already proves pattern) |\n| Adoption friction | Higher (install extension) | Lower (enable config flag) |\n| Blocking dependencies | Needs extension system (s-65aw) | None - can implement now |\n\nFollowing mail's proven pattern provides simpler implementation and better developer experience.\n\n### Goals\n\n1. **Expose credential brokering via MAP methods** - Agents request credentials through `cred/get`, `cred/list`, `cred/status`\n2. **No secret sharing** - Provider secrets (GitHub App keys, AWS role ARNs) live only on the server; agents only see short-lived scoped credentials\n3. **Token-based authorization** - Credential requests are authorized by checking the agent-iam token already verified and stored on the session by the auth layer ([[s-6kja]])\n4. **Leverage existing agent-iam providers** - No changes to agent-iam; consume `Broker.getCredential()` and `Broker.checkPermission()` as-is\n\n### Non-Goals\n\n- Implementing new credential providers (agent-iam already has GitHub, AWS, Google, API Key)\n- Token verification or capability mapping (handled by auth layer [[s-6kja]])\n- Modifying the agent-iam library\n\n### Relationship to Auth Layer\n\nThis spec and [[s-6kja]] are companion specs that together provide complete agent-iam integration with MAP:\n\n```\n┌───────────────────────────────────────────────────────────────┐\n│ MAP Server │\n│ │\n│ [[s-6kja]] Auth Layer [[this spec]] Credential Layer │\n│ ┌───────────────────┐ ┌───────────────────┐ │\n│ │ AgentIAMProvider │ │ CredentialHandlers │ │\n│ │ │ token │ │ │\n│ │ • verify token │────────▶│ • cred/get │ │\n│ │ • map caps │(session)│ • cred/list │ │\n│ │ • delegate spawn │ │ • cred/status │ │\n│ └───────────────────┘ └────────┬──────────┘ │\n│ │ │\n│ │ consumes │\n│ ▼ │\n│ ┌───────────────────┐ │\n│ │ agent-iam Broker │ │\n│ │ (no changes) │ │\n│ │ • checkPermission │ │\n│ │ • getCredential │ │\n│ │ • GitHub Provider │ │\n│ │ • AWS Provider │ │\n│ │ • API Key Provider│ │\n│ └───────────────────┘ │\n└───────────────────────────────────────────────────────────────┘\n```\n\n- **Auth layer** handles connection-time authentication, capability mapping, and spawn delegation\n- **Credential layer** handles runtime credential issuance for external services\n- **Bridge**: The agent-iam token stored on `session.authMetadata` by the auth layer\n\n### Why Separate from Auth\n\n1. **Not all auth providers broker credentials** - JWT and mTLS authenticators don't issue GitHub tokens\n2. **Not all credential users need MAP auth** - agent-iam works standalone\n3. **Different lifecycle** - Auth happens at connect time; credentials are requested repeatedly during the session\n4. **Composable** - Mix any auth provider with any credential broker\n\n---\n\n## Dependencies\n\n- **[[s-6kja]] Pluggable Auth Providers** - Provides the authenticated session with agent-iam token stored on `session.authMetadata`\n- **agent-iam** (external, no changes) - Provides `Broker`, `TokenService`, and credential providers\n\n**Note**: No longer depends on extension system (s-65aw) - following core-optional pattern instead.\n\n---\n\n## Design\n\n### 1. Capability Config (Following Mail Pattern)\n\n```typescript\n// ts-sdk/src/server/credentials/types.ts\n\nimport type { Broker } from 'agent-iam';\n\n/**\n * Configuration for credential brokering capability.\n * Follows the same pattern as MailCapabilityConfig.\n */\nexport interface CredentialCapabilityConfig {\n /** Whether credential brokering is enabled on this server */\n enabled: boolean;\n \n /** Configured agent-iam Broker instance (holds provider secrets) */\n broker: Broker;\n \n /** Whether to emit audit events for credential requests (default: true) */\n auditEvents?: boolean;\n \n /** Allowed providers - if set, only these providers are available (default: all configured) */\n allowedProviders?: string[];\n \n /** Can request credentials (default: true) */\n canGet?: boolean;\n \n /** Can list available scopes (default: true) */\n canList?: boolean;\n \n /** Can check broker status (default: true) */\n canStatus?: boolean;\n}\n```\n\n### 2. Handler Factory\n\n```typescript\n// ts-sdk/src/server/credentials/handlers.ts\n\nimport type { Broker, AgentToken } from 'agent-iam';\nimport type { HandlerContext, HandlerRegistry } from '../types';\n\nexport interface CredentialHandlerOptions {\n broker: Broker;\n auditEvents?: boolean;\n allowedProviders?: string[];\n}\n\n/**\n * Create handlers for credential brokering methods.\n * \n * Methods:\n * - `cred/get` - Request a short-lived credential\n * - `cred/list` - List available scopes for current token\n * - `cred/status` - Check broker status and providers\n */\nexport function createCredentialHandlers(\n options: CredentialHandlerOptions\n): HandlerRegistry {\n const { broker, auditEvents = true, allowedProviders } = options;\n \n return {\n 'cred/get': async (params: CredGetParams, ctx: HandlerContext) => {\n const token = getTokenFromSession(ctx);\n \n // Check permission using agent-iam's scope + constraint checking\n const check = broker.checkPermission(token, params.scope, params.resource);\n \n if (!check.valid) {\n if (auditEvents && ctx.emit) {\n ctx.emit('credential.denied', {\n agentId: token.agentId,\n scope: params.scope,\n resource: params.resource,\n reason: check.error,\n });\n }\n throw new Error(check.error ?? 'Permission denied');\n }\n \n // Get actual credential from provider\n const credential = await broker.getCredential(\n token,\n params.scope,\n params.resource\n );\n \n if (auditEvents && ctx.emit) {\n ctx.emit('credential.issued', {\n agentId: token.agentId,\n scope: params.scope,\n resource: params.resource,\n credentialType: credential.credentialType,\n expiresAt: credential.expiresAt,\n // Never log the actual credential\n });\n }\n \n return credential;\n },\n \n 'cred/list': async (_params: unknown, ctx: HandlerContext) => {\n const token = getTokenFromSession(ctx);\n \n return {\n agentId: token.agentId,\n scopes: token.scopes,\n constraints: token.constraints,\n expiresAt: token.expiresAt,\n };\n },\n \n 'cred/status': async (_params: unknown, _ctx: HandlerContext) => {\n const status = broker.getStatus();\n \n // Filter to allowed providers if configured\n const providers = allowedProviders\n ? status.configuredProviders.filter(p => allowedProviders.includes(p))\n : status.configuredProviders;\n \n return {\n configuredProviders: providers,\n // Don't expose sensitive config details\n };\n },\n };\n}\n\nfunction getTokenFromSession(ctx: HandlerContext): AgentToken {\n const token = ctx.session.authMetadata?.agentIamToken;\n if (!token) {\n throw new Error(\n 'No agent-iam token on session. Authenticate with x-agent-iam first.'\n );\n }\n return token;\n}\n\ninterface CredGetParams {\n /** Scope to request (e.g., 'github:repo:write') */\n scope: string;\n /** Resource to access (e.g., 'acme-corp/frontend') */\n resource: string;\n}\n```\n\n### 3. Integration with Connection Handlers\n\nFollowing the mail pattern, update ConnectionHandlerOptions:\n\n```typescript\n// In ts-sdk/src/server/router/handlers.ts\n\nexport interface ConnectionHandlerOptions {\n sessions: SessionManager;\n agents?: AgentRegistry;\n subscriptions?: SubscriptionManager;\n scopes?: ScopeManager;\n serverName?: string;\n serverVersion?: string;\n authManager?: AuthManager;\n mailCapabilities?: MailCapabilityConfig;\n credentialCapabilities?: CredentialCapabilityConfig; // Add this\n}\n\n// In map/connect handler, advertise credential capabilities:\nif (credentialCapabilities?.enabled) {\n capabilities.credentials = {\n enabled: true,\n canGet: credentialCapabilities.canGet ?? true,\n canList: credentialCapabilities.canList ?? true,\n canStatus: credentialCapabilities.canStatus ?? true,\n providers: credentialCapabilities.allowedProviders ?? 'all',\n };\n}\n```\n\n### 4. Server Setup\n\n```typescript\nimport { Broker } from 'agent-iam';\nimport { AgentIAMProvider } from 'map-protocol/server/auth/providers/agent-iam';\nimport { createCredentialHandlers } from 'map-protocol/server/credentials';\nimport { combineHandlers, createConnectionHandlers } from 'map-protocol/server/router';\n\n// One Broker instance holds all secrets\nconst broker = new Broker(configDir);\n\n// Auth layer: uses broker's TokenService for verification\nconst authProvider = new AgentIAMProvider({\n tokenService: broker.tokenService,\n systemId: 'my-system',\n});\n\n// Credential handlers\nconst credentialHandlers = createCredentialHandlers({\n broker,\n auditEvents: true,\n});\n\n// Combine all handlers\nconst handlers = combineHandlers(\n createConnectionHandlers({\n sessions,\n authManager,\n credentialCapabilities: { enabled: true, broker }, // Advertise capability\n }),\n createAgentHandlers({ ... }),\n credentialHandlers, // Add credential handlers\n);\n```\n\nBoth layers share the same `Broker` instance, but use different parts:\n- Auth layer → `broker.tokenService` (verify, delegate)\n- Credential layer → `broker.getCredential()`, `broker.checkPermission()` (full broker)\n\n---\n\n## E2E Lifecycle: Credential Request\n\nBuilding on the auth lifecycle from [[s-6kja]]:\n\n```\nGit Worker (already connected via x-agent-iam, token on session)\n │\n │── cred/get ──────────────────────────────▶ MAP Server\n │ { scope: 'github:repo:write', │\n │ resource: 'acme-corp/frontend' } │\n │ │\n │ ┌─────────────┤\n │ │ 1. Read token from session.authMetadata\n │ │ 2. broker.checkPermission(token, scope, resource)\n │ │ - Check: has 'github:repo:write' scope? ✓\n │ │ - Check: resource matches constraint? ✓\n │ │ 3. broker.getCredential(token, scope, resource)\n │ │ - GitHub Provider:\n │ │ → Create installation token via GitHub App API\n │ │ → Scope to repo: acme-corp/frontend\n │ │ → Permissions: contents:write\n │ │ 4. Emit 'credential.issued' audit event\n │ └─────────────┤\n │ │\n │◀── credential ───────────────────────────────────│\n │ { credentialType: 'bearer_token', │\n │ credential: { token: 'ghs_xxxx...' }, │\n │ expiresAt: '2026-02-05T20:00:00Z' } │\n │\n │── git push (using ghs_xxxx token) ──────────────▶ GitHub API\n```\n\n### What Agents Never See\n\n- GitHub App private key\n- HMAC signing secret\n- AWS role ARN / access keys\n- Provider configuration\n- Other agents' credentials\n\nAgents only see their token (scoped, signed, time-limited) and short-lived credentials for specific resources.\n\n---\n\n## Security Considerations\n\n### Credential Scoping\n- Credentials are scoped to the exact resource requested, validated against token constraints\n- `broker.checkPermission()` enforces resource glob patterns, time windows, and usage limits\n\n### Audit Trail\n- `credential.issued` and `credential.denied` events provide full audit trail\n- Events include agentId, scope, resource - never the credential itself\n- Events are filterable via MAP's subscription system\n\n### Secret Isolation\n- All provider secrets live only on the server in the Broker instance\n- Agents authenticate via tokens (no secret material)\n- Credentials returned are short-lived (minutes to hours, provider-dependent)\n\n### Token Revalidation\n- Each `cred/get` call revalidates the token via `broker.checkPermission()`\n- Catches: expired tokens, exhausted maxUses, time window violations\n- Token on session may have been valid at connect time but expired since\n\n---\n\n## File Changes\n\n```\nts-sdk/src/server/credentials/\n ├── index.ts ← new: exports\n ├── types.ts ← new: CredentialCapabilityConfig\n └── handlers.ts ← new: createCredentialHandlers()\n\nts-sdk/src/server/router/\n └── handlers.ts ← modify: add credentialCapabilities to ConnectionHandlerOptions\n advertise in connect response\n```\n\n---\n\n## Implementation Phases\n\n### Phase 1: Types & Handler Factory\n- [ ] Create `CredentialCapabilityConfig` interface\n- [ ] Create `createCredentialHandlers()` factory with `cred/get`, `cred/list`, `cred/status`\n- [ ] Token extraction from session (`session.authMetadata.agentIamToken`)\n- [ ] Permission checking via `broker.checkPermission()`\n- [ ] Credential issuance via `broker.getCredential()`\n\n### Phase 2: Integration with Connection Handlers\n- [ ] Add `credentialCapabilities` to `ConnectionHandlerOptions`\n- [ ] Advertise credential capabilities in `map/connect` response (following mail pattern)\n\n### Phase 3: Audit Events\n- [ ] Emit `credential.issued` event on successful credential issuance\n- [ ] Emit `credential.denied` event on permission denial\n- [ ] Ensure credentials are never included in events\n\n### Phase 4: Testing\n- [ ] Unit: `cred/get` with valid token and scope → credential returned\n- [ ] Unit: `cred/get` with missing scope → denied\n- [ ] Unit: `cred/get` with resource constraint violation → denied\n- [ ] Unit: `cred/get` with expired token → denied\n- [ ] Unit: `cred/list` returns token scopes and constraints\n- [ ] Unit: `cred/status` returns provider status without secrets\n- [ ] Unit: no agent-iam token on session → auth error\n- [ ] Integration: connect with agent-iam → cred/get → use credential\n- [ ] Integration: delegated child token → cred/get with narrower scopes\n- [ ] Integration: connect response includes credential capabilities when enabled\n\n---\n\n## Future Work\n\n- **Credential caching**: Cache credentials on the server to reduce provider API calls for repeated requests\n- **Rate limiting**: Per-agent rate limits on credential requests\n- **Credential refresh**: Proactive refresh before expiry for long-running operations\n- **Additional providers**: agent-iam provider system is extensible; new providers (Azure, GCP, etc.) can be added without MAP changes\n- **Extension migration**: If extension system (s-65aw) is implemented and gains traction, credential brokering could optionally be migrated to an extension for servers that prefer dynamic capability negotiation\n\n---\n\n## References\n\n- [[s-6kja]] Pluggable Auth Providers & Agent-IAM Integration (companion spec)\n- `ts-sdk/src/server/router/handlers.ts` - MailCapabilityConfig pattern to follow\n- `references/agent-iam/` - agent-iam implementation (consumed as dependency, no changes)\n- `references/agent-iam/src/broker.ts` - Broker.getCredential() and checkPermission()\n- `references/agent-iam/src/providers/` - GitHub, AWS, Google, API Key providers\n","priority":1,"archived":0,"archived_at":null,"created_at":"2026-02-05 20:13:00","updated_at":"2026-02-06 01:21:04","parent_id":null,"parent_uuid":null,"relationships":[{"from":"s-2w2c","from_type":"spec","to":"s-65aw","to_type":"spec","type":"depends-on"},{"from":"s-2w2c","from_type":"spec","to":"s-6kja","to_type":"spec","type":"depends-on"}],"tags":["agent-iam","auth","core-optional","credentials","p1"]}
13
- {"id":"s-1sz9","uuid":"ffc2cc86-2ba7-46a3-bf1c-e01debe56095","title":"MAP Mail Protocol - Integration Guide","file_path":"specs/s-1sz9_map_mail_protocol_integration_guide.md","content":"# MAP Mail Protocol - Integration Guide\n\n## Overview\n\nThis guide describes how agents, clients, orchestrators, and server implementations integrate with the MAP Mail Protocol. Mail is designed for **progressive adoption** - agents can participate in conversations with zero code changes, and adopt richer features incrementally.\n\n---\n\n## Integration Levels\n\n### Level 0: Unaware Agent (Zero Changes)\n\nAgents that don't know about mail work exactly as before. If an orchestrator sends them a message with `mail` meta, the agent:\n\n- Receives the message normally via `onMessage()`\n- Ignores the `mail` key in meta (unknown fields are ignored per MAP spec)\n- Replies normally via `send()` or `reply()`\n\nThe reply will NOT be recorded as a conversation turn unless the agent forwards the `mail` meta. The orchestrator is responsible for tracking the conversation.\n\n```typescript\nconst agent = new AgentConnection(stream);\nawait agent.connect({ agentId: 'worker-1' });\n\n// Works exactly as before - no mail awareness needed\nagent.onMessage(async (message) => {\n const result = await doWork(message.payload);\n await agent.reply(message, { result });\n});\n```\n\n### Level 1: Pass-Through Agent (Minimal Change)\n\nAn agent that forwards the `mail` meta from incoming messages to its replies. This ensures its replies are recorded as conversation turns without the agent needing to understand mail.\n\n**This is the recommended minimum integration level.**\n\n```typescript\nagent.onMessage(async (message) => {\n const result = await doWork(message.payload);\n \n // Forward mail context if present - one line change\n await agent.reply(message, { result }, {\n mail: message.meta?.mail\n });\n});\n```\n\nWhat happens:\n- If the incoming message had `mail` meta, the reply is recorded as a turn\n- If the incoming message had no `mail` meta, `undefined` is passed and nothing changes\n- The agent doesn't import any mail types\n\n### Level 2: Conversation-Aware Agent\n\nAn agent that knows its conversation ID and can add turns explicitly. Useful for agents that want to record observations, status updates, or results.\n\n```typescript\nagent.onMessage(async (message) => {\n const convId = message.meta?.mail?.conversationId;\n \n // Record a status event (not routed, just recorded)\n if (convId) {\n await agent.request('mail/turn', {\n conversationId: convId,\n contentType: 'event',\n content: { event: 'analysis.started', dataset: 'Q4' }\n });\n }\n \n const result = await doWork(message.payload);\n \n // Record completion event\n if (convId) {\n await agent.request('mail/turn', {\n conversationId: convId,\n contentType: 'event',\n content: { event: 'analysis.completed', durationMs: 3400 }\n });\n }\n \n // Send results back (routed + recorded)\n await agent.reply(message, { result }, {\n mail: message.meta?.mail\n });\n});\n```\n\n### Level 3: Orchestrator Agent (Full Integration)\n\nAn orchestrator creates and manages conversations, coordinates agents, and tracks the full interaction lifecycle.\n\n```typescript\nconst orchestrator = new AgentConnection(stream);\nconst connectResult = await orchestrator.connect({\n agentId: 'orchestrator',\n capabilities: {\n mail: { canCreate: true, canInvite: true, canViewHistory: true }\n }\n});\n\nconst mailEnabled = connectResult.capabilities?.mail?.enabled;\n\nasync function handleUserRequest(userMessage: string) {\n // 1. Create conversation\n const conv = await orchestrator.request('mail/create', {\n type: 'user-session',\n subject: userMessage.slice(0, 80),\n initialParticipants: [\n { id: 'researcher', role: 'worker' },\n { id: 'writer', role: 'worker' }\n ]\n });\n const convId = conv.conversation.id;\n \n // 2. Record user message\n await orchestrator.request('mail/turn', {\n conversationId: convId,\n contentType: 'text',\n content: { text: userMessage }\n });\n \n // 3. Delegate to researcher (routed + recorded)\n await orchestrator.send(\n { agent: 'researcher' },\n { task: 'research', query: userMessage },\n { mail: { conversationId: convId } }\n );\n \n // 4. Wait for result via message handler\n // (researcher replies with mail meta, reply is recorded as turn)\n \n // 5. Delegate to writer (routed + recorded)\n await orchestrator.send(\n { agent: 'writer' },\n { task: 'summarize', data: researchResults },\n { mail: { conversationId: convId } }\n );\n \n // 6. Record final answer for user\n await orchestrator.request('mail/turn', {\n conversationId: convId,\n contentType: 'text',\n content: { text: finalAnswer }\n });\n \n // 7. Close conversation\n await orchestrator.request('mail/close', {\n conversationId: convId,\n status: 'completed',\n generateSummary: true\n });\n}\n```\n\n### Level 4: Observer Client (Dashboard/UI)\n\nA client that doesn't participate in conversations but watches them for display.\n\n```typescript\nconst client = new ClientConnection(stream);\nawait client.connect({\n capabilities: {\n mail: { canViewHistory: true }\n }\n});\n\n// List active conversations\nconst convs = await client.request('mail/list', {\n filter: { status: ['active'] }\n});\n\n// Watch a specific conversation\nconst sub = await client.subscribe({\n eventTypes: ['mail.turn.added', 'mail.participant.joined', 'mail.participant.left'],\n mail: { conversationId: 'conv-001' }\n});\n\nfor await (const event of sub) {\n switch (event.type) {\n case 'mail.turn.added': {\n const { turn } = event.data;\n renderTurn(turn);\n break;\n }\n case 'mail.participant.joined': {\n const { participant } = event.data;\n renderParticipantJoined(participant);\n break;\n }\n }\n}\n\n// Query full history for a conversation\nconst history = await client.request('mail/turns/list', {\n conversationId: 'conv-001',\n order: 'asc'\n});\n```\n\n---\n\n## Server Implementation Guide\n\n### Minimum Viable Implementation\n\nA server that supports mail needs:\n\n1. **Storage** for conversations, participants, turns, and threads\n2. **Turn recording hook** in the `map/send` handler\n3. **Method handlers** for `mail/*` methods\n4. **Event emission** for `mail.*` events\n5. **Capability advertisement** in connect response\n\n### 1. Storage Layer\n\n```typescript\ninterface MailStore {\n // Conversations\n createConversation(conv: Conversation): Promise<void>;\n getConversation(id: ConversationId): Promise<Conversation | null>;\n listConversations(filter: MailListFilter, limit: number, cursor?: string): Promise<{ conversations: Conversation[]; nextCursor?: string }>;\n updateConversation(id: ConversationId, updates: Partial<Conversation>): Promise<void>;\n \n // Participants\n addParticipant(convId: ConversationId, participant: ConversationParticipant): Promise<void>;\n removeParticipant(convId: ConversationId, participantId: ParticipantId): Promise<void>;\n getParticipants(convId: ConversationId): Promise<ConversationParticipant[]>;\n isParticipant(convId: ConversationId, participantId: ParticipantId): Promise<boolean>;\n \n // Turns\n addTurn(turn: Turn): Promise<void>;\n getTurn(id: TurnId): Promise<Turn | null>;\n listTurns(convId: ConversationId, filter: TurnFilter, limit: number, order: 'asc' | 'desc'): Promise<{ turns: Turn[]; nextCursor?: string }>;\n \n // Threads\n createThread(thread: Thread): Promise<void>;\n listThreads(convId: ConversationId): Promise<Thread[]>;\n}\n```\n\nAn in-memory implementation is sufficient for development and testing. Production implementations would use a database.\n\n### 2. Turn Recording in map/send\n\nAdd turn recording to the existing `map/send` handler chain:\n\n```typescript\n// In server request handling\nasync handleSend(params: SendRequestParams, ctx: RequestContext): Promise<SendResponseResult> {\n // Existing: build and route the message\n const message = buildMessage(params, ctx);\n const result = await this.router.route(message);\n \n // New: record turn if mail meta present\n if (this.mailStore && params.meta?.mail) {\n const { conversationId, threadId, inReplyTo, visibility } = params.meta.mail;\n \n try {\n const conv = await this.mailStore.getConversation(conversationId);\n if (!conv) {\n this.logger.warn('Mail: conversation not found', { conversationId });\n } else if (conv.status !== 'active') {\n this.logger.warn('Mail: conversation not active', { conversationId, status: conv.status });\n } else {\n const turn: Turn = {\n id: generateTurnId(),\n conversationId,\n participant: ctx.participantId,\n timestamp: Date.now(),\n contentType: 'data',\n content: params.payload,\n threadId,\n inReplyTo,\n visibility: visibility ?? { type: 'all' },\n source: { type: 'intercepted', messageId: message.id },\n };\n \n await this.mailStore.addTurn(turn);\n \n this.eventBus.emit({\n type: 'mail.turn.added',\n data: { conversationId, turn },\n });\n }\n } catch (err) {\n // Never fail the send due to mail errors\n this.logger.warn('Mail: turn recording failed', { err, messageId: message.id });\n }\n }\n \n return result;\n}\n```\n\n### 3. Method Handlers\n\nRegister handlers for each `mail/*` method:\n\n```typescript\n// Method registration\nserver.registerMethod('mail/create', async (params: MailCreateRequest, ctx) => {\n assertCapability(ctx, 'mail', 'canCreate');\n \n const conversation: Conversation = {\n id: generateConversationId(),\n type: params.type ?? 'mixed',\n status: 'active',\n subject: params.subject,\n participantCount: 1,\n parentConversationId: params.parentConversationId,\n parentTurnId: params.parentTurnId,\n createdAt: Date.now(),\n updatedAt: Date.now(),\n createdBy: ctx.participantId,\n metadata: params.metadata,\n };\n \n await store.createConversation(conversation);\n \n // Add creator as participant\n const participant: ConversationParticipant = {\n id: ctx.participantId,\n type: ctx.participantType,\n role: 'initiator',\n joinedAt: Date.now(),\n permissions: defaultPermissions('initiator'),\n };\n await store.addParticipant(conversation.id, participant);\n \n // Add initial participants if specified\n if (params.initialParticipants) {\n for (const p of params.initialParticipants) {\n await store.addParticipant(conversation.id, {\n id: p.id,\n type: 'agent',\n role: p.role ?? 'worker',\n joinedAt: Date.now(),\n permissions: { ...defaultPermissions(p.role ?? 'worker'), ...p.permissions },\n });\n }\n }\n \n // Record initial turn if provided\n let initialTurn: Turn | undefined;\n if (params.initialTurn) {\n initialTurn = {\n id: generateTurnId(),\n conversationId: conversation.id,\n participant: ctx.participantId,\n timestamp: Date.now(),\n contentType: params.initialTurn.contentType,\n content: params.initialTurn.content,\n source: { type: 'explicit', method: 'mail/turn' },\n visibility: params.initialTurn.visibility ?? { type: 'all' },\n };\n await store.addTurn(initialTurn);\n }\n \n eventBus.emit({ type: 'mail.created', data: { conversationId: conversation.id, conversation } });\n \n return { conversation, participant, initialTurn };\n});\n\nserver.registerMethod('mail/turn', async (params: MailTurnRequest, ctx) => {\n const conv = await store.getConversation(params.conversationId);\n if (!conv) throw mailError(10000, 'Conversation not found');\n if (conv.status !== 'active') throw mailError(10001, 'Conversation closed');\n \n const isParticipant = await store.isParticipant(params.conversationId, ctx.participantId);\n if (!isParticipant) throw mailError(10002, 'Not a participant');\n \n const turn: Turn = {\n id: generateTurnId(),\n conversationId: params.conversationId,\n participant: ctx.participantId,\n timestamp: Date.now(),\n contentType: params.contentType,\n content: params.content,\n threadId: params.threadId,\n inReplyTo: params.inReplyTo,\n source: { type: 'explicit', method: 'mail/turn' },\n visibility: params.visibility ?? { type: 'all' },\n metadata: params.metadata,\n };\n \n await store.addTurn(turn);\n \n eventBus.emit({\n type: 'mail.turn.added',\n data: { conversationId: params.conversationId, turn },\n });\n \n return { turn };\n});\n\n// ... similar handlers for mail/get, mail/list, mail/join, mail/leave, etc.\n```\n\n### 4. Capability Advertisement\n\nIn the connect handler, advertise mail support:\n\n```typescript\nasync handleConnect(params: ConnectRequestParams, ctx): Promise<ConnectResponseResult> {\n // Existing connect logic...\n \n return {\n protocolVersion: PROTOCOL_VERSION,\n sessionId: session.id,\n capabilities: {\n messaging: { canSend: true, canBroadcast: true },\n observation: { canObserve: true, canSubscribe: true },\n \n // Advertise mail support\n mail: this.mailStore ? {\n enabled: true,\n canCreate: hasPermission(ctx, 'mail.create'),\n canJoin: hasPermission(ctx, 'mail.join'),\n canInvite: hasPermission(ctx, 'mail.invite'),\n canViewHistory: hasPermission(ctx, 'mail.viewHistory'),\n canCreateThreads: hasPermission(ctx, 'mail.createThreads'),\n } : undefined,\n },\n };\n}\n```\n\n### 5. Event Filtering\n\nAdd mail filter matching to the subscription event delivery:\n\n```typescript\nfunction shouldDeliverEvent(event: Event, filter: SubscriptionFilter): boolean {\n // Existing event type and agent filters...\n \n // Mail filter matching\n if (filter.mail && event.extensionData?.mail) {\n const mailFilter = filter.mail;\n const mailData = event.extensionData.mail;\n \n if (mailFilter.conversationId && mailData.conversationId !== mailFilter.conversationId) {\n return false;\n }\n if (mailFilter.threadId && mailData.threadId !== mailFilter.threadId) {\n return false;\n }\n if (mailFilter.participantId && mailData.participantId !== mailFilter.participantId) {\n return false;\n }\n if (mailFilter.contentType && mailData.contentType !== mailFilter.contentType) {\n return false;\n }\n }\n \n return true;\n}\n```\n\n---\n\n## SDK Integration Options\n\n### Option A: Raw Protocol (Zero SDK Changes)\n\nMail works today with no SDK changes. Agents use the generic `sendRequest()` method:\n\n```typescript\n// Works with current BaseConnection\nawait connection.sendRequest('mail/create', { type: 'multi-agent', subject: '...' });\nawait connection.sendRequest('mail/turn', { conversationId: '...', contentType: 'text', content: { text: '...' } });\n```\n\nThis is a valid approach for early adoption and testing.\n\n### Option B: Typed MailClient (Recommended)\n\nA standalone class that wraps BaseConnection with typed methods:\n\n```typescript\nimport { BaseConnection } from '@multi-agent-protocol/sdk';\n\nclass MailClient {\n #connection: BaseConnection;\n \n constructor(connection: BaseConnection) {\n this.#connection = connection;\n }\n \n get enabled(): boolean {\n return !!this.#connection.serverCapabilities?.mail?.enabled;\n }\n \n async create(params: MailCreateRequest): Promise<MailCreateResponse> {\n return this.#connection.sendRequest('mail/create', params);\n }\n \n async get(params: MailGetRequest): Promise<MailGetResponse> {\n return this.#connection.sendRequest('mail/get', params);\n }\n \n async turn(params: MailTurnRequest): Promise<MailTurnResponse> {\n return this.#connection.sendRequest('mail/turn', params);\n }\n \n async listTurns(params: MailTurnsListRequest): Promise<MailTurnsListResponse> {\n return this.#connection.sendRequest('mail/turns/list', params);\n }\n \n async close(params: MailCloseRequest): Promise<MailCloseResponse> {\n return this.#connection.sendRequest('mail/close', params);\n }\n \n // ... other methods\n}\n\n// Usage\nconst agent = new AgentConnection(stream);\nawait agent.connect({ agentId: 'orchestrator' });\n\nconst mail = new MailClient(agent.connection);\nif (mail.enabled) {\n const conv = await mail.create({ type: 'multi-agent', subject: 'Sprint planning' });\n}\n```\n\n### Option C: Convenience Methods on AgentConnection\n\nSugar methods that make mail-aware sending ergonomic:\n\n```typescript\n// On AgentConnection\nasync sendInConversation(\n conversationId: ConversationId,\n to: Address,\n payload?: unknown,\n meta?: MessageMeta\n): Promise<SendResponseResult> {\n return this.send(to, payload, {\n ...meta,\n mail: { conversationId, ...meta?.mail }\n });\n}\n\nasync replyInConversation(\n originalMessage: Message,\n payload?: unknown,\n meta?: MessageMeta\n): Promise<SendResponseResult> {\n return this.reply(originalMessage, payload, {\n ...meta,\n mail: originalMessage.meta?.mail\n });\n}\n```\n\n---\n\n## Pattern: Mail Context Propagation\n\nFor multi-hop agent chains (A → B → C), the mail context should propagate:\n\n```typescript\n// Agent B receives from A, delegates to C\nagent.onMessage(async (message) => {\n const mailCtx = message.meta?.mail;\n \n // Delegate subtask to Agent C, preserving mail context\n await agent.send(\n { agent: 'agent-c' },\n { subtask: 'validate', data: message.payload },\n { mail: mailCtx }\n );\n});\n\n// Agent C receives from B, replies\nagent.onMessage(async (message) => {\n const result = await validate(message.payload.data);\n \n // Reply preserving mail context\n await agent.reply(message, { valid: true }, {\n mail: message.meta?.mail\n });\n});\n```\n\nAll three hops (A→B, B→C, C→B) are recorded as turns in the same conversation because each agent forwarded the `mail` meta.\n\n---\n\n## Pattern: Trajectory Child Conversations\n\nWhen an orchestrator wants to track an agent's internal work separately from the main conversation:\n\n```typescript\n// Orchestrator creates main conversation\nconst mainConv = await mail.create({ \n type: 'user-session', \n subject: 'User request' \n});\n\n// Create child conversation for agent trajectory\nconst trajectory = await mail.create({\n type: 'agent-task',\n subject: 'researcher work log',\n parentConversationId: mainConv.conversation.id,\n});\n\n// Send task to researcher with BOTH conversation IDs\nawait orchestrator.send(\n { agent: 'researcher' },\n { \n task: 'research', \n query: '...',\n trajectoryConversationId: trajectory.conversation.id // in payload, not meta\n },\n { mail: { conversationId: mainConv.conversation.id } } // main conversation in meta\n);\n```\n\nThe researcher can then record tool calls in the trajectory conversation:\n\n```typescript\nagent.onMessage(async (message) => {\n const mainConvId = message.meta?.mail?.conversationId;\n const trajectoryConvId = message.payload.trajectoryConversationId;\n \n // Record tool call in trajectory (if provided)\n if (trajectoryConvId) {\n await agent.request('mail/turn', {\n conversationId: trajectoryConvId,\n contentType: 'x-tool-call',\n content: { tool: 'search', input: { query: '...' } }\n });\n }\n \n const results = await doResearch();\n \n // Reply to main conversation\n await agent.reply(message, { results }, {\n mail: { conversationId: mainConvId }\n });\n});\n```\n\n---\n\n## Pattern: Graceful Degradation\n\nAll mail integration should degrade gracefully when mail is not supported:\n\n```typescript\nclass TaskOrchestrator {\n #agent: AgentConnection;\n #mailEnabled: boolean;\n \n async initialize() {\n const result = await this.#agent.connect({ \n agentId: 'orchestrator',\n capabilities: { mail: { canCreate: true } }\n });\n this.#mailEnabled = !!result.capabilities?.mail?.enabled;\n }\n \n async executeTask(task: string) {\n // Create conversation if mail available\n let convId: string | undefined;\n if (this.#mailEnabled) {\n const conv = await this.#agent.request('mail/create', {\n type: 'agent-task',\n subject: task,\n });\n convId = conv.conversation.id;\n }\n \n // Send task - works with or without mail\n await this.#agent.send(\n { agent: 'worker' },\n { task },\n convId ? { mail: { conversationId: convId } } : undefined\n );\n }\n}\n```\n\n---\n\n## Summary: Integration Effort by Role\n\n| Role | What to do | Effort |\n|------|-----------|--------|\n| **Simple agent** | Nothing, or add `mail: message.meta?.mail` to replies | Zero to one line |\n| **Coordinating agent** | Forward `mail` meta through delegation chains | Minimal |\n| **Orchestrator** | Create conversations, record turns, manage lifecycle | Medium |\n| **Client/Dashboard** | Subscribe to mail events, query history | Medium |\n| **Server** | Implement mail store, turn recording in map/send, mail method handlers | Significant |\n| **SDK** | Optional typed MailClient wrapper | Low |\n","priority":2,"archived":0,"archived_at":null,"created_at":"2026-02-05 20:23:52","updated_at":"2026-02-05 20:23:52","parent_id":"s-1bob","parent_uuid":"96dd7cfe-bf89-4aa1-9aeb-5576e54a471a","relationships":[],"tags":["guide","integration","mail"]}
14
- {"id":"s-9xbb","uuid":"843d2ced-fa20-4b20-b228-d3b3a9165fc7","title":"Credential Forwarding & Hoisting","file_path":"specs/s-9xbb_credential_forwarding_hoisting.md","content":"# Credential Forwarding & Hoisting\n\n> **Status**: Design phase. This spec captures design decisions for a future expansion direction. No implementation is planned yet, but the design must remain aligned with [[s-6kja]] (auth providers) and [[s-2w2c]] (credential brokering core).\n\n## Overview\n\nTwo related capabilities that extend credential brokering across trust boundaries:\n\n1. **Credential Forwarding (Proxy Model)**: Agents on remote systems can request credentials from an originating system's broker, proxied through MAP transport\n2. **Credential Hoisting**: Human users/clients can inject their own credentials into a MAP session for agents to use on their behalf\n\nBoth follow the principle: **credentials stay at the source, requests are proxied**.\n\n---\n\n## Design Decisions\n\n### Forwarding Strategy: Proxy (not Forward)\n\n**Decision**: Use the proxy model where credential requests are routed back to the originating system's broker. Raw credentials never cross system boundaries.\n\n**Rationale**:\n- Credentials stay in one place — the originating broker\n- Full audit trail — every issuance goes through the broker's audit events\n- Instant revocation — broker controls all future issuance\n- Scope attenuation per-request — each `cred/get` is individually authorized\n- No secret sprawl — credentials don't replicate across systems\n\n**Tradeoff accepted**: Higher latency (round-trip to originating system per request) and coupling (originating system must be online). Acceptable for the security benefits.\n\n**Future optimization**: Delegation grants (broker-to-broker delegation with signed authorization) can reduce per-request latency without forwarding raw credentials. This would leverage agent-iam's existing delegation primitives extended to broker-level grants.\n\n### Hoisted Credential Storage: Session-scoped (not Persisted)\n\n**Decision**: Hoisted credentials live on the MAP session and are garbage collected on disconnect.\n\n**Rationale**:\n- Simplest implementation — just a field on the existing session object\n- Automatic cleanup — disconnect = credentials gone, no orphaned secrets\n- Matches mental model — \"I'm here, agents can use my creds; I leave, they can't\"\n- Smallest security surface — no at-rest encryption, no key management, no storage infrastructure\n- No new infrastructure — builds on existing session management\n\n**Tradeoff accepted**: Long-running pipelines lose credentials on disconnect. Acceptable for the initial design; future expansion to persisted storage (Option B/C from design discussion) can be layered on.\n\n**Migration path**: Session → Server-side store (add persistence layer) → agent-iam HoistedProvider (unified credential issuance). Each step builds on the previous without breaking changes.\n\n### agent-iam Changes: None Required\n\n**Decision**: The initial implementation requires zero changes to agent-iam. All new logic lives in MAP.\n\n**Why proxy forwarding works without agent-iam changes**:\n- Token delegation on spawn already creates child tokens signed by the originating system's key\n- When a proxied `cred/get` reaches the originating broker, the delegated token is verifiable by `broker.checkPermission()` because it was signed with that broker's HMAC key\n- `broker.getCredential()` issues credentials based on verified token scopes — unchanged\n- Federation metadata (`hopCount`, `allowedSystems`, `crossSystemAllowed`) already tracks cross-system usage\n\n**Why credential hoisting works without agent-iam changes**:\n- Hoisted credentials are raw secrets (PATs, OAuth tokens), not agent-iam tokens\n- MAP handles storage (session field), policy enforcement, and delivery\n- agent-iam's `IdentityBinding.principalType: \"human\"` already supports audit trail for human-originated credentials\n- No agent-iam token wrapping needed for the session-scoped approach\n\n**Future agent-iam changes** (not in scope):\n- `HoistedProvider`: Dynamic provider that accepts per-user credential registration (needed for Option C storage)\n- Broker-to-broker delegation grants (needed for latency optimization of proxy model)\n- Central broker mode (shared credential store across systems)\n\n---\n\n## Credential Forwarding (Proxy Model)\n\n### Flow\n\n```\nSystem X (has broker) System Y (no broker for this provider)\n┌───────────────────────┐ ┌───────────────────────┐\n│ Agent A (orchestrator) │ │ Agent B (worker) │\n│ Token: T_A (root) │ │ Token: T_B (delegated) │\n│ │ │ │\n│ 1. Spawn Agent B ─────┼── delegate ───▶│ 2. Connect with T_B │\n│ T_B = delegate(T_A,│ T_B signed │ │\n│ { scopes: [read], │ by X's key │ 3. Agent B needs cred │\n│ agent: 'B' }) │ │ cred/get { github: │\n│ │ │ repo:read, │\n│ │ │ acme/frontend } │\n│ │ │ │ │\n│ │ ◀── proxy ───┼─────────┘ │\n│ 4. Broker X receives │ │ │\n│ checkPermission( │ │ │\n│ T_B, scope, res) │ │ │\n│ → valid (T_B signed│ │ │\n│ by X's key) │ │ │\n│ │ │ │\n│ 5. getCredential( │ │ │\n│ T_B, scope, res) │ │ │\n│ → { ghs_yyy... } │ │ │\n│ │── credential ─▶│ 6. Agent B uses cred │\n│ │ │ git clone (ghs_yyy)│\n└───────────────────────┘ └───────────────────────┘\n```\n\n### Key Invariant\n\nThe delegated token T_B is signed by System X's HMAC key. When the proxied request returns to System X, `broker.verifyToken(T_B)` succeeds because the signature matches. This is why no agent-iam changes are needed.\n\n### MAP Transport for Proxy\n\nMAP needs to route `cred/get` requests across connected systems. This requires:\n\n1. **Credential routing metadata on the token/session**: Which system's broker should handle this `cred/get`?\n - Option: Use `token.identity.systemId` (already exists) to determine the originating broker\n - Option: Explicit `brokerOrigin` field on the credential capability\n\n2. **Cross-system request forwarding**: MAP already handles message routing between connected agents. Proxied `cred/get` is a special case of cross-system request routing.\n\n3. **Response forwarding**: Credential result flows back through the same route.\n\n```typescript\n// In MAP's cred/get handler, add proxy logic:\nasync function handleCredGet(params: CredGetParams, ctx: HandlerContext) {\n const token = getTokenFromSession(ctx);\n \n // Determine if this request should be proxied\n const brokerSystem = token.identity?.systemId;\n \n if (brokerSystem && brokerSystem !== thisSystemId) {\n // Proxy to originating system\n return await proxyCredentialRequest(brokerSystem, token, params);\n }\n \n // Local broker handles it\n return await localBroker.getCredential(token, params.scope, params.resource);\n}\n```\n\n### Security Considerations\n\n- **Token never leaves originating trust domain**: T_B was created by System X, used by Agent B on System Y, but credential issuance happens on System X\n- **Audit trail**: System X's broker logs all `credential.issued` events, including proxied ones\n- **Federation limits**: `token.federation.maxHops` prevents infinite proxy chains\n- **System allowlisting**: `token.federation.allowedSystems` restricts which systems can proxy requests\n\n---\n\n## Credential Hoisting\n\n### Flow\n\n```\nHuman User (browser/CLI)\n │\n │── map/connect ──────────────────────────▶ MAP Server\n │ { auth: 'oauth', token: 'user-jwt' } │\n │ │\n │── cred/hoist ──────────────────────────▶ MAP Server\n │ { │\n │ provider: 'github', │\n │ credential: 'ghp_user_pat_xxx', │\n │ policy: { │\n │ allowedScopes: ['github:repo:read'], │\n │ allowedAgents: ['code-review-*'], │\n │ maxDelegationDepth: 2, │\n │ ttl: 3600 │\n │ } │\n │ } │\n │ ┌───────────┤\n │ │ 1. Validate human session\n │ │ 2. Store on session:\n │ │ session.hoistedCredentials = [{\n │ │ id: 'hoist-abc',\n │ │ provider: 'github',\n │ │ credential: encrypted(PAT),\n │ │ policy: { ... },\n │ │ owner: 'user-jane',\n │ │ hoistedAt: '...',\n │ │ expiresAt: '...'\n │ │ }]\n │ └───────────┤\n │ │\n │◀── { hoisted: true, │\n │ id: 'hoist-abc', │\n │ expiresAt: '...' } │\n │ │\n │ │\n ├── Later: Agent \"code-review-bot\" ──────────────┤\n │ cred/get { scope: 'github:repo:read', │\n │ resource: 'acme/frontend' } │\n │ ┌───────────┤\n │ │ 1. Check agent-iam token (auth layer)\n │ │ 2. Check hoisted credentials:\n │ │ - Agent matches 'code-review-*'? ✓\n │ │ - Scope in allowedScopes? ✓\n │ │ - TTL valid? ✓\n │ │ 3. Return hoisted credential\n │ │ (scoped if provider supports it,\n │ │ or raw PAT if not)\n │ └───────────┤\n │ ◀── { credentialType: 'bearer_token', │\n │ credential: { token: 'ghp_...' }, │\n │ source: 'hoisted', │\n │ hoistId: 'hoist-abc', │\n │ owner: 'user-jane' } │\n```\n\n### New MAP Methods\n\n```typescript\n// cred/hoist - Human injects credential into session\ninterface CredHoistParams {\n /** Provider identifier (e.g., 'github', 'aws', 'custom') */\n provider: string;\n \n /** The raw credential to hoist */\n credential: string | Record<string, unknown>;\n \n /** Policy controlling how agents can use this credential */\n policy: HoistPolicy;\n}\n\ninterface HoistPolicy {\n /** Which scopes this credential can satisfy (e.g., ['github:repo:read']) */\n allowedScopes: string[];\n \n /** Agent ID patterns that can access this credential (glob) */\n allowedAgents?: string[];\n \n /** Whether agents can pass this credential to their children */\n allowDelegation?: boolean;\n \n /** Maximum delegation depth for this credential */\n maxDelegationDepth?: number;\n \n /** TTL in seconds (credential removed after this, even if session alive) */\n ttl?: number;\n \n /** Resource patterns this credential is valid for */\n allowedResources?: string[];\n}\n\n// cred/unhoist - Human revokes a hoisted credential\ninterface CredUnhoistParams {\n /** The hoist ID to revoke */\n id: string;\n}\n\n// cred/list-hoisted - Human views their hoisted credentials\n// Returns: list of { id, provider, policy, hoistedAt, expiresAt }\n// Never returns the actual credential\n```\n\n### Session Storage\n\n```typescript\ninterface HoistedCredential {\n /** Unique identifier for this hoisted credential */\n id: string;\n \n /** Provider identifier */\n provider: string;\n \n /** The credential (encrypted in memory) */\n credential: EncryptedValue;\n \n /** Usage policy */\n policy: HoistPolicy;\n \n /** Identity of the human who hoisted this */\n owner: string;\n \n /** When hoisted */\n hoistedAt: string;\n \n /** When this credential expires (min of policy.ttl and session expiry) */\n expiresAt: string;\n}\n\n// On the session object:\ninterface Session {\n // ... existing fields ...\n hoistedCredentials?: HoistedCredential[];\n}\n```\n\n### Credential Resolution Priority\n\nWhen `cred/get` is called, the handler must resolve credentials from multiple sources. The priority order:\n\n```typescript\nasync function resolveCredential(\n params: CredGetParams,\n ctx: HandlerContext\n): Promise<CredentialResult> {\n // 1. First: Check hoisted credentials (human-provided, most specific)\n const hoisted = findMatchingHoistedCredential(ctx.session, params);\n if (hoisted) {\n return { ...hoisted, source: 'hoisted' };\n }\n \n // 2. Second: Check local broker (system-configured providers)\n const token = getTokenFromSession(ctx);\n if (localBroker) {\n const check = localBroker.checkPermission(token, params.scope, params.resource);\n if (check.valid) {\n return await localBroker.getCredential(token, params.scope, params.resource);\n }\n }\n \n // 3. Third: Proxy to originating system's broker\n const brokerSystem = token.identity?.systemId;\n if (brokerSystem && brokerSystem !== thisSystemId) {\n return await proxyCredentialRequest(brokerSystem, token, params);\n }\n \n throw new Error('No credential source available for this scope');\n}\n```\n\n### Hoisted Credential Propagation to Child Agents\n\nWhen an agent spawns a child, should the child also have access to hoisted credentials?\n\n**Decision**: Yes, if the hoisting policy allows it (`allowDelegation: true`) and the child agent matches `allowedAgents` pattern. Propagation follows these rules:\n\n1. Child inherits parent's access to hoisted credentials (not the credential itself)\n2. `maxDelegationDepth` decrements with each spawn\n3. Child must still match `allowedAgents` pattern\n4. Hoisted credential stays on the originating session — child requests are resolved against the parent session's hoisted store\n\n```\nHuman hoists PAT → Session S1\n │\n Agent A (matches policy) → cred/get resolves from S1.hoistedCredentials\n │\n Agent B (spawned, matches policy, depth OK) → cred/get resolves from S1.hoistedCredentials\n │\n Agent C (spawned, depth exceeded) → cred/get denied for hoisted creds\n```\n\n---\n\n## Audit Events\n\n### Forwarding Audit\n\n```typescript\n// Emitted on the originating system when a proxied request is fulfilled\n{\n type: 'credential.issued',\n agentId: 'agent-b',\n scope: 'github:repo:read',\n resource: 'acme/frontend',\n source: 'proxy',\n originSystem: 'system-x',\n requestingSystem: 'system-y',\n}\n```\n\n### Hoisting Audit\n\n```typescript\n// Emitted when human hoists a credential\n{\n type: 'credential.hoisted',\n owner: 'user-jane',\n provider: 'github',\n allowedScopes: ['github:repo:read'],\n allowedAgents: ['code-review-*'],\n ttl: 3600,\n // Never includes the credential itself\n}\n\n// Emitted when agent uses a hoisted credential\n{\n type: 'credential.issued',\n agentId: 'code-review-bot',\n scope: 'github:repo:read',\n resource: 'acme/frontend',\n source: 'hoisted',\n hoistId: 'hoist-abc',\n owner: 'user-jane',\n}\n\n// Emitted when hoisted credential is revoked or expires\n{\n type: 'credential.unhoisted',\n id: 'hoist-abc',\n reason: 'manual' | 'expired' | 'session_ended',\n}\n```\n\n---\n\n## Security Considerations\n\n### Forwarding Security\n- Raw credentials never cross system boundaries — only proxied requests and responses\n- Token verification happens on the originating system with its own HMAC key\n- Federation limits (`maxHops`, `allowedSystems`) prevent unbounded proxy chains\n- Each proxied request generates an audit event on the originating system\n\n### Hoisting Security\n- Hoisted credentials are encrypted in memory on the session\n- Credentials are garbage collected on disconnect (session-scoped)\n- Policy enforcement is mandatory — no open-ended access\n- Agent pattern matching prevents unauthorized agents from accessing hoisted credentials\n- TTL ensures credentials don't outlive their intended use\n- `cred/list-hoisted` never returns the actual credential, only metadata\n\n### Combined Security Model\n```\nHuman credential → HoistPolicy filter → Agent identity filter → Scope filter → Credential\n (allowedAgents) (agent-iam token) (allowedScopes)\n```\n\nThree independent checks must pass before a hoisted credential is returned. The agent-iam token check (middle layer) ensures the agent is authenticated and authorized, even when using human-provided credentials.\n\n---\n\n## File Changes (When Implemented)\n\n```\nts-sdk/src/server/credentials/\n ├── handlers.ts ← modify: add proxy logic to cred/get, add cred/hoist, cred/unhoist\n ├── types.ts ← modify: add HoistPolicy, HoistedCredential, CredHoistParams\n ├── hoisting.ts ← new: hoisted credential storage, policy matching, resolution\n └── proxy.ts ← new: cross-system credential request proxying\n\nts-sdk/src/server/router/\n └── handlers.ts ← modify: credential resolution priority (hoisted → local → proxy)\n```\n\n---\n\n## Implementation Phases (Future)\n\n### Phase 1: Credential Hoisting (MVP)\n- [ ] `cred/hoist` and `cred/unhoist` methods\n- [ ] Session-scoped hoisted credential storage\n- [ ] Policy enforcement (allowedScopes, allowedAgents, TTL)\n- [ ] Integration with `cred/get` resolution (hoisted → broker fallback)\n- [ ] Audit events for hoist/unhoist/usage\n\n### Phase 2: Credential Forwarding (Proxy)\n- [ ] Cross-system `cred/get` proxying via MAP transport\n- [ ] Token-based broker origin detection (`token.identity.systemId`)\n- [ ] Proxy audit events on originating system\n- [ ] Federation limit enforcement for proxy chains\n\n### Phase 3: Hoisted Credential Delegation\n- [ ] Propagation to spawned child agents\n- [ ] `maxDelegationDepth` enforcement for hoisted credentials\n- [ ] Agent pattern matching for children\n\n### Phase 4 (Future): Persistence & Central Broker\n- [ ] Persistent hoisted credential store (survives disconnect)\n- [ ] agent-iam `HoistedProvider` for unified credential issuance\n- [ ] Broker-to-broker delegation grants for reduced proxy latency\n- [ ] Central broker mode for multi-system credential management\n\n---\n\n## References\n\n- [[s-2w2c]] Credential Brokering (Core Optional Capability) — parent spec\n- [[s-6kja]] Pluggable Auth Providers & Agent-IAM Integration — auth layer\n- `references/agent-iam/src/types.ts` — IdentityBinding, FederationMetadata, AgentCapabilities (existing primitives that support this design)\n- `references/agent-iam/src/token.ts` — delegation and verification (no changes needed)\n","priority":2,"archived":0,"archived_at":null,"created_at":"2026-02-06 02:03:21","updated_at":"2026-02-06 02:03:21","parent_id":"s-2w2c","parent_uuid":"41285f26-9e0e-466d-bdb9-ea29b9f74a77","relationships":[{"from":"s-9xbb","from_type":"spec","to":"s-2w2c","to_type":"spec","type":"depends-on"},{"from":"s-9xbb","from_type":"spec","to":"s-6kja","to_type":"spec","type":"depends-on"}],"tags":["agent-iam","auth","core-optional","credentials","design-phase","federation","hoisting","p2"]}
15
- {"id":"s-4m1e","uuid":"3217695c-131c-4445-b166-9d5350bd1ff5","title":"Federation Envelope Extensions & Auth Interceptors","file_path":"specs/s-4m1e_federation_envelope_extensions_auth_interceptors.md","content":"\n# Federation Envelope Extensions & Auth Interceptors\n\n**Status**: Open design document — needs further investigation and design.\n\n## Problem\n\nThe MAP SDK provides two federation auth methods on the `AuthProvider` interface:\n- `prepareFederatedToken()` — create a delegated credential for a target system\n- `handleFederatedToken()` — verify an incoming credential from a peer system\n\nAnd a concrete implementation in `AgentIAMFederationGateway` (implemented in [[s-6kja]] Phase 4, [[i-1dnu]]).\n\nHowever, **nothing in the federation routing infrastructure calls these methods**. There is:\n1. No field on `ServerFederationEnvelope` to carry credentials\n2. No hook point to inject credentials before sending\n3. No hook point to verify credentials after receiving\n4. No passthrough semantics for multi-hop forwarding\n\nThe auth \"engine\" exists but has no integration point with the routing layer.\n\n## Design Philosophy\n\nMAP should **not** prescribe the federation auth wire format. Following the approach used by gRPC (opaque metadata headers) and NATS (connection-level auth with opaque credentials), MAP should:\n\n- Define an opaque slot on the envelope for implementation-specific data\n- Provide hook points so implementations can populate and verify that data\n- Stay out of interpreting the contents — implementations choose their own auth mechanism\n\nThis is consistent with MAP's existing design: pluggable auth, composable building blocks, \"don't pay for what you don't use.\"\n\n### Prior art\n\n| Project | Approach | Details |\n|---------|----------|---------|\n| **gRPC** | Opaque metadata passthrough | Auth goes in metadata headers (key-value pairs). gRPC doesn't care what's in `authorization` — could be JWT, API key, anything. Interceptors on each side handle verification. |\n| **NATS** | Connection-level trust | Leaf nodes authenticate at connection time. Individual messages carry no credentials. |\n| **Matrix** | Fully prescribed | Server-Server API prescribes JSON signing algorithm, key publication endpoints, HTTP Authorization header format. Every request is signed. |\n| **Kafka** | Connection-level trust | Inter-broker SASL at connection time. All messages on the connection are trusted. |\n\nMAP fits camp #2 (opaque passthrough) by philosophy.\n\n## Two-Layer Auth Model\n\nFederation auth operates at two layers:\n\n| Layer | When | What it proves | Already exists? |\n|-------|------|----------------|-----------------|\n| **Connection-level** | `federation/connect` | System B trusts System A as a peer | Yes — `auth.credentials` field |\n| **Per-message** | Envelope extensions | Agent X in System A has permission to do Y | No — this spec |\n\nThese are complementary. An implementation might use only connection-level (simpler), only per-message (more granular), or both.\n\n---\n\n## Proposed Changes\n\n### 1. Envelope extensions field\n\nAdd an opaque extensions field to `ServerFederationEnvelope`:\n\n```typescript\ninterface ServerFederationEnvelope {\n payload: unknown;\n routing: { from: string; to: string; hops: string[]; maxHops: number };\n timestamp: number;\n extensions?: Record<string, unknown>; // NEW\n}\n```\n\nSimilarly for the client-side `FederationEnvelope` type.\n\n### 2. Interceptor hooks\n\nHook points on the federation gateway or decorators that fire before send and after receive:\n\n```\nOutbound: envelope created → onOutbound(envelope, context) → routeToPeer()\nInbound: envelope arrives → onInbound(envelope, context) → deliver or reject\n```\n\n### 3. Passthrough on forward\n\nWhen a gateway forwards an envelope (multi-hop), extensions are preserved by default. Interceptors may transform them.\n\n---\n\n## Open Design Questions\n\n### Q1: Interceptor hook shape\n\nWhat API surface do interceptors expose?\n\n**Option A — Callback functions on gateway options:**\n```typescript\ninterface FederationGatewayOptions {\n onOutbound?: (envelope, context) => Promise<ServerFederationEnvelope | null>;\n onInbound?: (envelope, context) => Promise<ServerFederationEnvelope | null>;\n}\n```\nSimple, but only one interceptor per direction.\n\n**Option B — Interceptor interface with chaining:**\n```typescript\ninterface FederationInterceptor {\n onOutbound?(envelope, context): Promise<ServerFederationEnvelope | null>;\n onInbound?(envelope, context): Promise<ServerFederationEnvelope | null>;\n}\n\ninterface FederationGatewayOptions {\n interceptors?: FederationInterceptor[];\n}\n```\nSupports multiple concerns (auth, logging, metrics) as separate interceptors.\n\n**Option C — Middleware pattern (matching existing router middleware):**\n```typescript\ntype FederationMiddleware = (\n envelope: ServerFederationEnvelope,\n context: FederationHookContext,\n next: () => Promise<void>,\n) => Promise<void>;\n```\nConsistent with existing router middleware pattern. Supports chaining and short-circuiting.\n\n### Q2: Outbound context — what does the hook receive?\n\nTo call `prepareFederatedToken()`, the outbound hook needs the sending agent's auth principal/session. But the federation decorators currently only see a `ServerMessage` with a `from` agent ID — the auth context is lost by then.\n\nOptions:\n- **Enrich the context**: Pass session/principal info through to the hook. Requires threading auth context through the decorator layer.\n- **External lookup**: Hook receives the agent ID and looks up auth state from SessionManager or similar. More decoupled but requires the implementation to set up the wiring.\n- **Envelope-in/envelope-out only**: Hook only sees the envelope and manages context externally. Simplest for the SDK but pushes complexity to implementations.\n\n### Q3: Multi-hop extension semantics\n\nWhen System A → System B → System C, and B forwards to C, what happens to A's extensions?\n\n- **Preserve**: B's outbound hook receives the envelope with A's extensions still present. Hook decides what to keep, add, or strip.\n- **Replace**: B strips all extensions, outbound hook starts fresh. Simpler but loses provenance.\n- **Accumulate**: Extensions are keyed by system ID so they don't collide. C can see both A's and B's auth.\n\nRecommendation: **Preserve by default** (MAP doesn't touch extensions on forward), let interceptors decide. This is most consistent with the \"black box\" philosophy.\n\n### Q4: Rejection handling\n\nWhen an inbound interceptor rejects a message (auth verification fails):\n\n- **Silent drop**: Sending system gets no feedback. Simple but hard to debug.\n- **Audit event**: Emit `federation.auth.denied` locally for observability. Consistent with existing credential denial events.\n- **Error response envelope**: Requires a response channel. Current `routeToPeer` is fire-and-forget with no response path.\n- **Combination**: Audit event + silent drop (no response). Most pragmatic given current architecture.\n\nAlso: rejected messages should NOT be buffered for retry. Need to distinguish \"peer is down\" (buffer) from \"auth failed\" (reject permanently).\n\n### Q5: Extension namespacing\n\nIf multiple systems or interceptors add data to extensions, how to avoid key collisions?\n\n- **No namespacing**: Simple. Risk of collision if independent interceptors use the same key.\n- **Convention-based**: Recommend keys like `\"agent-iam\"`, `\"my-company\"`. No enforcement.\n- **System-ID keyed**: Extensions keyed by the system that added them, e.g. `{ \"system-a\": { token: \"...\" } }`. Natural for multi-hop accumulation.\n\n### Q6: SDK ergonomics — built-in auth interceptor?\n\nShould the SDK provide a ready-made interceptor that wires `AuthProvider.prepareFederatedToken()` / `handleFederatedToken()` into the hook system?\n\n- **Option A: Yes** — SDK provides `createAuthFederationInterceptor(authProvider)` that implementations plug in. More ergonomic, less boilerplate.\n- **Option B: No** — SDK provides raw hooks only. Implementations wire auth themselves. More flexible, less opinionated.\n- **Option C: Both** — Raw hooks as the primitive, built-in auth interceptor as a convenience. Most work but best developer experience.\n\n### Q7: Per-peer vs global interceptors\n\nShould interceptors be configurable per-peer (different auth for different peers) or global only?\n\n- Different peers may use different auth mechanisms (System B uses agent-iam, System C uses mTLS)\n- Per-peer config adds complexity but matches the existing `peerTrustConfigs` pattern in `AgentIAMFederationGateway`\n- Could support both: global interceptors run for all peers, per-peer interceptors override or augment\n\n### Q8: Backward compatibility\n\n- Systems that don't understand extensions receive an envelope with an unexpected `extensions` field. Since it's optional and envelopes are JSON objects, unknown fields should be ignored. **Should be safe.**\n- Older systems that forward envelopes may strip unknown fields. If an implementation relies on extensions surviving multi-hop through legacy systems, that's a problem. **Should MAP mandate that gateways preserve unknown envelope fields?**\n\n---\n\n## Scope\n\n### What's protocol-level (wire format)\n- `extensions` field on `ServerFederationEnvelope` / `FederationEnvelope`\n- Passthrough semantics: gateways MUST preserve `extensions` on forward\n- Recommendation: namespace extension keys to avoid collision\n\n### What's SDK-level (implementation)\n- Interceptor hook API\n- Auth provider integration\n- Error/rejection handling\n- Per-peer configuration\n\n### What's implementation-level (up to consumers)\n- What goes in extensions (tokens, signatures, metadata)\n- Wire format of credentials within extensions\n- Multi-hop accumulation vs replacement strategy\n\n---\n\n## Dependencies\n\n- [[s-6kja]] — AgentIAM federation gateway (provides `handleFederatedToken` / `prepareFederatedToken`)\n- [[i-1dnu]] — Federation gateway implementation (completed)\n- Federation gateway (`FederationGatewayImpl`) and decorators (`FederatedMessageRouter`, etc.)\n\n## Related\n\n- [[s-2w2c]] — Credential Brokering Extension (companion auth feature)\n- [[s-9xbb]] — Credential Forwarding & Hoisting (may interact with per-message auth in federation)\n","priority":2,"archived":0,"archived_at":null,"created_at":"2026-02-06 06:59:18","updated_at":"2026-02-06 06:59:18","parent_id":null,"parent_uuid":null,"relationships":[{"from":"s-4m1e","from_type":"spec","to":"s-2w2c","to_type":"spec","type":"related"},{"from":"s-4m1e","from_type":"spec","to":"s-6kja","to_type":"spec","type":"depends-on"},{"from":"s-4m1e","from_type":"spec","to":"s-9xbb","to_type":"spec","type":"related"}],"tags":["auth","design-doc","federation","open-questions"]}