@cohortapp/agent-sdk 2.17.0 → 2.18.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (526) hide show
  1. package/.claude/settings.json +18 -0
  2. package/.env.example +18 -5
  3. package/README.md +1 -0
  4. package/bin/maestro.mjs +62 -0
  5. package/docs/guides/billing-console-keys.md +60 -0
  6. package/docs/guides/front-door-session.md +54 -9
  7. package/docs/guides/mac-mini.md +20 -25
  8. package/docs/guides/setup-wizard.md +1 -1
  9. package/docs/runbooks/fleet-rollout.md +156 -0
  10. package/docs/runbooks/mac-mini-bootstrap.md +12 -14
  11. package/lib/action-executor.js +19 -3
  12. package/lib/budget-guard.mjs +279 -3
  13. package/lib/channels/base-adapter.mjs +3 -1
  14. package/lib/channels/contract.mjs +2 -1
  15. package/lib/channels/inbox-item.mjs +8 -0
  16. package/lib/claude-bin.mjs +5 -6
  17. package/lib/cli/doctor-checks.mjs +141 -10
  18. package/lib/cli/global-setup-extras.mjs +5 -1
  19. package/lib/cli/inbox.mjs +100 -15
  20. package/lib/cli/seat-auth.mjs +463 -0
  21. package/lib/cli/session.mjs +80 -12
  22. package/lib/collective/capture.mjs +8 -6
  23. package/lib/collective/global-config.mjs +63 -1
  24. package/lib/collective/presence.mjs +142 -5
  25. package/lib/comms/send-gate.mjs +559 -1
  26. package/lib/diagnostics/alerts.mjs +49 -0
  27. package/lib/diagnostics/cadence-output-freshness.mjs +288 -0
  28. package/lib/engine/agents/definitions.mjs +343 -0
  29. package/lib/engine/agents/persist.mjs +275 -0
  30. package/lib/engine/agents/runtime.mjs +748 -0
  31. package/lib/engine/agents/usage.mjs +95 -0
  32. package/lib/engine/auth-status.mjs +139 -0
  33. package/lib/engine/budget.mjs +194 -0
  34. package/lib/engine/cli.mjs +1204 -0
  35. package/lib/engine/commands/index.mjs +269 -0
  36. package/lib/engine/context/budget.mjs +219 -0
  37. package/lib/engine/context/cache.mjs +125 -0
  38. package/lib/engine/context/child-env.mjs +215 -0
  39. package/lib/engine/context/compaction.mjs +342 -0
  40. package/lib/engine/context/images.mjs +90 -0
  41. package/lib/engine/context/instructions.mjs +327 -0
  42. package/lib/engine/context/lazy-instructions.mjs +169 -0
  43. package/lib/engine/context/manager.mjs +182 -0
  44. package/lib/engine/context/real-path.mjs +91 -0
  45. package/lib/engine/context/secret-values.mjs +163 -0
  46. package/lib/engine/context/settings.mjs +274 -0
  47. package/lib/engine/context/stream-input.mjs +159 -0
  48. package/lib/engine/guard.mjs +152 -0
  49. package/lib/engine/hooks.mjs +713 -0
  50. package/lib/engine/loop.mjs +560 -0
  51. package/lib/engine/mcp/client.mjs +254 -0
  52. package/lib/engine/mcp/config.mjs +301 -0
  53. package/lib/engine/mcp/http.mjs +201 -0
  54. package/lib/engine/mcp/index.mjs +146 -0
  55. package/lib/engine/mcp/jsonrpc.mjs +147 -0
  56. package/lib/engine/mcp/naming.mjs +66 -0
  57. package/lib/engine/mcp/resources.mjs +89 -0
  58. package/lib/engine/mcp/results.mjs +133 -0
  59. package/lib/engine/mcp/stdio.mjs +137 -0
  60. package/lib/engine/mcp/supervisor.mjs +116 -0
  61. package/lib/engine/messages.mjs +104 -0
  62. package/lib/engine/output/json.mjs +164 -0
  63. package/lib/engine/output/stream-json.mjs +266 -0
  64. package/lib/engine/permissions.mjs +845 -0
  65. package/lib/engine/process-identity.mjs +164 -0
  66. package/lib/engine/process-tree.mjs +551 -0
  67. package/lib/engine/prompt.mjs +60 -0
  68. package/lib/engine/session/store.mjs +299 -0
  69. package/lib/engine/session-runtime/args.mjs +97 -0
  70. package/lib/engine/session-runtime/host.mjs +143 -0
  71. package/lib/engine/session-runtime/inbox.mjs +122 -0
  72. package/lib/engine/session-runtime/notifications.mjs +129 -0
  73. package/lib/engine/session-runtime/registry.mjs +328 -0
  74. package/lib/engine/session-runtime/runner.mjs +344 -0
  75. package/lib/engine/session-runtime/socket.mjs +212 -0
  76. package/lib/engine/session-runtime/wakeup.mjs +115 -0
  77. package/lib/engine/skills/index.mjs +321 -0
  78. package/lib/engine/tools/bash-background.mjs +533 -0
  79. package/lib/engine/tools/bash.mjs +216 -0
  80. package/lib/engine/tools/edit.mjs +97 -0
  81. package/lib/engine/tools/glob.mjs +81 -0
  82. package/lib/engine/tools/grep.mjs +224 -0
  83. package/lib/engine/tools/index.mjs +84 -0
  84. package/lib/engine/tools/list-agents.mjs +32 -0
  85. package/lib/engine/tools/ls.mjs +127 -0
  86. package/lib/engine/tools/monitor.mjs +82 -0
  87. package/lib/engine/tools/notebook-edit.mjs +218 -0
  88. package/lib/engine/tools/read.mjs +103 -0
  89. package/lib/engine/tools/schedule-wakeup.mjs +45 -0
  90. package/lib/engine/tools/schema.mjs +144 -0
  91. package/lib/engine/tools/send-message.mjs +77 -0
  92. package/lib/engine/tools/session.mjs +70 -0
  93. package/lib/engine/tools/todo.mjs +144 -0
  94. package/lib/engine/tools/toolsearch.mjs +217 -0
  95. package/lib/engine/tools/walk.mjs +193 -0
  96. package/lib/engine/tools/web-switch.mjs +31 -0
  97. package/lib/engine/tools/webfetch-html.mjs +387 -0
  98. package/lib/engine/tools/webfetch-net.mjs +340 -0
  99. package/lib/engine/tools/webfetch.mjs +198 -0
  100. package/lib/engine/tools/websearch.mjs +91 -0
  101. package/lib/engine/tools/workflow.mjs +95 -0
  102. package/lib/engine/tools/write.mjs +76 -0
  103. package/lib/engine/tui/line-editor.mjs +137 -0
  104. package/lib/engine/tui/render.mjs +86 -0
  105. package/lib/engine/tui/tui.mjs +274 -0
  106. package/lib/engine/wire/anthropic-messages.mjs +263 -0
  107. package/lib/engine/wire/effort.mjs +36 -0
  108. package/lib/engine/wire/errors.mjs +496 -0
  109. package/lib/engine/wire/http.mjs +441 -0
  110. package/lib/engine/wire/index.mjs +76 -0
  111. package/lib/engine/wire/openai-chat.mjs +332 -0
  112. package/lib/engine/wire/prompt-cache.mjs +79 -0
  113. package/lib/engine/wire/search.mjs +140 -0
  114. package/lib/engine/wire/sse.mjs +114 -0
  115. package/lib/engine/wire/stall.mjs +349 -0
  116. package/lib/engine/wire/token-provider.mjs +175 -0
  117. package/lib/engine/wire/usage.mjs +192 -0
  118. package/lib/engine/workflow/host.mjs +524 -0
  119. package/lib/engine/workflow/journal.mjs +188 -0
  120. package/lib/engine/workflow/json-schema.mjs +171 -0
  121. package/lib/engine/workflow/meta.mjs +329 -0
  122. package/lib/engine/workflow/notifications.mjs +52 -0
  123. package/lib/engine/workflow/runtime.mjs +447 -0
  124. package/lib/engine/workflow/sandbox.mjs +534 -0
  125. package/lib/engine/workflow/worker.mjs +141 -0
  126. package/lib/engine/workflow/worktree.mjs +74 -0
  127. package/lib/execution/disposition.mjs +1 -1
  128. package/lib/execution/intake.mjs +10 -0
  129. package/lib/execution/surface-policy.mjs +15 -0
  130. package/lib/learning/curator.mjs +8 -6
  131. package/lib/learning/reflect.mjs +8 -6
  132. package/lib/model-router/catalog/cohort.yaml +137 -0
  133. package/lib/model-router/catalog.mjs +118 -1
  134. package/lib/model-router/failover.mjs +67 -16
  135. package/lib/model-router/llm-task.mjs +39 -3
  136. package/lib/model-router/resolve.mjs +89 -3
  137. package/lib/model-router/spawn.mjs +46 -47
  138. package/lib/model-router/taxonomy.mjs +126 -4
  139. package/lib/org/cost-sync.mjs +141 -11
  140. package/lib/org/inbound/broadcast.mjs +289 -0
  141. package/lib/org/inbound/collective.mjs +375 -0
  142. package/lib/org/inbound/directedness.mjs +96 -8
  143. package/lib/org/inbound/facts.mjs +78 -2
  144. package/lib/org/inbound/project.mjs +22 -0
  145. package/lib/org/inbound/surfaces.mjs +14 -0
  146. package/lib/org/llm-token.mjs +879 -0
  147. package/lib/org/mesh.mjs +61 -0
  148. package/lib/org/messaging.mjs +3 -1
  149. package/lib/org/protocol.checksum +1 -1
  150. package/lib/org/protocol.mjs +15 -0
  151. package/lib/org/quota.mjs +520 -0
  152. package/lib/org/tool-surface.mjs +104 -16
  153. package/lib/org/ui-parity.mjs +16 -1
  154. package/lib/org/work-ledger.mjs +37 -6
  155. package/lib/rate-guard.mjs +114 -1
  156. package/lib/resource-governor.mjs +41 -6
  157. package/lib/runtime/adapter.mjs +823 -0
  158. package/lib/runtime/child-env.mjs +191 -0
  159. package/lib/runtime/legacy-shell-guard.mjs +97 -0
  160. package/lib/runtime/seat-engine.mjs +162 -0
  161. package/lib/session/ask-ledger.mjs +271 -0
  162. package/lib/session/current-work.mjs +676 -0
  163. package/lib/session/feed-core.mjs +40 -3
  164. package/lib/session/launch-args.mjs +56 -4
  165. package/lib/session/status-summary.mjs +26 -9
  166. package/lib/session/upgrade-notice.mjs +42 -0
  167. package/lib/setup/claude-probe.mjs +117 -13
  168. package/lib/setup/enrich.mjs +13 -10
  169. package/lib/setup/sections/model.mjs +39 -13
  170. package/lib/telemetry/collect.mjs +208 -9
  171. package/lib/upgrade/ignored-drift.mjs +105 -0
  172. package/lib/voice/post-call-brief.mjs +30 -17
  173. package/package.json +13 -3
  174. package/plugins/maestro-skills/skills/board-work.md +5 -0
  175. package/plugins/maestro-skills/skills/inbound-triage.md +56 -15
  176. package/plugins/maestro-skills/skills/main-session.md +18 -7
  177. package/scripts/ci/check-tarball-fidelity.mjs +126 -2
  178. package/scripts/ci/run-tests.mjs +47 -19
  179. package/scripts/cohort-llm/api-key-helper.mjs +92 -0
  180. package/scripts/collective/hook-runner.mjs +29 -2
  181. package/scripts/continuous-monitor.sh +13 -0
  182. package/scripts/cost/track-claude-usage.mjs +15 -0
  183. package/scripts/daemon/agent-daemon.mjs +408 -20
  184. package/scripts/daemon/assurance.mjs +48 -12
  185. package/scripts/daemon/cadence-consumer.mjs +218 -68
  186. package/scripts/daemon/cadence-handlers.mjs +73 -4
  187. package/scripts/daemon/classifier.mjs +75 -26
  188. package/scripts/daemon/context-compiler.mjs +51 -37
  189. package/scripts/daemon/deliver.mjs +30 -1
  190. package/scripts/daemon/dispatcher.mjs +595 -149
  191. package/scripts/daemon/health.mjs +14 -1
  192. package/scripts/daemon/maestro-daemon.mjs +11 -0
  193. package/scripts/daemon/prompt-builder.mjs +24 -0
  194. package/scripts/daemon/responder.mjs +246 -79
  195. package/scripts/daemon/sdk-version.mjs +98 -16
  196. package/scripts/eval/probe-gateway.mjs +635 -0
  197. package/scripts/eval/replay/extract.mjs +270 -0
  198. package/scripts/eval/replay/grade.mjs +260 -0
  199. package/scripts/eval/replay/lib/config.mjs +50 -0
  200. package/scripts/eval/replay/lib/effects.mjs +65 -0
  201. package/scripts/eval/replay/lib/fixture.mjs +188 -0
  202. package/scripts/eval/replay/lib/judge.mjs +72 -0
  203. package/scripts/eval/replay/lib/redact.mjs +136 -0
  204. package/scripts/eval/replay/lib/sandbox.mjs +170 -0
  205. package/scripts/eval/replay/lib/schema-check.mjs +63 -0
  206. package/scripts/eval/replay/lib/transcript.mjs +76 -0
  207. package/scripts/eval/replay/mcp-replay-stub.mjs +101 -0
  208. package/scripts/eval/replay/report.mjs +185 -0
  209. package/scripts/eval/replay/run.mjs +404 -0
  210. package/scripts/fleet/rollout.mjs +1094 -0
  211. package/scripts/hooks/pre-send-audit.sh +36 -245
  212. package/scripts/hooks/pre-write-yaml-validate.mjs +275 -0
  213. package/scripts/hooks/validate-state-yaml.sh +190 -0
  214. package/scripts/huddle/huddle-llm.mjs +361 -0
  215. package/scripts/huddle/huddle-server.mjs +46 -121
  216. package/scripts/local-triggers/autoupdate.sh +448 -78
  217. package/scripts/local-triggers/run-trigger.sh +13 -0
  218. package/scripts/maintenance/pin-integrity.mjs +364 -0
  219. package/scripts/poll-slack-events.sh +41 -9
  220. package/scripts/poller/slack-socket-mode.mjs +28 -3
  221. package/scripts/session/supervisor.mjs +80 -13
  222. package/scripts/spawn-session.sh +13 -0
  223. package/bin/maestro.test.mjs +0 -1574
  224. package/lib/action-executor.test.mjs +0 -871
  225. package/lib/archetype.test.mjs +0 -132
  226. package/lib/assurance/plan-note.test.mjs +0 -234
  227. package/lib/assurance/room-budget.test.mjs +0 -486
  228. package/lib/assurance/tier.test.mjs +0 -174
  229. package/lib/autonomy.test.mjs +0 -66
  230. package/lib/backlog.test.mjs +0 -302
  231. package/lib/backup/policy.test.mjs +0 -305
  232. package/lib/budget-escalate.test.mjs +0 -232
  233. package/lib/budget-guard.envelope.test.mjs +0 -476
  234. package/lib/budget-guard.test.mjs +0 -427
  235. package/lib/cadence-bus-requeue.test.mjs +0 -83
  236. package/lib/cadence-bus-schedule.test.mjs +0 -194
  237. package/lib/cadence-bus.test.mjs +0 -720
  238. package/lib/cadences.test.mjs +0 -230
  239. package/lib/capability/inventory.test.mjs +0 -232
  240. package/lib/capability.test.mjs +0 -78
  241. package/lib/channels/base-adapter.test.mjs +0 -590
  242. package/lib/channels/channels.test.mjs +0 -371
  243. package/lib/channels/contract.test.mjs +0 -162
  244. package/lib/channels/inbox-item.test.mjs +0 -368
  245. package/lib/channels/orgmail/adapter.test.mjs +0 -448
  246. package/lib/channels/pairing.test.mjs +0 -270
  247. package/lib/channels/repeat-suppressor.test.mjs +0 -134
  248. package/lib/channels/slack-adapter.test.mjs +0 -212
  249. package/lib/channels/telegram-adapter.test.mjs +0 -306
  250. package/lib/channels/voice/adapter.test.mjs +0 -278
  251. package/lib/channels/whatsapp/adapter-baileys.test.mjs +0 -359
  252. package/lib/channels/whatsapp/baileys-typing.test.mjs +0 -154
  253. package/lib/charter.test.mjs +0 -89
  254. package/lib/claude-bin.test.mjs +0 -131
  255. package/lib/cli/board.test.mjs +0 -227
  256. package/lib/cli/design.test.mjs +0 -270
  257. package/lib/cli/doctor-checks.test.mjs +0 -336
  258. package/lib/cli/global-setup-extras.test.mjs +0 -462
  259. package/lib/cli/inbox.test.mjs +0 -230
  260. package/lib/cli/session-ack.test.mjs +0 -63
  261. package/lib/cli/session.test.mjs +0 -613
  262. package/lib/collective/capture.test.mjs +0 -121
  263. package/lib/collective/cards.test.mjs +0 -114
  264. package/lib/collective/config.test.mjs +0 -123
  265. package/lib/collective/global-config.test.mjs +0 -220
  266. package/lib/collective/global-skills.test.mjs +0 -126
  267. package/lib/collective/presence.test.mjs +0 -95
  268. package/lib/collective/recall.test.mjs +0 -116
  269. package/lib/collective/vendor-skills.test.mjs +0 -306
  270. package/lib/comms/send-gate.test.mjs +0 -770
  271. package/lib/comms.test.mjs +0 -41
  272. package/lib/context/budget.test.mjs +0 -252
  273. package/lib/context/history-scope.test.mjs +0 -79
  274. package/lib/cost/ledger-row.test.mjs +0 -183
  275. package/lib/design/design-md.test.mjs +0 -318
  276. package/lib/design/fixtures/DESIGN.golden.md +0 -238
  277. package/lib/design/fixtures/PRODUCT.golden.md +0 -67
  278. package/lib/design/fixtures/foundation.json +0 -133
  279. package/lib/design/refresh-gate.test.mjs +0 -144
  280. package/lib/design/write.test.mjs +0 -241
  281. package/lib/diagnostics/alerts.test.mjs +0 -318
  282. package/lib/diagnostics/backup-freshness.test.mjs +0 -185
  283. package/lib/diagnostics/counters.test.mjs +0 -206
  284. package/lib/diagnostics/events.test.mjs +0 -290
  285. package/lib/diagnostics/otel.test.mjs +0 -196
  286. package/lib/diagnostics/trace.test.mjs +0 -251
  287. package/lib/env-compat.test.mjs +0 -104
  288. package/lib/execution/disposition.test.mjs +0 -553
  289. package/lib/execution/drive.test.mjs +0 -270
  290. package/lib/execution/effects.test.mjs +0 -344
  291. package/lib/execution/intake.test.mjs +0 -389
  292. package/lib/execution/journal.test.mjs +0 -261
  293. package/lib/execution/match.test.mjs +0 -235
  294. package/lib/execution/pipeline.test.mjs +0 -392
  295. package/lib/execution/route.test.mjs +0 -186
  296. package/lib/execution/surface-policy.test.mjs +0 -162
  297. package/lib/fs-atomic.test.mjs +0 -72
  298. package/lib/fs-ownership.test.mjs +0 -158
  299. package/lib/goals/admission.test.mjs +0 -164
  300. package/lib/goals/classify.test.mjs +0 -167
  301. package/lib/goals/collaborate.test.mjs +0 -336
  302. package/lib/goals/gaps.test.mjs +0 -284
  303. package/lib/goals/loop.test.mjs +0 -845
  304. package/lib/hooks/bus.test.mjs +0 -387
  305. package/lib/identity/persona.test.mjs +0 -142
  306. package/lib/kpi-sensors.test.mjs +0 -278
  307. package/lib/kpi.test.mjs +0 -244
  308. package/lib/learning/config.test.mjs +0 -75
  309. package/lib/learning/counters.test.mjs +0 -69
  310. package/lib/learning/curator-consolidate.test.mjs +0 -238
  311. package/lib/learning/curator.test.mjs +0 -106
  312. package/lib/learning/reflect.test.mjs +0 -0
  313. package/lib/learning/session-index.test.mjs +0 -125
  314. package/lib/learning/skill-writer.test.mjs +0 -210
  315. package/lib/mandate/audit.test.mjs +0 -195
  316. package/lib/mandate/contract.test.mjs +0 -185
  317. package/lib/mandate/derive.test.mjs +0 -274
  318. package/lib/mandate/model.test.mjs +0 -164
  319. package/lib/mandate/refresh.test.mjs +0 -389
  320. package/lib/mcp/server.test.mjs +0 -426
  321. package/lib/model-router/auth-profiles.test.mjs +0 -580
  322. package/lib/model-router/catalog.test.mjs +0 -385
  323. package/lib/model-router/economics.test.mjs +0 -438
  324. package/lib/model-router/failover.test.mjs +0 -439
  325. package/lib/model-router/health.test.mjs +0 -338
  326. package/lib/model-router/integration-coverage.test.mjs +0 -831
  327. package/lib/model-router/integration.test.mjs +0 -564
  328. package/lib/model-router/ledger.test.mjs +0 -415
  329. package/lib/model-router/llm-task.test.mjs +0 -392
  330. package/lib/model-router/org-credentials.test.mjs +0 -265
  331. package/lib/model-router/pricing-refresh.test.mjs +0 -286
  332. package/lib/model-router/reconcile.test.mjs +0 -316
  333. package/lib/model-router/repair.test.mjs +0 -180
  334. package/lib/model-router/spawn.test.mjs +0 -446
  335. package/lib/model-router/taxonomy.test.mjs +0 -410
  336. package/lib/model-router.test.mjs +0 -1207
  337. package/lib/org/activity.test.mjs +0 -134
  338. package/lib/org/approvals.test.mjs +0 -216
  339. package/lib/org/awareness.test.mjs +0 -159
  340. package/lib/org/board-mine-cache.test.mjs +0 -53
  341. package/lib/org/board.test.mjs +0 -187
  342. package/lib/org/bootstrap-context.test.mjs +0 -153
  343. package/lib/org/client.test.mjs +0 -1206
  344. package/lib/org/cohort-client.test.mjs +0 -126
  345. package/lib/org/cost-sync.test.mjs +0 -153
  346. package/lib/org/doctor.test.mjs +0 -346
  347. package/lib/org/engagement-ledger.test.mjs +0 -112
  348. package/lib/org/engagement.test.mjs +0 -739
  349. package/lib/org/handoff.test.mjs +0 -269
  350. package/lib/org/inbound/directedness.test.mjs +0 -668
  351. package/lib/org/inbound/facts.test.mjs +0 -471
  352. package/lib/org/inbound/hydrate.test.mjs +0 -908
  353. package/lib/org/inbound/index.test.mjs +0 -429
  354. package/lib/org/inbound/project.test.mjs +0 -287
  355. package/lib/org/integration-tools.test.mjs +0 -160
  356. package/lib/org/keys.test.mjs +0 -92
  357. package/lib/org/knowledge.test.mjs +0 -326
  358. package/lib/org/leases.test.mjs +0 -235
  359. package/lib/org/mesh-directives.test.mjs +0 -110
  360. package/lib/org/mesh-integration.test.mjs +0 -127
  361. package/lib/org/mesh.test.mjs +0 -400
  362. package/lib/org/messaging.test.mjs +0 -471
  363. package/lib/org/param-contract.test.mjs +0 -477
  364. package/lib/org/policy.test.mjs +0 -237
  365. package/lib/org/protocol.checksum.test.mjs +0 -90
  366. package/lib/org/protocol.test.mjs +0 -323
  367. package/lib/org/push.test.mjs +0 -792
  368. package/lib/org/registry.test.mjs +0 -100
  369. package/lib/org/resource-tools.test.mjs +0 -361
  370. package/lib/org/tool-access.test.mjs +0 -144
  371. package/lib/org/tool-surface-integration.test.mjs +0 -120
  372. package/lib/org/tool-surface.test.mjs +0 -1268
  373. package/lib/org/typing.test.mjs +0 -291
  374. package/lib/org/ui-parity.test.mjs +0 -560
  375. package/lib/org/verify.test.mjs +0 -194
  376. package/lib/org/work-ledger.test.mjs +0 -273
  377. package/lib/plan/adoption-e2e.test.mjs +0 -366
  378. package/lib/plan/budget-enforcement.test.mjs +0 -400
  379. package/lib/plan/compile.test.mjs +0 -382
  380. package/lib/plan/emit.test.mjs +0 -269
  381. package/lib/plan/explain.test.mjs +0 -188
  382. package/lib/prompts/parallelism.test.mjs +0 -177
  383. package/lib/rag/rag.test.mjs +0 -505
  384. package/lib/rate-guard.test.mjs +0 -272
  385. package/lib/reactive-gate.test.mjs +0 -57
  386. package/lib/render.test.mjs +0 -68
  387. package/lib/resource-governor.test.mjs +0 -488
  388. package/lib/scheduling/dynamic-jobs.test.mjs +0 -344
  389. package/lib/scheduling/jitter.test.mjs +0 -140
  390. package/lib/secrets/broker.test.mjs +0 -280
  391. package/lib/secrets/providers.test.mjs +0 -274
  392. package/lib/security/audit-engine.test.mjs +0 -424
  393. package/lib/security/coerce-args.test.mjs +0 -281
  394. package/lib/security/dangerous-tools.test.mjs +0 -68
  395. package/lib/security/external-content.test.mjs +0 -84
  396. package/lib/security/redact.test.mjs +0 -441
  397. package/lib/security/secret-equal.test.mjs +0 -55
  398. package/lib/session/config.test.mjs +0 -92
  399. package/lib/session/feed-core.test.mjs +0 -198
  400. package/lib/session/first-run.test.mjs +0 -121
  401. package/lib/session/frontdoor.test.mjs +0 -205
  402. package/lib/session/handoffs.test.mjs +0 -183
  403. package/lib/session/identity.test.mjs +0 -180
  404. package/lib/session/inbox-claims.test.mjs +0 -286
  405. package/lib/session/launch-args.test.mjs +0 -157
  406. package/lib/session/liveness.test.mjs +0 -100
  407. package/lib/session/status-summary.test.mjs +0 -118
  408. package/lib/session-permissions.test.mjs +0 -120
  409. package/lib/setup/claude-probe.test.mjs +0 -187
  410. package/lib/setup/completeness.test.mjs +0 -110
  411. package/lib/setup/context-pack.test.mjs +0 -89
  412. package/lib/setup/enrich.test.mjs +0 -115
  413. package/lib/setup/enroll-from-cohort.test.mjs +0 -300
  414. package/lib/setup/integration.test.mjs +0 -162
  415. package/lib/setup/io.test.mjs +0 -77
  416. package/lib/setup/runner.test.mjs +0 -132
  417. package/lib/setup/sections/identity.test.mjs +0 -234
  418. package/lib/setup/sections/inventory.test.mjs +0 -198
  419. package/lib/setup/sections/learning.test.mjs +0 -81
  420. package/lib/setup/sections/mandate.test.mjs +0 -388
  421. package/lib/setup/sections/messaging.test.mjs +0 -127
  422. package/lib/setup/sections/model.test.mjs +0 -240
  423. package/lib/setup/sections/org.test.mjs +0 -346
  424. package/lib/setup/sections/orgmail.test.mjs +0 -118
  425. package/lib/setup/sections/recovery.test.mjs +0 -98
  426. package/lib/setup/sections/subagents.test.mjs +0 -429
  427. package/lib/setup/sections/verify.test.mjs +0 -175
  428. package/lib/setup/sot.test.mjs +0 -81
  429. package/lib/setup/state.test.mjs +0 -115
  430. package/lib/singleton.test.mjs +0 -151
  431. package/lib/subagents/cli.test.mjs +0 -389
  432. package/lib/subagents/client.test.mjs +0 -309
  433. package/lib/subagents/gap.test.mjs +0 -234
  434. package/lib/subagents/lock.test.mjs +0 -248
  435. package/lib/subagents/manifest.test.mjs +0 -175
  436. package/lib/subagents/refs.test.mjs +0 -204
  437. package/lib/subagents/resolve.test.mjs +0 -422
  438. package/lib/subagents/schema.test.mjs +0 -328
  439. package/lib/telemetry/alerts.test.mjs +0 -109
  440. package/lib/telemetry/collect.test.mjs +0 -1274
  441. package/lib/tool-definitions-integration.test.mjs +0 -83
  442. package/lib/tool-definitions.test.mjs +0 -437
  443. package/lib/upgrade/global-refresh.test.mjs +0 -65
  444. package/lib/upgrade/launchd-reconcile.test.mjs +0 -272
  445. package/lib/upgrade/post-steps.test.mjs +0 -200
  446. package/lib/upgrade/verify.test.mjs +0 -164
  447. package/lib/util/fetch-timeout.test.mjs +0 -202
  448. package/lib/util/reconnect.test.mjs +0 -369
  449. package/lib/util/unhandled.test.mjs +0 -216
  450. package/lib/voice/outbound.test.mjs +0 -69
  451. package/lib/voice/session-rotation.test.mjs +0 -114
  452. package/lib/voice/stt.test.mjs +0 -226
  453. package/lib/voice/voice.test.mjs +0 -990
  454. package/scripts/cadence/enqueue-cadence-tick.test.mjs +0 -187
  455. package/scripts/ci/check-docs-accuracy.test.mjs +0 -409
  456. package/scripts/ci/check-durable-write-seam.test.mjs +0 -90
  457. package/scripts/ci/check-no-build-artifacts.test.mjs +0 -71
  458. package/scripts/ci/check-no-residual-identity.test.mjs +0 -202
  459. package/scripts/ci/check-skill-packs.test.mjs +0 -495
  460. package/scripts/ci/check-subagent-frontmatter.test.mjs +0 -124
  461. package/scripts/ci/check.test.mjs +0 -194
  462. package/scripts/ci/conformance-org-api.test.mjs +0 -425
  463. package/scripts/cloud-relay/voice/relay-identity.test.mjs +0 -96
  464. package/scripts/collective/hook-runner.test.mjs +0 -173
  465. package/scripts/cost/fleet-digest.test.mjs +0 -207
  466. package/scripts/cost/track-claude-usage-pricing.test.mjs +0 -183
  467. package/scripts/cost/track-claude-usage.test.mjs +0 -148
  468. package/scripts/daemon/agent-daemon-board-mine.test.mjs +0 -96
  469. package/scripts/daemon/agent-daemon-design.test.mjs +0 -238
  470. package/scripts/daemon/agent-daemon-frontdoor.test.mjs +0 -60
  471. package/scripts/daemon/agent-daemon.test.mjs +0 -995
  472. package/scripts/daemon/assurance-e2e.test.mjs +0 -613
  473. package/scripts/daemon/assurance.test.mjs +0 -1791
  474. package/scripts/daemon/board-mirror.test.mjs +0 -165
  475. package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +0 -393
  476. package/scripts/daemon/cadence-consumer-governance.test.mjs +0 -276
  477. package/scripts/daemon/cadence-consumer.test.mjs +0 -776
  478. package/scripts/daemon/cadence-handlers.test.mjs +0 -837
  479. package/scripts/daemon/classifier-identity.test.mjs +0 -137
  480. package/scripts/daemon/classifier.test.mjs +0 -266
  481. package/scripts/daemon/classify-kind.test.mjs +0 -40
  482. package/scripts/daemon/context-compiler.test.mjs +0 -406
  483. package/scripts/daemon/deliver.test.mjs +0 -564
  484. package/scripts/daemon/dispatcher-cooldown.test.mjs +0 -122
  485. package/scripts/daemon/dispatcher-governance.test.mjs +0 -1013
  486. package/scripts/daemon/dispatcher-resume.test.mjs +0 -166
  487. package/scripts/daemon/dispatcher-session-continuity.test.mjs +0 -365
  488. package/scripts/daemon/execution-ladder.test.mjs +0 -470
  489. package/scripts/daemon/goal-steward-cadence.test.mjs +0 -312
  490. package/scripts/daemon/inbox-deferral-session.test.mjs +0 -49
  491. package/scripts/daemon/inbox-deferral.test.mjs +0 -336
  492. package/scripts/daemon/inbox-wake.test.mjs +0 -199
  493. package/scripts/daemon/integration.test.mjs +0 -149
  494. package/scripts/daemon/lib/self-echo.test.mjs +0 -153
  495. package/scripts/daemon/lib/session-router.test.mjs +0 -554
  496. package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
  497. package/scripts/daemon/prompt-builder.test.mjs +0 -556
  498. package/scripts/daemon/responder-cost.test.mjs +0 -68
  499. package/scripts/daemon/responder-history.test.mjs +0 -221
  500. package/scripts/daemon/sdk-version.test.mjs +0 -31
  501. package/scripts/daemon/session-lock.test.mjs +0 -252
  502. package/scripts/daemon/session-outcomes.test.mjs +0 -533
  503. package/scripts/daemon/typing-registry.test.mjs +0 -102
  504. package/scripts/hooks/pre-send-audit.test.mjs +0 -354
  505. package/scripts/huddle/huddle-prompt.test.mjs +0 -176
  506. package/scripts/local-triggers/autoupdate.test.mjs +0 -518
  507. package/scripts/local-triggers/generate-plists.test.mjs +0 -456
  508. package/scripts/media-generation/brand-clause.test.mjs +0 -135
  509. package/scripts/org/send-orgmail.first-contact.test.mjs +0 -102
  510. package/scripts/poller/inbox-privilege-injection.test.mjs +0 -167
  511. package/scripts/poller/inbox-scan-poller.test.mjs +0 -295
  512. package/scripts/poller/lib/cloud-relay-dedup.test.mjs +0 -133
  513. package/scripts/poller/slack-socket-mode.test.mjs +0 -805
  514. package/scripts/poller-launchd/install.test.mjs +0 -243
  515. package/scripts/restore-from-backup.test.mjs +0 -181
  516. package/scripts/session/feed.test.mjs +0 -196
  517. package/scripts/session/supervisor-sh.test.mjs +0 -218
  518. package/scripts/session/supervisor.test.mjs +0 -482
  519. package/scripts/setup/configure-macos.test.mjs +0 -306
  520. package/scripts/setup/gen-subagent-manifest.test.mjs +0 -124
  521. package/scripts/setup/generate-agent-package-json.test.mjs +0 -143
  522. package/scripts/setup/generate-capability.test.mjs +0 -134
  523. package/scripts/setup/init-agent.test.mjs +0 -370
  524. package/scripts/setup/init-skill-marketplace.test.mjs +0 -193
  525. package/scripts/vendor/sync-skill-packs.test.mjs +0 -103
  526. package/scripts/watchdog/memory-watchdog.test.mjs +0 -64
