akm-cli 0.9.0-rc.0 → 0.9.0-rc.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 (262) hide show
  1. package/CHANGELOG.md +339 -2
  2. package/SECURITY.md +23 -24
  3. package/dist/assets/help/help-improve.md +10 -10
  4. package/dist/assets/hints/cli-hints-full.md +44 -10
  5. package/dist/assets/hints/cli-hints-short.md +6 -2
  6. package/dist/assets/{profiles → improve-strategies}/default.json +1 -0
  7. package/dist/assets/{profiles → improve-strategies}/graph-refresh.json +1 -1
  8. package/dist/assets/{profiles → improve-strategies}/proactive-maintenance.json +2 -3
  9. package/dist/assets/{profiles → improve-strategies}/reflect-distill.json +3 -4
  10. package/dist/assets/prompts/workflow-unit-preamble.md +26 -0
  11. package/dist/assets/stash-skeleton/README.md +28 -0
  12. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +6 -0
  13. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +6 -0
  14. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +12 -1
  15. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +11 -1
  16. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +6 -0
  17. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +9 -0
  18. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +7 -0
  19. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +7 -0
  20. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +6 -0
  21. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +98 -0
  22. package/dist/assets/stash-skeleton/facts/conventions/domains.md +63 -0
  23. package/dist/assets/stash-skeleton/facts/conventions/organization.md +127 -0
  24. package/dist/assets/tasks/core/backup.yml +1 -0
  25. package/dist/assets/tasks/core/extract.yml +1 -0
  26. package/dist/assets/tasks/core/improve.yml +1 -0
  27. package/dist/assets/tasks/core/index-refresh.yml +1 -0
  28. package/dist/assets/tasks/core/sync.yml +1 -0
  29. package/dist/assets/tasks/core/version-check.yml +1 -0
  30. package/dist/assets/tasks/graph-refresh-weekly.yml +4 -4
  31. package/dist/assets/templates/html/health.html +5 -1
  32. package/dist/cli/config-migrate.js +31 -138
  33. package/dist/cli/config-validate.js +10 -8
  34. package/dist/cli.js +48 -14
  35. package/dist/commands/agent/agent-dispatch.js +17 -16
  36. package/dist/commands/agent/agent-support.js +0 -24
  37. package/dist/commands/agent/contribute-cli.js +5 -15
  38. package/dist/commands/backup-cli.js +54 -0
  39. package/dist/commands/config-cli.js +45 -159
  40. package/dist/commands/env/env-binding.js +95 -0
  41. package/dist/commands/env/env-cli.js +8 -65
  42. package/dist/commands/env/secret.js +8 -5
  43. package/dist/commands/health/checks.js +130 -83
  44. package/dist/commands/health/html-report.js +4 -0
  45. package/dist/commands/health/improve-metrics.js +30 -32
  46. package/dist/commands/health/llm-usage.js +19 -19
  47. package/dist/commands/health/md-report.js +4 -0
  48. package/dist/commands/health/metrics.js +2 -1
  49. package/dist/commands/health/surfaces.js +5 -4
  50. package/dist/commands/health.js +1 -1
  51. package/dist/commands/improve/consolidate/chunking.js +2 -2
  52. package/dist/commands/improve/consolidate.js +28 -25
  53. package/dist/commands/improve/distill/promote-memory.js +5 -12
  54. package/dist/commands/improve/distill/quality-gate.js +5 -7
  55. package/dist/commands/improve/distill.js +16 -5
  56. package/dist/commands/improve/eligibility.js +22 -12
  57. package/dist/commands/improve/extract-cli.js +47 -19
  58. package/dist/commands/improve/extract.js +110 -62
  59. package/dist/commands/improve/improve-cli.js +38 -16
  60. package/dist/commands/improve/improve-result-file.js +30 -24
  61. package/dist/commands/improve/improve-strategies.js +137 -0
  62. package/dist/commands/improve/improve.js +60 -30
  63. package/dist/commands/improve/locks.js +66 -45
  64. package/dist/commands/improve/loop-stages.js +75 -33
  65. package/dist/commands/improve/memory/memory-belief.js +79 -7
  66. package/dist/commands/improve/memory/memory-contradiction-detect.js +12 -4
  67. package/dist/commands/improve/preparation.js +71 -73
  68. package/dist/commands/improve/procedural.js +3 -2
  69. package/dist/commands/improve/recombine.js +2 -1
  70. package/dist/commands/improve/reflect.js +119 -214
  71. package/dist/commands/improve/shared.js +11 -5
  72. package/dist/commands/lint/base-linter.js +152 -42
  73. package/dist/commands/mv-cli.js +809 -0
  74. package/dist/commands/proposal/proposal-cli.js +18 -8
  75. package/dist/commands/proposal/propose.js +64 -69
  76. package/dist/commands/read/knowledge.js +436 -4
  77. package/dist/commands/read/remember-cli.js +39 -2
  78. package/dist/commands/read/search-cli.js +6 -1
  79. package/dist/commands/registry-cli.js +29 -14
  80. package/dist/commands/remember.js +2 -0
  81. package/dist/commands/sources/init.js +13 -14
  82. package/dist/commands/sources/migration-help.js +7 -4
  83. package/dist/commands/sources/schema-repair.js +2 -4
  84. package/dist/commands/sources/source-add.js +62 -73
  85. package/dist/commands/sources/source-manage.js +50 -46
  86. package/dist/commands/sources/stash-cli.js +41 -4
  87. package/dist/commands/tasks/default-tasks.js +12 -12
  88. package/dist/commands/tasks/tasks-cli.js +7 -3
  89. package/dist/commands/tasks/tasks.js +113 -18
  90. package/dist/commands/wiki-cli.js +9 -10
  91. package/dist/commands/workflow-cli.js +276 -12
  92. package/dist/core/asset/asset-spec.js +58 -1
  93. package/dist/core/asset/frontmatter.js +12 -2
  94. package/dist/core/common.js +5 -3
  95. package/dist/core/config/config-io.js +28 -17
  96. package/dist/core/config/config-schema.js +379 -66
  97. package/dist/core/config/config-types.js +3 -3
  98. package/dist/core/config/config-version.js +29 -0
  99. package/dist/core/config/config-walker.js +98 -27
  100. package/dist/core/config/config.js +132 -266
  101. package/dist/core/config/deep-merge.js +41 -0
  102. package/dist/core/config/engine-semantics.js +32 -0
  103. package/dist/core/errors.js +2 -2
  104. package/dist/core/extra-params.js +61 -0
  105. package/dist/core/file-lock.js +201 -56
  106. package/dist/core/improve-result.js +178 -0
  107. package/dist/core/json-schema.js +142 -0
  108. package/dist/core/maintenance-barrier.js +119 -0
  109. package/dist/core/migration-backup.js +416 -0
  110. package/dist/core/paths.js +3 -0
  111. package/dist/core/redaction.js +358 -0
  112. package/dist/core/state/migrations.js +17 -2
  113. package/dist/core/state-db.js +44 -1
  114. package/dist/indexer/db/db.js +118 -2
  115. package/dist/indexer/graph/graph-extraction.js +28 -16
  116. package/dist/indexer/index-writer-lock.js +31 -24
  117. package/dist/indexer/index-written-assets.js +15 -6
  118. package/dist/indexer/indexer.js +47 -2
  119. package/dist/indexer/passes/memory-inference.js +10 -6
  120. package/dist/indexer/passes/metadata.js +250 -0
  121. package/dist/indexer/search/db-search.js +111 -44
  122. package/dist/indexer/search/fts-query.js +41 -0
  123. package/dist/indexer/search/ranking-contributors.js +48 -0
  124. package/dist/indexer/search/ranking.js +36 -23
  125. package/dist/indexer/search/search-fields.js +11 -1
  126. package/dist/indexer/walk/matchers.js +39 -0
  127. package/dist/integrations/agent/builder-shared.js +7 -0
  128. package/dist/integrations/agent/builders.js +5 -50
  129. package/dist/integrations/agent/config.js +3 -143
  130. package/dist/integrations/agent/detect.js +17 -2
  131. package/dist/integrations/agent/engine-resolution.js +202 -0
  132. package/dist/integrations/agent/index.js +1 -2
  133. package/dist/integrations/agent/model-aliases.js +16 -2
  134. package/dist/integrations/agent/profiles.js +36 -62
  135. package/dist/integrations/agent/runner-dispatch.js +91 -4
  136. package/dist/integrations/agent/runner.js +76 -207
  137. package/dist/integrations/agent/spawn.js +141 -20
  138. package/dist/integrations/harnesses/aider/agent-builder.js +112 -0
  139. package/dist/integrations/harnesses/aider/index.js +57 -0
  140. package/dist/integrations/harnesses/aider/result-extractor.js +53 -0
  141. package/dist/integrations/harnesses/amazonq/agent-builder.js +152 -0
  142. package/dist/integrations/harnesses/amazonq/index.js +58 -0
  143. package/dist/integrations/harnesses/amazonq/result-extractor.js +48 -0
  144. package/dist/integrations/harnesses/claude/agent-builder.js +46 -8
  145. package/dist/integrations/harnesses/claude/index.js +25 -25
  146. package/dist/integrations/harnesses/claude/result-extractor.js +52 -0
  147. package/dist/integrations/harnesses/codex/agent-builder.js +136 -0
  148. package/dist/integrations/harnesses/codex/index.js +62 -0
  149. package/dist/integrations/harnesses/codex/result-extractor.js +73 -0
  150. package/dist/integrations/harnesses/copilot/agent-builder.js +121 -0
  151. package/dist/integrations/harnesses/copilot/index.js +59 -0
  152. package/dist/integrations/harnesses/copilot/result-extractor.js +151 -0
  153. package/dist/integrations/harnesses/gemini/agent-builder.js +120 -0
  154. package/dist/integrations/harnesses/gemini/index.js +59 -0
  155. package/dist/integrations/harnesses/gemini/result-extractor.js +121 -0
  156. package/dist/integrations/harnesses/index.js +27 -28
  157. package/dist/integrations/harnesses/opencode/agent-builder.js +2 -3
  158. package/dist/integrations/harnesses/opencode/index.js +15 -22
  159. package/dist/integrations/harnesses/opencode-sdk/harness.js +60 -0
  160. package/dist/integrations/harnesses/opencode-sdk/index.js +8 -32
  161. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +681 -108
  162. package/dist/integrations/harnesses/openhands/agent-builder.js +125 -0
  163. package/dist/integrations/harnesses/openhands/index.js +57 -0
  164. package/dist/integrations/harnesses/openhands/result-extractor.js +103 -0
  165. package/dist/integrations/harnesses/pi/agent-builder.js +103 -0
  166. package/dist/integrations/harnesses/pi/index.js +57 -0
  167. package/dist/integrations/harnesses/pi/result-extractor.js +135 -0
  168. package/dist/integrations/harnesses/types.js +8 -32
  169. package/dist/integrations/lockfile.js +32 -21
  170. package/dist/integrations/session-logs/index.js +24 -11
  171. package/dist/llm/client.js +48 -14
  172. package/dist/llm/feature-gate.js +15 -47
  173. package/dist/llm/graph-extract.js +1 -1
  174. package/dist/llm/index-passes.js +8 -42
  175. package/dist/llm/memory-infer-impl.js +1 -1
  176. package/dist/llm/usage-persist.js +4 -0
  177. package/dist/llm/usage-telemetry.js +35 -5
  178. package/dist/output/renderers.js +3 -2
  179. package/dist/output/shapes/helpers.js +2 -1
  180. package/dist/output/shapes/passthrough.js +6 -0
  181. package/dist/output/text/helpers.js +215 -2
  182. package/dist/output/text/workflow.js +3 -1
  183. package/dist/schemas/akm-config.json +16638 -0
  184. package/dist/schemas/akm-task.json +87 -0
  185. package/dist/schemas/akm-workflow.json +372 -0
  186. package/dist/scripts/migrate-storage.js +10944 -8801
  187. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9247 -350
  188. package/dist/setup/detected-engines.js +142 -0
  189. package/dist/setup/engine-config.js +89 -0
  190. package/dist/setup/setup.js +236 -132
  191. package/dist/setup/steps/connection.js +61 -32
  192. package/dist/setup/steps/platforms.js +4 -4
  193. package/dist/setup/steps.js +3 -2
  194. package/dist/storage/database.js +13 -1
  195. package/dist/storage/engines/sqlite-migrations.js +1 -0
  196. package/dist/storage/repositories/improve-runs-repository.js +5 -5
  197. package/dist/storage/repositories/task-history-repository.js +78 -0
  198. package/dist/storage/repositories/workflow-runs-repository.js +190 -1
  199. package/dist/tasks/parser.js +138 -52
  200. package/dist/tasks/runner.js +71 -75
  201. package/dist/tasks/schema.js +1 -1
  202. package/dist/tasks/validator.js +11 -6
  203. package/dist/text-import-hook.mjs +1 -1
  204. package/dist/wiki/wiki.js +9 -8
  205. package/dist/workflows/authoring/authoring.js +123 -10
  206. package/dist/workflows/authoring/workflow-program-template.yaml +31 -0
  207. package/dist/workflows/cli.js +4 -0
  208. package/dist/workflows/concurrency-policy.js +15 -0
  209. package/dist/workflows/db.js +200 -13
  210. package/dist/workflows/exec/brief.js +478 -0
  211. package/dist/workflows/exec/frozen-judge.js +47 -0
  212. package/dist/workflows/exec/native-executor.js +1034 -0
  213. package/dist/workflows/exec/param-secrets.js +115 -0
  214. package/dist/workflows/exec/report.js +1355 -0
  215. package/dist/workflows/exec/run-workflow.js +609 -0
  216. package/dist/workflows/exec/scheduler.js +71 -0
  217. package/dist/workflows/exec/step-work.js +1212 -0
  218. package/dist/workflows/exec/unit-writer.js +23 -0
  219. package/dist/workflows/exec/watch.js +116 -0
  220. package/dist/workflows/exec/worktree.js +171 -0
  221. package/dist/workflows/ir/compile.js +375 -0
  222. package/dist/workflows/ir/freeze.js +243 -0
  223. package/dist/workflows/ir/params.js +54 -0
  224. package/dist/workflows/ir/plan-hash.js +68 -0
  225. package/dist/workflows/ir/schema.js +545 -0
  226. package/dist/workflows/parser.js +10 -1
  227. package/dist/workflows/program/expressions.js +369 -0
  228. package/dist/workflows/program/parser.js +869 -0
  229. package/dist/workflows/program/project.js +104 -0
  230. package/dist/workflows/program/schema.js +54 -0
  231. package/dist/workflows/renderer.js +82 -5
  232. package/dist/workflows/resource-limits.js +20 -0
  233. package/dist/workflows/runtime/agent-identity.js +59 -14
  234. package/dist/workflows/runtime/plan-classifier.js +187 -0
  235. package/dist/workflows/runtime/runs.js +246 -69
  236. package/dist/workflows/runtime/unit-checkin.js +45 -0
  237. package/dist/workflows/runtime/workflow-asset-loader.js +42 -1
  238. package/dist/workflows/validate-summary.js +24 -3
  239. package/dist/workflows/validator.js +26 -1
  240. package/docs/data-and-telemetry.md +4 -3
  241. package/docs/migration/release-notes/0.6.0.md +1 -1
  242. package/docs/migration/release-notes/0.7.0.md +5 -4
  243. package/docs/migration/release-notes/0.9.0-beta.60.md +19 -0
  244. package/docs/migration/v0.8-to-v0.9.md +401 -0
  245. package/package.json +4 -2
  246. package/schemas/akm-config.json +16638 -0
  247. package/schemas/akm-task.json +87 -0
  248. package/schemas/akm-workflow.json +372 -0
  249. package/dist/commands/improve/improve-profiles.js +0 -168
  250. package/dist/core/config/config-migration.js +0 -602
  251. package/dist/core/deep-merge.js +0 -38
  252. package/dist/llm/call-ai.js +0 -62
  253. package/dist/setup/legacy-config.js +0 -106
  254. package/docs/README.md +0 -104
  255. /package/dist/assets/{profiles → improve-strategies}/catchup.json +0 -0
  256. /package/dist/assets/{profiles → improve-strategies}/consolidate.json +0 -0
  257. /package/dist/assets/{profiles → improve-strategies}/frequent.json +0 -0
  258. /package/dist/assets/{profiles → improve-strategies}/memory-focus.json +0 -0
  259. /package/dist/assets/{profiles → improve-strategies}/quick.json +0 -0
  260. /package/dist/assets/{profiles → improve-strategies}/recombine-only.json +0 -0
  261. /package/dist/assets/{profiles → improve-strategies}/synthesize.json +0 -0
  262. /package/dist/assets/{profiles → improve-strategies}/thorough.json +0 -0
