@cohortapp/agent-sdk 2.16.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 (529) hide show
  1. package/.claude/settings.json +18 -0
  2. package/.env.example +23 -7
  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/poller-daemon-setup.md +4 -1
  9. package/docs/guides/setup-wizard.md +1 -1
  10. package/docs/runbooks/fleet-rollout.md +156 -0
  11. package/docs/runbooks/mac-mini-bootstrap.md +12 -14
  12. package/lib/action-executor.js +19 -3
  13. package/lib/budget-guard.mjs +279 -3
  14. package/lib/channels/base-adapter.mjs +3 -1
  15. package/lib/channels/contract.mjs +2 -1
  16. package/lib/channels/inbox-item.mjs +8 -0
  17. package/lib/claude-bin.mjs +5 -6
  18. package/lib/cli/doctor-checks.mjs +141 -10
  19. package/lib/cli/global-setup-extras.mjs +5 -1
  20. package/lib/cli/inbox.mjs +100 -15
  21. package/lib/cli/seat-auth.mjs +463 -0
  22. package/lib/cli/session.mjs +80 -12
  23. package/lib/collective/capture.mjs +8 -6
  24. package/lib/collective/global-config.mjs +63 -1
  25. package/lib/collective/presence.mjs +142 -5
  26. package/lib/comms/send-gate.mjs +559 -1
  27. package/lib/context/budget.mjs +327 -0
  28. package/lib/context/history-scope.mjs +138 -0
  29. package/lib/diagnostics/alerts.mjs +49 -0
  30. package/lib/diagnostics/cadence-output-freshness.mjs +288 -0
  31. package/lib/engine/agents/definitions.mjs +343 -0
  32. package/lib/engine/agents/persist.mjs +275 -0
  33. package/lib/engine/agents/runtime.mjs +748 -0
  34. package/lib/engine/agents/usage.mjs +95 -0
  35. package/lib/engine/auth-status.mjs +139 -0
  36. package/lib/engine/budget.mjs +194 -0
  37. package/lib/engine/cli.mjs +1204 -0
  38. package/lib/engine/commands/index.mjs +269 -0
  39. package/lib/engine/context/budget.mjs +219 -0
  40. package/lib/engine/context/cache.mjs +125 -0
  41. package/lib/engine/context/child-env.mjs +215 -0
  42. package/lib/engine/context/compaction.mjs +342 -0
  43. package/lib/engine/context/images.mjs +90 -0
  44. package/lib/engine/context/instructions.mjs +327 -0
  45. package/lib/engine/context/lazy-instructions.mjs +169 -0
  46. package/lib/engine/context/manager.mjs +182 -0
  47. package/lib/engine/context/real-path.mjs +91 -0
  48. package/lib/engine/context/secret-values.mjs +163 -0
  49. package/lib/engine/context/settings.mjs +274 -0
  50. package/lib/engine/context/stream-input.mjs +159 -0
  51. package/lib/engine/guard.mjs +152 -0
  52. package/lib/engine/hooks.mjs +713 -0
  53. package/lib/engine/loop.mjs +560 -0
  54. package/lib/engine/mcp/client.mjs +254 -0
  55. package/lib/engine/mcp/config.mjs +301 -0
  56. package/lib/engine/mcp/http.mjs +201 -0
  57. package/lib/engine/mcp/index.mjs +146 -0
  58. package/lib/engine/mcp/jsonrpc.mjs +147 -0
  59. package/lib/engine/mcp/naming.mjs +66 -0
  60. package/lib/engine/mcp/resources.mjs +89 -0
  61. package/lib/engine/mcp/results.mjs +133 -0
  62. package/lib/engine/mcp/stdio.mjs +137 -0
  63. package/lib/engine/mcp/supervisor.mjs +116 -0
  64. package/lib/engine/messages.mjs +104 -0
  65. package/lib/engine/output/json.mjs +164 -0
  66. package/lib/engine/output/stream-json.mjs +266 -0
  67. package/lib/engine/permissions.mjs +845 -0
  68. package/lib/engine/process-identity.mjs +164 -0
  69. package/lib/engine/process-tree.mjs +551 -0
  70. package/lib/engine/prompt.mjs +60 -0
  71. package/lib/engine/session/store.mjs +299 -0
  72. package/lib/engine/session-runtime/args.mjs +97 -0
  73. package/lib/engine/session-runtime/host.mjs +143 -0
  74. package/lib/engine/session-runtime/inbox.mjs +122 -0
  75. package/lib/engine/session-runtime/notifications.mjs +129 -0
  76. package/lib/engine/session-runtime/registry.mjs +328 -0
  77. package/lib/engine/session-runtime/runner.mjs +344 -0
  78. package/lib/engine/session-runtime/socket.mjs +212 -0
  79. package/lib/engine/session-runtime/wakeup.mjs +115 -0
  80. package/lib/engine/skills/index.mjs +321 -0
  81. package/lib/engine/tools/bash-background.mjs +533 -0
  82. package/lib/engine/tools/bash.mjs +216 -0
  83. package/lib/engine/tools/edit.mjs +97 -0
  84. package/lib/engine/tools/glob.mjs +81 -0
  85. package/lib/engine/tools/grep.mjs +224 -0
  86. package/lib/engine/tools/index.mjs +84 -0
  87. package/lib/engine/tools/list-agents.mjs +32 -0
  88. package/lib/engine/tools/ls.mjs +127 -0
  89. package/lib/engine/tools/monitor.mjs +82 -0
  90. package/lib/engine/tools/notebook-edit.mjs +218 -0
  91. package/lib/engine/tools/read.mjs +103 -0
  92. package/lib/engine/tools/schedule-wakeup.mjs +45 -0
  93. package/lib/engine/tools/schema.mjs +144 -0
  94. package/lib/engine/tools/send-message.mjs +77 -0
  95. package/lib/engine/tools/session.mjs +70 -0
  96. package/lib/engine/tools/todo.mjs +144 -0
  97. package/lib/engine/tools/toolsearch.mjs +217 -0
  98. package/lib/engine/tools/walk.mjs +193 -0
  99. package/lib/engine/tools/web-switch.mjs +31 -0
  100. package/lib/engine/tools/webfetch-html.mjs +387 -0
  101. package/lib/engine/tools/webfetch-net.mjs +340 -0
  102. package/lib/engine/tools/webfetch.mjs +198 -0
  103. package/lib/engine/tools/websearch.mjs +91 -0
  104. package/lib/engine/tools/workflow.mjs +95 -0
  105. package/lib/engine/tools/write.mjs +76 -0
  106. package/lib/engine/tui/line-editor.mjs +137 -0
  107. package/lib/engine/tui/render.mjs +86 -0
  108. package/lib/engine/tui/tui.mjs +274 -0
  109. package/lib/engine/wire/anthropic-messages.mjs +263 -0
  110. package/lib/engine/wire/effort.mjs +36 -0
  111. package/lib/engine/wire/errors.mjs +496 -0
  112. package/lib/engine/wire/http.mjs +441 -0
  113. package/lib/engine/wire/index.mjs +76 -0
  114. package/lib/engine/wire/openai-chat.mjs +332 -0
  115. package/lib/engine/wire/prompt-cache.mjs +79 -0
  116. package/lib/engine/wire/search.mjs +140 -0
  117. package/lib/engine/wire/sse.mjs +114 -0
  118. package/lib/engine/wire/stall.mjs +349 -0
  119. package/lib/engine/wire/token-provider.mjs +175 -0
  120. package/lib/engine/wire/usage.mjs +192 -0
  121. package/lib/engine/workflow/host.mjs +524 -0
  122. package/lib/engine/workflow/journal.mjs +188 -0
  123. package/lib/engine/workflow/json-schema.mjs +171 -0
  124. package/lib/engine/workflow/meta.mjs +329 -0
  125. package/lib/engine/workflow/notifications.mjs +52 -0
  126. package/lib/engine/workflow/runtime.mjs +447 -0
  127. package/lib/engine/workflow/sandbox.mjs +534 -0
  128. package/lib/engine/workflow/worker.mjs +141 -0
  129. package/lib/engine/workflow/worktree.mjs +74 -0
  130. package/lib/execution/disposition.mjs +1 -1
  131. package/lib/execution/intake.mjs +10 -0
  132. package/lib/execution/surface-policy.mjs +15 -0
  133. package/lib/learning/curator.mjs +8 -6
  134. package/lib/learning/reflect.mjs +8 -6
  135. package/lib/model-router/catalog/cohort.yaml +137 -0
  136. package/lib/model-router/catalog.mjs +118 -1
  137. package/lib/model-router/economics.mjs +9 -0
  138. package/lib/model-router/failover.mjs +67 -16
  139. package/lib/model-router/llm-task.mjs +39 -3
  140. package/lib/model-router/resolve.mjs +95 -3
  141. package/lib/model-router/spawn.mjs +46 -47
  142. package/lib/model-router/taxonomy.mjs +126 -4
  143. package/lib/org/cost-sync.mjs +141 -11
  144. package/lib/org/inbound/broadcast.mjs +289 -0
  145. package/lib/org/inbound/collective.mjs +375 -0
  146. package/lib/org/inbound/directedness.mjs +96 -8
  147. package/lib/org/inbound/facts.mjs +82 -4
  148. package/lib/org/inbound/hydrate.mjs +555 -51
  149. package/lib/org/inbound/project.mjs +22 -0
  150. package/lib/org/inbound/surfaces.mjs +14 -0
  151. package/lib/org/llm-token.mjs +879 -0
  152. package/lib/org/mesh.mjs +61 -0
  153. package/lib/org/messaging.mjs +3 -1
  154. package/lib/org/protocol.checksum +1 -1
  155. package/lib/org/protocol.mjs +15 -0
  156. package/lib/org/quota.mjs +520 -0
  157. package/lib/org/tool-surface.mjs +104 -16
  158. package/lib/org/ui-parity.mjs +16 -1
  159. package/lib/org/work-ledger.mjs +37 -6
  160. package/lib/rate-guard.mjs +114 -1
  161. package/lib/resource-governor.mjs +41 -6
  162. package/lib/runtime/adapter.mjs +823 -0
  163. package/lib/runtime/child-env.mjs +191 -0
  164. package/lib/runtime/legacy-shell-guard.mjs +97 -0
  165. package/lib/runtime/seat-engine.mjs +162 -0
  166. package/lib/session/ask-ledger.mjs +271 -0
  167. package/lib/session/current-work.mjs +676 -0
  168. package/lib/session/feed-core.mjs +40 -3
  169. package/lib/session/launch-args.mjs +56 -4
  170. package/lib/session/status-summary.mjs +26 -9
  171. package/lib/session/upgrade-notice.mjs +42 -0
  172. package/lib/setup/claude-probe.mjs +117 -13
  173. package/lib/setup/enrich.mjs +13 -10
  174. package/lib/setup/sections/model.mjs +39 -13
  175. package/lib/telemetry/collect.mjs +208 -9
  176. package/lib/upgrade/ignored-drift.mjs +105 -0
  177. package/lib/voice/post-call-brief.mjs +30 -17
  178. package/package.json +15 -3
  179. package/plugins/maestro-skills/skills/board-work.md +5 -0
  180. package/plugins/maestro-skills/skills/inbound-triage.md +56 -15
  181. package/plugins/maestro-skills/skills/main-session.md +18 -7
  182. package/scripts/ci/check-tarball-fidelity.mjs +126 -2
  183. package/scripts/ci/run-tests.mjs +47 -19
  184. package/scripts/cohort-llm/api-key-helper.mjs +92 -0
  185. package/scripts/collective/hook-runner.mjs +29 -2
  186. package/scripts/continuous-monitor.sh +13 -0
  187. package/scripts/cost/track-claude-usage.mjs +15 -0
  188. package/scripts/daemon/agent-daemon.mjs +408 -20
  189. package/scripts/daemon/assurance.mjs +48 -12
  190. package/scripts/daemon/cadence-consumer.mjs +218 -68
  191. package/scripts/daemon/cadence-handlers.mjs +73 -4
  192. package/scripts/daemon/classifier.mjs +75 -26
  193. package/scripts/daemon/context-compiler.mjs +104 -59
  194. package/scripts/daemon/deliver.mjs +30 -1
  195. package/scripts/daemon/dispatcher.mjs +804 -157
  196. package/scripts/daemon/health.mjs +14 -1
  197. package/scripts/daemon/lib/session-router.mjs +310 -42
  198. package/scripts/daemon/maestro-daemon.mjs +11 -0
  199. package/scripts/daemon/prompt-builder.mjs +121 -12
  200. package/scripts/daemon/responder.mjs +315 -146
  201. package/scripts/daemon/sdk-version.mjs +98 -16
  202. package/scripts/eval/probe-gateway.mjs +635 -0
  203. package/scripts/eval/replay/extract.mjs +270 -0
  204. package/scripts/eval/replay/grade.mjs +260 -0
  205. package/scripts/eval/replay/lib/config.mjs +50 -0
  206. package/scripts/eval/replay/lib/effects.mjs +65 -0
  207. package/scripts/eval/replay/lib/fixture.mjs +188 -0
  208. package/scripts/eval/replay/lib/judge.mjs +72 -0
  209. package/scripts/eval/replay/lib/redact.mjs +136 -0
  210. package/scripts/eval/replay/lib/sandbox.mjs +170 -0
  211. package/scripts/eval/replay/lib/schema-check.mjs +63 -0
  212. package/scripts/eval/replay/lib/transcript.mjs +76 -0
  213. package/scripts/eval/replay/mcp-replay-stub.mjs +101 -0
  214. package/scripts/eval/replay/report.mjs +185 -0
  215. package/scripts/eval/replay/run.mjs +404 -0
  216. package/scripts/fleet/rollout.mjs +1094 -0
  217. package/scripts/hooks/pre-send-audit.sh +36 -245
  218. package/scripts/hooks/pre-write-yaml-validate.mjs +275 -0
  219. package/scripts/hooks/validate-state-yaml.sh +190 -0
  220. package/scripts/huddle/huddle-llm.mjs +361 -0
  221. package/scripts/huddle/huddle-server.mjs +46 -121
  222. package/scripts/local-triggers/autoupdate.sh +448 -78
  223. package/scripts/local-triggers/run-trigger.sh +13 -0
  224. package/scripts/maintenance/pin-integrity.mjs +364 -0
  225. package/scripts/poll-slack-events.sh +41 -9
  226. package/scripts/poller/slack-socket-mode.mjs +28 -3
  227. package/scripts/session/supervisor.mjs +80 -13
  228. package/scripts/spawn-session.sh +13 -0
  229. package/bin/maestro.test.mjs +0 -1574
  230. package/lib/action-executor.test.mjs +0 -871
  231. package/lib/archetype.test.mjs +0 -132
  232. package/lib/assurance/plan-note.test.mjs +0 -234
  233. package/lib/assurance/room-budget.test.mjs +0 -486
  234. package/lib/assurance/tier.test.mjs +0 -174
  235. package/lib/autonomy.test.mjs +0 -66
  236. package/lib/backlog.test.mjs +0 -302
  237. package/lib/backup/policy.test.mjs +0 -305
  238. package/lib/budget-escalate.test.mjs +0 -232
  239. package/lib/budget-guard.envelope.test.mjs +0 -476
  240. package/lib/budget-guard.test.mjs +0 -427
  241. package/lib/cadence-bus-requeue.test.mjs +0 -83
  242. package/lib/cadence-bus-schedule.test.mjs +0 -194
  243. package/lib/cadence-bus.test.mjs +0 -720
  244. package/lib/cadences.test.mjs +0 -230
  245. package/lib/capability/inventory.test.mjs +0 -232
  246. package/lib/capability.test.mjs +0 -78
  247. package/lib/channels/base-adapter.test.mjs +0 -590
  248. package/lib/channels/channels.test.mjs +0 -371
  249. package/lib/channels/contract.test.mjs +0 -162
  250. package/lib/channels/inbox-item.test.mjs +0 -368
  251. package/lib/channels/orgmail/adapter.test.mjs +0 -448
  252. package/lib/channels/pairing.test.mjs +0 -270
  253. package/lib/channels/repeat-suppressor.test.mjs +0 -134
  254. package/lib/channels/slack-adapter.test.mjs +0 -212
  255. package/lib/channels/telegram-adapter.test.mjs +0 -306
  256. package/lib/channels/voice/adapter.test.mjs +0 -278
  257. package/lib/channels/whatsapp/adapter-baileys.test.mjs +0 -359
  258. package/lib/channels/whatsapp/baileys-typing.test.mjs +0 -154
  259. package/lib/charter.test.mjs +0 -89
  260. package/lib/claude-bin.test.mjs +0 -131
  261. package/lib/cli/board.test.mjs +0 -227
  262. package/lib/cli/design.test.mjs +0 -270
  263. package/lib/cli/doctor-checks.test.mjs +0 -336
  264. package/lib/cli/global-setup-extras.test.mjs +0 -462
  265. package/lib/cli/inbox.test.mjs +0 -230
  266. package/lib/cli/session-ack.test.mjs +0 -63
  267. package/lib/cli/session.test.mjs +0 -613
  268. package/lib/collective/capture.test.mjs +0 -121
  269. package/lib/collective/cards.test.mjs +0 -114
  270. package/lib/collective/config.test.mjs +0 -123
  271. package/lib/collective/global-config.test.mjs +0 -220
  272. package/lib/collective/global-skills.test.mjs +0 -126
  273. package/lib/collective/presence.test.mjs +0 -95
  274. package/lib/collective/recall.test.mjs +0 -116
  275. package/lib/collective/vendor-skills.test.mjs +0 -306
  276. package/lib/comms/send-gate.test.mjs +0 -770
  277. package/lib/comms.test.mjs +0 -41
  278. package/lib/cost/ledger-row.test.mjs +0 -183
  279. package/lib/design/design-md.test.mjs +0 -318
  280. package/lib/design/fixtures/DESIGN.golden.md +0 -238
  281. package/lib/design/fixtures/PRODUCT.golden.md +0 -67
  282. package/lib/design/fixtures/foundation.json +0 -133
  283. package/lib/design/refresh-gate.test.mjs +0 -144
  284. package/lib/design/write.test.mjs +0 -241
  285. package/lib/diagnostics/alerts.test.mjs +0 -318
  286. package/lib/diagnostics/backup-freshness.test.mjs +0 -185
  287. package/lib/diagnostics/counters.test.mjs +0 -206
  288. package/lib/diagnostics/events.test.mjs +0 -290
  289. package/lib/diagnostics/otel.test.mjs +0 -196
  290. package/lib/diagnostics/trace.test.mjs +0 -251
  291. package/lib/env-compat.test.mjs +0 -104
  292. package/lib/execution/disposition.test.mjs +0 -553
  293. package/lib/execution/drive.test.mjs +0 -270
  294. package/lib/execution/effects.test.mjs +0 -344
  295. package/lib/execution/intake.test.mjs +0 -389
  296. package/lib/execution/journal.test.mjs +0 -261
  297. package/lib/execution/match.test.mjs +0 -235
  298. package/lib/execution/pipeline.test.mjs +0 -392
  299. package/lib/execution/route.test.mjs +0 -186
  300. package/lib/execution/surface-policy.test.mjs +0 -162
  301. package/lib/fs-atomic.test.mjs +0 -72
  302. package/lib/fs-ownership.test.mjs +0 -158
  303. package/lib/goals/admission.test.mjs +0 -164
  304. package/lib/goals/classify.test.mjs +0 -167
  305. package/lib/goals/collaborate.test.mjs +0 -336
  306. package/lib/goals/gaps.test.mjs +0 -284
  307. package/lib/goals/loop.test.mjs +0 -845
  308. package/lib/hooks/bus.test.mjs +0 -387
  309. package/lib/identity/persona.test.mjs +0 -142
  310. package/lib/kpi-sensors.test.mjs +0 -278
  311. package/lib/kpi.test.mjs +0 -244
  312. package/lib/learning/config.test.mjs +0 -75
  313. package/lib/learning/counters.test.mjs +0 -69
  314. package/lib/learning/curator-consolidate.test.mjs +0 -238
  315. package/lib/learning/curator.test.mjs +0 -106
  316. package/lib/learning/reflect.test.mjs +0 -0
  317. package/lib/learning/session-index.test.mjs +0 -125
  318. package/lib/learning/skill-writer.test.mjs +0 -210
  319. package/lib/mandate/audit.test.mjs +0 -195
  320. package/lib/mandate/contract.test.mjs +0 -185
  321. package/lib/mandate/derive.test.mjs +0 -274
  322. package/lib/mandate/model.test.mjs +0 -164
  323. package/lib/mandate/refresh.test.mjs +0 -389
  324. package/lib/mcp/server.test.mjs +0 -426
  325. package/lib/model-router/auth-profiles.test.mjs +0 -580
  326. package/lib/model-router/catalog.test.mjs +0 -385
  327. package/lib/model-router/economics.test.mjs +0 -438
  328. package/lib/model-router/failover.test.mjs +0 -439
  329. package/lib/model-router/health.test.mjs +0 -338
  330. package/lib/model-router/integration-coverage.test.mjs +0 -831
  331. package/lib/model-router/integration.test.mjs +0 -564
  332. package/lib/model-router/ledger.test.mjs +0 -415
  333. package/lib/model-router/llm-task.test.mjs +0 -392
  334. package/lib/model-router/org-credentials.test.mjs +0 -265
  335. package/lib/model-router/pricing-refresh.test.mjs +0 -286
  336. package/lib/model-router/reconcile.test.mjs +0 -316
  337. package/lib/model-router/repair.test.mjs +0 -180
  338. package/lib/model-router/spawn.test.mjs +0 -446
  339. package/lib/model-router/taxonomy.test.mjs +0 -410
  340. package/lib/model-router.test.mjs +0 -1207
  341. package/lib/org/activity.test.mjs +0 -134
  342. package/lib/org/approvals.test.mjs +0 -216
  343. package/lib/org/awareness.test.mjs +0 -159
  344. package/lib/org/board-mine-cache.test.mjs +0 -53
  345. package/lib/org/board.test.mjs +0 -187
  346. package/lib/org/bootstrap-context.test.mjs +0 -153
  347. package/lib/org/client.test.mjs +0 -1206
  348. package/lib/org/cohort-client.test.mjs +0 -126
  349. package/lib/org/cost-sync.test.mjs +0 -153
  350. package/lib/org/doctor.test.mjs +0 -346
  351. package/lib/org/engagement-ledger.test.mjs +0 -112
  352. package/lib/org/engagement.test.mjs +0 -739
  353. package/lib/org/handoff.test.mjs +0 -269
  354. package/lib/org/inbound/directedness.test.mjs +0 -668
  355. package/lib/org/inbound/facts.test.mjs +0 -471
  356. package/lib/org/inbound/hydrate.test.mjs +0 -453
  357. package/lib/org/inbound/index.test.mjs +0 -429
  358. package/lib/org/inbound/project.test.mjs +0 -287
  359. package/lib/org/integration-tools.test.mjs +0 -160
  360. package/lib/org/keys.test.mjs +0 -92
  361. package/lib/org/knowledge.test.mjs +0 -326
  362. package/lib/org/leases.test.mjs +0 -235
  363. package/lib/org/mesh-directives.test.mjs +0 -110
  364. package/lib/org/mesh-integration.test.mjs +0 -127
  365. package/lib/org/mesh.test.mjs +0 -400
  366. package/lib/org/messaging.test.mjs +0 -471
  367. package/lib/org/param-contract.test.mjs +0 -477
  368. package/lib/org/policy.test.mjs +0 -237
  369. package/lib/org/protocol.checksum.test.mjs +0 -90
  370. package/lib/org/protocol.test.mjs +0 -323
  371. package/lib/org/push.test.mjs +0 -792
  372. package/lib/org/registry.test.mjs +0 -100
  373. package/lib/org/resource-tools.test.mjs +0 -361
  374. package/lib/org/tool-access.test.mjs +0 -144
  375. package/lib/org/tool-surface-integration.test.mjs +0 -120
  376. package/lib/org/tool-surface.test.mjs +0 -1268
  377. package/lib/org/typing.test.mjs +0 -291
  378. package/lib/org/ui-parity.test.mjs +0 -560
  379. package/lib/org/verify.test.mjs +0 -194
  380. package/lib/org/work-ledger.test.mjs +0 -273
  381. package/lib/plan/adoption-e2e.test.mjs +0 -366
  382. package/lib/plan/budget-enforcement.test.mjs +0 -400
  383. package/lib/plan/compile.test.mjs +0 -382
  384. package/lib/plan/emit.test.mjs +0 -269
  385. package/lib/plan/explain.test.mjs +0 -188
  386. package/lib/prompts/parallelism.test.mjs +0 -177
  387. package/lib/rag/rag.test.mjs +0 -505
  388. package/lib/rate-guard.test.mjs +0 -272
  389. package/lib/reactive-gate.test.mjs +0 -57
  390. package/lib/render.test.mjs +0 -68
  391. package/lib/resource-governor.test.mjs +0 -488
  392. package/lib/scheduling/dynamic-jobs.test.mjs +0 -344
  393. package/lib/scheduling/jitter.test.mjs +0 -140
  394. package/lib/secrets/broker.test.mjs +0 -280
  395. package/lib/secrets/providers.test.mjs +0 -274
  396. package/lib/security/audit-engine.test.mjs +0 -424
  397. package/lib/security/coerce-args.test.mjs +0 -281
  398. package/lib/security/dangerous-tools.test.mjs +0 -68
  399. package/lib/security/external-content.test.mjs +0 -84
  400. package/lib/security/redact.test.mjs +0 -441
  401. package/lib/security/secret-equal.test.mjs +0 -55
  402. package/lib/session/config.test.mjs +0 -92
  403. package/lib/session/feed-core.test.mjs +0 -198
  404. package/lib/session/first-run.test.mjs +0 -121
  405. package/lib/session/frontdoor.test.mjs +0 -205
  406. package/lib/session/handoffs.test.mjs +0 -183
  407. package/lib/session/identity.test.mjs +0 -180
  408. package/lib/session/inbox-claims.test.mjs +0 -286
  409. package/lib/session/launch-args.test.mjs +0 -157
  410. package/lib/session/liveness.test.mjs +0 -100
  411. package/lib/session/status-summary.test.mjs +0 -118
  412. package/lib/session-permissions.test.mjs +0 -120
  413. package/lib/setup/claude-probe.test.mjs +0 -187
  414. package/lib/setup/completeness.test.mjs +0 -110
  415. package/lib/setup/context-pack.test.mjs +0 -89
  416. package/lib/setup/enrich.test.mjs +0 -115
  417. package/lib/setup/enroll-from-cohort.test.mjs +0 -300
  418. package/lib/setup/integration.test.mjs +0 -162
  419. package/lib/setup/io.test.mjs +0 -77
  420. package/lib/setup/runner.test.mjs +0 -132
  421. package/lib/setup/sections/identity.test.mjs +0 -234
  422. package/lib/setup/sections/inventory.test.mjs +0 -198
  423. package/lib/setup/sections/learning.test.mjs +0 -81
  424. package/lib/setup/sections/mandate.test.mjs +0 -388
  425. package/lib/setup/sections/messaging.test.mjs +0 -127
  426. package/lib/setup/sections/model.test.mjs +0 -240
  427. package/lib/setup/sections/org.test.mjs +0 -346
  428. package/lib/setup/sections/orgmail.test.mjs +0 -118
  429. package/lib/setup/sections/recovery.test.mjs +0 -98
  430. package/lib/setup/sections/subagents.test.mjs +0 -429
  431. package/lib/setup/sections/verify.test.mjs +0 -175
  432. package/lib/setup/sot.test.mjs +0 -81
  433. package/lib/setup/state.test.mjs +0 -115
  434. package/lib/singleton.test.mjs +0 -151
  435. package/lib/subagents/cli.test.mjs +0 -389
  436. package/lib/subagents/client.test.mjs +0 -309
  437. package/lib/subagents/gap.test.mjs +0 -234
  438. package/lib/subagents/lock.test.mjs +0 -248
  439. package/lib/subagents/manifest.test.mjs +0 -175
  440. package/lib/subagents/refs.test.mjs +0 -204
  441. package/lib/subagents/resolve.test.mjs +0 -422
  442. package/lib/subagents/schema.test.mjs +0 -328
  443. package/lib/telemetry/alerts.test.mjs +0 -109
  444. package/lib/telemetry/collect.test.mjs +0 -1274
  445. package/lib/tool-definitions-integration.test.mjs +0 -83
  446. package/lib/tool-definitions.test.mjs +0 -437
  447. package/lib/upgrade/global-refresh.test.mjs +0 -65
  448. package/lib/upgrade/launchd-reconcile.test.mjs +0 -272
  449. package/lib/upgrade/post-steps.test.mjs +0 -200
  450. package/lib/upgrade/verify.test.mjs +0 -164
  451. package/lib/util/fetch-timeout.test.mjs +0 -202
  452. package/lib/util/reconnect.test.mjs +0 -369
  453. package/lib/util/unhandled.test.mjs +0 -216
  454. package/lib/voice/outbound.test.mjs +0 -69
  455. package/lib/voice/session-rotation.test.mjs +0 -114
  456. package/lib/voice/stt.test.mjs +0 -226
  457. package/lib/voice/voice.test.mjs +0 -990
  458. package/scripts/cadence/enqueue-cadence-tick.test.mjs +0 -187
  459. package/scripts/ci/check-docs-accuracy.test.mjs +0 -409
  460. package/scripts/ci/check-durable-write-seam.test.mjs +0 -90
  461. package/scripts/ci/check-no-build-artifacts.test.mjs +0 -71
  462. package/scripts/ci/check-no-residual-identity.test.mjs +0 -202
  463. package/scripts/ci/check-skill-packs.test.mjs +0 -495
  464. package/scripts/ci/check-subagent-frontmatter.test.mjs +0 -124
  465. package/scripts/ci/check.test.mjs +0 -194
  466. package/scripts/ci/conformance-org-api.test.mjs +0 -425
  467. package/scripts/cloud-relay/voice/relay-identity.test.mjs +0 -96
  468. package/scripts/collective/hook-runner.test.mjs +0 -173
  469. package/scripts/cost/fleet-digest.test.mjs +0 -207
  470. package/scripts/cost/track-claude-usage-pricing.test.mjs +0 -183
  471. package/scripts/cost/track-claude-usage.test.mjs +0 -148
  472. package/scripts/daemon/agent-daemon-board-mine.test.mjs +0 -96
  473. package/scripts/daemon/agent-daemon-design.test.mjs +0 -238
  474. package/scripts/daemon/agent-daemon-frontdoor.test.mjs +0 -60
  475. package/scripts/daemon/agent-daemon.test.mjs +0 -995
  476. package/scripts/daemon/assurance-e2e.test.mjs +0 -613
  477. package/scripts/daemon/assurance.test.mjs +0 -1791
  478. package/scripts/daemon/board-mirror.test.mjs +0 -165
  479. package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +0 -393
  480. package/scripts/daemon/cadence-consumer-governance.test.mjs +0 -276
  481. package/scripts/daemon/cadence-consumer.test.mjs +0 -776
  482. package/scripts/daemon/cadence-handlers.test.mjs +0 -837
  483. package/scripts/daemon/classifier-identity.test.mjs +0 -137
  484. package/scripts/daemon/classifier.test.mjs +0 -266
  485. package/scripts/daemon/classify-kind.test.mjs +0 -40
  486. package/scripts/daemon/context-compiler.test.mjs +0 -300
  487. package/scripts/daemon/deliver.test.mjs +0 -564
  488. package/scripts/daemon/dispatcher-cooldown.test.mjs +0 -122
  489. package/scripts/daemon/dispatcher-governance.test.mjs +0 -1013
  490. package/scripts/daemon/dispatcher-resume.test.mjs +0 -166
  491. package/scripts/daemon/execution-ladder.test.mjs +0 -470
  492. package/scripts/daemon/goal-steward-cadence.test.mjs +0 -312
  493. package/scripts/daemon/inbox-deferral-session.test.mjs +0 -49
  494. package/scripts/daemon/inbox-deferral.test.mjs +0 -336
  495. package/scripts/daemon/inbox-wake.test.mjs +0 -199
  496. package/scripts/daemon/integration.test.mjs +0 -149
  497. package/scripts/daemon/lib/self-echo.test.mjs +0 -153
  498. package/scripts/daemon/lib/session-router.test.mjs +0 -295
  499. package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
  500. package/scripts/daemon/prompt-builder.test.mjs +0 -344
  501. package/scripts/daemon/responder-cost.test.mjs +0 -68
  502. package/scripts/daemon/responder-history.test.mjs +0 -185
  503. package/scripts/daemon/sdk-version.test.mjs +0 -31
  504. package/scripts/daemon/session-lock.test.mjs +0 -252
  505. package/scripts/daemon/session-outcomes.test.mjs +0 -533
  506. package/scripts/daemon/typing-registry.test.mjs +0 -102
  507. package/scripts/hooks/pre-send-audit.test.mjs +0 -354
  508. package/scripts/huddle/huddle-prompt.test.mjs +0 -176
  509. package/scripts/local-triggers/autoupdate.test.mjs +0 -518
  510. package/scripts/local-triggers/generate-plists.test.mjs +0 -456
  511. package/scripts/media-generation/brand-clause.test.mjs +0 -135
  512. package/scripts/org/send-orgmail.first-contact.test.mjs +0 -102
  513. package/scripts/poller/inbox-privilege-injection.test.mjs +0 -167
  514. package/scripts/poller/inbox-scan-poller.test.mjs +0 -295
  515. package/scripts/poller/lib/cloud-relay-dedup.test.mjs +0 -133
  516. package/scripts/poller/slack-socket-mode.test.mjs +0 -805
  517. package/scripts/poller-launchd/install.test.mjs +0 -243
  518. package/scripts/restore-from-backup.test.mjs +0 -181
  519. package/scripts/session/feed.test.mjs +0 -196
  520. package/scripts/session/supervisor-sh.test.mjs +0 -218
  521. package/scripts/session/supervisor.test.mjs +0 -482
  522. package/scripts/setup/configure-macos.test.mjs +0 -306
  523. package/scripts/setup/gen-subagent-manifest.test.mjs +0 -124
  524. package/scripts/setup/generate-agent-package-json.test.mjs +0 -143
  525. package/scripts/setup/generate-capability.test.mjs +0 -134
  526. package/scripts/setup/init-agent.test.mjs +0 -370
  527. package/scripts/setup/init-skill-marketplace.test.mjs +0 -193
  528. package/scripts/vendor/sync-skill-packs.test.mjs +0 -103
  529. 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
  }