akm-cli 0.9.0-rc.1 → 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 (228) hide show
  1. package/CHANGELOG.md +223 -9
  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/stash-skeleton/README.md +28 -0
  11. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +6 -0
  12. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +6 -0
  13. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +12 -1
  14. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +11 -1
  15. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +6 -0
  16. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +9 -0
  17. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +7 -0
  18. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +7 -0
  19. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +6 -0
  20. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +98 -0
  21. package/dist/assets/stash-skeleton/facts/conventions/domains.md +63 -0
  22. package/dist/assets/stash-skeleton/facts/conventions/organization.md +127 -0
  23. package/dist/assets/tasks/core/backup.yml +1 -0
  24. package/dist/assets/tasks/core/extract.yml +1 -0
  25. package/dist/assets/tasks/core/improve.yml +1 -0
  26. package/dist/assets/tasks/core/index-refresh.yml +1 -0
  27. package/dist/assets/tasks/core/sync.yml +1 -0
  28. package/dist/assets/tasks/core/version-check.yml +1 -0
  29. package/dist/assets/tasks/graph-refresh-weekly.yml +4 -4
  30. package/dist/assets/templates/html/health.html +5 -1
  31. package/dist/cli/config-migrate.js +31 -138
  32. package/dist/cli/config-validate.js +10 -8
  33. package/dist/cli.js +48 -14
  34. package/dist/commands/agent/agent-dispatch.js +17 -16
  35. package/dist/commands/agent/agent-support.js +0 -24
  36. package/dist/commands/agent/contribute-cli.js +5 -15
  37. package/dist/commands/backup-cli.js +54 -0
  38. package/dist/commands/config-cli.js +45 -159
  39. package/dist/commands/env/secret.js +8 -5
  40. package/dist/commands/health/checks.js +130 -83
  41. package/dist/commands/health/html-report.js +4 -0
  42. package/dist/commands/health/improve-metrics.js +30 -32
  43. package/dist/commands/health/llm-usage.js +19 -19
  44. package/dist/commands/health/md-report.js +4 -0
  45. package/dist/commands/health/metrics.js +2 -1
  46. package/dist/commands/health/surfaces.js +5 -4
  47. package/dist/commands/health.js +1 -1
  48. package/dist/commands/improve/consolidate/chunking.js +2 -2
  49. package/dist/commands/improve/consolidate.js +28 -25
  50. package/dist/commands/improve/distill/promote-memory.js +5 -12
  51. package/dist/commands/improve/distill/quality-gate.js +5 -7
  52. package/dist/commands/improve/distill.js +16 -5
  53. package/dist/commands/improve/eligibility.js +22 -12
  54. package/dist/commands/improve/extract-cli.js +47 -19
  55. package/dist/commands/improve/extract.js +110 -62
  56. package/dist/commands/improve/improve-cli.js +38 -16
  57. package/dist/commands/improve/improve-result-file.js +30 -24
  58. package/dist/commands/improve/improve-strategies.js +137 -0
  59. package/dist/commands/improve/improve.js +60 -30
  60. package/dist/commands/improve/locks.js +66 -45
  61. package/dist/commands/improve/loop-stages.js +75 -33
  62. package/dist/commands/improve/memory/memory-belief.js +79 -7
  63. package/dist/commands/improve/memory/memory-contradiction-detect.js +12 -4
  64. package/dist/commands/improve/preparation.js +71 -73
  65. package/dist/commands/improve/procedural.js +3 -2
  66. package/dist/commands/improve/recombine.js +2 -1
  67. package/dist/commands/improve/reflect.js +119 -214
  68. package/dist/commands/improve/shared.js +11 -5
  69. package/dist/commands/lint/base-linter.js +152 -42
  70. package/dist/commands/mv-cli.js +809 -0
  71. package/dist/commands/proposal/proposal-cli.js +18 -8
  72. package/dist/commands/proposal/propose.js +64 -69
  73. package/dist/commands/read/knowledge.js +436 -4
  74. package/dist/commands/read/remember-cli.js +39 -2
  75. package/dist/commands/read/search-cli.js +6 -1
  76. package/dist/commands/registry-cli.js +29 -14
  77. package/dist/commands/remember.js +2 -0
  78. package/dist/commands/sources/init.js +13 -14
  79. package/dist/commands/sources/schema-repair.js +2 -4
  80. package/dist/commands/sources/source-add.js +62 -73
  81. package/dist/commands/sources/source-manage.js +50 -46
  82. package/dist/commands/sources/stash-cli.js +41 -4
  83. package/dist/commands/tasks/default-tasks.js +12 -12
  84. package/dist/commands/tasks/tasks-cli.js +7 -3
  85. package/dist/commands/tasks/tasks.js +113 -18
  86. package/dist/commands/wiki-cli.js +9 -10
  87. package/dist/core/asset/frontmatter.js +12 -2
  88. package/dist/core/common.js +5 -3
  89. package/dist/core/config/config-io.js +28 -17
  90. package/dist/core/config/config-schema.js +358 -66
  91. package/dist/core/config/config-types.js +3 -3
  92. package/dist/core/config/config-version.js +29 -0
  93. package/dist/core/config/config-walker.js +98 -27
  94. package/dist/core/config/config.js +132 -266
  95. package/dist/core/config/deep-merge.js +41 -0
  96. package/dist/core/config/engine-semantics.js +32 -0
  97. package/dist/core/errors.js +2 -2
  98. package/dist/core/extra-params.js +61 -0
  99. package/dist/core/file-lock.js +201 -56
  100. package/dist/core/improve-result.js +178 -0
  101. package/dist/core/maintenance-barrier.js +119 -0
  102. package/dist/core/migration-backup.js +416 -0
  103. package/dist/core/paths.js +3 -0
  104. package/dist/core/redaction.js +358 -0
  105. package/dist/core/state/migrations.js +17 -2
  106. package/dist/core/state-db.js +44 -1
  107. package/dist/indexer/db/db.js +116 -1
  108. package/dist/indexer/graph/graph-extraction.js +28 -16
  109. package/dist/indexer/index-writer-lock.js +31 -24
  110. package/dist/indexer/index-written-assets.js +15 -6
  111. package/dist/indexer/indexer.js +47 -2
  112. package/dist/indexer/passes/memory-inference.js +10 -6
  113. package/dist/indexer/passes/metadata.js +250 -0
  114. package/dist/indexer/search/db-search.js +111 -44
  115. package/dist/indexer/search/fts-query.js +41 -0
  116. package/dist/indexer/search/ranking-contributors.js +48 -0
  117. package/dist/indexer/search/ranking.js +36 -23
  118. package/dist/indexer/search/search-fields.js +11 -1
  119. package/dist/integrations/agent/builder-shared.js +7 -0
  120. package/dist/integrations/agent/builders.js +5 -52
  121. package/dist/integrations/agent/config.js +3 -143
  122. package/dist/integrations/agent/detect.js +17 -2
  123. package/dist/integrations/agent/engine-resolution.js +202 -0
  124. package/dist/integrations/agent/index.js +1 -2
  125. package/dist/integrations/agent/model-aliases.js +7 -2
  126. package/dist/integrations/agent/profiles.js +6 -99
  127. package/dist/integrations/agent/runner-dispatch.js +76 -13
  128. package/dist/integrations/agent/runner.js +76 -207
  129. package/dist/integrations/agent/spawn.js +4 -6
  130. package/dist/integrations/harnesses/aider/agent-builder.js +2 -3
  131. package/dist/integrations/harnesses/aider/index.js +0 -1
  132. package/dist/integrations/harnesses/amazonq/agent-builder.js +2 -3
  133. package/dist/integrations/harnesses/amazonq/index.js +0 -1
  134. package/dist/integrations/harnesses/claude/agent-builder.js +2 -3
  135. package/dist/integrations/harnesses/claude/index.js +0 -2
  136. package/dist/integrations/harnesses/codex/agent-builder.js +2 -3
  137. package/dist/integrations/harnesses/codex/index.js +0 -1
  138. package/dist/integrations/harnesses/copilot/agent-builder.js +2 -3
  139. package/dist/integrations/harnesses/copilot/index.js +0 -1
  140. package/dist/integrations/harnesses/gemini/agent-builder.js +2 -3
  141. package/dist/integrations/harnesses/gemini/index.js +0 -1
  142. package/dist/integrations/harnesses/index.js +1 -24
  143. package/dist/integrations/harnesses/opencode/agent-builder.js +2 -3
  144. package/dist/integrations/harnesses/opencode/index.js +0 -6
  145. package/dist/integrations/harnesses/opencode-sdk/harness.js +1 -6
  146. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +274 -191
  147. package/dist/integrations/harnesses/openhands/agent-builder.js +2 -3
  148. package/dist/integrations/harnesses/openhands/index.js +0 -1
  149. package/dist/integrations/harnesses/pi/agent-builder.js +2 -3
  150. package/dist/integrations/harnesses/pi/index.js +0 -1
  151. package/dist/integrations/harnesses/types.js +1 -32
  152. package/dist/integrations/lockfile.js +32 -21
  153. package/dist/llm/client.js +48 -14
  154. package/dist/llm/feature-gate.js +15 -47
  155. package/dist/llm/graph-extract.js +1 -1
  156. package/dist/llm/index-passes.js +8 -42
  157. package/dist/llm/memory-infer-impl.js +1 -1
  158. package/dist/llm/usage-persist.js +4 -0
  159. package/dist/llm/usage-telemetry.js +35 -5
  160. package/dist/output/shapes/helpers.js +2 -1
  161. package/dist/output/shapes/passthrough.js +2 -0
  162. package/dist/output/text/helpers.js +3 -1
  163. package/dist/schemas/akm-config.json +11013 -8600
  164. package/dist/schemas/akm-task.json +87 -0
  165. package/dist/schemas/akm-workflow.json +57 -13
  166. package/dist/scripts/migrate-storage.js +8591 -509
  167. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +8623 -484
  168. package/dist/setup/detected-engines.js +142 -0
  169. package/dist/setup/engine-config.js +89 -0
  170. package/dist/setup/setup.js +236 -132
  171. package/dist/setup/steps/connection.js +61 -32
  172. package/dist/setup/steps/platforms.js +4 -4
  173. package/dist/setup/steps.js +3 -2
  174. package/dist/storage/database.js +13 -1
  175. package/dist/storage/engines/sqlite-migrations.js +1 -0
  176. package/dist/storage/repositories/improve-runs-repository.js +5 -5
  177. package/dist/storage/repositories/task-history-repository.js +78 -0
  178. package/dist/storage/repositories/workflow-runs-repository.js +9 -8
  179. package/dist/tasks/parser.js +138 -52
  180. package/dist/tasks/runner.js +71 -75
  181. package/dist/tasks/schema.js +1 -1
  182. package/dist/tasks/validator.js +11 -6
  183. package/dist/wiki/wiki.js +9 -8
  184. package/dist/workflows/authoring/workflow-program-template.yaml +3 -3
  185. package/dist/workflows/concurrency-policy.js +15 -0
  186. package/dist/workflows/db.js +65 -13
  187. package/dist/workflows/exec/brief.js +18 -24
  188. package/dist/workflows/exec/frozen-judge.js +47 -0
  189. package/dist/workflows/exec/native-executor.js +124 -65
  190. package/dist/workflows/exec/report.js +93 -33
  191. package/dist/workflows/exec/run-workflow.js +38 -25
  192. package/dist/workflows/exec/scheduler.js +12 -41
  193. package/dist/workflows/exec/step-work.js +91 -35
  194. package/dist/workflows/ir/compile.js +13 -26
  195. package/dist/workflows/ir/freeze.js +243 -0
  196. package/dist/workflows/ir/plan-hash.js +40 -5
  197. package/dist/workflows/ir/schema.js +542 -1
  198. package/dist/workflows/parser.js +7 -0
  199. package/dist/workflows/program/parser.js +132 -23
  200. package/dist/workflows/program/project.js +3 -4
  201. package/dist/workflows/program/schema.js +2 -2
  202. package/dist/workflows/resource-limits.js +20 -0
  203. package/dist/workflows/runtime/plan-classifier.js +187 -0
  204. package/dist/workflows/runtime/runs.js +54 -47
  205. package/dist/workflows/runtime/workflow-asset-loader.js +0 -22
  206. package/dist/workflows/validator.js +25 -0
  207. package/docs/data-and-telemetry.md +2 -2
  208. package/docs/migration/release-notes/0.6.0.md +1 -1
  209. package/docs/migration/release-notes/0.7.0.md +5 -4
  210. package/docs/migration/v0.8-to-v0.9.md +401 -0
  211. package/package.json +4 -2
  212. package/schemas/akm-config.json +16638 -0
  213. package/schemas/akm-task.json +87 -0
  214. package/schemas/akm-workflow.json +372 -0
  215. package/dist/commands/improve/improve-profiles.js +0 -168
  216. package/dist/core/config/config-migration.js +0 -602
  217. package/dist/core/deep-merge.js +0 -38
  218. package/dist/llm/call-ai.js +0 -62
  219. package/dist/setup/legacy-config.js +0 -106
  220. package/docs/README.md +0 -104
  221. /package/dist/assets/{profiles → improve-strategies}/catchup.json +0 -0
  222. /package/dist/assets/{profiles → improve-strategies}/consolidate.json +0 -0
  223. /package/dist/assets/{profiles → improve-strategies}/frequent.json +0 -0
  224. /package/dist/assets/{profiles → improve-strategies}/memory-focus.json +0 -0
  225. /package/dist/assets/{profiles → improve-strategies}/quick.json +0 -0
  226. /package/dist/assets/{profiles → improve-strategies}/recombine-only.json +0 -0
  227. /package/dist/assets/{profiles → improve-strategies}/synthesize.json +0 -0
  228. /package/dist/assets/{profiles → improve-strategies}/thorough.json +0 -0