@@ -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. Spawn the configured agent profile via {@link runAgent}.
14
+ * 4. Dispatch the selected named engine via {@link executeRunner}.
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"`.
@@ -30,22 +30,19 @@ import { parseFrontmatter } from "../../core/asset/frontmatter.js";
30
30
  import { stripMarkdownFences } from "../../core/asset/markdown.js";
31
31
  import { DESCRIPTION_MAX_CHARS, requiresDescription } from "../../core/authoring-rules.js";
32
32
  import { resolveStashDir } from "../../core/common.js";
33
- import { getImproveProcessConfig, loadConfig } from "../../core/config/config.js";
34
- import { ConfigError, UsageError } from "../../core/errors.js";
33
+ import { loadConfig } from "../../core/config/config.js";
34
+ import { ConfigError } from "../../core/errors.js";
35
35
  import { appendEvent, readEvents } from "../../core/events.js";
36
36
  import { lintLessonContent } from "../../core/lesson-lint.js";
37
+ import { redactSensitiveText } from "../../core/redaction.js";
37
38
  import { resolveStandardsContext } from "../../core/standards/resolve-standards-context.js";
38
- import { warn } from "../../core/warn.js";
39
39
  import { lookup } from "../../indexer/indexer.js";
40
- import { runAgent, } from "../../integrations/agent/index.js";
41
- import { resolveProcessAgentProfile } from "../../integrations/agent/config.js";
40
+ import { resolveEngine } from "../../integrations/agent/engine-resolution.js";
42
41
  import { buildReflectPrompt, extractDraftConfidence, parseAgentProposalPayload, } from "../../integrations/agent/prompts.js";
