@iowarp/clio-coder 0.3.6 → 0.3.8

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 (323) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +22 -6
  3. package/dist/{acp-2BEHC4DL.js → acp-U67UHUK2.js} +14 -14
  4. package/dist/{agents-LNNFTM53.js → agents-YU6SGALZ.js} +42 -35
  5. package/dist/assets/codewiki.json +1 -1
  6. package/dist/{auth-KXXFI2VS.js → auth-5ZPJOIVG.js} +32 -24
  7. package/dist/builtins-C6JMZVV6.js +17 -0
  8. package/dist/{chunk-E25LMLRW.js → chunk-26LEYJZH.js} +2 -2
  9. package/dist/{chunk-TSHXZTOQ.js → chunk-2HEJ2F35.js} +5 -5
  10. package/dist/{chunk-PBTHKCPN.js → chunk-2HFZQUHL.js} +7 -7
  11. package/dist/{chunk-FO5ZOVUY.js → chunk-3BINW3FP.js} +5 -5
  12. package/dist/{chunk-4OC57DA6.js → chunk-4DGYLA73.js} +53 -2
  13. package/dist/{chunk-CKXWIANG.js → chunk-4SPRNWDE.js} +18 -16
  14. package/dist/{chunk-E2ER4LJF.js → chunk-5C3AQNDW.js} +25 -1
  15. package/dist/{chunk-XF5N4U5A.js → chunk-5DHKRSMQ.js} +9 -8
  16. package/dist/{chunk-43AOLP7E.js → chunk-5FR74PWO.js} +2 -1
  17. package/dist/{chunk-EKY57CSP.js → chunk-5H3GB5BO.js} +68 -772
  18. package/dist/{chunk-6XXKFVSN.js → chunk-5Q2VVUKB.js} +4 -4
  19. package/dist/{chunk-DJVECN66.js → chunk-7RFXX52T.js} +295 -3705
  20. package/dist/{chunk-ZXF4XRKW.js → chunk-7RGZWPB6.js} +158 -8
  21. package/dist/{chunk-FYYLNIL5.js → chunk-A2NJGIB3.js} +2 -2
  22. package/dist/{chunk-XXQNGV4M.js → chunk-A3WNZD3P.js} +548 -72
  23. package/dist/{chunk-VEZEGCGW.js → chunk-B5CSFE7B.js} +23 -21
  24. package/dist/{chunk-2SFS6XQE.js → chunk-DGSYXYMX.js} +3 -2
  25. package/dist/chunk-DR52UMZW.js +21 -0
  26. package/dist/{chunk-ZWLZP4ZT.js → chunk-DYIM5TJT.js} +98 -12
  27. package/dist/{chunk-QKMUKYO7.js → chunk-E77JEWSD.js} +270 -125
  28. package/dist/{chunk-LYF7OHWH.js → chunk-EMYUUSFG.js} +12 -467
  29. package/dist/{chunk-4VP4KH3K.js → chunk-EQ63NRB7.js} +8 -8
  30. package/dist/chunk-FBVTI2TJ.js +518 -0
  31. package/dist/{chunk-R46L2BIR.js → chunk-FHJEP5SW.js} +19 -25
  32. package/dist/{chunk-WR67VIZY.js → chunk-GN57SG4G.js} +66 -8
  33. package/dist/{chunk-RD5U66HV.js → chunk-GPIEI3LY.js} +9 -9
  34. package/dist/{chunk-PCZJO5TI.js → chunk-GU2UIAFZ.js} +13 -178
  35. package/dist/chunk-GWS3VEIW.js +195 -0
  36. package/dist/chunk-H7IXIC72.js +103 -0
  37. package/dist/{chunk-3BPUFZDL.js → chunk-HLE42MG7.js} +3 -3
  38. package/dist/{chunk-24I7BN55.js → chunk-IFBNV6H6.js} +3 -3
  39. package/dist/{chunk-QNQHSOLF.js → chunk-IGWKHNIQ.js} +114 -55
  40. package/dist/chunk-IIZWH4XA.js +172 -0
  41. package/dist/{chunk-AD2SYQYC.js → chunk-IJ7RPIYJ.js} +124 -6
  42. package/dist/chunk-J3YUBZWY.js +382 -0
  43. package/dist/{chunk-5JGRAMKL.js → chunk-JOZYP4GM.js} +8 -6
  44. package/dist/{chunk-OH3TOQTB.js → chunk-K4XHGFR5.js} +751 -15
  45. package/dist/{chunk-EYPA3EGJ.js → chunk-KTYTFRMB.js} +178 -14
  46. package/dist/chunk-LU7P4LHA.js +33 -0
  47. package/dist/chunk-ME6CCNFO.js +108 -0
  48. package/dist/chunk-MXKJU4JB.js +1100 -0
  49. package/dist/{chunk-RY3LY4J5.js → chunk-N22QMJKY.js} +21 -18
  50. package/dist/chunk-NMPKI6XL.js +3006 -0
  51. package/dist/chunk-NUGM5KR6.js +165 -0
  52. package/dist/{chunk-22NAGB7X.js → chunk-P43ETTHK.js} +5 -94
  53. package/dist/{chunk-NILBFAPG.js → chunk-PMDBGQSJ.js} +2 -2
  54. package/dist/chunk-PT7HYKEM.js +165 -0
  55. package/dist/chunk-RVG5JXAL.js +41 -0
  56. package/dist/{chunk-GEYXPTRF.js → chunk-RWSI4YD7.js} +2 -2
  57. package/dist/{chunk-4BPJXDWC.js → chunk-TANS5ZJS.js} +35 -19
  58. package/dist/{verifiers-NCBTHHN2.js → chunk-TB5666IT.js} +67 -324
  59. package/dist/{chunk-QM3F2GKX.js → chunk-TLQJPP24.js} +7927 -8088
  60. package/dist/{chunk-G7MUEIGA.js → chunk-TT36MB5S.js} +2 -1
  61. package/dist/chunk-TTHACPOM.js +961 -0
  62. package/dist/{chunk-6US73PDB.js → chunk-TYPGUK6W.js} +7 -7
  63. package/dist/{chunk-MFFY33HR.js → chunk-U6MBIEMB.js} +554 -209
  64. package/dist/chunk-VAWNZU7Z.js +242 -0
  65. package/dist/{chunk-IR4CFBFN.js → chunk-VCBR6CU7.js} +12 -12
  66. package/dist/{chunk-WHJYKASB.js → chunk-VHN4MY6O.js} +2 -2
  67. package/dist/{chunk-XYDYPRZI.js → chunk-VWZOAB7K.js} +10 -10
  68. package/dist/{chunk-ZRGEBJ4T.js → chunk-WLFILSD5.js} +48 -48
  69. package/dist/{chunk-K7T3E2SR.js → chunk-WNIJTQQK.js} +12 -11
  70. package/dist/{chunk-CYQKWTG3.js → chunk-WSB3FPX7.js} +68 -20
  71. package/dist/{chunk-KHSFENX2.js → chunk-WWCZ5F23.js} +116 -14
  72. package/dist/{chunk-XE2VEJHX.js → chunk-WXY7KU3G.js} +2 -2
  73. package/dist/{chunk-PPAMZ32Z.js → chunk-XK56QHLX.js} +6 -1
  74. package/dist/{chunk-KOHPCX4K.js → chunk-XWSF374K.js} +5 -5
  75. package/dist/{chunk-CJUB2JJ2.js → chunk-YS5VLNH5.js} +10 -10
  76. package/dist/{chunk-ZZMN5OM4.js → chunk-ZNLWCMVZ.js} +2 -2
  77. package/dist/{chunk-WHGPSPT5.js → chunk-ZVJ5BLO2.js} +2 -2
  78. package/dist/cli/index.js +33 -31
  79. package/dist/{clio-M2KGYUFZ.js → clio-QVTYJ57A.js} +10 -10
  80. package/dist/{code-nav-GQNL7XA6.js → code-nav-FGGFIE7L.js} +5 -5
  81. package/dist/codewiki/build-worker.js +4 -4
  82. package/dist/{components-5TTYYX6G.js → components-ZFA3SAER.js} +9 -9
  83. package/dist/{config-XUUYQIWO.js → config-LW5IJFQN.js} +65 -55
  84. package/dist/{configure-IHJ7YOMV.js → configure-7XIZCOU4.js} +29 -24
  85. package/dist/{context-75MIWW3U.js → context-L3WL3X7K.js} +54 -43
  86. package/dist/{context-ZQ7SIFJV.js → context-N52ZA626.js} +30 -14
  87. package/dist/{context-74JLXAWD.js → context-Y6Y7QPR6.js} +12 -12
  88. package/dist/{context-clear-GYKWNUML.js → context-clear-MBQRLSDQ.js} +54 -43
  89. package/dist/{context-index-SSR5ECNE.js → context-index-HVMFQHK3.js} +5 -5
  90. package/dist/{context-working-set-UX5KEP4J.js → context-working-set-GS6DSO7F.js} +18 -18
  91. package/dist/{dispatch-runner-GIJBHNFL.js → dispatch-runner-22ZCNOM3.js} +367 -79
  92. package/dist/{docs-6FZSCG5B.js → docs-7LQ23DLM.js} +9 -9
  93. package/dist/{doctor-SVJ5BZCW.js → doctor-M7YEDGAE.js} +27 -23
  94. package/dist/{eval-CG6LLBLD.js → eval-BEC2WHDA.js} +73 -20
  95. package/dist/{evidence-ZYFIEN42.js → evidence-REJUMSKM.js} +70 -59
  96. package/dist/{evolve-QGEXEMDW.js → evolve-PY5ZBA5K.js} +50 -39
  97. package/dist/{extensions-ADGNCJJD.js → extensions-HVKU65YU.js} +7 -7
  98. package/dist/{fleet-S5R4ZOQY.js → fleet-7WZEWRFA.js} +236 -377
  99. package/dist/fleet-commands-UVHWM76J.js +70 -0
  100. package/dist/fleet-graph-6ULH7PES.js +125 -0
  101. package/dist/fleet-new-RDVJLHHH.js +48 -0
  102. package/dist/{fleet-preflight-BHSNPBMH.js → fleet-preflight-J53T6CCE.js} +5 -5
  103. package/dist/fleet-validate-72PC4SLA.js +79 -0
  104. package/dist/{init-5DRU55YR.js → init-OG3TPGQG.js} +69 -57
  105. package/dist/library-CNTMPLRF.js +217 -0
  106. package/dist/{memory-7YKKR6UC.js → memory-6IS7F275.js} +52 -41
  107. package/dist/{models-ZPOLRU2C.js → models-ENRJDA5W.js} +37 -31
  108. package/dist/{monitor-US5F5YGZ.js → monitor-XLDVO7TN.js} +79 -42
  109. package/dist/{orchestrator-E2AL4T5N.js → orchestrator-6KSPYRHA.js} +4126 -762
  110. package/dist/{paths-E7KYAQWE.js → paths-DBXMZMDU.js} +6 -6
  111. package/dist/registry-LG64LTF4.js +11 -0
  112. package/dist/{reset-KZ652EK6.js → reset-RZ4ER727.js} +12 -12
  113. package/dist/{run-SRNBKDWD.js → run-Y2CNK5RU.js} +89 -76
  114. package/dist/{share-CGZE33UP.js → share-A55GYP6Z.js} +37 -13
  115. package/dist/{skills-S2X4DLY5.js → skills-ALC5J6AT.js} +29 -13
  116. package/dist/{skills-eval-W2GGIC4R.js → skills-eval-JPBEBYQU.js} +60 -48
  117. package/dist/support-MIETYA5E.js +38 -0
  118. package/dist/{targets-54SWINWB.js → targets-VGNXIR3S.js} +46 -36
  119. package/dist/{terminal-lease-SAIF2OGY.js → terminal-lease-WOBR64YA.js} +4 -4
  120. package/dist/{uninstall-BVLWXKBT.js → uninstall-ZJF5H5ZN.js} +9 -9
  121. package/dist/{upgrade-JKAR27XC.js → upgrade-FUSUAGHR.js} +31 -28
  122. package/dist/{usage-MSAWCLX4.js → usage-N4MKVHKD.js} +140 -68
  123. package/dist/verifiers-YAWOJ3H2.js +336 -0
  124. package/dist/{verify-X5HDROLA.js → verify-LTDHYBGY.js} +10 -9
  125. package/dist/{wiki-generate-GUSOQ6ZP.js → wiki-generate-6M7GHTBJ.js} +75 -62
  126. package/dist/worker/entry.js +67 -57
  127. package/docs/README.md +3 -2
  128. package/docs/acp.md +1 -1
  129. package/docs/alcf-provider.md +1 -1
  130. package/docs/architecture.md +2 -2
  131. package/docs/artifact-versions.md +11 -6
  132. package/docs/built-in-agents.md +26 -2
  133. package/docs/capacity-and-scheduling.md +1 -1
  134. package/docs/commands-and-modes.md +83 -3
  135. package/docs/configuration-and-targets.md +86 -4
  136. package/docs/context-engine.md +1 -1
  137. package/docs/development-pipeline.md +1 -1
  138. package/docs/dispatch-architecture-rationale.md +1 -1
  139. package/docs/documentation-coverage.md +3 -3
  140. package/docs/documentation-guide.md +3 -3
  141. package/docs/eval-runner.md +1 -1
  142. package/docs/evals-internal.md +1 -1
  143. package/docs/evidence-and-memory.md +74 -10
  144. package/docs/evolution.md +1 -1
  145. package/docs/exit-codes-and-output.md +4 -1
  146. package/docs/extensions-and-sharing.md +6 -2
  147. package/docs/fleet-demo-runbook.md +2 -2
  148. package/docs/fleet-dispatch.md +228 -16
  149. package/docs/git-commit-provenance.md +2 -2
  150. package/docs/glossary.md +22 -2
  151. package/docs/installation-and-lifecycle.md +2 -2
  152. package/docs/middleware-and-components.md +2 -1
  153. package/docs/model-catalog.md +1 -1
  154. package/docs/observability.md +56 -9
  155. package/docs/proactive-memory.md +1 -1
  156. package/docs/prompt-envelope-and-tools.md +4 -2
  157. package/docs/provider-adapter-cookbook.md +1 -1
  158. package/docs/release-cut-checklist.md +83 -65
  159. package/docs/resource-library.md +59 -0
  160. package/docs/safety-model.md +2 -2
  161. package/docs/scientific-validation.md +3 -3
  162. package/docs/session-lifecycle.md +37 -1
  163. package/docs/skills-marketplace.md +16 -3
  164. package/docs/tool-usage.md +14 -7
  165. package/docs/trace-store.md +1 -1
  166. package/docs/troubleshooting.md +1 -1
  167. package/docs/tui-design.md +1 -1
  168. package/docs/worker-dispatch-mechanics.md +3 -3
  169. package/package.json +1 -2
  170. package/src/cli/argv.ts +5 -0
  171. package/src/cli/configure.ts +107 -23
  172. package/src/cli/doctor.ts +5 -1
  173. package/src/cli/evidence.ts +30 -25
  174. package/src/cli/fleet-commands.ts +37 -0
  175. package/src/cli/fleet-graph.ts +102 -0
  176. package/src/cli/fleet-new.ts +36 -0
  177. package/src/cli/fleet-preflight.ts +111 -0
  178. package/src/cli/fleet-validate.ts +30 -0
  179. package/src/cli/fleet.ts +173 -335
  180. package/src/cli/index.ts +3 -1
  181. package/src/cli/library.ts +190 -0
  182. package/src/cli/share.ts +13 -1
  183. package/src/cli/shared.ts +1 -0
  184. package/src/cli/targets.ts +4 -1
  185. package/src/cli/usage.ts +111 -19
  186. package/src/cli/validate-model.ts +60 -5
  187. package/src/core/bus-events.ts +29 -0
  188. package/src/core/commit-attribution.ts +4 -4
  189. package/src/core/config.ts +130 -0
  190. package/src/core/defaults.ts +81 -0
  191. package/src/core/path-boundary.ts +100 -0
  192. package/src/domains/agents/builtins/architect.md +1 -0
  193. package/src/domains/agents/builtins/oracle.md +33 -0
  194. package/src/domains/agents/catalog.ts +13 -1
  195. package/src/domains/agents/extension.ts +2 -11
  196. package/src/domains/agents/fleet-contract.ts +304 -24
  197. package/src/domains/agents/index.ts +14 -0
  198. package/src/domains/agents/recipe.ts +7 -1
  199. package/src/domains/agents/registry.ts +73 -5
  200. package/src/domains/agents/result-contract.ts +360 -15
  201. package/src/domains/agents/write-boundary.ts +15 -50
  202. package/src/domains/config/classify.ts +4 -0
  203. package/src/domains/context/project-rules.ts +51 -1
  204. package/src/domains/dispatch/active-route-planner.ts +14 -0
  205. package/src/domains/dispatch/assignment-reconcile.ts +22 -5
  206. package/src/domains/dispatch/assignment-store.ts +151 -14
  207. package/src/domains/dispatch/backoff.ts +2 -1
  208. package/src/domains/dispatch/capability-match.ts +1 -0
  209. package/src/domains/dispatch/checkout-writer-lease.ts +175 -0
  210. package/src/domains/dispatch/contract.ts +48 -0
  211. package/src/domains/dispatch/delegation-plan.ts +164 -0
  212. package/src/domains/dispatch/execution-plan.ts +76 -5
  213. package/src/domains/dispatch/execution-role.ts +12 -2
  214. package/src/domains/dispatch/execution-scheduler.ts +183 -67
  215. package/src/domains/dispatch/extension.ts +412 -74
  216. package/src/domains/dispatch/fleet-gate.ts +14 -0
  217. package/src/domains/dispatch/fleet-plan.ts +63 -3
  218. package/src/domains/dispatch/fleet-run.ts +791 -0
  219. package/src/domains/dispatch/gate-role-prompts.ts +47 -0
  220. package/src/domains/dispatch/host-verification.ts +178 -0
  221. package/src/domains/dispatch/index.ts +41 -1
  222. package/src/domains/dispatch/intent-requirements.ts +40 -0
  223. package/src/domains/dispatch/intent.ts +235 -0
  224. package/src/domains/dispatch/path-scope.ts +370 -0
  225. package/src/domains/dispatch/receipt-integrity.ts +9 -4
  226. package/src/domains/dispatch/state.ts +36 -3
  227. package/src/domains/dispatch/types.ts +64 -12
  228. package/src/domains/dispatch/validation.ts +69 -6
  229. package/src/domains/dispatch/write-boundary-enforcer.ts +45 -0
  230. package/src/domains/dispatch/write-boundary.ts +201 -22
  231. package/src/domains/eval/metrics/evidence.ts +79 -2
  232. package/src/domains/eval/runners/clio-run.ts +12 -2
  233. package/src/domains/evidence/build.ts +69 -11
  234. package/src/domains/evidence/index.ts +21 -0
  235. package/src/domains/evidence/provenance.ts +46 -11
  236. package/src/domains/evidence/trust-projection.ts +274 -0
  237. package/src/domains/evidence/trust-status.ts +155 -18
  238. package/src/domains/evidence/types.ts +4 -0
  239. package/src/domains/extensions/discovery.ts +88 -1
  240. package/src/domains/extensions/resources.ts +20 -8
  241. package/src/domains/extensions/state.ts +6 -2
  242. package/src/domains/extensions/types.ts +4 -1
  243. package/src/domains/lifecycle/doctor.ts +140 -1
  244. package/src/domains/middleware/index.ts +15 -0
  245. package/src/domains/middleware/watchdog.ts +281 -0
  246. package/src/domains/observability/contract.ts +3 -1
  247. package/src/domains/observability/cost.ts +12 -1
  248. package/src/domains/observability/extension.ts +2 -2
  249. package/src/domains/observability/index.ts +10 -0
  250. package/src/domains/observability/out-of-turn-usage.ts +223 -0
  251. package/src/domains/prompts/contract.ts +3 -5
  252. package/src/domains/providers/extension.ts +30 -2
  253. package/src/domains/resources/common-loader.ts +3 -0
  254. package/src/domains/resources/index.ts +20 -0
  255. package/src/domains/resources/library.ts +326 -0
  256. package/src/domains/resources/prompts/loader.ts +119 -14
  257. package/src/domains/resources/skills/marketplace.ts +37 -12
  258. package/src/domains/safety/policy-engine.ts +5 -5
  259. package/src/domains/safety/run-effects.ts +64 -1
  260. package/src/domains/safety/scope.ts +7 -12
  261. package/src/domains/session/handoff.ts +629 -0
  262. package/src/domains/share/archive.ts +67 -2
  263. package/src/engine/acp/server.ts +4 -1
  264. package/src/engine/prompt-templates.ts +18 -1
  265. package/src/engine/worker-runtime.ts +6 -3
  266. package/src/entry/orchestrator.ts +37 -0
  267. package/src/interactive/bus-notices.ts +26 -0
  268. package/src/interactive/chat-loop.ts +235 -1
  269. package/src/interactive/chat-renderer.ts +22 -0
  270. package/src/interactive/cost-overlay.ts +31 -3
  271. package/src/interactive/council-dispatch.ts +30 -0
  272. package/src/interactive/council-grid.ts +213 -0
  273. package/src/interactive/council.ts +99 -0
  274. package/src/interactive/dispatch-board.ts +311 -17
  275. package/src/interactive/fleet-run-preview.ts +307 -0
  276. package/src/interactive/footer/notifications.ts +219 -0
  277. package/src/interactive/handoff-round.ts +56 -0
  278. package/src/interactive/interactive-application.ts +43 -1
  279. package/src/interactive/interactive-event-projection.ts +23 -1
  280. package/src/interactive/interactive-slash-runtime.ts +52 -2
  281. package/src/interactive/interactive-subscriptions.ts +14 -2
  282. package/src/interactive/oracle.ts +179 -0
  283. package/src/interactive/overlay-ask-user-lifecycle.ts +6 -0
  284. package/src/interactive/overlay-general-openers.ts +190 -1
  285. package/src/interactive/overlay-key-routing.ts +17 -1
  286. package/src/interactive/overlay-lifecycle.ts +41 -1
  287. package/src/interactive/overlay-permission-lifecycle.ts +10 -0
  288. package/src/interactive/overlay-resource-openers.ts +11 -3
  289. package/src/interactive/overlay-session-lifecycle.ts +234 -2
  290. package/src/interactive/overlays/fleet-run-approval.ts +208 -0
  291. package/src/interactive/overlays/handoff-review.ts +185 -0
  292. package/src/interactive/overlays/library-install-confirm.ts +151 -0
  293. package/src/interactive/overlays/list-overlay.ts +168 -2
  294. package/src/interactive/overlays/settings.ts +221 -30
  295. package/src/interactive/overlays/side-question.ts +139 -0
  296. package/src/interactive/overlays/skills-hub.ts +401 -15
  297. package/src/interactive/side-question.ts +171 -0
  298. package/src/interactive/slash-commands.ts +439 -7
  299. package/src/interactive/slash-spec.ts +19 -6
  300. package/src/interactive/theme/tokens.ts +30 -0
  301. package/src/interactive/turn-middleware.ts +15 -1
  302. package/src/interactive/view/artifacts.ts +42 -9
  303. package/src/interactive/view/view-overlay.ts +15 -3
  304. package/src/interactive/watchdog-run.ts +75 -0
  305. package/src/interactive/worker-receipts.ts +14 -2
  306. package/src/interactive/worker-share.ts +56 -1
  307. package/src/interactive/worker-stream.ts +15 -0
  308. package/src/tools/bootstrap.ts +3 -0
  309. package/src/tools/compete-worktrees.ts +13 -79
  310. package/src/tools/dispatch-admission.ts +251 -18
  311. package/src/tools/dispatch-arguments.ts +84 -1
  312. package/src/tools/dispatch-plan.ts +165 -6
  313. package/src/tools/dispatch-runner.ts +364 -23
  314. package/src/tools/dispatch-types.ts +20 -1
  315. package/src/tools/dispatch.ts +72 -2
  316. package/src/tools/monitor.ts +29 -0
  317. package/src/tools/profiles.ts +18 -4
  318. package/src/tools/task-worktree.ts +238 -0
  319. package/src/tools/verify/authoring.ts +61 -1
  320. package/src/tools/verify/scripts.ts +62 -0
  321. package/src/tools/worker-evidence.ts +21 -14
  322. package/src/worker/spec-contract.ts +3 -1
  323. package/dist/chunk-HC4CLZ2Y.js +0 -68