@@ -60,7 +60,9 @@ import { resultTextFromStdout } from "./session-outcomes.mjs";
60
60
  // The ack generator funnels through `claude --print` like every other daemon
61
61
  // model call (CEO directive 2026-04-27: no direct Anthropic API from the
62
62
  // daemon), pinned to the cheapest model with a tight, env-tunable timeout.
63
- import { resolveClaudeBin, augmentedPath, daemonClaudeArgs } from "../../lib/claude-bin.mjs";
63
+ import { resolveClaudeBin } from "../../lib/claude-bin.mjs";
64
+ import { buildSpawn } from "../../lib/runtime/adapter.mjs";
65
+ import { resolveSeatSpawn } from "../../lib/runtime/seat-engine.mjs";
64
66
  import { sessionPermissionArgs } from "../../lib/session-permissions.mjs";
65
67
  // Second self-echo guard: even if an echo somehow reaches the ack decision,
66
68
  // this agent never acknowledges its own message back at itself.
@@ -76,6 +78,7 @@ import * as counters from "../../lib/diagnostics/counters.mjs";
76
78
  // the only composer of interim copy left in the system, and carries the two
77
79
  // copy rules (GENERIC_OPENER, FORWARD_PROMISE) every interim must satisfy.
78
80
  import { replyTier } from "../../lib/assurance/tier.mjs";