@@ -1,18 +1,23 @@
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 { decodeImproveResult } from "../../core/improve-result.js";
4
5
  import { queryImproveRuns } from "../../storage/repositories/improve-runs-repository.js";
6
+ import { decodeTaskHistoryMetadata } from "../../storage/repositories/task-history-repository.js";
5
7
  import { summarizeCalibration } from "../improve/calibration.js";
6
8
  export function roundRate(value) {
7
9
  return Number(value.toFixed(4));
8
10
  }
9
11
  export function parseTaskMetadata(row) {
10
- try {
11
- return JSON.parse(row.metadata_json);
12
- }
13
- catch {
14
- return {};
15
- }
12
+ const metadata = decodeTaskHistoryMetadata(row.metadata_json);
13
+ return {
14
+ ...(metadata.durationMs !== undefined ? { durationMs: metadata.durationMs } : {}),
15
+ ...(metadata.detail ? { detail: metadata.detail } : {}),
16
+ ...(metadata.metadataVersion === 2 && metadata.engine !== undefined ? { engine: metadata.engine } : {}),
17
+ ...(metadata.metadataVersion === 1 && metadata.legacyProfile !== undefined
18
+ ? { legacyProfile: metadata.legacyProfile }
19
+ : {}),
20
+ };
16
21
  }