@@ -12,7 +12,7 @@
12
12
  import { createHash, randomBytes } from "node:crypto";
13
13
  import { type Dirent, existsSync, readdirSync, readFileSync } from "node:fs";
14
14
  import { isAbsolute, relative, resolve as resolvePath, sep } from "node:path";
15
- import { BusChannels, type DispatchCompletedPayload } from "../../core/bus-events.js";
15
+ import { BusChannels, type DispatchCompletedPayload, type DispatchRunIdentity } from "../../core/bus-events.js";
16
16
  import { DEFAULT_SETTINGS, type DelegationToolGovernance } from "../../core/defaults.js";
17
17
  import type { DomainBundle, DomainContext, DomainExtension } from "../../core/domain-loader.js";
18
18
  import { GUARDRAIL_DEFAULTS } from "../../core/guardrails.js";
@@ -40,6 +40,12 @@ import { WORKER_RUNTIME_MEDIATES_CLIO_DISPATCH } from "../../engine/worker-runti
40
40
  import { toolPromptHintsForNames } from "../../tools/builtin-tool-catalog.js";
41
41
  import { networkToolsDisabled } from "../../tools/network-policy.js";
42
42
  import { applyToolProfile, assertToolProfileEnforceable, type ToolProfileName } from "../../tools/profiles.js";
