harnery 0.36.0 → 0.37.0

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 (311) hide show
  1. package/dist/commander.js +4 -0
  2. package/dist/commands/agents.d.ts.map +1 -1
  3. package/dist/commands/agents.js +87 -46
  4. package/dist/commands/artifacts.d.ts.map +1 -1
  5. package/dist/commands/artifacts.js +26 -1
  6. package/dist/commands/browse.d.ts.map +1 -1
  7. package/dist/commands/browse.js +11 -0
  8. package/dist/commands/diagnostics.d.ts +4 -0
  9. package/dist/commands/diagnostics.d.ts.map +1 -0
  10. package/dist/commands/diagnostics.js +229 -0
  11. package/dist/commands/files.d.ts +12 -0
  12. package/dist/commands/files.d.ts.map +1 -1
  13. package/dist/commands/files.js +54 -1
  14. package/dist/commands/governor.d.ts.map +1 -1
  15. package/dist/commands/governor.js +4 -0
  16. package/dist/commands/ledger-v3.d.ts.map +1 -1
  17. package/dist/commands/ledger-v3.js +71 -2
  18. package/dist/commands/qa-run.d.ts +4 -0
  19. package/dist/commands/qa-run.d.ts.map +1 -0
  20. package/dist/commands/qa-run.js +141 -0
  21. package/dist/commands/resources.d.ts +4 -0
  22. package/dist/commands/resources.d.ts.map +1 -0
  23. package/dist/commands/resources.js +29 -0
  24. package/dist/commands/supervisor.d.ts +4 -0
  25. package/dist/commands/supervisor.d.ts.map +1 -0
  26. package/dist/commands/supervisor.js +101 -0
  27. package/dist/commands/web.d.ts.map +1 -1
  28. package/dist/commands/web.js +17 -0
  29. package/dist/commands/workflow.d.ts.map +1 -1
  30. package/dist/commands/workflow.js +4 -0
  31. package/dist/core/agents/cli.js +7 -3
  32. package/dist/core/agents/health.d.ts +17 -0
  33. package/dist/core/agents/health.d.ts.map +1 -0
  34. package/dist/core/agents/health.js +109 -0
  35. package/dist/core/agents/mailbox.d.ts +92 -0
  36. package/dist/core/agents/mailbox.d.ts.map +1 -0
  37. package/dist/core/agents/mailbox.js +325 -0
  38. package/dist/core/agents/reconcile-coordination-v3.d.ts +44 -0
  39. package/dist/core/agents/reconcile-coordination-v3.d.ts.map +1 -0
  40. package/dist/core/agents/reconcile-coordination-v3.js +58 -0
  41. package/dist/core/agents/render/prompt-context.d.ts +5 -0
  42. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  43. package/dist/core/agents/render/prompt-context.js +34 -13
  44. package/dist/core/agents/render/session-context.d.ts +8 -0
  45. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  46. package/dist/core/agents/render/session-context.js +34 -3
  47. package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
  48. package/dist/core/agents/rules/commit-conflict.js +50 -1
  49. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  50. package/dist/core/agents/rules/stop-hook.js +6 -10
  51. package/dist/core/agents/session-finalizer-v3.js +4 -1
  52. package/dist/core/artifacts/index.d.ts +26 -1
  53. package/dist/core/artifacts/index.d.ts.map +1 -1
  54. package/dist/core/artifacts/index.js +146 -6
  55. package/dist/core/config.d.ts +12 -0
  56. package/dist/core/config.d.ts.map +1 -1
  57. package/dist/core/config.js +43 -0
  58. package/dist/core/diagnostics/advice.d.ts +9 -0
  59. package/dist/core/diagnostics/advice.d.ts.map +1 -0
  60. package/dist/core/diagnostics/advice.js +112 -0
  61. package/dist/core/diagnostics/bundle.d.ts +35 -0
  62. package/dist/core/diagnostics/bundle.d.ts.map +1 -0
  63. package/dist/core/diagnostics/bundle.js +874 -0
  64. package/dist/core/diagnostics/comparison.d.ts +5 -0
  65. package/dist/core/diagnostics/comparison.d.ts.map +1 -0
  66. package/dist/core/diagnostics/comparison.js +444 -0
  67. package/dist/core/diagnostics/contract.d.ts +245 -0
  68. package/dist/core/diagnostics/contract.d.ts.map +1 -0
  69. package/dist/core/diagnostics/contract.js +18 -0
  70. package/dist/core/diagnostics/identity.d.ts +3 -0
  71. package/dist/core/diagnostics/identity.d.ts.map +1 -0
  72. package/dist/core/diagnostics/identity.js +10 -0
  73. package/dist/core/diagnostics/index.d.ts +8 -0
  74. package/dist/core/diagnostics/index.d.ts.map +1 -0
  75. package/dist/core/diagnostics/index.js +7 -0
  76. package/dist/core/diagnostics/replay.d.ts +8 -0
  77. package/dist/core/diagnostics/replay.d.ts.map +1 -0
  78. package/dist/core/diagnostics/replay.js +213 -0
  79. package/dist/core/diagnostics/sanitize.d.ts +9 -0
  80. package/dist/core/diagnostics/sanitize.d.ts.map +1 -0
  81. package/dist/core/diagnostics/sanitize.js +84 -0
  82. package/dist/core/events/legacy-storage/compression.d.ts +13 -0
  83. package/dist/core/events/legacy-storage/compression.d.ts.map +1 -0
  84. package/dist/core/events/legacy-storage/compression.js +107 -0
  85. package/dist/core/events/legacy-storage/index.d.ts +1 -0
  86. package/dist/core/events/legacy-storage/index.d.ts.map +1 -1
  87. package/dist/core/events/legacy-storage/index.js +1 -0
  88. package/dist/core/events/v3/archive-retention.d.ts +33 -0
  89. package/dist/core/events/v3/archive-retention.d.ts.map +1 -0
  90. package/dist/core/events/v3/archive-retention.js +195 -0
  91. package/dist/core/events/v3/capabilities.d.ts +1 -1
  92. package/dist/core/events/v3/capabilities.d.ts.map +1 -1
  93. package/dist/core/events/v3/capabilities.js +1 -0
  94. package/dist/core/events/v3/index.d.ts +1 -0
  95. package/dist/core/events/v3/index.d.ts.map +1 -1
  96. package/dist/core/events/v3/index.js +1 -0
  97. package/dist/core/events/v3/live-routing.d.ts.map +1 -1
  98. package/dist/core/events/v3/live-routing.js +1 -0
  99. package/dist/core/events/v3/producers/hook-base.d.ts +1 -1
  100. package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -1
  101. package/dist/core/events/v3/producers/hook-base.js +13 -2
  102. package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
  103. package/dist/core/events/v3/producers/intake.js +1 -0
  104. package/dist/core/events/v3/producers/recorder.d.ts +16 -0
  105. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  106. package/dist/core/events/v3/producers/recorder.js +88 -1
  107. package/dist/core/hooks/adapter/output.d.ts +8 -0
  108. package/dist/core/hooks/adapter/output.d.ts.map +1 -1
  109. package/dist/core/hooks/adapter/output.js +11 -1
  110. package/dist/core/hooks/cli.js +183 -192
  111. package/dist/core/hooks/health.d.ts +59 -0
  112. package/dist/core/hooks/health.d.ts.map +1 -0
  113. package/dist/core/hooks/health.js +84 -0
  114. package/dist/core/resources/contract.d.ts +90 -0
  115. package/dist/core/resources/contract.d.ts.map +1 -0
  116. package/dist/core/resources/contract.js +2 -0
  117. package/dist/core/resources/index.d.ts +6 -0
  118. package/dist/core/resources/index.d.ts.map +1 -0
  119. package/dist/core/resources/index.js +5 -0
  120. package/dist/core/resources/sampler.d.ts +27 -0
  121. package/dist/core/resources/sampler.d.ts.map +1 -0
  122. package/dist/core/resources/sampler.js +357 -0
  123. package/dist/core/resources/service-status.d.ts +3 -0
  124. package/dist/core/resources/service-status.d.ts.map +1 -0
  125. package/dist/core/resources/service-status.js +48 -0
  126. package/dist/core/resources/service.d.ts +26 -0
  127. package/dist/core/resources/service.d.ts.map +1 -0
  128. package/dist/core/resources/service.js +292 -0
  129. package/dist/core/resources/storage.d.ts +13 -0
  130. package/dist/core/resources/storage.d.ts.map +1 -0
  131. package/dist/core/resources/storage.js +30 -0
  132. package/dist/core/storage/atomic-json.d.ts +3 -0
  133. package/dist/core/storage/atomic-json.d.ts.map +1 -0
  134. package/dist/core/storage/atomic-json.js +15 -0
  135. package/dist/core/storage/builtins.d.ts.map +1 -1
  136. package/dist/core/storage/builtins.js +45 -6
  137. package/dist/core/storage/logger.d.ts +2 -0
  138. package/dist/core/storage/logger.d.ts.map +1 -1
  139. package/dist/core/storage/logger.js +2 -0
  140. package/dist/core/storage/query.d.ts +14 -0
  141. package/dist/core/storage/query.d.ts.map +1 -1
  142. package/dist/core/storage/query.js +51 -0
  143. package/dist/core/supervisor/activity.d.ts +4 -0
  144. package/dist/core/supervisor/activity.d.ts.map +1 -0
  145. package/dist/core/supervisor/activity.js +141 -0
  146. package/dist/core/supervisor/contract.d.ts +268 -0
  147. package/dist/core/supervisor/contract.d.ts.map +1 -0
  148. package/dist/core/supervisor/contract.js +35 -0
  149. package/dist/core/supervisor/explanations.d.ts +4 -0
  150. package/dist/core/supervisor/explanations.d.ts.map +1 -0
  151. package/dist/core/supervisor/explanations.js +71 -0
  152. package/dist/core/supervisor/findings.d.ts +20 -0
  153. package/dist/core/supervisor/findings.d.ts.map +1 -0
  154. package/dist/core/supervisor/findings.js +399 -0
  155. package/dist/core/supervisor/history.d.ts +9 -0
  156. package/dist/core/supervisor/history.d.ts.map +1 -0
  157. package/dist/core/supervisor/history.js +46 -0
  158. package/dist/core/supervisor/hook-health-alerts.d.ts +14 -0
  159. package/dist/core/supervisor/hook-health-alerts.d.ts.map +1 -0
  160. package/dist/core/supervisor/hook-health-alerts.js +70 -0
  161. package/dist/core/supervisor/hook-health-storage.d.ts +5 -0
  162. package/dist/core/supervisor/hook-health-storage.d.ts.map +1 -0
  163. package/dist/core/supervisor/hook-health-storage.js +24 -0
  164. package/dist/core/supervisor/hook-health.d.ts +72 -0
  165. package/dist/core/supervisor/hook-health.d.ts.map +1 -0
  166. package/dist/core/supervisor/hook-health.js +203 -0
  167. package/dist/core/supervisor/hooks.d.ts +10 -0
  168. package/dist/core/supervisor/hooks.d.ts.map +1 -0
  169. package/dist/core/supervisor/hooks.js +28 -0
  170. package/dist/core/supervisor/index.d.ts +15 -0
  171. package/dist/core/supervisor/index.d.ts.map +1 -0
  172. package/dist/core/supervisor/index.js +14 -0
  173. package/dist/core/supervisor/log-feed.d.ts +9 -0
  174. package/dist/core/supervisor/log-feed.d.ts.map +1 -0
  175. package/dist/core/supervisor/log-feed.js +103 -0
  176. package/dist/core/supervisor/service.d.ts +29 -0
  177. package/dist/core/supervisor/service.d.ts.map +1 -0
  178. package/dist/core/supervisor/service.js +450 -0
  179. package/dist/core/supervisor/services.d.ts +11 -0
  180. package/dist/core/supervisor/services.d.ts.map +1 -0
  181. package/dist/core/supervisor/services.js +155 -0
  182. package/dist/core/supervisor/status.d.ts +3 -0
  183. package/dist/core/supervisor/status.d.ts.map +1 -0
  184. package/dist/core/supervisor/status.js +48 -0
  185. package/dist/core/supervisor/storage.d.ts +30 -0
  186. package/dist/core/supervisor/storage.d.ts.map +1 -0
  187. package/dist/core/supervisor/storage.js +73 -0
  188. package/dist/core/supervisor/timeline.d.ts +3 -0
  189. package/dist/core/supervisor/timeline.d.ts.map +1 -0
  190. package/dist/core/supervisor/timeline.js +87 -0
  191. package/dist/core/workflow/admission.d.ts +23 -0
  192. package/dist/core/workflow/admission.d.ts.map +1 -0
  193. package/dist/core/workflow/admission.js +136 -0
  194. package/dist/core/workflow/engine.d.ts.map +1 -1
  195. package/dist/core/workflow/engine.js +77 -2
  196. package/dist/core/workflow/index.d.ts +3 -2
  197. package/dist/core/workflow/index.d.ts.map +1 -1
  198. package/dist/core/workflow/index.js +2 -1
  199. package/dist/core/workflow/proof.d.ts +2 -1
  200. package/dist/core/workflow/proof.d.ts.map +1 -1
  201. package/dist/core/workflow/proof.js +39 -1
  202. package/dist/core/workflow/run-state.d.ts +3 -2
  203. package/dist/core/workflow/run-state.d.ts.map +1 -1
  204. package/dist/core/workflow/run-state.js +8 -1
  205. package/dist/core/workflow/types.d.ts +33 -1
  206. package/dist/core/workflow/types.d.ts.map +1 -1
  207. package/dist/core/workflow/types.js +2 -1
  208. package/dist/lib/browser/client.d.ts +5 -0
  209. package/dist/lib/browser/client.d.ts.map +1 -1
  210. package/dist/lib/browser/client.js +1 -0
  211. package/dist/lib/browser/index.d.ts +2 -0
  212. package/dist/lib/browser/index.d.ts.map +1 -1
  213. package/dist/lib/browser/index.js +2 -0
  214. package/dist/lib/browser/qa-run-contracts.d.ts +173 -0
  215. package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -0
  216. package/dist/lib/browser/qa-run-contracts.js +226 -0
  217. package/dist/lib/browser/qa-run.d.ts +51 -0
  218. package/dist/lib/browser/qa-run.d.ts.map +1 -0
  219. package/dist/lib/browser/qa-run.js +684 -0
  220. package/dist/lib/coord-root-id.d.ts +9 -0
  221. package/dist/lib/coord-root-id.d.ts.map +1 -0
  222. package/dist/lib/coord-root-id.js +17 -0
  223. package/dist/lib/instructions/apply.d.ts +3 -1
  224. package/dist/lib/instructions/apply.d.ts.map +1 -1
  225. package/dist/lib/instructions/apply.js +74 -3
  226. package/dist/lib/instructions/templates.d.ts.map +1 -1
  227. package/dist/lib/instructions/templates.js +6 -0
  228. package/package.json +12 -1
  229. package/src/commander.ts +26 -0
  230. package/src/commands/agents.ts +102 -49
  231. package/src/commands/artifacts.ts +40 -2
  232. package/src/commands/browse.ts +15 -0
  233. package/src/commands/diagnostics.ts +271 -0
  234. package/src/commands/files.ts +102 -2
  235. package/src/commands/governor.ts +8 -0
  236. package/src/commands/ledger-v3.ts +82 -0
  237. package/src/commands/qa-run.ts +184 -0
  238. package/src/commands/resources.ts +38 -0
  239. package/src/commands/supervisor.ts +141 -0
  240. package/src/commands/web.ts +19 -0
  241. package/src/commands/workflow.ts +8 -0
  242. package/src/core/agents/cli.ts +7 -3
  243. package/src/core/agents/health.ts +142 -0
  244. package/src/core/agents/mailbox.ts +373 -0
  245. package/src/core/agents/reconcile-coordination-v3.ts +77 -0
  246. package/src/core/agents/render/prompt-context.ts +42 -12
  247. package/src/core/agents/render/session-context.ts +37 -3
  248. package/src/core/agents/rules/commit-conflict.ts +58 -1
  249. package/src/core/agents/rules/stop-hook.ts +6 -12
  250. package/src/core/agents/session-finalizer-v3.ts +4 -1
  251. package/src/core/artifacts/index.ts +196 -10
  252. package/src/core/config.ts +111 -2
  253. package/src/core/diagnostics/advice.ts +147 -0
  254. package/src/core/diagnostics/bundle.ts +1142 -0
  255. package/src/core/diagnostics/comparison.ts +565 -0
  256. package/src/core/diagnostics/contract.ts +311 -0
  257. package/src/core/diagnostics/identity.ts +11 -0
  258. package/src/core/diagnostics/index.ts +7 -0
  259. package/src/core/diagnostics/replay.ts +314 -0
  260. package/src/core/diagnostics/sanitize.ts +97 -0
  261. package/src/core/events/legacy-storage/compression.ts +130 -0
  262. package/src/core/events/legacy-storage/index.ts +1 -0
  263. package/src/core/events/v3/archive-retention.ts +251 -0
  264. package/src/core/events/v3/capabilities.ts +2 -0
  265. package/src/core/events/v3/index.ts +9 -0
  266. package/src/core/events/v3/live-routing.ts +1 -0
  267. package/src/core/events/v3/producers/hook-base.ts +14 -2
  268. package/src/core/events/v3/producers/intake.ts +1 -0
  269. package/src/core/events/v3/producers/recorder.ts +110 -1
  270. package/src/core/hooks/adapter/output.ts +12 -1
  271. package/src/core/hooks/cli.ts +192 -215
  272. package/src/core/hooks/health.ts +146 -0
  273. package/src/core/resources/contract.ts +98 -0
  274. package/src/core/resources/index.ts +5 -0
  275. package/src/core/resources/sampler.ts +458 -0
  276. package/src/core/resources/service-status.ts +62 -0
  277. package/src/core/resources/service.ts +337 -0
  278. package/src/core/resources/storage.ts +44 -0
  279. package/src/core/storage/atomic-json.ts +15 -0
  280. package/src/core/storage/builtins.ts +62 -6
  281. package/src/core/storage/logger.ts +2 -0
  282. package/src/core/storage/query.ts +58 -0
  283. package/src/core/supervisor/activity.ts +193 -0
  284. package/src/core/supervisor/contract.ts +312 -0
  285. package/src/core/supervisor/explanations.ts +104 -0
  286. package/src/core/supervisor/findings.ts +788 -0
  287. package/src/core/supervisor/history.ts +61 -0
  288. package/src/core/supervisor/hook-health-alerts.ts +108 -0
  289. package/src/core/supervisor/hook-health-storage.ts +25 -0
  290. package/src/core/supervisor/hook-health.ts +301 -0
  291. package/src/core/supervisor/hooks.ts +35 -0
  292. package/src/core/supervisor/index.ts +14 -0
  293. package/src/core/supervisor/log-feed.ts +131 -0
  294. package/src/core/supervisor/service.ts +511 -0
  295. package/src/core/supervisor/services.ts +203 -0
  296. package/src/core/supervisor/status.ts +59 -0
  297. package/src/core/supervisor/storage.ts +126 -0
  298. package/src/core/supervisor/timeline.ts +133 -0
  299. package/src/core/workflow/admission.ts +215 -0
  300. package/src/core/workflow/engine.ts +93 -1
  301. package/src/core/workflow/index.ts +10 -0
  302. package/src/core/workflow/proof.ts +54 -1
  303. package/src/core/workflow/run-state.ts +13 -1
  304. package/src/core/workflow/types.ts +39 -1
  305. package/src/lib/browser/client.ts +6 -0
  306. package/src/lib/browser/index.ts +29 -0
  307. package/src/lib/browser/qa-run-contracts.ts +380 -0
  308. package/src/lib/browser/qa-run.ts +784 -0
  309. package/src/lib/coord-root-id.ts +23 -0
  310. package/src/lib/instructions/apply.ts +81 -2
  311. package/src/lib/instructions/templates.ts +6 -0
