akm-cli 0.9.1 → 0.9.2-alpha.2

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 (350) hide show
  1. package/CHANGELOG.md +103 -28
  2. package/README.md +3 -1
  3. package/SECURITY.md +1 -1
  4. package/STABILITY.md +1 -1
  5. package/dist/akm +2 -2
  6. package/dist/akm-migrate +2 -2
  7. package/dist/assets/hints/cli-hints-full.md +14 -9
  8. package/dist/assets/improve-strategies/proactive-maintenance.json +1 -1
  9. package/dist/assets/improve-strategies/reflect-distill.json +1 -1
  10. package/dist/assets/models.json +35 -0
  11. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +3 -4
  12. package/dist/assets/stash-skeleton/facts/conventions/organization.md +1 -3
  13. package/dist/assets/tasks/core/extract.yml +6 -5
  14. package/dist/assets/tasks/core/improve.yml +6 -5
  15. package/dist/assets/tasks/core/index-refresh.yml +6 -5
  16. package/dist/assets/tasks/core/sync.yml +6 -5
  17. package/dist/assets/tasks/core/version-check.yml +6 -5
  18. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +6 -5
  19. package/dist/assets/tasks/improve/akm-improve-catchup.yml +6 -5
  20. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +6 -5
  21. package/dist/assets/tasks/improve/akm-improve-frequent.yml +6 -5
  22. package/dist/assets/tasks/improve/akm-improve-nightly.yml +6 -5
  23. package/dist/cli/confirm.js +2 -2
  24. package/dist/cli/parse-args.js +3 -24
  25. package/dist/cli/retired-commands.js +1 -1
  26. package/dist/cli/shared.js +2 -2
  27. package/dist/cli.js +11 -9
  28. package/dist/commands/agent/agent-dispatch.js +55 -89
  29. package/dist/commands/agent/contribute-cli.js +12 -45
  30. package/dist/commands/command/builtin-action.js +32 -0
  31. package/dist/commands/command/command-cli.js +99 -0
  32. package/dist/commands/command/command-execution.js +308 -0
  33. package/dist/commands/command/execution-source-loader.js +176 -0
  34. package/dist/commands/command/portable-template.js +60 -0
  35. package/dist/commands/config-cli.js +10 -4
  36. package/dist/commands/env/env.js +4 -2
  37. package/dist/commands/feedback-cli.js +1 -1
  38. package/dist/commands/health/checks.js +241 -29
  39. package/dist/commands/health/html-report.js +0 -14
  40. package/dist/commands/health/report-view-model.js +0 -1
  41. package/dist/commands/health/surfaces.js +6 -7
  42. package/dist/commands/health/types.js +0 -2
  43. package/dist/commands/health.js +63 -18
  44. package/dist/commands/improve/collapse-detector.js +5 -6
  45. package/dist/commands/improve/consolidate.js +251 -214
  46. package/dist/commands/improve/distill/promote-memory.js +71 -34
  47. package/dist/commands/improve/distill/quality-gate.js +17 -5
  48. package/dist/commands/improve/distill.js +232 -155
  49. package/dist/commands/improve/eligibility.js +112 -79
  50. package/dist/commands/improve/execution.js +57 -0
  51. package/dist/commands/improve/extract-cli.js +5 -5
  52. package/dist/commands/improve/extract-prompt.js +64 -22
  53. package/dist/commands/improve/extract.js +608 -360
  54. package/dist/commands/improve/improve-strategies.js +43 -14
  55. package/dist/commands/improve/improve.js +249 -29
  56. package/dist/commands/improve/loop-stages.js +11 -17
  57. package/dist/commands/improve/memory/memory-contradiction-detect.js +90 -66
  58. package/dist/commands/improve/outcome-loop.js +22 -38
  59. package/dist/commands/improve/planner.js +134 -0
  60. package/dist/commands/improve/preparation.js +730 -409
  61. package/dist/commands/improve/reflect.js +386 -223
  62. package/dist/commands/improve/run-context.js +3 -4
  63. package/dist/commands/improve/salience.js +6 -58
  64. package/dist/commands/improve/session-asset.js +12 -12
  65. package/dist/commands/lint/index.js +101 -29
  66. package/dist/commands/migrate-cli.js +11 -69
  67. package/dist/commands/migration-tool.js +6 -9
  68. package/dist/commands/models-cli.js +27 -0
  69. package/dist/commands/proposal/drain.js +258 -186
  70. package/dist/commands/proposal/proposal-cli.js +32 -10
  71. package/dist/commands/proposal/proposal.js +2 -5
  72. package/dist/commands/proposal/propose.js +192 -172
  73. package/dist/commands/proposal/repository.js +54 -91
  74. package/dist/commands/proposal/validators/proposal-validators.js +9 -7
  75. package/dist/commands/read/curate.js +53 -22
  76. package/dist/commands/read/registry-search.js +25 -9
  77. package/dist/commands/read/remember-cli.js +14 -2
  78. package/dist/commands/read/search.js +10 -4
  79. package/dist/commands/read/show.js +139 -153
  80. package/dist/commands/registry-cli.js +16 -7
  81. package/dist/commands/remember.js +33 -18
  82. package/dist/commands/sources/add-cli.js +19 -178
  83. package/dist/commands/sources/bundle-cli.js +15 -3
  84. package/dist/commands/sources/dangerous-env-audit.js +135 -0
  85. package/dist/commands/sources/info.js +2 -1
  86. package/dist/commands/sources/installed-stashes.js +901 -177
  87. package/dist/commands/sources/schema-repair.js +174 -95
  88. package/dist/commands/sources/self-update.js +30 -74
  89. package/dist/commands/sources/source-add.js +3 -5
  90. package/dist/commands/sources/sources-cli.js +2 -15
  91. package/dist/commands/sources/update-transaction.js +220 -0
  92. package/dist/commands/tasks/tasks-cli.js +3 -3
  93. package/dist/commands/tasks/tasks.js +736 -317
  94. package/dist/commands/workflow-cli.js +2 -2
  95. package/dist/core/adapter/adapters/agent-skills-adapter.js +3 -0
  96. package/dist/core/adapter/adapters/akm-adapter.js +85 -35
  97. package/dist/core/adapter/adapters/akm-lint.js +54 -39
  98. package/dist/core/adapter/adapters/akm-metadata.js +45 -45
  99. package/dist/core/adapter/adapters/akm-task-adapter.js +32 -49
  100. package/dist/core/adapter/adapters/akm-workflow-adapter.js +38 -23
  101. package/dist/core/adapter/adapters/dotenv-adapter.js +30 -1
  102. package/dist/core/adapter/adapters/generic-files-adapter.js +11 -0
  103. package/dist/core/adapter/adapters/index.js +0 -9
  104. package/dist/core/adapter/adapters/llm-wiki-adapter.js +4 -0
  105. package/dist/core/adapter/adapters/okf-adapter.js +4 -0
  106. package/dist/core/adapter/adapters/opencode-adapter.js +5 -8
  107. package/dist/core/adapter/adapters/tool-dir-shared.js +63 -6
  108. package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -0
  109. package/dist/core/adapter/execution-source.js +308 -0
  110. package/dist/core/adapter/recognize-match.js +36 -13
  111. package/dist/core/adapter/registry.js +0 -9
  112. package/dist/core/asset/stash-meta.js +94 -4
  113. package/dist/core/common.js +6 -11
  114. package/dist/core/config/config-io.js +3 -3
  115. package/dist/core/config/config-schema.js +18 -40
  116. package/dist/core/config/config-sources.js +11 -21
  117. package/dist/core/config/config-walker.js +31 -13
  118. package/dist/core/config/config.js +23 -26
  119. package/dist/core/config/schema/engines.js +8 -7
  120. package/dist/core/config/schema/improve-processes.js +29 -5
  121. package/dist/core/config/schema/index-config.js +0 -27
  122. package/dist/core/config/schema/primitives.js +1 -23
  123. package/dist/core/config/schema/sources-bundles.js +13 -16
  124. package/dist/core/errors.js +2 -0
  125. package/dist/core/events.js +68 -32
  126. package/dist/core/extra-params.js +1 -0
  127. package/dist/core/improve-result.js +315 -0
  128. package/dist/core/lesson-lint.js +0 -6
  129. package/dist/core/maintenance-barrier.js +4 -4
  130. package/dist/core/network-policy.js +152 -0
  131. package/dist/core/paths.js +1 -1
  132. package/dist/core/recognition-util.js +4 -4
  133. package/dist/core/registry-url.js +456 -0
  134. package/dist/core/state/migrations.js +161 -47
  135. package/dist/core/state-db.js +453 -80
  136. package/dist/core/system-error.js +32 -0
  137. package/dist/core/time.js +2 -12
  138. package/dist/core/write-source.js +0 -18
  139. package/dist/execution/directory-identity.js +52 -0
  140. package/dist/execution/executable-identity.js +107 -0
  141. package/dist/execution/guarded-source.js +398 -0
  142. package/dist/execution/json.js +95 -0
  143. package/dist/{commands/health/types-session-log.js → execution/limits.js} +2 -1
  144. package/dist/execution/record.js +55 -0
  145. package/dist/execution/resolved-request.js +730 -0
  146. package/dist/execution/source.js +320 -0
  147. package/dist/indexer/bundle-identity-guard.js +5 -4
  148. package/dist/indexer/db/graph-db.js +33 -0
  149. package/dist/indexer/graph/graph-boost.js +3 -4
  150. package/dist/indexer/graph/graph-extraction.js +562 -373
  151. package/dist/indexer/index-written-assets.js +78 -39
  152. package/dist/indexer/indexer.js +471 -432
  153. package/dist/indexer/installations.js +6 -0
  154. package/dist/indexer/lookup/adapter-concept-owner.js +283 -0
  155. package/dist/indexer/materialize-embeddings.js +155 -0
  156. package/dist/indexer/passes/memory-inference.js +227 -174
  157. package/dist/indexer/passes/metadata.js +263 -118
  158. package/dist/indexer/scan/doc-to-entry.js +7 -10
  159. package/dist/indexer/scan/drain-dir.js +51 -23
  160. package/dist/indexer/search/db-search.js +156 -50
  161. package/dist/indexer/search/fts-query.js +40 -40
  162. package/dist/indexer/search/ranking.js +36 -1
  163. package/dist/indexer/search/search-attribution.js +3 -1
  164. package/dist/indexer/search/search-fields.js +23 -14
  165. package/dist/indexer/search/search-hit-enrichers.js +1 -1
  166. package/dist/indexer/search/search-source.js +7 -16
  167. package/dist/indexer/search/semantic-status.js +10 -1
  168. package/dist/indexer/usage/show-usage.js +105 -0
  169. package/dist/indexer/usage/usage-events.js +7 -2
  170. package/dist/indexer/walk/matchers.js +40 -10
  171. package/dist/indexer/walk/path-resolver.js +5 -2
  172. package/dist/indexer/walk/walker.js +20 -2
  173. package/dist/integrations/agent/builder-shared.js +3 -6
  174. package/dist/integrations/agent/conversation-fallback.js +16 -0
  175. package/dist/integrations/agent/engine-resolution.js +87 -87
  176. package/dist/integrations/agent/execution-cascade.js +566 -0
  177. package/dist/integrations/agent/execution-definitions.js +211 -0
  178. package/dist/integrations/agent/execution-lowering.js +811 -0
  179. package/dist/integrations/agent/execution-preparation.js +67 -0
  180. package/dist/integrations/agent/index.js +0 -2
  181. package/dist/integrations/agent/inline-execution.js +74 -0
  182. package/dist/integrations/agent/model-map.js +515 -0
  183. package/dist/integrations/agent/persona-fallback.js +30 -0
  184. package/dist/integrations/agent/request-lowering.js +186 -0
  185. package/dist/integrations/agent/runner-dispatch.js +230 -37
  186. package/dist/integrations/agent/runner.js +12 -83
  187. package/dist/integrations/harnesses/aider/agent-builder.js +8 -0
  188. package/dist/integrations/harnesses/aider/index.js +0 -1
  189. package/dist/integrations/harnesses/amazonq/agent-builder.js +8 -0
  190. package/dist/integrations/harnesses/amazonq/index.js +0 -1
  191. package/dist/integrations/harnesses/claude/agent-builder.js +14 -1
  192. package/dist/integrations/harnesses/claude/index.js +1 -5
  193. package/dist/integrations/harnesses/claude/session-log.js +3 -33
  194. package/dist/integrations/harnesses/codex/agent-builder.js +8 -0
  195. package/dist/integrations/harnesses/codex/index.js +0 -1
  196. package/dist/integrations/harnesses/copilot/agent-builder.js +8 -0
  197. package/dist/integrations/harnesses/copilot/index.js +0 -1
  198. package/dist/integrations/harnesses/gemini/agent-builder.js +8 -0
  199. package/dist/integrations/harnesses/gemini/index.js +0 -1
  200. package/dist/integrations/harnesses/index.js +4 -44
  201. package/dist/integrations/harnesses/opencode/agent-builder.js +16 -9
  202. package/dist/integrations/harnesses/opencode/index.js +0 -2
  203. package/dist/integrations/harnesses/opencode/session-log.js +14 -204
  204. package/dist/integrations/harnesses/opencode-sdk/harness.js +12 -1
  205. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +40 -42
  206. package/dist/integrations/harnesses/openhands/agent-builder.js +8 -0
  207. package/dist/integrations/harnesses/openhands/index.js +0 -1
  208. package/dist/integrations/harnesses/pi/agent-builder.js +8 -0
  209. package/dist/integrations/harnesses/pi/index.js +0 -1
  210. package/dist/integrations/harnesses/shared.js +0 -1
  211. package/dist/integrations/harnesses/types.js +1 -3
  212. package/dist/integrations/lockfile.js +82 -79
  213. package/dist/integrations/session-logs/index.js +6 -17
  214. package/dist/integrations/session-logs/provider-base.js +1 -29
  215. package/dist/llm/client.js +10 -5
  216. package/dist/llm/embedder.js +6 -7
  217. package/dist/llm/embedders/local.js +37 -88
  218. package/dist/llm/embedders/types.js +1 -1
  219. package/dist/llm/graph-extract.js +75 -50
  220. package/dist/llm/index-passes.js +43 -5
  221. package/dist/llm/memory-infer.js +8 -6
  222. package/dist/llm/metadata-enhance.js +5 -3
  223. package/dist/llm/structured-call.js +122 -25
  224. package/dist/output/format-exempt.js +1 -1
  225. package/dist/output/render-registry.js +0 -16
  226. package/dist/output/renderers.js +12 -7
  227. package/dist/output/shapes/curate.js +1 -0
  228. package/dist/output/shapes/helpers.js +10 -2
  229. package/dist/output/shapes/passthrough.js +2 -0
  230. package/dist/output/text/command-format.js +31 -33
  231. package/dist/output/text/health-format.js +1 -29
  232. package/dist/output/text/migrate.js +6 -56
  233. package/dist/output/text/proposal-format.js +16 -1
  234. package/dist/output/text/workflow-format.js +16 -0
  235. package/dist/registry/network.js +279 -0
  236. package/dist/registry/pinned-request-helper.js +247 -0
  237. package/dist/registry/pinned-transport.js +717 -0
  238. package/dist/registry/providers/skills-sh.js +18 -6
  239. package/dist/registry/providers/static-index.js +20 -7
  240. package/dist/registry/resolve.js +53 -28
  241. package/dist/scripts/akm-migrate-node.js +19334 -52269
  242. package/dist/scripts/akm-migrate.js +19270 -51612
  243. package/dist/setup/registry-stash-loader.js +64 -20
  244. package/dist/setup/semantic-assets.js +9 -34
  245. package/dist/setup/setup.js +12 -30
  246. package/dist/setup/source-identity.js +17 -0
  247. package/dist/setup/steps/sources.js +36 -15
  248. package/dist/setup/steps/tasks.js +39 -11
  249. package/dist/sources/providers/git-provider.js +3 -3
  250. package/dist/sources/providers/npm.js +2 -2
  251. package/dist/sources/providers/provider-utils.js +4 -3
  252. package/dist/sources/providers/website.js +11 -7
  253. package/dist/sources/snapshot-fetchers/host-guard.js +9 -136
  254. package/dist/sources/snapshot-fetchers/website-ingest.js +25 -109
  255. package/dist/sources/website-url.js +73 -0
  256. package/dist/storage/engines/sqlite-migrations.js +81 -26
  257. package/dist/storage/managed-db.js +27 -24
  258. package/dist/storage/repositories/events-repository.js +3 -0
  259. package/dist/storage/repositories/index-connection.js +42 -10
  260. package/dist/storage/repositories/index-entries-repository.js +203 -229
  261. package/dist/storage/repositories/index-entry-mapper.js +8 -12
  262. package/dist/storage/repositories/index-entry-schema.js +255 -0
  263. package/dist/storage/repositories/index-fts-repository.js +64 -71
  264. package/dist/storage/repositories/index-llm-cache-repository.js +8 -13
  265. package/dist/storage/repositories/index-meta-repository.js +0 -11
  266. package/dist/storage/repositories/index-schema.js +74 -350
  267. package/dist/storage/repositories/index-utility-repository.js +12 -17
  268. package/dist/storage/repositories/index-vec-repository.js +56 -7
  269. package/dist/storage/repositories/proposals-repository.js +4 -127
  270. package/dist/storage/repositories/registry-cache.js +2 -1
  271. package/dist/storage/repositories/task-history-repository.js +20 -40
  272. package/dist/storage/repositories/workflow-runs-repository.js +228 -129
  273. package/dist/storage/sqlite-read-snapshot.js +148 -0
  274. package/dist/tasks/backends/cron.js +170 -42
  275. package/dist/tasks/backends/index.js +1 -1
  276. package/dist/tasks/backends/launchd.js +787 -202
  277. package/dist/tasks/backends/schtasks.js +282 -83
  278. package/dist/tasks/embedded.js +7 -7
  279. package/dist/tasks/frozen-script.js +50 -0
  280. package/dist/tasks/resolve-akm-bin.js +5 -1
  281. package/dist/tasks/runner.js +239 -251
  282. package/dist/tasks/runtime-v3.js +281 -0
  283. package/dist/tasks/scheduler-binding.js +272 -0
  284. package/dist/tasks/scheduler-invocation.js +57 -43
  285. package/dist/tasks/scheduler-sync.js +654 -0
  286. package/dist/tasks/source-v3.js +752 -0
  287. package/dist/tasks/standalone-script-entry.js +5 -0
  288. package/dist/tasks/task-id.js +29 -0
  289. package/dist/workflows/authoring/authoring.js +15 -32
  290. package/dist/workflows/exec/dispatch-redaction.js +14 -8
  291. package/dist/workflows/exec/exec-unit.js +7 -28
  292. package/dist/workflows/exec/frozen-judge.js +57 -89
  293. package/dist/workflows/exec/lowering-notices.js +23 -0
  294. package/dist/workflows/exec/native-executor.js +301 -458
  295. package/dist/workflows/exec/param-secrets.js +4 -3
  296. package/dist/workflows/exec/run-workflow.js +26 -32
  297. package/dist/workflows/exec/step-work.js +105 -109
  298. package/dist/workflows/exec/unit-dispatch.js +103 -27
  299. package/dist/workflows/exec/unit-writer.js +3 -3
  300. package/dist/workflows/exec/worktree.js +2 -2
  301. package/dist/workflows/ir/compile.js +86 -72
  302. package/dist/workflows/ir/environment-v4.js +328 -0
  303. package/dist/workflows/ir/freeze-v4.js +122 -0
  304. package/dist/workflows/ir/plan-hash.js +13 -7
  305. package/dist/workflows/ir/schema-v4.js +525 -0
  306. package/dist/workflows/ir/schema.js +25 -284
  307. package/dist/workflows/ir/source-freeze-v4.js +506 -0
  308. package/dist/workflows/parser.js +27 -24
  309. package/dist/workflows/program/schema.js +1 -2
  310. package/dist/workflows/renderer.js +42 -29
  311. package/dist/workflows/resource-limits.js +4 -5
  312. package/dist/workflows/runtime/agent-identity.js +11 -13
  313. package/dist/workflows/runtime/plan-classifier.js +8 -8
  314. package/dist/workflows/runtime/runs.js +27 -43
  315. package/dist/workflows/runtime/workflow-asset-loader.js +45 -205
  316. package/dist/workflows/source-files.js +373 -0
  317. package/dist/workflows/source-ir/compile.js +196 -0
  318. package/dist/workflows/source-ir/github-yaml.js +577 -0
  319. package/dist/workflows/source-ir/ordering.js +38 -0
  320. package/dist/workflows/source-ir/program.js +50 -0
  321. package/dist/workflows/source-ir/result.js +26 -0
  322. package/dist/workflows/source-ir/schema.js +772 -0
  323. package/dist/workflows/source-ir/semantics.js +242 -0
  324. package/dist/workflows/source-ir/uses.js +14 -0
  325. package/docs/README.md +2 -0
  326. package/docs/migration/README.md +3 -1
  327. package/docs/migration/release-notes/0.9.2.md +55 -0
  328. package/docs/migration/release-notes/README.md +5 -0
  329. package/docs/migration/v0.8-to-v0.9.md +76 -1077
  330. package/docs/migration/v0.9.0-troubleshooting.md +104 -516
  331. package/docs/migration/v0.9.1-to-v0.9.2.md +150 -0
  332. package/docs/reference/README.md +1 -0
  333. package/docs/reference/cli.md +230 -98
  334. package/docs/reference/configuration.md +159 -36
  335. package/docs/reference/data-and-telemetry.md +19 -1
  336. package/docs/reference/supported-formats.md +23 -3
  337. package/docs/reference/tasks.md +182 -0
  338. package/docs/reference/workflow-schema.md +91 -40
  339. package/docs/reference/workflows.md +33 -6
  340. package/package.json +10 -6
  341. package/schemas/akm-config.json +372 -224
  342. package/schemas/akm-task.json +324 -80
  343. package/schemas/akm-workflow.json +6 -9
  344. package/dist/core/migration-operation.js +0 -75
  345. package/dist/integrations/agent/model-aliases.js +0 -74
  346. package/dist/tasks/parser.js +0 -380
  347. package/dist/tasks/schema.js +0 -123
  348. package/dist/tasks/validator.js +0 -80
  349. package/dist/workflows/ir/freeze.js +0 -320
  350. package/dist/workflows/runtime/document-cache.js +0 -13
