@iowarp/clio-coder 0.3.7 → 0.3.9

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 (400) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +13 -4
  3. package/dist/{acp-SK4MD6MM.js → acp-7LOELQFP.js} +13 -13
  4. package/dist/{agents-2FN2K6ME.js → agents-FIBG2SHA.js} +41 -37
  5. package/dist/assets/codewiki.json +1 -1
  6. package/dist/{auth-QIYZWM5I.js → auth-OI4LIH2I.js} +31 -24
  7. package/dist/builtins-AD25UL3C.js +17 -0
  8. package/dist/{chunk-5FR74PWO.js → chunk-2JDWVJND.js} +2 -2
  9. package/dist/chunk-3DPEIQKN.js +113 -0
  10. package/dist/{chunk-EOOQZZDE.js → chunk-3DUR4WUA.js} +19 -19
  11. package/dist/{chunk-WHJYKASB.js → chunk-3MRC2YSQ.js} +2 -2
  12. package/dist/{chunk-EBEFWSGL.js → chunk-3UUY7R3Z.js} +14 -10
  13. package/dist/{chunk-LADCF22A.js → chunk-3V5AYSEQ.js} +113 -54
  14. package/dist/{chunk-BMWK7ZIZ.js → chunk-465CC7FK.js} +16 -13
  15. package/dist/{chunk-5WIGXA4T.js → chunk-47CMYGET.js} +111 -4
  16. package/dist/{chunk-CEYBNUGC.js → chunk-4H6ULJ3H.js} +378 -36
  17. package/dist/{chunk-YTYFXUI3.js → chunk-4LJX2PUC.js} +9 -9
  18. package/dist/{chunk-DOOEX22V.js → chunk-56KB5IJP.js} +5 -5
  19. package/dist/{chunk-SPULKLCF.js → chunk-5DQRIYDZ.js} +2 -2
  20. package/dist/{chunk-TSHXZTOQ.js → chunk-5HFBWUMU.js} +23 -11
  21. package/dist/{chunk-5UJ6ECTS.js → chunk-5PVQ4SRS.js} +80 -8
  22. package/dist/{chunk-ZWLZP4ZT.js → chunk-5QKCQQ3E.js} +359 -17
  23. package/dist/{chunk-6M7VS3J3.js → chunk-5T7RBWN2.js} +111 -5
  24. package/dist/chunk-774ILSRL.js +172 -0
  25. package/dist/chunk-7C6RYZGQ.js +391 -0
  26. package/dist/{chunk-GH5622CP.js → chunk-A2NJGIB3.js} +2 -2
  27. package/dist/{chunk-C4JBQ5SR.js → chunk-AD7Y7STJ.js} +6 -6
  28. package/dist/{chunk-GEYXPTRF.js → chunk-AEYBF3TB.js} +33 -12
  29. package/dist/{chunk-2SFS6XQE.js → chunk-AMKHQW3C.js} +3 -2
  30. package/dist/{chunk-D4MDIG46.js → chunk-B5CSFE7B.js} +7 -7
  31. package/dist/{chunk-MXI6J5JF.js → chunk-B5XRQOLB.js} +10 -10
  32. package/dist/{chunk-X2KV5FXT.js → chunk-BVDVID7E.js} +2 -2
  33. package/dist/{chunk-JNXPYBB4.js → chunk-CA42X6KT.js} +3 -3
  34. package/dist/{chunk-VREKEFLL.js → chunk-D73KXYPF.js} +3 -3
  35. package/dist/{chunk-JTSEDYVQ.js → chunk-DG4M6ZUE.js} +7 -7
  36. package/dist/{chunk-DQA7QLMD.js → chunk-EBOC7MT3.js} +10 -25
  37. package/dist/{chunk-KZ2H5X4G.js → chunk-ECUO3KDP.js} +129 -14
  38. package/dist/{chunk-JRIO5UD2.js → chunk-EQ63NRB7.js} +5 -5
  39. package/dist/{chunk-YD734TPH.js → chunk-FALJGAWU.js} +2 -2
  40. package/dist/{chunk-GWS3VEIW.js → chunk-FWDFM5ZU.js} +24 -3
  41. package/dist/{chunk-XEGB6BCN.js → chunk-GAYUJ7LE.js} +68 -14
  42. package/dist/{chunk-UND3GU2L.js → chunk-H7IXIC72.js} +2 -2
  43. package/dist/{chunk-IR4CFBFN.js → chunk-HAY4ZE2P.js} +12 -12
  44. package/dist/{chunk-UVDSQ6LW.js → chunk-HCBCAYZU.js} +74 -147
  45. package/dist/{chunk-4DWFMQDR.js → chunk-HJB5IUKP.js} +89 -145
  46. package/dist/{chunk-M4AKACEO.js → chunk-HKO36JWF.js} +33 -5
  47. package/dist/{chunk-KCMKRQX4.js → chunk-HPCTNZM2.js} +45 -82
  48. package/dist/{chunk-465YSENW.js → chunk-IFBNV6H6.js} +3 -3
  49. package/dist/{chunk-FJ3H4MN5.js → chunk-IHKBWSXF.js} +2 -2
  50. package/dist/chunk-JEQQR47K.js +3025 -0
  51. package/dist/{chunk-FO5ZOVUY.js → chunk-KV2AOLDF.js} +27 -7
  52. package/dist/chunk-LU7P4LHA.js +33 -0
  53. package/dist/{chunk-6TUKSZVF.js → chunk-LXPJXFM5.js} +11 -11
  54. package/dist/{chunk-VQNODYQ4.js → chunk-MIX5N5AC.js} +488 -3668
  55. package/dist/chunk-MLOK6ZOS.js +2888 -0
  56. package/dist/{chunk-ZZMN5OM4.js → chunk-MV2VUEJC.js} +2 -2
  57. package/dist/{chunk-MVVUPGPW.js → chunk-MXHC5QYU.js} +6 -6
  58. package/dist/{chunk-OBMAI2DP.js → chunk-N3PBVRTZ.js} +12 -388
  59. package/dist/{chunk-WJHBC77E.js → chunk-N5XKWMDW.js} +17 -7
  60. package/dist/{chunk-5C3AQNDW.js → chunk-NNNWO6F2.js} +124 -36
  61. package/dist/{chunk-UFQ3F4FW.js → chunk-NQ6UCCOD.js} +4 -4
  62. package/dist/chunk-NUGM5KR6.js +165 -0
  63. package/dist/{chunk-DMD2AGVS.js → chunk-NZU6YDNV.js} +20 -18
  64. package/dist/{chunk-WHGPSPT5.js → chunk-O6I4CIEU.js} +151 -13
  65. package/dist/{chunk-XN3L4EYL.js → chunk-OEDBCISO.js} +2 -2
  66. package/dist/{chunk-PD3MESLB.js → chunk-P3JGPQFL.js} +4 -4
  67. package/dist/{chunk-UHXRNZ2J.js → chunk-PNY46YEY.js} +23 -6
  68. package/dist/{chunk-THKY7CD7.js → chunk-PZ4I4JE2.js} +134 -29
  69. package/dist/{chunk-SROCI7ZU.js → chunk-QQ7EKM72.js} +5 -5
  70. package/dist/{chunk-QCTRSGHQ.js → chunk-R7LNVMCS.js} +91 -53
  71. package/dist/{chunk-GOXNB3AO.js → chunk-RAPCMZL4.js} +75 -4
  72. package/dist/chunk-RKKLTLYB.js +45 -0
  73. package/dist/{chunk-OB5HIGJY.js → chunk-RKRLDWD3.js} +4 -1
  74. package/dist/{chunk-DJNLUABN.js → chunk-S4COXYBG.js} +588 -32
  75. package/dist/{chunk-3HAPLH5M.js → chunk-T3Z6VAAF.js} +172 -11
  76. package/dist/{chunk-FOT2FX5J.js → chunk-TD7UE2L5.js} +12 -10
  77. package/dist/{chunk-UUANF5CR.js → chunk-TEO2TLVN.js} +856 -967
  78. package/dist/{chunk-FCSXB6T2.js → chunk-UOSL25KY.js} +14 -2
  79. package/dist/{chunk-EELBMBT6.js → chunk-VKBMFOYV.js} +74 -15
  80. package/dist/chunk-VO2LKSTM.js +165 -0
  81. package/dist/{chunk-5C77SEEY.js → chunk-VPTUJU4P.js} +3 -3
  82. package/dist/{chunk-WEH5XRJQ.js → chunk-WIE7ZOSW.js} +2 -2
  83. package/dist/{chunk-J7PIKKWC.js → chunk-WXCJ7VME.js} +8 -8
  84. package/dist/{chunk-4DGYLA73.js → chunk-XDOQXGFO.js} +22 -7
  85. package/dist/{chunk-PPAMZ32Z.js → chunk-XK56QHLX.js} +6 -1
  86. package/dist/{chunk-AB4XIIVB.js → chunk-YKOFT37S.js} +6 -6
  87. package/dist/chunk-YSEHGPCT.js +127 -0
  88. package/dist/{chunk-HFSBBKSQ.js → chunk-YW7UVM5V.js} +138 -3
  89. package/dist/cli/index.js +32 -32
  90. package/dist/{clio-WBVQEBKO.js → clio-LT5V7SSZ.js} +9 -9
  91. package/dist/{code-nav-FGGFIE7L.js → code-nav-LMW275PA.js} +5 -5
  92. package/dist/codewiki/build-worker.js +4 -4
  93. package/dist/{components-F7OEATSO.js → components-ZFA3SAER.js} +8 -8
  94. package/dist/{config-TRBL3RCF.js → config-RXS5T3JT.js} +98 -65
  95. package/dist/{configure-OLCVPHNM.js → configure-2WYWSCSD.js} +26 -22
  96. package/dist/{context-MJIJ6GOX.js → context-I3BTOTCS.js} +12 -12
  97. package/dist/{context-XEWE3MOJ.js → context-MVOORGMF.js} +54 -47
  98. package/dist/{context-WFPKQSM6.js → context-PALKKQYL.js} +28 -28
  99. package/dist/{context-clear-KNOS2JPB.js → context-clear-N2WOYZ2K.js} +53 -46
  100. package/dist/{context-index-SSR5ECNE.js → context-index-HNG3MOME.js} +6 -6
  101. package/dist/{context-working-set-EUXAZI6N.js → context-working-set-MIEVECVZ.js} +17 -18
  102. package/dist/{dispatch-runner-B7MTOVKL.js → dispatch-runner-VVA4SRRH.js} +90 -61
  103. package/dist/{docs-FLJTIDSE.js → docs-7LQ23DLM.js} +8 -8
  104. package/dist/doctor-TWBWFK5V.js +165 -0
  105. package/dist/eval-IJ5VEZDJ.js +4483 -0
  106. package/dist/{evidence-JZNBUOQZ.js → evidence-L5APPXNV.js} +68 -61
  107. package/dist/{evolve-FJVC4KKI.js → evolve-RGNKFJ52.js} +47 -40
  108. package/dist/{extensions-IQL36S7K.js → extensions-7WYWUX5A.js} +13 -7
  109. package/dist/{fleet-BDKYJFCP.js → fleet-6CNVBZZP.js} +113 -76
  110. package/dist/{fleet-commands-ZFIWZSB3.js → fleet-commands-L2SXSYEI.js} +10 -10
  111. package/dist/{fleet-graph-Y6HPXIVF.js → fleet-graph-2J3OOIPO.js} +17 -15
  112. package/dist/{fleet-preflight-BHSNPBMH.js → fleet-preflight-CZRJ4JP5.js} +5 -6
  113. package/dist/{fleet-validate-BIYREGIK.js → fleet-validate-C5RI6DP7.js} +20 -19
  114. package/dist/{init-LQUB5COQ.js → init-VBN2ACVA.js} +70 -63
  115. package/dist/{library-NJAHIGG4.js → library-JHGUMLY2.js} +22 -20
  116. package/dist/{memory-OG6HOYKM.js → memory-K4OQIYWG.js} +49 -42
  117. package/dist/{models-5ZG5XY7J.js → models-2NCZUWDD.js} +35 -29
  118. package/dist/{monitor-TJ7AMTGB.js → monitor-MMVTJABD.js} +64 -45
  119. package/dist/{orchestrator-WZYB54DM.js → orchestrator-ZKBPCHW6.js} +1971 -520
  120. package/dist/{paths-XUC7GS6E.js → paths-DBXMZMDU.js} +5 -5
  121. package/dist/registry-LG64LTF4.js +11 -0
  122. package/dist/{reset-PXQT45IY.js → reset-DD5JGOY3.js} +11 -11
  123. package/dist/{run-FQ74YF62.js → run-QEGNX7FL.js} +89 -83
  124. package/dist/{share-FW7SVCL3.js → share-JKD3BQMW.js} +20 -18
  125. package/dist/{skills-7E7IRB3R.js → skills-LMQIKDOZ.js} +23 -21
  126. package/dist/{skills-eval-LI75W6OK.js → skills-eval-I7X2774U.js} +59 -52
  127. package/dist/{steer-GGWFUJUD.js → steer-CF5TDANS.js} +3 -3
  128. package/dist/support-I7LOJLIF.js +38 -0
  129. package/dist/{targets-4CIFKCTW.js → targets-RUSR6B5Z.js} +77 -42
  130. package/dist/{terminal-lease-WUZY7ZV5.js → terminal-lease-QYVORFR4.js} +6 -4
  131. package/dist/{trace-PNCASAXC.js → trace-ODOQIVIW.js} +61 -6
  132. package/dist/{uninstall-7FV7IP4E.js → uninstall-ZJF5H5ZN.js} +8 -8
  133. package/dist/{upgrade-K2HVIVMQ.js → upgrade-XANW3FXB.js} +29 -26
  134. package/dist/{usage-GTZELZQX.js → usage-4H7ZRXQT.js} +110 -61
  135. package/dist/{verifiers-RLAHT27O.js → verifiers-UZXNBZEB.js} +13 -13
  136. package/dist/{verify-BX3BRKH5.js → verify-BVKWTNDL.js} +9 -9
  137. package/dist/{wiki-generate-ASIFASCN.js → wiki-generate-MY7WV2QI.js} +76 -69
  138. package/dist/worker/entry.js +69 -66
  139. package/docs/alcf-provider.md +1 -1
  140. package/docs/architecture.md +1 -1
  141. package/docs/artifact-versions.md +11 -5
  142. package/docs/built-in-agents.md +1 -1
  143. package/docs/capacity-and-scheduling.md +23 -2
  144. package/docs/commands-and-modes.md +2 -2
  145. package/docs/configuration-and-targets.md +37 -5
  146. package/docs/context-engine.md +63 -4
  147. package/docs/documentation-coverage.md +3 -3
  148. package/docs/documentation-guide.md +1 -1
  149. package/docs/environment-variables.md +2 -0
  150. package/docs/eval-runner.md +262 -11
  151. package/docs/evals-internal.md +72 -2
  152. package/docs/evidence-and-memory.md +77 -12
  153. package/docs/evolution.md +1 -1
  154. package/docs/extensions-and-sharing.md +3 -1
  155. package/docs/fleet-dispatch.md +34 -9
  156. package/docs/glossary.md +21 -1
  157. package/docs/installation-and-lifecycle.md +1 -1
  158. package/docs/middleware-and-components.md +1 -1
  159. package/docs/model-catalog.md +1 -1
  160. package/docs/observability.md +54 -3
  161. package/docs/proactive-memory.md +127 -14
  162. package/docs/prompt-envelope-and-tools.md +22 -2
  163. package/docs/provider-adapter-cookbook.md +1 -1
  164. package/docs/release-cut-checklist.md +60 -41
  165. package/docs/safety-model.md +1 -1
  166. package/docs/scientific-validation.md +1 -1
  167. package/docs/skills-marketplace.md +1 -1
  168. package/docs/tool-usage.md +1 -1
  169. package/docs/trace-store.md +1 -1
  170. package/docs/troubleshooting.md +87 -0
  171. package/docs/tui-design.md +1 -1
  172. package/docs/worker-dispatch-mechanics.md +1 -1
  173. package/package.json +2 -2
  174. package/src/cli/agents.ts +1 -1
  175. package/src/cli/argv.ts +5 -0
  176. package/src/cli/config-inspect.ts +33 -6
  177. package/src/cli/config.ts +1 -1
  178. package/src/cli/configure.ts +107 -23
  179. package/src/cli/doctor-state-size.ts +82 -0
  180. package/src/cli/doctor.ts +7 -1
  181. package/src/cli/eval.ts +80 -16
  182. package/src/cli/evidence.ts +30 -25
  183. package/src/cli/extensions.ts +5 -1
  184. package/src/cli/fleet-preflight.ts +2 -12
  185. package/src/cli/fleet.ts +32 -3
  186. package/src/cli/shared.ts +1 -0
  187. package/src/cli/targets.ts +45 -11
  188. package/src/cli/trace.ts +63 -4
  189. package/src/cli/usage.ts +63 -14
  190. package/src/cli/validate-model.ts +60 -5
  191. package/src/core/bus-events.ts +54 -1
  192. package/src/core/cache-telemetry.ts +42 -0
  193. package/src/core/commit-attribution.ts +4 -4
  194. package/src/core/config.ts +18 -0
  195. package/src/core/defaults.ts +36 -6
  196. package/src/core/endpoint-key.ts +27 -0
  197. package/src/core/path-boundary.ts +100 -0
  198. package/src/core/residency-target-key.ts +25 -0
  199. package/src/core/response-schema.ts +36 -2
  200. package/src/domains/agents/extension.ts +2 -11
  201. package/src/domains/agents/fleet-contract.ts +30 -12
  202. package/src/domains/agents/recipe.ts +7 -1
  203. package/src/domains/agents/registry.ts +73 -5
  204. package/src/domains/agents/result-contract.ts +128 -17
  205. package/src/domains/agents/write-boundary.ts +15 -50
  206. package/src/domains/config/classify.ts +3 -0
  207. package/src/domains/context/codewiki/coordinator.ts +12 -4
  208. package/src/domains/context/project-rules.ts +51 -1
  209. package/src/domains/dispatch/admission.ts +40 -3
  210. package/src/domains/dispatch/assignment-reconcile.ts +22 -5
  211. package/src/domains/dispatch/assignment-store.ts +151 -14
  212. package/src/domains/dispatch/capacity-lease.ts +98 -9
  213. package/src/domains/dispatch/contract.ts +26 -1
  214. package/src/domains/dispatch/delegation-plan.ts +2 -5
  215. package/src/domains/dispatch/execution-plan.ts +44 -4
  216. package/src/domains/dispatch/execution-role.ts +9 -1
  217. package/src/domains/dispatch/extension.ts +309 -96
  218. package/src/domains/dispatch/fleet-run.ts +78 -4
  219. package/src/domains/dispatch/gate-role-prompts.ts +38 -0
  220. package/src/domains/dispatch/heartbeat.ts +32 -8
  221. package/src/domains/dispatch/index.ts +6 -1
  222. package/src/domains/dispatch/intent-requirements.ts +40 -0
  223. package/src/domains/dispatch/intent.ts +84 -8
  224. package/src/domains/dispatch/orphan-recovery.ts +5 -0
  225. package/src/domains/dispatch/path-scope.ts +370 -0
  226. package/src/domains/dispatch/receipt-integrity.ts +2 -1
  227. package/src/domains/dispatch/reservation-store.ts +116 -8
  228. package/src/domains/dispatch/state.ts +4 -0
  229. package/src/domains/dispatch/types.ts +14 -7
  230. package/src/domains/dispatch/validation.ts +6 -3
  231. package/src/domains/dispatch/worker-spawn.ts +25 -11
  232. package/src/domains/dispatch/write-boundary-enforcer.ts +62 -0
  233. package/src/domains/dispatch/write-boundary.ts +262 -22
  234. package/src/domains/eval/artifacts/store.ts +62 -0
  235. package/src/domains/eval/compare/behavioral.ts +224 -0
  236. package/src/domains/eval/compare/compare.ts +355 -2
  237. package/src/domains/eval/compare/envelope.ts +128 -0
  238. package/src/domains/eval/compare/gates.ts +24 -6
  239. package/src/domains/eval/compare/thresholds.ts +30 -3
  240. package/src/domains/eval/execution-provenance.ts +240 -0
  241. package/src/domains/eval/metrics/aggregate.ts +136 -0
  242. package/src/domains/eval/metrics/call-ledger-stream.ts +112 -0
  243. package/src/domains/eval/metrics/evidence.ts +79 -2
  244. package/src/domains/eval/metrics/tracked.ts +413 -0
  245. package/src/domains/eval/provenance.ts +117 -0
  246. package/src/domains/eval/reports/comparison.ts +128 -0
  247. package/src/domains/eval/reports/junit.ts +17 -3
  248. package/src/domains/eval/reports/markdown.ts +3 -3
  249. package/src/domains/eval/reports/text.ts +14 -0
  250. package/src/domains/eval/run-compare.ts +20 -0
  251. package/src/domains/eval/runners/clio-run.ts +139 -2
  252. package/src/domains/eval/runners/external-command.ts +28 -3
  253. package/src/domains/eval/schema/adapter.ts +111 -0
  254. package/src/domains/eval/schema/artifact.ts +20 -0
  255. package/src/domains/eval/schema/behavioral-metrics.ts +204 -0
  256. package/src/domains/eval/schema/behavioral.ts +520 -0
  257. package/src/domains/eval/schema/execution-envelope.ts +194 -0
  258. package/src/domains/eval/schema/serving.ts +74 -0
  259. package/src/domains/eval/schema/suite.ts +38 -8
  260. package/src/domains/eval/schema/validate.ts +58 -3
  261. package/src/domains/eval/schema/verdict.ts +237 -0
  262. package/src/domains/eval/suites/resolve.ts +2 -0
  263. package/src/domains/eval/suites/run.ts +264 -33
  264. package/src/domains/eval/verifiers/command.ts +2 -1
  265. package/src/domains/eval/workspaces/temp-copy.ts +145 -13
  266. package/src/domains/evidence/build.ts +68 -21
  267. package/src/domains/evidence/eval.ts +2 -12
  268. package/src/domains/evidence/findings-markdown.ts +33 -0
  269. package/src/domains/evidence/index.ts +21 -0
  270. package/src/domains/evidence/provenance.ts +46 -11
  271. package/src/domains/evidence/run-trust.ts +7 -113
  272. package/src/domains/evidence/trust-projection.ts +274 -0
  273. package/src/domains/evidence/trust-status.ts +145 -17
  274. package/src/domains/evidence/types.ts +4 -0
  275. package/src/domains/extensions/compatibility.ts +285 -0
  276. package/src/domains/extensions/discovery.ts +126 -4
  277. package/src/domains/extensions/resources.ts +21 -9
  278. package/src/domains/extensions/state.ts +18 -5
  279. package/src/domains/extensions/types.ts +6 -1
  280. package/src/domains/lifecycle/doctor.ts +209 -2
  281. package/src/domains/memory/index.ts +14 -0
  282. package/src/domains/memory/task-bank-promotion.ts +64 -0
  283. package/src/domains/memory/task-memory-policy.ts +77 -8
  284. package/src/domains/memory/task-memory-spend.ts +131 -0
  285. package/src/domains/memory/task-memory-status.ts +7 -0
  286. package/src/domains/memory/task-memory-telemetry.ts +2 -0
  287. package/src/domains/middleware/index.ts +1 -0
  288. package/src/domains/middleware/memory-intervention.ts +69 -5
  289. package/src/domains/middleware/memory-step-endpoint.ts +71 -0
  290. package/src/domains/observability/background-memory-usage.ts +140 -0
  291. package/src/domains/observability/cost.ts +1 -1
  292. package/src/domains/observability/index.ts +7 -0
  293. package/src/domains/observability/out-of-turn-usage.ts +51 -2
  294. package/src/domains/observability/trace-store.ts +192 -2
  295. package/src/domains/prompts/compiler.ts +100 -13
  296. package/src/domains/prompts/contract.ts +3 -5
  297. package/src/domains/providers/endpoint-capacity.ts +96 -0
  298. package/src/domains/providers/extension.ts +30 -2
  299. package/src/domains/providers/index.ts +10 -0
  300. package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +243 -1
  301. package/src/domains/providers/runtime-resolution.ts +8 -1
  302. package/src/domains/providers/runtimes/common/probe-helpers.ts +31 -9
  303. package/src/domains/providers/runtimes/local-native/llamacpp-anthropic.ts +1 -1
  304. package/src/domains/providers/runtimes/local-native/llamacpp-completion.ts +1 -1
  305. package/src/domains/providers/runtimes/local-native/llamacpp-embed.ts +1 -1
  306. package/src/domains/providers/runtimes/local-native/llamacpp-rerank.ts +1 -1
  307. package/src/domains/providers/runtimes/local-native/llamacpp.ts +4 -1
  308. package/src/domains/providers/runtimes/local-native/lmstudio.ts +4 -1
  309. package/src/domains/providers/runtimes/local-native/ollama-native.ts +6 -1
  310. package/src/domains/providers/types/capability-flags.ts +2 -0
  311. package/src/domains/providers/types/target-descriptor.ts +2 -0
  312. package/src/domains/resources/common-loader.ts +3 -0
  313. package/src/domains/resources/prompts/loader.ts +184 -17
  314. package/src/domains/safety/call-target.ts +52 -0
  315. package/src/domains/safety/policy-engine.ts +5 -5
  316. package/src/domains/safety/run-effects.ts +96 -2
  317. package/src/domains/safety/scope.ts +7 -12
  318. package/src/domains/session/context-accounting.ts +52 -1
  319. package/src/domains/session/context-ledger.ts +37 -13
  320. package/src/domains/session/index.ts +6 -0
  321. package/src/domains/session/prompt-cache.ts +140 -0
  322. package/src/domains/session/prompt-manifest.ts +42 -0
  323. package/src/engine/acp/adapter.ts +18 -3
  324. package/src/engine/acp/server.ts +4 -1
  325. package/src/engine/ai.ts +35 -0
  326. package/src/engine/apis/llamacpp-residency.ts +55 -3
  327. package/src/engine/apis/lmstudio.ts +25 -5
  328. package/src/engine/apis/ollama-native.ts +2 -1
  329. package/src/engine/apis/openai-completions.ts +80 -17
  330. package/src/engine/apis/residency-lock.ts +3 -1
  331. package/src/engine/apis/residency.ts +34 -1
  332. package/src/engine/prompt-templates.ts +18 -1
  333. package/src/engine/provider-payload.ts +29 -1
  334. package/src/engine/worker-runtime.ts +6 -3
  335. package/src/entry/orchestrator.ts +176 -30
  336. package/src/interactive/chat-loop-messages.ts +26 -7
  337. package/src/interactive/chat-loop.ts +318 -41
  338. package/src/interactive/chat-panel.ts +62 -8
  339. package/src/interactive/clio-editor.ts +45 -8
  340. package/src/interactive/context-activity.ts +5 -1
  341. package/src/interactive/context-meter.ts +1 -1
  342. package/src/interactive/context-overlay.ts +40 -10
  343. package/src/interactive/cost-overlay.ts +64 -6
  344. package/src/interactive/dispatch-board.ts +127 -5
  345. package/src/interactive/fleet-run-preview.ts +41 -15
  346. package/src/interactive/handoff-round.ts +41 -2
  347. package/src/interactive/interactive-application.ts +24 -1
  348. package/src/interactive/interactive-event-projection.ts +14 -0
  349. package/src/interactive/interactive-input-runtime.ts +8 -0
  350. package/src/interactive/interactive-presentation.ts +4 -0
  351. package/src/interactive/interactive-shell.ts +20 -17
  352. package/src/interactive/interactive-slash-runtime.ts +27 -4
  353. package/src/interactive/memory-overlay.ts +8 -0
  354. package/src/interactive/mutation-preview.ts +295 -0
  355. package/src/interactive/overlay-general-openers.ts +16 -0
  356. package/src/interactive/overlay-key-routing.ts +38 -0
  357. package/src/interactive/overlay-lifecycle.ts +38 -5
  358. package/src/interactive/overlay-permission-lifecycle.ts +22 -2
  359. package/src/interactive/overlay-session-lifecycle.ts +73 -9
  360. package/src/interactive/overlays/ask-user.ts +91 -19
  361. package/src/interactive/overlays/help-reference.ts +4 -0
  362. package/src/interactive/overlays/prompts.ts +11 -1
  363. package/src/interactive/overlays/settings.ts +176 -48
  364. package/src/interactive/permission-hint.ts +34 -2
  365. package/src/interactive/permission-overlay.ts +159 -9
  366. package/src/interactive/prewarm.ts +197 -0
  367. package/src/interactive/render-trace.ts +162 -15
  368. package/src/interactive/renderers/tool-execution.ts +4 -0
  369. package/src/interactive/side-question.ts +58 -1
  370. package/src/interactive/slash-commands.ts +7 -2
  371. package/src/interactive/status/controller.ts +11 -0
  372. package/src/interactive/status/state-machine.ts +54 -2
  373. package/src/interactive/status/types.ts +7 -0
  374. package/src/interactive/terminal-lease.ts +2 -0
  375. package/src/interactive/turn-context.ts +299 -31
  376. package/src/interactive/turn-persistence.ts +14 -4
  377. package/src/interactive/turn-prewarm.ts +364 -0
  378. package/src/interactive/turn-queues.ts +7 -4
  379. package/src/interactive/turn-runtime.ts +8 -1
  380. package/src/interactive/turn-state.ts +23 -0
  381. package/src/interactive/view/artifacts.ts +42 -9
  382. package/src/interactive/view/view-overlay.ts +43 -6
  383. package/src/interactive/worker-receipts.ts +14 -2
  384. package/src/interactive/worker-stream.ts +8 -0
  385. package/src/tools/ask-user.ts +43 -2
  386. package/src/tools/dispatch-admission.ts +12 -13
  387. package/src/tools/dispatch-arguments.ts +27 -0
  388. package/src/tools/dispatch-plan.ts +46 -9
  389. package/src/tools/dispatch-runner.ts +48 -13
  390. package/src/tools/dispatch-scout.ts +1 -1
  391. package/src/tools/monitor.ts +13 -0
  392. package/src/tools/registry.ts +16 -0
  393. package/src/tools/worker-evidence.ts +19 -13
  394. package/src/worker/spec-contract.ts +2 -1
  395. package/dist/chunk-AOCYTWAV.js +0 -449
  396. package/dist/chunk-HWUFFB6L.js +0 -83
  397. package/dist/chunk-R346GLFC.js +0 -31
  398. package/dist/chunk-ZGH7FGS5.js +0 -1079
  399. package/dist/doctor-RN4YKO2X.js +0 -87
  400. package/dist/eval-RUBJVSNQ.js +0 -2557