43
- import { resolveDefaultLlmRunner, resolveImproveProcessRunnerFromProfile, runnerIsLlm, runnerSupportsFileWrite, } from "../../integrations/agent/runner.js";
44
- import { executeRunner } from "../../integrations/agent/runner-dispatch.js";
45
- import { runOpencodeSdk } from "../../integrations/harnesses/opencode-sdk/index.js";
42
+ import { materializeLlmRunnerConnection, resolveImproveProcessRunner, runnerIsLlm, runnerSupportsFileWrite, } from "../../integrations/agent/runner.js";
43
+ import { collectDispatchSensitiveValues, executeRunner } from "../../integrations/agent/runner-dispatch.js";
46
44
  import { chatCompletion } from "../../llm/client.js";
47
- import { isLlmFeatureEnabled } from "../../llm/feature-gate.js";
48
- import { baseFailureFields, enoentHintMessage, isEnoentFailure, loadAgentConfigFromDisk, resolveAgentProfile, } from "../agent/agent-support.js";
45
+ import { baseFailureFields, enoentHintMessage, isEnoentFailure } from "../agent/agent-support.js";
49
46
  import { createProposal, isProposalSkipped, listProposals, } from "../proposal/repository.js";
50
47
  import { checkReflectSize, isValidDescription } from "../proposal/validators/proposal-quality-validators.js";
51
48
  import { deriveLessonRef, runLessonQualityJudge } from "./distill.js";