81
+ import { isRollCallItem } from "../../lib/org/inbound/broadcast.mjs";
79
82
  import {
80
83
  claimRoomInterim,
81
84
  claimRoomNotice,
@@ -602,6 +605,14 @@ export function shouldAcknowledge(o = {}) {
602
605
  logDispatchGateRefusal(item, "peer-ack-shaped");
603
606
  return { ack: false, reason: "peer-ack-shaped", emitterClass: true, tier };
604
607
  }
608
+ // ── A ROLL-CALL NEVER GETS AN INTERIM ─────────────────────────────────────
609
+ // A human asked the whole room for a line each. Thirteen "checking now"
610
+ // lines followed by thirteen answers is twice the traffic and half the
611
+ // signal; the answer IS the acknowledgement. `ack:false` here makes
612
+ // `interimForbidden` durable on the debt, so no later sweep in any process
613
+ // posts one either. The caller still opens the debt — silence about timing
614
+ // is not silence about outcome.
615
+ if (isRollCallItem(item)) return { ack: false, reason: "roll-call", tier };
605
616
  // ── THE TIER ──────────────────────────────────────────────────────────────
606
617
  // The answer tier is silence by construction: the reply arrives in this turn,
607
618
  // so an interim in front of it is the content-free traffic this whole change
@@ -650,21 +661,46 @@ const CLAUDE_BIN = resolveClaudeBin();
650
661
  * same spawn-not-exec injection hygiene) rather than importing it, because the