@@ -12,6 +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 { performance } from "node:perf_hooks";
15
16
  import { BusChannels, type DispatchCompletedPayload, type DispatchRunIdentity } from "../../core/bus-events.js";
16
17
  import { DEFAULT_SETTINGS, type DelegationToolGovernance } from "../../core/defaults.js";
17
18
  import type { DomainBundle, DomainContext, DomainExtension } from "../../core/domain-loader.js";
@@ -76,6 +77,9 @@ import { type EffectivePricing, resolveEffectivePricing } from "../providers/cat
76
77
  import {
77
78
  type CapabilityFlags,
78
79
  canonicalizeWireModelId,
80
+ type EndpointCapacity,
81
+ endpointCapacitiesForStatuses,
82
+ endpointCapacityForStatus,
79
83
  firstRuntimeResolutionError,
80
84
  isDispatchEligibleRuntime,
81
85
  type ProvidersContract,
@@ -123,6 +127,7 @@ import { AssignmentRegistry, applyActiveRouteSelection, asAssignmentId } from ".
123
127
  import type { AssignmentAttemptStartEvent } from "./assignment-events.js";
124
128
  import { reconcileOrphanAssignments } from "./assignment-reconcile.js";
125
129
  import {
130
+ assignmentProcessOwnerAlive,
126
131
  cancelStoredAssignment,
127
132
  failQueuedAssignment,
128
133
  getStoredAssignment,
@@ -165,8 +170,16 @@ import {
165
170
  } from "./failure-classification.js";
166
171
  import { routeFactVerdict } from "./fleet-preflight.js";
167
172
  import { competeStanceLiner, isBoundedGateRolePrompt } from "./gate-role-prompts.js";
168
- import { classifyHeartbeat, DEFAULT_HEARTBEAT_SPEC, type HeartbeatSpec, type HeartbeatStatus } from "./heartbeat.js";
173
+ import {
174
+ classifyHeartbeat,
175
+ DEFAULT_HEARTBEAT_SPEC,
176
+ type HeartbeatSpec,
177
+ type HeartbeatStamp,
178
+ type HeartbeatStatus,
179
+ heartbeatMonotonicAt,
180
+ } from "./heartbeat.js";
169
181
  import { hostVerificationRejection, runHostVerification } from "./host-verification.js";
182
+ import { renderDispatchIntentRequirements } from "./intent-requirements.js";
170
183
  import { adaptJointRouteInput } from "./joint-route-adapter.js";
171
184
  import {
172
185
  configuredJointNodes,
@@ -181,6 +194,12 @@ import {
181
194
  resultContractWasDue,
182
195
  runStatusForOutcome,
183
196
  } from "./outcome.js";
197
+ import {
198
+ type DispatchPathScope,
199
+ declaredScopeReplacementDiagnostic,
200
+ declaredScopeReplacementNotice,
201
+ resolveDispatchPathScope,
202
+ } from "./path-scope.js";
184
203
  import { deriveEnvelopePhaseDurations, deriveRunPhaseDurations, recordRunTimingBestEffort } from "./phase-timing.js";
185
204
  import { createFleetPlacementPreviewResolver, createFleetPlacementResolver } from "./placement.js";
186
205
  import {
@@ -197,6 +216,7 @@ import {
197
216
  planQueueSlot,
198
217
  type ReservationCapacitySnapshot,
199
218
  rebindDispatchReservationMember,
219
+ releaseDispatchReservation,
200
220
  releaseDispatchReservationMember,
201
221
  reservedBudgetUsd,
202
222
  reservedPlanPeakSlots,
@@ -278,6 +298,7 @@ import {
278
298
  type SpawnOptions as WorkerSpawnOptions,
279
299
  type WorkerSpec,
280
300
  } from "./worker-spawn.js";
301
+ import type { WriteBoundaryAttribution, WriteBoundaryAttributionDowngrade } from "./write-boundary.js";
281
302
 
282
303
  interface RunTokenMeter {
283
304
  inputTokens: number;
@@ -329,7 +350,7 @@ interface ActiveRun {
329
350
  /** ACP event-inactivity window; null for native runs (heartbeat spec governs those). */
330
351
  stallTimeoutMs: number | null;
331
352
  lineage: RunLineage;
332
- heartbeatAt: { current: number } | null;
353
+ heartbeatAt: HeartbeatStamp | null;
333
354
  heartbeatStatus: HeartbeatStatus;
334
355
  meter: RunTokenMeter;
335
356
  pricing: EffectivePricing["rates"];
@@ -365,6 +386,8 @@ export interface DispatchBundleOptions {
365
386
  heartbeatIntervalMs?: number;
366
387
  resilienceCooldownMs?: number;
367
388
  now?: () => number;
389
+ /** Monotonic clock for heartbeat age and ACP event-inactivity spans. */
390
+ monotonicNow?: () => number;
368
391
  /** Immutable per-attempt session settings view; falls back to the shared snapshot. */
369
392
  getSettings?: () => EffectiveSettings;
370
393
  /** Live hard-block state cloned into each mediated worker spec. */
@@ -614,6 +637,14 @@ function recordWorkerRunEffect(recorder: RunEffectsRecorder, event: Record<strin
614
637
  */
615
638
  const DISPATCH_DRAIN_GRACE_MS = 2000;
616
639
 
640
+ /**
641
+ * How many finalized runs keep their observed write targets in memory. The
642
+ * write boundary reads a run's record while closing the window it ran in, which
643
+ * is the same wave, so a few hundred runs of headroom is far more than the
644
+ * lookup ever reaches back for.
645
+ */
646
+ const RUN_WRITE_TARGET_HISTORY = 512;
647
+
617
648
  async function awaitEventDrain(drained: Promise<void>, graceMs = DISPATCH_DRAIN_GRACE_MS): Promise<boolean> {
618
649
  let timer: ReturnType<typeof setTimeout> | undefined;
619
650
  const grace = new Promise<false>((resolve) => {
@@ -751,9 +782,14 @@ function pickOrchestratorScope(safety: SafetyContract): ScopeSpec {
751
782
  return safety.scopes.workspace;
752
783
  }
753
784
 
754
- function pickWorkerScope(safety: SafetyContract, requestedActions: ReadonlyArray<ActionClass>): ScopeSpec {
785
+ function pickWorkerScope(
786
+ safety: SafetyContract,
787
+ requestedActions: ReadonlyArray<ActionClass>,
788
+ pathScope: DispatchPathScope,
789
+ ): ScopeSpec {
755
790
  if (requestedActions.every((action) => action === "read")) return safety.scopes.readonly;
756
- return safety.scopes.workspace;
791
+ if (pathScope.writeBoundaries.length === 0) return safety.scopes.workspace;
792
+ return { ...safety.scopes.workspace, allowedWriteRoots: pathScope.writeBoundaries };
757
793
  }
758
794
 
759
795
  function deriveRequestedActions(tools: ReadonlyArray<ToolName>, safety: SafetyContract): ReadonlyArray<ActionClass> {
@@ -993,6 +1029,25 @@ function hasPersonaOverride(req: DispatchRequest): boolean {
993
1029
  return typeof req.systemPrompt === "string" && req.systemPrompt.trim().length > 0;
994
1030
  }
995
1031
 
1032
+ /**
1033
+ * Whether this request's system prompt is a caller-authored persona rather
1034
+ * than one of the coordinator's own bounded gate-role prompts.
1035
+ *
1036
+ * A reviewer, a compete judge, and a council synthesis all carry a system
1037
+ * prompt the coordinator wrote, pinned by {@link isBoundedGateRolePrompt} to
1038
+ * one exact text under one gate role and read-only autonomy. That is the
1039
+ * topology speaking, not an operator reshaping a recipe, so it is not the
1040
+ * thing the shadow and internal audiences are protected from. The ACP
1041
+ * delegation path already draws the line here; without the same line, a
1042
+ * council that seats the builtin read-only `researcher` (a shadow recipe, and
1043
+ * the agent every unnamed council seats) refused `--synthesis judge` outright,
1044
+ * before a single member ran.
1045
+ */
1046
+ function hasCallerPersonaOverride(req: DispatchRequest): boolean {
1047
+ if (!hasPersonaOverride(req)) return false;
1048
+ return !isBoundedGateRolePrompt({ role: req.gate?.role, autonomy: req.autonomy, systemPrompt: req.systemPrompt });
1049
+ }
1050
+
996
1051
  function personaOverrideFor(req: DispatchRequest, staticCompositionHash: string | null): RunPersonaOverride | null {
997
1052
  if (!hasPersonaOverride(req) || staticCompositionHash === null) return null;
998
1053
  return { promptHash: staticCompositionHash };
@@ -1003,9 +1058,8 @@ function personaOverrideFor(req: DispatchRequest, staticCompositionHash: string
1003
1058
  * flows through dynamic messages, never through the system prompt. The system
1004
1059
  * prompt itself is stable per recipe and tool surface with one exception: the
1005
1060
  * operator-editable layer (`additionalFragments`) folds in path-scoped project
1006
- * rules selected by `workerWorkingContextPaths`, which are recalled from the
1007
- * task and briefing text, so `staticCompositionHash` can differ between two
1008
- * runs of one recipe when a rule's glob matches one task and not the other.
1061
+ * rules selected from the canonical request scope, so `staticCompositionHash`
1062
+ * can differ between two runs of one recipe when their scoped rules differ.
1009
1063
  * A local model's worker prefix cache misses on that flip; the trade was made
1010
1064
  * deliberately in #96 to keep rules scoped rather than shipped wholesale.
1011
1065
  */
@@ -1074,35 +1128,6 @@ function workerProjectContextIncludesVerification(body: string): boolean {
1074
1128
  return body.includes("\nVerification expectations:\n");
1075
1129
  }
1076
1130
 
1077
- /**
1078
- * Path-like tokens in free text: a slash-separated segment, or a bare
1079
- * `name.ext` token. The model-facing dispatch tool has no structured
1080
- * path/file field, so a worker's task text is the only working-context
1081
- * signal available at compile time; this is best-effort recall, not a
1082
- * parser. A false-positive token is harmless because `selectActiveRules`
1083
- * only activates a rule when the token matches that rule's own glob.
1084
- */
1085
- const PATH_TOKEN_RE = /(?:[\w.-]+\/)+[\w.-]+|\b[\w-]+\.[A-Za-z0-9]{1,8}\b/g;
1086
-
1087
- /**
1088
- * Best-effort working-context paths for a dispatched worker: `writeRoots`
1089
- * when the caller set them (the precise signal, for internal/programmatic
1090
- * callers), plus path-like tokens recalled from the task and briefing text.
1091
- * Feeds the same `selectActiveRules` a session uses, so a worker whose task
1092
- * touches a ruled path reads that rule instead of never hearing about it.
1093
- */
1094
- function workerWorkingContextPaths(req: DispatchRequest): string[] {
1095
- const out = new Set<string>();
1096
- for (const root of req.writeRoots ?? []) out.add(root);
1097
- const text = `${req.task}\n${req.briefing ?? ""}`;
1098
- for (const match of text.matchAll(PATH_TOKEN_RE)) {
1099
- const token = match[0];
1100
- if (token.startsWith("http://") || token.startsWith("https://")) continue;
1101
- out.add(token);
1102
- }
1103
- return [...out];
1104
- }
1105
-
1106
1131
  export function buildDynamicPromptMessages(
1107
1132
  req: DispatchRequest,
1108
1133
  dynamicContext: WorkerDynamicContext = {},
@@ -1126,6 +1151,10 @@ export function buildDynamicPromptMessages(
1126
1151
  const body = `Safety posture: autonomy ${autonomy}. ${workerSafetyOneLiner(autonomy, permission)} Worker permission routing: ${permission}.`;
1127
1152
  messages.push({ id: "dispatch-safety-posture", body, contentHash: sha256(body) });
1128
1153
  }
1154
+ const requirements = renderDispatchIntentRequirements(req.intent);
1155
+ if (requirements !== null) {
1156
+ messages.push({ id: "dispatch-intent-requirements", body: requirements, contentHash: sha256(requirements) });
1157
+ }
1129
1158
  if (req.competeStance !== undefined) {
1130
1159
  const body = competeStanceLiner(req.competeStance);
1131
1160
  messages.push({ id: "dispatch-compete-stance", body, contentHash: sha256(body) });
@@ -1250,6 +1279,7 @@ interface DispatchAdmissionStage {
1250
1279
 
1251
1280
  interface DispatchWorkerSpecInput {
1252
1281
  req: DispatchRequest;
1282
+ pathScope: DispatchPathScope;
1253
1283
  target: ResolvedTarget;
1254
1284
  admission: DispatchAdmissionStage;
1255
1285
  recipe?: AgentRecipe | null;
@@ -1269,6 +1299,7 @@ interface DispatchWorkerSpecInput {
1269
1299
 
1270
1300
  interface DispatchLifecycleStage {
1271
1301
  recipe: AgentRecipe | null;
1302
+ pathScope: DispatchPathScope;
1272
1303
  admission: DispatchAdmissionStage;
1273
1304
  target: ResolvedTarget;
1274
1305
  cwd: string;
@@ -1304,6 +1335,7 @@ interface DispatchLifecycleStage {
1304
1335
 
1305
1336
  interface AcpDelegationLifecycleStage {
1306
1337
  admission: DispatchAdmissionStage;
1338
+ pathScope: DispatchPathScope;
1307
1339
  agentConfig: ReturnType<ConfigContract["get"]>["delegation"]["agents"][number];
1308
1340
  cwd: string;
1309
1341
  systemPrompt: string;
@@ -1488,10 +1520,6 @@ function explicitToolCapability(
1488
1520
  return typeof fromKnowledgeBase === "boolean" ? fromKnowledgeBase : null;
1489
1521
  }
1490
1522
 
1491
- function writeConfinedRequest(req: DispatchRequest): boolean {
1492
- return req.writeRoots !== undefined && req.writeRoots.length > 0;
1493
- }
1494
-
1495
1523
  function effectiveToolNames(
1496
1524
  allowedTools: ReadonlyArray<ToolName>,
1497
1525
  target: ResolvedTarget,
@@ -1717,6 +1745,7 @@ function resolveDispatchAdmissionStage(
1717
1745
  req: DispatchRequest,
1718
1746
  recipe: AgentRecipe,
1719
1747
  safety: SafetyContract,
1748
+ pathScope: DispatchPathScope,
1720
1749
  ): DispatchAdmissionStage {
1721
1750
  const recipeTools = recipe.tools;
1722
1751
  const candidateTools = recipeTools && recipeTools.length > 0 ? (Array.from(recipeTools) as ToolName[]) : [];
@@ -1730,7 +1759,7 @@ function resolveDispatchAdmissionStage(
1730
1759
  }
1731
1760
  const requestedActions = deriveRequestedActions(allowedTools, safety);
1732
1761
  const orchScope = pickOrchestratorScope(safety);
1733
- const workerScope = pickWorkerScope(safety, requestedActions);
1762
+ const workerScope = pickWorkerScope(safety, requestedActions, pathScope);
1734
1763
  const verdict = admit(
1735
1764
  {
1736
1765
  requestedScope: workerScope,
@@ -1755,7 +1784,7 @@ function resolveDelegationAdmissionStage(req: DispatchRequest, safety: SafetyCon
1755
1784
  const allowedTools = applyToolProfile([], req.toolProfile);
1756
1785
  const requestedActions = deriveRequestedActions(allowedTools, safety);
1757
1786
  const orchScope = pickOrchestratorScope(safety);
1758
- const workerScope = pickWorkerScope(safety, requestedActions);
1787
+ const workerScope = pickWorkerScope(safety, requestedActions, resolveDispatchPathScope(req));
1759
1788
  const verdict = admit(
1760
1789
  {
1761
1790
  requestedScope: workerScope,
@@ -1833,9 +1862,17 @@ function buildDispatchWorkerSpec(input: DispatchWorkerSpecInput, config?: Config
1833
1862
  // The worker repairs against exactly the contract the orchestrator will seal.
1834
1863
  // A gate role that overrides the recipe contract gets no worker-side repair,
1835
1864
  // for the same reason it gets no recipe validation.
1836
- if (appliesRecipeResultContract(input.req.gate?.role) && input.recipe?.resultContract) {
1837
- spec.resultContract = input.req.resultContractOverride ?? input.recipe.resultContract;
1838
- }
1865
+ //
1866
+ // The request's own override is the contract whether or not the seated
1867
+ // recipe declares one, because that is the resolution the seal already uses.
1868
+ // Requiring a recipe contract here meant a caller-supplied override against a
1869
+ // recipe with none was sealed but never sent, so the worker spent no repair
1870
+ // round on a contract it was never told about and the run failed on a shape
1871
+ // nothing had asked it for.
1872
+ const workerResultContract = appliesRecipeResultContract(input.req.gate?.role)
1873
+ ? (input.req.resultContractOverride ?? input.recipe?.resultContract)
1874
+ : undefined;
1875
+ if (workerResultContract) spec.resultContract = workerResultContract;
1839
1876
  const product = input.req.product ?? input.recipe?.product;
1840
1877
  if (product) spec.product = product;
1841
1878
  // The orchestrator's tool decision is the one the run was admitted under and
@@ -1886,14 +1923,9 @@ function buildDispatchWorkerSpec(input: DispatchWorkerSpecInput, config?: Config
1886
1923
  if (escalation) spec.escalation = { timeoutMs: escalation.timeoutMs, fallback: escalation.fallback };
1887
1924
  }
1888
1925
  assertRuntimeCanHonorWorkerPermissionMode(input.target.runtime, spec.onPermission);
1889
- // Carry write-root confinement to the worker safety seam. Refuse it up front
1890
- // on runtimes that cannot mediate per-tool calls (subprocess) so it is never
1891
- // silently ignored. Roots are resolved against the job cwd so a relative root
1892
- // reaches the worker absolute, matching the validation contract.
1893
- if (input.req.writeRoots !== undefined && input.req.writeRoots.length > 0) {
1894
- const jobCwd = input.req.cwd !== undefined && input.req.cwd.length > 0 ? input.req.cwd : process.cwd();
1895
- spec.writeRoots = input.req.writeRoots.map((root) => resolvePath(jobCwd, root));
1896
- }
1926
+ // Carry canonical write boundaries to the worker safety seam. Exact files
1927
+ // remain exact and trailing-slash entries remain subtrees after resolution.
1928
+ if (input.pathScope.writeBoundaries.length > 0) spec.writeRoots = [...input.pathScope.writeBoundaries];
1897
1929
  assertWriteRootsEnforceable(input.target.runtime, spec.writeRoots);
1898
1930
  // Carry the tool profile so external CLI runtimes that cannot mediate
1899
1931
  // per-tool calls can refuse a narrowing profile they would otherwise ignore.
@@ -2380,6 +2412,7 @@ export function createDispatchBundle(
2380
2412
  return DEFAULT_RESILIENCE_COOLDOWN_MS;
2381
2413
  };
2382
2414
  const now = options?.now ?? (() => Date.now());
2415
+ const monotonicNow = options?.monotonicNow ?? (() => performance.now());
2383
2416
  // Durable evidence owner used by shadow observation and active readiness.
2384
2417
  const routeObserver: RouteObserver = options?.routeObserver ?? createRouteObserver({});
2385
2418
 
@@ -2388,12 +2421,32 @@ export function createDispatchBundle(
2388
2421
  const active = new Map<string, ActiveRun>();
2389
2422
  const targetCooldowns = new Map<string, { until: number; reason: string }>();
2390
2423
  const ownedReservations = new Set<string>();
2424
+ /**
2425
+ * Attribution from each finalized run's successful tool calls, newest last.
2426
+ * An incomplete record retains its paths as a lower bound beside the reasons
2427
+ * it cannot be read as a closed list.
2428
+ *
2429
+ * Held in memory rather than sealed into the receipt: the only reader is the
2430
+ * write boundary closing a window seconds later in this process, and a
2431
+ * per-run path list is run-shaped evidence the receipt schema does not carry.
2432
+ * Bounded because a long session finalizes many runs and none of this is
2433
+ * needed once its window has closed.
2434
+ */
2435
+ const runWriteAttributions = new Map<string, WriteBoundaryAttribution>();
2436
+ const recordRunWriteAttribution = (runId: string, attribution: WriteBoundaryAttribution): void => {
2437
+ runWriteAttributions.set(runId, attribution);
2438
+ while (runWriteAttributions.size > RUN_WRITE_TARGET_HISTORY) {
2439
+ const oldest = runWriteAttributions.keys().next();
2440
+ if (oldest.done === true) break;
2441
+ runWriteAttributions.delete(oldest.value);
2442
+ }
2443
+ };
2391
2444
  const capacityAdmission = createCapacityAdmissionController({
2392
2445
  limits: () => {
2393
2446
  const settings = getEffectiveSettings();
2394
2447
  const nodes: Record<string, number> = { local: configuredGlobalCapacity(settings) };
2395
2448
  for (const node of settings?.fleet?.nodes ?? []) nodes[node.id] = node.maxWorkers;
2396
- return { global: configuredGlobalCapacity(settings), nodes };
2449
+ return { global: configuredGlobalCapacity(settings), nodes, endpoints: configuredEndpointLimits() };
2397
2450
  },
2398
2451
  now,
2399
2452
  reservedPlanPeak: (planId) =>
@@ -2406,6 +2459,21 @@ export function createDispatchBundle(
2406
2459
  }),
2407
2460
  );
2408
2461
 
2462
+ function configuredEndpointLimits(): Readonly<Record<string, number>> {
2463
+ return Object.fromEntries(
2464
+ Object.entries(endpointCapacitiesForStatuses(providers.list())).map(([key, value]) => [key, value.limit]),
2465
+ );
2466
+ }
2467
+
2468
+ function endpointCapacityForTarget(targetId: string): EndpointCapacity | null {
2469
+ const status = providers.list().find((entry) => entry.target.id === targetId);
2470
+ if (status === undefined) return null;
2471
+ const endpoint = endpointCapacityForStatus(status);
2472
+ if (endpoint === null) return null;
2473
+ const configured = endpointCapacitiesForStatuses(providers.list())[endpoint.key];
2474
+ return configured ?? endpoint;
2475
+ }
2476
+
2409
2477
  function reservationCapacitySnapshot(settings: EffectiveSettings): ReservationCapacitySnapshot {
2410
2478
  const preflight = scheduling.preflight();
2411
2479
  // Reservation planning compares against a fresh read, never the display cache.
@@ -2415,9 +2483,21 @@ export function createDispatchBundle(
2415
2483
  };
2416
2484
  for (const node of settings?.fleet?.nodes ?? [])
2417
2485
  nodes[node.id] = { active: usage.nodes[node.id] ?? 0, limit: node.maxWorkers };
2486
+ const endpoints = Object.fromEntries(
2487
+ Object.entries(configuredEndpointLimits()).map(([key, limit]) => [
2488
+ key,
2489
+ {
2490
+ active: usage.endpoints[key] ?? 0,
2491
+ limit,
2492
+ leases: usage.endpointHolders[key]?.leases ?? 0,
2493
+ foregroundStreams: usage.endpointHolders[key]?.foregroundStreams ?? 0,
2494
+ },
2495
+ ]),
2496
+ );
2418
2497
  return {
2419
2498
  global: { active: usage.global, limit: configuredGlobalCapacity(settings) },
2420
2499
  nodes,
2500
+ endpoints,
2421
2501
  budget: { currentUsd: preflight.currentUsd, ceilingUsd: preflight.ceilingUsd },
2422
2502
  };
2423
2503
  }
@@ -2429,6 +2509,7 @@ export function createDispatchBundle(
2429
2509
  memberId: task.memberId,
2430
2510
  wave: task.wave,
2431
2511
  nodeId: task.resolution.node.id,
2512
+ ...(task.resolution.endpoint !== undefined ? { endpointKey: task.resolution.endpoint.key } : {}),
2432
2513
  costUpperBoundUsd: task.resolution.routeApproval?.totalCostUpperBoundUsd ?? task.resolution.costUpperBoundUsd,
2433
2514
  })),
2434
2515
  capacity: reservationCapacitySnapshot(getEffectiveSettings()),
@@ -2483,7 +2564,12 @@ export function createDispatchBundle(
2483
2564
  return scheduling.maxWorkers?.() ?? (configured === "auto" || configured === undefined ? 4 : Math.max(1, configured));
2484
2565
  }
2485
2566
 
2486
- async function admitAssignmentCapacity(req: DispatchRequest, nodeId: string, timing: RunPhaseMarks) {
2567
+ async function admitAssignmentCapacity(
2568
+ req: DispatchRequest,
2569
+ nodeId: string,
2570
+ timing: RunPhaseMarks,
2571
+ endpoint?: EndpointCapacity | null,
2572
+ ) {
2487
2573
  const queuedAt = now();
2488
2574
  timing.queuedAt = new Date(queuedAt).toISOString();
2489
2575
  const assignmentId = req.lineage?.rootRunId ?? `pending-${queuedAt.toString(36)}-${randomBytes(6).toString("hex")}`;
@@ -2501,9 +2587,16 @@ export function createDispatchBundle(
2501
2587
  const admitted = await capacityAdmission.admit({
2502
2588
  assignmentId,
2503
2589
  nodeId,
2590
+ ...(endpoint !== null && endpoint !== undefined ? { endpointKey: endpoint.key } : {}),
2504
2591
  deadlineAt,
2505
2592
  ...plan,
2506
- ...(req.reservation !== undefined && req.lineage === undefined ? { reservation: req.reservation } : {}),
2593
+ // A fleet's first attempt carries lineage and still owns the held
2594
+ // reservation member that admission must transfer into its lease.
2595
+ // Retries carry the same reservation identity after that member was
2596
+ // consumed, so they reacquire/rebind the assignment lease normally.
2597
+ ...(req.reservation !== undefined && (req.lineage === undefined || req.lineage.attempt === 0)
2598
+ ? { reservation: req.reservation }
2599
+ : {}),
2507
2600
  });
2508
2601
  timing.admittedAt = new Date(admitted.admittedAt).toISOString();
2509
2602
  return admitted.lease;
@@ -2520,6 +2613,7 @@ export function createDispatchBundle(
2520
2613
  function rebindReservationSlot(
2521
2614
  req: DispatchRequest,
2522
2615
  nodeId: string,
2616
+ endpoint: EndpointCapacity | null,
2523
2617
  costUsd: number,
2524
2618
  settings: EffectiveSettings,
2525
2619
  ): void {
@@ -2528,6 +2622,7 @@ export function createDispatchBundle(
2528
2622
  const rebind = {
2529
2623
  ...req.reservation,
2530
2624
  nodeId,
2625
+ ...(endpoint !== null ? { endpointKey: endpoint.key } : {}),
2531
2626
  costUpperBoundUsd: req.routeApproval?.totalCostUpperBoundUsd ?? costUsd,
2532
2627
  capacity,
2533
2628
  nowMs: now(),
@@ -2635,6 +2730,21 @@ export function createDispatchBundle(
2635
2730
 
2636
2731
  type RetryScheduleResult = { scheduled: true } | { scheduled: false; settlementDetail?: string };
2637
2732
 
2733
+ /** A reserved plan member owns its retry chain independently of the shared fleet root assignment. */
2734
+ function retryChainIsLive(run: ActiveRun): boolean {
2735
+ if (run.aborted) return false;
2736
+ const reservation = run.req.reservation;
2737
+ if (reservation === undefined) return assignments.get(run.lineage.rootRunId)?.status === "running";
2738
+ try {
2739
+ const record = getDispatchReservation(reservation.ownerId);
2740
+ const member = record?.members.find((entry) => entry.memberId === reservation.memberId);
2741
+ return record?.status === "active" && member !== undefined && member.status !== "released";
2742
+ } catch (error) {
2743
+ reportDispatchDiagnostic(`read retry liveness for reservation member ${reservation.memberId}`, error);
2744
+ return false;
2745
+ }
2746
+ }
2747
+
2638
2748
  function maybeScheduleRetry(
2639
2749
  run: ActiveRun,
2640
2750
  outcome: RunOutcome,
@@ -2644,7 +2754,7 @@ export function createDispatchBundle(
2644
2754
  ): RetryScheduleResult {
2645
2755
  if (draining) return { scheduled: false };
2646
2756
  const rootRunId = run.lineage.rootRunId;
2647
- if (assignments.get(rootRunId)?.status !== "running") return { scheduled: false };
2757
+ if (!retryChainIsLive(run)) return { scheduled: false };
2648
2758
  if (!RETRYABLE_OUTCOMES.has(outcome)) {
2649
2759
  retryBackoff.delete(rootRunId);
2650
2760
  return { scheduled: false };
@@ -2791,7 +2901,7 @@ export function createDispatchBundle(
2791
2901
  reason = "retry",
2792
2902
  decision: RetryDecision = decideRetry("internal", run.lineage.attempt, assignmentPolicyFor(run.req).maxRetries),
2793
2903
  ): Promise<void> {
2794
- if (draining || assignments.get(run.lineage.rootRunId)?.status !== "running") return;
2904
+ if (draining || !retryChainIsLive(run)) return;
2795
2905
  const excludesNode = decision.excludedRouteParts.includes("node");
2796
2906
  // Record the failed node so placement can choose an eligible survivor.
2797
2907
  const rerouteHops =
@@ -2973,8 +3083,8 @@ export function createDispatchBundle(
2973
3083
  }
2974
3084
  }
2975
3085
 
2976
- function heartbeatIso(heartbeatMs: number): string {
2977
- return new Date(heartbeatMs).toISOString();
3086
+ function heartbeatIso(heartbeat: HeartbeatStamp): string {
3087
+ return new Date(heartbeat.current).toISOString();
2978
3088
  }
2979
3089
 
2980
3090
  function heartbeatRunStatus(status: HeartbeatStatus): RunStatus {
@@ -2995,7 +3105,7 @@ export function createDispatchBundle(
2995
3105
  event: {
2996
3106
  type: "heartbeat_status",
2997
3107
  status,
2998
- heartbeatAt: run.heartbeatAt ? heartbeatIso(run.heartbeatAt.current) : null,
3108
+ heartbeatAt: run.heartbeatAt ? heartbeatIso(run.heartbeatAt) : null,
2999
3109
  },
3000
3110
  });
3001
3111
  }
@@ -3013,16 +3123,16 @@ export function createDispatchBundle(
3013
3123
  */
3014
3124
  function checkActiveHeartbeats(): void {
3015
3125
  if (!ledger) return;
3016
- const tickNow = now();
3126
+ const tickMonotonic = monotonicNow();
3017
3127
  for (const run of active.values()) {
3018
3128
  if (run.aborted || run.stallKilled || !run.heartbeatAt) continue;
3019
- const heartbeatMs = run.heartbeatAt.current;
3020
- if (!Number.isFinite(heartbeatMs)) continue;
3129
+ const heartbeatMonotonic = heartbeatMonotonicAt(run.heartbeatAt);
3130
+ if (!Number.isFinite(heartbeatMonotonic) || !Number.isFinite(run.heartbeatAt.current)) continue;
3021
3131
  if (run.runtimeKind === "acp-delegation") {
3022
- ledger.update(run.runId, { heartbeatAt: heartbeatIso(heartbeatMs) });
3132
+ ledger.update(run.runId, { heartbeatAt: heartbeatIso(run.heartbeatAt) });
3023
3133
  const stallMs = run.stallTimeoutMs;
3024
3134
  if (stallMs === null || stallMs <= 0) continue;
3025
- if (tickNow - heartbeatMs <= stallMs) continue;
3135
+ if (tickMonotonic - heartbeatMonotonic <= stallMs) continue;
3026
3136
  run.stallKilled = true;
3027
3137
  run.heartbeatStatus = "dead";
3028
3138
  emitHeartbeatStatus(run, "dead");
@@ -3033,10 +3143,10 @@ export function createDispatchBundle(
3033
3143
  }
3034
3144
  continue;
3035
3145
  }
3036
- const status = classifyHeartbeat(heartbeatMs, tickNow, heartbeatSpec);
3146
+ const status = classifyHeartbeat(heartbeatMonotonic, tickMonotonic, heartbeatSpec);
3037
3147
  const patch: Partial<RunEnvelope> = {
3038
3148
  status: heartbeatRunStatus(status),
3039
- heartbeatAt: heartbeatIso(heartbeatMs),
3149
+ heartbeatAt: heartbeatIso(run.heartbeatAt),
3040
3150
  };
3041
3151
  ledger.update(run.runId, patch);
3042
3152
  // Node staleness display feeds off the freshest worker heartbeat.
@@ -3169,6 +3279,27 @@ export function createDispatchBundle(
3169
3279
  targetCooldowns.set(key, { until: now() + cooldownMs, reason: failureClass });
3170
3280
  }
3171
3281
 
3282
+ function publishDispatchPathScope(req: DispatchRequest, pathScope: DispatchPathScope): void {
3283
+ const replacementDiagnostic = declaredScopeReplacementDiagnostic(pathScope);
3284
+ if (replacementDiagnostic !== null) {
3285
+ reportDispatchDiagnostic("typed scope replacement", new Error(replacementDiagnostic));
3286
+ }
3287
+ // Only the replacement case reaches the transcript. A dispatch that
3288
+ // declared no intent is the ordinary case rather than an anomaly, and one
3289
+ // receipt in ninety-nine carries an intent key today, so noticing it would
3290
+ // warn on almost every dispatch and teach the operator to skip the channel.
3291
+ // Its provenance is not lost: the receipt seals `pathProvenance`, and the
3292
+ // approval artifact renders every inferred entry in full before a
3293
+ // supervised dispatch runs, which is where an operator can still act on it.
3294
+ const notice = declaredScopeReplacementNotice(pathScope);
3295
+ if (notice !== null) {
3296
+ context.bus.emit(BusChannels.DispatchScopeNotice, {
3297
+ ...notice,
3298
+ agentId: req.agentId,
3299
+ });
3300
+ }
3301
+ }
3302
+
3172
3303
  async function resolveLifecycle(req: DispatchRequest, settings: EffectiveSettings): Promise<DispatchLifecycleStage> {
3173
3304
  const recipe = agents.get(req.agentId);
3174
3305
  if (!recipe) {
@@ -3180,7 +3311,7 @@ export function createDispatchBundle(
3180
3311
  `dispatch: agent '${req.agentId}' is a ${spec.audience} agent reserved for Clio internal orchestration`,
3181
3312
  );
3182
3313
  }
3183
- if (hasPersonaOverride(req) && (spec.audience === "shadow" || spec.audience === "internal")) {
3314
+ if (hasCallerPersonaOverride(req) && (spec.audience === "shadow" || spec.audience === "internal")) {
3184
3315
  throw new Error(`dispatch: persona overrides are not allowed for ${spec.audience} agent '${req.agentId}'`);
3185
3316
  }
3186
3317
  // A read-only recipe pointed at a mutating task is knowable here and costs
@@ -3202,7 +3333,9 @@ export function createDispatchBundle(
3202
3333
  specs: agents.listSpecs(),
3203
3334
  });
3204
3335
  if (capabilityMismatch?.verdict === "refuse") throw new Error(capabilityMismatch.detail);
3205
- const admission = resolveDispatchAdmissionStage(req, recipe, safety);
3336
+ const pathScope = resolveDispatchPathScope(req);
3337
+ publishDispatchPathScope(req, pathScope);
3338
+ const admission = resolveDispatchAdmissionStage(req, recipe, safety, pathScope);
3206
3339
  const targets = readWorkerTargets(settings);
3207
3340
  const target = resolveDispatchTarget(
3208
3341
  req,
@@ -3221,7 +3354,7 @@ export function createDispatchBundle(
3221
3354
  const sessionAutonomy = settings?.autonomy ?? "auto-edit";
3222
3355
  const effectiveAutonomy = clampWorkerAutonomy(sessionAutonomy, req.autonomy);
3223
3356
  const effectiveTools = withLedgerToolNarrowing(
3224
- effectiveToolNames(admission.allowedTools, target, writeConfinedRequest(req), deniedToolNames(req)),
3357
+ effectiveToolNames(admission.allowedTools, target, pathScope.writeBoundaries.length > 0, deniedToolNames(req)),
3225
3358
  req,
3226
3359
  );
3227
3360
  assertPostRuntimeToolCompatibility(req.agentId, spec, effectiveTools, target);
@@ -3250,7 +3383,7 @@ export function createDispatchBundle(
3250
3383
  dynamic: false,
3251
3384
  },
3252
3385
  cwd,
3253
- workingContextPaths: workerWorkingContextPaths(req),
3386
+ workingContextPaths: pathScope.workingContextPaths,
3254
3387
  });
3255
3388
  const systemPrompt = compiledWorkerPrompt.systemPrompt;
3256
3389
  const budgetEnvelope = resolveEffectiveWorkerBudget({
@@ -3294,6 +3427,7 @@ export function createDispatchBundle(
3294
3427
  const limitations = runtimeLimitations(runtimeKind, target.runtime.id);
3295
3428
  return {
3296
3429
  recipe,
3430
+ pathScope,
3297
3431
  admission: effectiveAdmission,
3298
3432
  target,
3299
3433
  cwd,
@@ -3331,12 +3465,7 @@ export function createDispatchBundle(
3331
3465
  ): AcpDelegationLifecycleStage {
3332
3466
  const agentId = req.delegationAgentId;
3333
3467
  if (!agentId) throw new Error("dispatch: missing delegationAgentId");
3334
- const boundedGateRolePrompt = isBoundedGateRolePrompt({
3335
- role: req.gate?.role,
3336
- autonomy: req.autonomy,
3337
- systemPrompt: req.systemPrompt,
3338
- });
3339
- if (hasPersonaOverride(req) && !boundedGateRolePrompt) {
3468
+ if (hasCallerPersonaOverride(req)) {
3340
3469
  throw new Error(`dispatch: persona overrides are not allowed for ACP delegation agent '${agentId}'`);
3341
3470
  }
3342
3471
  if (req.agentId && maybeAgents) {
@@ -3365,6 +3494,7 @@ export function createDispatchBundle(
3365
3494
  );
3366
3495
  }
3367
3496
  const cwd = req.cwd ?? process.cwd();
3497
+ const pathScope = resolveDispatchPathScope(req);
3368
3498
  const personaBody = workerPersonaBody(req, null, []);
3369
3499
  // ACP owns an unknown external tool inventory, so it receives the raw
3370
3500
  // bounded persona rather than a native Clio schema-harness claim.
@@ -3391,6 +3521,7 @@ export function createDispatchBundle(
3391
3521
  const personaOverride = personaOverrideFor(req, staticCompositionHash);
3392
3522
  return {
3393
3523
  admission,
3524
+ pathScope,
3394
3525
  agentConfig: configured,
3395
3526
  cwd,
3396
3527
  systemPrompt,
@@ -3426,6 +3557,7 @@ export function createDispatchBundle(
3426
3557
  finalPromise: Promise<RunReceipt>;
3427
3558
  }> {
3428
3559
  const lifecycle = resolveAcpDelegationLifecycle(req, settings);
3560
+ publishDispatchPathScope(req, lifecycle.pathScope);
3429
3561
  timing.decisionCompletedAt = new Date(now()).toISOString();
3430
3562
  const targetId = `delegation:${lifecycle.agentConfig.id}`;
3431
3563
  const runtimeId = "acp";
@@ -3434,7 +3566,7 @@ export function createDispatchBundle(
3434
3566
 
3435
3567
  assertBudgetAdmitsRoute(req, { rates: null, provenance: "unknown" }, settings);
3436
3568
 
3437
- const capacityLease = await admitAssignmentCapacity(req, "local", timing);
3569
+ const capacityLease = await admitAssignmentCapacity(req, "local", timing, null);
3438
3570
  const leaseSlot = createLeaseSlotGuard(capacityAdmission, capacityLease.leaseId, req.lineage !== undefined);
3439
3571
 
3440
3572
  // Resolve the ledger before starting the ACP process: an external agent
@@ -3460,6 +3592,8 @@ export function createDispatchBundle(
3460
3592
  safety,
3461
3593
  autonomy: lifecycle.autonomy,
3462
3594
  clientVersion: readClioVersion(),
3595
+ now,
3596
+ monotonicNow,
3463
3597
  });
3464
3598
  } catch (error) {
3465
3599
  leaseSlot.release();
@@ -3648,7 +3782,7 @@ export function createDispatchBundle(
3648
3782
  ledgerRef.update(envelope.id, {
3649
3783
  status: "running",
3650
3784
  pid: acp.pid,
3651
- heartbeatAt: heartbeatIso(acp.heartbeatAt.current),
3785
+ heartbeatAt: heartbeatIso(acp.heartbeatAt),
3652
3786
  lineage,
3653
3787
  identity,
3654
3788
  node: LOCAL_RUN_NODE,
@@ -3799,6 +3933,7 @@ export function createDispatchBundle(
3799
3933
  requestOrigin: lifecycle.requestOrigin,
3800
3934
  task: req.task,
3801
3935
  ...(req.intent !== undefined ? { intent: structuredClone(req.intent) } : {}),
3936
+ pathScope: structuredClone(lifecycle.pathScope.provenance),
3802
3937
  targetId,
3803
3938
  wireModelId,
3804
3939
  runtimeId,
@@ -4044,7 +4179,7 @@ export function createDispatchBundle(
4044
4179
  cacheReadTokenCount: receiptDraft.cacheReadTokenCount ?? 0,
4045
4180
  cacheWriteTokenCount: receiptDraft.cacheWriteTokenCount ?? 0,
4046
4181
  reasoningTokenCount: receiptDraft.reasoningTokenCount ?? 0,
4047
- heartbeatAt: heartbeatIso(acp.heartbeatAt.current),
4182
+ heartbeatAt: heartbeatIso(acp.heartbeatAt),
4048
4183
  };
4049
4184
  ledgerRef.update(envelope.id, ledgerPatch);
4050
4185
  const receipt = ledgerRef.recordReceipt(envelope.id, sealRouteDecision(receiptDraft, routeDecision));
@@ -4202,7 +4337,7 @@ export function createDispatchBundle(
4202
4337
  "dispatch: writeRoots cannot be enforced on an ACP delegation target; the external agent runs its own tool surface. Dispatch to a native or claude-sdk worker.",
4203
4338
  );
4204
4339
  }
4205
- rebindReservationSlot(req, "local", UNKNOWN_PRICING_ADMISSION_ESTIMATE_USD, settings);
4340
+ rebindReservationSlot(req, "local", null, UNKNOWN_PRICING_ADMISSION_ESTIMATE_USD, settings);
4206
4341
  routeObservation = observeShadowRoute(req, undefined, settings);
4207
4342
  return attachRouteObservation({
4208
4343
  ...(await dispatchAcpDelegation(req, settings, timing, routeObservation.decision, observer)),
@@ -4236,10 +4371,12 @@ export function createDispatchBundle(
4236
4371
  model: lifecycle.target.wireModelId,
4237
4372
  node: placement?.node?.id ?? "local",
4238
4373
  };
4374
+ const endpoint = endpointCapacityForTarget(lifecycle.target.target.id);
4239
4375
  assertRouteWithinApprovedEnvelope(req, effectiveRoute);
4240
4376
  rebindReservationSlot(
4241
4377
  req,
4242
4378
  effectiveRoute.node,
4379
+ endpoint,
4243
4380
  conservativeRouteAdmissionEstimateUsd(lifecycle.target.effectivePricing, admissionMaxOutputTokens(settings)),
4244
4381
  settings,
4245
4382
  );
@@ -4258,7 +4395,7 @@ export function createDispatchBundle(
4258
4395
  shadow: () => observeShadowRoute(req, placed, settings),
4259
4396
  });
4260
4397
 
4261
- const capacityLease = await admitAssignmentCapacity(req, placement?.node.id ?? "local", timing);
4398
+ const capacityLease = await admitAssignmentCapacity(req, placement?.node.id ?? "local", timing, endpoint);
4262
4399
  const leaseSlot = createLeaseSlotGuard(capacityAdmission, capacityLease.leaseId, req.lineage !== undefined);
4263
4400
 
4264
4401
  const ledgerRef = (() => {
@@ -4276,6 +4413,7 @@ export function createDispatchBundle(
4276
4413
  const spec = buildDispatchWorkerSpec(
4277
4414
  {
4278
4415
  req,
4416
+ pathScope: lifecycle.pathScope,
4279
4417
  target: lifecycle.target,
4280
4418
  admission: lifecycle.admission,
4281
4419
  recipe: lifecycle.recipe,
@@ -4326,6 +4464,8 @@ export function createDispatchBundle(
4326
4464
  try {
4327
4465
  worker = (placement?.spawn ?? spawnWorker)(spec, {
4328
4466
  cwd: lifecycle.cwd,
4467
+ now,
4468
+ monotonicNow,
4329
4469
  ...(agentLedgerId !== null ? { onLedgerPost } : {}),
4330
4470
  });
4331
4471
  } catch (error) {
@@ -4382,10 +4522,21 @@ export function createDispatchBundle(
4382
4522
  const upstreamResponses: RunReceiptUpstreamResponse[] = [];
4383
4523
  const skillActivations: SkillActivation[] = [];
4384
4524
  const finishContractEntries: unknown[] = [];
4525
+ let runIdForPermissionAudit: string | null = null;
4385
4526
  // The worker's own tool calls, folded into what this run changed and what
4386
4527
  // it validated. The sealed mutation-report contract is measured against
4387
4528
  // this, so a reported path the run never touched cannot seal as fact.
4388
- const runEffects = createRunEffectsRecorder(lifecycle.cwd);
4529
+ const runEffects = createRunEffectsRecorder(lifecycle.cwd, {
4530
+ onWriteRecordDowngraded(downgrade) {
4531
+ const runId = runIdForPermissionAudit;
4532
+ if (runId === null) return;
4533
+ context.bus.emit(BusChannels.DispatchProgress, {
4534
+ runId,
4535
+ agentId: req.agentId,
4536
+ event: { type: "clio_write_record_downgraded", payload: downgrade },
4537
+ });
4538
+ },
4539
+ });
4389
4540
  let finishContractAssistantText = "";
4390
4541
  let finishContractAssistantTurnId: string | null = null;
4391
4542
  let failureMessage: string | undefined;
@@ -4395,7 +4546,6 @@ export function createDispatchBundle(
4395
4546
  const acceptsOutcomeCodeEvents =
4396
4547
  lifecycle.runtimeKind === "http" ||
4397
4548
  (lifecycle.runtimeKind === "sdk" && lifecycle.target.runtime.id === "claude-sdk");
4398
- let runIdForPermissionAudit: string | null = null;
4399
4549
  let workerPolicyPermissionCounter = 0;
4400
4550
  const outputCapture = createWorkerOutputCapture();
4401
4551
  const markObservedPhase = (field: "firstModelTokenAt" | "firstToolAt"): void => {
@@ -4678,7 +4828,7 @@ export function createDispatchBundle(
4678
4828
  ...(req.council !== undefined ? { council: req.council } : {}),
4679
4829
  ...(req.plan !== undefined ? { plan: req.plan } : {}),
4680
4830
  ...(lifecycle.personaOverride ? { personaOverride: lifecycle.personaOverride } : {}),
4681
- ...(heartbeatAt ? { heartbeatAt: heartbeatIso(heartbeatAt.current) } : {}),
4831
+ ...(heartbeatAt ? { heartbeatAt: heartbeatIso(heartbeatAt) } : {}),
4682
4832
  });
4683
4833
  observer?.onAdmitted({
4684
4834
  runId: envelope.id,
@@ -4694,6 +4844,7 @@ export function createDispatchBundle(
4694
4844
  // per-worker meter. All optional so consumers degrade to local/plain.
4695
4845
  const fleetIdentity = {
4696
4846
  budget: lifecycle.budgetEnvelope,
4847
+ ...(endpoint !== null ? { endpoint: { key: endpoint.key, label: endpoint.label, limit: endpoint.limit } } : {}),
4697
4848
  ...(placement !== undefined && placement !== null ? { node: placement.node.id } : {}),
4698
4849
  ...(req.gate !== undefined ? { gate: { role: req.gate.role, cycle: req.gate.cycle } } : {}),
4699
4850
  ...(req.council !== undefined ? { council: req.council } : {}),
@@ -4893,6 +5044,7 @@ export function createDispatchBundle(
4893
5044
  outcomeCode,
4894
5045
  lineage,
4895
5046
  ...(req.intent !== undefined ? { intent: structuredClone(req.intent) } : {}),
5047
+ pathScope: structuredClone(lifecycle.pathScope.provenance),
4896
5048
  identity,
4897
5049
  node: placement?.node ?? LOCAL_RUN_NODE,
4898
5050
  ...receiptAttestationFields(worker.attestation?.() ?? null),
@@ -5269,13 +5421,51 @@ export function createDispatchBundle(
5269
5421
  ...(receiptDraft.cacheWriteTokenCount !== undefined
5270
5422
  ? { cacheWriteTokenCount: receiptDraft.cacheWriteTokenCount }
5271
5423
  : {}),
5272
- ...(activeRun.heartbeatAt ? { heartbeatAt: heartbeatIso(activeRun.heartbeatAt.current) } : {}),
5424
+ ...(activeRun.heartbeatAt ? { heartbeatAt: heartbeatIso(activeRun.heartbeatAt) } : {}),
5273
5425
  };
5274
5426
  if (receiptDraft.reasoningTokenCount !== undefined) {
5275
5427
  ledgerPatch.reasoningTokenCount = receiptDraft.reasoningTokenCount;
5276
5428
  }
5277
5429
  ledgerRef.update(envelope.id, ledgerPatch);
5278
5430
  const receipt = ledgerRef.recordReceipt(envelope.id, sealRouteDecision(receiptDraft, routeObservation.decision));
5431
+ // Two independent ways a run's write record can fail to be a closed
5432
+ // list, and either one files it as `null` rather than as an empty
5433
+ // one. The difference decides whether the write boundary may clear a
5434
+ // path it did not see this run touch.
5435
+ //
5436
+ // The telemetry axis is whether the events were seen at all: a
5437
+ // subprocess runtime publishes no tool events, and a partial trail
5438
+ // has its holes exactly where an unobserved write would sit.
5439
+ //
5440
+ // The opacity axis is whether the events that were seen say enough.
5441
+ // A run that called bash, verify, dispatch, or steer to a clean exit
5442
+ // could have written through a channel no argument names, so nothing
5443
+ // in the checkout can be cleared on the strength of its absence from
5444
+ // the set. That run's window keeps today's blame-the-whole-diff
5445
+ // behaviour; a run that never touched such a tool keeps the
5446
+ // protection.
5447
+ const toolTelemetryComplete = receipt.safety?.toolTelemetry?.coverage === "complete";
5448
+ const downgrades: WriteBoundaryAttributionDowngrade[] = observedRunEffects.writeRecordDowngrades.map((entry) => ({
5449
+ reason: entry.reason,
5450
+ tool: entry.tool,
5451
+ toolCallId: entry.toolCallId,
5452
+ runId: envelope.id,
5453
+ stepId: null,
5454
+ }));
5455
+ if (!toolTelemetryComplete) {
5456
+ downgrades.push({
5457
+ reason: "incomplete_tool_telemetry",
5458
+ tool: null,
5459
+ toolCallId: null,
5460
+ runId: envelope.id,
5461
+ stepId: null,
5462
+ });
5463
+ }
5464
+ recordRunWriteAttribution(envelope.id, {
5465
+ recorded: [...observedRunEffects.mutatedPaths],
5466
+ complete: toolTelemetryComplete && observedRunEffects.writeRecordComplete,
5467
+ downgrades,
5468
+ });
5279
5469
  await ledgerRef.persist();
5280
5470
  if (worktreeReceipt?.applied === true && req.taskWorktree !== undefined) {
5281
5471
  try {
@@ -5510,10 +5700,11 @@ export function createDispatchBundle(
5510
5700
  `dispatch: agent '${req.agentId}' is a ${agentSpec.audience} agent reserved for Clio internal orchestration`,
5511
5701
  );
5512
5702
  }
5513
- if (hasPersonaOverride(req) && (agentSpec.audience === "shadow" || agentSpec.audience === "internal")) {
5703
+ if (hasCallerPersonaOverride(req) && (agentSpec.audience === "shadow" || agentSpec.audience === "internal")) {
5514
5704
  throw new Error(`dispatch: persona overrides are not allowed for ${agentSpec.audience} agent '${req.agentId}'`);
5515
5705
  }
5516
- const admission = resolveDispatchAdmissionStage(req, recipe, safety);
5706
+ const pathScope = resolveDispatchPathScope(req);
5707
+ const admission = resolveDispatchAdmissionStage(req, recipe, safety, pathScope);
5517
5708
  const targets = readWorkerTargets(settings);
5518
5709
  const target = resolveDispatchTarget(
5519
5710
  req,
@@ -5526,14 +5717,14 @@ export function createDispatchBundle(
5526
5717
  );
5527
5718
  enforceCapabilityGate(target.target.id, target.modelCapabilities, req.requiredCapabilities);
5528
5719
  const effectiveTools = withLedgerToolNarrowing(
5529
- effectiveToolNames(admission.allowedTools, target, writeConfinedRequest(req), deniedToolNames(req)),
5720
+ effectiveToolNames(admission.allowedTools, target, pathScope.writeBoundaries.length > 0, deniedToolNames(req)),
5530
5721
  req,
5531
5722
  );
5532
5723
  assertPostRuntimeToolCompatibility(req.agentId, agentSpec, effectiveTools, target);
5533
5724
  assertRuntimeCanHonorWorkerPermissionMode(target.runtime, settings?.workers.onPermission ?? "deny");
5534
5725
  assertWorkerBudgetEnforceable(target.runtime, agentSpec.budget !== null || req.budget !== undefined);
5535
5726
  assertResponseSchemaEnforceable(target.runtime, target.modelCapabilities, req.responseSchema, effectiveTools.length);
5536
- assertWriteRootsEnforceable(target.runtime, req.writeRoots);
5727
+ assertWriteRootsEnforceable(target.runtime, pathScope.writeBoundaries);
5537
5728
  assertProtectedArtifactsEnforceable(
5538
5729
  target.runtime.id,
5539
5730
  target.runtime.kind !== "subprocess",
@@ -5546,12 +5737,14 @@ export function createDispatchBundle(
5546
5737
  allowedTools: effectiveTools,
5547
5738
  settings,
5548
5739
  });
5740
+ const endpoint = endpointCapacityForTarget(target.target.id);
5549
5741
  return {
5550
5742
  agentId: req.agentId,
5551
5743
  specFingerprint: agentSpecFingerprint(agentSpec),
5552
5744
  targetId: target.target.id,
5553
5745
  wireModelId: target.wireModelId,
5554
5746
  runtimeId: target.runtime.id,
5747
+ ...(endpoint !== null ? { endpoint: { key: endpoint.key, label: endpoint.label, limit: endpoint.limit } } : {}),
5555
5748
  node: previewNode(req).node,
5556
5749
  thinkingLevel: target.thinkingLevel,
5557
5750
  toolSignature: toolSignature(effectiveTools),
@@ -5833,7 +6026,7 @@ export function createDispatchBundle(
5833
6026
  async function sealCouncilSynthesis(input: {
5834
6027
  group: string;
5835
6028
  round: number;
5836
- kind: "none" | "vote";
6029
+ kind: "none" | "vote" | "judge";
5837
6030
  text: string;
5838
6031
  subjects: ReadonlyArray<{ runId: string; digest: string | null }>;
5839
6032
  template: RunReceipt;
@@ -5991,6 +6184,7 @@ export function createDispatchBundle(
5991
6184
  const reconciledLedger = ledger;
5992
6185
  const reconciled = await reconcileOrphanAssignments({
5993
6186
  listRunning: () => listStoredAssignments().filter((record) => record.status === "running"),
6187
+ ownerAlive: assignmentProcessOwnerAlive,
5994
6188
  lookupAttempt: (runId) => {
5995
6189
  const envelope = reconciledLedger?.get(runId) ?? null;
5996
6190
  if (!envelope) return null;
@@ -6000,7 +6194,8 @@ export function createDispatchBundle(
6000
6194
  succeeded: envelope.outcome === "succeeded",
6001
6195
  };
6002
6196
  },
6003
- settle: (assignmentId, terminalRunId, status) => settleStoredAssignment(assignmentId, terminalRunId, status),
6197
+ settle: (assignmentId, terminalRunId, status, owner) =>
6198
+ settleStoredAssignment(assignmentId, terminalRunId, status, owner),
6004
6199
  });
6005
6200
  if ((reconciled.recovered > 0 || reconciled.abandoned > 0) && process.env.CLIO_CODER_INTERACTIVE !== "1") {
6006
6201
  process.stderr.write(
@@ -6049,19 +6244,21 @@ export function createDispatchBundle(
6049
6244
  */
6050
6245
  function snapshot(): DispatchSnapshot {
6051
6246
  const tickNow = now();
6247
+ const tickMonotonic = monotonicNow();
6052
6248
  const running: DispatchSnapshot["running"] = [];
6053
6249
  const totals = { ...finalizedTotals };
6054
6250
  const costAmounts = [...finalizedCosts];
6055
6251
  for (const run of active.values()) {
6056
6252
  let heartbeat: "alive" | "stale" | "dead" | "n/a" = "n/a";
6057
- if (run.heartbeatAt && Number.isFinite(run.heartbeatAt.current)) {
6253
+ if (run.heartbeatAt && Number.isFinite(heartbeatMonotonicAt(run.heartbeatAt))) {
6254
+ const heartbeatMonotonic = heartbeatMonotonicAt(run.heartbeatAt);
6058
6255
  if (run.runtimeKind === "acp-delegation") {
6059
6256
  const stallMs = run.stallTimeoutMs;
6060
6257
  if (stallMs !== null && stallMs > 0) {
6061
- heartbeat = tickNow - run.heartbeatAt.current > stallMs ? "dead" : "alive";
6258
+ heartbeat = tickMonotonic - heartbeatMonotonic > stallMs ? "dead" : "alive";
6062
6259
  }
6063
6260
  } else {
6064
- heartbeat = classifyHeartbeat(run.heartbeatAt.current, tickNow, heartbeatSpec);
6261
+ heartbeat = classifyHeartbeat(heartbeatMonotonic, tickMonotonic, heartbeatSpec);
6065
6262
  }
6066
6263
  }
6067
6264
  const meter = run.meter;
@@ -6187,6 +6384,7 @@ export function createDispatchBundle(
6187
6384
  routeCandidates,
6188
6385
  reservations: {
6189
6386
  prepare: prepareReservation,
6387
+ release: (ownerId) => releaseDispatchReservation(ownerId, now()),
6190
6388
  rollback: (ownerId) => rollbackDispatchReservation(ownerId, now()),
6191
6389
  rollbackUnconsumed: (ownerId) => rollbackUnconsumedDispatchReservation(ownerId, now()),
6192
6390
  get: getDispatchReservation,
@@ -6204,6 +6402,21 @@ export function createDispatchBundle(
6204
6402
  if (!ledger) return null;
6205
6403
  return ledger.get(runId);
6206
6404
  },
6405
+ observedRunWrites(runId) {
6406
+ // A run this process never finalized, or finalized long enough ago to
6407
+ // have been evicted, is unknown rather than silent, so both answer null.
6408
+ const attribution = runWriteAttributions.get(runId);
6409
+ return attribution?.complete === true ? [...attribution.recorded] : null;
6410
+ },
6411
+ observedRunWriteAttribution(runId) {
6412
+ const attribution = runWriteAttributions.get(runId);
6413
+ if (attribution === undefined) return null;
6414
+ return {
6415
+ recorded: [...attribution.recorded],
6416
+ complete: attribution.complete,
6417
+ downgrades: attribution.downgrades?.map((entry) => ({ ...entry })) ?? [],
6418
+ };
6419
+ },
6207
6420
  assignments: {
6208
6421
  get: (id) => assignments.get(id),
6209
6422
  getStored: (id) => getStoredAssignment(id),