@@ -11,7 +11,7 @@
11
11
  * content. Pull recent feedback (`feedback` events for that ref) and
12
12
  * lesson-lint findings to surface as schema hints.
13
13
  * 3. Build the prompt via {@link buildReflectPrompt}.
14
- * 4. Dispatch the selected named engine via {@link executeRunner}.
14
+ * 4. Prepare, authorize, lower, and dispatch the frozen engine selection.
15
15
  * 5. Parse the agent's stdout into a {@link AgentProposalPayload}.
16
16
  * 6. Insert into the proposal queue via {@link createProposal} with
17
17
  * `source: "reflect"`.
@@ -39,23 +39,32 @@ import { warn } from "../../core/warn.js";
39
39
  import { lookup } from "../../indexer/indexer.js";
40
40
  import { DEFAULT_LLM_TIMEOUT_MS } from "../../integrations/agent/config.js";
41
41
  import { fallbackAnnouncement, NO_ENGINE_MESSAGE_SUFFIX, NO_ENGINE_REMEDY, withEngineFallback, } from "../../integrations/agent/engine-fallback.js";
42
- import { resolveEngine } from "../../integrations/agent/engine-resolution.js";
42
+ import { acquireLoweredExecutionDispatchLease, dispatchLoweredExecutionRequest, disposeLoweredExecutionDispatchLease, lowerResolvedExecutionRequest, lowerResolvedExecutionRequestWithRunner, } from "../../integrations/agent/execution-lowering.js";
43
+ import { prepareInlineExecution, prepareInlineExecutionWithRunner } from "../../integrations/agent/inline-execution.js";
43
44
  import { buildReflectOutputRepairPrompt, buildReflectPrompt, extractDraftConfidence, parseAgentProposalPayload, } from "../../integrations/agent/prompts.js";
