@cohortapp/agent-sdk 2.17.0 → 2.18.5

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