43
+ import {
44
+ applyTaskWorktree,
45
+ cleanupTaskWorktree,
46
+ createTaskWorktree,
47
+ gitCheckoutRoot,
48
+ } from "../../tools/task-worktree.js";
43
49
  import { truncateUtf8 } from "../../tools/truncate-utf8.js";
44
50
  import {
45
51
  serializeWorkerRuntimeDescriptor,
@@ -117,6 +123,7 @@ import { AssignmentRegistry, applyActiveRouteSelection, asAssignmentId } from ".
117
123
  import type { AssignmentAttemptStartEvent } from "./assignment-events.js";
118
124
  import { reconcileOrphanAssignments } from "./assignment-reconcile.js";
119
125
  import {
126
+ assignmentProcessOwnerAlive,
120
127
  cancelStoredAssignment,
121
128
  failQueuedAssignment,
122
129
  getStoredAssignment,
@@ -138,6 +145,7 @@ import { type BatchState, createBatch, onRunComplete, snapshotBatch } from "./ba
138
145
  import { type RunToolBudgetEnvelope, resolveToolBudgetEnvelope } from "./budget-envelope.js";
139
146
  import { assessCapabilityMismatch, type CapabilityMismatch } from "./capability-match.js";
140
147
  import { capacityLeaseUsage, createNodeLeaseUsageReader } from "./capacity-lease.js";
148
+ import { acquireCheckoutWriterLease, type CheckoutWriterLease } from "./checkout-writer-lease.js";
141
149
  import type {
142
150
  DispatchAdmissionObserver,
143
151
  DispatchContract,
@@ -159,6 +167,8 @@ import {
159
167
  import { routeFactVerdict } from "./fleet-preflight.js";
160
168
  import { competeStanceLiner, isBoundedGateRolePrompt } from "./gate-role-prompts.js";
161
169
  import { classifyHeartbeat, DEFAULT_HEARTBEAT_SPEC, type HeartbeatSpec, type HeartbeatStatus } from "./heartbeat.js";
170
+ import { hostVerificationRejection, runHostVerification } from "./host-verification.js";
171
+ import { renderDispatchIntentRequirements } from "./intent-requirements.js";
162
172
  import { adaptJointRouteInput } from "./joint-route-adapter.js";
163
173
  import {
164
174
  configuredJointNodes,
@@ -173,6 +183,12 @@ import {
173
183
  resultContractWasDue,
174
184
  runStatusForOutcome,
175
185
  } from "./outcome.js";
186
+ import {
187
+ type DispatchPathScope,
188
+ declaredScopeReplacementDiagnostic,
189
+ declaredScopeReplacementNotice,
190
+ resolveDispatchPathScope,
191
+ } from "./path-scope.js";
176
192
  import { deriveEnvelopePhaseDurations, deriveRunPhaseDurations, recordRunTimingBestEffort } from "./phase-timing.js";
177
193
  import { createFleetPlacementPreviewResolver, createFleetPlacementResolver } from "./placement.js";
178
194
  import {
@@ -209,7 +225,7 @@ import { createRouteObserver, type RouteObservationHandle, type RouteObserver }
209
225
  import { reduceRouteQuality } from "./route-quality.js";
210
226
  import { defaultRoutingIntent } from "./routing-intent.js";
211
227
  import { detectRunIdentity } from "./run-identity.js";
212
- import { type Ledger, openLedger } from "./state.js";
228
+ import { type Ledger, newRunId, openLedger } from "./state.js";
213
229
  import {
214
230
  countToolCalls,
215
231
  hasPotentiallyMutatingAttempt,
@@ -606,6 +622,14 @@ function recordWorkerRunEffect(recorder: RunEffectsRecorder, event: Record<strin
606
622
  */
607
623
  const DISPATCH_DRAIN_GRACE_MS = 2000;
608
624
 
625
+ /**
626
+ * How many finalized runs keep their observed write targets in memory. The
627
+ * write boundary reads a run's record while closing the window it ran in, which
628
+ * is the same wave, so a few hundred runs of headroom is far more than the
629
+ * lookup ever reaches back for.
630
+ */
631
+ const RUN_WRITE_TARGET_HISTORY = 512;
632
+
609
633
  async function awaitEventDrain(drained: Promise<void>, graceMs = DISPATCH_DRAIN_GRACE_MS): Promise<boolean> {
610
634
  let timer: ReturnType<typeof setTimeout> | undefined;
611
635
  const grace = new Promise<false>((resolve) => {
@@ -743,9 +767,14 @@ function pickOrchestratorScope(safety: SafetyContract): ScopeSpec {
743
767
  return safety.scopes.workspace;
744
768
  }
745
769
 
746
- function pickWorkerScope(safety: SafetyContract, requestedActions: ReadonlyArray<ActionClass>): ScopeSpec {
770
+ function pickWorkerScope(
771
+ safety: SafetyContract,
772
+ requestedActions: ReadonlyArray<ActionClass>,
773
+ pathScope: DispatchPathScope,
774
+ ): ScopeSpec {
747
775
  if (requestedActions.every((action) => action === "read")) return safety.scopes.readonly;
748
- return safety.scopes.workspace;
776
+ if (pathScope.writeBoundaries.length === 0) return safety.scopes.workspace;
777
+ return { ...safety.scopes.workspace, allowedWriteRoots: pathScope.writeBoundaries };
749
778
  }
750
779
 
751
780
  function deriveRequestedActions(tools: ReadonlyArray<ToolName>, safety: SafetyContract): ReadonlyArray<ActionClass> {
@@ -985,6 +1014,25 @@ function hasPersonaOverride(req: DispatchRequest): boolean {
985
1014
  return typeof req.systemPrompt === "string" && req.systemPrompt.trim().length > 0;
986
1015
  }
987
1016
 
1017
+ /**
1018
+ * Whether this request's system prompt is a caller-authored persona rather
1019
+ * than one of the coordinator's own bounded gate-role prompts.
1020
+ *
1021
+ * A reviewer, a compete judge, and a council synthesis all carry a system
1022
+ * prompt the coordinator wrote, pinned by {@link isBoundedGateRolePrompt} to
1023
+ * one exact text under one gate role and read-only autonomy. That is the
1024
+ * topology speaking, not an operator reshaping a recipe, so it is not the
1025
+ * thing the shadow and internal audiences are protected from. The ACP
1026
+ * delegation path already draws the line here; without the same line, a
1027
+ * council that seats the builtin read-only `researcher` (a shadow recipe, and
1028
+ * the agent every unnamed council seats) refused `--synthesis judge` outright,
1029
+ * before a single member ran.
1030
+ */
1031
+ function hasCallerPersonaOverride(req: DispatchRequest): boolean {
1032
+ if (!hasPersonaOverride(req)) return false;
1033
+ return !isBoundedGateRolePrompt({ role: req.gate?.role, autonomy: req.autonomy, systemPrompt: req.systemPrompt });
1034
+ }
1035
+
988
1036
  function personaOverrideFor(req: DispatchRequest, staticCompositionHash: string | null): RunPersonaOverride | null {
989
1037
  if (!hasPersonaOverride(req) || staticCompositionHash === null) return null;
990
1038
  return { promptHash: staticCompositionHash };
@@ -995,9 +1043,8 @@ function personaOverrideFor(req: DispatchRequest, staticCompositionHash: string
995
1043
  * flows through dynamic messages, never through the system prompt. The system
996
1044
  * prompt itself is stable per recipe and tool surface with one exception: the
997
1045
  * operator-editable layer (`additionalFragments`) folds in path-scoped project
998
- * rules selected by `workerWorkingContextPaths`, which are recalled from the
999
- * task and briefing text, so `staticCompositionHash` can differ between two
1000
- * runs of one recipe when a rule's glob matches one task and not the other.
1046
+ * rules selected from the canonical request scope, so `staticCompositionHash`
1047
+ * can differ between two runs of one recipe when their scoped rules differ.
1001
1048
  * A local model's worker prefix cache misses on that flip; the trade was made
1002
1049
  * deliberately in #96 to keep rules scoped rather than shipped wholesale.
1003
1050
  */
@@ -1066,35 +1113,6 @@ function workerProjectContextIncludesVerification(body: string): boolean {
1066
1113
  return body.includes("\nVerification expectations:\n");
1067
1114
  }
1068
1115
 
1069
- /**
1070
- * Path-like tokens in free text: a slash-separated segment, or a bare
1071
- * `name.ext` token. The model-facing dispatch tool has no structured
1072
- * path/file field, so a worker's task text is the only working-context
1073
- * signal available at compile time; this is best-effort recall, not a
1074
- * parser. A false-positive token is harmless because `selectActiveRules`
1075
- * only activates a rule when the token matches that rule's own glob.
1076
- */
1077
- const PATH_TOKEN_RE = /(?:[\w.-]+\/)+[\w.-]+|\b[\w-]+\.[A-Za-z0-9]{1,8}\b/g;
1078
-
1079
- /**
1080
- * Best-effort working-context paths for a dispatched worker: `writeRoots`
1081
- * when the caller set them (the precise signal, for internal/programmatic
1082
- * callers), plus path-like tokens recalled from the task and briefing text.
1083
- * Feeds the same `selectActiveRules` a session uses, so a worker whose task
1084
- * touches a ruled path reads that rule instead of never hearing about it.
1085
- */
1086
- function workerWorkingContextPaths(req: DispatchRequest): string[] {
1087
- const out = new Set<string>();
1088
- for (const root of req.writeRoots ?? []) out.add(root);
1089
- const text = `${req.task}\n${req.briefing ?? ""}`;
1090
- for (const match of text.matchAll(PATH_TOKEN_RE)) {
1091
- const token = match[0];
1092
- if (token.startsWith("http://") || token.startsWith("https://")) continue;
1093
- out.add(token);
1094
- }
1095
- return [...out];
1096
- }
1097
-
1098
1116
  export function buildDynamicPromptMessages(
1099
1117
  req: DispatchRequest,
1100
1118
  dynamicContext: WorkerDynamicContext = {},
@@ -1118,6 +1136,10 @@ export function buildDynamicPromptMessages(
1118
1136
  const body = `Safety posture: autonomy ${autonomy}. ${workerSafetyOneLiner(autonomy, permission)} Worker permission routing: ${permission}.`;
1119
1137
  messages.push({ id: "dispatch-safety-posture", body, contentHash: sha256(body) });
1120
1138
  }
1139
+ const requirements = renderDispatchIntentRequirements(req.intent);
1140
+ if (requirements !== null) {
1141
+ messages.push({ id: "dispatch-intent-requirements", body: requirements, contentHash: sha256(requirements) });
1142
+ }
1121
1143
  if (req.competeStance !== undefined) {
1122
1144
  const body = competeStanceLiner(req.competeStance);
1123
1145
  messages.push({ id: "dispatch-compete-stance", body, contentHash: sha256(body) });
@@ -1242,6 +1264,7 @@ interface DispatchAdmissionStage {
1242
1264
 
1243
1265
  interface DispatchWorkerSpecInput {
1244
1266
  req: DispatchRequest;
1267
+ pathScope: DispatchPathScope;
1245
1268
  target: ResolvedTarget;
1246
1269
  admission: DispatchAdmissionStage;
1247
1270
  recipe?: AgentRecipe | null;
@@ -1261,6 +1284,7 @@ interface DispatchWorkerSpecInput {
1261
1284
 
1262
1285
  interface DispatchLifecycleStage {
1263
1286
  recipe: AgentRecipe | null;
1287
+ pathScope: DispatchPathScope;
1264
1288
  admission: DispatchAdmissionStage;
1265
1289
  target: ResolvedTarget;
1266
1290
  cwd: string;
@@ -1296,6 +1320,7 @@ interface DispatchLifecycleStage {
1296
1320
 
1297
1321
  interface AcpDelegationLifecycleStage {
1298
1322
  admission: DispatchAdmissionStage;
1323
+ pathScope: DispatchPathScope;
1299
1324
  agentConfig: ReturnType<ConfigContract["get"]>["delegation"]["agents"][number];
1300
1325
  cwd: string;
1301
1326
  systemPrompt: string;
@@ -1480,10 +1505,6 @@ function explicitToolCapability(
1480
1505
  return typeof fromKnowledgeBase === "boolean" ? fromKnowledgeBase : null;
1481
1506
  }
1482
1507
 
1483
- function writeConfinedRequest(req: DispatchRequest): boolean {
1484
- return req.writeRoots !== undefined && req.writeRoots.length > 0;
1485
- }
1486
-
1487
1508
  function effectiveToolNames(
1488
1509
  allowedTools: ReadonlyArray<ToolName>,
1489
1510
  target: ResolvedTarget,
@@ -1709,6 +1730,7 @@ function resolveDispatchAdmissionStage(
1709
1730
  req: DispatchRequest,
1710
1731
  recipe: AgentRecipe,
1711
1732
  safety: SafetyContract,
1733
+ pathScope: DispatchPathScope,
1712
1734
  ): DispatchAdmissionStage {
1713
1735
  const recipeTools = recipe.tools;
1714
1736
  const candidateTools = recipeTools && recipeTools.length > 0 ? (Array.from(recipeTools) as ToolName[]) : [];
@@ -1722,7 +1744,7 @@ function resolveDispatchAdmissionStage(
1722
1744
  }
1723
1745
  const requestedActions = deriveRequestedActions(allowedTools, safety);
1724
1746
  const orchScope = pickOrchestratorScope(safety);
1725
- const workerScope = pickWorkerScope(safety, requestedActions);
1747
+ const workerScope = pickWorkerScope(safety, requestedActions, pathScope);
1726
1748
  const verdict = admit(
1727
1749
  {
1728
1750
  requestedScope: workerScope,
@@ -1747,7 +1769,7 @@ function resolveDelegationAdmissionStage(req: DispatchRequest, safety: SafetyCon
1747
1769
  const allowedTools = applyToolProfile([], req.toolProfile);
1748
1770
  const requestedActions = deriveRequestedActions(allowedTools, safety);
1749
1771
  const orchScope = pickOrchestratorScope(safety);
1750
- const workerScope = pickWorkerScope(safety, requestedActions);
1772
+ const workerScope = pickWorkerScope(safety, requestedActions, resolveDispatchPathScope(req));
1751
1773
  const verdict = admit(
1752
1774
  {
1753
1775
  requestedScope: workerScope,
@@ -1825,9 +1847,17 @@ function buildDispatchWorkerSpec(input: DispatchWorkerSpecInput, config?: Config
1825
1847
  // The worker repairs against exactly the contract the orchestrator will seal.
1826
1848
  // A gate role that overrides the recipe contract gets no worker-side repair,
1827
1849
  // for the same reason it gets no recipe validation.
1828
- if (appliesRecipeResultContract(input.req.gate?.role) && input.recipe?.resultContract) {
1829
- spec.resultContract = input.recipe.resultContract;
1830
- }
1850
+ //
1851
+ // The request's own override is the contract whether or not the seated
1852
+ // recipe declares one, because that is the resolution the seal already uses.
1853
+ // Requiring a recipe contract here meant a caller-supplied override against a
1854
+ // recipe with none was sealed but never sent, so the worker spent no repair
1855
+ // round on a contract it was never told about and the run failed on a shape
1856
+ // nothing had asked it for.
1857
+ const workerResultContract = appliesRecipeResultContract(input.req.gate?.role)
1858
+ ? (input.req.resultContractOverride ?? input.recipe?.resultContract)
1859
+ : undefined;
1860
+ if (workerResultContract) spec.resultContract = workerResultContract;
1831
1861
  const product = input.req.product ?? input.recipe?.product;
1832
1862
  if (product) spec.product = product;
1833
1863
  // The orchestrator's tool decision is the one the run was admitted under and
@@ -1878,14 +1908,9 @@ function buildDispatchWorkerSpec(input: DispatchWorkerSpecInput, config?: Config
1878
1908
  if (escalation) spec.escalation = { timeoutMs: escalation.timeoutMs, fallback: escalation.fallback };
1879
1909
  }
1880
1910
  assertRuntimeCanHonorWorkerPermissionMode(input.target.runtime, spec.onPermission);
1881
- // Carry write-root confinement to the worker safety seam. Refuse it up front
1882
- // on runtimes that cannot mediate per-tool calls (subprocess) so it is never
1883
- // silently ignored. Roots are resolved against the job cwd so a relative root
1884
- // reaches the worker absolute, matching the validation contract.
1885
- if (input.req.writeRoots !== undefined && input.req.writeRoots.length > 0) {
1886
- const jobCwd = input.req.cwd !== undefined && input.req.cwd.length > 0 ? input.req.cwd : process.cwd();
1887
- spec.writeRoots = input.req.writeRoots.map((root) => resolvePath(jobCwd, root));
1888
- }
1911
+ // Carry canonical write boundaries to the worker safety seam. Exact files
1912
+ // remain exact and trailing-slash entries remain subtrees after resolution.
1913
+ if (input.pathScope.writeBoundaries.length > 0) spec.writeRoots = [...input.pathScope.writeBoundaries];
1889
1914
  assertWriteRootsEnforceable(input.target.runtime, spec.writeRoots);
1890
1915
  // Carry the tool profile so external CLI runtimes that cannot mediate
1891
1916
  // per-tool calls can refuse a narrowing profile they would otherwise ignore.
@@ -2380,6 +2405,26 @@ export function createDispatchBundle(
2380
2405
  const active = new Map<string, ActiveRun>();
2381
2406
  const targetCooldowns = new Map<string, { until: number; reason: string }>();
2382
2407
  const ownedReservations = new Set<string>();
2408
+ /**
2409
+ * Paths each finalized run's own tool calls aimed a successful mutation at,
2410
+ * newest last. `null` records a run whose write record is unusable, which is
2411
+ * a different answer from an empty list and has to survive as one.
2412
+ *
2413
+ * Held in memory rather than sealed into the receipt: the only reader is the
2414
+ * write boundary closing a window seconds later in this process, and a
2415
+ * per-run path list is run-shaped evidence the receipt schema does not carry.
2416
+ * Bounded because a long session finalizes many runs and none of this is
2417
+ * needed once its window has closed.
2418
+ */
2419
+ const runWriteTargets = new Map<string, ReadonlyArray<string> | null>();
2420
+ const recordRunWriteTargets = (runId: string, paths: ReadonlyArray<string> | null): void => {
2421
+ runWriteTargets.set(runId, paths);
2422
+ while (runWriteTargets.size > RUN_WRITE_TARGET_HISTORY) {
2423
+ const oldest = runWriteTargets.keys().next();
2424
+ if (oldest.done === true) break;
2425
+ runWriteTargets.delete(oldest.value);
2426
+ }
2427
+ };
2383
2428
  const capacityAdmission = createCapacityAdmissionController({
2384
2429
  limits: () => {
2385
2430
  const settings = getEffectiveSettings();
@@ -3161,6 +3206,27 @@ export function createDispatchBundle(
3161
3206
  targetCooldowns.set(key, { until: now() + cooldownMs, reason: failureClass });
3162
3207
  }
3163
3208
 
3209
+ function publishDispatchPathScope(req: DispatchRequest, pathScope: DispatchPathScope): void {
3210
+ const replacementDiagnostic = declaredScopeReplacementDiagnostic(pathScope);
3211
+ if (replacementDiagnostic !== null) {
3212
+ reportDispatchDiagnostic("typed scope replacement", new Error(replacementDiagnostic));
3213
+ }
3214
+ // Only the replacement case reaches the transcript. A dispatch that
3215
+ // declared no intent is the ordinary case rather than an anomaly, and one
3216
+ // receipt in ninety-nine carries an intent key today, so noticing it would
3217
+ // warn on almost every dispatch and teach the operator to skip the channel.
3218
+ // Its provenance is not lost: the receipt seals `pathProvenance`, and the
3219
+ // approval artifact renders every inferred entry in full before a
3220
+ // supervised dispatch runs, which is where an operator can still act on it.
3221
+ const notice = declaredScopeReplacementNotice(pathScope);
3222
+ if (notice !== null) {
3223
+ context.bus.emit(BusChannels.DispatchScopeNotice, {
3224
+ ...notice,
3225
+ agentId: req.agentId,
3226
+ });
3227
+ }
3228
+ }
3229
+
3164
3230
  async function resolveLifecycle(req: DispatchRequest, settings: EffectiveSettings): Promise<DispatchLifecycleStage> {
3165
3231
  const recipe = agents.get(req.agentId);
3166
3232
  if (!recipe) {
@@ -3172,7 +3238,7 @@ export function createDispatchBundle(
3172
3238
  `dispatch: agent '${req.agentId}' is a ${spec.audience} agent reserved for Clio internal orchestration`,
3173
3239
  );
3174
3240
  }
3175
- if (hasPersonaOverride(req) && (spec.audience === "shadow" || spec.audience === "internal")) {
3241
+ if (hasCallerPersonaOverride(req) && (spec.audience === "shadow" || spec.audience === "internal")) {
3176
3242
  throw new Error(`dispatch: persona overrides are not allowed for ${spec.audience} agent '${req.agentId}'`);
3177
3243
  }
3178
3244
  // A read-only recipe pointed at a mutating task is knowable here and costs
@@ -3194,7 +3260,9 @@ export function createDispatchBundle(
3194
3260
  specs: agents.listSpecs(),
3195
3261
  });
3196
3262
  if (capabilityMismatch?.verdict === "refuse") throw new Error(capabilityMismatch.detail);
3197
- const admission = resolveDispatchAdmissionStage(req, recipe, safety);
3263
+ const pathScope = resolveDispatchPathScope(req);
3264
+ publishDispatchPathScope(req, pathScope);
3265
+ const admission = resolveDispatchAdmissionStage(req, recipe, safety, pathScope);
3198
3266
  const targets = readWorkerTargets(settings);
3199
3267
  const target = resolveDispatchTarget(
3200
3268
  req,
@@ -3213,7 +3281,7 @@ export function createDispatchBundle(
3213
3281
  const sessionAutonomy = settings?.autonomy ?? "auto-edit";
3214
3282
  const effectiveAutonomy = clampWorkerAutonomy(sessionAutonomy, req.autonomy);
3215
3283
  const effectiveTools = withLedgerToolNarrowing(
3216
- effectiveToolNames(admission.allowedTools, target, writeConfinedRequest(req), deniedToolNames(req)),
3284
+ effectiveToolNames(admission.allowedTools, target, pathScope.writeBoundaries.length > 0, deniedToolNames(req)),
3217
3285
  req,
3218
3286
  );
3219
3287
  assertPostRuntimeToolCompatibility(req.agentId, spec, effectiveTools, target);
@@ -3242,7 +3310,7 @@ export function createDispatchBundle(
3242
3310
  dynamic: false,
3243
3311
  },
3244
3312
  cwd,
3245
- workingContextPaths: workerWorkingContextPaths(req),
3313
+ workingContextPaths: pathScope.workingContextPaths,
3246
3314
  });
3247
3315
  const systemPrompt = compiledWorkerPrompt.systemPrompt;
3248
3316
  const budgetEnvelope = resolveEffectiveWorkerBudget({
@@ -3286,6 +3354,7 @@ export function createDispatchBundle(
3286
3354
  const limitations = runtimeLimitations(runtimeKind, target.runtime.id);
3287
3355
  return {
3288
3356
  recipe,
3357
+ pathScope,
3289
3358
  admission: effectiveAdmission,
3290
3359
  target,
3291
3360
  cwd,
@@ -3323,12 +3392,7 @@ export function createDispatchBundle(
3323
3392
  ): AcpDelegationLifecycleStage {
3324
3393
  const agentId = req.delegationAgentId;
3325
3394
  if (!agentId) throw new Error("dispatch: missing delegationAgentId");
3326
- const boundedGateRolePrompt = isBoundedGateRolePrompt({
3327
- role: req.gate?.role,
3328
- autonomy: req.autonomy,
3329
- systemPrompt: req.systemPrompt,
3330
- });
3331
- if (hasPersonaOverride(req) && !boundedGateRolePrompt) {
3395
+ if (hasCallerPersonaOverride(req)) {
3332
3396
  throw new Error(`dispatch: persona overrides are not allowed for ACP delegation agent '${agentId}'`);
3333
3397
  }
3334
3398
  if (req.agentId && maybeAgents) {
@@ -3357,6 +3421,7 @@ export function createDispatchBundle(
3357
3421
  );
3358
3422
  }
3359
3423
  const cwd = req.cwd ?? process.cwd();
3424
+ const pathScope = resolveDispatchPathScope(req);
3360
3425
  const personaBody = workerPersonaBody(req, null, []);
3361
3426
  // ACP owns an unknown external tool inventory, so it receives the raw
3362
3427
  // bounded persona rather than a native Clio schema-harness claim.
@@ -3383,6 +3448,7 @@ export function createDispatchBundle(
3383
3448
  const personaOverride = personaOverrideFor(req, staticCompositionHash);
3384
3449
  return {
3385
3450
  admission,
3451
+ pathScope,
3386
3452
  agentConfig: configured,
3387
3453
  cwd,
3388
3454
  systemPrompt,
@@ -3418,6 +3484,7 @@ export function createDispatchBundle(
3418
3484
  finalPromise: Promise<RunReceipt>;
3419
3485
  }> {
3420
3486
  const lifecycle = resolveAcpDelegationLifecycle(req, settings);
3487
+ publishDispatchPathScope(req, lifecycle.pathScope);
3421
3488
  timing.decisionCompletedAt = new Date(now()).toISOString();
3422
3489
  const targetId = `delegation:${lifecycle.agentConfig.id}`;
3423
3490
  const runtimeId = "acp";
@@ -3610,10 +3677,12 @@ export function createDispatchBundle(
3610
3677
  let identity!: ReturnType<typeof detectRunIdentity>;
3611
3678
  try {
3612
3679
  envelope = ledgerRef.create({
3680
+ ...(req.runIdHint !== undefined ? { id: req.runIdHint } : {}),
3613
3681
  agentId: req.agentId,
3614
3682
  executionRole: withAttemptRole(req.executionRole, req.lineage?.attempt ?? 0),
3615
3683
  requestOrigin: lifecycle.requestOrigin,
3616
3684
  task: req.task,
3685
+ ...(req.intent !== undefined ? { intent: structuredClone(req.intent) } : {}),
3617
3686
  targetId,
3618
3687
  wireModelId,
3619
3688
  runtimeId,
@@ -3645,6 +3714,7 @@ export function createDispatchBundle(
3645
3714
  ...(lifecycle.pipeline ? { pipeline: lifecycle.pipeline } : {}),
3646
3715
  ...(lifecycle.briefing ? { briefing: lifecycle.briefing } : {}),
3647
3716
  ...(req.gate !== undefined ? { gate: req.gate } : {}),
3717
+ ...(req.council !== undefined ? { council: req.council } : {}),
3648
3718
  ...(req.plan !== undefined ? { plan: req.plan } : {}),
3649
3719
  ...(lifecycle.personaOverride ? { personaOverride: lifecycle.personaOverride } : {}),
3650
3720
  });
@@ -3787,6 +3857,8 @@ export function createDispatchBundle(
3787
3857
  executionRole: envelope.executionRole,
3788
3858
  requestOrigin: lifecycle.requestOrigin,
3789
3859
  task: req.task,
3860
+ ...(req.intent !== undefined ? { intent: structuredClone(req.intent) } : {}),
3861
+ pathScope: structuredClone(lifecycle.pathScope.provenance),
3790
3862
  targetId,
3791
3863
  wireModelId,
3792
3864
  runtimeId,
@@ -3801,6 +3873,7 @@ export function createDispatchBundle(
3801
3873
  ...(lifecycle.pipeline ? { pipeline: lifecycle.pipeline } : {}),
3802
3874
  ...(lifecycle.briefing ? { briefing: lifecycle.briefing } : {}),
3803
3875
  ...(req.gate !== undefined ? { gate: req.gate } : {}),
3876
+ ...(req.council !== undefined ? { council: req.council } : {}),
3804
3877
  ...(req.plan !== undefined ? { plan: req.plan } : {}),
3805
3878
  ...(lifecycle.personaOverride ? { personaOverride: lifecycle.personaOverride } : {}),
3806
3879
  projectContext: lifecycle.projectContext,
@@ -3913,6 +3986,7 @@ export function createDispatchBundle(
3913
3986
  durationMs,
3914
3987
  exitCode: receipt.exitCode,
3915
3988
  toolActivity: receipt.toolActivity ?? null,
3989
+ ...(receipt.hostVerification !== undefined ? { hostVerification: receipt.hostVerification.status } : {}),
3916
3990
  ...(receipt.skillActivations && receipt.skillActivations.length > 0
3917
3991
  ? { skillActivations: [...receipt.skillActivations] }
3918
3992
  : {}),
@@ -4262,6 +4336,7 @@ export function createDispatchBundle(
4262
4336
  const spec = buildDispatchWorkerSpec(
4263
4337
  {
4264
4338
  req,
4339
+ pathScope: lifecycle.pathScope,
4265
4340
  target: lifecycle.target,
4266
4341
  admission: lifecycle.admission,
4267
4342
  recipe: lifecycle.recipe,
@@ -4444,7 +4519,8 @@ export function createDispatchBundle(
4444
4519
  if (
4445
4520
  event.type === "clio_run_outcome" &&
4446
4521
  isRunOutcomeCode(event.payload?.outcomeCode) &&
4447
- event.payload.outcomeCode !== "worker_final_output_missing"
4522
+ event.payload.outcomeCode !== "worker_final_output_missing" &&
4523
+ event.payload.outcomeCode !== "host_verification_rejected"
4448
4524
  ) {
4449
4525
  if (acceptsOutcomeCodeEvents) {
4450
4526
  trustedOutcomeCodes.add(event.payload.outcomeCode);
@@ -4604,11 +4680,13 @@ export function createDispatchBundle(
4604
4680
  let identity!: ReturnType<typeof detectRunIdentity>;
4605
4681
  try {
4606
4682
  envelope = ledgerRef.create({
4683
+ ...(req.runIdHint !== undefined ? { id: req.runIdHint } : {}),
4607
4684
  agentId: req.agentId,
4608
4685
  executionRole: withAttemptRole(req.executionRole, req.lineage?.attempt ?? 0),
4609
4686
  agentAudience: lifecycle.agentAudience,
4610
4687
  requestOrigin: lifecycle.requestOrigin,
4611
4688
  task: req.task,
4689
+ ...(req.intent !== undefined ? { intent: structuredClone(req.intent) } : {}),
4612
4690
  budget: lifecycle.budgetEnvelope,
4613
4691
  targetId: lifecycle.target.target.id,
4614
4692
  wireModelId: lifecycle.target.wireModelId,
@@ -4658,6 +4736,7 @@ export function createDispatchBundle(
4658
4736
  ...(lifecycle.pipeline ? { pipeline: lifecycle.pipeline } : {}),
4659
4737
  ...(lifecycle.briefing ? { briefing: lifecycle.briefing } : {}),
4660
4738
  ...(req.gate !== undefined ? { gate: req.gate } : {}),
4739
+ ...(req.council !== undefined ? { council: req.council } : {}),
4661
4740
  ...(req.plan !== undefined ? { plan: req.plan } : {}),
4662
4741
  ...(lifecycle.personaOverride ? { personaOverride: lifecycle.personaOverride } : {}),
4663
4742
  ...(heartbeatAt ? { heartbeatAt: heartbeatIso(heartbeatAt.current) } : {}),
@@ -4678,6 +4757,7 @@ export function createDispatchBundle(
4678
4757
  budget: lifecycle.budgetEnvelope,
4679
4758
  ...(placement !== undefined && placement !== null ? { node: placement.node.id } : {}),
4680
4759
  ...(req.gate !== undefined ? { gate: { role: req.gate.role, cycle: req.gate.cycle } } : {}),
4760
+ ...(req.council !== undefined ? { council: req.council } : {}),
4681
4761
  ...(req.reroutes !== undefined && req.reroutes.length > 0 ? { rerouteCount: req.reroutes.length } : {}),
4682
4762
  ...(lifecycle.target.modelCapabilities !== undefined &&
4683
4763
  lifecycle.target.modelCapabilities !== null &&
@@ -4829,6 +4909,15 @@ export function createDispatchBundle(
4829
4909
  const tokenCount =
4830
4910
  tokenMeter.inputTokens + tokenMeter.outputTokens + tokenMeter.cacheReadTokens + tokenMeter.cacheWriteTokens;
4831
4911
  const protectedArtifacts = protectedArtifactReceiptSummary(spec.protectedArtifactState);
4912
+ const fleetGate = (() => {
4913
+ if (req.fleetGateReceipt === undefined) return null;
4914
+ try {
4915
+ const content = readFileSync(resolvePath(lifecycle.cwd, req.fleetGateReceipt.path));
4916
+ return { path: req.fleetGateReceipt.path, pathHash: createHash("sha256").update(content).digest("hex") };
4917
+ } catch {
4918
+ return { path: req.fleetGateReceipt.path, pathHash: createHash("sha256").update("").digest("hex") };
4919
+ }
4920
+ })();
4832
4921
  // Sealed from the stored board, never from anything the worker said
4833
4922
  // about itself. Absent when the run had no ledger.
4834
4923
  const ledgerContribution = (() => {
@@ -4864,6 +4953,8 @@ export function createDispatchBundle(
4864
4953
  outcome,
4865
4954
  outcomeCode,
4866
4955
  lineage,
4956
+ ...(req.intent !== undefined ? { intent: structuredClone(req.intent) } : {}),
4957
+ pathScope: structuredClone(lifecycle.pathScope.provenance),
4867
4958
  identity,
4868
4959
  node: placement?.node ?? LOCAL_RUN_NODE,
4869
4960
  ...receiptAttestationFields(worker.attestation?.() ?? null),
@@ -4874,7 +4965,9 @@ export function createDispatchBundle(
4874
4965
  ...(lifecycle.briefing ? { briefing: lifecycle.briefing } : {}),
4875
4966
  ...(steering.length > 0 ? { steering: steering.map((entry) => ({ ...entry })) } : {}),
4876
4967
  ...(req.gate !== undefined ? { gate: req.gate } : {}),
4968
+ ...(req.council !== undefined ? { council: req.council } : {}),
4877
4969
  ...(req.plan !== undefined ? { plan: req.plan } : {}),
4970
+ ...(fleetGate !== null ? { fleetGate } : {}),
4878
4971
  ...(lifecycle.personaOverride ? { personaOverride: lifecycle.personaOverride } : {}),
4879
4972
  projectContext: lifecycle.projectContext,
4880
4973
  rulesApplied: lifecycle.rulesApplied,
@@ -5004,6 +5097,7 @@ export function createDispatchBundle(
5004
5097
  durationMs,
5005
5098
  exitCode: receipt.exitCode,
5006
5099
  toolActivity: receipt.toolActivity ?? null,
5100
+ ...(receipt.hostVerification !== undefined ? { hostVerification: receipt.hostVerification.status } : {}),
5007
5101
  ...(receipt.skillActivations && receipt.skillActivations.length > 0
5008
5102
  ? { skillActivations: [...receipt.skillActivations] }
5009
5103
  : {}),
@@ -5102,7 +5196,7 @@ export function createDispatchBundle(
5102
5196
  const capturedOutput = outputCapture.snapshot();
5103
5197
  const observedRunEffects = runEffects.snapshot();
5104
5198
  const appliedResultContract = appliesRecipeResultContract(req.gate?.role)
5105
- ? (lifecycle.recipe?.resultContract ?? null)
5199
+ ? (req.resultContractOverride ?? lifecycle.recipe?.resultContract ?? null)
5106
5200
  : null;
5107
5201
  const resultContract = validateRecipeResult({
5108
5202
  contract: appliedResultContract,
@@ -5166,13 +5260,40 @@ export function createDispatchBundle(
5166
5260
  finalDetail = [finalDetail, describeUngroundedValidations(validationGrounding)].filter(Boolean).join("; ");
5167
5261
  }
5168
5262
  const hasTerminalArtifact =
5169
- lifecycle.recipe?.resultContract.kind === "architect-plan" && resultValidation?.conformance === "pass";
5263
+ (req.resultContractOverride ?? lifecycle.recipe?.resultContract)?.kind === "architect-plan" &&
5264
+ resultValidation?.conformance === "pass";
5170
5265
  if (finalOutcome === "succeeded" && !hasDurableFinalOutput(capturedOutput) && !hasTerminalArtifact) {
5171
5266
  finalOutcome = "failed";
5172
5267
  outcomeCode = "worker_final_output_missing";
5173
5268
  finalDetail = WORKER_FINAL_OUTPUT_MISSING_DETAIL;
5174
5269
  failureMessage = finalDetail;
5175
5270
  }
5271
+ const hostVerification = await runHostVerification({
5272
+ runId: envelope.id,
5273
+ request: req,
5274
+ workerSuccessful: finalOutcome === "succeeded",
5275
+ onDiagnostic: (error) => reportDispatchDiagnostic(`write host verification memo for ${envelope.id}`, error),
5276
+ });
5277
+ const hostRejection = hostVerificationRejection(hostVerification);
5278
+ if (hostRejection !== null && finalOutcome === "succeeded") {
5279
+ finalOutcome = "failed";
5280
+ outcomeCode = hostRejection.outcomeCode;
5281
+ finalDetail = hostRejection.detail;
5282
+ failureMessage = finalDetail;
5283
+ }
5284
+ let worktreeReceipt: RunReceiptDraft["worktree"];
5285
+ if (req.taskWorktree !== undefined && finalOutcome === "succeeded") {
5286
+ worktreeReceipt = applyTaskWorktree({
5287
+ worktree: req.taskWorktree,
5288
+ apply: req.apply ?? "merge",
5289
+ protectedPaths: getProtectedArtifactState().artifacts.map((artifact) => artifact.path),
5290
+ });
5291
+ if (worktreeReceipt.reason !== undefined) {
5292
+ finalOutcome = "failed";
5293
+ finalDetail = worktreeReceipt.reason;
5294
+ failureMessage = finalDetail;
5295
+ }
5296
+ }
5176
5297
  const status = runStatusForOutcome(finalOutcome);
5177
5298
  const failureClass = classifyFailure(evidence, result, finalOutcome, outcomeCode);
5178
5299
  const receiptDraft = buildReceiptDraft(
@@ -5186,6 +5307,8 @@ export function createDispatchBundle(
5186
5307
  sealedResultContractFact,
5187
5308
  validationGrounding === null ? null : { ...validationGrounding, ungrounded: [...validationGrounding.ungrounded] },
5188
5309
  );
5310
+ if (hostVerification !== undefined) receiptDraft.hostVerification = hostVerification;
5311
+ if (worktreeReceipt !== undefined) receiptDraft.worktree = worktreeReceipt;
5189
5312
  const ledgerPatch: Partial<RunEnvelope> = {
5190
5313
  status,
5191
5314
  outcome: finalOutcome,
@@ -5215,7 +5338,33 @@ export function createDispatchBundle(
5215
5338
  }
5216
5339
  ledgerRef.update(envelope.id, ledgerPatch);
5217
5340
  const receipt = ledgerRef.recordReceipt(envelope.id, sealRouteDecision(receiptDraft, routeObservation.decision));
5341
+ // Two independent ways a run's write record can fail to be a closed
5342
+ // list, and either one files it as `null` rather than as an empty
5343
+ // one. The difference decides whether the write boundary may clear a
5344
+ // path it did not see this run touch.
5345
+ //
5346
+ // The telemetry axis is whether the events were seen at all: a
5347
+ // subprocess runtime publishes no tool events, and a partial trail
5348
+ // has its holes exactly where an unobserved write would sit.
5349
+ //
5350
+ // The opacity axis is whether the events that were seen say enough.
5351
+ // A run that called bash, verify, dispatch, or steer to a clean exit
5352
+ // could have written through a channel no argument names, so nothing
5353
+ // in the checkout can be cleared on the strength of its absence from
5354
+ // the set. That run's window keeps today's blame-the-whole-diff
5355
+ // behaviour; a run that never touched such a tool keeps the
5356
+ // protection.
5357
+ const writeRecordUsable =
5358
+ receipt.safety?.toolTelemetry?.coverage === "complete" && observedRunEffects.writeRecordComplete;
5359
+ recordRunWriteTargets(envelope.id, writeRecordUsable ? [...observedRunEffects.mutatedPaths] : null);
5218
5360
  await ledgerRef.persist();
5361
+ if (worktreeReceipt?.applied === true && req.taskWorktree !== undefined) {
5362
+ try {
5363
+ cleanupTaskWorktree(req.taskWorktree, true);
5364
+ } catch (cleanupError) {
5365
+ reportDispatchDiagnostic(`clean applied task worktree ${req.taskWorktree.runId}`, cleanupError);
5366
+ }
5367
+ }
5219
5368
  active.delete(envelope.id);
5220
5369
  recordTargetOutcome(
5221
5370
  lifecycle.target.target.id,
@@ -5303,10 +5452,58 @@ export function createDispatchBundle(
5303
5452
  events: AsyncIterableIterator<unknown>;
5304
5453
  finalPromise: Promise<RunReceipt>;
5305
5454
  }> {
5455
+ let prepared = req;
5456
+ let createdWorktree: DispatchRequest["taskWorktree"];
5457
+ let writerLease: CheckoutWriterLease | undefined;
5458
+ const writerRecipe = agents.get(req.agentId);
5459
+ if (writerRecipe !== null && normalizeAgentSpec(writerRecipe).capabilityClass === "workspace-edit") {
5460
+ const checkout = req.taskWorktree?.root ?? gitCheckoutRoot(req.cwd ?? process.cwd());
5461
+ if (checkout !== null) writerLease = acquireCheckoutWriterLease({ checkout });
5462
+ }
5463
+ if (req.worktree === true && req.taskWorktree === undefined) {
5464
+ const requestedCwd = resolvePath(req.cwd ?? process.cwd());
5465
+ const root = gitCheckoutRoot(requestedCwd);
5466
+ if (root === null) throw new Error("worktree_non_git_checkout");
5467
+ if (req.taskWorktreeDestination !== undefined && root !== req.taskWorktreeDestination) {
5468
+ writerLease?.release();
5469
+ throw new Error("dispatch: worktree merge destination differs from the approved execution snapshot");
5470
+ }
5471
+ const runId = newRunId();
5472
+ let taskWorktree: NonNullable<DispatchRequest["taskWorktree"]>;
5473
+ try {
5474
+ taskWorktree = createTaskWorktree(root, runId);
5475
+ } catch (error) {
5476
+ writerLease?.release();
5477
+ throw error;
5478
+ }
5479
+ createdWorktree = taskWorktree;
5480
+ const cwdRelative = relative(root, requestedCwd);
5481
+ const workerCwd = resolvePath(taskWorktree.path, cwdRelative);
5482
+ const writeRoots = req.writeRoots?.map((entry) => {
5483
+ const rel = relative(root, entry);
5484
+ return rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel) ? entry : resolvePath(taskWorktree.path, rel);
5485
+ });
5486
+ prepared = {
5487
+ ...req,
5488
+ runIdHint: runId,
5489
+ taskWorktree,
5490
+ cwd: workerCwd,
5491
+ protectedArtifactRemap: { sourceRoot: root, workerRoot: taskWorktree.path },
5492
+ ...(writeRoots === undefined ? {} : { writeRoots }),
5493
+ };
5494
+ }
5306
5495
  let handle: Awaited<ReturnType<typeof dispatchAttempt>>;
5307
5496
  try {
5308
- handle = await dispatchAttempt(req, observer);
5497
+ handle = await dispatchAttempt(prepared, observer);
5309
5498
  } catch (error) {
5499
+ writerLease?.release();
5500
+ if (createdWorktree !== undefined) {
5501
+ try {
5502
+ cleanupTaskWorktree(createdWorktree, true);
5503
+ } catch (cleanupError) {
5504
+ reportDispatchDiagnostic(`clean task worktree ${createdWorktree.runId} after admission failure`, cleanupError);
5505
+ }
5506
+ }
5310
5507
  if (req.reservation !== undefined && req.lineage === undefined) {
5311
5508
  rollbackDispatchReservation(req.reservation.ownerId, now());
5312
5509
  }
@@ -5314,7 +5511,7 @@ export function createDispatchBundle(
5314
5511
  }
5315
5512
  // Requests carrying lineage are internal attempts (retry or nested
5316
5513
  // orchestration) and retain the per-attempt handle contract.
5317
- if (req.lineage) return handle;
5514
+ if (req.lineage) return { ...handle, finalPromise: handle.finalPromise.finally(() => writerLease?.release()) };
5318
5515
 
5319
5516
  const assignment = assignments.open(handle.runId, assignmentPolicyFor(handle.effectiveRequest));
5320
5517
  persistAssignment(registerAssignment(assignment.id), `${assignment.id}:open`);
@@ -5327,9 +5524,10 @@ export function createDispatchBundle(
5327
5524
  const reservation = req.reservation;
5328
5525
  const terminal =
5329
5526
  reservation === undefined
5330
- ? assignment.terminal
5527
+ ? assignment.terminal.finally(() => writerLease?.release())
5331
5528
  : assignment.terminal.finally(() => {
5332
5529
  releaseDispatchReservationMember(reservation.ownerId, reservation.memberId, now());
5530
+ writerLease?.release();
5333
5531
  });
5334
5532
  return { runId: handle.runId, events: assignment.events, finalPromise: terminal };
5335
5533
  }
@@ -5393,10 +5591,11 @@ export function createDispatchBundle(
5393
5591
  `dispatch: agent '${req.agentId}' is a ${agentSpec.audience} agent reserved for Clio internal orchestration`,
5394
5592
  );
5395
5593
  }
5396
- if (hasPersonaOverride(req) && (agentSpec.audience === "shadow" || agentSpec.audience === "internal")) {
5594
+ if (hasCallerPersonaOverride(req) && (agentSpec.audience === "shadow" || agentSpec.audience === "internal")) {
5397
5595
  throw new Error(`dispatch: persona overrides are not allowed for ${agentSpec.audience} agent '${req.agentId}'`);
5398
5596
  }
5399
- const admission = resolveDispatchAdmissionStage(req, recipe, safety);
5597
+ const pathScope = resolveDispatchPathScope(req);
5598
+ const admission = resolveDispatchAdmissionStage(req, recipe, safety, pathScope);
5400
5599
  const targets = readWorkerTargets(settings);
5401
5600
  const target = resolveDispatchTarget(
5402
5601
  req,
@@ -5409,14 +5608,14 @@ export function createDispatchBundle(
5409
5608
  );
5410
5609
  enforceCapabilityGate(target.target.id, target.modelCapabilities, req.requiredCapabilities);
5411
5610
  const effectiveTools = withLedgerToolNarrowing(
5412
- effectiveToolNames(admission.allowedTools, target, writeConfinedRequest(req), deniedToolNames(req)),
5611
+ effectiveToolNames(admission.allowedTools, target, pathScope.writeBoundaries.length > 0, deniedToolNames(req)),
5413
5612
  req,
5414
5613
  );
5415
5614
  assertPostRuntimeToolCompatibility(req.agentId, agentSpec, effectiveTools, target);
5416
5615
  assertRuntimeCanHonorWorkerPermissionMode(target.runtime, settings?.workers.onPermission ?? "deny");
5417
5616
  assertWorkerBudgetEnforceable(target.runtime, agentSpec.budget !== null || req.budget !== undefined);
5418
5617
  assertResponseSchemaEnforceable(target.runtime, target.modelCapabilities, req.responseSchema, effectiveTools.length);
5419
- assertWriteRootsEnforceable(target.runtime, req.writeRoots);
5618
+ assertWriteRootsEnforceable(target.runtime, pathScope.writeBoundaries);
5420
5619
  assertProtectedArtifactsEnforceable(
5421
5620
  target.runtime.id,
5422
5621
  target.runtime.kind !== "subprocess",
@@ -5713,6 +5912,137 @@ export function createDispatchBundle(
5713
5912
  };
5714
5913
  }
5715
5914
 
5915
+ async function sealCouncilSynthesis(input: {
5916
+ group: string;
5917
+ round: number;
5918
+ kind: "none" | "vote" | "judge";
5919
+ text: string;
5920
+ subjects: ReadonlyArray<{ runId: string; digest: string | null }>;
5921
+ template: RunReceipt;
5922
+ }): Promise<RunReceipt> {
5923
+ const l = requireLedger();
5924
+ const runId = newRunId();
5925
+ const task = `Council ${input.kind} synthesis`;
5926
+ // The synthesis is the council's own verdict, filed under the origin the
5927
+ // council was asked from: an internal origin would keep it out of the
5928
+ // transcript worker fold, and with it out of /share.
5929
+ const origin = input.template.requestOrigin ?? "agent";
5930
+ const gate = { role: "synthesis" as const, group: input.group, cycle: input.round, subjects: [...input.subjects] };
5931
+ const council = { group: input.group, label: "synthesis", round: input.round };
5932
+ const created = l.create({
5933
+ id: runId,
5934
+ agentId: "council-synthesis",
5935
+ executionRole: "judge",
5936
+ requestOrigin: origin,
5937
+ task,
5938
+ targetId: input.template.targetId,
5939
+ wireModelId: input.template.wireModelId,
5940
+ runtimeId: input.template.runtimeId,
5941
+ runtimeKind: input.template.runtimeKind,
5942
+ sessionId: input.template.sessionId,
5943
+ cwd: input.template.reproducibility?.cwd ?? process.cwd(),
5944
+ });
5945
+ const endedAt = new Date().toISOString();
5946
+ l.update(runId, {
5947
+ status: "completed",
5948
+ outcome: "succeeded",
5949
+ endedAt,
5950
+ exitCode: 0,
5951
+ gate,
5952
+ council,
5953
+ ...(input.template.plan !== undefined ? { plan: input.template.plan } : {}),
5954
+ });
5955
+ const receipt = l.recordReceipt(runId, {
5956
+ runId,
5957
+ agentId: "council-synthesis",
5958
+ executionRole: "judge",
5959
+ requestOrigin: origin,
5960
+ task,
5961
+ targetId: input.template.targetId,
5962
+ wireModelId: input.template.wireModelId,
5963
+ runtimeId: input.template.runtimeId,
5964
+ runtimeKind: input.template.runtimeKind,
5965
+ startedAt: created.startedAt,
5966
+ endedAt,
5967
+ outcome: "succeeded",
5968
+ outcomeDetail: null,
5969
+ exitCode: 0,
5970
+ tokenCount: 0,
5971
+ inputTokenCount: 0,
5972
+ outputTokenCount: 0,
5973
+ reasoningTokenCount: 0,
5974
+ cacheReadTokenCount: 0,
5975
+ cacheWriteTokenCount: 0,
5976
+ output: { state: "final", text: input.text, bytes: Buffer.byteLength(input.text, "utf8"), truncated: false },
5977
+ costUsd: 0,
5978
+ costProvenance: "unknown",
5979
+ compiledPromptHash: null,
5980
+ staticCompositionHash: null,
5981
+ clioVersion: readClioVersion(),
5982
+ piMonoVersion: readPiMonoVersion(),
5983
+ platform: process.platform,
5984
+ nodeVersion: process.version,
5985
+ toolCalls: 0,
5986
+ toolStats: [],
5987
+ verification: { state: "unverified", basis: "no-validation-tool" },
5988
+ routingIntent: defaultRoutingIntent({}),
5989
+ quality: createRunReceiptQuality({ runtimeEnforceable: false, enforcementPassed: null, resultContract: null }),
5990
+ reproducibility: collectReproducibility(input.template.reproducibility?.cwd ?? process.cwd(), null),
5991
+ sessionId: input.template.sessionId,
5992
+ gate,
5993
+ council,
5994
+ ...(input.template.plan !== undefined ? { plan: input.template.plan } : {}),
5995
+ });
5996
+ await l.persist();
5997
+ // The synthesis never ran as a worker, so nothing else publishes its
5998
+ // lifecycle. The Fleet Runs board and /share are built from these
5999
+ // events, and a council whose synthesis they cannot see is a council
6000
+ // whose verdict the operator cannot read or share.
6001
+ const identity: DispatchRunIdentity = {
6002
+ runId,
6003
+ agentId: "council-synthesis",
6004
+ task,
6005
+ requestOrigin: origin,
6006
+ targetId: receipt.targetId,
6007
+ wireModelId: receipt.wireModelId,
6008
+ runtimeId: receipt.runtimeId,
6009
+ runtimeKind: receipt.runtimeKind,
6010
+ gate: { role: gate.role, cycle: gate.cycle },
6011
+ council,
6012
+ };
6013
+ context.bus.emit(BusChannels.DispatchEnqueued, { ...identity, requestOrigin: origin });
6014
+ context.bus.emit(BusChannels.DispatchStarted, {
6015
+ ...identity,
6016
+ requestOrigin: origin,
6017
+ pid: null,
6018
+ assignmentId: runId,
6019
+ attempt: 0,
6020
+ });
6021
+ context.bus.emit(BusChannels.DispatchCompleted, {
6022
+ ...identity,
6023
+ requestOrigin: origin,
6024
+ outcome: "succeeded",
6025
+ outcomeCode: null,
6026
+ outcomeDetail: null,
6027
+ lineage: input.template.lineage ?? { parentRunId: null, rootRunId: runId, attempt: 0, depth: 0 },
6028
+ tokenCount: 0,
6029
+ inputTokenCount: 0,
6030
+ outputTokenCount: 0,
6031
+ cacheReadTokenCount: 0,
6032
+ cacheWriteTokenCount: 0,
6033
+ reasoningTokenCount: 0,
6034
+ staticShellHash: null,
6035
+ sessionShellHash: null,
6036
+ dynamicHash: null,
6037
+ costUsd: 0,
6038
+ costProvenance: "unknown",
6039
+ durationMs: 0,
6040
+ exitCode: 0,
6041
+ toolActivity: null,
6042
+ });
6043
+ return receipt;
6044
+ }
6045
+
5716
6046
  const extension: DomainExtension = {
5717
6047
  async start() {
5718
6048
  // No in-memory executor survives a process restart, so every active
@@ -5743,6 +6073,7 @@ export function createDispatchBundle(
5743
6073
  const reconciledLedger = ledger;
5744
6074
  const reconciled = await reconcileOrphanAssignments({
5745
6075
  listRunning: () => listStoredAssignments().filter((record) => record.status === "running"),
6076
+ ownerAlive: assignmentProcessOwnerAlive,
5746
6077
  lookupAttempt: (runId) => {
5747
6078
  const envelope = reconciledLedger?.get(runId) ?? null;
5748
6079
  if (!envelope) return null;
@@ -5752,7 +6083,8 @@ export function createDispatchBundle(
5752
6083
  succeeded: envelope.outcome === "succeeded",
5753
6084
  };
5754
6085
  },
5755
- settle: (assignmentId, terminalRunId, status) => settleStoredAssignment(assignmentId, terminalRunId, status),
6086
+ settle: (assignmentId, terminalRunId, status, owner) =>
6087
+ settleStoredAssignment(assignmentId, terminalRunId, status, owner),
5756
6088
  });
5757
6089
  if ((reconciled.recovered > 0 || reconciled.abandoned > 0) && process.env.CLIO_CODER_INTERACTIVE !== "1") {
5758
6090
  process.stderr.write(
@@ -5947,6 +6279,7 @@ export function createDispatchBundle(
5947
6279
  protectedArtifactState: () => getProtectedArtifactState(),
5948
6280
  dispatch,
5949
6281
  dispatchBatch,
6282
+ sealCouncilSynthesis,
5950
6283
  listRuns(status) {
5951
6284
  const l = requireLedger();
5952
6285
  return status ? l.list({ status }) : l.list();
@@ -5955,6 +6288,11 @@ export function createDispatchBundle(
5955
6288
  if (!ledger) return null;
5956
6289
  return ledger.get(runId);
5957
6290
  },
6291
+ observedRunWrites(runId) {
6292
+ // A run this process never finalized, or finalized long enough ago to
6293
+ // have been evicted, is unknown rather than silent, so both answer null.
6294
+ return runWriteTargets.get(runId) ?? null;
6295
+ },
5958
6296
  assignments: {
5959
6297
  get: (id) => assignments.get(id),
5960
6298
  getStored: (id) => getStoredAssignment(id),