@@ -23,7 +23,6 @@ import { dirname, join } from "node:path";
23
23
  import { coordEnv } from "../../lib/env.ts";
24
24
  import { buildInstructionBundle } from "../../lib/instructions/bundle.ts";
25
25
  import type { Adapter } from "../adapter.ts";
26
- import { coordBinPath } from "../agents/coord-bin.ts";
27
26
  import { resolveCoordRoot } from "../agents/coord-client.ts";
28
27
  import {
29
28
  type ClaimFinalizationDecision,
@@ -31,7 +30,10 @@ import {
31
30
  classifyWriteClaimFinalization,
32
31
  formatWriteClaimFinalizationDenial,
33
32
  } from "../agents/finalization.ts";
34
- import { restoreLiveCoordinationStateAfterEpochV3 } from "../agents/live-authority-v3.ts";
33
+ import {
34
+ recordLiveClaimChangeV3,
35
+ restoreLiveCoordinationStateAfterEpochV3,
36
+ } from "../agents/live-authority-v3.ts";
35
37
  import { evaluateStopHook, STOP_REMEDIATION_MARKER } from "../agents/rules/stop-hook.ts";
36
38
  import {
37
39
  assistantTextStartsWithSessionNameBlock,
@@ -45,6 +47,7 @@ import type { Heartbeat } from "../agents/state/heartbeat-reader.ts";
45
47
  import { readHeartbeat, stampSessionNameSeen } from "../agents/state/heartbeat-writer.ts";
46
48
  import { readLiveCoordinationRow } from "../agents/state/live-coordination-view.ts";
47
49
  import { ensureLiveCoordinationHeartbeat } from "../agents/state/live-coordination-writer.ts";
50
+ import { assignName } from "../agents/state/names.ts";
48
51
  import { writePidmapRow } from "../agents/state/pidmap.ts";
49
52
  import { agentsRequireGitFinalization, resolveBinName } from "../config.ts";
50
53
  import {
@@ -100,6 +103,13 @@ import {
100
103
  } from "./effects/index.ts";
101
104
  import { toolInputHash, toolTargetHash } from "./events/input-hash.ts";
102
105
  import { canonicalize } from "./guard-path.ts";
106
+ import {
107
+ beginHookHealth,
108
+ type HookHealthState,
109
+ observeHookDebug,
110
+ observeHookError,
111
+ writeHookHealthCompletion,
112
+ } from "./health.ts";
103
113
  import { adapterPidFromEnv, parsePsChainLine, selectAnchorPid } from "./resolve/anchor.ts";
104
114
  import { extractIntentComment, resolveIntent } from "./resolve/intent.ts";
105
115
  import { resolveOwner } from "./resolve/owner.ts";
@@ -118,6 +128,8 @@ interface Argv {
118
128
  extra: string[];
119
129
  }
120
130
 
131
+ let hookHealthState: HookHealthState | undefined;
132
+
121
133
  function parseArgv(argv: string[]): Argv {
122
134
  const out: Argv = { eventName: null, extra: [] };
123
135
  for (let i = 0; i < argv.length; i++) {
@@ -155,17 +167,16 @@ async function readStdin(): Promise<string> {
155
167
  }
156
168
 
157
169
  /**
158
- * Env for child agent-coord spawns, with the coord root pinned. The hook
159
- * process may be running with the session shell's cwd (inside a submodule or
160
- * journal dir), and agent-coord resolves its root from cwd unless overridden —
161
- * without the pin a child could write state to a different .harnery than the
162
- * one this hook resolved.
170
+ * Env for a detached hook retry worker, with the coord root pinned. The hook
171
+ * may run with the session shell's cwd (inside a submodule or journal dir), so
172
+ * the worker must retain the root this process already resolved.
163
173
  */
164
174
  function childEnv(coordRoot: string): NodeJS.ProcessEnv {
165
175
  return { ...process.env, HARNERY_COORD_ROOT_OVERRIDE: coordRoot };
166
176
  }
167
177
 
168
178
  function appendDebug(coordRoot: string, entry: Record<string, unknown>): void {
179
+ observeHookDebug(hookHealthState, entry);
169
180
  if (coordEnv("SHARED_LOGS") !== "0") {
170
181
  try {
171
182
  processLogger(coordRoot, "agent-hook").debug("agent_hook.diagnostic", legacyLogFields(entry));
@@ -184,6 +195,7 @@ function appendDebug(coordRoot: string, entry: Record<string, unknown>): void {
184
195
  }
185
196
 
186
197
  function logError(coordRoot: string | null, err: unknown, context: Record<string, unknown>): void {
198
+ observeHookError(hookHealthState, context.phase);
187
199
  if (!coordRoot) return;
188
200
  if (coordEnv("SHARED_LOGS") !== "0") {
189
201
  try {
@@ -216,38 +228,18 @@ function logError(coordRoot: string | null, err: unknown, context: Record<string
216
228
  }
217
229
 
218
230
  /**
219
- * Spawn `agent-coord assign-name <owner> <kind>` to mint or recover the
220
- * hurricane-style name for this owner. Returns null on any failure so
221
- * session.started emission never breaks the adapter flow.
222
- *
223
- * Lives at agent-hooks side (not agent-coord) to keep emitter/consumer
224
- * separation: we spawn rather than import.
231
+ * Mint or recover the hurricane-style name for this owner in-process. Returns
232
+ * null on any failure so session.started emission never breaks adapter flow.
225
233
  */
226
- function assignNameViaAgentCoord(
234
+ function assignNameInProcess(
227
235
  coordRoot: string,
228
236
  instanceId: string,
229
237
  kind: "session" | "subagent" | "transient",
230
238
  forkedFrom?: string,
231
239
  ): { name: string; kind: string } | null {
232
- const binary = coordBinPath("agent-coord", coordRoot) ?? "";
233
- if (!existsSync(binary)) return null;
234
240
  try {
235
- const args = ["assign-name", instanceId, kind];
236
- if (forkedFrom) args.push("--forked-from", forkedFrom);
237
- const result = spawnSync(binary, args, {
238
- encoding: "utf8",
239
- timeout: 2000,
240
- env: childEnv(coordRoot),
241
- });
242
- if (result.status !== 0 || !result.stdout) return null;
243
- const parsed = JSON.parse(result.stdout.trim()) as {
244
- name?: string;
245
- kind?: string;
246
- };
247
- if (parsed.name && parsed.kind) {
248
- return { name: parsed.name, kind: parsed.kind };
249
- }
250
- return null;
241
+ const name = assignName(coordRoot, instanceId, kind, forkedFrom ? { forkedFrom } : undefined);
242
+ return { name, kind };
251
243
  } catch {
252
244
  return null;
253
245
  }
@@ -321,14 +313,9 @@ function buildEventData(
321
313
  // tool.requested heal path instead. The forkedFrom plumbing below stays
322
314
  // for adapters that DO report a parent at session start.
323
315
  const forkedFrom: string | undefined = undefined;
324
- // Assign (or recover) name + kind via agent-coord. Idempotent: resume
316
+ // Assign (or recover) name + kind in-process. Idempotent: resume
325
317
  // returns the original name; new owner consumes a counter slot.
326
- const assigned = assignNameViaAgentCoord(
327
- ctx.coordRoot,
328
- ctx.instanceId,
329
- "session",
330
- forkedFrom,
331
- );
318
+ const assigned = assignNameInProcess(ctx.coordRoot, ctx.instanceId, "session", forkedFrom);
332
319
  // Write the adapter pid-map row so `harn agents whoami` ppid-walks find
333
320
  // this owner. Prefer the payload pid (the actual claude binary), then the
334
321
  // anchor walk (the `node` ancestor for Cursor, which has no payload pid),
@@ -396,7 +383,7 @@ function buildEventData(
396
383
  ctx.adapter === "cursor" &&
397
384
  !readLiveCoordinationRow(ctx.coordRoot, ctx.instanceId)?.name
398
385
  ) {
399
- assignNameViaAgentCoord(ctx.coordRoot, ctx.instanceId, "session");
386
+ assignNameInProcess(ctx.coordRoot, ctx.instanceId, "session");
400
387
  }
401
388
  const prompt = p?.prompt ?? "";
402
389
  const { value, truncated } = clampString(prompt, 4000);
@@ -467,7 +454,7 @@ function buildEventData(
467
454
  // (agent-coord/state/names.ts → kind=transient). Use the call ID as the
468
455
  // instance_id input; assignName falls through to transient.
469
456
  const childInstanceId = subagentCallId ?? ctx.instanceId;
470
- const assigned = assignNameViaAgentCoord(ctx.coordRoot, childInstanceId, "subagent");
457
+ const assigned = assignNameInProcess(ctx.coordRoot, childInstanceId, "subagent");
471
458
  return {
472
459
  agent_type:
473
460
  (p?.raw.agent_type as string | undefined) ??
@@ -597,10 +584,18 @@ function numberField(value: unknown): number | undefined {
597
584
 
598
585
  async function main(): Promise<number> {
599
586
  const hookStartedAt = performance.now();
587
+ const hookStartedRss = process.memoryUsage().rss;
600
588
  const hookClock = captureSpanClockV3();
601
589
  const { eventName, extra } = parseArgv(process.argv.slice(2));
602
590
  const adapter = detectAdapter(process.argv.slice(2));
603
591
  const raw = await readStdin();
592
+ hookHealthState = beginHookHealth({
593
+ started_at_ms: hookStartedAt,
594
+ started_rss_bytes: hookStartedRss,
595
+ event_name: eventName,
596
+ adapter,
597
+ payload_bytes: raw.length,
598
+ });
604
599
 
605
600
  // Cursor executes the Claude Code project hooks too on hosts wired for both
606
601
  // adapters, piping them its own payload (identifiable by the top-level
@@ -612,6 +607,7 @@ async function main(): Promise<number> {
612
607
  if (coordEnv("AGENT_COORD_OFF") !== "1") {
613
608
  const coordRoot = resolveCoordRoot(process.cwd());
614
609
  if (coordRoot) {
610
+ hookHealthState.coord_root = coordRoot;
615
611
  appendDebug(coordRoot, {
616
612
  ts: new Date().toISOString(),
617
613
  event_name: eventName,
@@ -668,6 +664,7 @@ async function main(): Promise<number> {
668
664
 
669
665
  const coordRoot = resolveCoordRoot(process.cwd());
670
666
  if (!coordRoot) return 0;
667
+ hookHealthState.coord_root = coordRoot;
671
668
  if (eventName === "runtime-context-retry") {
672
669
  return runRuntimeContextRetryWorker(coordRoot, adapter, extra);
673
670
  }
@@ -696,8 +693,9 @@ async function main(): Promise<number> {
696
693
  // that tool. It is not a transcript and therefore cannot prove that an
697
694
  // earlier assistant response omitted the required session-name block.
698
695
  // afterAgentResponse is the one native boundary that carries the completed
699
- // assistant text, so stamp a positive sighting here without retaining or
700
- // emitting the response body into V3.
696
+ // assistant text. Preserve only privacy-safe ritual booleans in the V3
697
+ // producer's open-turn state; Stop consumes them into the authoritative
698
+ // turn.completed event. The response body never enters V3 or heartbeat.
701
699
  if (adapter === "cursor" && eventName === "after-agent-response") {
702
700
  const payload = parsePayload(raw, adapter);
703
701
  const owner = resolveOwner({ payload: payload?.raw ?? null, coordRoot });
@@ -713,6 +711,53 @@ async function main(): Promise<number> {
713
711
  } else {
714
712
  appendDebug(coordRoot, { ...debugBase, skipped: "no-session-name-sighting" });
715
713
  }
714
+ if (ledgerRoute.state === "blocked") {
715
+ appendDebug(coordRoot, {
716
+ ...debugBase,
717
+ skipped: "v3-control-blocked",
718
+ reason: ledgerRoute.reason,
719
+ owner_source: owner.source,
720
+ });
721
+ return 0;
722
+ }
723
+ const statusBoxPresentStrict = scanAssistantStatusBoxPresent(undefined, text);
724
+ // The hook parser needs the completed reply long enough to scan it, but
725
+ // durable hook intake must never retain that body. Keep every other native
726
+ // field so turn correlation remains available to the producer.
727
+ const { text: _completedReply, ...privacySafeRaw } = payload?.raw ?? {};
728
+ const privacySafePayload = payload ? { ...payload, raw: privacySafeRaw } : null;
729
+ const v3Result = recordLiveHookSignalV3({
730
+ coordRoot,
731
+ route: ledgerRoute,
732
+ eventName,
733
+ payload: privacySafePayload,
734
+ adapter,
735
+ instanceId: owner.instance_id,
736
+ hook_name: eventName,
737
+ hook_duration_ms: Math.max(0, Math.floor(performance.now() - hookStartedAt)),
738
+ monotonic_ns: hookClock.monotonic_ns,
739
+ turn_ritual: {
740
+ status_box_present: statusBoxPresentStrict,
741
+ status_box_present_strict: statusBoxPresentStrict,
742
+ session_name_required: name !== undefined,
743
+ session_name_present: name ? assistantTextStartsWithSessionNameBlock(text, name) : false,
744
+ },
745
+ });
746
+ observeHookDebug(hookHealthState, { event_v3_state: v3Result.state });
747
+ appendDebug(coordRoot, {
748
+ ...debugBase,
749
+ effect: "cursor-response-ritual-observed",
750
+ owner_source: owner.source,
751
+ event_v3_state: v3Result.state,
752
+ ...(v3Result.state === "observed"
753
+ ? {
754
+ generation_id: v3Result.generation_id,
755
+ turn_id: v3Result.turn_id,
756
+ response_observed_at: v3Result.observed_at,
757
+ status_box_present_strict: statusBoxPresentStrict,
758
+ }
759
+ : {}),
760
+ });
716
761
  return 0;
717
762
  }
718
763
 
@@ -735,6 +780,7 @@ async function main(): Promise<number> {
735
780
  });
736
781
  return 0;
737
782
  }
783
+ hookHealthState.owner_id = owner.instance_id;
738
784
 
739
785
  const sessionId = payload?.session_id ?? payload?.conversation_id ?? owner.instance_id;
740
786
  const priorCoordination = readHeartbeat(coordRoot, owner.instance_id);
@@ -812,6 +858,9 @@ async function main(): Promise<number> {
812
858
  ...(turnRitual ? { turn_ritual: turnRitual } : {}),
813
859
  ...(deferV3Drain ? { defer_drain: true } : {}),
814
860
  });
861
+ observeHookDebug(hookHealthState, {
862
+ ...(v3Result ? { event_v3_state: v3Result.state } : {}),
863
+ });
815
864
  const recordedGenerationId =
816
865
  v3Result && "event" in v3Result && "generation_id" in v3Result.event.scope
817
866
  ? (v3Result.event.scope.generation_id as `gen_${string}`)
@@ -966,6 +1015,7 @@ async function main(): Promise<number> {
966
1015
  // Session start is the only caller of the artifact janitor and the storage
967
1016
  // maintenance pass, so both load here instead of on every tool call.
968
1017
  const { autoCleanArtifacts } = await import("../artifacts/index.ts");
1018
+ const { autoCleanEventV3Archives } = await import("../events/v3/archive-retention.ts");
969
1019
  const { createAutomaticMaintenanceComposition, runAutomaticMaintenancePass } = await import(
970
1020
  "../storage/maintenance-providers.ts"
971
1021
  );
@@ -977,6 +1027,13 @@ async function main(): Promise<number> {
977
1027
  } catch (err) {
978
1028
  logError(coordRoot, err, { phase: "artifact-auto-clean" });
979
1029
  }
1030
+ // Effect: apply the same daily throttle to closed V3 epochs. The newest
1031
+ // recovery epochs remain protected by the archive retention policy.
1032
+ try {
1033
+ autoCleanEventV3Archives(coordRoot);
1034
+ } catch (err) {
1035
+ logError(coordRoot, err, { phase: "event-v3-archive-auto-clean" });
1036
+ }
980
1037
  // Plan one bounded, claim-first global slice from cached pressure only.
981
1038
  // Existing owner janitors above remain the only active executors until
982
1039
  // each provider receives a separate production activation.
@@ -1040,7 +1097,7 @@ async function main(): Promise<number> {
1040
1097
  // agnostic since v0.5.0.
1041
1098
  if (norm.event_type === "session.ended") {
1042
1099
  try {
1043
- cleanupSessionEnd(coordRoot, owner.instance_id, (data.reason as string) ?? "unknown");
1100
+ cleanupSessionEnd(coordRoot, owner.instance_id);
1044
1101
  } catch (err) {
1045
1102
  logError(coordRoot, err, { phase: "session-end-cleanup" });
1046
1103
  }
@@ -1065,27 +1122,9 @@ async function main(): Promise<number> {
1065
1122
  if (norm.event_type === "agent.started") {
1066
1123
  try {
1067
1124
  const childInstanceId = (data.agent_id as string | undefined) ?? owner.instance_id;
1068
- const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
1069
- if (existsSync(agentCoordBin)) {
1070
- spawnSync(agentCoordBin, ["project"], {
1071
- encoding: "utf8",
1072
- timeout: 3000,
1073
- env: childEnv(coordRoot),
1074
- });
1075
- spawnSync(
1076
- agentCoordBin,
1077
- [
1078
- "log",
1079
- `SUBAGENT_START agent_type=${(data.agent_type as string) ?? "unknown"} agent_id=${childInstanceId.slice(0, 8)} platform=${adapterPlatform(adapter)}`,
1080
- "--instance",
1081
- childInstanceId,
1082
- ],
1083
- { encoding: "utf8", timeout: 2000, env: childEnv(coordRoot) },
1084
- );
1085
- }
1086
- emitSubagentStartContext(coordRoot, childInstanceId, sessionId, data, adapter);
1125
+ await emitSubagentStartContext(coordRoot, childInstanceId, sessionId, data, adapter);
1087
1126
  } catch (err) {
1088
- logError(coordRoot, err, { phase: "subagent-start-project" });
1127
+ logError(coordRoot, err, { phase: "subagent-start-context" });
1089
1128
  }
1090
1129
  }
1091
1130
 
@@ -1093,20 +1132,7 @@ async function main(): Promise<number> {
1093
1132
  if (norm.event_type === "agent.completed") {
1094
1133
  try {
1095
1134
  const childInstanceId = payload?.subagent_id ?? payload?.agent_id ?? owner.instance_id;
1096
- cleanupSessionEnd(coordRoot, childInstanceId, (data.reason as string) ?? "unknown");
1097
- const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
1098
- if (existsSync(agentCoordBin)) {
1099
- spawnSync(
1100
- agentCoordBin,
1101
- [
1102
- "log",
1103
- `SUBAGENT_STOP agent_id=${childInstanceId.slice(0, 8)} platform=${adapterPlatform(adapter)}`,
1104
- "--instance",
1105
- childInstanceId,
1106
- ],
1107
- { encoding: "utf8", timeout: 2000, env: childEnv(coordRoot) },
1108
- );
1109
- }
1135
+ cleanupSessionEnd(coordRoot, childInstanceId);
1110
1136
  const { reconcileSessionFinalizationV3 } = await import("../agents/session-finalizer-v3.ts");
1111
1137
  reconcileSessionFinalizationV3(coordRoot, { archive_observations: [] });
1112
1138
  } catch (err) {
@@ -1494,42 +1520,19 @@ async function runPreToolUseGuard(
1494
1520
  }
1495
1521
  if (targets.length === 0) return true;
1496
1522
 
1497
- const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
1498
- if (!existsSync(agentCoordBin)) {
1499
- if (agentsRequireGitFinalization(coordRoot)) {
1500
- const { emitDeny } = await import("./adapter/output.ts");
1501
- emitDeny(
1502
- adapter,
1503
- "Harnery denied the write before mutation because the claim coordinator is unavailable; repair the project's Harnery installation and retry.",
1504
- );
1505
- return false;
1506
- }
1507
- return true;
1508
- }
1523
+ const { evaluateClaim } = await import("../agents/rules/claim-conflict.ts");
1509
1524
 
1510
1525
  // For apply_patch (multi-file), collect siblings so the deny reason names
1511
1526
  // them. For single-file tools the array has one entry.
1512
1527
  for (const target of targets) {
1513
- const verdictReq = JSON.stringify({
1514
- rule: "claim",
1515
- instance_id: instanceId,
1516
- session_id: sessionId,
1517
- path: target.path,
1518
- });
1519
- const result = spawnSync(agentCoordBin, ["verdict"], {
1520
- input: verdictReq,
1521
- encoding: "utf8",
1522
- timeout: 3000,
1523
- env: childEnv(coordRoot),
1524
- });
1525
- if (result.status !== 0 || !result.stdout) continue;
1526
- let parsed: { allow?: boolean; reason?: string } = {};
1527
1528
  try {
1528
- parsed = JSON.parse(result.stdout.trim());
1529
- } catch {
1530
- continue;
1531
- }
1532
- if (parsed.allow === false) {
1529
+ const parsed = evaluateClaim(coordRoot, {
1530
+ rule: "claim",
1531
+ instance_id: instanceId,
1532
+ session_id: sessionId,
1533
+ path: target.path,
1534
+ });
1535
+ if (parsed.allow !== false) continue;
1533
1536
  let reason =
1534
1537
  parsed.reason ?? `Path ${target.path} is currently being edited by another agent.`;
1535
1538
  if (targets.length > 1) {
@@ -1545,7 +1548,7 @@ async function runPreToolUseGuard(
1545
1548
  const { emitDeny } = await import("./adapter/output.ts");
1546
1549
  emitDeny(adapter, reason);
1547
1550
  return false;
1548
- }
1551
+ } catch {}
1549
1552
  }
1550
1553
  return true;
1551
1554
  }
@@ -1748,16 +1751,23 @@ function releaseClaimOnFailure(
1748
1751
  const row = readLiveCoordinationRow(coordRoot, instanceId);
1749
1752
  if (!row?.files_touched?.includes(canonical)) return;
1750
1753
 
1751
- const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
1752
- if (!existsSync(agentCoordBin)) return;
1753
- spawnSync(agentCoordBin, ["release-claim", instanceId, canonical], {
1754
- encoding: "utf8",
1755
- timeout: 2000,
1756
- env: childEnv(coordRoot),
1757
- });
1754
+ const before = readHeartbeat(coordRoot, instanceId);
1755
+ try {
1756
+ recordLiveClaimChangeV3({
1757
+ coordRoot,
1758
+ owner: instanceId,
1759
+ nativeSessionId: before?.session_id ?? instanceId,
1760
+ adapter: adapterFromPlatform(before?.platform),
1761
+ operation: "released",
1762
+ path: canonical,
1763
+ access: "write",
1764
+ });
1765
+ } catch {
1766
+ /* best-effort release must not break adapter flow */
1767
+ }
1758
1768
  }
1759
1769
 
1760
- function cleanupSessionEnd(coordRoot: string, instanceId: string, reason: string): void {
1770
+ function cleanupSessionEnd(coordRoot: string, instanceId: string): void {
1761
1771
  // Sweep pid-map entries pointing to this instance
1762
1772
  const pidmapDir = join(coordRoot, ".harnery", "pid-map");
1763
1773
  if (existsSync(pidmapDir)) {
@@ -1779,15 +1789,6 @@ function cleanupSessionEnd(coordRoot: string, instanceId: string, reason: string
1779
1789
  /* swallow */
1780
1790
  }
1781
1791
  }
1782
- // Activity log
1783
- const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
1784
- if (existsSync(agentCoordBin)) {
1785
- spawnSync(
1786
- agentCoordBin,
1787
- ["log", `SESSION_END reason=${reason}`, "--instance", instanceId],
1788
- { encoding: "utf8", timeout: 2000 },
1789
- );
1790
- }
1791
1792
  }
1792
1793
 
1793
1794
  async function emitUserPromptSubmitSystemMessage(
@@ -1798,33 +1799,18 @@ async function emitUserPromptSubmitSystemMessage(
1798
1799
  workspaceCwd?: string,
1799
1800
  recoveryBriefing = "",
1800
1801
  ): Promise<boolean> {
1801
- const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
1802
- let additionalContext = "";
1803
-
1804
- if (existsSync(agentCoordBin)) {
1805
- const args = ["prompt-context", "--instance", instanceId, "--session", sessionId];
1806
- // Every human-facing adapter gets the first-session naming reminder from
1807
- // the shared hook path. Cursor + Codex additionally get ongoing set-task
1808
- // staleness nudges; Claude Code enforces those via its Stop transcript.
1809
- // Codex also gets the optional project-owned prompt reminder. Claude Code
1810
- // already has native output-style reminders, so duplicating it would add
1811
- // noise. Cursor's current beforeSubmitPrompt output can allow or block but
1812
- // cannot inject model context; an Always Apply rule is its durable carrier.
1813
- // Only the boolean route travels through argv, never the host's text.
1814
- if (adapter === "codex") args.push("--host-prompt-reminder");
1815
- // Codex gets a fresh status-footer reminder on every prompt. Its Stop hook
1816
- // stays observe-only, so missing the footer never replaces the answer.
1817
- // Claude Code gets a fresh per-turn ritual reminder: satisfying the
1818
- // contract up front is far cheaper than the bounce-and-retry the Stop hook
1819
- // otherwise forces. Cursor's beforeSubmitPrompt cannot inject context, so
1820
- // its Stop follow-up remains the supported enforcement channel.
1821
- args.push("--session-name-nudge");
1822
- if (adapter === "cursor" || adapter === "codex") args.push("--task-nudge");
1823
- if (adapter === "codex") args.push("--status-footer-nudge");
1824
- if (adapter === "claude-code") args.push("--turn-ritual-nudge", adapter);
1825
- const result = spawnSync(agentCoordBin, args, { encoding: "utf8", timeout: 3000 });
1826
- if (result.status === 0 && result.stdout) additionalContext = result.stdout.trim();
1827
- }
1802
+ const { renderPromptContext } = await import("../agents/render/prompt-context.ts");
1803
+ let additionalContext = renderPromptContext({
1804
+ coordRoot,
1805
+ instanceId,
1806
+ sessionId,
1807
+ adapter,
1808
+ sessionNameNudge: true,
1809
+ taskNudge: adapter === "cursor" || adapter === "codex",
1810
+ hostPromptReminder: adapter === "codex",
1811
+ statusFooterNudge: adapter === "codex",
1812
+ turnRitualNudge: adapter === "claude-code" ? adapter : undefined,
1813
+ }).trim();
1828
1814
  if (adapter === "codex") {
1829
1815
  const fileLinkContext = renderCodexWslFileLinkContext(coordRoot, workspaceCwd);
1830
1816
  if (fileLinkContext) {
@@ -1841,14 +1827,14 @@ async function emitUserPromptSubmitSystemMessage(
1841
1827
  return true;
1842
1828
  }
1843
1829
 
1844
- function emitSubagentStartContext(
1830
+ async function emitSubagentStartContext(
1845
1831
  coordRoot: string,
1846
1832
  instanceId: string,
1847
1833
  sessionId: string,
1848
1834
  data: Record<string, unknown>,
1849
1835
  adapter: Adapter,
1850
- ): void {
1851
- // Look up the subagent's assigned name (just-written by agent-coord assignName
1836
+ ): Promise<void> {
1837
+ // Look up the subagent's assigned name (just-written by the in-process name
1852
1838
  // in session.started data) + the parent's short id for the "you are a subagent
1853
1839
  // of X" framing.
1854
1840
  const subagentName = (data.name as string | undefined) ?? "";
@@ -1861,23 +1847,20 @@ function emitSubagentStartContext(
1861
1847
  // Render peer table inline since the subagent might want to know who else
1862
1848
  // is around. Reuse prompt-context (which dedups against the per-owner hash);
1863
1849
  // first call will always emit.
1864
- const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
1865
1850
  let combined = message;
1866
- if (existsSync(agentCoordBin)) {
1867
- const result = spawnSync(
1868
- agentCoordBin,
1869
- ["prompt-context", "--instance", instanceId, "--session", sessionId, "--name", subagentName],
1870
- { encoding: "utf8", timeout: 3000 },
1871
- );
1872
- const ctx = (result.stdout ?? "").trim();
1873
- if (ctx) combined = `${message}\n\n${ctx}`;
1874
- }
1851
+ const { renderPromptContext } = await import("../agents/render/prompt-context.ts");
1852
+ const context = renderPromptContext({
1853
+ coordRoot,
1854
+ instanceId,
1855
+ sessionId,
1856
+ agentName: subagentName,
1857
+ }).trim();
1858
+ if (context) combined = `${message}\n\n${context}`;
1875
1859
 
1876
1860
  // Use SubagentStart event-name in CC's hookSpecificOutput shape; cursor's
1877
1861
  // flat `additional_context` works the same way.
1878
- void import("./adapter/output.ts").then(({ emitContext }) => {
1879
- emitContext(adapter, "SubagentStart", combined);
1880
- });
1862
+ const { emitContext } = await import("./adapter/output.ts");
1863
+ emitContext(adapter, "SubagentStart", combined);
1881
1864
  }
1882
1865
 
1883
1866
  function adapterPlatform(adapter: Adapter): string {
@@ -1885,6 +1868,12 @@ function adapterPlatform(adapter: Adapter): string {
1885
1868
  return adapter;
1886
1869
  }
1887
1870
 
1871
+ function adapterFromPlatform(platform: unknown): Adapter {
1872
+ if (platform === "cursor") return "cursor";
1873
+ if (platform === "codex") return "codex";
1874
+ return "claude-code";
1875
+ }
1876
+
1888
1877
  async function emitSessionStartSystemMessage(
1889
1878
  coordRoot: string,
1890
1879
  instanceId: string,
@@ -1893,53 +1882,29 @@ async function emitSessionStartSystemMessage(
1893
1882
  adapter: Adapter,
1894
1883
  recoveryBriefing = "",
1895
1884
  ): Promise<boolean> {
1896
- const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
1897
1885
  const workflowChild = coordEnv("WORKFLOW_CHILD") === "1";
1898
1886
  let additionalContext = "";
1899
- if (existsSync(agentCoordBin)) {
1900
- // Sync-project so the heartbeat exists for downstream readers (peer table,
1901
- // wiring check, council invites).
1902
- spawnSync(agentCoordBin, ["project"], { encoding: "utf8", timeout: 3000 });
1903
- // Opportunistic reconciliation makes normal session starts a failsafe for
1904
- // archive, idle, cascade, and host lifecycle observations.
1905
- spawnSync(agentCoordBin, ["reconcile-finalization"], {
1906
- encoding: "utf8",
1907
- timeout: 5000,
1908
- env: { ...process.env, HARNERY_COORD_ROOT_OVERRIDE: coordRoot },
1909
- });
1910
-
1911
- // SESSION_START activity log line, fired across all adapters.
1912
- const model = (emittedData.model as string | undefined) ?? "unknown";
1913
- const source = (emittedData.source as string | undefined) ?? "startup";
1914
- const platform = adapterPlatform(adapter);
1915
- spawnSync(
1916
- agentCoordBin,
1917
- [
1918
- "log",
1919
- `SESSION_START model=${model} source=${source} platform=${platform}`,
1920
- "--instance",
1921
- instanceId,
1922
- ],
1923
- { encoding: "utf8", timeout: 2000 },
1924
- );
1925
-
1926
- // Workflow children retain lifecycle/event capture but do not receive
1927
- // operator-facing peer, council, or init-remediation context. Injecting
1928
- // that context can make a bounded child follow housekeeping instructions
1929
- // instead of its assigned prompt.
1930
- if (!workflowChild) {
1931
- const agentName = (emittedData.name as string | undefined) ?? "";
1932
- const args = ["session-context", "--instance", instanceId, "--session", sessionId];
1933
- if (agentName) args.push("--name", agentName);
1934
- // The "You are agent-X." prefix in session-context renders unqualified by
1935
- // default (claude-code-style). For cursor/codex the bash dispatchers add
1936
- // a "(Cursor)" / "(Codex)" suffix; pass it through as --platform-label.
1937
- if (adapter !== "claude-code") {
1938
- args.push("--platform-label", platform === "cursor" ? "Cursor" : "Codex");
1939
- }
1940
- const result = spawnSync(agentCoordBin, args, { encoding: "utf8", timeout: 3000 });
1941
- if (result.status === 0 && result.stdout) additionalContext = result.stdout.trim();
1942
- }
1887
+ // Opportunistic reconciliation makes normal session starts a failsafe for
1888
+ // archive, idle, cascade, and host lifecycle observations. It also runs the
1889
+ // stale cache sweep through the shared ADR 0077 composition.
1890
+ const { reconcileCoordinationV3 } = await import("../agents/reconcile-coordination-v3.ts");
1891
+ reconcileCoordinationV3(coordRoot);
1892
+
1893
+ // Workflow children retain lifecycle/event capture but do not receive
1894
+ // operator-facing peer, council, or init-remediation context. Injecting
1895
+ // that context can make a bounded child follow housekeeping instructions
1896
+ // instead of its assigned prompt.
1897
+ if (!workflowChild) {
1898
+ const agentName = (emittedData.name as string | undefined) ?? "";
1899
+ const { renderSessionContext } = await import("../agents/render/session-context.ts");
1900
+ additionalContext = renderSessionContext({
1901
+ coordRoot,
1902
+ instanceId,
1903
+ sessionId,
1904
+ agentName: agentName || undefined,
1905
+ platformLabel:
1906
+ adapter === "claude-code" ? undefined : adapter === "cursor" ? "Cursor" : "Codex",
1907
+ }).trim();
1943
1908
  }
1944
1909
 
1945
1910
  // Effect (claude-code): merge the journal recovery cue into the session-start
@@ -1987,6 +1952,11 @@ function completeRecoveryInjection(
1987
1952
 
1988
1953
  main()
1989
1954
  .then(async (code) => {
1955
+ writeHookHealthCompletion(hookHealthState, {
1956
+ finished_at_ms: performance.now(),
1957
+ finished_rss_bytes: process.memoryUsage().rss,
1958
+ pid: process.pid,
1959
+ });
1990
1960
  await closeProcessLoggers();
1991
1961
  process.exit(code);
1992
1962
  })
@@ -1994,6 +1964,13 @@ main()
1994
1964
  logError(resolveCoordRoot(process.cwd()), err, {
1995
1965
  argv: process.argv.slice(2),
1996
1966
  pid: process.pid,
1967
+ phase: "top-level",
1968
+ });
1969
+ writeHookHealthCompletion(hookHealthState, {
1970
+ finished_at_ms: performance.now(),
1971
+ finished_rss_bytes: process.memoryUsage().rss,
1972
+ pid: process.pid,
1973
+ faulted: true,
1997
1974
  });
1998
1975
  await closeProcessLoggers();
1999
1976
  process.exit(0);