17
22
  function createUnknownImproveMetrics() {
18
23
  return {
@@ -21,7 +26,7 @@ function createUnknownImproveMetrics() {
21
26
  skipped: 0,
22
27
  skipReasons: {},
23
28
  plannedRefs: 0,
24
- profileFilteredRefs: 0,
29
+ strategyFilteredRefs: 0,
25
30
  actions: {
26
31
  reflect: { ok: 0, failed: 0, cooldown: 0, skipped: 0, guardRejected: 0, skippedByReason: {} },
27
32
  distill: {
@@ -181,12 +186,14 @@ function projectRunMetrics(result) {
181
186
  const plannedRefs = result.plannedRefs;
182
187
  if (Array.isArray(plannedRefs))
183
188
  metrics.plannedRefs += plannedRefs.length;
184
- // profileFilteredRefs (array of {ref, reason}) — 2026-05-27: pre-filter
189
+ // strategyFilteredRefs (array of {ref, reason}) — 2026-05-27: pre-filter
185
190
  // bucket from `collectEligibleRefs` so the metric reflects work the
186
191
  // planner dropped before signal-delta / per-pass dispatch.
187
- const profileFilteredRefs = result.profileFilteredRefs;
188
- if (Array.isArray(profileFilteredRefs))
189
- metrics.profileFilteredRefs += profileFilteredRefs.length;
192
+ // Health v3 reports strategy metrics only. Historical v1 profile filtering
193
+ // remains legacy data and must not be silently relabelled as a strategy metric.
194
+ const strategyFilteredRefs = result.schemaVersion === 2 ? result.strategyFilteredRefs : undefined;
195
+ if (Array.isArray(strategyFilteredRefs))
196
+ metrics.strategyFilteredRefs += strategyFilteredRefs.length;
190
197
  // actions: split reflect / distill by outcome, count others.
191
198
  const actions = result.actions;
192
199
  if (Array.isArray(actions)) {
@@ -496,8 +503,8 @@ function finalizeImproveMetrics(metrics) {
496
503
  */
497
504
  function mergeImproveMetrics(dst, src) {
498
505
  dst.plannedRefs += src.plannedRefs;
499
- // profileFilteredRefs is the count of refs the planner drops up-front for the
500
- // active profile — recomputed against the (stable) stash every run, so it is a
506
+ // strategyFilteredRefs is the count of refs the planner drops up-front for the
507
+ // active strategy — recomputed against the (stable) stash every run, so it is a
501
508
  // snapshot, NOT a per-run increment. Summing it re-counts the same refs each
502
509
  // run (the ~2.4M bug). Set from the most recent run in summarizeImproveRuns.
503
510
  dst.actions.reflect.ok += src.actions.reflect.ok;
@@ -613,22 +620,16 @@ export function summarizeImproveRuns(db, since, until) {
613
620
  // MOST RECENT run's snapshot (current state) — not a sum across runs.
614
621
  let latestStartMs = Number.NEGATIVE_INFINITY;
615
622
  let latestMemorySummary;
616
- let latestProfileFilteredRefs = 0;
623
+ let latestStrategyFilteredRefs = 0;
617
624
  for (const row of rows) {
618
- let result;
619
- try {
620
- result = JSON.parse(row.result_json);
621
- }
622
- catch {
623
- continue;
624
- }
625
+ const result = decodeImproveResult(row.result_json).envelope;
625
626
  const perRow = projectRunMetrics(result);
626
627
  mergeImproveMetrics(accum, perRow);
627
628
  const startMs = new Date(row.started_at).getTime();
628
629
  if (Number.isFinite(startMs) && startMs >= latestStartMs) {
629
630
  latestStartMs = startMs;
630
631
  latestMemorySummary = perRow.memorySummary;
631
- latestProfileFilteredRefs = perRow.profileFilteredRefs;
632
+ latestStrategyFilteredRefs = perRow.strategyFilteredRefs;
632
633
  }
633
634
  // Collect per-phase durations directly off the envelope. consolidation's
634
635
  // duration lives inside the sub-object; memoryInference and graphExtraction
@@ -648,7 +649,7 @@ export function summarizeImproveRuns(db, since, until) {
648
649
  finalizeImproveMetrics(accum);
649
650
  if (latestMemorySummary)
650
651
  accum.memorySummary = latestMemorySummary;
651
- accum.profileFilteredRefs = latestProfileFilteredRefs;
652
+ accum.strategyFilteredRefs = latestStrategyFilteredRefs;
652
653
  accum.wallTime.byPhase = {
653
654
  consolidation: summarizePhaseDurations(phaseDurations.consolidation),
654
655
  memoryInference: summarizePhaseDurations(phaseDurations.memoryInference),
@@ -680,13 +681,8 @@ export function summarizePhaseDurations(samples) {
680
681
  * Used by `akm health --detail per-run`.
681
682
  */
682
683
  export function projectImproveRunSummary(row, wallTimeMs, taskId) {
683
- let result = {};
684
- try {
685
- result = JSON.parse(row.result_json);
686
- }
687
- catch {
688
- // fall through with empty result so per-stage rollups are zeros
689
- }
684
+ const decoded = decodeImproveResult(row.result_json);
685
+ const result = decoded.envelope;
690
686
  const perRow = projectRunMetrics(result);
691
687
  finalizeImproveMetrics(perRow);
692
688
  const orphansPurged = toFiniteNumber(result.orphansPurged);
@@ -699,6 +695,8 @@ export function projectImproveRunSummary(row, wallTimeMs, taskId) {
699
695
  completedAt: row.completed_at,
700
696
  wallTimeMs,
701
697
  ok: row.ok === 1,
698
+ strategy: row.strategy,
699
+ legacyProfile: row.legacyProfile,
702
700
  scope: {
703
701
  mode: row.scope_mode,
704
702
  ...(row.scope_value ? { value: row.scope_value } : {}),
@@ -744,10 +742,10 @@ export function buildImproveSkipSummary(events) {
744
742
  // - Per-occurrence (no `count`): one event per skipped ref → SUM is correct.
745
743
  // - Aggregated snapshot (carries `count`): a single per-run event whose count
746
744
  // is the number of refs that hit a STABLE, whole-stash condition that run
747
- // (`no_new_signal`, `profile_filtered_all_passes`). Each run re-counts the
745
+ // (`no_new_signal`, `strategy_filtered_all_passes`). Each run re-counts the
748
746
  // same stable set, so summing across the window re-counts it N times (the
749
747
  // 2.7M / 3M inflation). For these we keep the MOST RECENT run's count — the
750
- // current snapshot — matching how memorySummary/profileFilteredRefs are
748
+ // current snapshot — matching how memorySummary/strategyFilteredRefs are
751
749
  // handled. Events arrive in chronological (offset) order, so the last
752
750
  // count-bearing event per reason is the latest run's value.
753
751
  const summed = {};
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import { readEvents } from "../../core/events.js";
9
9
  import { LLM_USAGE_EVENT } from "../../llm/usage-persist.js";
10
- import { toFiniteNumber } from "./improve-metrics.js";
10
+ import { decodeLlmUsageRecord } from "../../llm/usage-telemetry.js";
11
11
  /** Stage key used for `llm_usage` events recorded outside any stage scope. */
12
12
  const UNATTRIBUTED_STAGE = "unattributed";
13
13
  function emptyLlmUsageStageAggregate() {
@@ -21,7 +21,7 @@ function emptyLlmUsageStageAggregate() {
21
21
  };
22
22
  }
23
23
  function emptyLlmUsageAggregate() {
24
- return { ...emptyLlmUsageStageAggregate(), byStage: {} };
24
+ return { ...emptyLlmUsageStageAggregate(), byStage: {}, byProcess: {}, byEngine: {} };
25
25
  }
26
26
  /**
27
27
  * Aggregate `llm_usage` events (#576) into a window total plus a per-stage
@@ -32,25 +32,25 @@ function emptyLlmUsageAggregate() {
32
32
  export function summarizeLlmUsage(events) {
33
33
  const aggregate = emptyLlmUsageAggregate();
34
34
  for (const event of events) {
35
- const meta = event.metadata ?? {};
36
- const stageKey = typeof meta.stage === "string" && meta.stage ? meta.stage : UNATTRIBUTED_STAGE;
37
- let stage = aggregate.byStage[stageKey];
38
- if (!stage) {
39
- stage = emptyLlmUsageStageAggregate();
40
- aggregate.byStage[stageKey] = stage;
35
+ const record = decodeLlmUsageRecord(event.metadata);
36
+ if (!record)
37
+ continue;
38
+ const dimensions = [];
39
+ for (const [groups, key] of [
40
+ [aggregate.byStage, record.stage ?? UNATTRIBUTED_STAGE],
41
+ [aggregate.byProcess, record.process ?? UNATTRIBUTED_STAGE],
42
+ [aggregate.byEngine, record.engine ?? UNATTRIBUTED_STAGE],
43
+ ]) {
44
+ groups[key] ??= emptyLlmUsageStageAggregate();
45
+ dimensions.push(groups[key]);
41
46
  }
42
- const durationMs = toFiniteNumber(meta.durationMs);
43
- const promptTokens = toFiniteNumber(meta.promptTokens);
44
- const completionTokens = toFiniteNumber(meta.completionTokens);
45
- const totalTokens = toFiniteNumber(meta.totalTokens);
46
- const reasoningTokens = toFiniteNumber(meta.reasoningTokens);
47
- for (const target of [aggregate, stage]) {
47
+ for (const target of [aggregate, ...dimensions]) {
48
48
  target.calls += 1;
49
- target.totalDurationMs += durationMs;
50
- target.promptTokens += promptTokens;
51
- target.completionTokens += completionTokens;
52
- target.totalTokens += totalTokens;
53
- target.reasoningTokens += reasoningTokens;
49
+ target.totalDurationMs += record.durationMs;
50
+ target.promptTokens += record.promptTokens ?? 0;
51
+ target.completionTokens += record.completionTokens ?? 0;
52
+ target.totalTokens += record.totalTokens ?? 0;
53
+ target.reasoningTokens += record.reasoningTokens ?? 0;
54
54
  }
55
55
  }
56
56
  return aggregate;
@@ -28,6 +28,8 @@ export function renderRunsDetailMd(runs) {
28
28
  const headers = [
29
29
  "ts",
30
30
  "ok",
31
+ "strategy",
32
+ "legacy_profile",
31
33
  "actions",
32
34
  "refl_ok/fail/cd/skip",
33
35
  "distill_q/llm-fail/qrej/cfg/skip",
@@ -54,6 +56,8 @@ export function renderRunsDetailMd(runs) {
54
56
  return [
55
57
  r.startedAt,
56
58
  String(r.ok),
59
+ r.strategy ?? "",
60
+ r.legacyProfile ?? "",
57
61
  String(totalActions),
58
62
  `${r.actions.reflect.ok}/${r.actions.reflect.failed}/${r.actions.reflect.cooldown}/${r.actions.reflect.skipped}`,
59
63
  `${r.actions.distill.queued}/${r.actions.distill.llmFailed}/${r.actions.distill.qualityRejected}/${r.actions.distill.configDisabled}/${r.actions.distill.skipped}`,
@@ -7,6 +7,7 @@
7
7
  * minting rollup, and the WS-5 per-run degradation metrics.
8
8
  */
9
9
  import { appendEvent, readEvents } from "../../core/events.js";
10
+ import { decodeImproveResult } from "../../core/improve-result.js";
10
11
  import { queryImproveRuns } from "../../storage/repositories/improve-runs-repository.js";
11
12
  import { listProposalGateDecisions, listStateProposals } from "../../storage/repositories/proposals-repository.js";
12
13
  import { gateDecisionsToSamples, summarizeCalibration } from "../improve/calibration.js";
@@ -221,7 +222,7 @@ export function computeDegradationMetrics(db, since, until) {
221
222
  let totalProcessed = 0;
222
223
  for (const row of runs) {
223
224
  try {
224
- const result = JSON.parse(row.result_json);
225
+ const result = decodeImproveResult(row.result_json).envelope;
225
226
  const cons = result.consolidation;
226
227
  if (cons) {
227
228
  totalContradicted += toFiniteNumber(cons.contradicted);
@@ -18,7 +18,8 @@
18
18
  */
19
19
  import fs from "node:fs";
20
20
  import path from "node:path";
21
- import { CURRENT_CONFIG_VERSION, compareConfigVersion } from "../../core/config/config-migration.js";
21
+ import { CURRENT_CONFIG_VERSION } from "../../core/config/config-schema.js";
22
+ import { compareConfigVersion } from "../../core/config/config-version.js";
22
23
  const GROUP_OTHER_BITS = 0o077;
23
24
  const OFFENDER_EVIDENCE_CAP = 50;
24
25
  function modeOctal(mode) {
@@ -182,10 +183,10 @@ export function collectEgressAdvisory(config) {
182
183
  continue;
183
184
  endpoints.push(`source ${source.name ?? "(unnamed)"} (${source.type ?? "?"}): ${source.url}`);
184
185
  }
185
- for (const [name, profile] of Object.entries(config.profiles?.llm ?? {})) {
186
- if (!profile?.endpoint)
186
+ for (const [name, engine] of Object.entries(config.engines ?? {})) {
187
+ if (engine?.kind !== "llm" || !engine.endpoint)
187
188
  continue;
188
- endpoints.push(`llm ${name}: ${profile.endpoint}`);
189
+ endpoints.push(`llm ${name}: ${engine.endpoint}`);
189
190
  }
190
191
  if (config.embedding?.endpoint)
191
192
  endpoints.push(`embedding: ${config.embedding.endpoint}`);
@@ -284,7 +284,7 @@ export function akmHealth(options = {}) {
284
284
  runs = buildPerRunSummaries(db, since);
285
285
  }
286
286
  return {
287
- schemaVersion: 2,
287
+ schemaVersion: 3,
288
288
  ok: !hardFailure,
289
289
  status,
290
290
  since: topLevelSince,
@@ -33,9 +33,9 @@ const PROMPT_OVERHEAD_TOKENS = 2_000;
33
33
  * chunk content.
34
34
  * - When the HTTP path is used (an LLM profile is selected), only the akm
35
35
  * system prompt and user prompt are sent, so the budget can be set to the
36
- * model's actual context length via profiles.llm[defaults.llm].contextLength.
36
+ * model's actual context length via the selected LLM engine's contextLength.
37
37
  *
38
- * Set profiles.llm[defaults.llm].contextLength in your config file to the
38
+ * Set the selected LLM engine's contextLength in your config file to the
39
39
  * model's actual context window to allow larger chunks on the HTTP path.
40
40
  */
41
41
  export const DEFAULT_CONTEXT_LENGTH_TOKENS = 4_096;
@@ -29,12 +29,13 @@ import { openStateDatabase, withStateDb } from "../../core/state-db.js";
29
29
  import { warn } from "../../core/warn.js";
30
30
  import { commitWriteTargetBoundary, deleteAssetFromSource, resolveWriteTarget, writeAssetToSource, } from "../../core/write-source.js";
31
31
  import { closeDatabase, findEntryIdByRef, getAllEntries, getEntryById, getNeighborsByEntryId, openExistingDatabase, } from "../../indexer/db/db.js";
32
- import { resolveImproveProcessRunnerFromProfile, runnerIsLlm } from "../../integrations/agent/runner.js";
32
+ import { materializeLlmRunnerConnection, resolveImproveProcessRunner } from "../../integrations/agent/runner.js";
33
33
  import { chatCompletion } from "../../llm/client.js";
34
34
  import { cosineSimilarity, embedBatch, resolveEmbeddingModelId } from "../../llm/embedder.js";
35
- import { isLlmFeatureEnabled, tryLlmFeature } from "../../llm/feature-gate.js";
35
+ import { tryLlmFeature } from "../../llm/feature-gate.js";
36
36
  import { getConsolidationJudgedMap, upsertConsolidationJudged, } from "../../storage/repositories/consolidation-repository.js";
37
37
  import { getBodyEmbeddings, upsertBodyEmbeddings } from "../../storage/repositories/embeddings-repository.js";
38
+ import { resolveImproveStrategy, resolveProcessEnabled } from "./improve-strategies.js";
38
39
  // Chunk sizing + per-chunk prompt assembly live in ./consolidate/chunking.
39
40
  // Imported for internal use by the orchestrator and re-exported for importers.
40
41
  import { buildChunkPrompt, computeSafeChunkSize, DEFAULT_CONTEXT_LENGTH_TOKENS } from "./consolidate/chunking.js";
@@ -493,13 +494,13 @@ function archiveMemory(filePath, stashDir, ref, reason, opIndex, supersededBy, w
493
494
  *
494
495
  * Priority order (mirrors extract / reflect / distill — see
495
496
  * `src/commands/extract.ts:421-438` and the canonical
496
- * `resolveImproveProcessRunnerFromProfile` pattern):
497
+ * `resolveImproveProcessRunner` pattern):
497
498
  *
498
- * 1. `profiles.improve.default.processes.consolidate.profile` (or `mode`)
499
- * via {@link resolveImproveProcessRunnerFromProfile}. Lets the user pin
499
+ * 1. `improve.strategies.<name>.processes.consolidate.engine`
500
+ * via {@link resolveImproveProcessRunner}. Lets the user pin
500
501
  * a dedicated model (e.g. `ministral-3b`) for consolidation instead of
501
- * whatever `defaults.llm` happens to be.
502
- * 2. `getDefaultLlmConfig(config)` — the baseline default LLM profile.
502
+ * whatever `defaults.llmEngine` happens to be.
503
+ * 2. `getDefaultLlmConfig(config)` — the baseline default LLM engine.
503
504
  *
504
505
  * Regression guard (2026-05-26): before this resolver, `akmConsolidate`
505
506
  * called `getDefaultLlmConfig` directly and silently ignored a configured
@@ -509,13 +510,9 @@ function archiveMemory(filePath, stashDir, ref, reason, opIndex, supersededBy, w
509
510
  * `/tmp/akm-health-investigations/consolidation-no-op.md`.
510
511
  */
511
512
  function resolveConsolidateLlmConfig(config, activeProfile) {
512
- const consolidateProcess = getImproveProcessConfig(config, "consolidate", activeProfile);
513
- const runnerSpec = resolveImproveProcessRunnerFromProfile(consolidateProcess, config);
514
- if (runnerSpec && runnerIsLlm(runnerSpec)) {
515
- return runnerSpec.connection;
516
- }
517
- // Non-LLM runner modes (agent/sdk) don't apply to consolidate's HTTP path;
518
- // fall back to the default LLM profile rather than disabling the pass.
513
+ const runnerSpec = resolveImproveProcessRunner(activeProfile, "consolidate", config);
514
+ if (runnerSpec)
515
+ return materializeLlmRunnerConnection(runnerSpec);
519
516
  return getDefaultLlmConfig(config);
520
517
  }
521
518
  // ── Judged-state cache (#581) ────────────────────────────────────────────────
@@ -578,8 +575,9 @@ export async function akmConsolidate(opts = {}) {
578
575
  // standalone `akm consolidate` gets a self-contained token.
579
576
  const sourceRun = opts.sourceRun ?? `consolidate-${startMs}`;
580
577
  const config = opts.config ?? loadConfig();
578
+ opts = { ...opts, improveProfile: opts.improveProfile ?? resolveImproveStrategy(undefined, config).config };
581
579
  const stashDir = opts.stashDir ?? resolveStashDir();
582
- if (!isLlmFeatureEnabled(config, "memory_consolidation")) {
580
+ if (!resolveProcessEnabled("consolidate", opts.improveProfile ?? resolveImproveStrategy(undefined, config).config)) {
583
581
  return makeConsolidateResult({
584
582
  dryRun: opts.dryRun ?? false,
585
583
  target: opts.target ?? stashDir,
@@ -868,9 +866,11 @@ async function planConsolidation(opts, config, stashDir, startMs, memories, warn
868
866
  // CLI. The agent CLI is for interactive agent sessions (reflect, propose);
869
867
  // structured JSON generation works better and faster via HTTP.
870
868
  //
871
- // Honor `profiles.improve.default.processes.consolidate.profile` first; fall
872
- // back to the default LLM. See {@link resolveConsolidateLlmConfig}.
873
- const llmConfig = resolveConsolidateLlmConfig(config, opts.improveProfile);
869
+ // Improve supplies a frozen connection; standalone consolidate resolves its
870
+ // selected strategy/default engine here.
871
+ const llmConfig = Object.hasOwn(opts, "llmConfig")
872
+ ? (opts.llmConfig ?? undefined)
873
+ : resolveConsolidateLlmConfig(config, opts.improveProfile);
874
874
  const isHttpPath = !!llmConfig;
875
875
  // Chunk sizing: derive a safe chunk size from the configured model context
876
876
  // window so that the full prompt (system prompt + chunk user prompt) never
@@ -1087,7 +1087,7 @@ async function planConsolidation(opts, config, stashDir, startMs, memories, warn
1087
1087
  catch (e) {
1088
1088
  return { ok: false, error: String(e) };
1089
1089
  }
1090
- }, { ok: false, error: fallbackError });
1090
+ }, { ok: false, error: fallbackError }, { enabled: true });
1091
1091
  let raw = await callChunkLlm(`chunk ${chunkIdx + 1} failed`);
1092
1092
  if (!raw.ok) {
1093
1093
  // Single retry with 2s backoff before recording chunk as lost.
@@ -1230,7 +1230,7 @@ async function planConsolidation(opts, config, stashDir, startMs, memories, warn
1230
1230
  * the op-handlers' `pushSkipReason`. Behavior-identical to the former inlined
1231
1231
  * write block. Never invoked on the dry-run or aborted-confirm paths.
1232
1232
  */
1233
- async function applyConsolidationPlan(config, stashDir, sourceRun, memories, warnings, allOps, accounting, dedupCollapsed, activeProfile) {
1233
+ async function applyConsolidationPlan(config, stashDir, sourceRun, memories, warnings, allOps, accounting, dedupCollapsed, activeProfile, llmConfig) {
1234
1234
  // -- Phase B + writes -------------------------------------------------------
1235
1235
  const target = resolveWriteTarget(config);
1236
1236
  const timestamp = timestampForFilename();
@@ -1258,6 +1258,7 @@ async function applyConsolidationPlan(config, stashDir, sourceRun, memories, war
1258
1258
  const opCtx = {
1259
1259
  config,
1260
1260
  improveProfile: activeProfile,
1261
+ llmConfig: llmConfig ?? null,
1261
1262
  stashDir,
1262
1263
  sourceRun,
1263
1264
  target,
@@ -1380,7 +1381,9 @@ async function akmConsolidateInner(opts, config, stashDir, startMs, sourceRun, w
1380
1381
  }
1381
1382
  }
1382
1383
  // -- Pass 3: execute the plan against the filesystem ------------------------
1383
- const { merged, deleted, contradicted, mergeFloorViolations, mergedSecondaries, promoted } = await applyConsolidationPlan(config, stashDir, sourceRun, memories, warnings, allOps, accounting, dedupCollapsed, opts.improveProfile);
1384
+ const { merged, deleted, contradicted, mergeFloorViolations, mergedSecondaries, promoted } = await applyConsolidationPlan(config, stashDir, sourceRun, memories, warnings, allOps, accounting, dedupCollapsed, opts.improveProfile, Object.hasOwn(opts, "llmConfig")
1385
+ ? (opts.llmConfig ?? null)
1386
+ : (resolveConsolidateLlmConfig(config, opts.improveProfile) ?? null));
1384
1387
  const runDurationMs = Date.now() - startMs;
1385
1388
  const budgetFraction = opts.runBudgetMs !== undefined && opts.runBudgetMs > 0 ? runDurationMs / opts.runBudgetMs : undefined;
1386
1389
  return {
@@ -1502,7 +1505,7 @@ export async function handleMergeOp(op, opIndex, ctx) {
1502
1505
  emitMergeFailureSkips("merge_read_failed");
1503
1506
  return;
1504
1507
  }
1505
- const mergeResult = await generateMergedContent(config, op.primary, primaryBody, op.secondaries, memoryByRef, ctx.improveProfile);
1508
+ const mergeResult = await generateMergedContent(config, op.primary, primaryBody, op.secondaries, memoryByRef, ctx.improveProfile, ctx.llmConfig);
1506
1509
  if ("error" in mergeResult) {
1507
1510
  warnings.push(`Merge: ${mergeResult.error} for ${mergeResult.detail}.`);
1508
1511
  emitMergeFailureSkips(mergeResult.error);
@@ -2147,7 +2150,7 @@ function loadMemoriesForSource(source, stashDir, warnings) {
2147
2150
  }
2148
2151
  return memories;
2149
2152
  }
2150
- async function generateMergedContent(config, primaryRef, primaryBody, secondaryRefs, memoryByRef, activeProfile) {
2153
+ async function generateMergedContent(config, primaryRef, primaryBody, secondaryRefs, memoryByRef, activeProfile, resolvedLlmConfig) {
2151
2154
  // Only handle single-secondary merges per design (one call per merge op)
2152
2155
  const secRef = secondaryRefs[0];
2153
2156
  const secEntry = memoryByRef.get(secRef);
@@ -2191,7 +2194,7 @@ async function generateMergedContent(config, primaryRef, primaryBody, secondaryR
2191
2194
  .join("\n");
2192
2195
  // Use the same per-process profile resolution as the chunk-plan call above
2193
2196
  // so the merge generation step doesn't silently revert to the default LLM.
2194
- const llmConfig = resolveConsolidateLlmConfig(config, activeProfile);
2197
+ const llmConfig = resolvedLlmConfig === null ? undefined : (resolvedLlmConfig ?? resolveConsolidateLlmConfig(config, activeProfile));
2195
2198
  const result = await tryLlmFeature("memory_consolidation", config, async () => {
2196
2199
  if (!llmConfig)
2197
2200
  return { ok: false, error: "No LLM configured for consolidation" };
@@ -2204,7 +2207,7 @@ async function generateMergedContent(config, primaryRef, primaryBody, secondaryR
2204
2207
  catch (e) {
2205
2208
  return { ok: false, error: String(e) };
2206
2209
  }
2207
- }, { ok: false, error: `merge content generation failed for ${primaryRef}` });
2210
+ }, { ok: false, error: `merge content generation failed for ${primaryRef}` }, { enabled: true });
2208
2211
  if (!result.ok) {
2209
2212
  return {
2210
2213
  error: "merge_transport_failed",
@@ -18,11 +18,8 @@
18
18
  */
19
19
  import fs from "node:fs";
20
20
  import { parseFrontmatter } from "../../../core/asset/frontmatter.js";
21
- import { getDefaultLlmConfig } from "../../../core/config/config.js";
22
- import { ConfigError } from "../../../core/errors.js";
23
21
  import { appendEvent } from "../../../core/events.js";
24
22
  import { parseEmbeddedJsonResponse } from "../../../llm/client.js";
25
- import { isLlmFeatureEnabled } from "../../../llm/feature-gate.js";
26
23
  import { createProposal, isProposalSkipped } from "../../proposal/repository.js";
27
24
  import { assessMemoryKnowledgePromotionCandidate } from "../distill-promotion-policy.js";
28
25
  import { persistOutputEncodingSalience, runLessonQualityJudge, writeQualityRejection } from "./quality-gate.js";
@@ -62,7 +59,7 @@ export async function promoteMemoryToKnowledge(ctx) {
62
59
  }
63
60
  })()
64
61
  : null;
65
- if (existingKnowledgeContent && config && getDefaultLlmConfig(config)) {
62
+ if (existingKnowledgeContent && ctx.llmConfig) {
66
63
  // Existing content found: call LLM for contradiction-resolution merge.
67
64
  const mergePrompt = [
68
65
  "You are merging two versions of a knowledge document.",
@@ -81,11 +78,7 @@ export async function promoteMemoryToKnowledge(ctx) {
81
78
  "```",
82
79
  ].join("\n");
83
80
  try {
84
- const mergeLlm = getDefaultLlmConfig(config);
85
- if (!mergeLlm) {
86
- throw new ConfigError("LLM is not configured for distillation merge.", "LLM_NOT_CONFIGURED");
87
- }
88
- const mergeResponse = await chat(mergeLlm, [
81
+ const mergeResponse = await chat(ctx.llmConfig, [
89
82
  { role: "system", content: "Return only valid JSON. No prose." },
90
83
  { role: "user", content: mergePrompt },
91
84
  ]);
@@ -122,7 +115,7 @@ export async function promoteMemoryToKnowledge(ctx) {
122
115
  // The reviewer will see both versions in the proposal diff.
123
116
  }
124
117
  }
125
- else if (existingKnowledgeContent && config && !getDefaultLlmConfig(config)) {
118
+ else if (existingKnowledgeContent) {
126
119
  // No LLM configured: include existing content as context in the proposal
127
120
  // so the reviewer can do the contradiction resolution manually.
128
121
  resolvedPromotionContent = [
@@ -141,10 +134,10 @@ export async function promoteMemoryToKnowledge(ctx) {
141
134
  // D-5 / #388: Three-band system — review_needed band queues to proposal
142
135
  // queue with review_needed outcome rather than auto-rejecting.
143
136
  let knowledgeJudgeConfidence;
144
- if (isLlmFeatureEnabled(config, "lesson_quality_gate")) {
137
+ if (ctx.strategy?.processes?.distill?.qualityGate?.enabled ?? true) {
145
138
  // D-4 / #390: retrieve top-3 similar lessons for dedup check in judge.
146
139
  const similarLessons = await fetchSimilarLessonsFn(resolvedPromotionContent.slice(0, 500), 3);
147
- const judgeResult = await runLessonQualityJudge(config, resolvedPromotionContent, assetContent ?? "", chat, similarLessons.length > 0 ? similarLessons : undefined);
140
+ const judgeResult = await runLessonQualityJudge(config, resolvedPromotionContent, assetContent ?? "", chat, similarLessons.length > 0 ? similarLessons : undefined, ctx.llmConfig);
148
141
  if (!judgeResult.pass) {
149
142
  if (judgeResult.reviewNeeded) {
150
143
  // Uncertainty band (2.5–3.5): queue as review_needed instead of rejecting.
@@ -102,10 +102,8 @@ export function buildJudgePrompt(lessonContent, sourceContent, similarLessons) {
102
102
  * Run the LLM-as-judge quality gate on a proposal's content.
103
103
  *
104
104
  * Exported so reflect.ts can apply the same gate to reflect proposals (R-5 / #374).
105
- * Gated by the flag name `lesson_quality_gate` (or its alias
106
- * `proposal_quality_gate`) via {@link isLlmFeatureEnabled} which reads
107
- * `profiles.improve.default.processes.distill.qualityGate.enabled` (and the
108
- * corresponding `.reflect.qualityGate.enabled` for proposals).
105
+ * The selected strategy's distill/reflect quality-gate setting is resolved by
106
+ * the caller before this function runs.
109
107
  *
110
108
  * Fail-CLOSED (07 P0-2): returns `pass: false` (score -1) on timeout, parse
111
109
  * failure, or missing LLM. Minted content that cannot be judged is rejected,
@@ -114,12 +112,12 @@ export function buildJudgePrompt(lessonContent, sourceContent, similarLessons) {
114
112
  */
115
113
  export async function runLessonQualityJudge(config, lessonContent, sourceContent, chat,
116
114
  /** D-4 / #390: top-3 similar existing lessons for dedup check. */
117
- similarLessons) {
118
- const llmConfig = getDefaultLlmConfig(config);
115
+ similarLessons, llmConfigOverride) {
116
+ const llmConfig = llmConfigOverride ?? getDefaultLlmConfig(config);
119
117
  if (!llmConfig) {
120
118
  return { pass: false, score: -1, reason: "no LLM configured — cannot judge, failing closed" };
121
119
  }
122
- const judgeLlmConfig = llmConfig.judgeModel ? { ...llmConfig, model: llmConfig.judgeModel } : llmConfig;
120
+ const judgeLlmConfig = llmConfig;
123
121
  const JUDGE_TIMEOUT_MS = 8_000;
124
122
  try {
125
123
  const raw = await Promise.race([
@@ -69,8 +69,9 @@ import { withStateDb } from "../../core/state-db.js";
69
69
  import { warnVerbose } from "../../core/warn.js";
70
70
  import { closeDatabase, getAllEntries, openIndexDatabase } from "../../indexer/db/db.js";
71
71
  import { resolveAssetPath } from "../../indexer/walk/path-resolver.js";
72
+ import { materializeLlmRunnerConnection, resolveImproveProcessRunner } from "../../integrations/agent/runner.js";
72
73
  import { chatCompletion, parseEmbeddedJsonResponse } from "../../llm/client.js";
73
- import { isLlmFeatureEnabled, tryLlmFeature } from "../../llm/feature-gate.js";
74
+ import { tryLlmFeature } from "../../llm/feature-gate.js";
74
75
  import { createProposal, isProposalSkipped, listProposals, } from "../proposal/repository.js";
75
76
  import { stripFrontmatterBody as stripBodyForFidelity } from "./dedup.js";
76
77
  import { autoRepairLessonFrontmatter, autoSwapDescriptionWhenToUse, collectLessonQualityFindings, repairLessonDescriptionTruncation, } from "./distill/content-repair.js";
@@ -79,6 +80,7 @@ import { fetchTopSimilarLessons, persistOutputEncodingSalience, runLessonQuality
79
80
  import { buildClsContext, checkDistillFidelity } from "./distill-guards.js";
80
81
  import { deriveKnowledgeRef } from "./distill-promotion-policy.js";
81
82
  import { buildRefVocabulary, scoreEncodingSalience } from "./encoding-salience.js";
83
+ import { resolveImproveStrategy, resolveProcessEnabled } from "./improve-strategies.js";
82
84
  import { computeSalience, upsertAssetSalience } from "./salience.js";
83
85
  // Re-exported for `reflect.ts`, which applies the same LLM-as-judge gate to
84
86
  // reflect proposals (R-5 / #374).
@@ -474,8 +476,15 @@ export async function akmDistill(options) {
474
476
  };
475
477
  }
476
478
  const config = options.config ?? loadConfig();
479
+ options = { ...options, improveProfile: options.improveProfile ?? resolveImproveStrategy(undefined, config).config };
477
480
  const stash = options.stashDir ?? resolveStashDir();
478
481
  const chat = options.chat ?? chatCompletion;
482
+ const distillLlm = Object.hasOwn(options, "llmConfig")
483
+ ? (options.llmConfig ?? undefined)
484
+ : (() => {
485
+ const runner = resolveImproveProcessRunner(options.improveProfile, "distill", config);
486
+ return runner ? materializeLlmRunnerConnection(runner) : getDefaultLlmConfig(config);
487
+ })();
479
488
  const lookup = options.lookupFn ?? defaultLookup;
480
489
  const readEventsImpl = options.readEventsFn ?? readEvents;
481
490
  // R1 opt-out must flow into every computeSalience call this command makes so
@@ -602,6 +611,8 @@ export async function akmDistill(options) {
602
611
  assetContent,
603
612
  filteredEvents,
604
613
  config,
614
+ strategy: options.improveProfile,
615
+ llmConfig: distillLlm,
605
616
  chat,
606
617
  stash,
607
618
  lookup,
@@ -684,12 +695,11 @@ export async function akmDistill(options) {
684
695
  const distillSchema = effectiveProposalKind === "knowledge" ? DISTILL_KNOWLEDGE_JSON_SCHEMA : DISTILL_LESSON_JSON_SCHEMA;
685
696
  let fallbackReason;
686
697
  const raw = await tryLlmFeature("distill", config, async () => {
687
- const distillLlm = getDefaultLlmConfig(config);
688
698
  if (!distillLlm) {
689
699
  // No LLM connection configured — treat as gate-disabled. Throwing
690
700
  // here lets `tryLlmFeature` route us through the "error" fallback,
691
701
  // which is the same graceful skipped path.
692
- throw new ConfigError("No LLM connection configured. Set `defaults.llm` and a profile under `profiles.llm`.", "LLM_NOT_CONFIGURED");
702
+ throw new ConfigError("No LLM engine configured. Set defaults.llmEngine or improve.strategies.<name>.processes.distill.engine.", "LLM_NOT_CONFIGURED");
693
703
  }
694
704
  // Production path: pass the JSON schema so providers that honour
695
705
  // `response_format: json_schema` enforce shape upstream. Providers that
@@ -701,6 +711,7 @@ export async function akmDistill(options) {
701
711
  // functions (which return markdown strings) continue to work.
702
712
  return chat(distillLlm, messages);
703
713
  }, null, {
714
+ enabled: resolveProcessEnabled("distill", options.improveProfile ?? resolveImproveStrategy(undefined, config).config),
704
715
  onFallback: (evt) => {
705
716
  fallbackReason = evt.reason;
706
717
  // Log the fallback reason; the caller (raw === null path) handles
@@ -822,10 +833,10 @@ export async function akmDistill(options) {
822
833
  // D-5 / #388: Three-band system — review_needed band queues a proposal
823
834
  // with review_needed outcome rather than auto-rejecting.
824
835
  let lessonJudgeConfidence;
825
- if (isLlmFeatureEnabled(config, "lesson_quality_gate")) {
836
+ if (options.improveProfile?.processes?.distill?.qualityGate?.enabled ?? true) {
826
837
  // D-4 / #390: retrieve top-3 similar lessons for dedup check in judge.
827
838
  const similarLessons = await fetchSimilarLessonsFn(content.slice(0, 500), 3);
828
- const judgeResult = await runLessonQualityJudge(config, content, assetContent ?? "", chat, similarLessons.length > 0 ? similarLessons : undefined);
839
+ const judgeResult = await runLessonQualityJudge(config, content, assetContent ?? "", chat, similarLessons.length > 0 ? similarLessons : undefined, distillLlm);
829
840
  if (!judgeResult.pass) {
830
841
  if (judgeResult.reviewNeeded) {
831
842
  return writeQualityRejection(stash, inputRef, effectiveLessonRef, content, judgeResult.score, judgeResult.reason, {