agent-tempo 1.2.0 → 1.4.0

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 (281) hide show
  1. package/CLAUDE.md +253 -219
  2. package/LICENSE +21 -21
  3. package/README.md +293 -289
  4. package/assets/icon-dark.svg +9 -9
  5. package/assets/icon.svg +9 -9
  6. package/assets/logo-dark.svg +11 -11
  7. package/assets/logo-light.svg +11 -11
  8. package/dashboard/README.md +91 -91
  9. package/dashboard/dist/assets/{index-D6Xyje_n.js → index-jmYe6rmS.js} +2 -2
  10. package/dashboard/dist/assets/index-jmYe6rmS.js.map +1 -0
  11. package/dashboard/dist/index.html +20 -20
  12. package/dashboard/package.json +47 -47
  13. package/dist/activities/outbox.d.ts +30 -1
  14. package/dist/activities/outbox.js +96 -3
  15. package/dist/adapters/base.js +5 -0
  16. package/dist/adapters/copilot/adapter.js +12 -1
  17. package/dist/adapters/index.d.ts +1 -1
  18. package/dist/adapters/index.js +7 -0
  19. package/dist/adapters/pi/adapter.d.ts +2 -0
  20. package/dist/adapters/pi/adapter.js +43 -0
  21. package/dist/adapters/pi/index.d.ts +16 -0
  22. package/dist/adapters/pi/index.js +10 -0
  23. package/dist/cli/global-wrapper.d.ts +19 -0
  24. package/dist/cli/global-wrapper.js +169 -0
  25. package/dist/cli/help-text.js +97 -97
  26. package/dist/cli/startup.js +11 -0
  27. package/dist/cli/upgrade-command.js +81 -81
  28. package/dist/cli.js +12 -0
  29. package/dist/client/core.js +9 -2
  30. package/dist/client/interface.d.ts +6 -0
  31. package/dist/config.d.ts +79 -0
  32. package/dist/config.js +74 -0
  33. package/dist/daemon.js +37 -1
  34. package/dist/http/aggregate.d.ts +22 -1
  35. package/dist/http/aggregate.js +41 -0
  36. package/dist/http/auth.d.ts +94 -8
  37. package/dist/http/auth.js +93 -9
  38. package/dist/http/body.d.ts +4 -1
  39. package/dist/http/body.js +6 -3
  40. package/dist/http/event-bus.js +1 -0
  41. package/dist/http/event-types.d.ts +34 -2
  42. package/dist/http/event-types.js +1 -0
  43. package/dist/http/gate-audit.d.ts +12 -0
  44. package/dist/http/gate-audit.js +95 -0
  45. package/dist/http/gate-registry.d.ts +167 -0
  46. package/dist/http/gate-registry.js +163 -0
  47. package/dist/http/gate-routes.d.ts +48 -0
  48. package/dist/http/gate-routes.js +102 -0
  49. package/dist/http/ingest-registry.d.ts +30 -0
  50. package/dist/http/ingest-registry.js +108 -0
  51. package/dist/http/inner-loop-routes.d.ts +66 -0
  52. package/dist/http/inner-loop-routes.js +182 -0
  53. package/dist/http/inner-loop.d.ts +92 -0
  54. package/dist/http/inner-loop.js +155 -0
  55. package/dist/http/server.d.ts +38 -3
  56. package/dist/http/server.js +211 -6
  57. package/dist/http/snapshot.d.ts +6 -0
  58. package/dist/http/snapshot.js +6 -0
  59. package/dist/pi/cue-pump.d.ts +61 -0
  60. package/dist/pi/cue-pump.js +95 -0
  61. package/dist/pi/extension.d.ts +45 -0
  62. package/dist/pi/extension.js +407 -0
  63. package/dist/pi/gate-client.d.ts +54 -0
  64. package/dist/pi/gate-client.js +136 -0
  65. package/dist/pi/headless.d.ts +85 -0
  66. package/dist/pi/headless.js +224 -0
  67. package/dist/pi/index.d.ts +28 -0
  68. package/dist/pi/index.js +43 -0
  69. package/dist/pi/inner-loop-client.d.ts +67 -0
  70. package/dist/pi/inner-loop-client.js +164 -0
  71. package/dist/pi/inner-loop-publisher.d.ts +187 -0
  72. package/dist/pi/inner-loop-publisher.js +236 -0
  73. package/dist/pi/lazy-proxy.d.ts +37 -0
  74. package/dist/pi/lazy-proxy.js +55 -0
  75. package/dist/pi/mission-control/actions.d.ts +48 -0
  76. package/dist/pi/mission-control/actions.js +98 -0
  77. package/dist/pi/mission-control/board.d.ts +53 -0
  78. package/dist/pi/mission-control/board.js +104 -0
  79. package/dist/pi/mission-control/extension.d.ts +44 -0
  80. package/dist/pi/mission-control/extension.js +251 -0
  81. package/dist/pi/mission-control/index.d.ts +15 -0
  82. package/dist/pi/mission-control/index.js +32 -0
  83. package/dist/pi/mission-control/inner-tail.d.ts +48 -0
  84. package/dist/pi/mission-control/inner-tail.js +76 -0
  85. package/dist/pi/mission-control/pi-ui.d.ts +43 -0
  86. package/dist/pi/mission-control/pi-ui.js +10 -0
  87. package/dist/pi/mission-control/render.d.ts +6 -0
  88. package/dist/pi/mission-control/render.js +95 -0
  89. package/dist/pi/phase-driver.d.ts +74 -0
  90. package/dist/pi/phase-driver.js +122 -0
  91. package/dist/pi/pi-types.d.ts +208 -0
  92. package/dist/pi/pi-types.js +21 -0
  93. package/dist/pi/probe.d.ts +80 -0
  94. package/dist/pi/probe.js +154 -0
  95. package/dist/pi/render-tools.d.ts +17 -0
  96. package/dist/pi/render-tools.js +51 -0
  97. package/dist/pi/reset-pump.d.ts +47 -0
  98. package/dist/pi/reset-pump.js +85 -0
  99. package/dist/pi/tool-capability.d.ts +60 -0
  100. package/dist/pi/tool-capability.js +156 -0
  101. package/dist/pi/workflow-client.d.ts +158 -0
  102. package/dist/pi/workflow-client.js +289 -0
  103. package/dist/pi/zod-to-typebox.d.ts +74 -0
  104. package/dist/pi/zod-to-typebox.js +191 -0
  105. package/dist/scripts/verify-daemon-isolation-guard.js +24 -24
  106. package/dist/server-tools.d.ts +2 -0
  107. package/dist/server-tools.js +50 -46
  108. package/dist/server.js +4 -0
  109. package/dist/spawn.d.ts +55 -0
  110. package/dist/spawn.js +84 -12
  111. package/dist/tools/agent-types.d.ts +2 -2
  112. package/dist/tools/agent-types.js +22 -17
  113. package/dist/tools/attachment-info.d.ts +2 -2
  114. package/dist/tools/attachment-info.js +38 -33
  115. package/dist/tools/broadcast.d.ts +2 -2
  116. package/dist/tools/broadcast.js +69 -64
  117. package/dist/tools/cancel-stage.d.ts +2 -2
  118. package/dist/tools/cancel-stage.js +20 -15
  119. package/dist/tools/clear-state.d.ts +2 -2
  120. package/dist/tools/clear-state.js +25 -20
  121. package/dist/tools/coat-check-evict.d.ts +2 -2
  122. package/dist/tools/coat-check-evict.js +30 -25
  123. package/dist/tools/coat-check-get.d.ts +2 -2
  124. package/dist/tools/coat-check-get.js +39 -34
  125. package/dist/tools/coat-check-list.d.ts +2 -2
  126. package/dist/tools/coat-check-list.js +48 -43
  127. package/dist/tools/coat-check-put.d.ts +2 -2
  128. package/dist/tools/coat-check-put.js +41 -36
  129. package/dist/tools/cue.d.ts +2 -2
  130. package/dist/tools/cue.js +57 -52
  131. package/dist/tools/descriptor.d.ts +72 -0
  132. package/dist/tools/descriptor.js +39 -0
  133. package/dist/tools/destroy.d.ts +2 -2
  134. package/dist/tools/destroy.js +153 -148
  135. package/dist/tools/ensemble.d.ts +2 -2
  136. package/dist/tools/ensemble.js +71 -66
  137. package/dist/tools/evaluate-gate.d.ts +2 -2
  138. package/dist/tools/evaluate-gate.js +33 -27
  139. package/dist/tools/fetch-state.d.ts +2 -2
  140. package/dist/tools/fetch-state.js +43 -38
  141. package/dist/tools/gates.d.ts +2 -2
  142. package/dist/tools/gates.js +39 -34
  143. package/dist/tools/hosts.d.ts +2 -2
  144. package/dist/tools/hosts.js +25 -20
  145. package/dist/tools/listen.d.ts +2 -2
  146. package/dist/tools/listen.js +23 -18
  147. package/dist/tools/load-lineup.d.ts +2 -2
  148. package/dist/tools/load-lineup.js +324 -319
  149. package/dist/tools/migrate.d.ts +2 -2
  150. package/dist/tools/migrate.js +45 -40
  151. package/dist/tools/pause.d.ts +2 -2
  152. package/dist/tools/pause.js +34 -29
  153. package/dist/tools/play.d.ts +2 -2
  154. package/dist/tools/play.js +53 -48
  155. package/dist/tools/quality-gate.d.ts +2 -2
  156. package/dist/tools/quality-gate.js +26 -21
  157. package/dist/tools/recall.d.ts +2 -2
  158. package/dist/tools/recall.js +32 -27
  159. package/dist/tools/recruit.d.ts +2 -2
  160. package/dist/tools/recruit.js +325 -256
  161. package/dist/tools/release.d.ts +2 -2
  162. package/dist/tools/release.js +85 -80
  163. package/dist/tools/report.d.ts +2 -2
  164. package/dist/tools/report.js +28 -23
  165. package/dist/tools/reset.d.ts +3 -0
  166. package/dist/tools/reset.js +51 -0
  167. package/dist/tools/restart.d.ts +2 -2
  168. package/dist/tools/restart.js +51 -46
  169. package/dist/tools/restore.d.ts +2 -2
  170. package/dist/tools/restore.js +76 -71
  171. package/dist/tools/save-lineup.d.ts +2 -2
  172. package/dist/tools/save-lineup.js +32 -27
  173. package/dist/tools/save-state.d.ts +2 -2
  174. package/dist/tools/save-state.js +43 -38
  175. package/dist/tools/schedule.d.ts +2 -2
  176. package/dist/tools/schedule.js +133 -128
  177. package/dist/tools/schedules.d.ts +2 -2
  178. package/dist/tools/schedules.js +41 -36
  179. package/dist/tools/set-ensemble-description.d.ts +2 -2
  180. package/dist/tools/set-ensemble-description.js +26 -21
  181. package/dist/tools/set-name.d.ts +2 -2
  182. package/dist/tools/set-name.js +38 -33
  183. package/dist/tools/set-part.d.ts +2 -2
  184. package/dist/tools/set-part.js +20 -15
  185. package/dist/tools/shutdown.d.ts +2 -2
  186. package/dist/tools/shutdown.js +39 -34
  187. package/dist/tools/stage.d.ts +2 -2
  188. package/dist/tools/stage.js +28 -23
  189. package/dist/tools/stages.d.ts +2 -2
  190. package/dist/tools/stages.js +36 -31
  191. package/dist/tools/unschedule.d.ts +2 -2
  192. package/dist/tools/unschedule.js +30 -25
  193. package/dist/tools/who-am-i.d.ts +2 -2
  194. package/dist/tools/who-am-i.js +36 -31
  195. package/dist/tools/worktree.d.ts +2 -2
  196. package/dist/tools/worktree.js +134 -129
  197. package/dist/tui/index.js +6 -6
  198. package/dist/types.d.ts +47 -2
  199. package/dist/types.js +1 -1
  200. package/dist/utils/default-part.js +1 -0
  201. package/dist/utils/grpc-shutdown-guard.d.ts +52 -0
  202. package/dist/utils/grpc-shutdown-guard.js +88 -0
  203. package/dist/utils/sdk-probe.d.ts +23 -0
  204. package/dist/utils/sdk-probe.js +46 -7
  205. package/dist/worker.d.ts +3 -1
  206. package/dist/worker.js +6 -2
  207. package/dist/workflows/session.js +70 -2
  208. package/dist/workflows/signals.d.ts +32 -2
  209. package/dist/workflows/signals.js +25 -2
  210. package/examples/agents/tempo-composer.md +56 -56
  211. package/examples/agents/tempo-conductor.md +117 -117
  212. package/examples/agents/tempo-critic.md +73 -73
  213. package/examples/agents/tempo-improv.md +74 -74
  214. package/examples/agents/tempo-liner.md +75 -75
  215. package/examples/agents/tempo-roadie.md +61 -61
  216. package/examples/agents/tempo-soloist.md +71 -71
  217. package/examples/agents/tempo-tuner.md +94 -94
  218. package/examples/ensembles/tempo-big-band.yaml +146 -146
  219. package/examples/ensembles/tempo-dev-team.yaml +58 -58
  220. package/examples/ensembles/tempo-headless-jam.yaml +77 -77
  221. package/examples/ensembles/tempo-jam-session.yaml +41 -41
  222. package/examples/ensembles/tempo-mock-jam.yaml +79 -79
  223. package/examples/ensembles/tempo-review-squad.yaml +32 -32
  224. package/package.json +176 -173
  225. package/packaging/launchd/com.agent.tempo.plist +46 -46
  226. package/packaging/systemd/agent-tempo.service +32 -32
  227. package/packaging/windows/install-task.ps1 +71 -71
  228. package/scenarios/conductor-recruit-mock.yaml +33 -33
  229. package/scenarios/echo-roundtrip.yaml +15 -15
  230. package/scenarios/multi-player-handoff.yaml +38 -38
  231. package/scenarios/recruit-cascade.yaml +38 -38
  232. package/scenarios/two-player-conversation.yaml +33 -33
  233. package/workflow-bundle.js +97 -6
  234. package/dashboard/dist/assets/index-D6Xyje_n.js.map +0 -1
  235. package/dist/activities/claude-stop.d.ts +0 -21
  236. package/dist/activities/claude-stop.js +0 -94
  237. package/dist/channel.d.ts +0 -3
  238. package/dist/channel.js +0 -48
  239. package/dist/copilot-bridge.d.ts +0 -22
  240. package/dist/copilot-bridge.js +0 -565
  241. package/dist/scripts/258-spotcheck.js +0 -303
  242. package/dist/tools/detach.d.ts +0 -4
  243. package/dist/tools/detach.js +0 -45
  244. package/dist/tools/encore.d.ts +0 -4
  245. package/dist/tools/encore.js +0 -31
  246. package/dist/tools/helpers.d.ts +0 -21
  247. package/dist/tools/helpers.js +0 -25
  248. package/dist/tools/pause-ensemble.d.ts +0 -4
  249. package/dist/tools/pause-ensemble.js +0 -58
  250. package/dist/tools/resume-ensemble.d.ts +0 -4
  251. package/dist/tools/resume-ensemble.js +0 -79
  252. package/dist/tools/stop.d.ts +0 -4
  253. package/dist/tools/stop.js +0 -29
  254. package/dist/tui/client.d.ts +0 -6
  255. package/dist/tui/client.js +0 -9
  256. package/dist/tui/components/ActivityLog.d.ts +0 -16
  257. package/dist/tui/components/ActivityLog.js +0 -36
  258. package/dist/tui/components/CommandOverlay.d.ts +0 -15
  259. package/dist/tui/components/CommandOverlay.js +0 -34
  260. package/dist/tui/components/ConductorChat.d.ts +0 -16
  261. package/dist/tui/components/ConductorChat.js +0 -32
  262. package/dist/tui/components/EnsembleListView.d.ts +0 -14
  263. package/dist/tui/components/EnsembleListView.js +0 -32
  264. package/dist/tui/components/EnsemblePanel.d.ts +0 -12
  265. package/dist/tui/components/EnsemblePanel.js +0 -40
  266. package/dist/tui/components/InputBar.d.ts +0 -13
  267. package/dist/tui/components/InputBar.js +0 -58
  268. package/dist/tui/components/ScheduleOverlay.d.ts +0 -13
  269. package/dist/tui/components/ScheduleOverlay.js +0 -113
  270. package/dist/tui/components/TopBar.d.ts +0 -12
  271. package/dist/tui/components/TopBar.js +0 -15
  272. package/dist/tui/core-api.d.ts +0 -26
  273. package/dist/tui/core-api.js +0 -67
  274. package/dist/tui/hooks/useEnsembleDiscovery.d.ts +0 -3
  275. package/dist/tui/hooks/useEnsembleDiscovery.js +0 -30
  276. package/dist/tui/hooks/useMaestroPoller.d.ts +0 -3
  277. package/dist/tui/hooks/useMaestroPoller.js +0 -36
  278. package/dist/tui/hooks/useSendCommand.d.ts +0 -7
  279. package/dist/tui/hooks/useSendCommand.js +0 -29
  280. package/dist/utils/bg-preflight.d.ts +0 -25
  281. package/dist/utils/bg-preflight.js +0 -154
