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
@@ -4,7 +4,7 @@
4
4
  import path from "node:path";
5
5
  import { parseAssetRef } from "../../core/asset/asset-ref.js";
6
6
  import { daysToMs } from "../../core/common.js";
7
- import { loadConfig } from "../../core/config/config.js";
7
+ import { DEFAULT_GRAPH_EXTRACTION_BATCH_SIZE, loadConfig } from "../../core/config/config.js";
8
8
  import { UsageError } from "../../core/errors.js";
9
9
  import { appendEvent } from "../../core/events.js";
10
10
  import { openLogsDatabase, purgeOldTaskLogs } from "../../core/logs-db.js";
@@ -12,11 +12,11 @@ import { getDbPath } from "../../core/paths.js";
12
12
  import { withStateDb } from "../../core/state-db.js";
13
13
  import { info, warn } from "../../core/warn.js";
14
14
  import { closeDatabase, openIndexDatabase } from "../../indexer/db/db.js";
15
- import { runGraphExtractionPass } from "../../indexer/graph/graph-extraction.js";
15
+ import { DEFAULT_GRAPH_EXTRACTION_INCLUDE_TYPES, runGraphExtractionPass, } from "../../indexer/graph/graph-extraction.js";
16
16
  import { withIndexWriterLease } from "../../indexer/index-writer-lock.js";
17
17
  import { collectPendingMemories, runMemoryInferencePass, } from "../../indexer/passes/memory-inference.js";
18
18
  import { getWritableStashDirs, resolveSourceEntries } from "../../indexer/search/search-source.js";
19
- import { resolveImproveProcessRunnerFromProfile } from "../../integrations/agent/runner.js";
19
+ import { materializeLlmRunnerConnection } from "../../integrations/agent/runner.js";
20
20
  import { isProcessEnabled } from "../../llm/feature-gate.js";
21
21
  import { withLlmStage } from "../../llm/usage-telemetry.js";
22
22
  import { purgeOldCycleMetrics } from "../../storage/repositories/canaries-repository.js";
@@ -31,7 +31,7 @@ import { deriveKnowledgeRef } from "./distill-promotion-policy.js";
31
31
  import { findAssetFilePath, isDistillCandidateRef } from "./eligibility.js";
32
32
  import { writeEvalCase } from "./eval-cases.js";
33
33
  import { makeGateConfig, runAutoAcceptGate } from "./improve-auto-accept.js";
34
- import { resolveProcessEnabled, shouldSkipRef } from "./improve-profiles.js";
34
+ import { shouldSkipRef } from "./improve-strategies.js";
35
35
  // The pre-loop preparation pipeline lives in ./preparation.
36
36
  import { maybeAutoTuneThreshold } from "./preparation.js";
37
37
  import { akmProcedural } from "./procedural.js";
@@ -41,7 +41,7 @@ import { errMessage, refSlug } from "./shared.js";
41
41
  // ── improve loop / post-loop / maintenance stages ───────────────────
42
42
  // The cycle stages run by akmImprove, extracted from improve.ts.