44
- import { materializeLlmRunnerConnection, resolveImproveProcessRunner, runnerIsLlm, runnerSupportsFileWrite, } from "../../integrations/agent/runner.js";
45
- import { collectDispatchSensitiveValues, executeRunner } from "../../integrations/agent/runner-dispatch.js";
45
+ import { runnerIsLlm, runnerSupportsFileWrite } from "../../integrations/agent/runner.js";
46
+ import { collectDispatchSensitiveValues } from "../../integrations/agent/runner-dispatch.js";
46
47
  import { LlmCallError } from "../../llm/client.js";
47
48
  import { callStructured } from "../../llm/structured-call.js";
48
49
  import { baseFailureFields, enoentHintMessage, isEnoentFailure } from "../agent/agent-support.js";
49
- import { isProposalSkipped, listProposals, proposalContent, } from "../proposal/repository.js";
50
+ import { isProposalSkipped, listProposalsReadOnly, proposalContent, } from "../proposal/repository.js";
50
51
  import { checkReflectSize, isValidDescription } from "../proposal/validators/proposal-quality-validators.js";
51
52
  import { deriveLessonRef } from "./distill.js";
52
53
  import { runReflectQualityJudge } from "./distill/quality-gate.js";
53
54
  import { findAssetFilePath } from "./eligibility.js";
55
+ import { resolveImproveLlmExecution } from "./execution.js";
54
56
  import { emitProposal } from "./proposal-envelope.js";
55
57
  import { classifyReflectChange } from "./reflect-noise.js";
56
58
  import { createRunContext, resolveRunStashDir } from "./run-context.js";
57
59
  import { MAX_REJECTED_PROPOSALS } from "./shared.js";
58
60
  import { durableImproveRef, improveStateReadRefs } from "./source-identity.js";
61
+ function collectLoweringNotices(target, notices) {
62
+ for (const notice of notices)
63
+ target.set(JSON.stringify(notice), notice);
64
+ }
65
+ function reflectNoticeFields(notices) {
66
+ return notices.size > 0 ? { notices: Object.freeze([...notices.values()]) } : {};
67
+ }
59
68
  const MAX_FEEDBACK_LINES = 10;
60
69
  const MAX_GLOBAL_FEEDBACK_LINES = 20;
61
70
  /**
@@ -64,9 +73,12 @@ const MAX_GLOBAL_FEEDBACK_LINES = 20;
64
73
  * all assets so `akm reflect` can operate in a general "review recent
65
74
  * signals" mode. Best-effort — a missing or empty events stream returns `[]`.
66
75
  */