package/CLAUDE.md CHANGED
@@ -1,219 +1,253 @@
1
- # CLAUDE.md
2
-
3
- ## What is this?
4
-
5
- agent-tempo is an MCP server that enables multiple Claude Code sessions to coordinate via Temporal.
6
-
7
- ## Tech Stack
8
-
9
- - **Runtime**: Node.js 20+ with TypeScript
10
- - **MCP**: `@modelcontextprotocol/sdk` (stdio transport)
11
- - **Temporal**: `@temporalio/client`, `@temporalio/worker`, `@temporalio/workflow`, `@temporalio/activity`
12
- - **croner** — cron expression parsing and next-fire computation (used by `schedule` tool)
13
- - **yaml**, **zod** — lineup parsing and schema validation
14
-
15
- ## Project Structure
16
-
17
- ```
18
- src/
19
- ├── server.ts # MCP server entry point
20
- ├── cli.ts # CLI entry point (agent-tempo command)
21
- ├── daemon.ts # Daemon entry point — runs Temporal workers as a detached background process
22
- ├── cli/
23
- │ ├── commands.ts # CLI command implementations (up, start, conduct, status, stop, …)
24
- │ ├── config-command.ts # config subcommand (interactive + set/show) — crash-proof for show/set
25
- │ ├── daemon.ts # Daemon management utilities (start, stop, status, heartbeat, isDaemonRunning)
26
- │ ├── daemon-command.ts # daemon subcommand handler — crash-proof, no Temporal deps
27
- │ ├── dashboard-command.ts # dashboard subcommand — crash-proof; opens the web dashboard, optionally minting a QR-code pairing token (#340)
28
- │ ├── dev-banner.ts # [DEV MODE] banner formatter (ADR 0014 §5.4) — gate 4 production-safety line
29
- │ ├── dev-mode-bootstrap.ts # pre-import side-effect: promotes top-level `--dev` flag to `CLAUDE_TEMPO_DEV_MODE=1` before any other module loads
30
- │ ├── dev-verbs.ts # dev-mode scriptable CLI verbs (#432) — shell-scriptable wrappers over MCP tools for E2E validation; stripped from production surface
31
- │ ├── help-text.ts # help outputcrash-proof, no Temporal deps
32
- │ ├── legacy-migration.ts # one-shot idempotent copy `~/.claude-tempo/` `~/.agent-tempo/` on first v1.0 boot (PR-2 of rebrand)
33
- │ ├── mcp.ts # MCP server registration helpers (init, global vs project)
34
- │ ├── output.ts # Shared CLI output formatting helpers
35
- │ ├── preflight.ts # Environment preflight checks
36
- │ ├── removed-verbs.ts # lookup table for the 10 CLI verbs removed in #288 — dispatches migration hints before loading Temporal surface
37
- │ ├── sa-preflight.ts # search-attribute preflight REQUIRED_SEARCH_ATTRIBUTES list (single source of truth), registerSearchAttribute, verifySearchAttributes, assertSearchAttributesOrExit
38
- │ ├── scenarios-command.ts # scenarios subcommand (dev mode only) — list/show shipped YAML scenario library (ADR 0014 §4.8)
39
- │ ├── startup.ts # auto-provisioning bootstrap state machine (#289) — six-step idempotent sequence used by bare `agent-tempo` invocation
40
- └── upgrade-command.ts # upgrade subcommandcrash-proof; dynamic-imports Temporal only for active-session warning
41
- ├── adapters/
42
- ├── README.md # Adapter contract documentation
43
- │ ├── index.ts # Adapter registry bootstrap + barrel exports (mock registered iff isDevMode())
44
- │ ├── base.ts # BaseAttachment + SdkAttachment base classes (lifecycle skeleton)
45
- │ ├── terminal-error.ts # Shared terminal-class error classifier for signal/query failures (#249)
46
- │ ├── claude-code/ # InteractiveAttachment Claude Code CLI adapter
47
- │ ├── copilot/ # CopilotSdkAttachmentCopilot bridge adapter
48
- │ ├── claude-api/ # ClaudeApiAttachmentheadless adapter via Anthropic Messages API (#131)
49
- │ ├── opencode/ # OpenCodeAttachment — headless multi-provider adapter via SST OpenCode subprocess (#449)
50
- │ ├── mock/ # MockAttachmentdev-mode-only SDK adapter (ADR 0014 PR-2). prepack strips dist/adapters/mock from npm tarball.
51
- └── sdk/ # SDK-style adapter base (used by Copilot bridge and opencode)
52
- ├── client/
53
- ├── interface.ts # TempoClient TypeScript interface and related types
54
- │ └── index.ts # TempoClient factory implementation and barrel re-exports
55
- ├── worker.ts # Temporal worker setup (used by daemon only)
56
- ├── connection.ts # Temporal connection factory (shared by server + CLI)
57
- ├── constants.ts # Shared string constants (ensemble ready banner/directive, etc.)
58
- ├── spawn.ts # Cross-platform process spawning helpers
59
- ├── workflows/
60
- ├── session.ts # claude-session workflow
61
- ├── scheduler.ts # durable scheduler workflow (one per ensemble)
62
- │ ├── maestro.ts # Maestro workflows — per-ensemble hub and global hub
63
- │ ├── attachment-math.ts # Pure CAN-boundary lease-extension helper (no Temporal imports)
64
- │ ├── maestro-signals.ts / scheduler-signals.ts / signals.ts # Signal/query/update type defs
65
- └── index.ts # Workflow re-exports for worker bundle
66
- ├── activities/
67
- ├── outbox.ts # Outbox delivery activities (cue, report, recruit, release, spawn)
68
- ├── maestro.ts # Maestro activities
69
- │ ├── hard-terminate.ts # Per-host process kill activity (used by destroy when attached)
70
- │ ├── resolve.ts # Session resolver shared by outbox + schedule-fire activities
71
- └── schedule-fire.ts
72
- ├── http/ # Daemon HTTP/SSE event source (#94/#95)
73
- ├── server.ts # Express-style HTTP server — snapshot + streaming endpoints
74
- ├── event-bus.ts # In-process EnsembleEventBus (fanout to SSE clients)
75
- │ ├── event-types.ts # TempoEvent / ClusterEvent wire type definitions
76
- │ ├── sse-handler.ts # SSE response lifecycle (ring-buffer replay, gap detection, backpressure)
77
- │ ├── ring-buffer.ts # Fixed-size event ring buffer (256 events) for Last-Event-ID replay
78
- │ ├── snapshot.ts # On-demand ensemble state snapshot (prelude + poll)
79
- │ ├── aggregate.ts # AggregateRunner wires bus + snapshot + HTTP server startup
80
- │ ├── auth.ts / cors.ts / responses.ts / event-id.ts / port-file.ts / index.ts
81
- ├── reconcile/
82
- └── orphans.ts # Shared orphan-query helper (daemon reconcile-on-boot + CLI restore)
83
- ├── ensemble/
84
- │ ├── schema.ts / loader.ts / saver.ts # Lineup type definitions, load, save
85
- └── agent-types.ts # Agent type discovery, resolution, and lineup resolution
86
- ├── tools/ # One file per MCP tool see docs/tools.md for full reference
87
- │ ├── ensemble.ts / cue.ts / recruit.ts / report.ts / broadcast.ts / recall.ts / listen.ts
88
- │ ├── restart.ts / destroy.ts / migrate.ts / attachment-info.ts
89
- │ ├── schedule.ts / unschedule.ts / schedules.ts
90
- ├── quality-gate.ts / evaluate-gate.ts / gates.ts
91
- ├── worktree.ts / stage.ts / stages.ts / cancel-stage.ts
92
- ├── load-lineup.ts / save-lineup.ts / agent-types.ts / resolve.ts
93
- │ ├── set-name.ts / set-part.ts / who-am-i.ts / release.ts
94
- ├── pause.ts / play.ts / shutdown.ts / restore.ts
95
- ├── hosts.ts / set-ensemble-description.ts
96
- │ ├── save-state.ts / fetch-state.ts / clear-state.ts
97
- │ ├── coat-check-put.ts / coat-check-get.ts / coat-check-list.ts / coat-check-evict.ts
98
- └── helpers.ts # Zod/MCP tool registration wrapper
99
- ├── tui/
100
- │ ├── App.tsx / store.ts / commands.ts # TUI root, state, slash commands
101
- │ ├── sse-handler.ts # SSE event → TUI store dispatch mapping (PR-4a of #94/#95)
102
- │ ├── components/ # Ink components see docs/tui.md for inventory
103
- └── utils/ # format, platform, theme, fullscreen, history
104
- ├── utils/
105
- │ ├── validation.ts / worktree.ts / safe-path.ts / duration.ts / search-attributes.ts
106
- │ ├── attachment-format.ts / recall-format.ts # Shared display formatters (attachment-info, recall)
107
- ├── hosts.ts / format-hosts.ts # Host enumeration + shared hosts display formatter (#274)
108
- ├── sdk-probe.ts # Filesystem-walk probe for installed optional npm deps (used by opencode adapter + recruit preflight, #449)
109
- │ ├── ensemble-ops.ts # Shared pause/unpause/fan-out helpers for ensemble-scope verbs (pause, play, shutdown, restore, destroy)
110
- │ ├── query-timeout.ts # Bounded WorkflowHandle.query() wrapper with in-flight deduplication; prevents hung queries from wedging snapshot endpoint (#433)
111
- │ ├── visibility-deadline.ts # Iterator-level deadlines for Temporal workflow.list() scans; prevents memory growth from stalled visibility page streams (#336, #529)
112
- │ ├── default-part.ts # Derives human-readable "X session" part string from a player's type name (e.g. tempo-conductor"Conductor session")
113
- │ ├── restore-format.ts # Pure formatter for restore --all-hosts cluster-view listing; no Temporal/I/O deps (#151)
114
- └── parent-death-watchdog.ts # Self-exit watchdog (stdin EOF + PPID poll) so MCP server children don't outlive their parent host (#604)
115
- ├── types.ts # Shared type definitions
116
- ├── git-info.ts # Git repository detection helper
117
- └── config.ts # Env var handling
118
- ```
119
-
120
- See [docs/tui-performance.md](docs/tui-performance.md) for Ink/React performance notes when
121
- touching `src/tui/`.
122
-
123
- ## Development
124
-
125
- ```bash
126
- npm install
127
- npm run build # compiles TS, scripts/*.ts dist/scripts/, and pre-bundles workflow code into workflow-bundle.js
128
- npm test
129
- npm run check:all # runs every CI gate locally (build, tests, drift checks, lints, dashboard, size-limit, tarball). Run before pushing to catch CI failures up front.
130
- ```
131
-
132
- > **Always run `npm run build` after changing workflow code (`src/workflows/`).** The build
133
- > pre-bundles workflows into `workflow-bundle.js` so all workers use identical code.
134
-
135
- > **Two test directories.** Mocha tests live in `test/` (Temporal workflow integration
136
- > suites, wire-protocol drift detector). Vitest tests live in `tests/` (TUI components,
137
- > TempoClient fallback paths). Both are first-class targets when doing call-site
138
- > surveys or migrations, always grep **both** `test/` and `tests/` or you will miss
139
- > mocks and assertions that only live in one directory.
140
-
141
- > **Test-only hooks live with the module they reset and follow the
142
- > `__<verb><Noun>ForTests` naming convention** see
143
- > [docs/adr/0006-test-hooks-naming.md](docs/adr/0006-test-hooks-naming.md). The
144
- > double-underscore prefix telegraphs "test escape hatch, do not call from
145
- > production code"; the hook's doc-comment should restate that explicitly. Hooks
146
- > are never surfaced through barrels or `TempoClient`.
147
-
148
- > **Project standard is npm** (declared via `package.json#packageManager`,
149
- > enforced in CI by `lint:lockfile-canonical`). If `npm` is blocked locally
150
- > (e.g. corp networks), `pnpm` works as a personal workaround — `pnpm-lock.yaml`
151
- > is gitignored, so it can exist on disk but must NOT be committed. The
152
- > `lint:lockfile-canonical` step inside `check:all` fails if any non-npm
153
- > lockfile (`pnpm-lock.yaml`, `yarn.lock`, or their `dashboard/` counterparts)
154
- > appears in `git ls-files`. The `dashboard/` subworkspace also uses npm and
155
- > ships its own `package-lock.json` — see `.github/workflows/ci.yml` for why
156
- > the install is intentionally independent.
157
-
158
- See [docs/development.md](docs/development.md) for full setup (Temporal dev server command,
159
- daemon worker notes, `npx ts-node` dev runner).
160
-
161
- ## Key Concepts
162
-
163
- > **Architecture overview**: See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for the three-layer workflow/adapter/process model and ensemble coordination layer.
164
-
165
- - **Player**: A Claude Code session registered as a Temporal workflow
166
- - **Conductor**: A special player that acts as orchestration hub, connected to external interfaces (one per ensemble)
167
- - **Ensemble**: The set of all active players, namespaced by `CLAUDE_TEMPO_ENSEMBLE`
168
- - **Cue**: A message sent to a player by name via Temporal signal
169
- - **Part**: A player's description of what it's working on
170
- - **Outbox**: Outbound requests (cue, report, recruit, restart, detach, destroy, …) go through the session's workflow outbox instead of directly signaling other workflows. The dispatch loop processes entries via activities, decoupling tools from cross-workflow signaling.
171
- - **Attachment phase** (v0.26): Seven phases tracked on the session workflow — `booting → attached → processing | awaiting → draining → detached → gone`. The phase is authoritative for lifecycle truth: adapters drive it via `claimAttachment` / `adapterExited` / `forceDetach` / `destroy`, and the workflow publishes it on the `ClaudeTempoAttachmentState` search attribute. Replaced the v0.25 `ClaudeTempoStatus` heuristic (removed in v0.26). See [docs/concepts.md](docs/concepts.md) for the phase table and [docs/ops/v0.26-migration.md](docs/ops/v0.26-migration.md) for the upgrade path.
172
- - **Adapter heartbeat observability** (#249): After `claimAttachment`, the base adapter logs `first heartbeat scheduled in Xms` then `heartbeat#1 delivered` on the first tick. Every 10 ticks it emits `heartbeats-delivered=N / phase-ticks=N` breadcrumbs. Any silent guard trip in `tickHeartbeat` / `tickPhaseWatcher` now emits a structured `guard tripped: {stopped, reconnecting, …}` log instead of silently orphaning the timer. The phase-watcher emits `WARNING: heartbeat staleness` when `lastHeartbeatAt` falls more than 2× `heartbeatMs` behind `now`. Grep `[agent-tempo:adapter]` to confirm loop health without parsing Temporal history.
173
- - **Per-host task queues**: `host` param on `recruit`/`restart`/`migrate` routes to `agent-tempo-{hostname}` task queue. When `host` is set on `recruit`, a pre-flight check validates the target daemon is live and supports the requested agent type (`force: true` bypasses). Daemon boot profiles (hostname, platform, available player types) are advertised via the `hostProfile` signal and maintained in the global maestro's `hostProfiles` map — surfaced by the `hosts` MCP tool, `agent-tempo hosts` CLI, and `/hosts` TUI command (#274). See [docs/concepts.md](docs/concepts.md) for cross-machine recruiting details.
174
- - **Wire protocol**: All signal/query/update names are documented in [`docs/WIRE-PROTOCOL.md`](docs/WIRE-PROTOCOL.md) and are stable renaming or removing any is a breaking change. **Process**: update `docs/WIRE-PROTOCOL.md` in the same commit as any new signal, query, or update.
175
- - **Daemon**: Standalone background process (`src/daemon.ts`) that runs all Temporal workers and the HTTP/SSE event source (`src/http/`). Auto-started by any `agent-tempo` command. PID at `~/.agent-tempo/daemon.pid`; logs at `~/.agent-tempo/daemon.log`. Exposes local HTTP endpoints (`/v1/health`, `/v1/ensembles`, `/v1/state/:ensemble`, `/v1/events/:ensemble` SSE stream, etc.) consumed by the TUI and `TempoClient.subscribe()`. See [docs/SSE-PROTOCOL.md](docs/SSE-PROTOCOL.md).
176
- - **Player types**: Reusable agent definitions in Claude Code's subagent format (`.md` files with YAML frontmatter). Three-tier lookup: project `.claude/agents/` user `~/.claude/agents/` → shipped `examples/agents/`. Discover via `agent_types` tool or `agent-tempo agent-types` CLI. Shipped types: tempo-conductor, tempo-composer, tempo-soloist, tempo-tuner, tempo-critic, tempo-roadie, tempo-improv, tempo-liner.
177
- - **Dev mode** (`--dev` flag or `CLAUDE_TEMPO_DEV_MODE=1`): Isolated testing profile — flips home dir to `~/.agent-tempo-dev/`, HTTP port to 8474, Temporal namespace to `agent-tempo-dev`, task queue to `agent-tempo-dev`. Required to use the mock adapter. Post-#423 isolation guarantees: (1) `TEMPORAL_NAMESPACE` / `TEMPORAL_ADDRESS` shell env vars are ignored in dev mode — the dev namespace is not overridable by shell config, only CLI flags and `~/.agent-tempo-dev/config.json`. (2) `--dev down` will not kill the Temporal server if the prod profile is alive; use `--kill-shared-temporal` to override. Canonical entry point (no global install required): `node dist/cli.js --dev <verb>`. Quick E2E: `node dist/cli.js --dev daemon start && node dist/cli.js --dev up --lineup tempo-mock-jam`. See [docs/dev-mode.md](docs/dev-mode.md) for the full reference.
178
- - **Claude API adapter** (`agent: 'claude-api'`, #131): Headless adapter that drives sessions via the Anthropic Messages API (`@anthropic-ai/sdk`) — no terminal, no Claude Code CLI. Requires `ANTHROPIC_API_KEY` env var and the `@anthropic-ai/sdk` optional dependency. Default model `claude-opus-4-7` (overridable via `model` recruit arg or `CLAUDE_TEMPO_API_MODEL` env). Claude-API players have access to agent-tempo MCP tools (cue, report, recall, ensemble, …) but not file-edit/shell/web tools. See `src/adapters/claude-api/`.
179
- - **OpenCode adapter** (`agent: 'opencode'`, #449): Headless multi-provider adapter that drives sessions via [SST OpenCode](https://opencode.ai) as a managed subprocess — supports Anthropic, OpenAI, Bedrock, Vertex, Ollama, and ~70 other providers via OpenCode's `provider/model` selector. Requires OpenCode CLI (`npm install -g opencode-ai`) and the `@opencode-ai/sdk` optional dependency. Recruit with `model: 'provider/name'` (e.g. `'anthropic/claude-opus-4-7'`). Tool bridging is MCP-native — OpenCode spawns `dist/server.js` as its own stdio MCP child. Session state is persisted server-side by OpenCode; the adapter stashes the session id on workflow metadata for reconnect across `opencode serve` restarts. See `src/adapters/opencode/`.
180
- - **Claude Code headless adapter** (`agent: 'claude-code-headless'`, #520): Headless adapter that drives sessions via the official `claude` CLI as a per-turn `claude -p --output-format stream-json` subprocess. The whole point: turns bill against the host's existing Claude Code subscription extra-usage credits (Pro / Max plans) rather than a Console workspace API key — the only ToS-clean way for a third-party tool to tap that pool. Requires the `claude` binary on PATH AND a logged-in Claude Code session (`claude auth login`); recruit pre-flight rejects with an actionable error otherwise. Tool surface is the union of full Claude Code built-ins (Bash / Read / Write / Edit / Glob / Grep / WebSearch / WebFetch) and the agent-tempo MCP surface — registered via inline `--mcp-config` so `claude` spawns `dist/server.js` as its own MCP child (no in-process bridge). Recruit knobs: `permissionMode` (default `'acceptEdits'`) or `dangerouslySkipPermissions: true` (mutually exclusive). Sessions resume across restart via the existing `sessionId` metadata field — the same UUID is shared with the interactive `claude-code` adapter (per-cwd JSONL is per-cwd, not per-adapter). See `src/adapters/claude-code-headless/` and `examples/ensembles/tempo-headless-jam.yaml`.
181
- - **Mock adapter** (`agent: 'mock'`, dev mode only): Four modes: `echo` (echoes input), `scripted` (replays YAML scenario rules), `silent` (drains messages without replying — heartbeat-stale validation), `chaos` (probabilistic fail/crash injection via seeded PRNG). Only registered when `isDevMode()` is true; stripped from the npm tarball by `prepack`. See `src/adapters/mock/`.
182
- - **Saveable state** (#334, ADR 0011): Per-player curated state slots — the player itself decides what context survives a restart. Three MCP tools: `save_state` (owner-only write, max 4 slots × 32 KiB), `fetch_state` (read self or peer; audit identity recorded on each entry's `savedBy`), `clear_state` (owner-only). `restart` accepts `loadFromState: true | 'someKey'` to seed the new session from a saved-state slot instead of (or, with `transcript: 'replay'`, alongside) transcript replay. Saved-state delivery uses `from: 'self-restart'` as a stable system identity. Empty-slot fallback: graceful falls through to transcript replay with a log line. See [docs/design/334-player-saveable-state.md](docs/design/334-player-saveable-state.md).
183
- - **Coat-check** (#318, ADR 0008): Per-ensemble transient content store on Maestro state. Solves the 100 KB cue body cap — stash a large artifact with `coat_check_put` (returns a ticket id) and attach the ticket to a `cue` via `attachmentTicket`; the recipient calls `coat_check_get` to pull the full body. Four MCP tools: `coat_check_put` (any player; max 32 KiB per entry, 20 slots per ensemble, TTL 7d default), `coat_check_get` (any player; bumps fetch-audit counters), `coat_check_list` (read-only survey; headers only, content omitted), `coat_check_evict` (owner or conductor). Saturation rejects with `CoatCheckSlotsFull` (no LRU eviction). See `src/tools/coat-check-*.ts` and [docs/adr/0008-coat-check-pattern.md](docs/adr/0008-coat-check-pattern.md).
184
- - **Lineup examples**: Six pre-built ensemble YAML files in `examples/ensembles/` — `tempo-big-band`, `tempo-dev-team`, `tempo-review-squad`, `tempo-jam-session`, `tempo-mock-jam` (dev-mode all-mock ensemble), `tempo-headless-jam` (#520 all-`claude-code-headless` subscription-billed ensemble). Load with `agent-tempo up --lineup <name>` or the `load_lineup` tool.
185
- - **GitHub App identity** (`agent-tempo[bot]`): When a player writes to GitHub — issue comments, PR creation/merge, commits, labels, check runs — **use `./scripts/ensemble-gh`** instead of `gh`. The wrapper mints a short-lived installation token so the action is attributed to `agent-tempo[bot]`, not to the human maintainer, making the AI authorship visible. Plain `gh` is still correct for read-only local dev (`gh pr view`, `gh repo clone`, `gh auth status`). Every bot-authored comment/PR body must include the AI attribution footer documented in [docs/github-app.md](docs/github-app.md).
186
-
187
- See [docs/concepts.md](docs/concepts.md) for the full glossary (Adapter, Attachment phases, Restart, Detach/Destroy, Migrate, Broadcast, Recall, Schedule, Lineup, Quality Gate, Worktree, Stage, Hold/Release, Pause/Resume, Maestro, TempoClient, and more).
188
-
189
- ## Commit Convention
190
-
191
- Use conventional commits: `type(scope): message`
192
-
193
- Examples:
194
- - `feat(tools): add ensemble discovery tool`
195
- - `fix(workflow): handle signal delivery edge case`
196
- - `docs: update getting started guide`
197
-
198
- ## PR Body Conventions
199
-
200
- GitHub's auto-close keywords (`Closes`, `Fixes`, `Resolves`) ignore any trailing qualifier text. They cannot express "this PR closes part of an issue" — they always close the full issue.
201
-
202
- For multi-PR efforts tracked under a single issue, use these conventions in PR bodies:
203
-
204
- | Form | When to use |
205
- |---|---|
206
- | `Refs #N` | Any intermediate PR of a multi-PR effort. No auto-close. |
207
- | `Implements PR-K of #N` | Same as above, more explicit. No keyword match no auto-close. |
208
- | `Closes #N` | Final PR of the effort (or single-PR efforts). Triggers auto-close on merge. |
209
-
210
- **Avoid `Closes #N PR-K`**GitHub ignores the `PR-K` qualifier and auto-closes #N prematurely. If you find yourself wanting to express "closes part of," use `Refs #N` and add a manual close on the final PR.
211
-
212
- When sequencing multi-PR work, name the issue's open question explicitly in the first PR's body (e.g., "PR-1 of 2: foundation; PR-2 follows for the user-visible payoff") so reviewers know more is coming.
213
-
214
- ## Release Process
215
-
216
- > **Release rule**: Bump `package.json` + CHANGELOG before tagging. Never tag a commit that
217
- > doesn't match the version. Tagging prematurely publishes the old version to npm.
218
-
219
- See [docs/release-process.md](docs/release-process.md) for the full 4-step sequence.
1
+ # CLAUDE.md
2
+
3
+ ## What is this?
4
+
5
+ agent-tempo is an MCP server that enables multiple Claude Code sessions to coordinate via Temporal.
6
+
7
+ ## Tech Stack
8
+
9
+ - **Runtime**: Node.js 20+ with TypeScript
10
+ - **MCP**: `@modelcontextprotocol/sdk` (stdio transport)
11
+ - **Temporal**: `@temporalio/client`, `@temporalio/worker`, `@temporalio/workflow`, `@temporalio/activity`
12
+ - **croner** — cron expression parsing and next-fire computation (used by `schedule` tool)
13
+ - **yaml**, **zod** — lineup parsing and schema validation
14
+
15
+ ## Project Structure
16
+
17
+ ```
18
+ src/
19
+ ├── server.ts # MCP server entry point
20
+ ├── cli.ts # CLI entry point (agent-tempo command)
21
+ ├── daemon.ts # Daemon entry point — runs Temporal workers as a detached background process
22
+ ├── cli/
23
+ │ ├── commands.ts # CLI command implementations (up, start, conduct, status, stop, …)
24
+ │ ├── config-command.ts # config subcommand (interactive + set/show) — crash-proof for show/set
25
+ │ ├── daemon.ts # Daemon management utilities (start, stop, status, heartbeat, isDaemonRunning)
26
+ │ ├── daemon-command.ts # daemon subcommand handler — crash-proof, no Temporal deps
27
+ │ ├── dashboard-command.ts # dashboard subcommand — crash-proof; opens the web dashboard, optionally minting a QR-code pairing token (#340)
28
+ │ ├── dev-banner.ts # [DEV MODE] banner formatter (ADR 0014 §5.4) — gate 4 production-safety line
29
+ │ ├── dev-mode-bootstrap.ts # pre-import side-effect: promotes top-level `--dev` flag to `CLAUDE_TEMPO_DEV_MODE=1` before any other module loads
30
+ │ ├── dev-verbs.ts # dev-mode scriptable CLI verbs (#432) — shell-scriptable wrappers over MCP tools for E2E validation; stripped from production surface
31
+ │ ├── global-wrapper.ts # global wrapper auto-provisioning writes `~/.agent-tempo/bin/agent-tempo` shell/cmd scripts; entrypoint pointer refreshed on every CLI boot; emits one-time PATH hint when bin dir is off PATH (#620)
32
+ │ ├── help-text.ts # help output crash-proof, no Temporal deps
33
+ │ ├── legacy-migration.ts # one-shot idempotent copy `~/.claude-tempo/` → `~/.agent-tempo/` on first v1.0 boot (PR-2 of rebrand)
34
+ │ ├── mcp.ts # MCP server registration helpers (init, global vs project)
35
+ │ ├── output.ts # Shared CLI output formatting helpers
36
+ │ ├── preflight.ts # Environment preflight checks
37
+ │ ├── removed-verbs.ts # lookup table for the 10 CLI verbs removed in #288 dispatches migration hints before loading Temporal surface
38
+ │ ├── sa-preflight.ts # search-attribute preflight REQUIRED_SEARCH_ATTRIBUTES list (single source of truth), registerSearchAttribute, verifySearchAttributes, assertSearchAttributesOrExit
39
+ │ ├── scenarios-command.ts # scenarios subcommand (dev mode only) — list/show shipped YAML scenario library (ADR 0014 §4.8)
40
+ ├── startup.ts # auto-provisioning bootstrap state machine (#289) six-step idempotent sequence used by bare `agent-tempo` invocation
41
+ │ └── upgrade-command.ts # upgrade subcommand — crash-proof; dynamic-imports Temporal only for active-session warning
42
+ ├── adapters/
43
+ │ ├── README.md # Adapter contract documentation
44
+ │ ├── index.ts # Adapter registry bootstrap + barrel exports (mock registered iff isDevMode())
45
+ │ ├── base.ts # BaseAttachment + SdkAttachment base classes (lifecycle skeleton)
46
+ │ ├── terminal-error.ts # Shared terminal-class error classifier for signal/query failures (#249)
47
+ │ ├── claude-code/ # InteractiveAttachmentClaude Code CLI adapter
48
+ │ ├── copilot/ # CopilotSdkAttachmentCopilot bridge adapter
49
+ │ ├── claude-api/ # ClaudeApiAttachment — headless adapter via Anthropic Messages API (#131)
50
+ │ ├── opencode/ # OpenCodeAttachmentheadless multi-provider adapter via SST OpenCode subprocess (#449)
51
+ ├── pi/ # Headless Pi adapter descriptor + spawn entry (Phase 3a). No BaseAttachment; the Pi extension singleton owns lifecycle (claim/heartbeat/tools/cue pump). `adapter.ts` is the process entry; `index.ts` is the registry descriptor.
52
+ ├── mock/ # MockAttachment — dev-mode-only SDK adapter (ADR 0014 PR-2). prepack strips dist/adapters/mock from npm tarball.
53
+ └── sdk/ # SDK-style adapter base (used by Copilot bridge and opencode)
54
+ ├── client/
55
+ ├── interface.ts # TempoClient TypeScript interface and related types
56
+ │ └── index.ts # TempoClient factory implementation and barrel re-exports
57
+ ├── worker.ts # Temporal worker setup (used by daemon only)
58
+ ├── connection.ts # Temporal connection factory (shared by server + CLI)
59
+ ├── constants.ts # Shared string constants (ensemble ready banner/directive, etc.)
60
+ ├── spawn.ts # Cross-platform process spawning helpers
61
+ ├── workflows/
62
+ │ ├── session.ts # claude-session workflow
63
+ │ ├── scheduler.ts # durable scheduler workflow (one per ensemble)
64
+ │ ├── maestro.ts # Maestro workflows — per-ensemble hub and global hub
65
+ ├── attachment-math.ts # Pure CAN-boundary lease-extension helper (no Temporal imports)
66
+ ├── maestro-signals.ts / scheduler-signals.ts / signals.ts # Signal/query/update type defs
67
+ └── index.ts # Workflow re-exports for worker bundle
68
+ ├── activities/
69
+ │ ├── outbox.ts # Outbox delivery activities (cue, report, recruit, release, spawn)
70
+ │ ├── maestro.ts # Maestro activities
71
+ ├── hard-terminate.ts # Per-host process kill activity (used by destroy when attached)
72
+ ├── resolve.ts # Session resolver shared by outbox + schedule-fire activities
73
+ └── schedule-fire.ts
74
+ ├── http/ # Daemon HTTP/SSE event source (#94/#95)
75
+ │ ├── server.ts # Express-style HTTP server snapshot + streaming endpoints
76
+ │ ├── event-bus.ts # In-process EnsembleEventBus (fanout to SSE clients)
77
+ │ ├── event-types.ts # TempoEvent / ClusterEvent wire type definitions
78
+ │ ├── sse-handler.ts # SSE response lifecycle (ring-buffer replay, gap detection, backpressure)
79
+ │ ├── ring-buffer.ts # Fixed-size event ring buffer (256 events) for Last-Event-ID replay
80
+ │ ├── snapshot.ts # On-demand ensemble state snapshot (prelude + poll)
81
+ ├── aggregate.ts # AggregateRunner — wires bus + snapshot + HTTP server startup
82
+ ├── inner-loop.ts # InnerLoopRegistry + InnerSubscription — daemon-local fine-tail sink (3c MD-F); drop-oldest bounded queue (256) + `compacted{dropped,sinceTs}` marker; NOT on Temporal/bus, ephemeral no-replay
83
+ ├── ingest-registry.ts # IngestTokenRegistry — per-player ingest token (mint-on-pi-spawn / revoke-on-destroy / revokeAll-on-shutdown); timing-safe validation; cross-player-spoof guard
84
+ │ ├── inner-loop-routes.ts # 3 inner-loop HTTP routes: POST /inner/ingest + GET /inner/presence (INGRESS, loopback + X-Ingest-Token, uniform 403); GET /inner (EGRESS operator SSE, requireTier(3))
85
+ ├── gate-registry.ts # GateRegistry (3d MD-G) per-player armed-gate + pending-request store; 45s lazy auto-allow (R3 locked); arm/disarm/decide/getResolution; injected auditSink + publishToInner callback
86
+ ├── gate-routes.ts # Gate HTTP routes: POST /gate-arm + /gate-disarm + /gate/:requestId (OPERATOR, Tier 3); GET /gate/:requestId/resolution (SOURCE, loopback + X-Ingest-Token); uniform 403 no-leak
87
+ │ ├── gate-audit.ts # createGateAuditSink append-only JSONL at ~/.agent-tempo/gate-audit/<ensemble>/<workflowId>.jsonl; sync write (R5 durable-before-return); whitelisted path segments; swallows I/O errors
88
+ │ ├── auth.ts # 3e MD-E RBAC: two-token model (readToken T1 / adminToken T1+T2+T3 env-var-only); loadRbacTokens; requireTier(tier, input) → TierGuardResult; tierForToken; loadReadToken (env>config>legacy httpToken>auto-gen); loadAdminToken (env-only); TLS/legacy startup warnings in startHttpServer
89
+ │ ├── cors.ts / responses.ts / event-id.ts / port-file.ts / index.ts
90
+ ├── reconcile/
91
+ └── orphans.ts # Shared orphan-query helper (daemon reconcile-on-boot + CLI restore)
92
+ ├── ensemble/
93
+ │ ├── schema.ts / loader.ts / saver.ts # Lineup type definitions, load, save
94
+ └── agent-types.ts # Agent type discovery, resolution, and lineup resolution
95
+ ├── tools/ # One file per MCP tool — see docs/tools.md for full reference
96
+ │ ├── ensemble.ts / cue.ts / recruit.ts / report.ts / broadcast.ts / recall.ts / listen.ts
97
+ │ ├── restart.ts / destroy.ts / migrate.ts / attachment-info.ts
98
+ ├── schedule.ts / unschedule.ts / schedules.ts
99
+ ├── quality-gate.ts / evaluate-gate.ts / gates.ts
100
+ │ ├── worktree.ts / stage.ts / stages.ts / cancel-stage.ts
101
+ │ ├── load-lineup.ts / save-lineup.ts / agent-types.ts / resolve.ts
102
+ │ ├── set-name.ts / set-part.ts / who-am-i.ts / release.ts
103
+ ├── pause.ts / play.ts / shutdown.ts / restore.ts / reset.ts
104
+ ├── hosts.ts / set-ensemble-description.ts
105
+ │ ├── save-state.ts / fetch-state.ts / clear-state.ts
106
+ │ ├── coat-check-put.ts / coat-check-get.ts / coat-check-list.ts / coat-check-evict.ts
107
+ └── descriptor.ts # Transport-neutral tool descriptor (TempoToolDescriptor) + renderToMcp; per-tool `build*Tool` factories live in each tool file (MD-B, Phase 1)
108
+ ├── pi/ # Pi-native integration a Pi session as a first-class player over the Temporal core
109
+ │ ├── extension.ts # `export default function(pi)` — interactive runtime entry. Holds the MODULE-SCOPE singleton `Map<workflowId, PiPlayerRuntime>` that survives Pi's per-switch instance rebuild (rebind, not re-claim); full tool surface via renderToPi; Option-C reason-discriminated teardown
110
+ │ ├── phase-driver.ts / workflow-client.ts / cue-pump.ts # Pi-event→attachment-phase machine, thin client-side WorkflowClient (lease/heartbeat 90/30, handle getter), cue pump (D10 steer/followUp)
111
+ │ ├── lazy-proxy.ts # D11 createLazyProxy Client/WorkflowHandle proxy resolving the live module-scope target per call (survives instance rebuild)
112
+ │ ├── headless.ts # Headless Pi runtime (Phase 3a) boots Pi's createAgentSession with inline extension; `noExtensions: true` closes S2 exec-tool bypass; SIGTERM/SIGINT shutdown reliable detach + dispose
113
+ │ ├── render-tools.ts # renderToPi registers the shared tool descriptors on Pi's ExtensionAPI (TypeBox params via the converter)
114
+ ├── zod-to-typebox.ts # zod→TypeBox tool-schema converter (fail-loud on unsupported constructs; Phase 1 / D1)
115
+ ├── inner-loop-publisher.ts # InnerLoopPublisher (3c MD-F) — single Pi-source observer; Tier-1 coarse via heartbeat piggyback (currentTool + context pressure), Tier-2 fine presence-gated; source coalescing (100ms/2KB) + 2KB truncation
116
+ ├── inner-loop-client.ts # InnerLoopHttpClient production InnerLoopRegistry impl: thin loopback-HTTP calls (publish→POST ingest, subscriberCount→cached presence GET); no-ops without AGENT_TEMPO_INGEST_TOKEN; 3d: also caches gateArmed from presence response
117
+ │ ├── gate-client.ts # GateClient (3d MD-G) — Pi-subprocess poll client for operator gate resolution; awaitDecision(requestId) polls GET /gate/:id/resolution until resolved or timeout; fail-open (allow on timeout/error/auto-allow)
118
+ │ ├── reset-pump.ts # ResetPump (3d D14) — polls pendingReset query every 1s; on result calls session.newSession() (clean-wipe); sends system notice; acks via ackReset signal
119
+ │ ├── tool-capability.ts # classify(toolName) → ToolCapability ('exec' | 'high-blast' | 'low-risk'); EXEC_TOOLS denylist (F1 locked: bash/shell/exec/sh/powershell/pwsh/cmd/…); unknown → 'high-blast' (fail-safe)
120
+ │ ├── mission-control/ # 3f — observer-only Pi extension that turns one interactive Pi TUI into an ensemble mission-control board + operator controller. HTTP-driven (NOT MCP tools). Never claims attachment or registers as a player.
121
+ │ │ ├── extension.ts # Controller (testable command handlers) + Pi extension lifecycle: session_start opens coarse SSE + ~200ms render tick + registers /players /tail /cue /pause /play /restart /destroy /reset /arm /gate; session_shutdown tears down SSE + widget
122
+ │ │ ├── board.ts # Pure BoardModel + reducers: applyTempoEvent (coarse /events SSE) + applyInnerFrame (selected-player inner tail); revision counter drives render throttle
123
+ │ │ ├── render.ts # Pure BoardModel → string[] renderer; phase glyphs, part, currentTool, context%, selected-player tail (12 lines), inner.gate_pending/gate_resolved frames
124
+ │ │ ├── actions.ts # Daemon HTTP write-surface client (T2 + T3 bearer): cue/pause/play/restart/destroy + gate arm/disarm/decide; reads AGENT_TEMPO_HTTP_ADMIN_TOKEN
125
+ │ │ ├── inner-tail.ts # Operator-egress /inner SSE consumer (T3 bearer): pure parseInnerSse + injectable stream pump; cross-host note: baseUrl is the preferredHost seam
126
+ │ │ ├── pi-ui.ts # Local structural slice of Pi's ctx.ui / registerCommand / registerShortcut API (self-contained; tsc green without optional Pi dep)
127
+ │ │ └── index.ts # Barrel — re-exports Controller, BoardModel, renderBoard, MissionControlActions, and public types
128
+ │ ├── probe.ts / pi-types.ts / index.ts # optional-dep preflight, hand-written ExtensionAPI decls, barrel
129
+ │ └── README.md # Pi integration findings (abrupt-death/MD-A, D12a, Phase 2 singleton/teardown) + carry-items
130
+ ├── tui/
131
+ │ ├── App.tsx / store.ts / commands.ts # TUI root, state, slash commands
132
+ │ ├── sse-handler.ts # SSE event TUI store dispatch mapping (PR-4a of #94/#95)
133
+ │ ├── components/ # Ink components see docs/tui.md for inventory
134
+ │ └── utils/ # format, platform, theme, fullscreen, history
135
+ ├── utils/
136
+ │ ├── validation.ts / worktree.ts / safe-path.ts / duration.ts / search-attributes.ts
137
+ │ ├── attachment-format.ts / recall-format.ts # Shared display formatters (attachment-info, recall)
138
+ │ ├── hosts.ts / format-hosts.ts # Host enumeration + shared hosts display formatter (#274)
139
+ │ ├── sdk-probe.ts # Filesystem-walk probe for installed optional npm deps + version floor check (used by opencode adapter + recruit preflight + Pi/Copilot-via-Pi preflight, #449)
140
+ │ ├── ensemble-ops.ts # Shared pause/unpause/fan-out helpers for ensemble-scope verbs (pause, play, shutdown, restore, destroy)
141
+ │ ├── query-timeout.ts # Bounded WorkflowHandle.query() wrapper with in-flight deduplication; prevents hung queries from wedging snapshot endpoint (#433)
142
+ │ ├── visibility-deadline.ts # Iterator-level deadlines for Temporal workflow.list() scans; prevents memory growth from stalled visibility page streams (#336, #529)
143
+ │ ├── default-part.ts # Derives human-readable "X session" part string from a player's type name (e.g. tempo-conductor → "Conductor session")
144
+ │ ├── restore-format.ts # Pure formatter for restore --all-hosts cluster-view listing; no Temporal/I/O deps (#151)
145
+ │ ├── parent-death-watchdog.ts # Self-exit watchdog (stdin EOF + PPID poll) so MCP server children don't outlive their parent host (#604)
146
+ │ └── grpc-shutdown-guard.ts # Process-level `uncaughtException` guard that swallows benign post-shutdown "Channel has been shut down" gRPC errors at the three JS-client entrypoints (cli.ts, daemon.ts, server.ts); re-throws everything else (#624)
147
+ ├── types.ts # Shared type definitions
148
+ ├── git-info.ts # Git repository detection helper
149
+ └── config.ts # Env var handling
150
+ ```
151
+
152
+ See [docs/tui-performance.md](docs/tui-performance.md) for Ink/React performance notes when
153
+ touching `src/tui/`.
154
+
155
+ ## Development
156
+
157
+ ```bash
158
+ npm install
159
+ npm run build # compiles TS, scripts/*.ts → dist/scripts/, and pre-bundles workflow code into workflow-bundle.js
160
+ npm test
161
+ npm run check:all # runs every CI gate locally (build, tests, drift checks, lints, dashboard, size-limit, tarball). Run before pushing to catch CI failures up front.
162
+ ```
163
+
164
+ > **Always run `npm run build` after changing workflow code (`src/workflows/`).** The build
165
+ > pre-bundles workflows into `workflow-bundle.js` so all workers use identical code.
166
+
167
+ > **Two test directories.** Mocha tests live in `test/` (Temporal workflow integration
168
+ > suites, wire-protocol drift detector). Vitest tests live in `tests/` (TUI components,
169
+ > TempoClient fallback paths). Both are first-class targets when doing call-site
170
+ > surveys or migrations, always grep **both** `test/` and `tests/` or you will miss
171
+ > mocks and assertions that only live in one directory.
172
+
173
+ > **Test-only hooks live with the module they reset and follow the
174
+ > `__<verb><Noun>ForTests` naming convention**see
175
+ > [docs/adr/0006-test-hooks-naming.md](docs/adr/0006-test-hooks-naming.md). The
176
+ > double-underscore prefix telegraphs "test escape hatch, do not call from
177
+ > production code"; the hook's doc-comment should restate that explicitly. Hooks
178
+ > are never surfaced through barrels or `TempoClient`.
179
+
180
+ > **Project standard is npm** (declared via `package.json#packageManager`,
181
+ > enforced in CI by `lint:lockfile-canonical`). If `npm` is blocked locally
182
+ > (e.g. corp networks), `pnpm` works as a personal workaround`pnpm-lock.yaml`
183
+ > is gitignored, so it can exist on disk but must NOT be committed. The
184
+ > `lint:lockfile-canonical` step inside `check:all` fails if any non-npm
185
+ > lockfile (`pnpm-lock.yaml`, `yarn.lock`, or their `dashboard/` counterparts)
186
+ > appears in `git ls-files`. The `dashboard/` subworkspace also uses npm and
187
+ > ships its own `package-lock.json` see `.github/workflows/ci.yml` for why
188
+ > the install is intentionally independent.
189
+
190
+ See [docs/development.md](docs/development.md) for full setup (Temporal dev server command,
191
+ daemon worker notes, `npx ts-node` dev runner).
192
+
193
+ ## Key Concepts
194
+
195
+ > **Architecture overview**: See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for the three-layer workflow/adapter/process model and ensemble coordination layer.
196
+
197
+ - **Player**: A Claude Code session registered as a Temporal workflow
198
+ - **Conductor**: A special player that acts as orchestration hub, connected to external interfaces (one per ensemble)
199
+ - **Ensemble**: The set of all active players, namespaced by `CLAUDE_TEMPO_ENSEMBLE`
200
+ - **Cue**: A message sent to a player by name via Temporal signal
201
+ - **Part**: A player's description of what it's working on
202
+ - **Outbox**: Outbound requests (cue, report, recruit, restart, detach, destroy, …) go through the session's workflow outbox instead of directly signaling other workflows. The dispatch loop processes entries via activities, decoupling tools from cross-workflow signaling.
203
+ - **Attachment phase** (v0.26): Seven phases tracked on the session workflow — `booting → attached → processing | awaiting → draining → detached → gone`. The phase is authoritative for lifecycle truth: adapters drive it via `claimAttachment` / `adapterExited` / `forceDetach` / `destroy`, and the workflow publishes it on the `ClaudeTempoAttachmentState` search attribute. Replaced the v0.25 `ClaudeTempoStatus` heuristic (removed in v0.26). See [docs/concepts.md](docs/concepts.md) for the phase table and [docs/ops/v0.26-migration.md](docs/ops/v0.26-migration.md) for the upgrade path.
204
+ - **Adapter heartbeat observability** (#249): After `claimAttachment`, the base adapter logs `first heartbeat scheduled in Xms` then `heartbeat#1 delivered` on the first tick. Every 10 ticks it emits `heartbeats-delivered=N / phase-ticks=N` breadcrumbs. Any silent guard trip in `tickHeartbeat` / `tickPhaseWatcher` now emits a structured `guard tripped: {stopped, reconnecting, …}` log instead of silently orphaning the timer. The phase-watcher emits `WARNING: heartbeat staleness` when `lastHeartbeatAt` falls more than 2× `heartbeatMs` behind `now`. Grep `[agent-tempo:adapter]` to confirm loop health without parsing Temporal history.
205
+ - **Per-host task queues**: `host` param on `recruit`/`restart`/`migrate` routes to `agent-tempo-{hostname}` task queue. When `host` is set on `recruit`, a pre-flight check validates the target daemon is live and supports the requested agent type (`force: true` bypasses). Daemon boot profiles (hostname, platform, available player types) are advertised via the `hostProfile` signal and maintained in the global maestro's `hostProfiles` map — surfaced by the `hosts` MCP tool, `agent-tempo hosts` CLI, and `/hosts` TUI command (#274). See [docs/concepts.md](docs/concepts.md) for cross-machine recruiting details.
206
+ - **Wire protocol**: All signal/query/update names are documented in [`docs/WIRE-PROTOCOL.md`](docs/WIRE-PROTOCOL.md) and are stable renaming or removing any is a breaking change. **Process**: update `docs/WIRE-PROTOCOL.md` in the same commit as any new signal, query, or update.
207
+ - **Daemon**: Standalone background process (`src/daemon.ts`) that runs all Temporal workers and the HTTP/SSE event source (`src/http/`). Auto-started by any `agent-tempo` command. PID at `~/.agent-tempo/daemon.pid`; logs at `~/.agent-tempo/daemon.log`. Exposes local HTTP endpoints (`/v1/health`, `/v1/ensembles`, `/v1/state/:ensemble`, `/v1/events/:ensemble` SSE stream, etc.) consumed by the TUI and `TempoClient.subscribe()`. See [docs/SSE-PROTOCOL.md](docs/SSE-PROTOCOL.md).
208
+ - **Player types**: Reusable agent definitions in Claude Code's subagent format (`.md` files with YAML frontmatter). Three-tier lookup: project `.claude/agents/` → user `~/.claude/agents/` → shipped `examples/agents/`. Discover via `agent_types` tool or `agent-tempo agent-types` CLI. Shipped types: tempo-conductor, tempo-composer, tempo-soloist, tempo-tuner, tempo-critic, tempo-roadie, tempo-improv, tempo-liner.
209
+ - **Dev mode** (`--dev` flag or `CLAUDE_TEMPO_DEV_MODE=1`): Isolated testing profile — flips home dir to `~/.agent-tempo-dev/`, HTTP port to 8474, Temporal namespace to `agent-tempo-dev`, task queue to `agent-tempo-dev`. Required to use the mock adapter. Post-#423 isolation guarantees: (1) `TEMPORAL_NAMESPACE` / `TEMPORAL_ADDRESS` shell env vars are ignored in dev mode — the dev namespace is not overridable by shell config, only CLI flags and `~/.agent-tempo-dev/config.json`. (2) `--dev down` will not kill the Temporal server if the prod profile is alive; use `--kill-shared-temporal` to override. Canonical entry point (no global install required): `node dist/cli.js --dev <verb>`. Quick E2E: `node dist/cli.js --dev daemon start && node dist/cli.js --dev up --lineup tempo-mock-jam`. See [docs/dev-mode.md](docs/dev-mode.md) for the full reference.
210
+ - **Claude API adapter** (`agent: 'claude-api'`, #131): Headless adapter that drives sessions via the Anthropic Messages API (`@anthropic-ai/sdk`)no terminal, no Claude Code CLI. Requires `ANTHROPIC_API_KEY` env var and the `@anthropic-ai/sdk` optional dependency. Default model `claude-opus-4-7` (overridable via `model` recruit arg or `CLAUDE_TEMPO_API_MODEL` env). Claude-API players have access to agent-tempo MCP tools (cue, report, recall, ensemble, …) but not file-edit/shell/web tools. See `src/adapters/claude-api/`.
211
+ - **OpenCode adapter** (`agent: 'opencode'`, #449): Headless multi-provider adapter that drives sessions via [SST OpenCode](https://opencode.ai) as a managed subprocess — supports Anthropic, OpenAI, Bedrock, Vertex, Ollama, and ~70 other providers via OpenCode's `provider/model` selector. Requires OpenCode CLI (`npm install -g opencode-ai`) and the `@opencode-ai/sdk` optional dependency. Recruit with `model: 'provider/name'` (e.g. `'anthropic/claude-opus-4-7'`). Tool bridging is MCP-native — OpenCode spawns `dist/server.js` as its own stdio MCP child. Session state is persisted server-side by OpenCode; the adapter stashes the session id on workflow metadata for reconnect across `opencode serve` restarts. See `src/adapters/opencode/`.
212
+ - **Claude Code headless adapter** (`agent: 'claude-code-headless'`, #520): Headless adapter that drives sessions via the official `claude` CLI as a per-turn `claude -p --output-format stream-json` subprocess. The whole point: turns bill against the host's existing Claude Code subscription extra-usage credits (Pro / Max plans) rather than a Console workspace API key — the only ToS-clean way for a third-party tool to tap that pool. Requires the `claude` binary on PATH AND a logged-in Claude Code session (`claude auth login`); recruit pre-flight rejects with an actionable error otherwise. Tool surface is the union of full Claude Code built-ins (Bash / Read / Write / Edit / Glob / Grep / WebSearch / WebFetch) and the agent-tempo MCP surface — registered via inline `--mcp-config` so `claude` spawns `dist/server.js` as its own MCP child (no in-process bridge). Recruit knobs: `permissionMode` (default `'acceptEdits'`) or `dangerouslySkipPermissions: true` (mutually exclusive). Sessions resume across restart via the existing `sessionId` metadata field — the same UUID is shared with the interactive `claude-code` adapter (per-cwd JSONL is per-cwd, not per-adapter). See `src/adapters/claude-code-headless/` and `examples/ensembles/tempo-headless-jam.yaml`.
213
+ - **Pi adapter** (`agent: 'pi'`, #632 Phase 3a): Headless adapter that runs a Pi AI session as a first-class agent-tempo player — no terminal, no BaseAttachment. Recruit injects the Phase 2 `src/pi` extension into Pi's `createAgentSession`; the module-scope singleton owns claim/heartbeat/tools/cue pump (MD-D). Requires the `pi-ai` optional dependency (`npm install -g pi-ai`) on Node 22.19+. Optional `model: 'provider/model'` selector (e.g. `'anthropic/claude-opus-4-7'`, `'github-copilot/gpt-4o'`); absent → Pi's own default. MD-C tool-access policy: `toolAccess: 'restricted'` (default — Bash/shell/exec HARD-BLOCKED) | `'standard'` (scoped Bash) | `'full'` (unsandboxed; requires `force: true`). `noExtensions: true` closes the S2 exec-tool-bypass gap (no disk/package extensions load alongside the agent-tempo extension). See `src/adapters/pi/` and `src/pi/headless.ts`.
214
+ - **Mission-control Pi extension** (3f): An observer-only Pi extension that turns one interactive Pi TUI into a live ensemble board + operator controller. HTTP-drives the daemon — coarse ensemble view via `/v1/events/:ensemble` SSE + fine per-player tail via `/inner` (T3); operator controls (cue/pause/play/restart/destroy + gate arm/disarm/decide) POST to the daemon write surface using `AGENT_TEMPO_HTTP_ADMIN_TOKEN`. **Never claims attachment or registers as a player** — invisible to the ensemble. ~200ms render throttle from an in-memory `BoardModel`. Requires an interactive Pi session with `ctx.hasUI`. See `src/pi/mission-control/`.
215
+ - **Mock adapter** (`agent: 'mock'`, dev mode only): Four modes: `echo` (echoes input), `scripted` (replays YAML scenario rules), `silent` (drains messages without replying — heartbeat-stale validation), `chaos` (probabilistic fail/crash injection via seeded PRNG). Only registered when `isDevMode()` is true; stripped from the npm tarball by `prepack`. See `src/adapters/mock/`.
216
+ - **Saveable state** (#334, ADR 0011): Per-player curated state slots — the player itself decides what context survives a restart. Three MCP tools: `save_state` (owner-only write, max 4 slots × 32 KiB), `fetch_state` (read self or peer; audit identity recorded on each entry's `savedBy`), `clear_state` (owner-only). `restart` accepts `loadFromState: true | 'someKey'` to seed the new session from a saved-state slot instead of (or, with `transcript: 'replay'`, alongside) transcript replay. Saved-state delivery uses `from: 'self-restart'` as a stable system identity. Empty-slot fallback: graceful — falls through to transcript replay with a log line. See [docs/design/334-player-saveable-state.md](docs/design/334-player-saveable-state.md).
217
+ - **Coat-check** (#318, ADR 0008): Per-ensemble transient content store on Maestro state. Solves the 100 KB cue body cap — stash a large artifact with `coat_check_put` (returns a ticket id) and attach the ticket to a `cue` via `attachmentTicket`; the recipient calls `coat_check_get` to pull the full body. Four MCP tools: `coat_check_put` (any player; max 32 KiB per entry, 20 slots per ensemble, TTL 7d default), `coat_check_get` (any player; bumps fetch-audit counters), `coat_check_list` (read-only survey; headers only, content omitted), `coat_check_evict` (owner or conductor). Saturation rejects with `CoatCheckSlotsFull` (no LRU eviction). See `src/tools/coat-check-*.ts` and [docs/adr/0008-coat-check-pattern.md](docs/adr/0008-coat-check-pattern.md).
218
+ - **Lineup examples**: Six pre-built ensemble YAML files in `examples/ensembles/` — `tempo-big-band`, `tempo-dev-team`, `tempo-review-squad`, `tempo-jam-session`, `tempo-mock-jam` (dev-mode all-mock ensemble), `tempo-headless-jam` (#520 — all-`claude-code-headless` subscription-billed ensemble). Load with `agent-tempo up --lineup <name>` or the `load_lineup` tool.
219
+ - **GitHub App identity** (`agent-tempo[bot]`): When a player writes to GitHub — issue comments, PR creation/merge, commits, labels, check runs — **use `./scripts/ensemble-gh`** instead of `gh`. The wrapper mints a short-lived installation token so the action is attributed to `agent-tempo[bot]`, not to the human maintainer, making the AI authorship visible. Plain `gh` is still correct for read-only local dev (`gh pr view`, `gh repo clone`, `gh auth status`). Every bot-authored comment/PR body must include the AI attribution footer documented in [docs/github-app.md](docs/github-app.md).
220
+
221
+ See [docs/concepts.md](docs/concepts.md) for the full glossary (Adapter, Attachment phases, Restart, Detach/Destroy, Migrate, Broadcast, Recall, Schedule, Lineup, Quality Gate, Worktree, Stage, Hold/Release, Pause/Resume, Maestro, TempoClient, and more).
222
+
223
+ ## Commit Convention
224
+
225
+ Use conventional commits: `type(scope): message`
226
+
227
+ Examples:
228
+ - `feat(tools): add ensemble discovery tool`
229
+ - `fix(workflow): handle signal delivery edge case`
230
+ - `docs: update getting started guide`
231
+
232
+ ## PR Body Conventions
233
+
234
+ GitHub's auto-close keywords (`Closes`, `Fixes`, `Resolves`) ignore any trailing qualifier text. They cannot express "this PR closes part of an issue" — they always close the full issue.
235
+
236
+ For multi-PR efforts tracked under a single issue, use these conventions in PR bodies:
237
+
238
+ | Form | When to use |
239
+ |---|---|
240
+ | `Refs #N` | Any intermediate PR of a multi-PR effort. No auto-close. |
241
+ | `Implements PR-K of #N` | Same as above, more explicit. No keyword match → no auto-close. |
242
+ | `Closes #N` | Final PR of the effort (or single-PR efforts). Triggers auto-close on merge. |
243
+
244
+ **Avoid `Closes #N PR-K`** — GitHub ignores the `PR-K` qualifier and auto-closes #N prematurely. If you find yourself wanting to express "closes part of," use `Refs #N` and add a manual close on the final PR.
245
+
246
+ When sequencing multi-PR work, name the issue's open question explicitly in the first PR's body (e.g., "PR-1 of 2: foundation; PR-2 follows for the user-visible payoff") so reviewers know more is coming.
247
+
248
+ ## Release Process
249
+
250
+ > **Release rule**: Bump `package.json` + CHANGELOG before tagging. Never tag a commit that
251
+ > doesn't match the version. Tagging prematurely publishes the old version to npm.
252
+
253
+ See [docs/release-process.md](docs/release-process.md) for the full 4-step sequence.