43
43
  export async function runImproveLoopStage(args) {
44
- const { scope, options, primaryStashDir, reflectFn, distillFn, loopRefs, actions, signalBearingSet, distillCooledRefs, distillOnlyRefs, recentErrors, rejectedProposalsByRef, utilityMap, startMs, budgetMs, eventsCtx, improveProfile, } = args;
44
+ const { scope, options, primaryStashDir, reflectFn, distillFn, loopRefs, actions, signalBearingSet, distillCooledRefs, distillOnlyRefs, recentErrors, rejectedProposalsByRef, utilityMap, startMs, budgetMs, eventsCtx, improveProfile, resolvedPlan, } = args;
45
45
  // O-1 (#364): compute remaining budget at call time so each sub-call
46
46
  // receives only its fair share of the wall-clock budget.
47
47
  const remainingBudgetMs = () => Math.max(0, budgetMs - (Date.now() - startMs));
@@ -206,26 +206,22 @@ export async function runImproveLoopStage(args) {
206
206
  // O-1 (#364): pass remaining budget as timeoutMs so the agent spawn is
207
207
  // bounded by the wall-clock deadline rather than the default per-profile timeout.
208
208
  const reflectBudgetMs = remainingBudgetMs();
209
- // Wire profile.processes.reflect.{mode, profile, timeoutMs} into the reflect
210
- // dispatch when present. Falls back to akmReflect's own config-based resolution
211
- // (profiles.improve.<name>.processes.reflect → defaults.llm) when the profile
212
- // does not specify.
213
- const reflectProfileRunner = resolveImproveProcessRunnerFromProfile(improveProfile.processes?.reflect, options.config ?? loadConfig());
209
+ // Use the runner frozen in the invocation plan; no leaf re-resolution.
210
+ const reflectProfileRunner = resolvedPlan.processes.reflect.runner;
214
211
  const reflectCallArgs = {
215
212
  ref: planned.ref,
216
213
  task: options.task,
217
- // Active profile so reflect's per-process reads honor `--profile`.
214
+ // Active strategy supplies non-engine process tuning.
218
215
  ...(improveProfile ? { improveProfile } : {}),
216
+ config: options.config,
219
217
  ...(options.stashDir ? { stashDir: options.stashDir } : {}),
220
218
  ...(reflectErrors.length > 0 ? { avoidPatterns: [...reflectErrors] } : {}),
221
- agentProcess: options.agentProcess ?? "reflect",
222
219
  eventSource: "improve",
223
220
  // #639 — resolve the low-value filter from the ACTIVE improve profile
224
- // (default off when unset), so the running profile decides instead of
225
- // a hardcoded profiles.improve.default path.
221
+ // (default off when unset), so the running strategy decides.
226
222
  lowValueFilter: improveProfile.processes?.reflect?.lowValueFilter?.enabled === true,
227
223
  ...(reflectBudgetMs > 0 ? { timeoutMs: reflectBudgetMs } : {}),
228
- ...(reflectProfileRunner ? { runner: reflectProfileRunner } : {}),
224
+ runner: reflectProfileRunner ?? null,
229
225
  // Attribution: carry the eligibility lane so reflect stamps it on
230
226
  // the reflect_invoked event and the persisted proposal.
231
227
  ...(planned.eligibilitySource ? { eligibilitySource: planned.eligibilitySource } : {}),
@@ -241,11 +237,19 @@ export async function runImproveLoopStage(args) {
241
237
  if (remainingBudgetMs() <= 0)
242
238
  break;
243
239
  // draftMode: skip DB write so each sample doesn't create a proposal.
244
- samples.push(await withLlmStage("reflect", () => reflectFn({ ...reflectCallArgs, draftMode: true })));
240
+ samples.push(await withLlmStage("reflect", () => reflectFn({ ...reflectCallArgs, draftMode: true }), {
241
+ engine: resolvedPlan.processes.reflect.runner?.engine,
242
+ process: "reflect",
243
+ }));
245
244
  }
246
245
  const winner = pickMajorityVote(samples.length > 0
247
246
  ? samples
248
- : [await withLlmStage("reflect", () => reflectFn({ ...reflectCallArgs, draftMode: true }))]);
247
+ : [
248
+ await withLlmStage("reflect", () => reflectFn({ ...reflectCallArgs, draftMode: true }), {
249
+ engine: resolvedPlan.processes.reflect.runner?.engine,
250
+ process: "reflect",
251
+ }),
252
+ ]);
249
253
  // Persist only the majority-vote winner as a single real proposal.
250
254
  if (winner.ok && primaryStashDir) {
251
255
  const persistResult = createProposal(primaryStashDir, {
@@ -259,11 +263,12 @@ export async function runImproveLoopStage(args) {
259
263
  });
260
264
  reflectResult = isProposalSkipped(persistResult)
261
265
  ? {
262
- schemaVersion: 1,
266
+ schemaVersion: 2,
263
267
  ok: false,
264
268
  reason: "cooldown",
265
269
  error: `SC proposal skipped: ${persistResult.message}`,
266
270
  ref: winner.ref,
271
+ engine: winner.engine,
267
272
  exitCode: null,
268
273
  }
269
274
  : { ...winner, proposal: persistResult };
@@ -273,7 +278,10 @@ export async function runImproveLoopStage(args) {
273
278
  }
274
279
  }
275
280
  else {
276
- reflectResult = await withLlmStage("reflect", () => reflectFn(reflectCallArgs));
281
+ reflectResult = await withLlmStage("reflect", () => reflectFn(reflectCallArgs), {
282
+ engine: resolvedPlan.processes.reflect.runner?.engine,
283
+ process: "reflect",
284
+ });
277
285
  }
278
286
  const isCooldown = !reflectResult.ok && reflectResult.reason === "cooldown";
279
287
  // Content-policy guard hits (reflect size-rail rejections) are NOT
@@ -327,7 +335,7 @@ export async function runImproveLoopStage(args) {
327
335
  metadata: {
328
336
  ok: reflectResult.ok,
329
337
  durationMs: reflectResult.ok ? reflectResult.durationMs : undefined,
330
- agentProfile: reflectResult.ok ? reflectResult.agentProfile : undefined,
338
+ engine: reflectResult.engine,
331
339
  reason: reflectResult.ok ? undefined : reflectResult.reason,
332
340
  },
333
341
  }, eventsCtx);
@@ -478,10 +486,14 @@ export async function runImproveLoopStage(args) {
478
486
  ...(options.stashDir ? { stashDir: options.stashDir } : {}),
479
487
  // Active profile so distill's per-process reads honor `--profile`.
480
488
  ...(improveProfile ? { improveProfile } : {}),
489
+ config: options.config,
490
+ llmConfig: resolvedPlan.processes.distill.runner
491
+ ? materializeLlmRunnerConnection(resolvedPlan.processes.distill.runner)
492
+ : null,
481
493
  // Attribution: carry the eligibility lane so distill stamps it on the
482
494
  // distill_invoked event and the persisted proposal.
483
495
  ...(planned.eligibilitySource ? { eligibilitySource: planned.eligibilitySource } : {}),
484
- }));
496
+ }), { engine: resolvedPlan.processes.distill.runner?.engine, process: "distill" });
485
497
  actions.push({ ref: planned.ref, mode: "distill", result: distillResult });