@@ -153,8 +150,8 @@ function readRejectedProposals(stash, ref) {
153
150
  * The path lives under {@link os.tmpdir} and embeds the (sanitized) ref +
154
151
  * timestamp + random suffix so concurrent reflect calls cannot collide.
155
152
  *
156
- * Returns `undefined` for the LLM HTTP runner the chat-completion transport
157
- * has no filesystem access (see warning at `src/llm/call-ai.ts:64-71`).
153
+ * The LLM HTTP runner cannot use this path because chat-completion transport
154
+ * has no filesystem access.
158
155
  */
159
156
  function synthesizeReflectDraftPath(ref) {
160
157
  const safeRef = (ref ?? "no-ref").replace(/[^a-z0-9_-]/gi, "_");
@@ -321,19 +318,19 @@ function isStructuredCooldownSignal(stdout) {
321
318
  * as valid proposals. Anthropic agent best practices recommend structured
322
319
  * output when the SDK supports it; this tighter fallback is the safety net.
323
320
  *
324
- * When `sdkMode === true`, structured output (tool-call schema) should be used
321
+ * For SDK runners, structured output (tool-call schema) should be used
325
322
  * instead of this fallback. That wiring is tracked separately (full SDK
326
323
  * structured-output integration); for now this tighter parser applies to all
327
324
  * modes and is the primary R-6 deliverable.
328
325
  */
329
- function fallbackPayloadFromRawContent(stdout, ref, sdkMode = false) {
326
+ function fallbackPayloadFromRawContent(stdout, ref, sdkRunner = false) {
330
327
  if (!ref)
331
328
  return undefined;
332
329
  const trimmed = stripMarkdownFences(stdout).trim();
333
330
  if (!trimmed)
334
331
  return undefined;
335
332
  const targetType = ref.split(":")[0];
336
- if (!looksLikeAssetContent(trimmed, sdkMode, targetType))
333
+ if (!looksLikeAssetContent(trimmed, sdkRunner, targetType))
337
334
  return undefined;
338
335
  return { ref, content: trimmed };
339
336
  }
@@ -347,10 +344,10 @@ function fallbackPayloadFromRawContent(stdout, ref, sdkMode = false) {
347
344
  * blocks and pure delimiter sequences as valid payloads.
348
345
  * - Heading start (`#`): must have at least 3 non-blank lines after the heading,
349
346
  * to ensure there is actual body content and not just a title stub.
350
- * - In SDK mode (`sdkMode === true`): additionally requires `when_to_use:` for
347
+ * - For SDK runners: additionally requires `when_to_use:` for
351
348
  * lesson types (full structured output will replace this in a future PR).
352
349
  */
353
- function looksLikeAssetContent(value, sdkMode = false, targetType) {
350
+ function looksLikeAssetContent(value, sdkRunner = false, targetType) {
354
351
  if (value.startsWith("---")) {
355
352
  // YAML frontmatter must contain at least a description field.
356
353
  const fmEnd = value.indexOf("\n---", 4);
@@ -362,7 +359,7 @@ function looksLikeAssetContent(value, sdkMode = false, targetType) {
362
359
  return false;
363
360
  // In SDK mode, lesson assets additionally require a when_to_use field.
364
361
  // Use the target ref type rather than frontmatter type: (which is non-standard).
365
- if (sdkMode && targetType === "lesson") {
362
+ if (sdkRunner && targetType === "lesson") {
366
363
  return /^when_to_use\s*:/m.test(fmBlock);
367
364
  }
368
365
  return true;
@@ -661,19 +658,11 @@ export async function runReflectViaLlm(opts) {
661
658
  messages.push({ role: "user", content: REFLECT_CRITIQUE_PROMPT });
662
659
  }
663
660
  try {
664
- let stdout;
665
- if (opts.chat) {
666
- // Test seam: injected chat function (two-arg signature, no responseSchema).
667
- stdout = await opts.chat(opts.connection, messages);
668
- }
669
- else {
670
- // Production path: full chatCompletion with optional structured-output schema
671
- // and optional hard max_tokens cap (derived from source body size).
672
- stdout = await chatCompletion(opts.connection, messages, {
673
- ...(opts.responseSchema !== undefined ? { responseSchema: opts.responseSchema } : {}),
674
- ...(opts.maxTokens !== undefined ? { maxTokens: opts.maxTokens } : {}),
675
- });
676
- }
661
+ const stdout = await (opts.chat ?? chatCompletion)(opts.connection, messages, {
662
+ ...(Object.hasOwn(opts, "timeoutMs") ? { timeoutMs: opts.timeoutMs } : {}),
663
+ ...(opts.responseSchema !== undefined ? { responseSchema: opts.responseSchema } : {}),
664
+ ...(opts.maxTokens !== undefined ? { maxTokens: opts.maxTokens } : {}),
665
+ });
677
666
  return {
678
667
  ok: true,
679
668
  stdout,
@@ -695,10 +684,12 @@ export async function runReflectViaLlm(opts) {
695
684
  };
696
685
  }
697
686
  }
698
- function failureEnvelope(result, ref, fallbackReason = "non_zero_exit") {
687
+ function failureEnvelope(result, ref, engine, fallbackReason = "non_zero_exit") {
699
688
  return {
700
689
  ...baseFailureFields(result, fallbackReason),
690
+ schemaVersion: 2,
701
691
  ...(ref ? { ref } : {}),
692
+ ...(engine ? { engine } : {}),
702
693
  };
703
694
  }
704
695
  export async function akmReflect(options = {}) {
@@ -710,7 +701,7 @@ export async function akmReflect(options = {}) {
710
701
  ...(options.ref ? { ref: options.ref } : {}),
711
702
  metadata: {
712
703
  ...(options.task ? { task: options.task } : {}),
713
- ...(options.profile ? { profile: options.profile } : {}),
704
+ ...(options.engine ? { engine: options.engine } : {}),
714
705
  // Attribution tagging: stamp the eligibility lane so reflect_invoked can be
715
706
  // sliced by lane downstream. See EligibilitySource.
716
707
  ...(options.eligibilitySource ? { eligibilitySource: options.eligibilitySource } : {}),
@@ -755,7 +746,7 @@ export async function akmReflect(options = {}) {
755
746
  // ("Reflect refused asset type" — ~9% of reflect-failed events).
756
747
  emitReflectFailed("unsupported_type", "unsupported_type", options.ref, { type: parsedRef.type });
757
748
  return {
758
- schemaVersion: 1,
749
+ schemaVersion: 2,
759
750
  ok: false,
760
751
  reason: "unsupported_type",
761
752
  error: `Reflect refused: asset type "${parsedRef.type}" is not supported by reflect (only markdown-canonical types are allowed: ${[...REFLECT_ALLOWED_TYPES].sort().join(", ")}). Use \`akm propose\` or edit the file directly.`,
@@ -779,92 +770,42 @@ export async function akmReflect(options = {}) {
779
770
  }
780
771
  }
781
772
  }
782
- // 3. Resolve agent profile. ConfigError surfaces as a thrown error so the
783
- // CLI dispatcher renders the standard envelope.
784
- //
785
- // When an explicit --profile flag is given, honour it directly (existing
786
- // behaviour). Otherwise use resolveProcessAgentProfile so that per-process
787
- // agent config (agent.processes["reflect"]) is picked up automatically.
788
- let profile;
789
- let resolvedTimeoutMs = options.timeoutMs;
773
+ // 3. Resolve exactly one named engine. Standalone reflect uses --engine or
774
+ // defaults.engine; improve resolves its LLM-only strategy/process overlay.
775
+ // An incompatible explicit engine is an error and never falls through.
776
+ const config = options.config ?? loadConfig();
777
+ const activeStrategy = options.improveProfile ?? config.improve?.strategies?.[config.defaults?.improveStrategy ?? "default"];
790
778
  let runnerSpec;
791
- try {
792
- if (options.agentProfile) {
793
- // Test seam: injected profile bypasses all config.
794
- profile = options.agentProfile;
795
- }
796
- else if (options.runner) {
797
- // Caller-provided RunnerSpec (used in tests and --dry-run-resolve).
798
- runnerSpec = options.runner;
799
- }
800
- else {
801
- const cfg = options.config ?? loadConfig();
802
- const reflectProcess = getImproveProcessConfig(cfg, "reflect", options.improveProfile);
803
- // Resolve the runner from the improve profile's reflect entry when present.
804
- runnerSpec = resolveImproveProcessRunnerFromProfile(reflectProcess, cfg) ?? undefined;
805
- if (runnerSpec) {
806
- if (resolvedTimeoutMs === undefined && runnerSpec.timeoutMs !== undefined) {
807
- resolvedTimeoutMs = runnerSpec.timeoutMs;
808
- }
809
- }
810
- else {
811
- if (options.profile) {
812
- // Explicit --profile flag wins over process config.
813
- profile = resolveAgentProfile(options);
814
- }
815
- else {
816
- // Use per-process config resolution (falls back to defaults.agent).
817
- const agent = options.agentConfig ?? loadAgentConfigFromDisk();
818
- const processName = options.agentProcess ?? "reflect";
819
- const resolved = resolveProcessAgentProfile(processName, agent);
820
- profile = resolved.profile;
821
- // Only apply process-resolved timeoutMs when caller didn't supply one.
822
- if (resolvedTimeoutMs === undefined) {
823
- resolvedTimeoutMs = resolved.timeoutMs;
824
- }
825
- }
826
- }
827
- }
779
+ if (options.runner) {
780
+ runnerSpec = options.runner;
828
781
  }
829
- catch (err) {
830
- if (err instanceof ConfigError || err instanceof UsageError)
831
- throw err;
832
- throw err;
782
+ else if (Object.hasOwn(options, "runner")) {
783
+ 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.");
784
+ }
785
+ else if (options.engine) {
786
+ runnerSpec = resolveEngine(options.engine, config);
833
787
  }
834
- // P1.3 (meta-review 07, Chain G): unattended `akm improve` must never hand
835
- // reflect a tool-capable runner. Agent-CLI/SDK runners have filesystem
836
- // access, so a poisoned asset body flowing through the scheduled reflect
837
- // prompt could steer an agent with tools; the tool-less LLM HTTP runner
838
- // corrupts output text at worst (src/llm/call-ai.ts). An llm RunnerSpec
839
- // resolved from the process block is honored; anything else — an agent/sdk
840
- // spec, a resolved agent profile, or the default-agent fallback below — is
841
- // pinned to `defaults.llm`, failing CLOSED when that is unset.
842
- // `options.agentProfile` stays exempt: it is a direct-injection test seam
843
- // the cron path (loop-stages) never sets.
844
- if (options.eventSource === "improve" && !options.agentProfile && (!runnerSpec || !runnerIsLlm(runnerSpec))) {
845
- const cfg = options.config ?? loadConfig();
846
- const pinned = resolveDefaultLlmRunner(cfg, resolvedTimeoutMs);
847
- if (!pinned) {
848
- throw new ConfigError("Unattended improve pins reflect to the tool-less LLM runner, but the config resolves a tool-capable runner and no defaults.llm profile exists to pin to.", "LLM_NOT_CONFIGURED", 'Set processes.reflect.mode to "llm" on the active improve profile, or configure defaults.llm.');
788
+ else if (options.improveProfile) {
789
+ const processRunner = resolveImproveProcessRunner(activeStrategy, "reflect", config);
790
+ if (!processRunner) {
791
+ 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.");
849
792
  }
850
- warn(runnerSpec
851
- ? `[akm] reflect: unattended improve resolved a tool-capable "${runnerSpec.kind}" runner — pinned to the tool-less LLM runner (07 Chain-G).`
852
- : "[akm] reflect: unattended improve would have fallen back to an agent profile — pinned to the tool-less LLM runner (07 Chain-G).");
853
- runnerSpec = pinned;
854
- profile = undefined;
793
+ runnerSpec = processRunner;
794
+ }
795
+ else {
796
+ const defaultEngine = config.defaults?.engine;
797
+ if (!defaultEngine) {
798
+ throw new ConfigError("reflect requires --engine or defaults.engine.", "INVALID_CONFIG_FILE");
799
+ }
800
+ runnerSpec = resolveEngine(defaultEngine, config);
801
+ }
802
+ if (options.eventSource === "improve" && !runnerIsLlm(runnerSpec)) {
803
+ 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.");
855
804
  }
856
- // Ensure profile is set for agent/sdk runners that don't use runnerSpec
857
- if (!runnerSpec && !profile) {
858
- const agent = options.agentConfig ?? loadAgentConfigFromDisk();
859
- profile = resolveAgentProfile({ ...options, agentConfig: agent });
805
+ const engineName = runnerSpec.engine ?? options.engine;
806
+ if (!engineName) {
807
+ throw new ConfigError("Reflect requires a named engine.", "INVALID_CONFIG_FILE");
860
808
  }
861
- // Derive a display name for logging — either from the resolved profile or the runnerSpec.
862
- const resolvedProfileName = profile?.name ??
863
- (runnerSpec && runnerIsLlm(runnerSpec)
864
- ? `llm:${runnerSpec.connection.model}`
865
- : runnerSpec
866
- ? `${runnerSpec.kind}:${runnerSpec.profile.name ?? "unknown"}`
867
- : "unknown");
868
809
  // 4. Build the shared prompt inputs — feedback, hints, lessons, rejected
869
810
  // proposals. These are stable across refinement iterations; only the
870
811
  // `priorDraft` field changes per-iteration (R-1 / #372).
@@ -889,13 +830,16 @@ export async function akmReflect(options = {}) {
889
830
  const MAX_REFINE_ITERS = 3;
890
831
  const maxRefineIters = Math.min(Math.max(1, options.maxRefineIters ?? 1), MAX_REFINE_ITERS);
891
832
  const agentEnv = options.eventSource === "improve" ? { AKM_EVENT_SOURCE: "improve" } : {};
833
+ const sensitiveValues = collectDispatchSensitiveValues(runnerSpec, {
834
+ ...(Object.keys(agentEnv).length > 0 ? { env: agentEnv } : {}),
835
+ ...(options.runAgentOptions ?? {}),
836
+ });
892
837
  // Determine whether this dispatch can honour the file-write contract.
893
838
  // Agent CLI + OpenCode SDK runners both have filesystem access; the direct
894
- // LLM HTTP runner does NOT (see `src/llm/call-ai.ts:64-71`). The v1
895
- // `profile.sdkMode` fallback also runs the SDK so it counts as file-writable.
839
+ // LLM HTTP runner does NOT.
896
840
  // Test seams (`options.runAgentOptions.spawn`) emulate agent CLI behaviour so
897
841
  // they participate as well — tests opt out by simply not writing the file.
898
- const canRunnerWriteFile = runnerSpec ? runnerSupportsFileWrite(runnerSpec) : true;
842
+ const canRunnerWriteFile = runnerSupportsFileWrite(runnerSpec);
899
843
  // Initialized to a sentinel; always overwritten in the first loop iteration
900
844
  // (maxRefineIters is clamped to >= 1 above). TypeScript cannot prove a
901
845
  // for-loop always runs at least once, so we use a type assertion here.
@@ -960,76 +904,30 @@ export async function akmReflect(options = {}) {
960
904
  // (conservative — most models are 3.5–4) and add 500-char overhead for the
961
905
  // JSON wrapper and frontmatter block that surround the body in the response.
962
906
  const maxTokensForLlm = maxOutputChars !== undefined ? Math.ceil((maxOutputChars + 500) / 3) : undefined;
963
- let iterResult;
964
- if (options.runAgentOptions?.spawn) {
965
- // Test seam: use raw runAgent with injected spawn so tests remain deterministic.
966
- const resolvedProfile = profile;
967
- if (!resolvedProfile) {
968
- throw new Error("internal: reflect test-seam path requires a resolved agent profile");
969
- }
970
- const runOptions = {
971
- stdio: "captured",
972
- parseOutput: "text",
973
- ...(resolvedTimeoutMs !== undefined ? { timeoutMs: resolvedTimeoutMs } : {}),
974
- ...(Object.keys(agentEnv).length > 0 ? { env: agentEnv } : {}),
975
- ...(options.runAgentOptions ?? {}),
976
- };
977
- iterResult = await runAgent(resolvedProfile, prompt, runOptions);
978
- }
979
- else if (runnerSpec) {
980
- // v2: dispatch through the unified RunnerSpec seam (X3). The `agent` /
981
- // `sdk` arms route to the default profile runners; the `llm` arm is
982
- // reflect-specific (wraps `runReflectViaLlm` — its bespoke iteration
983
- // shape) so it is supplied as the `llm` handler.
984
- const runOptions = {
985
- stdio: "captured",
986
- parseOutput: "text",
987
- ...(Object.keys(agentEnv).length > 0 ? { env: agentEnv } : {}),
988
- };
989
- iterResult = await executeRunner(runnerSpec, prompt ?? "", runOptions, {
990
- llm: async (spec) =>
991
- // LLM HTTP path — `draftFilePath` is accepted for type symmetry
992
- // (see `RunReflectViaLlmOptions.draftFilePath` docstring) but is
993
- // intentionally a no-op. The prompt builder above also did not
994
- // include the file-write contract for this kind, so the LLM is
995
- // still asked for JSON via stdout.
996
- runReflectViaLlm({
997
- prompt,
998
- connection: spec.connection,
999
- timeoutMs: spec.timeoutMs ?? (typeof resolvedTimeoutMs === "number" ? resolvedTimeoutMs : undefined),
1000
- priorDraft,
1001
- iteration: iter,
1002
- responseSchema: REFLECT_JSON_SCHEMA,
1003
- chat: options.chat,
1004
- ...(maxTokensForLlm !== undefined ? { maxTokens: maxTokensForLlm } : {}),
1005
- }),
1006
- // The `agent` arm (and only the agent arm — preserving prior behavior)
1007
- // overlays `spec.timeoutMs` onto the base run options.
1008
- runAgent: (profile, p, opts) => runAgent(profile, p, {
1009
- ...opts,
1010
- ...(runnerSpec.timeoutMs !== undefined ? { timeoutMs: runnerSpec.timeoutMs } : {}),
1011
- }),
1012
- });
1013
- }
1014
- else {
1015
- // Production path (v1): dispatch directly to the appropriate runner.
1016
- // The fallback at the end of step 3 guarantees `profile` is set whenever
1017
- // `runnerSpec` is undefined, but TS can't prove that across the loop +
1018
- // await boundary — narrow into a const.
1019
- const resolvedProfile = profile;
1020
- if (!resolvedProfile) {
1021
- throw new Error("internal: reflect v1 dispatch reached without a resolved agent profile or runnerSpec");
1022
- }
1023
- const runOptions = {
1024
- stdio: "captured",
1025
- parseOutput: "text",
1026
- ...(resolvedTimeoutMs !== undefined ? { timeoutMs: resolvedTimeoutMs } : {}),
1027
- ...(Object.keys(agentEnv).length > 0 ? { env: agentEnv } : {}),
1028
- };
1029
- iterResult = resolvedProfile.sdkMode
1030
- ? await runOpencodeSdk(resolvedProfile, prompt ?? "", runOptions)
1031
- : await runAgent(resolvedProfile, prompt, runOptions);
1032
- }
907
+ // Every engine kind crosses the same dispatch seam. Injected spawn/timer
908
+ // functions remain ordinary run options for deterministic tests.
909
+ const runOptions = {
910
+ stdio: "captured",
911
+ parseOutput: "text",
912
+ ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
913
+ ...(Object.keys(agentEnv).length > 0 ? { env: agentEnv } : {}),
914
+ ...(options.runAgentOptions ?? {}),
915
+ };
916
+ const iterResult = await executeRunner(runnerSpec, prompt ?? "", runOptions, {
917
+ llm: async (spec, _prompt, opts) =>
918
+ // LLM HTTP runners cannot honor the file-write contract, so they
919
+ // return structured JSON through stdout.
920
+ runReflectViaLlm({
921
+ prompt,
922
+ connection: spec.connection,
923
+ ...(Object.hasOwn(opts, "timeoutMs") ? { timeoutMs: opts.timeoutMs } : {}),
924
+ priorDraft,
925
+ iteration: iter,
926
+ responseSchema: REFLECT_JSON_SCHEMA,
927
+ chat: options.chat,
928
+ ...(maxTokensForLlm !== undefined ? { maxTokens: maxTokensForLlm } : {}),
929
+ }),
930
+ });
1033
931
  result = iterResult;
1034
932
  if (!iterResult.ok)
1035
933
  break; // surface failure after loop
@@ -1051,11 +949,11 @@ export async function akmReflect(options = {}) {
1051
949
  ...(finalResult.exitCode !== undefined ? { exitCode: finalResult.exitCode } : {}),
1052
950
  });
1053
951
  return {
1054
- ...failureEnvelope(finalResult, options.ref),
1055
- error: enoentHintMessage(profile?.bin ?? resolvedProfileName),
952
+ ...failureEnvelope(finalResult, options.ref, engineName),
953
+ error: enoentHintMessage(runnerIsLlm(runnerSpec) ? engineName : runnerSpec.profile.bin),
1056
954
  };
1057
955
  }
1058
- const envelope = failureEnvelope(finalResult, options.ref);
956
+ const envelope = failureEnvelope(finalResult, options.ref, engineName);
1059
957
  emitReflectFailed(envelope.reason, "agent_crash", options.ref, {
1060
958
  ...(envelope.exitCode !== null ? { exitCode: envelope.exitCode } : {}),
1061
959
  });
@@ -1085,11 +983,12 @@ export async function akmReflect(options = {}) {
1085
983
  ...(result.exitCode !== null ? { exitCode: result.exitCode } : {}),
1086
984
  });
1087
985
  return {
1088
- schemaVersion: 1,
986
+ schemaVersion: 2,
1089
987
  ok: false,
1090
988
  reason: "parse_error",
1091
989
  error: `Agent emitted DRAFT_WRITTEN but draft file is missing or empty (${lastDraftPath}). The file-write contract failed; either the agent's file tools are broken or the path was unwritable.`,
1092
990
  ...(options.ref ? { ref: options.ref } : {}),
991
+ engine: engineName,
1093
992
  exitCode: result.exitCode,
1094
993
  stdout: result.stdout,
1095
994
  ...(result.stderr ? { stderr: result.stderr } : {}),
@@ -1099,7 +998,7 @@ export async function akmReflect(options = {}) {
1099
998
  // Happy path: agent wrote the body to disk. Use the ref the caller
1100
999
  // supplied (or a placeholder when omitted — the R-3 ref-mismatch guard
1101
1000
  // below has no effect when there is no expected ref).
1102
- const fileContent = fs.readFileSync(lastDraftPath, "utf8");
1001
+ const fileContent = redactSensitiveText(fs.readFileSync(lastDraftPath, "utf8"), sensitiveValues);
1103
1002
  // Phase 6A: file-write contract carries self-rated confidence on the
1104
1003
  // `DRAFT_WRITTEN confidence=<n>` sentinel line. Extract it so the
1105
1004
  // file-write path is on equal footing with the JSON-stdout path for
@@ -1120,7 +1019,7 @@ export async function akmReflect(options = {}) {
1120
1019
  payload = parseAgentProposalPayload(result.stdout ?? "");
1121
1020
  }
1122
1021
  catch (err) {
1123
- const fallback = fallbackPayloadFromRawContent(result.stdout ?? "", options.ref, profile?.sdkMode ?? false);
1022
+ const fallback = fallbackPayloadFromRawContent(result.stdout ?? "", options.ref, runnerSpec.kind === "sdk");
1124
1023
  if (fallback) {
1125
1024
  payload = fallback;
1126
1025
  }
@@ -1135,11 +1034,12 @@ export async function akmReflect(options = {}) {
1135
1034
  ...(result.exitCode !== null ? { exitCode: result.exitCode } : {}),
1136
1035
  });
1137
1036
  return {
1138
- schemaVersion: 1,
1037
+ schemaVersion: 2,
1139
1038
  ok: false,
1140
1039
  reason,
1141
1040
  error: err instanceof Error ? err.message : String(err),
1142
1041
  ...(options.ref ? { ref: options.ref } : {}),
1042
+ engine: engineName,
1143
1043
  exitCode: result.exitCode,
1144
1044
  stdout: result.stdout,
1145
1045
  ...(result.stderr ? { stderr: result.stderr } : {}),
@@ -1155,6 +1055,7 @@ export async function akmReflect(options = {}) {
1155
1055
  // draft paths.
1156
1056
  cleanupDrafts();
1157
1057
  }
1058
+ payload = { ...payload, content: redactSensitiveText(payload.content, sensitiveValues) };
1158
1059
  // 6b. Validate payload.ref === options.ref (R-3 / #366).
1159
1060
  // A hallucinating agent can silently retarget proposals to a different ref.
1160
1061
  // This guard normalises both refs through parseAssetRef so origin-prefix
@@ -1172,11 +1073,12 @@ export async function akmReflect(options = {}) {
1172
1073
  ...(result.exitCode !== null ? { exitCode: result.exitCode } : {}),
1173
1074
  });
1174
1075
  return {
1175
- schemaVersion: 1,
1076
+ schemaVersion: 2,
1176
1077
  ok: false,
1177
1078
  reason: "parse_error",
1178
1079
  error: `Agent retargeted proposal: expected ref "${options.ref}" but got "${payload.ref}". Proposal rejected to prevent silent ref hallucination.`,
1179
1080
  ref: options.ref,
1081
+ engine: engineName,
1180
1082
  exitCode: result.exitCode,
1181
1083
  stdout: result.stdout,
1182
1084
  ...(result.stderr ? { stderr: result.stderr } : {}),
@@ -1191,10 +1093,9 @@ export async function akmReflect(options = {}) {
1191
1093
  // 7. R-5 / #374: Apply the proposal quality gate when enabled.
1192
1094
  // Mirrors the lesson quality gate on distill proposals. The gate uses
1193
1095
  // `runLessonQualityJudge` from distill.ts and is gated behind either
1194
- // `profiles.improve.default.processes.reflect.qualityGate.enabled` or
1195
- // `profiles.improve.default.processes.distill.qualityGate.enabled` (the
1196
- // `lesson_quality_gate` flag name is the legacy alias still accepted by
1197
- // `isLlmFeatureEnabled`). Fail-CLOSED (07 P0-2): a judge error / no-LLM /
1096
+ // `processes.reflect.qualityGate.enabled` or
1097
+ // `processes.distill.qualityGate.enabled` on the selected strategy.
1098
+ // Fail-CLOSED (07 P0-2): a judge error / no-LLM /
1198
1099
  // parse failure rejects the proposal rather than passing it through.
1199
1100
  // G-Eval (arXiv:2303.16634) — quality judgment before admission.
1200
1101
  const runtimeConfig = options.config ??
@@ -1207,8 +1108,8 @@ export async function akmReflect(options = {}) {
1207
1108
  }
1208
1109
  })();
1209
1110
  const chatFn = options.chat ?? chatCompletion;
1210
- const qualityGateEnabled = isLlmFeatureEnabled(runtimeConfig, "proposal_quality_gate") ||
1211
- isLlmFeatureEnabled(runtimeConfig, "lesson_quality_gate");
1111
+ const qualityGateEnabled = (activeStrategy?.processes?.reflect?.qualityGate?.enabled ?? false) ||
1112
+ (activeStrategy?.processes?.distill?.qualityGate?.enabled ?? true);
1212
1113
  if (qualityGateEnabled && runtimeConfig) {
1213
1114
  const assetContent = (() => {
1214
1115
  if (!options.ref)
@@ -1229,7 +1130,7 @@ export async function akmReflect(options = {}) {
1229
1130
  return null;
1230
1131
  }
1231
1132
  })();
1232
- const judgeResult = await runLessonQualityJudge(runtimeConfig, payload.content, assetContent ?? "", chatFn);
1133
+ const judgeResult = await runLessonQualityJudge(runtimeConfig, payload.content, assetContent ?? "", chatFn, undefined, runnerIsLlm(runnerSpec) ? materializeLlmRunnerConnection(runnerSpec) : undefined);
1233
1134
  if (!judgeResult.pass) {
1234
1135
  // Quality gate rejected the proposal — surface as parse_error so the
1235
1136
  // improve orchestrator can log it and move on without crashing.
@@ -1244,11 +1145,12 @@ export async function akmReflect(options = {}) {
1244
1145
  },
1245
1146
  });
1246
1147
  return {
1247
- schemaVersion: 1,
1148
+ schemaVersion: 2,
1248
1149
  ok: false,
1249
1150
  reason: "parse_error",
1250
1151
  error: `Reflect proposal quality gate rejected: score=${judgeResult.score}, reason="${judgeResult.reason}"`,
1251
1152
  ...(options.ref ? { ref: options.ref } : {}),
1153
+ engine: engineName,
1252
1154
  exitCode: result.exitCode,
1253
1155
  };
1254
1156
  }
@@ -1277,11 +1179,12 @@ export async function akmReflect(options = {}) {
1277
1179
  },
1278
1180
  });
1279
1181
  return {
1280
- schemaVersion: 1,
1182
+ schemaVersion: 2,
1281
1183
  ok: false,
1282
1184
  reason: sanitizeOutcome.reject.reason,
1283
1185
  error: sanitizeOutcome.reject.error,
1284
1186
  ...(options.ref ? { ref: options.ref } : {}),
1187
+ engine: engineName,
1285
1188
  exitCode: result.exitCode,
1286
1189
  };
1287
1190
  }
@@ -1301,9 +1204,9 @@ export async function akmReflect(options = {}) {
1301
1204
  const changeKind = classifyReflectChange(assetContent, payload.content);
1302
1205
  // 'low-value' is config-gated (#639). DEFAULT OFF — absent = byte-identical
1303
1206
  // pre-#639 behaviour (low-value treated the same as substantive). Resolved
1304
- // by the caller from the ACTIVE improve profile's
1207
+ // by the caller from the active improve strategy's
1305
1208
  // `processes.reflect.lowValueFilter.enabled` and passed via options, so the
1306
- // running profile (not a hardcoded `profiles.improve.default`) decides.
1209
+ // running strategy decides.
1307
1210
  const lowValueFilterEnabled = options.lowValueFilter === true;
1308
1211
  const isDeferred = changeKind === "noop" || changeKind === "cosmetic" || (changeKind === "low-value" && lowValueFilterEnabled);
1309
1212
  if (isDeferred) {
@@ -1314,7 +1217,7 @@ export async function akmReflect(options = {}) {
1314
1217
  : "reflect_skipped_cosmetic";
1315
1218
  emitReflectFailed("no_change", subreason, options.ref, { changeKind });
1316
1219
  return {
1317
- schemaVersion: 1,
1220
+ schemaVersion: 2,
1318
1221
  ok: false,
1319
1222
  reason: "no_change",
1320
1223
  error: changeKind === "noop"
@@ -1323,6 +1226,7 @@ export async function akmReflect(options = {}) {
1323
1226
  ? `Reflect skipped: proposed content for ${payload.ref} is a low-value prose micro-rewrite (few changed tokens, no structural changes); no proposal created.`
1324
1227
  : `Reflect skipped: proposed content for ${payload.ref} is a cosmetic-only reformat of the current asset (whitespace/fence/YAML-folding changes); no proposal created.`,
1325
1228
  ...(options.ref ? { ref: options.ref } : {}),
1229
+ engine: engineName,
1326
1230
  exitCode: result.exitCode,
1327
1231
  };
1328
1232
  }
@@ -1370,11 +1274,11 @@ export async function akmReflect(options = {}) {
1370
1274
  ...(typeof payload.confidence === "number" ? { confidence: payload.confidence } : {}),
1371
1275
  };
1372
1276
  return {
1373
- schemaVersion: 1,
1277
+ schemaVersion: 2,
1374
1278
  ok: true,
1375
1279
  proposal: draftProposal,
1376
1280
  ref: draftProposal.ref,
1377
- agentProfile: resolvedProfileName,
1281
+ engine: engineName,
1378
1282
  durationMs: result.durationMs,
1379
1283
  };
1380
1284
  }
@@ -1405,11 +1309,12 @@ export async function akmReflect(options = {}) {
1405
1309
  proposalSkipReason: proposalResult.reason,
1406
1310
  });
1407
1311
  return {
1408
- schemaVersion: 1,
1312
+ schemaVersion: 2,
1409
1313
  ok: false,
1410
1314
  reason: "cooldown",
1411
1315
  error: `Proposal skipped (${proposalResult.reason}): ${proposalResult.message}`,
1412
1316
  ...(options.ref ? { ref: options.ref } : {}),
1317
+ engine: engineName,
1413
1318
  exitCode: null,
1414
1319
  };
1415
1320
  }
@@ -1420,15 +1325,15 @@ export async function akmReflect(options = {}) {
1420
1325
  metadata: {
1421
1326
  proposalId: proposal.id,
1422
1327
  source: "reflect",
1423
- agentProfile: resolvedProfileName,
1328
+ engine: engineName,
1424
1329
  },
1425
1330
  });
1426
1331
  return {
1427
- schemaVersion: 1,
1332
+ schemaVersion: 2,
1428
1333
  ok: true,
1429
1334
  proposal,
1430
1335
  ref: proposal.ref,
1431
- agentProfile: resolvedProfileName,
1336
+ engine: engineName,
1432
1337
  durationMs: result.durationMs,
1433
1338
  };
1434
1339
  }
@@ -1,9 +1,9 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- import { getDefaultLlmConfig, getImproveProcessConfig } from "../../core/config/config.js";
4
+ import { getDefaultLlmConfig } from "../../core/config/config.js";
5
5
  import { warn } from "../../core/warn.js";
6
- import { resolveImproveProcessRunnerFromProfile, runnerIsLlm } from "../../integrations/agent/runner.js";
6
+ import { materializeLlmRunnerConnection, resolveImproveProcessRunner } from "../../integrations/agent/runner.js";
7
7
  import { chatCompletion } from "../../llm/client.js";
8
8
  /** Normalize an unknown thrown value to a human-readable message string. */
9
9
  export function errMessage(e) {
@@ -30,9 +30,15 @@ export function refSlug(ref) {
30
30
  * profile's model; absent falls back to `default`.
31
31
  */
32
32
  export function resolveImproveLlmFn(config, opts) {
33
- const processConfig = getImproveProcessConfig(config, opts.processKey, opts.activeProfile);
34
- const runnerSpec = resolveImproveProcessRunnerFromProfile(processConfig, config);
35
- const llmConfig = runnerSpec && runnerIsLlm(runnerSpec) ? runnerSpec.connection : getDefaultLlmConfig(config);
33
+ const planOwnsResolution = Object.hasOwn(opts, "llmConfig");
34
+ const runnerSpec = planOwnsResolution
35
+ ? undefined
36
+ : resolveImproveProcessRunner(opts.activeProfile, opts.processKey, config);
37
+ const llmConfig = planOwnsResolution
38
+ ? (opts.llmConfig ?? undefined)
39
+ : runnerSpec
40
+ ? materializeLlmRunnerConnection(runnerSpec)
41
+ : getDefaultLlmConfig(config);
36
42
  if (!llmConfig)
37
43
  return undefined;
38
44
  return async (prompt) => {