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
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findSdkPackageJson = findSdkPackageJson;
3
4
  exports.probeSdkInstall = probeSdkInstall;
5
+ exports.readSdkPackageVersion = readSdkPackageVersion;
4
6
  /**
5
7
  * Filesystem-walk probe for an installed npm package.
6
8
  *
@@ -20,26 +22,63 @@ exports.probeSdkInstall = probeSdkInstall;
20
22
  * Used by:
21
23
  * - `src/adapters/opencode/adapter.ts` — module-load optional-dep gate
22
24
  * - `src/tools/recruit.ts` — recruit pre-flight check
25
+ * - `src/pi/probe.ts` — Pi / Copilot-via-Pi pre-flight (presence + version floor)
23
26
  */
24
27
  const fs_1 = require("fs");
25
28
  const path_1 = require("path");
26
29
  /**
30
+ * Locate an installed package's `package.json` by walking `node_modules`
31
+ * directories upward from `fromDir`. The single source of truth for the
32
+ * filesystem walk — {@link probeSdkInstall} (presence) and
33
+ * {@link readSdkPackageVersion} (version) both build on it.
34
+ *
27
35
  * @param pkgName Bare specifier (e.g. `'@opencode-ai/sdk'`).
28
36
  * @param fromDir Where to start the walk. Defaults to the caller's
29
- * `__dirname`-equivalent — pass an explicit value if you need to anchor
30
- * the search elsewhere.
31
- * @returns `true` if `<dir>/node_modules/<pkgName>/package.json` exists
32
- * anywhere on the walk up the filesystem.
37
+ * `__dirname`-equivalent — pass an explicit value to anchor elsewhere.
38
+ * @returns The absolute path to `<dir>/node_modules/<pkgName>/package.json`
39
+ * for the first match up the filesystem, or `null` if none is found.
33
40
  */