486
498
  if (distillResult.outcome === "queued" && distillResult.proposal) {
487
499
  const distillGr = await runAutoAcceptGate([{ proposalId: distillResult.proposal.id, confidence: distillResult.proposal.confidence }], distillGateCfg);
@@ -593,7 +605,7 @@ export async function runImproveLoopStage(args) {
593
605
  return { reflectsWithErrorContext, memoryRefsForInference, gateAutoAcceptedCount, gateAutoAcceptFailedCount };
594
606
  }
595
607
  export async function runImprovePostLoopStage(args) {
596
- const { scope, options, primaryStashDir, actionableRefs, appliedCleanup, cleanupWarnings, memoryRefsForInference, reindexFn, eventsCtx, budgetSignal, improveProfile, consolidationRan, } = args;
608
+ const { scope, options, primaryStashDir, actionableRefs, appliedCleanup, cleanupWarnings, memoryRefsForInference, reindexFn, eventsCtx, budgetSignal, improveProfile, resolvedPlan, consolidationRan, } = args;
597
609
  const allWarnings = [...cleanupWarnings, ...(appliedCleanup?.warnings ?? [])];
598
610
  info("[improve] post-loop maintenance starting");
599
611
  const maintenanceResult = await runImproveMaintenancePasses({
@@ -608,6 +620,7 @@ export async function runImprovePostLoopStage(args) {
608
620
  budgetSignal,
609
621
  eventsCtx,
610
622
  improveProfile,
623
+ resolvedPlan,
611
624
  });
612
625
  let deadUrls;
613
626
  if (scope.mode === "all" && primaryStashDir && actionableRefs.length > 0) {
@@ -641,15 +654,18 @@ export async function runImprovePostLoopStage(args) {
641
654
  let recombination;
642
655
  if (primaryStashDir &&
643
656
  improveProfile &&
644
- resolveProcessEnabled("recombine", improveProfile) &&
657
+ resolvedPlan?.processes.recombine.enabled === true &&
645
658
  scope.mode !== "ref" &&
646
659
  !options.dryRun) {
647
660
  const recombineFn = options.recombineFn ?? akmRecombine;
648
661
  try {
649
- recombination = await recombineFn({
662
+ recombination = await withLlmStage("recombine", () => recombineFn({
650
663
  stashDir: primaryStashDir,
651
664
  config: options.config ?? loadConfig(),
652
665
  improveProfile,
666
+ llmConfig: resolvedPlan?.processes.recombine.runner
667
+ ? materializeLlmRunnerConnection(resolvedPlan.processes.recombine.runner)
668
+ : null,
653
669
  ...(options.runId ? { sourceRun: options.runId } : {}),
654
670
  ...(budgetSignal ? { signal: budgetSignal } : {}),
655
671
  eligibilitySource: "recombine",
@@ -662,7 +678,7 @@ export async function runImprovePostLoopStage(args) {
662
678
  maxClusterSize: improveProfile.processes?.recombine?.maxClusterSize,
663
679
  excludeTags: improveProfile.processes?.recombine?.excludeTags,
664
680
  excludeEntities: improveProfile.processes?.recombine?.excludeEntities,
665
- });
681
+ }), { engine: resolvedPlan?.processes.recombine.runner?.engine, process: "recombine" });
666
682
  }
667
683
  catch (e) {
668
684
  allWarnings.push(`recombine: ${String(e)}`);
@@ -675,22 +691,25 @@ export async function runImprovePostLoopStage(args) {
675
691
  let proceduralCompilation;
676
692
  if (primaryStashDir &&
677
693
  improveProfile &&
678
- resolveProcessEnabled("procedural", improveProfile) &&
694
+ resolvedPlan?.processes.procedural.enabled === true &&
679
695
  scope.mode !== "ref" &&
680
696
  !options.dryRun) {
681
697
  const proceduralFn = options.proceduralFn ?? akmProcedural;
682
698
  try {
683
- proceduralCompilation = await proceduralFn({
699
+ proceduralCompilation = await withLlmStage("procedural", () => proceduralFn({
684
700
  stashDir: primaryStashDir,
685
701
  config: options.config ?? loadConfig(),
686
702
  ...(improveProfile ? { improveProfile } : {}),
703
+ llmConfig: resolvedPlan?.processes.procedural.runner
704
+ ? materializeLlmRunnerConnection(resolvedPlan.processes.procedural.runner)
705
+ : null,
687
706
  ...(options.runId ? { sourceRun: options.runId } : {}),
688
707
  ...(budgetSignal ? { signal: budgetSignal } : {}),
689
708
  eligibilitySource: "procedural",
690
709
  ...(eventsCtx ? { ctx: eventsCtx } : {}),
691
710
  minRecurrence: improveProfile.processes?.procedural?.minRecurrence,
692
711
  maxProposalsPerRun: improveProfile.processes?.procedural?.maxProposalsPerRun,
693
- });
712
+ }), { engine: resolvedPlan?.processes.procedural.runner?.engine, process: "procedural" });
694
713
  }
695
714
  catch (e) {
696
715
  allWarnings.push(`procedural: ${String(e)}`);
@@ -746,7 +765,7 @@ export async function runImprovePostLoopStage(args) {
746
765
  // Exported for tests (#584/#585 DB-locking regression coverage); production
747
766
  // callers reach it only through akmImprove → runImprovePostLoopStage.
748
767
  export async function runImproveMaintenancePasses(args) {
749
- const { options, primaryStashDir, memoryRefsForInference, allWarnings, reindexFn, consolidationRan, budgetSignal, eventsCtx, improveProfile, } = args;
768
+ const { options, primaryStashDir, memoryRefsForInference, allWarnings, reindexFn, consolidationRan, budgetSignal, eventsCtx, improveProfile, resolvedPlan, } = args;
750
769
  if (!primaryStashDir)
751
770
  return { memoryInferenceDurationMs: 0, graphExtractionDurationMs: 0 };
752
771
  const config = options.config ?? loadConfig();
@@ -825,6 +844,13 @@ export async function runImproveMaintenancePasses(args) {
825
844
  // O-1 (#364): pass budget signal so a hung inference call is cancelled.
826
845
  memoryInference = await withLlmStage("memory-inference", () => memoryInferenceFn({
827
846
  config,
847
+ ...(resolvedPlan
848
+ ? {
849
+ llmConfig: resolvedPlan.processes.memoryInference.runner
850
+ ? materializeLlmRunnerConnection(resolvedPlan.processes.memoryInference.runner)
851
+ : null,
852
+ }
853
+ : {}),
828
854
  sources,
829
855
  signal: budgetSignal,
830
856
  db,
@@ -833,7 +859,7 @@ export async function runImproveMaintenancePasses(args) {
833
859
  const current = event.currentRef ? ` ${event.currentRef}` : "";
834
860
  info(`[improve] memory inference ${event.processed}/${event.total}${current} (written ${event.writtenFacts}, skipped ${event.skippedNoFacts})`);
835
861
  },
836
- }));
862
+ }), { engine: resolvedPlan?.processes.memoryInference.runner?.engine, process: "memoryInference" });
837
863
  memoryInferenceDurationMs = Date.now() - inferenceStart;
838
864
  actions.push({ ref: "memory:_inference", mode: "memory-inference", result: memoryInference });
839
865
  info(`[improve] memory inference complete (${memoryInference.writtenFacts} facts written from ${memoryInference.splitParents} parents)`);
@@ -854,12 +880,16 @@ export async function runImproveMaintenancePasses(args) {
854
880
  allWarnings.push(`reindex after memory inference failed: ${errMessage(err)}`);
855
881
  }
856
882
  }
857
- const graphEnabled = isProcessEnabled("index", "graph_extraction", config);
883
+ const graphEnabled = resolvedPlan ? true : isProcessEnabled("index", "graph_extraction", config);
858
884
  const graphExtractionDisabledByProfile = improveProfile?.processes?.graphExtraction?.enabled === false;
859
885
  const graphExtractionFullScan = improveProfile?.processes?.graphExtraction?.fullScan === true;
860
886
  // #624 P2: optional incremental high-signal-first cap. Unset = process all
861
887
  // eligible (byte-identical to today; no ranking/slice).
862
888
  const graphExtractionTopN = improveProfile?.processes?.graphExtraction?.topN;
889
+ const graphExtractionIncludeTypes = improveProfile?.processes?.graphExtraction?.includeTypes ?? [
890
+ ...DEFAULT_GRAPH_EXTRACTION_INCLUDE_TYPES,
891
+ ];
892
+ const graphExtractionBatchSize = improveProfile?.processes?.graphExtraction?.batchSize ?? DEFAULT_GRAPH_EXTRACTION_BATCH_SIZE;
863
893
  // Build the set of refs actually touched this run.
864
894
  const touchedRefs = new Set();
865
895
  for (const r of args.actionableRefs)
@@ -916,13 +946,25 @@ export async function runImproveMaintenancePasses(args) {
916
946
  // O-1 (#364): pass budget signal so a hung graph extraction call is cancelled.
917
947
  graphExtraction = await withLlmStage("graph-extraction", () => graphExtractionFn({
918
948
  config,
949
+ ...(resolvedPlan
950
+ ? {
951
+ llmConfig: resolvedPlan.processes.graphExtraction.runner
952
+ ? materializeLlmRunnerConnection(resolvedPlan.processes.graphExtraction.runner)
953
+ : null,
954
+ }
955
+ : {}),
919
956
  sources,
920
957
  signal: budgetSignal,
921
958
  db,
922
959
  reEnrich: false,
923
960
  onProgress: progressHandler,
924
- options: { candidatePaths, ...(graphExtractionTopN != null ? { topN: graphExtractionTopN } : {}) },
925
- }));
961
+ options: {
962
+ candidatePaths,
963
+ includeTypes: graphExtractionIncludeTypes,
964
+ batchSize: graphExtractionBatchSize,
965
+ ...(graphExtractionTopN != null ? { topN: graphExtractionTopN } : {}),
966
+ },
967
+ }), { engine: resolvedPlan?.processes.graphExtraction.runner?.engine, process: "graphExtraction" });
926
968
  graphExtractionDurationMs = Date.now() - extractionStart;
927
969
  actions.push({ ref: "graph:_artifact", mode: "graph-extraction", result: graphExtraction });
928
970
  info(`[improve] graph extraction complete (${graphExtraction.quality.extractedFiles} files, ${graphExtraction.quality.entityCount} entities, ${graphExtraction.quality.relationCount} relations)`);
@@ -28,6 +28,25 @@
28
28
  * - MemOS (arXiv:2507.03724) — formal archive/merge/transition with shared state model
29
29
  */
30
30
  import { mutateFrontmatter } from "../../../core/asset/frontmatter.js";
31
+ // ── Shared edge-list reader ───────────────────────────────────────────────────
32
+ /**
33
+ * Read a frontmatter edge value (`supersededBy` / `contradictedBy`) as a
34
+ * string list, promoting a scalar string to a one-element list.
35
+ *
36
+ * Scalar edges are LIVE data: the indexer's `normalizeNonEmptyStringList`
37
+ * accepts them and lint deliberately never flags them. An Array.isArray-only
38
+ * read treats a scalar as "no existing edges" and silently destroys the edge
39
+ * on the next merge — mirror `mergeXrefsIntoContent`'s scalar promotion
40
+ * instead.
41
+ */
42
+ function readEdgeList(value) {
43
+ if (Array.isArray(value)) {
44
+ return value.filter((v) => typeof v === "string" && v.trim().length > 0);
45
+ }
46
+ if (typeof value === "string" && value.trim())
47
+ return [value.trim()];
48
+ return [];
49
+ }
31
50
  // ── Contradiction edge writer ─────────────────────────────────────────────────
32
51
  /**
33
52
  * Write `contradictedBy` and `beliefState: contradicted` edges to a memory
@@ -38,24 +57,77 @@ import { mutateFrontmatter } from "../../../core/asset/frontmatter.js";
38
57
  * - `memory-contradiction-detect.ts` for the M-1 automated contradiction pass
39
58
  * - `resolveFamilyContradictions` in `memory-improve.ts` for SCC resolution
40
59
  *
41
- * Idempotent: if the `contradictedByRef` is already in `contradictedBy`,
42
- * the file is not rewritten.
60
+ * Idempotent: if the `contradictedByRef` is already in `contradictedBy` AND
61
+ * the file already carries the demotion state, the file is not rewritten. The
62
+ * guard is state-aware like its sibling {@link writeSupersededEdge}: an edge
63
+ * present WITHOUT the demotion (e.g. a hand-written `contradictedBy:` line,
64
+ * or a beliefState lost to a partial edit) is repaired, not skipped — an
65
+ * edge-only guard would make such a file a permanent no-op while
66
+ * consolidate's handleContradictOp reports the contradiction as applied.
67
+ *
68
+ * Never weakens a stronger demotion: `archived` ranks BELOW `contradicted`
69
+ * (see `BELIEF_STATE_SCORE_CEILINGS` in
70
+ * src/indexer/search/ranking-contributors.ts), so contradicting an archived
71
+ * memory keeps `archived` and only appends the edge.
43
72
  *
44
73
  * @param filePath - Absolute path to the memory markdown file.
45
74
  * @param contradictedByRef - The ref that contradicts this memory.
46
75
  */
47
76
  export function writeContradictEdge(filePath, contradictedByRef) {
48
77
  mutateFrontmatter(filePath, (parsed) => {
49
- const existing = Array.isArray(parsed.data.contradictedBy)
50
- ? parsed.data.contradictedBy
51
- : [];
52
- if (existing.includes(contradictedByRef))
78
+ const existing = readEdgeList(parsed.data.contradictedBy);
79
+ const currentState = parsed.data.beliefState;
80
+ const nextState = currentState === "archived" ? currentState : "contradicted";
81
+ if (existing.includes(contradictedByRef) && currentState === nextState) {
53
82
  return null; // Already written — idempotent.
83
+ }
54
84
  const nextContradictedBy = [...new Set([...existing, contradictedByRef])].sort();
55
85
  return {
56
86
  ...parsed.data,
57
87
  contradictedBy: nextContradictedBy,
58
- beliefState: "contradicted",
88
+ beliefState: nextState,
89
+ };
90
+ });
91
+ }
92
+ // ── Supersession edge writer ─────────────────────────────────────────────────
93
+ /**
94
+ * Write `supersededBy` and `beliefState: superseded` edges to an asset file's
95
+ * frontmatter (SPEC-5, stash-conventions-code-spec.md).
96
+ *
97
+ * Sibling of {@link writeContradictEdge}: the shared primitive for the
98
+ * conventions' corrections pattern — when a new asset supersedes an old one,
99
+ * the old asset gets a METADATA-ONLY demotion edit (every other frontmatter
100
+ * key and the body are preserved) so the ranker's beliefStateBoost demotes the
101
+ * stale incumbent and `--belief current` hides it. Used by
102
+ * `akm remember --supersedes` / `akm import --supersedes`.
103
+ *
104
+ * Idempotent: if `supersededByRef` is already in `supersededBy` and the file
105
+ * is already demoted, the file is not rewritten. Multiple corrections
106
+ * sorted-set-append their refs.
107
+ *
108
+ * Never WEAKENS an existing demotion: `contradicted` and `archived` rank
109
+ * BELOW `superseded` (severity order deprecated > superseded > contradicted >
110
+ * archived — see `BELIEF_STATE_SCORE_CEILINGS` in
111
+ * src/indexer/search/ranking-contributors.ts), so superseding an already
112
+ * contradicted/archived asset keeps the stronger state and only appends the
113
+ * `supersededBy` edge.
114
+ *
115
+ * @param filePath - Absolute path to the asset markdown file.
116
+ * @param supersededByRef - The ref of the correction that supersedes this asset.
117
+ */
118
+ export function writeSupersededEdge(filePath, supersededByRef) {
119
+ mutateFrontmatter(filePath, (parsed) => {
120
+ const existing = readEdgeList(parsed.data.supersededBy);
121
+ const currentState = parsed.data.beliefState;
122
+ const nextState = currentState === "contradicted" || currentState === "archived" ? currentState : "superseded";
123
+ if (existing.includes(supersededByRef) && currentState === nextState) {
124
+ return null; // Already written — idempotent.
125
+ }
126
+ const nextSupersededBy = [...new Set([...existing, supersededByRef])].sort();
127
+ return {
128
+ ...parsed.data,
129
+ supersededBy: nextSupersededBy,
130
+ beliefState: nextState,
59
131
  };
60
132
  });
61
133
  }
@@ -22,7 +22,7 @@
22
22
  *
23
23
  * # LLM Feature Gate
24
24
  *
25
- * The pass is gated behind `profiles.improve.default.processes.consolidate.contradictionDetection.enabled`.
25
+ * The pass is gated by the selected strategy's consolidate contradiction setting.
26
26
  * When the gate is disabled or no LLM is configured,
27
27
  * the pass is a no-op and `analyzeMemoryCleanup` proceeds with only manually
28
28
  * annotated edges.
@@ -38,6 +38,7 @@ import path from "node:path";
38
38
  import contradictionJudgeTemplate from "../../../assets/prompts/contradiction-judge.md" with { type: "text" };
39
39
  import { mutateFrontmatter, parseFrontmatter } from "../../../core/asset/frontmatter.js";
40
40
  import { getDefaultLlmConfig } from "../../../core/config/config.js";
41
+ import { materializeLlmRunnerConnection, resolveImproveProcessRunner } from "../../../integrations/agent/runner.js";
41
42
  import { chatCompletion, parseEmbeddedJsonResponse } from "../../../llm/client.js";
42
43
  import { tryLlmFeature } from "../../../llm/feature-gate.js";
43
44
  // ── Constants ────────────────────────────────────────────────────────────────
@@ -173,14 +174,20 @@ function pickContradictionLoser(a, b) {
173
174
  * @param config - Loaded AKM config (used to access LLM settings).
174
175
  * @param chat - Optional chat seam for testing (defaults to chatCompletion).
175
176
  */
176
- export async function detectAndWriteContradictions(stashDir, config, chat = chatCompletion) {
177
+ export async function detectAndWriteContradictions(stashDir, config, chat = chatCompletion, strategy, resolvedLlmConfig) {
177
178
  const result = {
178
179
  familiesExamined: 0,
179
180
  pairsChecked: 0,
180
181
  edgesWritten: 0,
181
182
  warnings: [],
182
183
  };
183
- const contradictionLlm = getDefaultLlmConfig(config);
184
+ const contradictionLlm = resolvedLlmConfig === null
185
+ ? undefined
186
+ : (resolvedLlmConfig ??
187
+ (() => {
188
+ const runner = resolveImproveProcessRunner(strategy, "consolidate", config);
189
+ return runner ? materializeLlmRunnerConnection(runner) : getDefaultLlmConfig(config);
190
+ })());
184
191
  if (!contradictionLlm)
185
192
  return result;
186
193
  // Collect derived memories grouped by parent.
@@ -254,7 +261,8 @@ export async function detectAndWriteContradictions(stashDir, config, chat = chat
254
261
  { role: "system", content: "Return only valid JSON. No prose." },
255
262
  { role: "user", content: prompt },
256
263
  ]);
257
- }, null);
264
+ }, null, // Fallback: null means "skip" — gate disabled or LLM call failed.
265
+ { enabled: strategy?.processes?.consolidate?.contradictionDetection?.enabled ?? false });
258
266
  totalPairsChecked++;
259
267
  result.pairsChecked++;
260
268
  if (!judgeResult)