651
662
  * classifier's 30s budget is a different promise from this path's ~5s one.
652
663
  */
664
+ /**
665
+ * The ack lane's spawn from the runtime adapter (lane "ack"): --print
666
+ * <permissions> <MCP by source> --model <m> --append-system-prompt <sys>, prompt
667
+ * on stdin; stock env posture (stale API keys stripped, PATH that can find the
668
+ * binary's runtime under launchd).
669
+ *
670
+ * W4-E1 (CF-44): `seat` is the seat's engine resolved once for this spawn. A
671
+ * claude seat adds no fields (unchanged); an engine-cohort seat runs `cli.mjs run`.
672
+ * @param {{systemPrompt:string, userPrompt:string, env?:object, bin?:string, seat?:{fields:object}}} o
673
+ */
674
+ export function ackSpawn({ systemPrompt, userPrompt, env = process.env, bin = CLAUDE_BIN, seat = { fields: {} } }) {
675
+ return buildSpawn({
676
+ lane: "ack",
677
+ bin,
678
+ model: ACK_MODEL,
679
+ systemPrompt,
680
+ prompt: userPrompt,
681
+ permissions: sessionPermissionArgs({ source: "ack" }),
682
+ mcp: { source: "ack" },
683
+ env,
684
+ ...seat.fields,
685
+ });
686
+ }
687
+
653
688
  function runAckModel(systemPrompt, userPrompt, o = {}) {
689
+ // W4-E1: the seat's engine, once per spawn (a claude seat reads config/agent.json only).
690
+ return resolveSeatSpawn(AGENT_REPO_DIR).then((seat) => runAckModelWith(seat, systemPrompt, userPrompt, o));
691
+ }
692
+
693
+ function runAckModelWith(seat, systemPrompt, userPrompt, o = {}) {
654
694
  const timeoutMs = Number.isFinite(o.timeoutMs) ? o.timeoutMs : ACK_GEN_TIMEOUT_MS;
655
695
  return new Promise((resolvePromise, rejectPromise) => {
656
- const args = [
657
- "--print",
658
- ...sessionPermissionArgs({ source: "ack" }),
659
- ...daemonClaudeArgs(undefined, { source: "ack" }),
660
- "--model", ACK_MODEL,
661
- "--append-system-prompt", systemPrompt,
662
- ];
663
- const proc = spawn(CLAUDE_BIN, args, {
696
+ const spawnSpec = ackSpawn({ systemPrompt, userPrompt, seat });
697
+ if (!spawnSpec.ok) {
698
+ rejectPromise(new Error(`claude CLI unavailable: ${spawnSpec.error.message}`));
699
+ return;
700
+ }
701
+ const proc = spawn(spawnSpec.bin, spawnSpec.argv, {
664
702
  stdio: ["pipe", "pipe", "pipe"],
665
- // Keychain OAuth (Max subscription); strip stale API keys; PATH that can
666
- // actually find the binary's runtime under launchd.
667
- env: { ...process.env, PATH: augmentedPath(), ANTHROPIC_API_KEY: "", ANTHROPIC_AUTH_TOKEN: "" },
703
+ env: spawnSpec.env,
668
704
  });
669
705
  let stdout = "";
670
706
  let settled = false;
@@ -94,7 +94,7 @@ import {
94
94
  import { readFrontDoorState, shouldHandOffTick } from "../../lib/session/frontdoor.mjs";
95
95
  import { writeHandoff, listHandoffs, expireHandoffs, pruneHandoffs, handoffPaths } from "../../lib/session/handoffs.mjs";
96
96
  import { writeFileAtomic } from "../../lib/fs-atomic.mjs";
97
- import { resolveClaudeBin as sharedResolveClaude, augmentedPath, daemonClaudeArgs } from "../../lib/claude-bin.mjs";
97
+ import { resolveClaudeBin as sharedResolveClaude } from "../../lib/claude-bin.mjs";
98
98
  import { getCadenceDef } from "./cadence-handlers.mjs";
99
99
  import { obligationAllowedUnderPosture } from "../../lib/plan/compile.mjs";
100
100
  import { isHumanLaneCadence } from "../../lib/cadences.mjs";
@@ -112,7 +112,10 @@ import {
112
112
  loadRoutingConfig,
113
113
  resolveChain,
114
114
  } from "../../lib/model-router.mjs";
115
- import { buildChildEnv } from "../../lib/model-router/spawn.mjs";
115
+ import { buildSpawn, admitSpawn, isCohortRouted, readSeatEngine } from "../../lib/runtime/adapter.mjs";
116
+ import { seatEngineFields, engineRunShape, seatTokenSync } from "../../lib/runtime/seat-engine.mjs";
117
+ import { seatCohortClients } from "../../lib/org/llm-token.mjs";
118
+ import { COHORT_LLM_BILLED_BY } from "../../lib/org/cost-sync.mjs";
116
119
  import { budgetLadder, batchLaneFor, spawnKnobsFor } from "../../lib/model-router/economics.mjs";
117
120
  import * as budgetGuardForBand from "../../lib/budget-guard.mjs";
118
121
  // WS4 — the consumer is one of the spawn sources, so it honours the SAME
@@ -214,24 +217,38 @@ export function parseUsageFromStdout(stdoutPath) {
214
217
  }
215
218
  if (!obj || typeof obj !== "object") return { ok: false, reason: "no-json-object" };
216
219
 
220
+ // A Cohort Engine result names the gateway requests that served it.
221
+ const cohort = obj.cohort && typeof obj.cohort === "object" ? obj.cohort : null;
222
+ const cohortMeta = cohort
223
+ ? { servedByCohort: true, requestIds: Array.isArray(cohort.requestIds) ? cohort.requestIds.filter((x) => typeof x === "string" && x) : [] }
224
+ : {};
225
+
217
226
  const usage = obj.usage && typeof obj.usage === "object" ? obj.usage : null;
218
- if (!usage) return { ok: false, reason: "no-usage-field" };
227
+ if (!usage) return { ok: false, reason: "no-usage-field", ...cohortMeta };
219
228
 
220
229
  const inputTokens = Number(usage.input_tokens);
221
230
  const outputTokens = Number(usage.output_tokens);
222
231
  if (!Number.isFinite(inputTokens) || !Number.isFinite(outputTokens)) {
223
- return { ok: false, reason: "non-numeric-tokens" };
232
+ return { ok: false, reason: "non-numeric-tokens", ...cohortMeta };
224
233
  }
225
234
 
226
- const out = { ok: true, inputTokens, outputTokens };
235
+ const out = { ok: true, inputTokens, outputTokens, ...cohortMeta };
227
236
  const cacheRead = Number(usage.cache_read_input_tokens);
228
237
  if (Number.isFinite(cacheRead)) out.cacheReadTokens = cacheRead;
229
238
  // Cache CREATION tokens bill at 1.25x input; dropping them was a large slice
230
239
  // of the gap between the local estimate and the CLI's authoritative cost.
231
240
  const cacheWrite = Number(usage.cache_creation_input_tokens);
232
241
  if (Number.isFinite(cacheWrite)) out.cacheWriteTokens = cacheWrite;
233
- const totalCost = Number(obj.total_cost_usd);
234
- if (Number.isFinite(totalCost) && totalCost >= 0) out.totalCostUsd = totalCost;
242
+ // A NUMBER, not anything Number() will coerce. The Cohort Engine writes
243
+ // `total_cost_usd: null` when not every request reported gateway cost
244
+ // (lib/engine/output/json.mjs), and `Number(null)` is 0 — finite, >= 0 —
245
+ // which recorded such a cadence as $0.00 authoritative: the free-looking
246
+ // row the parse-failure branch below exists never to write. This consumer
247
+ // spawns the engine on an engine=cohort seat (engineRunShape above), so the
248
+ // null is reachable here exactly as in dispatcher.mjs#parseUsageFromText
249
+ // and responder.mjs#responderCostArgs. (engine-envelope-parity.test.mjs)
250
+ const totalCost = obj.total_cost_usd;
251
+ if (typeof totalCost === "number" && Number.isFinite(totalCost) && totalCost >= 0) out.totalCostUsd = totalCost;
235
252
  // The CLI may report the resolved model (e.g. "claude-sonnet-4-6"); map it to
236
253
  // the tracker's coarse class so pricing lookup stays correct.
237
254
  if (typeof obj.model === "string" && obj.model) {
@@ -363,7 +380,7 @@ export function renderCadencePromptBody(agentRoot, body) {
363
380
  * so non-zero exits remain diagnosable after the fact. The last ~4 KB of
364
381
  * stderr is also captured in-memory and surfaced on the failure event.
365
382
  */
366
- function realSpawnSession({ agentRoot, cadence, promptPath, timeoutMs, log }) {
383
+ function realSpawnSession({ agentRoot, cadence, promptPath, timeoutMs, log, target: gatedTarget, engine: gatedEngine, cohortToken: gatedToken, cohortBaseUrl: gatedBaseUrl }) {
367
384
  return new Promise((resolveOut) => {
368
385
  const fullPrompt = join(agentRoot, promptPath);
369
386
  if (!existsSync(fullPrompt)) {
@@ -392,64 +409,81 @@ function realSpawnSession({ agentRoot, cadence, promptPath, timeoutMs, log }) {
392
409
  // Route the cadence sub-session's model through the v2 router (opt-in). When
393
410
  // a backend is chosen we pass its --model flag + spawn knobs; otherwise the
394
411
  // argv stays exactly as before (no --model → stock CLI default model).
395
- const target = routeCadenceSpawn(agentRoot, cadence);
396
- const routedArgs = [];
412
+ // The consumer's cohort gate already resolved (and admitted) the target;
413
+ // re-resolving here could pick a different one. undefined = no gate ran.
414
+ const target = gatedTarget !== undefined ? gatedTarget : routeCadenceSpawn(agentRoot, cadence);
397
415
  if (target) {
398
- if (target.maxTurns != null && Number.isFinite(Number(target.maxTurns))) {
399
- routedArgs.push("--max-turns", String(target.maxTurns));
400
- }
401
- if (target.effort) routedArgs.push("--effort", String(target.effort));
402
- if (target.agentsJson) {
403
- routedArgs.push("--agents", typeof target.agentsJson === "string" ? target.agentsJson : JSON.stringify(target.agentsJson));
404
- }
405
- if (target.modelFlag) routedArgs.push("--model", String(target.modelFlag));
406
416
  log({ level: "info", stage: "cadence_routed", cadence, decision_id: target.decisionId, backend: target.backend, model: target.model, transport: target.transport, lane: target.lane, explain: target.explain });
407
417
  }
408
418
 
409
- // --output-format json makes stdout a single JSON object carrying the real
410
- // token usage (and total_cost_usd) for this run. The cadence sub-session's
411
- // stdout is redirected to stdoutPath (below) and nothing parses it for
412
- // CONTENT — only this function reads it back, to record a TRUTHFUL cost row
413
- // (recovery C1). Without this the ledger logged input/output tokens 0 → the
414
- // fleet's only spend control summed $0 forever.
415
- // The cadence PROMPT (body) stays the LAST positional arg; routed model +
416
- // knob flags slot before it (after the daemon args), mirroring the dispatcher.
417
- const args = ["--print", "--output-format", "json", ...permissionArgs, ...daemonClaudeArgs(), ...routedArgs, body];
418
- // PATH augmented via lib/claude-bin.mjs so subsession can find jq/node.
419
- let env = {
420
- ...process.env,
421
- AGENT_ROOT: agentRoot,
422
- AGENT_DIR: agentRoot,
423
- PATH: augmentedPath(),
424
- };
425
- // v2 third-party retarget: build the child env through the execution layer's
426
- // §7.3 allowlist scrub so no foreign credential leaks into the third-party
427
- // session (buildChildEnv injects ANTHROPIC_API_KEY="" for a retarget). We
428
- // preserve the cadence-specific env (AGENT_ROOT/AGENT_DIR/PATH) on top.
429
- const retargeting = !!(target && target.envForSpawn && target.envForSpawn.ANTHROPIC_BASE_URL);
430
- if (retargeting) {
431
- env = {
432
- ...buildChildEnv(env, target.envForSpawn),
433
- AGENT_ROOT: agentRoot,
434
- AGENT_DIR: agentRoot,
435
- PATH: augmentedPath(),
436
- };
437
- } else {
438
- // Auth handling. Claude Code authenticates via macOS Keychain
439
- // (OAuth from the user's Pro/Max subscription) when no API key is
440
- // set, OR via the ANTHROPIC_API_KEY env var when one is present.
441
- // If the env key is present BUT looks like a placeholder / empty
442
- // string, we strip it so claude can fall back to Keychain OAuth.
443
- // Set MAESTRO_PREFER_SUBSCRIPTION_AUTH=1 in .env to always strip
444
- // the API key (force subscription auth) — useful when the agent
445
- // owns a Claude Code Pro/Max subscription and shouldn't burn API
446
- // credits for routine ticks.
447
- const preferSubscription = process.env.MAESTRO_PREFER_SUBSCRIPTION_AUTH === "1";
448
- const apiKey = env.ANTHROPIC_API_KEY || "";
449
- if (preferSubscription || !apiKey.trim() || /^(your-api-key|placeholder|xxx+|sk-ant-xxx)/i.test(apiKey)) {
450
- delete env.ANTHROPIC_API_KEY;
419
+ // argv + env come from the runtime adapter (lib/runtime/adapter.mjs, lane
420
+ // "cadence"). What it builds, unchanged from the inline version:
421
+ // argv --print --output-format json <permissions> <daemon MCP args>
422
+ // <router knobs> [--model <flag>] <body>
423
+ // --output-format json makes stdout one JSON object carrying the real
424
+ // token usage, read back below only to record a TRUTHFUL cost row
425
+ // (recovery C1). The body stays the LAST positional arg; no router →
426
+ // no knobs and no --model (the CLI default model).
427
+ // env {...process.env, AGENT_ROOT, AGENT_DIR, PATH (augmented)}. Stock:
428
+ // ANTHROPIC_API_KEY is deleted when MAESTRO_PREFER_SUBSCRIPTION_AUTH=1
429
+ // or the key is empty/a placeholder, so claude rides the seat's
430
+ // subscription credential. A v2 retarget goes through the one runtime
431
+ // env builder (foreign credentials AND the OAuth token scrubbed,
432
+ // ANTHROPIC_API_KEY="", CLAUDE_CODE_ATTRIBUTION_HEADER=0) with the
433
+ // cadence vars re-applied on top.
434
+ // W4-E1 (CF-44): the engine the gate resolved for this tick (else read now,
435
+ // once). An engine-cohort seat runs `cli.mjs run` with the admitted seat
436
+ // token and no retarget; a claude seat's input is exactly as before.
437
+ const engine = gatedEngine === "cohort" || gatedEngine === "claude" ? gatedEngine : readSeatEngine(agentRoot);
438
+ const engineShape = engine === "cohort" ? engineRunShape({ target, laneModel: null }) : null;
439
+ let engineToken = null;
440
+ let engineBaseUrl = null;
441
+ if (engineShape) {
442
+ engineToken = gatedToken || null;
443
+ engineBaseUrl = gatedBaseUrl || null;
444
+ if (!engineToken || !engineBaseUrl) {
445
+ try {
446
+ const seatToken = seatCohortClients({ agentRoot }).token;
447
+ if (!engineToken) engineToken = seatTokenSync(seatToken);
448
+ if (!engineBaseUrl) engineBaseUrl = seatToken.baseUrl || null;
449
+ } catch { /* no seat key: the adapter refuses the spawn as a value */ }
451
450
  }
452
451
  }
452
+ const spawnInput = {
453
+ lane: "cadence",
454
+ bin,
455
+ prompt: body,
456
+ permissions: permissionArgs,
457
+ mcp: {},
458
+ knobs: engineShape ? engineShape.knobs : target,
459
+ model: engineShape ? engineShape.model : target && target.modelFlag ? target.modelFlag : null,
460
+ agentRoot,
461
+ env: process.env,
462
+ retarget: engineShape ? engineShape.retarget : (target && target.envForSpawn) || {},
463
+ ...seatEngineFields({ engine, agentRoot, token: engineToken, baseUrl: engineBaseUrl }),
464
+ };
465
+ let spawnSpec = buildSpawn(spawnInput);
466
+ let refusedRetarget = null;
467
+ if (!spawnSpec.ok && /^retarget_/.test(spawnSpec.error.code)) {
468
+ // Refused retarget (no third-party token): run the tick exactly as an
469
+ // unrouted spawn instead of handing that backend the seat's credential.
470
+ log({ level: "warn", stage: "cadence_retarget_refused", cadence, decision_id: target && target.decisionId, code: spawnSpec.error.code, error: spawnSpec.error.message });
471
+ refusedRetarget = spawnSpec.error;
472
+ spawnSpec = buildSpawn({ ...spawnInput, knobs: null, model: null, retarget: {} });
473
+ }
474
+ // CF-11: after a refusal the run is the unrouted stock session — the cost
475
+ // row must not carry the refused backend's model.
476
+ const costTarget = refusedRetarget ? null : target;
477
+ // The breaker and billing flags follow the engine as well as routing (W4-E1).
478
+ const servedByCohort = engine === "cohort" || (!refusedRetarget && isCohortRouted({ target }));
479
+ if (!spawnSpec.ok) {
480
+ resolveOut({ ok: false, exit_code: -5, cohort_routed: servedByCohort, error: `runtime unavailable: ${spawnSpec.error.message}` });
481
+ return;
482
+ }
483
+ const args = spawnSpec.argv;
484
+ // The adapter's binary: the claude binary resolved above, or node for the engine.
485
+ const spawnBin = spawnSpec.bin;
486
+ const env = spawnSpec.env;
453
487
  const started = Date.now();
454
488
 
455
489
  // Per-run log file. Pattern is short enough to be tail-friendly.
@@ -462,7 +496,7 @@ function realSpawnSession({ agentRoot, cadence, promptPath, timeoutMs, log }) {
462
496
  const stdoutFd = openSync(stdoutPath, "a");
463
497
  const stderrFd = openSync(stderrPath, "a");
464
498
 
465
- log({ level: "info", stage: "subsession_spawn", cadence, bin, stdout: stdoutPath, stderr: stderrPath });
499
+ log({ level: "info", stage: "subsession_spawn", cadence, bin: spawnBin, ...(engine === "cohort" ? { engine } : {}), stdout: stdoutPath, stderr: stderrPath });
466
500
 
467
501
  let child;
468
502
  try {
@@ -470,7 +504,7 @@ function realSpawnSession({ agentRoot, cadence, promptPath, timeoutMs, log }) {
470
504
  // 0 ignore (claude --print reads prompt from argv, not stdin)
471
505
  // 1 → file (capture stdout for later inspection)
472
506
  // 2 → file (capture stderr — critical for diagnosing exit-1)
473
- child = spawn(bin, args, { cwd: agentRoot, env, stdio: ["ignore", stdoutFd, stderrFd] });
507
+ child = spawn(spawnBin, args, { cwd: agentRoot, env, stdio: ["ignore", stdoutFd, stderrFd] });
474
508
  } catch (err) {
475
509
  try { closeSync(stdoutFd); closeSync(stderrFd); } catch { /* */ }
476
510
  resolveOut({ ok: false, exit_code: -4, error: `spawn failed: ${err.message}` });
@@ -501,6 +535,10 @@ function realSpawnSession({ agentRoot, cadence, promptPath, timeoutMs, log }) {
501
535
  stderrTail = so.slice(-4096);
502
536
  }
503
537
  } catch { /* file may not exist if spawn ENOENT before fd-redirect */ }
538
+ // stdout's own tail, for the structured cohort-refusal check: an engine
539
+ // run's JSON result lands on stdout even when stderr is not empty.
540
+ let stdoutTail = "";
541
+ try { stdoutTail = readFileSync(stdoutPath, "utf-8").slice(-65_536); } catch { /* */ }
504
542
 
505
543
  // Record a TRUTHFUL cost-ledger row (recovery C1). We parse the run's
506
544
  // `--output-format json` object from stdoutPath to read the REAL token
@@ -518,13 +556,16 @@ function realSpawnSession({ agentRoot, cadence, promptPath, timeoutMs, log }) {
518
556
  "--source", "cadence-consumer",
519
557
  // Prefer the CLI-reported resolved model class; else the routed
520
558
  // model flag (v2); else the historical sonnet default.
521
- "--model", usage.model || (target && target.model) || "sonnet",
559
+ "--model", usage.model || (costTarget && costTarget.model) || "sonnet",
522
560
  "--duration-ms", String(durationMs),
523
561
  "--exit", String(exit_code),
524
562
  ];
525
563
  // Carry the v2 RouteDecision id onto the ledger row (SPEC §4.7) so a
526
564
  // cadence spend row joins its routing decision. Absent on stock-CLI.
527
565
  if (target && target.decisionId) trackerArgs.push("--decision-id", String(target.decisionId));
566
+ // Metered by cohort-llm at the gateway: mark it so org cost-sync skips it.
567
+ if (servedByCohort || usage.servedByCohort) trackerArgs.push("--billed-by", COHORT_LLM_BILLED_BY);
568
+ if (Array.isArray(usage.requestIds) && usage.requestIds.length) trackerArgs.push("--request-ids", usage.requestIds.join(","));
528
569
  if (usage.ok) {
529
570
  trackerArgs.push("--input-tokens", String(usage.inputTokens));
530
571
  trackerArgs.push("--output-tokens", String(usage.outputTokens));
@@ -555,9 +596,11 @@ function realSpawnSession({ agentRoot, cadence, promptPath, timeoutMs, log }) {
555
596
  resolveOut({
556
597
  ok: exit_code === 0,
557
598
  exit_code,
599
+ cohort_routed: servedByCohort,
558
600
  signal: signal || null,
559
601
  duration_ms: durationMs,
560
602
  stderr_tail: stderrTail || null,
603
+ stdout_tail: stdoutTail || null,
561
604
  stdout_path: stdoutPath,
562
605
  stderr_path: stderrPath,
563
606
  });
@@ -604,6 +647,62 @@ export function startConsumer(opts = {}) {
604
647
  const rateGuard = opts.rateGuard || rateGuardModule;
605
648
  const budgetGuard = opts.budgetGuard || budgetGuardModule;
606
649
  const handoffDeadlineMs = opts.handoffDeadlineMs;
650
+ // cohort-llm pre-spawn gate (design C6 / §8.3). `opts.cohort` = {quota, token}
651
+ // (null disables it); `opts.routeCadence` replaces routeCadenceSpawn.
652
+ const routeCadence = typeof opts.routeCadence === "function" ? opts.routeCadence : routeCadenceSpawn;
653
+ let cohortClientsMemo = opts.cohort;
654
+ function cohortClients() {
655
+ if (cohortClientsMemo === undefined) {
656
+ try { cohortClientsMemo = seatCohortClients({ agentRoot }); } catch { cohortClientsMemo = null; }
657
+ }
658
+ return cohortClientsMemo;
659
+ }
660
+
661
+ /**
662
+ * May this cadence's sub-session start on cohort-llm? Not cohort-routed →
663
+ * yes. Cohort-routed → a cohort hold or the quota endpoint decides. Human-lane
664
+ * cadences are NOT exempt: a refused seat would 429 them too.
665
+ *
666
+ * The resolved target rides out on the result (`target`) and is handed to
667
+ * the spawn, so the session that starts is the one that was gated — never a
668
+ * second routing decision that could differ (a band change in between).
669
+ *
670
+ * Failure: if ROUTING throws, the tick runs unrouted (`target: null` — the
671
+ * stock session, which no cohort quota governs), loudly. If ADMISSION throws
672
+ * for a cohort-routed target, the tick is deferred for a short retry — it is
673
+ * never spawned into a gateway that may 429/402 it.
674
+ */
675
+ const COHORT_GATE_ERROR_RETRY_MS = 30_000;
676
+ // The seat's engine (config/agent.json runtime.engine). A seat on engine
677
+ // cohort is cohort-metered whatever routing picks, so it is always gated.
678
+ const seatEngine = typeof opts.seatEngine === "function" ? opts.seatEngine : () => readSeatEngine(agentRoot);
679
+ async function cohortGate(cadence) {
680
+ // W4-E1: the engine is resolved once per tick and rides the result to the spawn.
681
+ const engine = seatEngine();
682
+ let target;
683
+ try {
684
+ target = routeCadence(agentRoot, cadence);
685
+ } catch (err) {
686
+ log({ level: "error", stage: "cohort_gate_route_failed_unrouted", cadence, engine, error: err && err.message });
687
+ // Unrouted on the claude engine is the stock session no cohort quota
688
+ // governs; on engine cohort the session is still cohort-metered.
689
+ if (engine !== "cohort") return { ok: true, cohort: false, target: null, engine };
690
+ target = null;
691
+ }
692
+ if (!isCohortRouted({ engine, target })) return { ok: true, cohort: false, target: target || null, engine };
693
+ try {
694
+ const c = cohortClients();
695
+ const adm = await admitSpawn({ engine, target }, { quota: c && c.quota, token: c && c.token, rateGuard, rateGuardDeps: { agentRoot }, now: nowMs });
696
+ return { ...adm, target, engine };
697
+ } catch (err) {
698
+ log({ level: "error", stage: "cohort_gate_failed_closed", cadence, error: err && err.message });
699
+ const until = nowMs() + COHORT_GATE_ERROR_RETRY_MS;
700
+ return {
701
+ ok: false, cohort: true, target, engine,
702
+ deferral: { kind: "retry", until, code: "cohort_gate_error", message: `cohort-llm admission failed: ${err && err.message}`, refusal: null },
703
+ };
704
+ }
705
+ }
607
706
  const frontDoorStateImpl = typeof opts.frontDoorState === "function"
608
707
  ? opts.frontDoorState
609
708
  : () => readFrontDoorState(agentRoot, { now: nowMs() });
@@ -985,6 +1084,29 @@ export function startConsumer(opts = {}) {
985
1084
  if (!gov.admit) return deferTick(event, gov.reason);
986
1085
  }
987
1086
 
1087
+ // cohort-llm quota gate: a tick whose sub-session cohort-llm would refuse is
1088
+ // requeued, not spawned — and never failTick'd (a spent window is an
1089
+ // upstream gate, not a broken cadence). The refusal is recorded as a cohort
1090
+ // hold, so the requeued tick is gated again without another quota read.
1091
+ let gatedTarget;
1092
+ let gatedEngine;
1093
+ let gatedToken = null;
1094
+ let gatedBaseUrl = null;
1095
+ {
1096
+ const cg = await cohortGate(event.cadence);
1097
+ gatedTarget = cg.target;
1098
+ gatedEngine = cg.engine;
1099
+ gatedToken = cg.token || null;
1100
+ if (gatedEngine === "cohort") { try { gatedBaseUrl = cohortClients()?.token?.baseUrl || null; } catch { gatedBaseUrl = null; } }
1101
+ if (!cg.ok) {
1102
+ const d = cg.deferral || {};
1103
+ return deferTick(event, `cohort:${d.code || d.kind || "refused"}`, {
1104
+ cohort_kind: d.kind || null,
1105
+ until: Number.isFinite(d.until) ? new Date(d.until).toISOString() : null,
1106
+ });
1107
+ }
1108
+ }
1109
+
988
1110
  const def = getCadenceDef(event.cadence);
989
1111
  let promptPath = def?.prompt;
990
1112
  if (!promptPath) {
@@ -1069,6 +1191,12 @@ export function startConsumer(opts = {}) {
1069
1191
  promptPath,
1070
1192
  timeoutMs: maxSpawnMs,
1071
1193
  log,
1194
+ // The target the cohort gate admitted (null = run unrouted). Never re-resolved.
1195
+ target: gatedTarget,
1196
+ // W4-E1: the engine the gate resolved, and the seat token it admitted with.
1197
+ engine: gatedEngine,
1198
+ cohortToken: gatedToken,
1199
+ cohortBaseUrl: gatedBaseUrl,
1072
1200
  });
1073
1201
  } finally {
1074
1202
  activeSubSessions -= 1;
@@ -1087,8 +1215,9 @@ export function startConsumer(opts = {}) {
1087
1215
  stderr_path: result.stderr_path || null,
1088
1216
  });
1089
1217
  recordSubsessionSuccess(event.cadence);
1090
- // WS4: a clean sub-session closes the shared 429 breaker.
1091
- try { rateGuard.recordSuccess(RATE_PROVIDER, { agentRoot }); } catch { /* */ }
1218
+ // WS4: a clean sub-session closes the shared 429 breaker (the cohort hold
1219
+ // for a cohort-served run — never the Anthropic breaker on its behalf).
1220
+ try { rateGuard.recordSuccess(result.cohort_routed || seatEngine() === "cohort" ? rateGuardModule.COHORT_BACKEND : RATE_PROVIDER, { agentRoot }); } catch { /* */ }
1092
1221
  stats.escalated += 1;
1093
1222
  stats.last_decision = "escalated";
1094
1223
  return { ok: true, decision: "escalated", exit_code: result.exit_code };
@@ -1099,10 +1228,31 @@ export function startConsumer(opts = {}) {
1099
1228
  // instead of failTick — a 429 is an upstream gate, not a per-event failure,
1100
1229
  // so it must not burn this cadence's retry budget toward the DLQ.
1101
1230
  const cadenceOut = result.stderr_tail || result.error || result.stdout_tail || "";
1102
- const cadenceUl = rateGuard.classifyUsageLimit?.(cadenceOut, { agentRoot }) ?? { isLimit: false, resetAt: null };
1231
+ // A seat on engine cohort is cohort-served whatever routing picked (the gate's own test).
1232
+ const engineCohort = seatEngine() === "cohort";
1233
+ const cohortServed = Boolean(result.cohort_routed) || engineCohort;
1234
+ const breakerProvider = cohortServed ? rateGuardModule.COHORT_BACKEND : RATE_PROVIDER;
1235
+ // A cohort-llm refusal first: keyed by the "cohort" backend, held until its
1236
+ // resetsAt, never an Anthropic breaker, never a failed attempt. Both streams:
1237
+ // an engine's JSON result is on stdout even when stderr carries text.
1238
+ const refusalText = [result.stderr_tail, result.error, result.stdout_tail].filter(Boolean).join("\n");
1239
+ const cohortRefusal = cohortServed ? (rateGuard.classifyCohortRefusal?.(refusalText) ?? null) : null;
1240
+ if (cohortRefusal) {
1241
+ try {
1242
+ const rec = rateGuard.recordCohortRefusal(cohortRefusal, { agentRoot });
1243
+ log({ level: "warn", stage: "subsession_cohort_refused", id: event.id, cadence: event.cadence, code: cohortRefusal.code, disposition: rec.disposition && rec.disposition.kind, open_until: rec.openUntil });
1244
+ } catch { /* */ }
1245
+ try { const c = cohortClients(); if (c && c.quota && typeof c.quota.invalidate === "function") c.quota.invalidate(); } catch { /* */ }
1246
+ return deferTick(event, `cohort-refused:${cohortRefusal.code}`);
1247
+ }
1248
+ // An engine-cohort lane has no Claude subscription: its English limit text is
1249
+ // never a subscription usage limit (logged; the run fails or backs off below).
1250
+ const cadenceUlText = rateGuard.classifyUsageLimit?.(cadenceOut, { agentRoot }) ?? { isLimit: false, resetAt: null };
1251
+ const cadenceUl = engineCohort ? { isLimit: false, resetAt: null } : cadenceUlText;
1252
+ if (engineCohort && cadenceUlText.isLimit) log({ level: "warn", stage: "subsession_usage_limit_text_ignored", id: event.id, cadence: event.cadence, engine: "cohort" });
1103
1253
  if (cadenceUl.isLimit) {
1104
1254
  try {
1105
- const rec = rateGuard.recordUsageLimit(RATE_PROVIDER, cadenceUl.resetAt, { agentRoot });
1255
+ const rec = rateGuard.recordUsageLimit(breakerProvider, cadenceUl.resetAt, { agentRoot });
1106
1256
  log({ level: "warn", stage: "subsession_usage_limited", id: event.id, cadence: event.cadence, open_until: rec.openUntil, reset_at: rec.resetAt });
1107
1257
  } catch { /* */ }
1108
1258
  // Same handling as a 429: a window-usage limit is a shared, provider-side
@@ -1113,7 +1263,7 @@ export function startConsumer(opts = {}) {
1113
1263
  }
1114
1264
  if (rateGuard.classifyStderr(cadenceOut)) {
1115
1265
  try {
1116
- const rec = rateGuard.recordRateLimit(RATE_PROVIDER, { agentRoot });
1266
+ const rec = rateGuard.recordRateLimit(breakerProvider, { agentRoot });
1117
1267
  log({ level: "warn", stage: "subsession_rate_limited", id: event.id, cadence: event.cadence, open_until: rec.openUntil });
1118
1268
  } catch { /* */ }
1119
1269
  // L2: a 429 is a SHARED, provider-side rate-limit signal — the shared
@@ -500,7 +500,65 @@ async function handleNightlyBackup({ event, agentRoot, log }, opts = {}) {
500
500
  * (`flagged`) logs a warning. The result (drift %) goes in the inline result.
501
501
  * The lib fn is injectable via `opts.reconcileImpl` for tests.
502
502
  */
503
+ /**
504
+ * Regenerate state/dashboards/cost-summary.yaml from the local cost ledger.
505
+ *
506
+ * WHY THIS IS SPAWNED AND NOT IMPORTED: scripts/cost/track-claude-usage.mjs has
507
+ * no import.meta.main guard — it dispatches on process.argv at module scope, so
508
+ * importing it from the daemon would run the CLI's usage branch and
509
+ * process.exit(2) the daemon itself. A child process is the only safe call.
510
+ *
511
+ * Fail-soft by construction: this is a dashboard refresh, not the reconcile, so
512
+ * it must never turn a good reconcile into ok:false. Errors are RETURNED as a
513
+ * named reason, never thrown and never swallowed into silence.
514
+ *
515
+ * Injectable via `opts.summariseImpl` for tests (no child process).
516
+ */
517
+ async function runCostSummarise({ agentRoot, days = 7 }, opts = {}) {
518
+ try {
519
+ const runner =
520
+ opts.summariseImpl ||
521
+ (async () => {
522
+ const { execFile } = await import("node:child_process");
523
+ const script = join(agentRoot, "scripts", "cost", "track-claude-usage.mjs");
524
+ const fallback = join(__dirname, "..", "cost", "track-claude-usage.mjs");
525
+ const target = existsSync(script) ? script : fallback;
526
+ if (!existsSync(target)) return { code: 127, stderr: "tracker-not-found" };
527
+ return new Promise((resolvePromise) => {
528
+ execFile(
529
+ process.execPath,
530
+ [target, "summarise", "--days", String(days)],
531
+ { timeout: 2 * 60 * 1000, maxBuffer: 8 * 1024 * 1024, env: { ...process.env, AGENT_ROOT: agentRoot } },
532
+ (err, stdout, stderr) => {
533
+ resolvePromise({
534
+ code: err ? (typeof err.code === "number" ? err.code : 1) : 0,
535
+ stdout: String(stdout || ""),
536
+ stderr: String(stderr || ""),
537
+ });
538
+ }
539
+ );
540
+ });
541
+ });
542
+ const res = await runner({ agentRoot, days });
543
+ if (!res || res.code !== 0) {
544
+ return { ok: false, reason: (res && String(res.stderr || "").trim().slice(0, 200)) || `exit ${res && res.code}` };
545
+ }
546
+ return { ok: true, reason: null };
547
+ } catch (err) {
548
+ return { ok: false, reason: err && err.message ? err.message : String(err) };
549
+ }
550
+ }
551
+
503
552
  async function handleNightlyCostReconcile({ event, agentRoot, log }, opts = {}) {
553
+ // Refresh the cost dashboard FIRST. cost-summary.yaml is written by nothing
554
+ // else in production — `summarise` had no caller at all, so the file sat at a
555
+ // zeroed scaffold while real ledger rows accumulated beside it. Running it
556
+ // ahead of the reconcile means a reconcile failure cannot also cost the
557
+ // refresh, and its own failure is reported, never thrown.
558
+ const summarised = await runCostSummarise({ agentRoot }, opts);
559
+ if (!summarised.ok && typeof log === "function") {
560
+ log("warn", `[nightly-cost-reconcile] cost-summary regeneration failed: ${summarised.reason}`);
561
+ }
504
562
  try {
505
563
  const reconcile =
506
564
  opts.reconcileImpl ||
@@ -514,6 +572,8 @@ async function handleNightlyCostReconcile({ event, agentRoot, log }, opts = {})
514
572
  ok: true,
515
573
  decision: "inline",
516
574
  cadence: event.cadence,
575
+ costSummaryRegenerated: summarised.ok,
576
+ costSummaryError: summarised.reason,
517
577
  day: res?.day ?? null,
518
578
  driftPct: res?.driftPct ?? null,
519
579
  flagged: !!(res && res.flagged),
@@ -521,7 +581,7 @@ async function handleNightlyCostReconcile({ event, agentRoot, log }, opts = {})
521
581
  providerUSD: res?.providerUSD ?? null,
522
582
  };
523
583
  } catch (err) {
524
- return { ok: false, decision: "inline", cadence: event.cadence, error: err && err.message ? err.message : String(err) };
584
+ return { ok: false, decision: "inline", cadence: event.cadence, costSummaryRegenerated: summarised.ok, costSummaryError: summarised.reason, error: err && err.message ? err.message : String(err) };
525
585
  }
526
586
  }
527
587
 
@@ -588,7 +648,8 @@ async function handleOrgCostSync({ event, agentRoot }, opts = {}) {
588
648
 
589
649
  /**
590
650
  * monitoring-alerts (inline): gather the diagnostics counters, today's cost
591
- * ledger, peer presence, daemon health + backup freshness, evaluate the
651
+ * ledger, peer presence, daemon health + yesterday's cadence output artefacts,
652
+ * evaluate the
592
653
  * behavioral alert rules, and emit any firing alerts to the configured webhook
593
654
  * (config/alerts.yaml `alerts.webhook`) — else log them. Deterministic, no LLM.
594
655
  * Fail-open + fully injectable for tests. This is what makes lib/diagnostics/
@@ -609,8 +670,16 @@ async function handleMonitoringAlerts({ event, agentRoot, log }, opts = {}) {
609
670
  const health = opts.health !== undefined ? opts.health : recoverStaleClaims && readDaemonHealth(agentRoot);
610
671
  const cfg = opts.cfg !== undefined ? opts.cfg : loadAgentCfg(agentRoot);
611
672
  const now = opts.now || Date.now();
673
+ // Read yesterday's cadence OUTPUT artefacts off disk. This is the only input
674
+ // here that is not derived from the cadence bus, and it is deliberately so:
675
+ // the bus logged a timed-out evening wrap as stage="processed" on 29 Aug 2026,
676
+ // so no rule keyed on the bus's own decision can see that class of miss.
677
+ const outputs = opts.outputs !== undefined
678
+ ? opts.outputs
679
+ : (await import("../../lib/diagnostics/cadence-output-freshness.mjs"))
680
+ .cadenceOutputFreshness({ agentRoot, now });
612
681
 
613
- const alerts = A.evaluateAlerts({ snapshot, ledger, presence, health, thresholds: opts.thresholds, now }) || [];
682
+ const alerts = A.evaluateAlerts({ snapshot, ledger, presence, health, outputs, thresholds: opts.thresholds, now }) || [];
614
683
 
615
684
  const sinkUrl = cfg && cfg.alerts && cfg.alerts.webhook;
616
685
  const sink = opts.sink || (async (alert) => {
@@ -622,7 +691,7 @@ async function handleMonitoringAlerts({ event, agentRoot, log }, opts = {}) {
622
691
  }
623
692
  });
624
693
  const res = alerts.length ? await A.emitAlerts(alerts, { sink }) : { attempted: 0, delivered: 0, failed: 0 };
625
- return { ok: true, decision: "inline", cadence: event.cadence, alerts: alerts.length, delivered: res.delivered ?? 0, severities: alerts.map((a) => a.severity) };
694
+ return { ok: true, decision: "inline", cadence: event.cadence, alerts: alerts.length, delivered: res.delivered ?? 0, severities: alerts.map((a) => a.severity), outputsChecked: outputs?.checked ?? 0, outputsMissing: outputs?.missing?.length ?? 0 };
626
695
  } catch (err) {
627
696
  return { ok: false, decision: "inline", cadence: event.cadence, error: err && err.message ? err.message : String(err) };
628
697
  }