34
- function probeSdkInstall(pkgName, fromDir = __dirname) {
41
+ function findSdkPackageJson(pkgName, fromDir = __dirname) {
35
42
  let dir = fromDir;
36
43
  while (true) {
37
44
  const candidate = (0, path_1.join)(dir, 'node_modules', pkgName, 'package.json');
38
45
  if ((0, fs_1.existsSync)(candidate))
39
- return true;
46
+ return candidate;
40
47
  const parent = (0, path_1.dirname)(dir);
41
48
  if (parent === dir)
42
- return false;
49
+ return null;
43
50
  dir = parent;
44
51
  }
45
52
  }
53
+ /**
54
+ * @param pkgName Bare specifier (e.g. `'@opencode-ai/sdk'`).
55
+ * @param fromDir Where to start the walk. Defaults to the caller's
56
+ * `__dirname`-equivalent — pass an explicit value if you need to anchor
57
+ * the search elsewhere.
58
+ * @returns `true` if `<dir>/node_modules/<pkgName>/package.json` exists
59
+ * anywhere on the walk up the filesystem.
60
+ */
61
+ function probeSdkInstall(pkgName, fromDir = __dirname) {
62
+ return findSdkPackageJson(pkgName, fromDir) !== null;
63
+ }
64
+ /**
65
+ * Read an installed package's `package.json#version` via the same filesystem
66
+ * walk as {@link probeSdkInstall}. Returns `null` when the package isn't
67
+ * installed, its `package.json` is unreadable, or its `version` field is
68
+ * absent/non-string — callers treat `null` as "version unknown".
69
+ *
70
+ * @param pkgName Bare specifier (e.g. `'@earendil-works/pi-coding-agent'`).
71
+ * @param fromDir Walk start (defaults to this module's `__dirname`).
72
+ */
73
+ function readSdkPackageVersion(pkgName, fromDir = __dirname) {
74
+ const pkgPath = findSdkPackageJson(pkgName, fromDir);
75
+ if (!pkgPath)
76
+ return null;
77
+ try {
78
+ const parsed = JSON.parse((0, fs_1.readFileSync)(pkgPath, 'utf8'));
79
+ return typeof parsed.version === 'string' ? parsed.version : null;
80
+ }
81
+ catch {
82
+ return null;
83
+ }
84
+ }
package/dist/worker.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { Worker } from '@temporalio/worker';
2
2
  import { Config } from './config';
3
+ import type { IngestTokenRegistry } from './http/ingest-registry';
4
+ import type { GateRegistry } from './http/gate-registry';
3
5
  export interface DualWorkers {
4
6
  /** Shared queue worker: workflows + delivery activities + schedule activities */
5
7
  sharedWorker: Worker;
@@ -11,4 +13,4 @@ export interface DualWorkers {
11
13
  * - Shared queue: workflows + all delivery activities (deliverCue, deliverReport, terminateSession, startRecruitedSession) + schedule activities
12
14
  * - Per-host queue: spawnProcess only (routes recruit spawns to the correct machine)
13
15
  */
14
- export declare function createWorkers(config: Config): Promise<DualWorkers>;
16
+ export declare function createWorkers(config: Config, ingestTokens?: IngestTokenRegistry, gate?: GateRegistry): Promise<DualWorkers>;
package/dist/worker.js CHANGED
@@ -93,13 +93,17 @@ async function getWorkflowBundle() {
93
93
  * - Shared queue: workflows + all delivery activities (deliverCue, deliverReport, terminateSession, startRecruitedSession) + schedule activities
94
94
  * - Per-host queue: spawnProcess only (routes recruit spawns to the correct machine)
95
95
  */
96
- async function createWorkers(config) {
96
+ async function createWorkers(config, ingestTokens, gate) {
97
97
  const connection = await (0, connection_1.createTemporalNativeConnection)(config);
98
98
  // Create a Client connection for activities that need to interact with Temporal
99
99
  const clientConnection = await (0, connection_2.createTemporalConnection)(config);
100
100
  const client = new client_1.Client({ connection: clientConnection, namespace: config.temporalNamespace });
101
101
  const scheduleActivities = (0, schedule_fire_1.createScheduleActivities)(client);
102
- const outboxActivities = (0, outbox_1.createOutboxActivities)(client, config);
102
+ // 3c Tier-2 thread the daemon's shared IngestTokenRegistry into the outbox
103
+ // activities so the pi spawn branch can mint per-player ingest tokens and the
104
+ // destroy path can revoke them. Same singleton the HTTP server validates
105
+ // against (both run in this daemon process).
106
+ const outboxActivities = (0, outbox_1.createOutboxActivities)(client, config, ingestTokens, gate);
103
107
  const maestroActivities = (0, maestro_1.createMaestroActivities)(client);
104
108
  const workflowBundle = await getWorkflowBundle();
105
109
  const SHUTDOWN_GRACE_TIME = '10s';
@@ -16,7 +16,7 @@ const attachment_math_1 = require("./attachment-math");
16
16
  const signals_1 = require("./signals");
17
17
  const validation_1 = require("../utils/validation");
18
18
  // ── Outbox Activity Proxies ──
19
- const { deliverCue, deliverReport, terminateSession, startRecruitedSession, releasePlayer, deliverDetach, deliverDestroy, deliverRestart } = (0, workflow_1.proxyActivities)({
19
+ const { deliverCue, deliverReport, terminateSession, startRecruitedSession, releasePlayer, deliverDetach, deliverDestroy, deliverRestart, deliverReset } = (0, workflow_1.proxyActivities)({
20
20
  startToCloseTimeout: '30 seconds',
21
21
  retry: { maximumAttempts: 3 },
22
22
  });
@@ -117,6 +117,9 @@ async function agentSessionWorkflow(input) {
117
117
  const messages = input.messages ?? [];
118
118
  const sentMessages = input.sentMessages ?? [];
119
119
  const outbox = input.outbox ?? [];
120
+ // D14 — pending context-reset flag, polled + acked by the Pi extension. Single
121
+ // slot, latest-wins; survives continue-as-new until the extension acks it.
122
+ let pendingReset = input.pendingReset ?? null;
120
123
  let lastActivityTime = workflowNow().getTime();
121
124
  let lastOutboundTime = input.lastOutboundTime ?? workflowNow().getTime();
122
125
  let lastInboundRRTime = input.lastInboundRRTime ?? 0;
@@ -128,6 +131,13 @@ async function agentSessionWorkflow(input) {
128
131
  let activityCount = input.activityCount ?? 0;
129
132
  let receivedCount = input.receivedCount ?? 0;
130
133
  let sentCount = input.sentCount ?? 0;
134
+ // ── 3c Tier-1 — coarse activity (currentTool + context usage) ──
135
+ // Refreshed by the heartbeat piggyback; surfaced by `getCoarseActivityQuery`.
136
+ // Deliberately volatile/live — NOT carried across continueAsNew (no input
137
+ // field), so a fresh run reports `{currentTool:null}` until the next ~30s
138
+ // heartbeat repopulates it. Acceptable for coarse observability; the live,
139
+ // fine-grained tail is the off-wire /inner side-channel.
140
+ let coarseActivity = { currentTool: null };
131
141
  // ── Warm Hold + Pause State ──
132
142
  let outboxLocked = input.outboxLocked ?? false;
133
143
  let heldMessage = input.heldMessage;
@@ -308,6 +318,9 @@ async function agentSessionWorkflow(input) {
308
318
  else if (entry.type === 'release') {
309
319
  sentMessages.push({ id: entry.id, to: entry.targetPlayerId, text: '[release requested]', timestamp: entry.createdAt });
310
320
  }
321
+ else if (entry.type === 'reset') {
322
+ sentMessages.push({ id: entry.id, to: entry.targetPlayerId, text: '[reset requested]', timestamp: entry.createdAt });
323
+ }
311
324
  lastActivityTime = workflowNow().getTime();
312
325
  activityCount++;
313
326
  lastOutboundTime = workflowNow().getTime();
@@ -368,6 +381,29 @@ async function agentSessionWorkflow(input) {
368
381
  lastActivityTime = workflowNow().getTime();
369
382
  activityCount++;
370
383
  });
384
+ // ── Reset (D14) — set by deliverReset, polled + acked by the Pi extension ──
385
+ (0, workflow_1.setHandler)(signals_1.setPendingResetSignal, (r) => {
386
+ // Latest-wins; stamp requestedAt deterministically (workflowNow, not the activity's clock).
387
+ pendingReset = {
388
+ resetId: r.resetId,
389
+ fresh: r.fresh,
390
+ ...(r.reason !== undefined ? { reason: r.reason } : {}),
391
+ ...(r.requestedBy !== undefined ? { requestedBy: r.requestedBy } : {}),
392
+ requestedAt: workflowNow().toISOString(),
393
+ };
394
+ lastActivityTime = workflowNow().getTime();
395
+ activityCount++;
396
+ });
397
+ (0, workflow_1.setHandler)(signals_1.pendingResetQuery, () => pendingReset);
398
+ (0, workflow_1.setHandler)(signals_1.ackResetSignal, (resetId) => {
399
+ // Race-safe: only clear if the ack matches the current pending reset, so a
400
+ // newer reset landing during the extension's wipe isn't silently dropped.
401
+ if (pendingReset?.resetId === resetId) {
402
+ pendingReset = null;
403
+ }
404
+ lastActivityTime = workflowNow().getTime();
405
+ activityCount++;
406
+ });
371
407
  (0, workflow_1.setHandler)(signals_1.updateMetadataSignal, (update) => {
372
408
  if (update.hostname != null)
373
409
  input.metadata.hostname = update.hostname;
@@ -434,6 +470,8 @@ async function agentSessionWorkflow(input) {
434
470
  leaseMs: currentAttachment.leaseMs,
435
471
  };
436
472
  });
473
+ // 3c Tier-1 — surface the latest coarse activity for the snapshot fan-out.
474
+ (0, workflow_1.setHandler)(signals_1.getCoarseActivityQuery, () => ({ ...coarseActivity }));
437
475
  // ── Hold / Release Handlers ──
438
476
  (0, workflow_1.setHandler)(signals_1.releaseHeldSignal, () => {
439
477
  if (heldMessage) {
@@ -807,7 +845,7 @@ async function agentSessionWorkflow(input) {
807
845
  * `heartbeat` signal — extend the lease. Last-write-wins via the `attachmentId` guard;
808
846
  * heartbeats for superseded attachments are ignored.
809
847
  */
810
- (0, workflow_1.setHandler)(signals_1.heartbeatSignal, ({ attachmentId }) => {
848
+ (0, workflow_1.setHandler)(signals_1.heartbeatSignal, ({ attachmentId, currentTool, contextTokens, contextPercent }) => {
811
849
  if (!currentAttachment || currentAttachment.attachmentId !== attachmentId)
812
850
  return;
813
851
  const now = workflowNow();
@@ -818,6 +856,16 @@ async function agentSessionWorkflow(input) {
818
856
  currentAttachment.expiresAt = new Date(now.getTime() + currentAttachment.leaseMs).toISOString();
819
857
  lastActivityTime = now.getTime();
820
858
  activityCount++;
859
+ // 3c Tier-1 — refresh coarse activity from the heartbeat piggyback. Field-wise
860
+ // merge: only fields the sender included are updated. `currentTool` can be a
861
+ // legitimate `null` (idle), so `!== undefined` distinguishes "sent null" from
862
+ // "not sent" (a non-reporting sender leaves prior coarse intact).
863
+ if (currentTool !== undefined)
864
+ coarseActivity.currentTool = currentTool;
865
+ if (contextTokens !== undefined)
866
+ coarseActivity.contextTokens = contextTokens;
867
+ if (contextPercent !== undefined)
868
+ coarseActivity.contextPercent = contextPercent;
821
869
  });
822
870
  /**
823
871
  * `requestDetach` signal — adapter-initiated graceful detach. Transitions to `draining`;
@@ -1415,6 +1463,9 @@ async function agentSessionWorkflow(input) {
1415
1463
  // #131 Phase C — forward to spawnProcess so spawnClaudeApiAdapter
1416
1464
  // can plumb it into the subprocess env (AGENT_TEMPO_API_MODEL).
1417
1465
  ...(entry.model !== undefined ? { model: entry.model } : {}),
1466
+ // Phase 3a / MD-C — forward the headless Pi tool-access policy so
1467
+ // spawnPiHeadless plumbs AGENT_TEMPO_TOOL_ACCESS into the subprocess.
1468
+ ...(entry.toolAccess !== undefined ? { toolAccess: entry.toolAccess } : {}),
1418
1469
  });
1419
1470
  break;
1420
1471
  }
@@ -1468,6 +1519,21 @@ async function agentSessionWorkflow(input) {
1468
1519
  });
1469
1520
  break;
1470
1521
  }
1522
+ case 'reset': {
1523
+ // D14: operator/conductor CLEAN-WIPE. Sets a pendingReset flag the
1524
+ // Pi extension polls + acts on (newSession). POLL-delivery, not a
1525
+ // direct subprocess signal. resetId = this outbox entry id (the
1526
+ // extension acks with it). Does NOT route through the MD-G gate.
1527
+ await deliverReset({
1528
+ ensemble: input.metadata.ensemble,
1529
+ targetPlayerId: entry.targetPlayerId,
1530
+ resetId: entry.id,
1531
+ fresh: entry.fresh ?? true,
1532
+ ...(entry.reason !== undefined ? { reason: entry.reason } : {}),
1533
+ requestedBy: entry.invokerPlayerId ?? input.metadata.playerId,
1534
+ });
1535
+ break;
1536
+ }
1471
1537
  case 'spawn': {
1472
1538
  // PR-D: forward the pre-claimed attachment token + pinned runId +
1473
1539
  // resolved adapterId to the spawn activity. The child process picks
@@ -1599,6 +1665,8 @@ async function agentSessionWorkflow(input) {
1599
1665
  messages: messages.filter((m) => !m.delivered),
1600
1666
  sentMessages: sentMessages.slice(-50),
1601
1667
  outbox: outbox.filter((e) => e.status === 'pending' || e.status === 'processing'),
1668
+ // D14 — carry an un-acked pending reset across CAN (omit when null).
1669
+ ...(pendingReset ? { pendingReset } : {}),
1602
1670
  lastInboundRRTime,
1603
1671
  lastOutboundTime,
1604
1672
  // #399 W2 — counters carried across continueAsNew so the
@@ -1,5 +1,5 @@
1
- import type { SessionMetadata, Message, SentMessage, HistoryEntry, OutboxEntry, OutboxEntryInput, QualityGate, WorktreeEntry, StageEntry, AttachmentToken, AttachmentInfo, AdapterClass, DetachReason, OrphanSummary, PlayerStateEntry } from '../types';
2
- export type { SessionMetadata, SessionInput, Message, Command, PlayerReport, SentMessage, HistoryEntry, OutboxEntry, OutboxEntryInput, OutboxEntryStatus, CueOutboxEntry, RecruitOutboxEntry, ReportOutboxEntry, StopOutboxEntry, ReleaseOutboxEntry, SpawnOutboxEntry, AgentType, QualityGate, QualityGateCriterion, WorktreeEntry, StageEntry, StagePlayerStatus, AttachmentToken, AttachmentInfo, AttachmentPhase, Attachment, AdapterClass, AdapterDescriptor, DetachReason, AdapterDirective, OrphanSummary, PlayerStateEntry, } from '../types';
1
+ import type { SessionMetadata, Message, SentMessage, HistoryEntry, OutboxEntry, OutboxEntryInput, QualityGate, WorktreeEntry, StageEntry, AttachmentToken, AttachmentInfo, AdapterClass, DetachReason, OrphanSummary, PlayerStateEntry, PendingReset } from '../types';
2
+ export type { SessionMetadata, SessionInput, Message, Command, PlayerReport, SentMessage, HistoryEntry, OutboxEntry, OutboxEntryInput, OutboxEntryStatus, CueOutboxEntry, RecruitOutboxEntry, ReportOutboxEntry, StopOutboxEntry, ReleaseOutboxEntry, SpawnOutboxEntry, PendingReset, AgentType, QualityGate, QualityGateCriterion, WorktreeEntry, StageEntry, StagePlayerStatus, AttachmentToken, AttachmentInfo, AttachmentPhase, Attachment, AdapterClass, AdapterDescriptor, DetachReason, AdapterDirective, OrphanSummary, PlayerStateEntry, } from '../types';
3
3
  export declare const receiveMessageSignal: import("@temporalio/workflow").SignalDefinition<[{
4
4
  from: string;
5
5
  text: string;
@@ -34,6 +34,14 @@ export declare const getMetadataQuery: import("@temporalio/workflow").QueryDefin
34
34
  export declare const pendingMessagesQuery: import("@temporalio/workflow").QueryDefinition<Message[], [], string>;
35
35
  export declare const allMessagesQuery: import("@temporalio/workflow").QueryDefinition<Message[], [], string>;
36
36
  export declare const allSentMessagesQuery: import("@temporalio/workflow").QueryDefinition<SentMessage[], [], string>;
37
+ export declare const setPendingResetSignal: import("@temporalio/workflow").SignalDefinition<[{
38
+ resetId: string;
39
+ fresh: boolean;
40
+ reason?: string;
41
+ requestedBy?: string;
42
+ }], string>;
43
+ export declare const pendingResetQuery: import("@temporalio/workflow").QueryDefinition<PendingReset | null, [], string>;
44
+ export declare const ackResetSignal: import("@temporalio/workflow").SignalDefinition<[string], string>;
37
45
  /** Release a held session — unlocks the outbox and delivers the stored initial message. */
38
46
  export declare const releaseHeldSignal: import("@temporalio/workflow").SignalDefinition<[], "releaseHeld">;
39
47
  /** Query whether the session's outbox is locked (warm hold). */
@@ -138,10 +146,19 @@ export declare const setPreferredHostUpdate: import("@temporalio/common").Update
138
146
  /**
139
147
  * Liveness heartbeat from the adapter. Resets `lastHeartbeatAt` and extends `expiresAt` to
140
148
  * `workflow.now() + leaseMs` iff `attachmentId` matches the current attachment; otherwise ignored.
149
+ *
150
+ * 3c Tier-1 (additive, optional, non-breaking): the heartbeat doubles as the
151
+ * coarse-activity piggyback. `currentTool` (null = idle), `contextTokens`, and
152
+ * `contextPercent` (Pi `getContextUsage`, pull-only — no token event exists)
153
+ * refresh the workflow's queryable coarse state read by `getCoarseActivityQuery`.
154
+ * Omitted by senders that don't report coarse — the handler merges field-wise.
141
155
  */
142
156
  export declare const heartbeatSignal: import("@temporalio/workflow").SignalDefinition<[{
143
157
  attachmentId: string;
144
158
  at: string;
159
+ currentTool?: string | null;
160
+ contextTokens?: number;
161
+ contextPercent?: number;
145
162
  }], string>;
146
163
  /**
147
164
  * Adapter-, conductor-, or operator-initiated request to detach gracefully.
@@ -268,6 +285,19 @@ export declare const getLeaseStateQuery: import("@temporalio/workflow").QueryDef
268
285
  expiresAt: number | null;
269
286
  leaseMs: number | null;
270
287
  }, [], string>;
288
+ /**
289
+ * 3c Tier-1 coarse activity — the player's current tool + context-token usage,
290
+ * refreshed by the heartbeat piggyback (`heartbeatSignal`). Read by the snapshot
291
+ * fan-out (`getPlayerWireMeta`) → projected onto `PlayerSummaryV1` → the aggregate
292
+ * poll/diff emits `player.activity`. `currentTool` is `null` when idle/between
293
+ * tools; the context fields are absent when Pi can't report usage (e.g. right
294
+ * after compaction). A live read of volatile state — NOT durable metadata.
295
+ */
296
+ export declare const getCoarseActivityQuery: import("@temporalio/workflow").QueryDefinition<{
297
+ currentTool: string | null;
298
+ contextTokens?: number;
299
+ contextPercent?: number;
300
+ }, [], string>;
271
301
  export declare const testForceContinueAsNewSignal: import("@temporalio/workflow").SignalDefinition<[], "testForceContinueAsNew">;
272
302
  export declare const setQualityGateSignal: import("@temporalio/workflow").SignalDefinition<[{
273
303
  task: string;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.setStageSignal = exports.worktreesQuery = exports.removeWorktreeSignal = exports.setWorktreeSignal = exports.qualityGatesQuery = exports.evaluateGateCriteriaSignal = exports.setQualityGateSignal = exports.testForceContinueAsNewSignal = exports.getLeaseStateQuery = exports.getActivityStateQuery = exports.getMessagingStateQuery = exports.getRunIdQuery = exports.outboxQuery = exports.submitOutboxUpdate = exports.playerStateKeysQuery = exports.playerStateQuery = exports.clearPlayerStateUpdate = exports.savePlayerStateUpdate = exports.orphanSummaryQuery = exports.attachmentInfoQuery = exports.adapterExitedSignal = exports.requestDetachSignal = exports.heartbeatSignal = exports.setPreferredHostUpdate = exports.enqueueSpawnUpdate = exports.forceDetachUpdate = exports.claimAttachmentUpdate = exports.isDestroyedQuery = exports.destroyUpdate = exports.inFlightMessagesQuery = exports.processingEndUpdate = exports.processingStartUpdate = exports.historyQuery = exports.playerReportSignal = exports.commandSignal = exports.pausedQuery = exports.setPausedSignal = exports.outboxLockedQuery = exports.releaseHeldSignal = exports.allSentMessagesQuery = exports.allMessagesQuery = exports.pendingMessagesQuery = exports.getMetadataQuery = exports.getPartQuery = exports.updateMetadataSignal = exports.setNameSignal = exports.markDeliveredSignal = exports.setPartSignal = exports.recordSentMessageSignal = exports.receiveMessageSignal = void 0;
4
- exports.stagesQuery = exports.cancelStageSignal = void 0;
3
+ exports.qualityGatesQuery = exports.evaluateGateCriteriaSignal = exports.setQualityGateSignal = exports.testForceContinueAsNewSignal = exports.getCoarseActivityQuery = exports.getLeaseStateQuery = exports.getActivityStateQuery = exports.getMessagingStateQuery = exports.getRunIdQuery = exports.outboxQuery = exports.submitOutboxUpdate = exports.playerStateKeysQuery = exports.playerStateQuery = exports.clearPlayerStateUpdate = exports.savePlayerStateUpdate = exports.orphanSummaryQuery = exports.attachmentInfoQuery = exports.adapterExitedSignal = exports.requestDetachSignal = exports.heartbeatSignal = exports.setPreferredHostUpdate = exports.enqueueSpawnUpdate = exports.forceDetachUpdate = exports.claimAttachmentUpdate = exports.isDestroyedQuery = exports.destroyUpdate = exports.inFlightMessagesQuery = exports.processingEndUpdate = exports.processingStartUpdate = exports.historyQuery = exports.playerReportSignal = exports.commandSignal = exports.pausedQuery = exports.setPausedSignal = exports.outboxLockedQuery = exports.releaseHeldSignal = exports.ackResetSignal = exports.pendingResetQuery = exports.setPendingResetSignal = exports.allSentMessagesQuery = exports.allMessagesQuery = exports.pendingMessagesQuery = exports.getMetadataQuery = exports.getPartQuery = exports.updateMetadataSignal = exports.setNameSignal = exports.markDeliveredSignal = exports.setPartSignal = exports.recordSentMessageSignal = exports.receiveMessageSignal = void 0;
4
+ exports.stagesQuery = exports.cancelStageSignal = exports.setStageSignal = exports.worktreesQuery = exports.removeWorktreeSignal = exports.setWorktreeSignal = void 0;
5
5
  const workflow_1 = require("@temporalio/workflow");
6
6
  // ── Player Signals ──
7
7
  // `isScheduled` + `scheduleName` are set by `src/activities/schedule-fire.ts`
@@ -27,6 +27,14 @@ exports.getMetadataQuery = (0, workflow_1.defineQuery)('getMetadata');
27
27
  exports.pendingMessagesQuery = (0, workflow_1.defineQuery)('pendingMessages');
28
28
  exports.allMessagesQuery = (0, workflow_1.defineQuery)('allMessages');
29
29
  exports.allSentMessagesQuery = (0, workflow_1.defineQuery)('allSentMessages');
30
+ // ── Reset (D14) — context clean-wipe poll-delivery ──
31
+ // `deliverReset` sets the pending flag via `setPendingResetSignal`; the Pi
32
+ // extension polls `pendingResetQuery`, performs the wipe (newSession), then
33
+ // clears it via `ackResetSignal(resetId)`. Single-slot, latest-wins. The
34
+ // workflow stamps `requestedAt` (deterministic). See WIRE-PROTOCOL.md.
35
+ exports.setPendingResetSignal = (0, workflow_1.defineSignal)('setPendingReset');
36
+ exports.pendingResetQuery = (0, workflow_1.defineQuery)('pendingReset');
37
+ exports.ackResetSignal = (0, workflow_1.defineSignal)('ackReset');
30
38
  // ── Hold / Release ──
31
39
  /** Release a held session — unlocks the outbox and delivers the stored initial message. */
32
40
  exports.releaseHeldSignal = (0, workflow_1.defineSignal)('releaseHeld');
@@ -95,6 +103,12 @@ exports.setPreferredHostUpdate = (0, workflow_1.defineUpdate)('setPreferredHost'
95
103
  /**
96
104
  * Liveness heartbeat from the adapter. Resets `lastHeartbeatAt` and extends `expiresAt` to
97
105
  * `workflow.now() + leaseMs` iff `attachmentId` matches the current attachment; otherwise ignored.
106
+ *
107
+ * 3c Tier-1 (additive, optional, non-breaking): the heartbeat doubles as the
108
+ * coarse-activity piggyback. `currentTool` (null = idle), `contextTokens`, and
109
+ * `contextPercent` (Pi `getContextUsage`, pull-only — no token event exists)
110
+ * refresh the workflow's queryable coarse state read by `getCoarseActivityQuery`.
111
+ * Omitted by senders that don't report coarse — the handler merges field-wise.
98
112
  */
99
113
  exports.heartbeatSignal = (0, workflow_1.defineSignal)('heartbeat');
100
114
  /**
@@ -210,6 +224,15 @@ exports.getActivityStateQuery = (0, workflow_1.defineQuery)('getActivityState');
210
224
  * boundary.
211
225
  */
212
226
  exports.getLeaseStateQuery = (0, workflow_1.defineQuery)('getLeaseState');
227
+ /**
228
+ * 3c Tier-1 coarse activity — the player's current tool + context-token usage,
229
+ * refreshed by the heartbeat piggyback (`heartbeatSignal`). Read by the snapshot
230
+ * fan-out (`getPlayerWireMeta`) → projected onto `PlayerSummaryV1` → the aggregate
231
+ * poll/diff emits `player.activity`. `currentTool` is `null` when idle/between
232
+ * tools; the context fields are absent when Pi can't report usage (e.g. right
233
+ * after compaction). A live read of volatile state — NOT durable metadata.
234
+ */
235
+ exports.getCoarseActivityQuery = (0, workflow_1.defineQuery)('getCoarseActivity');
213
236
  // ── Test-only Signals ──
214
237
  //
215
238
  // **Test-only.** Forces the session workflow's main loop to take the
@@ -1,56 +1,56 @@
1
- ---
2
- name: tempo-composer
3
- description: Software architect — designs system structure, defines interfaces, makes technology decisions. Focuses on the "what" and "why", not implementation.
4
- model: opus
5
- ---
6
-
7
- You are the **Composer** of the ensemble — the Software Architect. You design the structure of the system: modules, boundaries, interfaces, data flow, and technical direction. You define *what* gets built and *why*, then hand off the *how* to soloists.
8
-
9
- ## Responsibilities
10
-
11
- - Design system architecture: module boundaries, service decomposition, data flow
12
- - Define interfaces and contracts between components
13
- - Make technology choices with clear rationale
14
- - Analyze dependencies, coupling, and integration points
15
- - Identify scalability, security, and maintainability risks before they become problems
16
- - Review proposed designs and implementations for architectural consistency
17
- - Select API paradigms (REST, GraphQL, RPC) based on use case requirements
18
-
19
- ## Working Style
20
-
21
- - **Understand before proposing**: Read existing code and architecture before suggesting changes. Respect what's already there.
22
- - **Think in systems**: Focus on boundaries, trade-offs, and data flow — not individual functions.
23
- - **Document decisions**: Every architectural decision should come with rationale and trade-offs considered. Write ADRs when the decision is significant.
24
- - **Be opinionated but open**: Have strong views on architecture, loosely held. Change your mind when presented with evidence.
25
- - **Delegate implementation**: Define the shape of the solution, then hand off to soloists. Don't get pulled into writing production code.
26
- - **Consider observability**: Design systems that are debuggable. Think about logging, tracing, and error reporting from the start.
27
- - **Don't over-architect**: Design the simplest structure that meets the known requirements. If you're adding abstraction layers without a concrete, present-tense benefit, remove them. Apply `/simplify` thinking — if a design element can't be justified by a real requirement, it doesn't belong.
28
- - **Avoid designing for imagined futures**: Add extensibility only where there's evidence you'll need it. Speculative abstractions become maintenance burdens. You can always refactor when the need is real.
29
-
30
- ## Ensemble Collaboration
31
-
32
- - **`ensemble`**: Check who's active before proposing designs that affect multiple players' work. Understand the current state of implementation.
33
- - **`cue`**: Use to share design decisions, interface definitions, and architectural guidance with soloists. When a soloist asks a design question, respond with structured reasoning: context, options, recommendation, trade-offs.
34
- - **`report`**: Report to the conductor when:
35
- - A design decision is made (so it can be communicated to affected players)
36
- - You identify an architectural risk or concern
37
- - You need input on requirements before you can finalize a design
38
- - A design review is complete (with approve/reject/concerns)
39
- - **`who_am_i`**: Check your assignment and any type-specific instructions at startup.
40
- - **`agent_types`**: If you identify a need for a specialist (e.g., security review of your design), suggest the conductor recruit one.
41
-
42
- ### When other players cue you
43
-
44
- - **Soloists asking design questions**: Respond promptly with clear, actionable guidance. Don't send them in circles.
45
- - **Conductor asking for design review**: Provide structured feedback — approved, changes requested, or concerns flagged — with specific reasoning.
46
- - **Tuners reporting architectural test gaps**: Acknowledge and adjust the design to improve testability if needed.
47
-
48
- ## Context Pressure
49
-
50
- If you notice your context growing large, you're losing track of earlier instructions, or you find yourself repeating work, report to the conductor immediately with a structured summary:
51
-
52
- 1. **Current task**: What you're working on right now
53
- 2. **Key findings so far**: Important decisions, completed work, file paths changed
54
- 3. **Recommended next steps**: What remains to be done
55
-
56
- This lets the conductor refresh your session with a clean context while preserving continuity.
1
+ ---
2
+ name: tempo-composer
3
+ description: Software architect — designs system structure, defines interfaces, makes technology decisions. Focuses on the "what" and "why", not implementation.
4
+ model: opus
5
+ ---
6
+
7
+ You are the **Composer** of the ensemble — the Software Architect. You design the structure of the system: modules, boundaries, interfaces, data flow, and technical direction. You define *what* gets built and *why*, then hand off the *how* to soloists.
8
+
9
+ ## Responsibilities
10
+
11
+ - Design system architecture: module boundaries, service decomposition, data flow
12
+ - Define interfaces and contracts between components
13
+ - Make technology choices with clear rationale
14
+ - Analyze dependencies, coupling, and integration points
15
+ - Identify scalability, security, and maintainability risks before they become problems
16
+ - Review proposed designs and implementations for architectural consistency
17
+ - Select API paradigms (REST, GraphQL, RPC) based on use case requirements
18
+
19
+ ## Working Style
20
+
21
+ - **Understand before proposing**: Read existing code and architecture before suggesting changes. Respect what's already there.
22
+ - **Think in systems**: Focus on boundaries, trade-offs, and data flow — not individual functions.
23
+ - **Document decisions**: Every architectural decision should come with rationale and trade-offs considered. Write ADRs when the decision is significant.
24
+ - **Be opinionated but open**: Have strong views on architecture, loosely held. Change your mind when presented with evidence.
25
+ - **Delegate implementation**: Define the shape of the solution, then hand off to soloists. Don't get pulled into writing production code.
26
+ - **Consider observability**: Design systems that are debuggable. Think about logging, tracing, and error reporting from the start.
27
+ - **Don't over-architect**: Design the simplest structure that meets the known requirements. If you're adding abstraction layers without a concrete, present-tense benefit, remove them. Apply `/simplify` thinking — if a design element can't be justified by a real requirement, it doesn't belong.
28
+ - **Avoid designing for imagined futures**: Add extensibility only where there's evidence you'll need it. Speculative abstractions become maintenance burdens. You can always refactor when the need is real.
29
+
30
+ ## Ensemble Collaboration
31
+
32
+ - **`ensemble`**: Check who's active before proposing designs that affect multiple players' work. Understand the current state of implementation.
33
+ - **`cue`**: Use to share design decisions, interface definitions, and architectural guidance with soloists. When a soloist asks a design question, respond with structured reasoning: context, options, recommendation, trade-offs.
34
+ - **`report`**: Report to the conductor when:
35
+ - A design decision is made (so it can be communicated to affected players)
36
+ - You identify an architectural risk or concern
37
+ - You need input on requirements before you can finalize a design
38
+ - A design review is complete (with approve/reject/concerns)
39
+ - **`who_am_i`**: Check your assignment and any type-specific instructions at startup.
40
+ - **`agent_types`**: If you identify a need for a specialist (e.g., security review of your design), suggest the conductor recruit one.
41
+
42
+ ### When other players cue you
43
+
44
+ - **Soloists asking design questions**: Respond promptly with clear, actionable guidance. Don't send them in circles.
45
+ - **Conductor asking for design review**: Provide structured feedback — approved, changes requested, or concerns flagged — with specific reasoning.
46
+ - **Tuners reporting architectural test gaps**: Acknowledge and adjust the design to improve testability if needed.
47
+
48
+ ## Context Pressure
49
+
50
+ If you notice your context growing large, you're losing track of earlier instructions, or you find yourself repeating work, report to the conductor immediately with a structured summary:
51
+
52
+ 1. **Current task**: What you're working on right now
53
+ 2. **Key findings so far**: Important decisions, completed work, file paths changed
54
+ 3. **Recommended next steps**: What remains to be done
55
+
56
+ This lets the conductor refresh your session with a clean context while preserving continuity.