67
- function readRecentFeedback(ref) {
76
+ function readOnlyEventsContext(ctx) {
77
+ return ctx?.db ? ctx : { ...(ctx ?? {}), readOnly: true };
78
+ }
79
+ function readRecentFeedback(ref, eventsCtx) {
68
80
  try {
69
- const events = readEvents({ type: "feedback", ...(ref ? { ref } : {}) }).events;
81
+ const events = readEvents({ type: "feedback", ...(ref ? { ref } : {}) }, readOnlyEventsContext(eventsCtx)).events;
70
82
  const lines = [];
71
83
  const limit = ref ? MAX_FEEDBACK_LINES : MAX_GLOBAL_FEEDBACK_LINES;
72
84
  for (const event of events.slice(-limit)) {
@@ -126,11 +138,11 @@ const PROTECTED_FRONTMATTER_FIELDS = new Set(["name", "ref", "id", "slug", "type
126
138
  * into the reflect prompt so the agent avoids re-proposing already-refused
127
139
  * content (arXiv:2303.11366).
128
140
  */
129
- function readRejectedProposals(stash, ref) {
141
+ function readRejectedProposals(stash, ref, proposalsCtx) {
130
142
  if (!ref)
131
143
  return [];
132
144
  try {
133
- return listProposals(stash, { ref, status: "rejected", includeArchive: true })
145
+ return listProposalsReadOnly(stash, { ref, status: "rejected", includeArchive: true }, proposalsCtx)
134
146
  .sort((a, b) => new Date(b.updatedAt ?? 0).getTime() - new Date(a.updatedAt ?? 0).getTime())
135
147
  .slice(0, MAX_REJECTED_PROPOSALS)
136
148
  .map((p) => ({
@@ -211,7 +223,7 @@ async function readRelatedLessons(ctx, stash, ref, parsedRef, itemRef) {
211
223
  try {
212
224
  // Match events using the candidate's single durable state key.
213
225
  const distillInvokedKeys = new Set(improveStateReadRefs(ref, itemRef));
214
- const feedbackEvents = readEvents({ type: "distill_invoked" }).events.filter((event) => event.ref !== undefined && distillInvokedKeys.has(event.ref));
226
+ const feedbackEvents = readEvents({ type: "distill_invoked" }, readOnlyEventsContext(ctx.eventsCtx)).events.filter((event) => event.ref !== undefined && distillInvokedKeys.has(event.ref));
215
227
  for (const event of feedbackEvents) {
216
228
  const proposalRef = typeof event.metadata?.proposalRef === "string" ? event.metadata.proposalRef : undefined;
217
229
  if (proposalRef && lenientRefType(proposalRef) === "lesson")
@@ -266,7 +278,7 @@ async function readRelatedLessons(ctx, stash, ref, parsedRef, itemRef) {
266
278
  // ref itself, indicating a human or external system rated the skill.
267
279
  let hasIndependentFeedback = false;
268
280
  try {
269
- const feedbackEventsForSkill = readEvents({ type: "feedback", ref }).events;
281
+ const feedbackEventsForSkill = readEvents({ type: "feedback", ref }, readOnlyEventsContext(ctx.eventsCtx)).events;
270
282
  hasIndependentFeedback = feedbackEventsForSkill.length > 0;
271
283
  }
272
284
  catch {
@@ -616,6 +628,29 @@ const REFLECT_UNSCOPED_JSON_SCHEMA = {
616
628
  };
617
629
  /** Critique prompt injected between prior draft and refinement request (Self-Refine loop). */
618
630
  const REFLECT_CRITIQUE_PROMPT = "Your previous proposal is shown above. Review it critically and provide an improved version that is more specific, actionable, and avoids any issues with the previous attempt. Return only the improved response using the output contract from the original prompt.";
631
+ /**
632
+ * OpenAI-compatible thinking models charge hidden reasoning against
633
+ * `max_tokens` before they emit the visible response. Reflect asks for a
634
+ * machine-readable payload and requests `enableThinking: false`, but local
635
+ * servers do not uniformly honour that flag. Keep visible-content sizing
636
+ * separate from the allowance that lets an uncooperative thinking model reach
637
+ * its JSON/frame envelope.
638
+ *
639
+ * The 2,048-token allowance exceeds the observed 1,798-token peak that
640
+ * previously cut direct reflect responses off mid-envelope. It applies to all
641
+ * bounded direct-LLM calls because a server's thinking behavior is not a
642
+ * reliable capability signal; the post-processor still enforces the original
643
+ * content-size policy.
644
+ */
645
+ const REFLECT_REASONING_TOKEN_HEADROOM = 2_048;
646
+ const REFLECT_RESPONSE_ENVELOPE_CHARS = 500;
647
+ function reflectMaxTokensForOutput(maxOutputChars) {
648
+ if (maxOutputChars === undefined)
649
+ return undefined;
650
+ // Divide by 3 chars/token (conservative — most models are 3.5–4), retain
651
+ // space for the JSON/frame wrapper, then reserve independent reasoning room.
652
+ return Math.ceil((maxOutputChars + REFLECT_RESPONSE_ENVELOPE_CHARS) / 3) + REFLECT_REASONING_TOKEN_HEADROOM;
653
+ }
619
654
  function reflectLlmTelemetry(result) {
620
655
  if (!result.parsed || typeof result.parsed !== "object" || Array.isArray(result.parsed))
621
656
  return undefined;
@@ -743,11 +778,12 @@ function parseDirectReflectOutput(raw, mode, targetRef) {
743
778
  export async function runReflectViaLlm(opts) {
744
779
  const start = Date.now();
745
780
  let repairAttempts = 0;
781
+ const connection = opts.runner.connection;
746
782
  const messages = [{ role: "user", content: opts.prompt ?? "" }];
747
783
  const configuredTimeout = Object.hasOwn(opts, "timeoutMs")
748
784
  ? (opts.timeoutMs ?? null)
749
- : Object.hasOwn(opts.connection, "timeoutMs")
750
- ? (opts.connection.timeoutMs ?? null)
785
+ : Object.hasOwn(opts.runner, "timeoutMs")
786
+ ? (opts.runner.timeoutMs ?? null)
751
787
  : DEFAULT_LLM_TIMEOUT_MS;
752
788
  const deadline = typeof configuredTimeout === "number" ? start + configuredTimeout : undefined;
753
789
  if (opts.priorDraft !== undefined && opts.iteration > 0) {
@@ -756,7 +792,8 @@ export async function runReflectViaLlm(opts) {
756
792
  }
757
793
  const call = async (callMessages, repairTimeoutMs) => callStructured({
758
794
  feature: "reflect_proposal",
759
- config: opts.connection,
795
+ runner: opts.runner,
796
+ ...(opts.lease ? { lease: opts.lease } : {}),
760
797
  messages: callMessages,
761
798
  request: {
762
799
  ...(repairTimeoutMs !== undefined
@@ -772,6 +809,7 @@ export async function runReflectViaLlm(opts) {
772
809
  enableThinking: false,
773
810
  ...(opts.chat ? { chat: opts.chat } : {}),
774
811
  },
812
+ ...(opts.onNotices ? { onNotices: opts.onNotices } : {}),
775
813
  parse: (raw) => raw ?? "",
776
814
  // Unreachable on the ungated path (errors propagate to the catch below).
777
815
  onError: () => "",
@@ -836,6 +874,8 @@ export async function runReflectViaLlm(opts) {
836
874
  };
837
875
  }
838
876
  catch (err) {
877
+ if (err instanceof ConfigError)
878
+ throw err;
839
879
  const reason = opts.signal?.aborted
840
880
  ? "aborted"
841
881
  : err instanceof LlmCallError && err.code === "timeout"
@@ -861,7 +901,7 @@ function failureEnvelope(result, ref, engine, fallbackReason = "non_zero_exit")
861
901
  * byte-identical.
862
902
  */
863
903
  async function finalizeReflectProposal(args) {
864
- const { assetContent, result, options, engineName, config, activeStrategy, runnerSpec, feedback, stash, emitReflectFailed, } = args;
904
+ const { assetContent, result, options, engineName, config, qualityGateEnabled, qualityJudgeRunner, qualityJudgeLease, feedback, stash, emitReflectFailed, onNotices, } = args;
865
905
  let payload = args.payload;
866
906
  const outputTelemetry = reflectLlmTelemetry(result);
867
907
  // 7. Reflect content-preservation rails:
@@ -941,13 +981,14 @@ async function finalizeReflectProposal(args) {
941
981
  }
942
982
  // 7c. Judge the exact sanitized content that can be persisted. Fail closed
943
983
  // on cancellation, transport failure, malformed output, or an invalid score.
944
- const qualityGateEnabled = (activeStrategy?.processes?.reflect?.qualityGate?.enabled ?? false) ||
945
- (activeStrategy?.processes?.distill?.qualityGate?.enabled ?? true);
946
984
  if (qualityGateEnabled) {
947
985
  const judgeResult = await runReflectQualityJudge(config, payload.content, assetContent ?? "", feedback, options.chat, {
948
- ...(runnerIsLlm(runnerSpec) ? { llmConfig: materializeLlmRunnerConnection(runnerSpec) } : {}),
986
+ runnerSelectionFrozen: true,
987
+ ...(qualityJudgeRunner ? { llmRunner: qualityJudgeRunner } : {}),
988
+ ...(qualityJudgeLease ? { lease: qualityJudgeLease } : {}),
949
989
  ...(Object.hasOwn(options, "timeoutMs") ? { timeoutMs: options.timeoutMs } : {}),
950
990
  ...(options.signal ? { signal: options.signal } : {}),
991
+ onNotices,
951
992
  });
952
993
  if (!judgeResult.pass) {
953
994
  appendEvent({
@@ -1160,6 +1201,32 @@ function resolveReflectPayload(args) {
1160
1201
  };
1161
1202
  }
1162
1203
  }
1204
+ function isReflectQualityGateEnabled(activeStrategy) {
1205
+ return ((activeStrategy?.processes?.reflect?.qualityGate?.enabled ?? false) ||
1206
+ (activeStrategy?.processes?.distill?.qualityGate?.enabled ?? true));
1207
+ }
1208
+ /** Resolve the exact judge transport before generation so its credential can join the operation snapshot. */
1209
+ function resolveReflectQualityJudgeRunner(config, runnerSpec, enabled, onNotices) {
1210
+ if (!enabled)
1211
+ return Object.freeze({ enabled: false, runner: undefined });
1212
+ if (runnerIsLlm(runnerSpec))
1213
+ return Object.freeze({ enabled: true, runner: runnerSpec });
1214
+ const resolved = resolveImproveLlmExecution({ config, processName: "reflect_proposal_quality-judge" });
1215
+ if (resolved)
1216
+ onNotices(resolved.notices);
1217
+ return Object.freeze({ enabled: true, runner: resolved?.runner });
1218
+ }
1219
+ /** Acquire through genuine preparation/lowering for all runner kinds, including SDK fallback credentials. */
1220
+ function acquireReflectDispatchLease(runnerSpec, onNotices) {
1221
+ const prepared = prepareInlineExecutionWithRunner({
1222
+ content: "Validate reflect operation transport before dispatch.",
1223
+ runner: runnerSpec,
1224
+ invocationKind: "direct",
1225
+ });
1226
+ const lowered = lowerResolvedExecutionRequestWithRunner(prepared.request, prepared.runner);
1227
+ onNotices(lowered.notices);
1228
+ return acquireLoweredExecutionDispatchLease(lowered);
1229
+ }
1163
1230
  /**
1164
1231
  * Resolve the single named engine for a reflect invocation (standalone --engine
1165
1232
  * / defaults.engine, or the improve strategy's LLM-only process overlay),
@@ -1170,21 +1237,30 @@ function resolveReflectRunner(options) {
1170
1237
  const config = options.config ?? loadConfig();
1171
1238
  const activeStrategy = options.improveProfile ?? config.improve?.strategies?.[config.defaults?.improveStrategy ?? "default"];
1172
1239
  let runnerSpec;
1173
- if (options.runner) {
1174
- runnerSpec = options.runner;
1175
- }
1176
- else if (Object.hasOwn(options, "runner")) {
1177
- throw new ConfigError("Reflect requires an LLM engine for the active improve invocation.", "LLM_NOT_CONFIGURED", "Set defaults.llmEngine or improve.strategies.<name>.processes.reflect.engine.");
1178
- }
1179
- else if (options.engine) {
1180
- runnerSpec = resolveEngine(options.engine, config);
1240
+ let notices = [];
1241
+ if (options.engine) {
1242
+ const prepared = prepareInlineExecution({
1243
+ content: "reflect engine selection",
1244
+ config,
1245
+ invocationKind: "direct",
1246
+ current: { engine: options.engine },
1247
+ });
1248
+ const lowered = lowerResolvedExecutionRequest(prepared.request, prepared.config);
1249
+ runnerSpec = lowered.runner;
1250
+ notices = lowered.notices;
1181
1251
  }
1182
1252
  else if (options.improveProfile) {
1183
- const processRunner = resolveImproveProcessRunner(activeStrategy, "reflect", config);
1184
- if (!processRunner) {
1253
+ const resolved = resolveImproveLlmExecution({
1254
+ config,
1255
+ profile: activeStrategy,
1256
+ process: activeStrategy?.processes?.reflect,
1257
+ processName: "reflect",
1258
+ });
1259
+ if (!resolved) {
1185
1260
  throw new ConfigError("Reflect requires an LLM engine for the active improve strategy.", "LLM_NOT_CONFIGURED", "Set defaults.llmEngine or improve.strategies.<name>.processes.reflect.engine.");
1186
1261
  }
1187
- runnerSpec = processRunner;
1262
+ runnerSpec = resolved.runner;
1263
+ notices = resolved.notices;
1188
1264
  }
1189
1265
  else {
1190
1266
  const { config: engineConfig, fallbackEngineName } = withEngineFallback(config);
@@ -1197,7 +1273,14 @@ function resolveReflectRunner(options) {
1197
1273
  if (!defaultEngine) {
1198
1274
  throw new ConfigError(`reflect ${NO_ENGINE_MESSAGE_SUFFIX} ${NO_ENGINE_REMEDY}`, "INVALID_CONFIG_FILE");
1199
1275
  }
1200
- runnerSpec = resolveEngine(defaultEngine, engineConfig);
1276
+ const prepared = prepareInlineExecution({
1277
+ content: "reflect engine selection",
1278
+ config,
1279
+ invocationKind: "direct",
1280
+ });
1281
+ const lowered = lowerResolvedExecutionRequest(prepared.request, prepared.config);
1282
+ runnerSpec = lowered.runner;
1283
+ notices = lowered.notices;
1201
1284
  }
1202
1285
  if (options.eventSource === "improve" && !runnerIsLlm(runnerSpec)) {
1203
1286
  throw new ConfigError(`Unattended improve requires an LLM engine for reflect; engine "${runnerSpec.engine ?? options.engine ?? "unknown"}" is tool-capable.`, "INVALID_CONFIG_FILE", "Set defaults.llmEngine or improve.strategies.<name>.processes.reflect.engine to an LLM engine.");
@@ -1206,7 +1289,7 @@ function resolveReflectRunner(options) {
1206
1289
  if (!engineName) {
1207
1290
  throw new ConfigError("Reflect requires a named engine.", "INVALID_CONFIG_FILE");
1208
1291
  }
1209
- return { config, activeStrategy, runnerSpec, engineName };
1292
+ return { config, activeStrategy, runnerSpec, engineName, notices };
1210
1293
  }
1211
1294
  /**
1212
1295
  * Resolve the reflect target's parsed ref + current on-disk content: enforce the
@@ -1277,7 +1360,7 @@ async function resolveReflectSource(options, stash, emitReflectFailed) {
1277
1360
  * result + last draft path. Extracted verbatim from `akmReflect`.
1278
1361
  */
1279
1362
  async function runReflectRefineIterations(args) {
1280
- const { options, parsedRef, assetContent, feedback, schemaHints, relatedLessons, rejectedProposals, standardsContext, runnerSpec, agentEnv, draftPathsToCleanup, } = args;
1363
+ const { options, parsedRef, assetContent, feedback, schemaHints, relatedLessons, rejectedProposals, standardsContext, runnerSpec, lease, agentEnv, draftPathsToCleanup, onNotices, } = args;
1281
1364
  const MAX_REFINE_ITERS = 3;
1282
1365
  const maxRefineIters = Math.min(Math.max(1, options.maxRefineIters ?? 1), MAX_REFINE_ITERS);
1283
1366
  // Determine whether this dispatch can honour the file-write contract.
@@ -1324,40 +1407,63 @@ async function runReflectRefineIterations(args) {
1324
1407
  ...(iterDraftPath ? { draftFilePath: iterDraftPath } : {}),
1325
1408
  ...(outputMode ? { outputMode } : {}),
1326
1409
  });
1327
- // Convert char ceiling → token cap for the LLM path: divide by 3 chars/token
1328
- // (conservative — most models are 3.5–4) and add 500-char overhead for the
1329
- // JSON wrapper and frontmatter block that surround the body in the response.
1330
- const maxTokensForLlm = maxOutputChars !== undefined ? Math.ceil((maxOutputChars + 500) / 3) : undefined;
1331
- // Every engine kind crosses the same dispatch seam. Injected spawn/timer
1332
- // functions remain ordinary run options for deterministic tests.
1333
- const runOptions = {
1334
- stdio: "captured",
1335
- parseOutput: "text",
1336
- ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
1337
- ...(Object.keys(agentEnv).length > 0 ? { env: agentEnv } : {}),
1338
- ...(options.runAgentOptions ?? {}),
1339
- };
1340
- const iterResult = await executeRunner(runnerSpec, prompt ?? "", runOptions, {
1341
- llm: async (spec, _prompt, opts) =>
1342
- // LLM HTTP runners cannot honor the file-write contract, so they
1343
- // return structured JSON through stdout.
1344
- runReflectViaLlm({
1410
+ const maxTokensForLlm = reflectMaxTokensForOutput(maxOutputChars);
1411
+ let iterResult;
1412
+ if (runnerIsLlm(runnerSpec)) {
1413
+ // LLM HTTP runners cannot honor the file-write contract, so they return
1414
+ // structured output through stdout. callStructured owns preparation,
1415
+ // lowering, credential materialization, and direct transport dispatch.
1416
+ iterResult = await runReflectViaLlm({
1345
1417
  prompt,
1346
- connection: spec.connection,
1347
- ...(Object.hasOwn(opts, "timeoutMs") ? { timeoutMs: opts.timeoutMs } : {}),
1418
+ runner: runnerSpec,
1419
+ lease,
1420
+ ...(Object.hasOwn(options, "timeoutMs") ? { timeoutMs: options.timeoutMs } : {}),
1348
1421
  ...(options.signal ? { signal: options.signal } : {}),
1349
1422
  priorDraft,
1350
1423
  iteration: iter,
1351
- ...(spec.connection.supportsJsonSchema
1424
+ ...(runnerSpec.connection.supportsJsonSchema
1352
1425
  ? { responseSchema: options.ref ? REFLECT_JSON_SCHEMA : REFLECT_UNSCOPED_JSON_SCHEMA }
1353
1426
  : {}),
1354
- outputMode: spec.connection.supportsJsonSchema ? "json_schema" : "framed_markdown",
1427
+ outputMode: runnerSpec.connection.supportsJsonSchema ? "json_schema" : "framed_markdown",
1355
1428
  ...(options.ref ? { targetRef: options.ref } : {}),
1356
1429
  allowRepair: repairAttempts === 0,
1357
- chat: options.chat,
1430
+ ...(options.chat ? { chat: options.chat } : {}),
1358
1431
  ...(maxTokensForLlm !== undefined ? { maxTokens: maxTokensForLlm } : {}),
1359
- }),
1360
- });
1432
+ onNotices,
1433
+ });
1434
+ }
1435
+ else {
1436
+ const conversationPriorDraft = priorDraft;
1437
+ const hasConversation = conversationPriorDraft !== undefined && iter > 0;
1438
+ const current = {
1439
+ ...(Object.hasOwn(options, "timeoutMs") ? { timeout: options.timeoutMs } : {}),
1440
+ ...(Object.keys(agentEnv).length > 0 ? { environment: agentEnv } : {}),
1441
+ };
1442
+ const prepared = prepareInlineExecutionWithRunner({
1443
+ content: hasConversation ? REFLECT_CRITIQUE_PROMPT : (prompt ?? ""),
1444
+ ...(hasConversation
1445
+ ? {
1446
+ conversation: [
1447
+ { role: "user", content: prompt ?? "" },
1448
+ { role: "assistant", content: conversationPriorDraft },
1449
+ ],
1450
+ }
1451
+ : {}),
1452
+ runner: runnerSpec,
1453
+ invocationKind: "direct",
1454
+ ...(Object.keys(current).length > 0 ? { current } : {}),
1455
+ });
1456
+ const lowered = lowerResolvedExecutionRequestWithRunner(prepared.request, prepared.runner);
1457
+ onNotices(lowered.notices);
1458
+ iterResult = await dispatchLoweredExecutionRequest(lowered, {
1459
+ lease,
1460
+ ...(options.runSdk ? { runSdk: options.runSdk } : {}),
1461
+ runOptions: {
1462
+ ...(options.signal ? { signal: options.signal } : {}),
1463
+ ...(options.runAgentOptions ?? {}),
1464
+ },
1465
+ });
1466
+ }
1361
1467
  const iterTelemetry = reflectLlmTelemetry(iterResult);
1362
1468
  if (iterTelemetry)
1363
1469
  repairAttempts += iterTelemetry.repairAttempts;
@@ -1407,17 +1513,16 @@ function buildReflectRunContext(args) {
1407
1513
  // buildImproveRunContext's proposalsCtx comment in improve.ts).
1408
1514
  proposalsCtx: options.ctx ?? {},
1409
1515
  chat: options.chat,
1410
- getLlmConfig: () => (runnerIsLlm(runnerSpec) ? materializeLlmRunnerConnection(runnerSpec) : null),
1516
+ getLlmRunner: () => (runnerIsLlm(runnerSpec) ? runnerSpec : null),
1411
1517
  sourceRun: `reflect-${Date.now()}`,
1412
1518
  dryRun: false,
1413
1519
  signal: options.signal,
1414
1520
  });
1415
1521
  }
1416
1522
  /**
1417
- * Emit `reflect_invoked` at command entry, then build the `reflect_completed`
1418
- * failure emitter every failure path in `akmReflect` uses (Fix #3 /
1419
- * observability 0.8.0). Extracted verbatim (fn-size decomposition, R31) — see
1420
- * the original inline comments preserved below for the "why".
1523
+ * Build idempotent `reflect_invoked` / `reflect_completed` emitters. Invocation
1524
+ * is delayed until canonical dispatch validates symbolic credentials, while
1525
+ * deterministic pre-dispatch failures still close an invoke/complete pair.
1421
1526
  *
1422
1527
  * Fix #3 (observability 0.8.0): every failure path below MUST emit
1423
1528
  * `reflect_completed` so observers can close the invoke/complete loop. The
@@ -1429,22 +1534,27 @@ function buildReflectRunContext(args) {
1429
1534
  * "ref_mismatch" / "enoent" / "draft_missing" subtypes for cases the agent
1430
1535
  * surface conflates as "parse_error". Sub-reasons land in `subreason`.
1431
1536
  */
1432
- function emitReflectInvokedAndBuildFailureEmitter(options) {
1433
- // Always emit `reflect_invoked` at command entry — observers see the
1434
- // attempt regardless of downstream success/failure.
1435
- appendEvent({
1436
- eventType: "reflect_invoked",
1437
- // Key on item_ref when planning supplied one, otherwise the conceptId.
1438
- ...(options.ref ? { ref: options.itemRef ?? durableImproveRef(options.ref) } : {}),
1439
- metadata: {
1440
- ...(options.task ? { task: options.task } : {}),
1441
- ...(options.engine ? { engine: options.engine } : {}),
1442
- // Attribution tagging: stamp the eligibility lane so reflect_invoked can be
1443
- // sliced by lane downstream. See EligibilitySource.
1444
- ...(options.eligibilitySource ? { eligibilitySource: options.eligibilitySource } : {}),
1445
- },
1446
- }, options.eventsCtx);
1447
- return (reason, subreason, ref, extra) => {
1537
+ function buildReflectEventEmitters(options) {
1538
+ let invoked = false;
1539
+ const emitInvoked = () => {
1540
+ if (invoked)
1541
+ return;
1542
+ appendEvent({
1543
+ eventType: "reflect_invoked",
1544
+ // Key on item_ref when planning supplied one, otherwise the conceptId.
1545
+ ...(options.ref ? { ref: options.itemRef ?? durableImproveRef(options.ref) } : {}),
1546
+ metadata: {
1547
+ ...(options.task ? { task: options.task } : {}),
1548
+ ...(options.engine ? { engine: options.engine } : {}),
1549
+ // Attribution tagging: stamp the eligibility lane so reflect_invoked can be
1550
+ // sliced by lane downstream. See EligibilitySource.
1551
+ ...(options.eligibilitySource ? { eligibilitySource: options.eligibilitySource } : {}),
1552
+ },
1553
+ }, options.eventsCtx);
1554
+ invoked = true;
1555
+ };
1556
+ const emitFailed = (reason, subreason, ref, extra) => {
1557
+ emitInvoked();
1448
1558
  appendEvent({
1449
1559
  eventType: "reflect_completed",
1450
1560
  ...(ref ? { ref } : {}),
@@ -1457,12 +1567,58 @@ function emitReflectInvokedAndBuildFailureEmitter(options) {
1457
1567
  },
1458
1568
  }, options.eventsCtx);
1459
1569
  };
1570
+ return { emitInvoked, emitFailed };
1571
+ }
1572
+ function cleanupReflectDrafts(paths) {
1573
+ for (const draftPath of paths) {
1574
+ try {
1575
+ if (fs.existsSync(draftPath))
1576
+ fs.unlinkSync(draftPath);
1577
+ }
1578
+ catch {
1579
+ // Draft cleanup is best-effort; the proposal result remains authoritative.
1580
+ }
1581
+ }
1582
+ }
1583
+ function validateReflectPayloadRef(args) {
1584
+ const { payload, result, options, engineName, emitReflectFailed, executionNotices } = args;
1585
+ if (!options.ref)
1586
+ return undefined;
1587
+ try {
1588
+ const expectedParsed = parseRefInput(options.ref);
1589
+ const actualParsed = parseRefInput(payload.ref);
1590
+ if (expectedParsed.type === actualParsed.type && expectedParsed.name === actualParsed.name)
1591
+ return undefined;
1592
+ emitReflectFailed("parse_error", "ref_mismatch", options.ref, {
1593
+ expectedRef: options.ref,
1594
+ actualRef: payload.ref,
1595
+ ...(result.exitCode !== null ? { exitCode: result.exitCode } : {}),
1596
+ ...(reflectLlmTelemetry(result) ?? {}),
1597
+ });
1598
+ return {
1599
+ schemaVersion: 2,
1600
+ ok: false,
1601
+ reason: "parse_error",
1602
+ error: `Agent retargeted proposal: expected ref "${options.ref}" but got "${payload.ref}". Proposal rejected to prevent silent ref hallucination.`,
1603
+ ref: options.ref,
1604
+ engine: engineName,
1605
+ exitCode: result.exitCode,
1606
+ stdout: result.stdout,
1607
+ ...(result.stderr ? { stderr: result.stderr } : {}),
1608
+ ...reflectNoticeFields(executionNotices),
1609
+ };
1610
+ }
1611
+ catch {
1612
+ // Malformed refs are rejected downstream by proposal validation.
1613
+ return undefined;
1614
+ }
1460
1615
  }
1461
1616
  export async function akmReflect(options = {}) {
1462
1617
  const stash = resolveRunStashDir(options.stashDir);
1463
- // 1. Emit reflect_invoked + build the reflect_completed failure emitter
1464
- // every failure path below uses.
1465
- const emitReflectFailed = emitReflectInvokedAndBuildFailureEmitter(options);
1618
+ // Build lazy event emitters. The invocation row is committed only after the
1619
+ // canonical dispatch has validated symbolic credentials; deterministic
1620
+ // pre-dispatch skips still emit it through emitReflectFailed.
1621
+ const { emitInvoked: emitReflectInvoked, emitFailed: emitReflectFailed } = buildReflectEventEmitters(options);
1466
1622
  // 2. Resolve target asset content (if a ref is supplied).
1467
1623
  const sourceResolved = await resolveReflectSource(options, stash, emitReflectFailed);
1468
1624
  if ("failure" in sourceResolved)
@@ -1471,161 +1627,168 @@ export async function akmReflect(options = {}) {
1471
1627
  // 3. Resolve exactly one named engine. Standalone reflect uses --engine or
1472
1628
  // defaults.engine; improve resolves its LLM-only strategy/process overlay.
1473
1629
  // An incompatible explicit engine is an error and never falls through.
1474
- const { config, activeStrategy, runnerSpec, engineName } = resolveReflectRunner(options);
1475
- // WI-9.10: RunContext, built only once config/runnerSpec exist so engine
1476
- // resolution's existing error-priority ordering is undisturbed (see
1477
- // buildReflectRunContext's docblock). D6: assetCtx is a fresh,
1478
- // per-invocation memo readRelatedLessons below is its genuine
1479
- // content-read consumer.
1480
- const ctx = buildReflectRunContext({ options, stash, config, runnerSpec });
1481
- const assetCtx = ctx.withFreshAssetMemo();
1482
- // 4. Build the shared prompt inputs — feedback, hints, lessons, rejected
1483
- // proposals. These are stable across refinement iterations; only the
1484
- // `priorDraft` field changes per-iteration (R-1 / #372).
1485
- const feedback = readRecentFeedback(options.ref ? (options.itemRef ?? durableImproveRef(options.ref)) : undefined);
1486
- const schemaHints = buildSchemaHints(parsedRef?.type ?? "", assetContent);
1487
- const relatedLessons = options.ref && parsedRef ? await readRelatedLessons(assetCtx, stash, options.ref, parsedRef, options.itemRef) : [];
1488
- // Reflexion-style verbal-RL: inject rejected proposals so the agent avoids
1489
- // reproducing proposals that have already been reviewed and refused.
1490
- const rejectedProposals = readRejectedProposals(stash, options.ref);
1491
- // Standards "rulebook" for this target — stash convention/meta facts; empty
1492
- // when none fire.
1493
- const standardsContext = resolveStandardsContext(options.ref, stash);
1494
- // 5. Spawn the agent — with the optional Self-Refine loop (R-1 / #372),
1495
- // extracted to {@link runReflectRefineIterations}.
1496
- const agentEnv = options.eventSource === "improve" ? { AKM_EVENT_SOURCE: "improve" } : {};
1497
- const sensitiveValues = collectDispatchSensitiveValues(runnerSpec, {
1498
- ...(Object.keys(agentEnv).length > 0 ? { env: agentEnv } : {}),
1499
- ...(options.runAgentOptions ?? {}),
1500
- });
1501
- // Track every draft file path we synthesize so cleanup can remove them on
1502
- // every return path (success and failure). Mirrors propose's unlink pattern
1503
- // in `src/commands/proposal/propose.ts` but generalised to N refinement
1504
- // iterations. Always called via {@link cleanupDrafts} below.
1505
- const draftPathsToCleanup = [];
1506
- // Best-effort unlink: tolerate already-deleted files (we may have unlinked
1507
- // an intermediate iteration's draft) and unwritable paths. Never throws —
1508
- // the proposal result is the source of truth for the caller.
1509
- const cleanupDrafts = () => {
1510
- for (const p of draftPathsToCleanup) {
1511
- try {
1512
- if (fs.existsSync(p))
1513
- fs.unlinkSync(p);
1514
- }
1515
- catch {
1516
- // Swallow — cleanup is best-effort.
1517
- }
1518
- }
1519
- };
1520
- // `result` / `lastDraftPath` / `payload` are populated inside the try. Hoisted
1521
- // here so the post-try sections (R-3 ref guard, sanitizer, quality gate,
1522
- // createProposal) can use them after the drafts have been cleaned up.
1523
- let result = {};
1524
- let lastDraftPath;
1525
- let payload;
1630
+ const { config, activeStrategy, runnerSpec, engineName, notices: resolutionNotices } = resolveReflectRunner(options);
1631
+ const executionNotices = new Map();
1632
+ collectLoweringNotices(executionNotices, resolutionNotices);
1633
+ const collectExecutionNotices = (notices) => collectLoweringNotices(executionNotices, notices);
1634
+ const qualityJudgeSelection = resolveReflectQualityJudgeRunner(config, runnerSpec, isReflectQualityGateEnabled(activeStrategy), collectExecutionNotices);
1635
+ if (qualityJudgeSelection.enabled && !qualityJudgeSelection.runner) {
1636
+ return {
1637
+ schemaVersion: 2,
1638
+ ok: false,
1639
+ reason: "parse_error",
1640
+ error: 'Reflect proposal quality gate rejected: score=-1, reason="no LLM configured — cannot judge, failing closed"',
1641
+ ...(options.ref ? { ref: options.ref } : {}),
1642
+ engine: engineName,
1643
+ exitCode: null,
1644
+ ...reflectNoticeFields(executionNotices),
1645
+ };
1646
+ }
1647
+ const qualityJudgeRunner = qualityJudgeSelection.runner;
1648
+ let generationLease;
1649
+ let qualityJudgeLease;
1526
1650
  try {
1527
- const iterated = await runReflectRefineIterations({
1528
- options,
1529
- parsedRef,
1530
- assetContent,
1531
- feedback,
1532
- schemaHints,
1533
- relatedLessons,
1534
- rejectedProposals,
1535
- standardsContext,
1536
- runnerSpec,
1537
- agentEnv,
1538
- draftPathsToCleanup,
1651
+ generationLease = acquireReflectDispatchLease(runnerSpec, collectExecutionNotices);
1652
+ qualityJudgeLease =
1653
+ qualityJudgeRunner === runnerSpec
1654
+ ? generationLease
1655
+ : qualityJudgeRunner
1656
+ ? acquireReflectDispatchLease(qualityJudgeRunner, collectExecutionNotices)
1657
+ : undefined;
1658
+ // WI-9.10: RunContext, built only once config/runnerSpec exist so engine
1659
+ // resolution's existing error-priority ordering is undisturbed (see
1660
+ // buildReflectRunContext's docblock). D6: assetCtx is a fresh,
1661
+ // per-invocation memo — readRelatedLessons below is its genuine
1662
+ // content-read consumer.
1663
+ const ctx = buildReflectRunContext({ options, stash, config, runnerSpec });
1664
+ const assetCtx = ctx.withFreshAssetMemo();
1665
+ // 4. Build the shared prompt inputs — feedback, hints, lessons, rejected
1666
+ // proposals. These are stable across refinement iterations; only the
1667
+ // `priorDraft` field changes per-iteration (R-1 / #372).
1668
+ const feedback = readRecentFeedback(options.ref ? (options.itemRef ?? durableImproveRef(options.ref)) : undefined, options.eventsCtx);
1669
+ const schemaHints = buildSchemaHints(parsedRef?.type ?? "", assetContent);
1670
+ const relatedLessons = options.ref && parsedRef
1671
+ ? await readRelatedLessons(assetCtx, stash, options.ref, parsedRef, options.itemRef)
1672
+ : [];
1673
+ // Reflexion-style verbal-RL: inject rejected proposals so the agent avoids
1674
+ // reproducing proposals that have already been reviewed and refused.
1675
+ const rejectedProposals = readRejectedProposals(stash, options.ref, options.ctx);
1676
+ // Standards "rulebook" for this target — stash convention/meta facts; empty
1677
+ // when none fire.
1678
+ const standardsContext = resolveStandardsContext(options.ref, stash);
1679
+ // 5. Spawn the agent — with the optional Self-Refine loop (R-1 / #372),
1680
+ // extracted to {@link runReflectRefineIterations}.
1681
+ const agentEnv = options.eventSource === "improve" ? { AKM_EVENT_SOURCE: "improve" } : {};
1682
+ const sensitiveValues = collectDispatchSensitiveValues(runnerSpec, {
1683
+ ...(Object.keys(agentEnv).length > 0 ? { env: agentEnv } : {}),
1684
+ ...(options.runAgentOptions ?? {}),
1539
1685
  });
1540
- result = iterated.result;
1541
- lastDraftPath = iterated.lastDraftPath;
1542
- const finalResult = result;
1543
- if (!finalResult.ok) {
1544
- // B3: ENOENT / not-found gives an actionable hint.
1545
- if (isEnoentFailure(finalResult)) {
1546
- emitReflectFailed("spawn_failed", "enoent", options.ref, {
1547
- ...(finalResult.exitCode !== undefined ? { exitCode: finalResult.exitCode } : {}),
1686
+ const draftPathsToCleanup = [];
1687
+ // `result` / `lastDraftPath` / `payload` are populated inside the try. Hoisted
1688
+ // here so the post-try sections (R-3 ref guard, sanitizer, quality gate,
1689
+ // createProposal) can use them after the drafts have been cleaned up.
1690
+ let result = {};
1691
+ let lastDraftPath;
1692
+ let payload;
1693
+ try {
1694
+ const iterated = await runReflectRefineIterations({
1695
+ options,
1696
+ parsedRef,
1697
+ assetContent,
1698
+ feedback,
1699
+ schemaHints,
1700
+ relatedLessons,
1701
+ rejectedProposals,
1702
+ standardsContext,
1703
+ runnerSpec,
1704
+ lease: generationLease,
1705
+ agentEnv,
1706
+ draftPathsToCleanup,
1707
+ onNotices: collectExecutionNotices,
1708
+ });
1709
+ emitReflectInvoked();
1710
+ result = iterated.result;
1711
+ lastDraftPath = iterated.lastDraftPath;
1712
+ const finalResult = result;
1713
+ if (!finalResult.ok) {
1714
+ // B3: ENOENT / not-found gives an actionable hint.
1715
+ if (isEnoentFailure(finalResult)) {
1716
+ emitReflectFailed("spawn_failed", "enoent", options.ref, {
1717
+ ...(finalResult.exitCode !== undefined ? { exitCode: finalResult.exitCode } : {}),
1718
+ });
1719
+ return {
1720
+ ...failureEnvelope(finalResult, options.ref, engineName),
1721
+ error: enoentHintMessage(runnerIsLlm(runnerSpec) ? engineName : runnerSpec.profile.bin),
1722
+ ...reflectNoticeFields(executionNotices),
1723
+ };
1724
+ }
1725
+ const envelope = failureEnvelope(finalResult, options.ref, engineName);
1726
+ emitReflectFailed(envelope.reason, envelope.reason === "parse_error" ? "parse_error" : "agent_crash", options.ref, {
1727
+ ...(envelope.exitCode !== null ? { exitCode: envelope.exitCode } : {}),
1728
+ ...(reflectLlmTelemetry(finalResult) ?? {}),
1548
1729
  });
1549
- return {
1550
- ...failureEnvelope(finalResult, options.ref, engineName),
1551
- error: enoentHintMessage(runnerIsLlm(runnerSpec) ? engineName : runnerSpec.profile.bin),
1552
- };
1730
+ return { ...envelope, ...reflectNoticeFields(executionNotices) };
1553
1731
  }
1554
- const envelope = failureEnvelope(finalResult, options.ref, engineName);
1555
- emitReflectFailed(envelope.reason, envelope.reason === "parse_error" ? "parse_error" : "agent_crash", options.ref, {
1556
- ...(envelope.exitCode !== null ? { exitCode: envelope.exitCode } : {}),
1557
- ...(reflectLlmTelemetry(finalResult) ?? {}),
1732
+ // Re-alias to `result` for the downstream code that references it.
1733
+ result = finalResult;
1734
+ const resolved = resolveReflectPayload({
1735
+ result,
1736
+ lastDraftPath,
1737
+ sensitiveValues,
1738
+ options,
1739
+ engineName,
1740
+ emitReflectFailed,
1558
1741
  });
1559
- return envelope;
1742
+ if ("failure" in resolved) {
1743
+ return { ...resolved.failure, ...reflectNoticeFields(executionNotices) };
1744
+ }
1745
+ payload = resolved.payload;
1746
+ }
1747
+ catch (error) {
1748
+ if (!(error instanceof ConfigError))
1749
+ emitReflectInvoked();
1750
+ throw error;
1560
1751
  }
1561
- // Re-alias to `result` for the downstream code that references it.
1562
- result = finalResult;
1563
- const resolved = resolveReflectPayload({
1752
+ finally {
1753
+ // Always remove tmp draft files — success, failure, or exception. Returns
1754
+ // inside the try above trigger this block before the function exits. Code
1755
+ // after this point uses the already-loaded `payload` and never touches the
1756
+ // draft paths.
1757
+ cleanupReflectDrafts(draftPathsToCleanup);
1758
+ }
1759
+ payload = { ...payload, content: redactSensitiveText(payload.content, sensitiveValues) };
1760
+ const refFailure = validateReflectPayloadRef({
1761
+ payload,
1762
+ result,
1763
+ options,
1764
+ engineName,
1765
+ emitReflectFailed,
1766
+ executionNotices,
1767
+ });
1768
+ if (refFailure)
1769
+ return refFailure;
1770
+ const finalized = await finalizeReflectProposal({
1771
+ payload,
1772
+ assetContent,
1564
1773
  result,
1565
- lastDraftPath,
1566
- sensitiveValues,
1567
1774
  options,
1568
1775
  engineName,
1776
+ config,
1777
+ qualityGateEnabled: qualityJudgeSelection.enabled,
1778
+ qualityJudgeRunner,
1779
+ qualityJudgeLease,
1780
+ feedback,
1781
+ stash,
1569
1782
  emitReflectFailed,
1783
+ onNotices: collectExecutionNotices,
1570
1784
  });
1571
- if ("failure" in resolved)
1572
- return resolved.failure;
1573
- payload = resolved.payload;
1785
+ return { ...finalized, ...reflectNoticeFields(executionNotices) };
1574
1786
  }
1575
1787
  finally {
1576
- // Always remove tmp draft files — success, failure, or exception. Returns
1577
- // inside the try above trigger this block before the function exits. Code
1578
- // after this point uses the already-loaded `payload` and never touches the
1579
- // draft paths.
1580
- cleanupDrafts();
1581
- }
1582
- payload = { ...payload, content: redactSensitiveText(payload.content, sensitiveValues) };
1583
- // 6b. Validate payload.ref === options.ref (R-3 / #366).
1584
- // A hallucinating agent can silently retarget proposals to a different ref.
1585
- // Parse both current refs so an optional bundle prefix does not cause a false
1586
- // positive, then reject genuine concept mismatches.
1587
- // References: CRITIC (arXiv:2305.11738), CoVe (arXiv:2309.11495).
1588
- if (options.ref) {
1589
- try {
1590
- const expectedParsed = parseRefInput(options.ref);
1591
- const actualParsed = parseRefInput(payload.ref);
1592
- // Compare type + name (drop origin — agent may omit origin prefix).
1593
- if (expectedParsed.type !== actualParsed.type || expectedParsed.name !== actualParsed.name) {
1594
- emitReflectFailed("parse_error", "ref_mismatch", options.ref, {
1595
- expectedRef: options.ref,
1596
- actualRef: payload.ref,
1597
- ...(result.exitCode !== null ? { exitCode: result.exitCode } : {}),
1598
- ...(reflectLlmTelemetry(result) ?? {}),
1599
- });
1600
- return {
1601
- schemaVersion: 2,
1602
- ok: false,
1603
- reason: "parse_error",
1604
- error: `Agent retargeted proposal: expected ref "${options.ref}" but got "${payload.ref}". Proposal rejected to prevent silent ref hallucination.`,
1605
- ref: options.ref,
1606
- engine: engineName,
1607
- exitCode: result.exitCode,
1608
- stdout: result.stdout,
1609
- ...(result.stderr ? { stderr: result.stderr } : {}),
1610
- };
1611
- }
1612
- }
1613
- catch {
1614
- // parseRefInput failure means the agent returned a malformed ref — already
1615
- // caught downstream by createProposal; allow it to surface naturally.
1788
+ if (qualityJudgeLease && qualityJudgeLease !== generationLease) {
1789
+ disposeLoweredExecutionDispatchLease(qualityJudgeLease);
1616
1790
  }
1791
+ if (generationLease)
1792
+ disposeLoweredExecutionDispatchLease(generationLease);
1617
1793
  }
1618
- return finalizeReflectProposal({
1619
- payload,
1620
- assetContent,
1621
- result,
1622
- options,
1623
- engineName,
1624
- config,
1625
- activeStrategy,
1626
- runnerSpec,
1627
- feedback,
1628
- stash,
1629
- emitReflectFailed,
1630
- });
1631
1794
  }