akm-cli 0.9.31 → 0.10.26101002-alpha

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 (236) hide show
  1. package/CHANGELOG.md +265 -0
  2. package/STABILITY.md +79 -53
  3. package/dist/api-node.mjs +20 -0
  4. package/dist/api.d.ts +23 -0
  5. package/dist/api.js +45 -0
  6. package/dist/assets/hints/cli-hints-full.md +13 -27
  7. package/dist/assets/hints/cli-hints-short.md +1 -1
  8. package/dist/assets/improve-strategies/default.json +3 -3
  9. package/dist/assets/prompts/consolidate-pair.md +9 -7
  10. package/dist/cli/shared.js +31 -48
  11. package/dist/cli/unknown-flags.js +6 -43
  12. package/dist/cli.js +8 -34
  13. package/dist/commands/agent/contribute-cli.js +1 -1
  14. package/dist/commands/completions.js +1 -2
  15. package/dist/commands/config-cli.js +5 -28
  16. package/dist/commands/env/env-cli.js +5 -5
  17. package/dist/commands/env/secret-cli.js +2 -2
  18. package/dist/commands/health/checks.js +41 -16
  19. package/dist/commands/health/plugin-staleness.js +52 -22
  20. package/dist/commands/improve/autonomy-gate.js +5 -3
  21. package/dist/commands/improve/consolidate/pair-pass.js +7 -1
  22. package/dist/commands/improve/consolidate.js +6 -35
  23. package/dist/commands/improve/distill/lesson-update.js +91 -0
  24. package/dist/commands/improve/distill-guards.js +1 -43
  25. package/dist/commands/improve/distill.js +140 -20
  26. package/dist/commands/improve/eligibility.js +14 -1
  27. package/dist/commands/improve/extract-cli.js +1 -1
  28. package/dist/commands/improve/improve-cli.js +87 -102
  29. package/dist/commands/improve/improve-strategies.js +8 -12
  30. package/dist/commands/improve/improve.js +2 -2
  31. package/dist/commands/improve/loop-stages.js +0 -1
  32. package/dist/commands/improve/planner.js +0 -1
  33. package/dist/commands/improve/preparation.js +40 -99
  34. package/dist/commands/improve/proactive-maintenance.js +2 -2
  35. package/dist/commands/improve/reflect-noise.js +3 -55
  36. package/dist/commands/improve/reflect.js +3 -8
  37. package/dist/commands/improve/retrieval-scope.js +2 -2
  38. package/dist/commands/improve/salience.js +2 -9
  39. package/dist/commands/improve/stage.js +14 -9
  40. package/dist/commands/lint/index.js +5 -20
  41. package/dist/commands/observability-cli.js +3 -9
  42. package/dist/commands/proposal/drain.js +39 -6
  43. package/dist/commands/proposal/proposal-cli.js +11 -31
  44. package/dist/commands/proposal/repository.js +3 -3
  45. package/dist/commands/read/knowledge.js +3 -3
  46. package/dist/commands/read/remember-cli.js +0 -13
  47. package/dist/commands/read/search-cli.js +29 -97
  48. package/dist/commands/read/search.js +0 -11
  49. package/dist/commands/read/show.js +0 -19
  50. package/dist/commands/sources/plugin-upgrade.js +164 -101
  51. package/dist/commands/sources/sources-cli.js +1 -15
  52. package/dist/commands/sources/stash-cli.js +4 -12
  53. package/dist/commands/tasks/tasks-cli.js +4 -115
  54. package/dist/commands/workflow/plan.js +11 -33
  55. package/dist/commands/workflow-cli.js +2 -7
  56. package/dist/core/adapter/adapter-ids.js +1 -2
  57. package/dist/core/adapter/adapters/agent-skills-adapter.js +0 -4
  58. package/dist/core/adapter/adapters/akm-adapter.js +9 -48
  59. package/dist/core/adapter/adapters/akm-lint.js +10 -26
  60. package/dist/core/adapter/adapters/akm-metadata.js +2 -72
  61. package/dist/core/adapter/adapters/akm-task-adapter.js +0 -5
  62. package/dist/core/adapter/adapters/akm-workflow-adapter.js +9 -28
  63. package/dist/core/adapter/adapters/dotenv-adapter.js +1 -20
  64. package/dist/core/adapter/adapters/generic-files-adapter.js +0 -10
  65. package/dist/core/adapter/adapters/llm-wiki-adapter.js +0 -4
  66. package/dist/core/adapter/adapters/tool-dir-shared.js +0 -24
  67. package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -4
  68. package/dist/core/adapter/recognize-match.js +3 -4
  69. package/dist/core/common.js +18 -2
  70. package/dist/core/config/config-schema.js +12 -15
  71. package/dist/core/config/config-types.js +6 -15
  72. package/dist/core/config/config-walker.js +6 -23
  73. package/dist/core/config/config.js +17 -80
  74. package/dist/core/config/engine-semantics.js +7 -3
  75. package/dist/core/config/schema/engines.js +7 -7
  76. package/dist/core/config/schema/improve-processes.js +8 -61
  77. package/dist/core/config/schema/improve.js +2 -21
  78. package/dist/core/config/schema/index-config.js +6 -39
  79. package/dist/core/env-secret-ref.js +2 -45
  80. package/dist/core/errors.js +1 -2
  81. package/dist/core/extra-params.js +2 -86
  82. package/dist/core/improve-result.js +11 -1
  83. package/dist/core/mutation-target.js +2 -2
  84. package/dist/core/recognition-util.js +4 -5
  85. package/dist/core/warn.js +21 -4
  86. package/dist/core/write-source.js +3 -3
  87. package/dist/execution/target-ref.js +1 -2
  88. package/dist/indexer/search/db-search.js +4 -19
  89. package/dist/indexer/search/fts-query.js +0 -19
  90. package/dist/integrations/agent/engine-resolution.js +5 -2
  91. package/dist/integrations/agent/index.js +1 -1
  92. package/dist/integrations/agent/profiles.js +41 -98
  93. package/dist/integrations/agent/request-lowering.js +3 -3
  94. package/dist/integrations/harnesses/aider/index.js +5 -0
  95. package/dist/integrations/harnesses/amazonq/index.js +1 -0
  96. package/dist/integrations/harnesses/claude/config-import.js +2 -3
  97. package/dist/integrations/harnesses/claude/index.js +5 -0
  98. package/dist/integrations/harnesses/codex/index.js +1 -0
  99. package/dist/integrations/harnesses/copilot/index.js +1 -0
  100. package/dist/integrations/harnesses/gemini/index.js +1 -0
  101. package/dist/integrations/harnesses/index.js +18 -39
  102. package/dist/integrations/harnesses/opencode/agent-builder.js +60 -35
  103. package/dist/integrations/harnesses/opencode/config-import.js +121 -52
  104. package/dist/integrations/harnesses/opencode/index.js +22 -0
  105. package/dist/integrations/harnesses/opencode/plugin-layout.js +68 -0
  106. package/dist/integrations/harnesses/opencode/session-log-types.js +12 -0
  107. package/dist/integrations/harnesses/opencode/session-log-v1.js +72 -0
  108. package/dist/integrations/harnesses/opencode/session-log-v2.js +140 -0
  109. package/dist/integrations/harnesses/opencode/session-log.js +104 -115
  110. package/dist/integrations/harnesses/opencode/version.js +98 -0
  111. package/dist/integrations/harnesses/opencode-sdk/{harness.js → index.js} +5 -17
  112. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +105 -206
  113. package/dist/integrations/harnesses/opencode-sdk/v1-adapter.js +199 -0
  114. package/dist/integrations/harnesses/opencode-sdk/v2-adapter.js +228 -0
  115. package/dist/integrations/harnesses/opencode-sdk/wire.js +66 -0
  116. package/dist/integrations/harnesses/openhands/index.js +1 -0
  117. package/dist/integrations/harnesses/pi/index.js +1 -0
  118. package/dist/integrations/harnesses/shared.js +2 -0
  119. package/dist/integrations/harnesses/types.js +2 -0
  120. package/dist/llm/client.js +41 -11
  121. package/dist/output/cli-hints.js +1 -1
  122. package/dist/output/context.js +10 -19
  123. package/dist/output/format-exempt.js +5 -8
  124. package/dist/output/shapes/helpers.js +1 -22
  125. package/dist/output/shapes/passthrough.js +0 -1
  126. package/dist/output/shapes.js +3 -31
  127. package/dist/output/stdout.js +5 -1
  128. package/dist/output/text/command-format.js +6 -1
  129. package/dist/output/text/workflow-format.js +4 -22
  130. package/dist/scripts/akm-migrate-node.js +13049 -14013
  131. package/dist/scripts/akm-migrate.js +16316 -17276
  132. package/dist/setup/detect.js +10 -4
  133. package/dist/setup/harness-config-import.js +5 -14
  134. package/dist/setup/setup.js +1 -1
  135. package/dist/setup/steps/tasks.js +12 -13
  136. package/dist/storage/repositories/task-history-repository.js +0 -5
  137. package/dist/storage/repositories/workflow-runs-repository.js +5 -15
  138. package/dist/tasks/embedded.js +2 -1
  139. package/dist/tasks/run/task-history.js +0 -2
  140. package/dist/tasks/schedule-gates.js +39 -0
  141. package/dist/tasks/scheduler-binding.js +0 -16
  142. package/dist/tasks/scheduler-sync.js +3 -97
  143. package/dist/tasks/source/bounded-document.js +1 -1
  144. package/dist/tasks/source/task-source-v4.js +2 -12
  145. package/dist/tasks/task-run-reserved-flags.js +2 -31
  146. package/dist/workflows/authoring/authoring.js +6 -6
  147. package/dist/workflows/compile.js +8 -65
  148. package/dist/workflows/exec/child-workflow.js +22 -3
  149. package/dist/workflows/exec/native-executor.js +12 -147
  150. package/dist/workflows/exec/run-workflow.js +31 -103
  151. package/dist/workflows/exec/step-work.js +9 -178
  152. package/dist/workflows/exec/unit-dispatch.js +0 -10
  153. package/dist/workflows/freeze/environment.js +3 -44
  154. package/dist/workflows/freeze/freeze.js +8 -41
  155. package/dist/workflows/freeze/targets/child-workflow.js +27 -63
  156. package/dist/workflows/freeze/targets/command.js +2 -16
  157. package/dist/workflows/freeze/targets/shell.js +0 -2
  158. package/dist/workflows/freeze/task-bindings.js +2 -28
  159. package/dist/workflows/ir/params.js +12 -2
  160. package/dist/workflows/parser.js +82 -232
  161. package/dist/workflows/plan.js +1 -1
  162. package/dist/workflows/renderer.js +10 -19
  163. package/dist/workflows/runtime/run-plan.js +11 -53
  164. package/dist/workflows/runtime/runs.js +4 -27
  165. package/dist/workflows/source-files.js +12 -16
  166. package/dist/workflows/source-semantics.js +1 -93
  167. package/docs/README.md +1 -1
  168. package/docs/agents/README.md +3 -3
  169. package/docs/agents/agent-install.md +1 -1
  170. package/docs/architecture/adapters.md +17 -31
  171. package/docs/architecture/architecture.md +4 -5
  172. package/docs/architecture/improvement.md +52 -21
  173. package/docs/architecture/internals/improve-workflow.md +15 -8
  174. package/docs/architecture/internals/indexing.md +1 -1
  175. package/docs/architecture/internals/search.md +1 -2
  176. package/docs/architecture/internals/storage-locations.md +5 -5
  177. package/docs/architecture/persisted-data-compat.md +13 -4
  178. package/docs/architecture/specs/0.9.0-decisions.md +6 -2
  179. package/docs/architecture/specs/improve-collapse-churn-detector-design.md +2 -2
  180. package/docs/architecture/specs/workflow-format-unification.md +1 -6
  181. package/docs/architecture/testing/manual-testing-checklist.md +20 -29
  182. package/docs/architecture/testing/task-workflow-manual-tests.md +106 -203
  183. package/docs/architecture/testing/testing-workflow.md +1 -1
  184. package/docs/architecture/workflow-engine.md +5 -56
  185. package/docs/guides/author-workflows.md +39 -69
  186. package/docs/guides/capture-knowledge.md +1 -1
  187. package/docs/guides/claude-code-vs-akm-workflows.md +1 -2
  188. package/docs/guides/discover-and-load.md +4 -4
  189. package/docs/guides/getting-started.md +1 -1
  190. package/docs/guides/improve-the-library.md +7 -7
  191. package/docs/guides/recipes/headless-install.md +2 -2
  192. package/docs/guides/run-workflows.md +1 -1
  193. package/docs/guides/use-with-any-agent.md +49 -4
  194. package/docs/integration/bundling-akm.md +7 -6
  195. package/docs/maintainers/curate-workmap.md +1 -2
  196. package/docs/maintainers/release-checklist.md +30 -1
  197. package/docs/posts/akm-command-tour.md +3 -3
  198. package/docs/product-surface.md +1 -1
  199. package/docs/reference/README.md +1 -0
  200. package/docs/reference/api.md +65 -0
  201. package/docs/reference/asset-types.md +3 -5
  202. package/docs/reference/bundle-types.md +2 -4
  203. package/docs/reference/cli.md +129 -171
  204. package/docs/reference/configuration.md +84 -19
  205. package/docs/reference/data-and-telemetry.md +1 -1
  206. package/docs/reference/env-and-secrets.md +2 -2
  207. package/docs/reference/supported-formats.md +3 -4
  208. package/docs/reference/tasks.md +12 -19
  209. package/docs/reference/website-sources.md +1 -1
  210. package/docs/reference/workflow-schema.md +89 -317
  211. package/docs/reference/workflows.md +20 -48
  212. package/package.json +12 -2
  213. package/schemas/akm-config.json +2 -243
  214. package/schemas/akm-task.json +2 -3
  215. package/schemas/akm-workflow.json +47 -84
  216. package/dist/assets/improve-strategies/catchup.json +0 -41
  217. package/dist/assets/improve-strategies/proactive-maintenance.json +0 -24
  218. package/dist/assets/improve-strategies/quick.json +0 -17
  219. package/dist/assets/improve-strategies/reflect-distill.json +0 -29
  220. package/dist/assets/improve-strategies/thorough.json +0 -49
  221. package/dist/assets/tasks/improve/akm-improve-catchup.yml +0 -6
  222. package/dist/assets/tasks/improve/akm-improve-frequent.yml +0 -4
  223. package/dist/assets/tasks/improve/akm-improve-nightly.yml +0 -4
  224. package/dist/cli/retired-commands.js +0 -118
  225. package/dist/commands/tasks/validate.js +0 -176
  226. package/dist/core/config/legacy-source-shape-shim.js +0 -117
  227. package/dist/integrations/harnesses/ids.js +0 -28
  228. package/dist/tasks/source/task-source-v3-frozen.js +0 -424
  229. package/dist/tasks/source/task-to-v4.js +0 -863
  230. package/dist/workflows/exec/worktree.js +0 -372
  231. package/dist/workflows/freeze/child-output-references.js +0 -83
  232. package/dist/workflows/freeze/identity.js +0 -22
  233. package/dist/workflows/freeze/targets/script.js +0 -50
  234. package/dist/workflows/freeze/targets/task.js +0 -124
  235. package/dist/workflows/github-yaml.js +0 -506
  236. package/dist/workflows/runtime/run-outputs.js +0 -80
@@ -39,8 +39,9 @@ everything else routes through `akm proposal accept`.
39
39
  `src/assets/improve-strategies/*.json`) — named presets that decide which
40
40
  improve processes run and with what engine/model/limits.
41
41
  - **Autonomy gate** (`src/commands/improve/autonomy-gate.ts`) — downgrades
42
- the handful of processes that would otherwise mutate assets without review,
43
- unless `experimental.improveAutonomy` is explicitly set.
42
+ the handful of processes that would otherwise mutate assets without review
43
+ (memory inference, memory cleanup, and a judged triage promote), unless
44
+ `experimental.improveAutonomy` is explicitly set.
44
45
  - **Reflect / distill / consolidate subprocesses** — the improve pipeline's
45
46
  proposal generators, invoked per asset (reflect, distill) or across the
46
47
  whole memory corpus (consolidate). See
@@ -80,10 +81,11 @@ everything else routes through `akm proposal accept`.
80
81
  reason or note, which gives the writer nothing to distil (10 of the 11
81
82
  lessons made from such memories were rejected). Unless
82
83
  `--require-feedback-signal` is set, the
83
- fallback lanes (high salience, and proactive maintenance where the strategy
84
- enables it) pick what the retrieval scope below admits, for scoring only:
85
- they plan nothing, so improve does not rewrite assets on a proactive
86
- cadence. It ranks the selected assets by salience, applies the limit, then
84
+ proactive-maintenance lane (where the strategy enables it, as `default`
85
+ does) adds the stable assets due for a revisit that the retrieval scope below
86
+ admits, and plans them with the feedback-bearing ones. At most `maxPerRun`
87
+ (15) assets are picked per run, an asset is due only if not reflected or
88
+ distilled for `dueDays` (30), and reflect's `limit` (25) bounds the run. It ranks the selected assets by salience, applies the limit, then
87
89
  runs whichever processes the selected strategy enables against each one (see
88
90
  [Improve Workflow](internals/improve-workflow.md#ledger-pre-filter-signal-delta)).
89
91
  4. Reflect and distill each emit at most one proposal per asset per run;
@@ -146,9 +148,8 @@ for the storage-level summary.
146
148
  ### Strategy inheritance
147
149
 
148
150
  Improve presets live under `improve.strategies` (config) and the built-in
149
- set: `default`, `quick`, `thorough`, `consolidate`, `catchup`,
150
- `reflect-distill`, and `proactive-maintenance`
151
- (`src/assets/improve-strategies/*.json`). Selection
151
+ set: `default` and `consolidate` (`src/assets/improve-strategies/*.json`). The removed `quick`, `thorough`,
152
+ `catchup`, `reflect-distill` and `proactive-maintenance` still work as user-defined strategies. Selection
152
153
  order is `--strategy`, then `defaults.improveStrategy`, then `default`.
153
154
 
154
155
  Resolution is a two-step deep merge (`resolveImproveStrategy`): a named
@@ -157,16 +158,16 @@ any user-defined override for that same name (under `improve.strategies` in
157
158
  config) is merged on top. So a strategy — built-in or user-defined — that
158
159
  omits a field, or an entire process block, inherits it from `default`; an
159
160
  explicit `enabled: true`/`false` in the more specific layer always wins. This
160
- is why, for example, `proactiveMaintenance` stays off in `default` and
161
- `reflect-distill`, but a preset that doesn't mention it at all still inherits
162
- that "off" rather than defaulting to on.
161
+ is why, for example, `proactiveMaintenance` is on in `default` and off in
162
+ `consolidate`, and a preset that doesn't mention it at all inherits the
163
+ `default` value. A user-defined strategy named `proactive-maintenance` (the name
164
+ of the retired built-in) resolves the same way, merged onto `default`.
163
165
 
164
166
  ### Retrieval scope
165
167
 
166
168
  Improve reworks only what gets read (#986). Fresh feedback and an explicit ref
167
169
  scope (`akm improve skills/x`) are usage evidence of their own. Every other
168
- pick — the proactive-maintenance and high-salience lanes (which only score what
169
- they pick), and the memories consolidation judges — must be in the retrieval
170
+ pick — the proactive-maintenance lane, and the memories consolidation judges — must be in the retrieval
170
171
  scope (`src/commands/improve/retrieval-scope.ts`):
171
172
 
172
173
  - **Retrieved:** a user-attributed `search`, `curate` or `show` returned the
@@ -217,7 +218,10 @@ both stay on the proposal when the drain accepts it, so a later audit can read
217
218
  why it passed (`akm proposal show --format json`). A distill pass is not staged:
218
219
  the lesson or promotion is minted `deferred` for a person (reason
219
220
  `distill-review`, same gate) with the same `scores` and `judgeReason`, and the
220
- triage drain and its judgment tier leave it alone. On 2026-10-05 the gate had
221
+ triage drain and its judgment tier leave it alone. A lesson that repeats an existing
222
+ lesson may instead be proposed as an update of that lesson (reason
223
+ `distill-update`, same gate), with only what the memory adds; the drain leaves
224
+ it for a person too. On 2026-10-05 the gate had
221
225
  staged 12 lessons and 10 were bad (they restated the memory, claimed what it
222
226
  does not say, or filed a dated status as a lesson), and no score separated them
223
227
  from the two good ones. Reflect revises only an asset's
@@ -225,6 +229,28 @@ from the two good ones. Reflect revises only an asset's
225
229
  those that fixed a frontmatter defect and left the body alone were good 24
226
230
  times in 26, and those that also rewrote the body were bad 175 times in 224.
227
231
 
232
+ ### Judgment tier: promotions only
233
+
234
+ The triage drain's judgment tier (`processes.triage.judgment`, off in every
235
+ built-in strategy) may accept only a consolidate promotion, a memory proposed as
236
+ a new knowledge note. A retirement, an exact fix or a reflect revision it would
237
+ accept is left for a person (`judgment-not-promotion`); it may still reject.
238
+ Measured on the nightly eval with `experimental.improveAutonomy` (#1132, #1094),
239
+ judging every kind solved 0.49 (gpt-5.6-terra) and 0.53 (qwen3.8-27b) of the
240
+ items, harmed 14 and 11, and changed 18 and 21 notes outside the planted set.
241
+ Judging promotions alone: precision 0.87, recall 0.78 and 4.8% of bad promotions
242
+ accepted on qwen3.8-27b; precision 1.0, recall 0.17 on gpt-5.6-terra.
243
+
244
+ ### Proactive-lane proposals wait for a person
245
+
246
+ A proposal planned by the proactive-maintenance lane (`eligibilitySource:
247
+ "proactive"`, an asset with no feedback behind it) is never accepted by the
248
+ drain, whether a deterministic gate (a staged quality-judge pass), the judgment
249
+ tier or an accept staged by an earlier drain would take it. It is left for a
250
+ person with reason `proactive-needs-review`, and the judgment tier does not see
251
+ it. An empty diff is still rejected. Proposals driven by feedback (`signal-delta`,
252
+ `scope`) follow the one rule unchanged (#1147).
253
+
228
254
  ### Retrieval regression gate
229
255
 
230
256
  Reflect refuses a revision of an existing asset that grades lower on the
@@ -297,8 +323,7 @@ but nothing assigns or emits it any more.
297
323
  ### Dry-run planning boundary
298
324
 
299
325
  Dry and live improve runs call the same selectors for signal-delta eligibility,
300
- the fallback lanes (proactive maintenance and high salience, which score and
301
- plan nothing), the retrieval scope, salience ranking, disk presence, and the
326
+ the proactive-maintenance lane, the retrieval scope, salience ranking, disk presence, and the
302
327
  final cap, and resolve the bundle they plan the same way: `--bundle`, else
303
328
  `defaultWriteTarget`, else the working bundle (`AKM_BUNDLE_DIR`, else
304
329
  `defaultBundle`). Each invocation reports a best-effort observation assembled
@@ -337,13 +362,20 @@ equals `effective`. When the run is unbounded, `totalCeiling` is omitted.
337
362
  candidates, validation, and proactive-maintenance selection are proposal-only
338
363
  and never write assets directly regardless of this gate.
339
364
  Three specific lanes *would* mutate assets without review and are downgraded
340
- unless `experimental.improveAutonomy` is explicitly set to `true`:
365
+ unless `experimental.improveAutonomy` is explicitly set to `true` (the triage
366
+ lane only when its judgment tier is on; see below):
341
367
 
342
368
  | Lane | What it does when enabled | With autonomy off |
343
369
  | --- | --- | --- |
344
370
  | `memoryInference` | Writes `.derived.md` children and rewrites parent frontmatter | disabled |
345
371
  | memory cleanup | Belief-state frontmatter rewrites, archive moves | analyzed but not applied |
346
- | `triage` `applyMode: "promote"` | Auto-accepts queued proposals into the bundle | downgraded to `queue` — triage still runs, it just does not auto-accept |
372
+ | `triage` `applyMode: "promote"` **with judgment on** | The judgment tier auto-accepts consolidate promotions into the bundle | downgraded to `queue` — triage still runs, it just does not auto-accept |
373
+
374
+ A deterministic-only `promote` (triage with judgment off) is not gated (#1143):
375
+ the `default` strategy drains the queue through the deterministic gates, up to
376
+ `maxAcceptsPerRun` (200) per run, with no opt-in. The nightly eval's baseline is
377
+ exactly this path (`proposal drain --promote` without judgment): 0.91 (terra) and
378
+ 0.89 (qwen) of items solved, 0 to 1 harmed.
347
379
 
348
380
  Every downgrade is reported, not silent: it warns on stderr, appends an
349
381
  `improve_skipped` event with `reason: "autonomy_gated"`, and is counted in
@@ -361,8 +393,7 @@ explicit promote surface independent of this gate.
361
393
  For git-backed bundles (detected by a `.git` directory), `akm improve`
362
394
  automatically commits its changes as a single batch at the end of the run —
363
395
  the same operation as `akm sync` — and pushes if the bundle is writable, per
364
- the active strategy's `sync` setting. The `reflect-distill` and
365
- `proactive-maintenance` strategies skip sync entirely, so an interrupted run
396
+ the active strategy's `sync` setting. A user-defined strategy can turn sync off, so an interrupted run
366
397
  does not leave an uncommitted backlog. `--no-sync` disables sync for a single
367
398
  run; `--no-push` commits without pushing. Strategy sync behavior is
368
399
  configured via the `sync` block under `improve.strategies.<name>`.
@@ -13,7 +13,7 @@
13
13
  | `--limit` | `number` | Cap the number of assets processed, taken from the salience ranking, highest first (refs routed to distill only come last). |
14
14
  | `--timeout-ms` | `number` | Wall-clock budget for the entire run. Default: 7 200 000 ms (2 hours). |
15
15
  | `--skip-if-locked` | `boolean` | If another improve owns the whole-run lock, return an exit-0 no-op result before triage, indexing, events, or sync. Without the flag, contention is a transient error (`IMPROVE_LOCK_HELD`, exit 75). |
16
- | `--require-feedback-signal` | `boolean` | Turn the fallback lanes (high salience, proactive maintenance) off for the run: they only select and score assets, and a rewrite needs negative feedback. |
16
+ | `--require-feedback-signal` | `boolean` | Turn the proactive-maintenance lane off for the run, so only assets with recent feedback are planned. |
17
17
 
18
18
  Injected function seams (`reflectFn`, `distillFn`, `ensureIndexFn`, `reindexFn`) replace production defaults in tests.
19
19
 
@@ -49,7 +49,7 @@ flowchart TD
49
49
  P -- yes --> P1[Log failures; refs that still fail\nare excluded from selection]
50
50
  P -- no --> L[Signal delta\nreflect: negative feedback, distill: any signal,\nnewer than the last ledger attempt\nand no hard ledger window]
51
51
  P1 --> L
52
- L --> L2[Fallback lanes\nproactive maintenance, high salience:\nonly retrieved or new material,\nscored and never planned]
52
+ L --> L2[Proactive maintenance lane:\nonly retrieved or new material,\nmaxPerRun picks, due after dueDays]
53
53
  L2 --> M[scoreSalience\nsalience vector per ref: encoding, outcome, retrieval\nutility scores from SQLite seed the outcome term]
54
54
  M --> N[Sort by salience rank DESC, no-op dampened\ndrop refs missing on disk\napply --limit if set]
55
55
  N --> Q
@@ -100,7 +100,7 @@ flowchart TD
100
100
  DISTILL_G -- no --> DISTILL_J{quality gate judge:\nreusable, non-redundancy, grounding}
101
101
  DISTILL_J -- pass --> DISTILL_H[createProposal lessons/slug-lesson\nor knowledge/slug\nsource: distill, gate decision deferred\nappendEvent: outcome=queued]
102
102
  DISTILL_J -- mean in the review band --> DISTILL_REVIEW[createProposal, gate decision deferred\nappendEvent: outcome=review_needed]
103
- DISTILL_J -- any criterion 2 or below, or the mean too low --> DISTILL_REJECT[improve_ledger row, no proposal\nappendEvent: outcome=quality_rejected]
103
+ DISTILL_J -- any criterion 2 or below, or the mean too low --> DISTILL_REJECT[improve_ledger row, no proposal\nappendEvent: outcome=quality_rejected\nunless a repeated lesson gets an update proposal, step 10]
104
104
  DISTILL_PROMOTE --> DISTILL_RETURN
105
105
  DISTILL_SKIP --> DISTILL_RETURN
106
106
  DISTILL_EXISTS --> DISTILL_RETURN
@@ -245,10 +245,17 @@ Reflect changes only an asset's `description`, `when_to_use` and title. The engi
245
245
  - **Grounding** asks whether every cause, step, number, rule and limit in the lesson is stated by the source or its feedback; 4–5 when each is, 3 when one stretches what the source says, 1–2 when any is in neither, when the lesson drops a limit the source states, or when it is about another subject. It is not part of the mean.
246
246
  - A criterion at 2 or below is `quality_rejected` whatever the mean is (the reason names the criterion: `grounding 2/5: …`). Otherwise the lesson passes when reusable and non-redundancy both score 4 or more, and a mean of those two of 2.5 or more is `review_needed`. A pass is also queued for a person (step 9), so the rejection is the only thing that keeps a lesson from a reviewer: on 2026-10-05 the old rubric (novelty, non-redundancy, and a grounding that scored 3 for a lesson that "goes beyond the source") passed 10 of the 17 bad lessons, because a lesson that adds to its memory scored as novel. The old novelty question was also the reason the judge rejected a faithful lesson of a lesson-worthy memory. A distill with no source to read (an unindexed ref distilled "from feedback signal alone") gives the judge an empty source, so its lesson is expected to be rejected.
247
247
  - `quality_rejected` writes an `improve_ledger` row for the input (30-day distill rejection window) and a `distill_invoked` event carrying `score`, the per-criterion `criteria` and the `reason`. It mints no proposal.
248
- - `review_needed` mints a pending proposal stamped `deferred` / `quality-gate` for a human; the triage drain leaves it alone. It is the outcome for a non-passing mean of 2.5 or more when no criterion is at 2 or below, a judge that times out or returns something unparseable or incomplete, the optional fidelity check's contradiction (`processes.distill.fidelityCheck.enabled`, off by default; a lesson that contradicts its source reaches a human this way, not through grounding), and a heuristic lesson-quality finding (for example an invalid `description`), which is found before the judge is called.
248
+ - `review_needed` mints a pending proposal stamped `deferred` / `quality-gate` for a human; the triage drain leaves it alone. It is the outcome for a non-passing mean of 2.5 or more when no criterion is at 2 or below, a judge that times out or returns something unparseable or incomplete, and a heuristic lesson-quality finding (for example an invalid `description`), which is found before the judge is called.
249
249
  9. Create proposal: `createProposal(stash, { ref: lessonRef, source: "distill", payload })` through `mintProposal`. A pass is minted `deferred` (gate `quality-gate`, reason `distill-review`) with the judge's per-criterion `scores` and `judgeReason`, for a person: the triage drain and its judgment tier leave it alone, and the improve ledger records `review_needed`. It is never `staged`. On 2026-10-05 the gate had staged 12 lessons and 10 were bad (they restated the memory, claimed what it does not say, or filed a dated status as a lesson); no judge score separated them from the two good ones. A promotion to knowledge that passes is deferred the same way. With `processes.distill.qualityGate` off nothing was judged, so the proposal is minted unstamped for the drain to decide.
250
250
  10. Emit `distill_invoked` event with `outcome: "queued"`.
251
251
 
252
+ **Updating a lesson the memory repeats (#1090):** when the lesson repeats one the library holds, distill extends that lesson instead of only dropping the duplicate. It happens in two places, both with the quality gate on: the judge scored the writer's lesson 2 or below on non-redundancy (step 8), or the writer answered `NONE` (step 5). Either way the update is tried for the related lessons (among the top 3 nearby assets, as for the judge, in the bundle the proposal is filed in) that the writer's `reason`, or the judge's, names by ref or name, or for the only related lesson when it names none. One lesson among several that merely sit near the memory costs no call. A second call shows the writer the memory, its feedback and the candidate lessons' bodies, and asks it to pick the lesson that already states the memory's rule, list in `new_facts` each cause, step, number, limit or consequence the memory states and the lesson lacks (the memory's own incident retold is not one), and return that lesson's whole body with a line added for each; it answers `decision: "none"` when no listed lesson states the rule, when `new_facts` is empty (an update with none is not proposed), or when the memory contradicts a line. Then:
253
+
254
+ - Every existing body line must still be there (compared by words, in any case and spacing). A body that drops or rewords one is not proposed.
255
+ - The extended lesson (existing frontmatter and the whole new body) is judged as one lesson, in one call, by step 8's judge with an edit variant of its rubric: **reusable** and **grounding** read the whole lesson (a line is grounded when the memory, its feedback or the lesson being extended states it), and **non-redundancy** is asked of each added line against the lesson being extended: 1–2 when it only says again what the lesson says (its rule or cause in other words, or the same rule on an example from the memory), 4–5 when it states a fact the lesson lacks, however specific. The lesson being extended is shown to the judge as such, not as an asset the new lesson might repeat. Judging the added lines alone as a new lesson failed `reusable` for a lone fact, and no update passed. A body that adds no line is not proposed; anything short of a judge pass is not proposed.
256
+ - The update is a pending proposal on the existing lesson's ref (`source: distill`), the lesson's frontmatter unchanged except that the memory is added to `xrefs`. Its `beforeHash` is the lesson's as read, so accepting it after the lesson changed is refused as stale. It is minted `deferred` (gate `quality-gate`, reason `distill-update`) with the judge's `scores` and `judgeReason`, so the triage drain and its judgment tier leave it for a person. The result and the `distill_invoked` event are `queued` with `proposalRef` set to the lesson and `updatesExisting: true`.
257
+ - When nothing is proposed, the outcome is what it was before: `quality_rejected` with the judge's reason after a duplicate lesson, `skipped` / `nothing_reusable` after `NONE`.
258
+
252
259
  **Lesson-ref derivation rule:** `lessons/<type>-<name>-lesson` where `<type>-<name>` is derived from the input ref with origin stripped and non-alphanumeric characters replaced by `-`. Example: `skills/deploy` → `lessons/skill-deploy-lesson`. A lesson already at that ref is never overwritten (see step 4).
253
260
 
254
261
  **What it writes:** one durable proposal row in `state.db`. Never writes asset files directly.
@@ -610,7 +617,7 @@ Two proposals can share the same `ref`; their UUID primary keys prevent collisio
610
617
 
611
618
  ## Scope restrictions
612
619
 
613
- `akm improve` reads and writes one bundle, its write target: `--bundle`, else `defaultWriteTarget`, else the working bundle. The candidate set is that bundle's assets and nothing else, because every proposal is filed in the write target. An asset that another writable bundle owns is never planned: reflect would read it from its own bundle and the proposal would land in this one, either forking the asset as a `create` or overwriting this bundle's copy with content taken from the other's (#1000). A bare ref scope (`akm improve skills/x`) resolves inside the write target only, and distill's memory-to-knowledge promotion merges only with a doc that already exists there. To improve another writable bundle, select it with `--bundle team` or a bundle-qualified scope such as `team//skills/code-review`; a scheduled run therefore covers only its write target, and each other bundle needs its own scheduled `akm improve --bundle <name>` run. A dry run (`--dry-run`, `--plan`) resolves the bundle the way a live run does, with the working bundle starting from `AKM_BUNDLE_DIR` before `defaultBundle`, so it previews the bundle a live run improves. As a second line of defence, `createProposal` refuses a proposal whose `itemRef` names an asset owned by a different configured bundle than the queue's.
620
+ `akm improve` reads and writes one bundle, its write target: `--bundle`, else `defaultWriteTarget`, else the working bundle. The candidate set is that bundle's assets and nothing else, because every proposal is filed in the write target. An asset that another writable bundle owns is never planned: reflect would read it from its own bundle and the proposal would land in this one, either forking the asset as a `create` or overwriting this bundle's copy with content taken from the other's (#1000). A bare ref scope (`akm improve skills/x`) resolves inside the write target only, and distill's memory-to-knowledge promotion merges only with a doc that already exists there. To improve another writable bundle, select it with `--bundle team` or a bundle-qualified scope such as `team//skills/code-review`; a scheduled run therefore covers only its write target, and each other bundle needs its own scheduled `akm improve --bundle <name>` run. A dry run (`--dry-run`) resolves the bundle the way a live run does, with the working bundle starting from `AKM_BUNDLE_DIR` before `defaultBundle`, so it previews the bundle a live run improves. As a second line of defence, `createProposal` refuses a proposal whose `itemRef` names an asset owned by a different configured bundle than the queue's.
614
621
 
615
622
  `akm improve` and `akm lint` only operate on writable bundle sources (sources with `writable: true`). Read-only sources (git, npm, website) are excluded from the candidate set before any other filtering; a read-only write target plans nothing.
616
623
 
@@ -622,12 +629,12 @@ Every stage reads the improve ledger (`improve_ledger` in `state.db`, one row pe
622
629
 
623
630
  - A ref that passes reflect is planned for the loop. If it does not pass distill, distill is skipped for it (a `distill-skipped` action and an `improve_skipped` event with reason `distill_no_new_signal`).
624
631
  - A ref that passes only distill, and is a distill candidate, is planned distill-only.
625
- - A ref with no in-window feedback and no reflect window is left to the fallback lanes (proactive maintenance and high salience), which pick only what retrieval returned or new material (see [Retrieval scope](../improvement.md#retrieval-scope)). The lanes only select and score: a ref they pick is not planned for reflect or distill, so improve does not rewrite assets on a proactive cadence.
626
- - Every ref the loop does not take is counted in the plan's `signal` gate (or its `retrieval` gate, when the fallback lanes could not pick it for lack of usage evidence) and reported once, in aggregate, as an `improve_skipped` event (`no_new_signal`, `not_retrieved`).
632
+ - A ref with no in-window feedback and no reflect window is left to the proactive-maintenance lane, which picks only what retrieval returned or new material (see [Retrieval scope](../improvement.md#retrieval-scope)) and plans its picks with the feedback-bearing refs. The cap against a proposal flood: `maxPerRun` (15) picks per run, an asset due only after `dueDays` (30) without a reflect or distill, and reflect's `limit` (25).
633
+ - Every ref the loop does not take is counted in the plan's `signal` gate (or its `retrieval` gate, when the proactive lane could not pick it for lack of usage evidence) and reported once, in aggregate, as an `improve_skipped` event (`no_new_signal`, `not_retrieved`).
627
634
  - A memory flagged wrong is not distilled: when a negative feedback in the 30-day window recorded the hash of the body it judged (`contentHash`, the `body` hash of `content-hash.ts`) and the file's body still has it, the loop skips distill for it (a `distill-skipped` action and an `improve_skipped` event with reason `distill_flagged_wrong`) and records the attempt in the ledger as `unchanged`, so the ref waits for newer feedback. Reflect still plans it. A write that leaves the body alone (an inference stamp, a frontmatter repair) does not lift the flag; changing the body does. Feedback recorded without a hash keeps the earlier test: it flags the memory while it is newer than the file's last write, whose modification time is the edit signal, as for the retrieval scope's new material.
628
635
  - A memory whose only feedback in the 30-day window is positive without a reason or note (`hasOnlyBarePositiveFeedback` in `preparation.ts`) is not distilled either: the loop skips distill for it (a `distill-skipped` action with the reason "only positive feedback, without a reason" and an `improve_skipped` event with reason `distill_positive_without_reason`) and records the attempt in the ledger as `unchanged`, so it waits for newer feedback. A bare `--positive` only records that a note helped, which gives the writer nothing to distil: 10 of the 11 lessons made from such a memory were rejected on 2026-10-05, and the 11th was good. A reason, a note or a negative signal among the feedback, or an explicit ref scope, lets it through.
629
636
  - A memory whose frontmatter says `beliefState: deprecated` or `superseded` (`isDeprecatedOrSuperseded` in `preparation.ts`) is not distilled either: the loop skips distill for it (a `distill-skipped` action with the reason "marked deprecated or superseded" and an `improve_skipped` event with reason `distill_deprecated_or_superseded`) and records the attempt in the ledger as `unchanged`. An explicit `--scope` ref still runs. `contradicted` is not skipped: it marks an open conflict, not a retired note.
630
- - The loop's refs are ranked by salience (`scoreSalience` in `preparation.ts`, which also scores the fallback lanes' picks and computes each vector with `computeSalience` from `salience.ts`): encoding, outcome, and retrieval frequency and recency, discounted for file size, with a ref that was repeatedly skipped as a no-op ranked lower. Refs missing on disk are dropped, and `--limit` cuts the list: reflect-path refs first, then distill-only refs.
637
+ - The loop's refs are ranked by salience (`scoreSalience` in `preparation.ts`, which also scores the lane's picks and computes each vector with `computeSalience` from `salience.ts`): encoding, outcome, and retrieval frequency and recency, discounted for file size, with a ref that was repeatedly skipped as a no-op ranked lower. Refs missing on disk are dropped, and `--limit` cuts the list: reflect-path refs first, then distill-only refs.
631
638
 
632
639
  An explicit ref scope bypasses every gate. Consolidation, extract and schema repair read their own ledger sources in their own stages.
633
640
 
@@ -429,7 +429,7 @@ so a layout change is applied in place rather than by discarding the index.
429
429
  Durable workflow, task, proposal, event, and usage state in `state.db` is
430
430
  never touched by these paths.
431
431
 
432
- Workflow `.md` and `.yml` adapters compile directly to source IR version 1.
432
+ The workflow `.md` adapter compiles directly to source IR version 1.
433
433
  The index stores only the ordinary normalized `entries` row and searchable
434
434
  metadata derived from that IR. It does not cache a second workflow AST or an
435
435
  executable plan. Starting a run recompiles the authored source once and freezes
@@ -223,11 +223,10 @@ By default (`--format json`, `--detail brief`), search emits minimal fields:
223
223
  `--detail normal` emits `type`, `name`, `description`, `action`, `score`, and
224
224
  `estimatedTokens`, plus optional warning, quality, or env-key fields.
225
225
  `--detail full` adds `whyMatched`, `origin`, `path`, and timing data.
226
- `--shape summary` returns metadata only (no content), under 200 tokens.
227
226
 
228
227
  ### Agent-optimized output
229
228
 
230
- For each materialized local hit, `--shape agent` includes `name`, canonical
229
+ For each materialized local hit, `--detail agent` includes `name`, canonical
231
230
  `ref`, `type`, absolute `path`, current-policy `editable`, `description`,
232
231
  `action`, `score`, and optional `estimatedTokens`/env keys. `editHint` appears
233
232
  only when `editable` is `false`. The hint is supplemental: `action` remains the
@@ -253,7 +253,7 @@ extension, so an install without `sqlite-vec` leaves the unread table in place.
253
253
 
254
254
  #### Workflow source indexing
255
255
 
256
- Peer `.md` and `.yml` workflow sources compile directly to source IR version 1.
256
+ Workflow `.md` sources compile directly to source IR version 1.
257
257
  The index stores the ordinary normalized `entries` row and metadata derived from
258
258
  that IR; there is no workflow-specific AST cache or parallel persisted source
259
259
  representation. Executable durable plans belong only to `state.db`.
@@ -503,7 +503,7 @@ Keys of `metadata_json`, each written only when the proposal has it:
503
503
  | `gateDecision` | `{ outcome: auto-accepted\|deferred\|staged\|auto-rejected, reason, gate?, contentHash?, measured?, thresholds?, scores?, judgeReason?, decidedAt }`; `scores` (per criterion) and `judgeReason` are the quality judge's evidence on its `staged` pass (or on a distill pass it deferred for review as `distill-review`), and stay on the row when the drain accepts it; a reflect pass deferred as `body-edit` by an older release carries them too |
504
504
  | `backupContent` | The target's content before promotion, for `revert` |
505
505
  | `acceptedTarget` | `{ source, root, path, contentHash }`, where accepted content went |
506
- | `eligibilitySource` | The improve lane that planned the asset: `signal-delta` or `scope`; `proactive` and `high-salience` only on rows an older release wrote, since those lanes score assets and no longer plan them |
506
+ | `eligibilitySource` | The improve lane that planned the asset: `signal-delta`, `proactive` or `scope`; `high-salience` only on rows an older release wrote, since that lane was removed |
507
507
  | `promotionSource`, `promotionSourceHash` | A consolidate promotion's source memory and its raw body hash at mint: accept archives the memory only if the hash still matches, and once the promotion is decided the ledger holds the memory until its body changes |
508
508
  | `retirement` | A `consolidate-pair` retire proposal's verdict: `retiredRef`, `successorRef`, `cosine`, `judgeLabel`, `judgeReason`, `onlyInRetired` and `onlyInSuccessor` (the judge's claim lists by role, each at most 20 x 200 chars; absent before they were recorded), `retiredContentHash`, `successorContentHash`, `reason`, and `continuityRisk` when the retirement continuity check flagged it |
509
509
  | `retiredArchive` | `{ dirs }`, the archive directories of an accepted retirement, for `revert` |
@@ -632,7 +632,7 @@ The JSONL file at `$CACHE/events.jsonl` is no longer read or written by akm.
632
632
  | `improve_invoked` | `akm improve`, live runs | `strategy`, `scope`, `dryRun`, `eligibleCount` |
633
633
  | `improve_completed` | `akm improve`, end of a run | `strategy`, `plannedRefs`, per-mode and per-class action counts, memory-cleanup counts |
634
634
  | `improve_failed` | `akm improve`, a run that crashed | `strategy`, `error` (redacted), `durationMs` |
635
- | `improve_skipped` | `akm improve`: a ref, a lane, or a group of refs left out | `reason`: `no_new_signal`, `not_retrieved`, `distill_no_new_signal`, `budget_exhausted`, `budget_exhausted_batch`, `asset_missing_on_disk`, `strategy_filtered_all_passes`, `autonomy_gated`, `engine_unavailable`, `pool_below_min_size`, `consolidation_no_memory_updates`, `below_min_new_sessions`, `derived_memory_reflect_skipped`, `memory_distill_requires_feedback`, `distill_flagged_wrong`, `distill_positive_without_reason`, `distill_deprecated_or_superseded`; with `count`, `remaining`, `strategy`, `lane` or `configKey` where they apply |
635
+ | `improve_skipped` | `akm improve`: a ref, a lane, or a group of refs left out | `reason`: `no_new_signal`, `not_retrieved`, `distill_no_new_signal`, `budget_exhausted`, `budget_exhausted_batch`, `asset_missing_on_disk`, `strategy_filtered_all_passes`, `autonomy_gated`, `engine_unavailable`, `consolidation_no_memory_updates`, `below_min_new_sessions`, `derived_memory_reflect_skipped`, `memory_distill_requires_feedback`, `distill_flagged_wrong`, `distill_positive_without_reason`, `distill_deprecated_or_superseded`; with `count`, `remaining`, `strategy`, `lane` or `configKey` where they apply |
636
636
  | `improve_lock_recovered` | `akm improve` reclaiming a stale run lock | `lockName`, `stalePid`, `lockedAt`, `recoveredAt`, `lockAgeMs`, `reason` |
637
637
  | `improve_review_needed` | `akm feedback`, when a high-utility asset's utility drops below the review threshold | `previousUtility`, `nextUtility`, `reason` |
638
638
  | `improve_reflect_outcome` | the improve loop, after each reflect call | `ok`, `durationMs`, `engine`, `reason` |
@@ -703,7 +703,7 @@ One line per memory belief-state transition: `{ appliedAt, ref, parentRef, fromS
703
703
 
704
704
  All asset files live under `$STASH/` in type-specific subdirectories defined by the `PLACEMENT_SPECS` map in `src/core/asset/asset-placement.ts`:
705
705
 
706
- The `workflows/` directory holds peer `.md` and `.yml` workflow sources. The
706
+ The `workflows/` directory holds `.md` workflow sources. The
707
707
  `tasks/` directory holds task source v4 `.yml` sources.
708
708
 
709
709
  | Subdirectory | Asset Type | Format |
@@ -713,7 +713,7 @@ The `workflows/` directory holds peer `.md` and `.yml` workflow sources. The
713
713
  | `agents/<name>.md` | agent | YAML-FM + Markdown |
714
714
  | `knowledge/<name>.md` | knowledge | YAML-FM + Markdown |
715
715
  | `instructions/<name>.md` | instruction | YAML-FM + Markdown |
716
- | `workflows/<name>.md` / `workflows/<name>.yml` | workflow | Peer `.md` Markdown and `.yml` GitHub-shaped sources; both compile through source IR v1 |
716
+ | `workflows/<name>.md` | workflow | Markdown source; compiles through source IR v1 |
717
717
  | `scripts/<name>.<ext>` | script | sh / ts / js / ps1 etc. |
718
718
  | `memories/<name>.md` | memory | YAML-FM + Markdown |
719
719
  | `env/<name>.env` | env | `KEY=VALUE` pairs |
@@ -8,7 +8,16 @@ close each format is to that contract today.
8
8
 
9
9
  ## The contract
10
10
 
11
- A reader must tolerate anything an earlier release wrote: convert the old
11
+ **Version floor (0.10, #1091): 0.9.15.** A 0.10 reader owes data written by
12
+ 0.9.15 or later, and nothing older. An install on an earlier release runs
13
+ `akm migrate apply` under akm 0.9.x first, then upgrades. A reader that meets a
14
+ shape older than the floor does not convert it: it refuses, with one message
15
+ naming `akm migrate apply` under akm 0.9.x (config: the retired
16
+ `stashDir`/`sources[]`/`installed[]` keys; `engines.*.extraParams` keys that
17
+ shadow a first-class field). A shape that 0.9.15 or later still wrote stays
18
+ readable; the rows below say which those are.
19
+
20
+ Above the floor, a reader must tolerate anything an earlier release wrote: convert the old
12
21
  shape in memory, warn once, and name `akm migrate apply` as the on-disk
13
22
  cleanup path — the migrator is where an explicit, backed-up rewrite happens,
14
23
  never a precondition for reading. The only refusal a reader is allowed is data
@@ -27,11 +36,11 @@ and named once, round-trips through ordinary writes, and is dropped by
27
36
 
28
37
  | Format | Where written | Version marker | Older data | Newer data | Gate |
29
38
  | --- | --- | --- | --- | --- | --- |
30
- | `config.json` | `src/core/config/config.ts` | `configVersion` field | `configVersion` is read, never gated on (`readConfigVersion`, `src/core/config/config.ts`): a missing field or `"0.9.0"` — the only value akm has shipped — loads silently, and any other value is named once and read as `0.9.0`; like the `extraParams` keys, the legacy `stashDir`/`sources[]`/`installed` shape is converted rather than dropped — `migrateLegacySourceShape` (`src/core/config/legacy-source-shape-shim.ts`) folds it into `bundles`/`defaultBundle` in memory on every load; `akm migrate apply`'s `configFile` step (`normalizeConfigFile`) persists all of it — `configVersion: "0.9.0"`, the converted source layout, the lifted `extraParams`, unknown keys dropped — to the file once, under a backup | Same as older: the value is named once and the file is read as `0.9.0`; there is no `UNSUPPORTED_CONFIG_VERSION` refusal. `akm health`'s `binary-config-skew` advisory (`src/commands/health/config-skew.ts`) is what says "a newer akm wrote this config, upgrade this install" | Upgrade rehearsal gate (`tests/integration/upgrade-rehearsal/`); `tests/config-v09.test.ts` |
39
+ | `config.json` | `src/core/config/config.ts` | `configVersion` field | `configVersion` is read, never gated on (`readConfigVersion`, `src/core/config/config.ts`): a missing field or `"0.9.0"` — the only value akm has shipped — loads silently, and any other value is named once and read as `0.9.0`. Below the 0.9.15 floor the file is refused, not converted: the retired `stashDir`/`sources[]`/`installed[]` layout fails validation (`AkmConfigSchema`, `src/core/config/config-schema.ts`) and an `extraParams` key that shadows a first-class engine field (`temperature`, `maxTokens`, `enableThinking`, `reasoningEffort`) is rejected by `validateExtraParams`, each telling the user to run `akm migrate apply` under akm 0.9.x or to set the field. A config with no `scheduler.enabled` (0.9.15 wrote none) is read as "keep what is installed": `akm task sync` adopts the akm-written scheduler rows as the list (`tests/integration/upgrade-rehearsal/`'s 0.9.15 origin). 0.9.16's `{kind, ref, sourceId}` `scheduler.enabled` entries are still read, as their `ref`, and written back as plain refs; `akm migrate apply`'s `configFile` step (`normalizeConfigFile`) persists that — `configVersion: "0.9.0"`, unknown keys dropped — to the file once, under a backup | Same as older: the value is named once and the file is read as `0.9.0`; there is no `UNSUPPORTED_CONFIG_VERSION` refusal. `akm health`'s `binary-config-skew` advisory (`src/commands/health/config-skew.ts`) is what says "a newer akm wrote this config, upgrade this install" | Upgrade rehearsal gate (`tests/integration/upgrade-rehearsal/`); `tests/config-v09.test.ts` |
31
40
  | `state.db` | `src/core/state/migrations.ts` (append-only `MIGRATIONS` registry, applied by `src/storage/sqlite-migrations.ts`) | `schema_migrations` ledger table | `openStateDatabase` opens one connection (mkdir, open, standard pragmas), reads the ledger on it, and runs every migration not yet applied forward in one transaction; a missing/empty/table-less file is simply a fresh database. Before a migration that drops schema (`018-drop-dead-lane-schema`, `028-improve-ledger`; `STATE_MIGRATION_SAFETY_BY_ID`) runs against a database that already has migrations applied, the file is copied beside itself to `state.db.pre-<migration id>.bak` via `VACUUM INTO` on the same connection | An older binary opening a `state.db` migrated by a newer akm continues with the schema it knows and warns once (`warnNewerStateLedger`, `src/core/state-db.ts`); it does not refuse; only a ledger that diverges from this akm's registry is refused (`assertMigrationLedger`) | Upgrade rehearsal gate (`tests/integration/upgrade-rehearsal/`) |
32
41
  | `index.db` | `src/storage/repositories/index-schema.ts` (`ensureSchema`; `index-entry-schema.ts` for the DDL and the layout constant) | `index_meta.version` = `CANONICAL_INDEX_DB_VERSION` (26), a layout marker | Readers (`openExistingDatabase` / `openReadonlyExistingDatabase`) serve it as-is and name `akm index` once (`checkIndexLayout`); the writable opener migrates in place — added columns via `ALTER TABLE`, a one-time FTS rebuild from `entries` for the layout-23 content-copy tables, and for layout 24 the drops of the sqlite-vec mirror `entries_vec` (when sqlite-vec loads; dropping a vec0 table needs it) and `entry_fragments_fts`, with `entries.search_text` hashed into `embed_hash` before the column is dropped so no vector is re-embedded, and for layout 25 the declared links (`asset_links`, #935) derived from each entry's stored `document_json` with no file read (only the directories holding workflows and tasks, whose targets no earlier layout stored, lose their incremental cursor and re-drain on the next `akm index`) — never dropping `embeddings`, `utility_scores*`, or `llm_enrichment_cache`. It drops the lazy-extraction queue `graph_extraction_queue`, retired in 0.9.17, and, on any layout, the LLM entity graph (`graph_meta`, `graph_files`, `graph_file_*`) and its `llm_enrichment_cache` rows, retired in 0.9.17-alpha.9 — an older release's `CREATE TABLE IF NOT EXISTS` recreates the graph tables (empty) if it ever opens this index again. It also deletes `llm_enrichment_cache` rows at the default `cache_variant` — the metadata-enhance pass, retired in 0.9.17-alpha.9 — on any layout; memory inference uses its own named variant and is unaffected. A migration leaves `index_meta.vacuumPending`, and the next `akm index` VACUUMs the freed pages. An `entries` table older than layout 21 has its entries-keyed tables recreated: one that lacks a column this release reads, or that still has a transitional column layout 21 removed (`entry_key`, `dir_path`, `stash_dir`, `entry_json`, `entry_type`), as the table 0.9.1 wrote does, with `document_json` empty on every row. A reader finds such a table unservable (`hasCurrentEntriesTable`), so a read rebuilds it inline. Only `SQLITE_CORRUPT` deletes and rebuilds the file (#865) | Refused by readers and the writable opener alike (`INDEX_SCHEMA_INCOMPATIBLE`, "Upgrade akm to use this index.", `newerIndexLayoutError`), leaving the file untouched: a newer layout may lack a column this akm reads, as layout 25 lacks `search_text` | `tests/integration/indexer/index-layout-migration.test.ts` (layout-23 and layout-24 fixtures); `tests/integration/previous-release-corpus.test.ts` (the layout-25 index 0.9.17-alpha.7 wrote, `index-v25.sql`, and the layout-20 index 0.9.1 wrote, `index-v20.sql`); `tests/integration/storage/index-generation-read-boundary.test.ts`; upgrade rehearsal gate (`tests/integration/upgrade-rehearsal/`) |
33
- | Task source (`tasks/*.yml`) | `src/tasks/source/parse-task-source.ts` | `version: 4` (`TASK_SOURCE_V4_VERSION`) | **The named exception to the contract (#987): the runtime reads only v4.** `version: 2` or `version: 3` throws `TASK_SCHEMA_VERSION_UNSUPPORTED`, and a declared `version: 4` document whose `schedule[]` still carries 0.9.15's retired `enabled` key throws `TASK_SOURCE_INVALID`; both messages name `akm migrate apply`, the one place the conversion happens (`scripts/akm-migrate/migrate/task-files.ts`, planning every v2/v3/v4 file straight to v4 with the pure planner `src/tasks/source/task-to-v4.ts`, rewriting under a backup; `akm upgrade` runs it after an install). It covers every stash the runtime reads tasks from: each enabled configured bundle, and the stash `AKM_BUNDLE_DIR` selects when no bundle names it; the `akm-task` probe still recognizes a root of v2/v3 task files, so one without a recorded adapter is found. Every caller reports the refusal for that one file: `akm task sync` keeps reconciling every other task and leaves the file's installed row as it is | Any other `version` throws `TASK_SCHEMA_VERSION_UNSUPPORTED` naming `akm migrate apply` | Upgrade rehearsal gate (`tests/integration/upgrade-rehearsal/`); `tests/integration/previous-release-corpus.test.ts` (each older shape is refused at runtime and converted by the migrator) |
34
- | Workflow IR (frozen plans) | `src/workflows/freeze/freeze.ts`, read back in `src/workflows/runtime/run-plan.ts` (`readRunPlan`, `decodeWorkflowPlan`) | `irVersion` (`WORKFLOW_PLAN_VERSION`, 6) on the plan and in `plan_ir_version`; `plan_hash` alongside | Every workflow compiles to one plan type. `decodeWorkflowPlan` reads the irVersion 4 and 5 shapes (their `sourceReadSet` and host-identity fields included) as well as the current one, and keeps keys it does not know. Neither the version nor the hash gates a read: a stored plan that decodes runs whatever `irVersion` froze it, with one warning; one that cannot be decoded is abandoned by `akm workflow run` (status `failed`) with a message naming `akm workflow run <ref>` to start afresh — a status change, never an exception; `status`/`list`/`abandon` never read the plan | A newer akm's plan that still decodes runs the same way; one that does not decode is left untouched and refused with a message naming "Upgrade akm" (`readRunPlan`'s `newer`) | `tests/workflows/run-plan.test.ts`; `tests/integration/workflows/frozen-plan.test.ts` — the rehearsal builds no workflow run |
42
+ | Task source (`tasks/*.yml`) | `src/tasks/source/parse-task-source.ts` | `version: 4` (`TASK_SOURCE_V4_VERSION`) | **The named exception to the contract (#987): the runtime reads only v4.** `version: 2` or `version: 3` throws `TASK_SCHEMA_VERSION_UNSUPPORTED`, and a declared `version: 4` document whose `schedule[]` still carries 0.9.15's retired `enabled` key throws `TASK_SOURCE_INVALID`; both messages name `akm migrate apply`, the one place the conversion happens (`scripts/akm-migrate/migrate/task-files.ts`, planning every v2/v3/v4 file straight to v4 with the pure planner `scripts/akm-migrate/migrate/task-to-v4.ts`, rewriting under a backup; `akm upgrade` runs it after an install). It covers every stash the runtime reads tasks from: each enabled configured bundle, and the stash `AKM_BUNDLE_DIR` selects when no bundle names it; the `akm-task` probe still recognizes a root of v2/v3 task files, so one without a recorded adapter is found. Every caller reports the refusal for that one file: `akm task sync` keeps reconciling every other task and leaves the file's installed row as it is | Any other `version` throws `TASK_SCHEMA_VERSION_UNSUPPORTED` naming `akm migrate apply` | Upgrade rehearsal gate (`tests/integration/upgrade-rehearsal/`); `tests/integration/previous-release-corpus.test.ts` (each older shape is refused at runtime and converted by the migrator) |
43
+ | Workflow IR (frozen plans) | `src/workflows/freeze/freeze.ts`, read back in `src/workflows/runtime/run-plan.ts` (`readRunPlan`, `decodeWorkflowPlan`) | `irVersion` (`WORKFLOW_PLAN_VERSION`, 6) on the plan and in `plan_ir_version`; `plan_hash` alongside | Every workflow compiles to one plan type. `decodeWorkflowPlan` reads the irVersion 5 shape 0.9.15 and 0.9.16 wrote (its `sourceReadSet` and host-identity fields included) as well as the current one, and keeps keys it does not know. Neither the version nor the hash gates a read: a stored plan that decodes runs whatever `irVersion` froze it, with one warning; one that cannot be decoded is abandoned by `akm workflow run` (status `failed`) with a message naming `akm workflow run <ref>` to start afresh — a status change, never an exception; `status`/`list`/`abandon` never read the plan | A newer akm's plan that still decodes runs the same way; one that does not decode is left untouched and refused with a message naming "Upgrade akm" (`readRunPlan`'s `newer`) | `tests/workflows/run-plan.test.ts`; `tests/integration/workflows/frozen-plan.test.ts` — the rehearsal builds no workflow run |
35
44
  | Native scheduler rows (crontab, launchd plists, Task Scheduler) | `src/tasks/backends/cron.ts`, `launchd.ts`, `schtasks.ts`; the invocation grammar in `src/tasks/scheduler-invocation.ts` | The row's own invocation shape: current rows set their environment inline (a cron `VAR=value` prefix, a plist `EnvironmentVariables` entry, a PowerShell `$env:` assignment) — always `AKM_BUNDLE_DIR`, the syncing shell's working stash — and carry no `--scheduler-context`; 0.9.0 – 0.9.17-alpha.6 rows carry `--scheduler-context <descriptor>` holding the same values; older rows carry neither. A cron command over 1,000 bytes runs an akm wrapper script (`sh <log dir>/.akm-cron-wrapper-…sh`) that holds the same line | Every shape is listed and parsed (`parseScheduledInvocationArgv`; a spilled cron row from its wrapper script): a row inside akm's own `# akm:task` markers from before `--scheduler-context` (#881), and a `--scheduler-context` row, which keeps firing (the CLI applies its descriptor, below) until the next plain `akm task sync` rewrites it in place — an update that keeps the row's launcher and schedule, never an add or remove. The primary bundle proves a row by the `AKM_BUNDLE_DIR` it carries, inline or in its descriptor (#846); any other bundle owns rows by name. Rows that still carry the pre-0.9.17 workflow execution-evidence marker (`# akm:workflow-evidence` in the crontab, an `akm-workflow-evidence` plist comment or PowerShell literal) are listed like any other and rewritten once without it. Plain `akm task sync` keeps an installed row's own launcher (`installOptionsFor`, `src/tasks/scheduler-sync.ts`) and recomputes its environment; `akm task sync --rebind` repoints the launcher | A row inside akm's markers, `com.akm.task.` label namespace, or `\akm\` task folder whose argv this binary cannot parse (for example one a newer akm wrote) is not listed: sync leaves it alone unless an enabled source renders a row with the same native id, which then replaces it — the row is derived state and its source is the record. A crontab whose akm markers are malformed is refused unmodified (`parseBlocks`, `cron.ts`) | Upgrade rehearsal gate (`tests/integration/upgrade-rehearsal/`: the previous release's `--scheduler-context` rows fire under the candidate, then `task sync --dry-run` shows them only as updates); `tests/tasks-sync.test.ts` (`--scheduler-context` rows and spilled rows are updates, never adds) |
36
45
  | Scheduler-context descriptor (`$DATA/tasks/context/<sha256>.json`, written by 0.9.0 – 0.9.17-alpha.6) | No longer written: a row carries its own environment (#987). Read by `src/tasks/scheduler-invocation.ts` (`readLegacySchedulerContext`) | `version: 1`; the environment key set | When a row that names one fires, the CLI reads the file as plain JSON and applies its `AKM_*_DIR` keys and, in one written before 0.9.17, `PATH`; nothing else about the file (name, mode, owner, link) is checked. Sync reads its `AKM_BUNDLE_DIR` to attribute the row. A plain `akm task sync` that reconciles the row rewrites it without the argument; a row a sync leaves as it is (its source failed, a `--bundle` sync did not cover it) keeps naming the file | A key this binary does not know is ignored. A file that is gone or is not JSON fails the run with `INVALID_CONFIG_FILE` naming `akm task sync`; sync cannot attribute such a row to the primary bundle and leaves it in place, and `akm task prune` reports it as `invalid-context` | `tests/tasks-scheduler-invocation.test.ts`; `tests/integration/scheduler-context-launcher.test.ts`; upgrade rehearsal gate (`tests/integration/upgrade-rehearsal/`) fires the previous release's rows under the candidate before and after its plain `task sync` |
37
46
  | Proposal `metadata_json` | `src/storage/repositories/proposals-repository.ts` | No explicit version field; presence/absence of the `changes` key | Rows from before the `changes` key existed (~89% of archived rows on real installs) are treated as a known legacy gap — decoded with an empty change list rather than thrown (`proposals-repository.ts:47-56`). 0.9.17-alpha.9 adds a consolidate pair-pass `retire` proposal's `changes[0].op: "delete"` (no `after`, already a valid `FileChangeOp`) and its `retirement` / `retiredArchive` / `promotionSource` / `promotionSourceHash` metadata keys, plus the retirement continuity check's `retirement.continuityRisk` (rule R3, item 1) — a person can accept a proposal carrying it by id even though it is excluded from bulk accept; an older reader's `validatePresentMetadata` does not enumerate these keys, so it neither inspects nor rejects them — the row decodes with them silently absent, same as any other field an older release predates. 0.9.19 adds `reviewHistory` (the rejections `akm proposal reopen` undid, oldest first, each with its gate verdict) the same way: a proposal reopened by 0.9.19 and read by an older release is an ordinary pending proposal with its history absent, and that release drops the key if it rewrites the row. The quality judge's evidence, `scores` and `judgeReason`, is added inside the existing `gateDecision` object (Unreleased): an older reader's `validatePresentMetadata` checks only the gate fields it knows and carries the object through whole, so the evidence survives a rewrite by an older release unread. An older release also counts a pending proposal's age (retention expiry, `--older-than`) from `createdAt`, so after a downgrade a reopened proposal can expire at the next `akm improve` run (a retire proposal never expires), or be swept by a scheduled `--older-than` bulk action or drain | 0.9.17-alpha.8 and earlier predate the retire proposal shape entirely — they were never taught to read a delete-primary change with no `after`. `akm proposal show` / `diff` / `drain` exit 70 on one, and drain's own nightly pre-pass failing on the first pending retire proposal it meets stops that run's auto-promotion for the whole stash, not just that one proposal. **Downgrade gate:** every pending retire proposal must be accepted or rejected (`akm proposal accept` / `reject`) before downgrading to 0.9.17-alpha.8 or earlier — once decided it is archived, out of the pending set `show`/`diff`/`drain` iterate, and no longer trips this. **Gap** otherwise (no code-level guard; this is operator guidance only). Downgrading also revives the starvation the second review round's B1 fixed forward-only: 0.9.17-alpha.8's `retrieval-scope.ts` counts a `consolidate-pair` ledger row as processed, so the pair pass's own nightly attempts crowd new material back out of every other improve lane's scope, the same way they did before B1 — measured on the real bundle, two alpha.9 nights left only 341 of 2,183 new-only assets still in scope once read under alpha.8. **Gap** here too (the ledger `source` string carries no version of its own; operator guidance only — do not downgrade across a pair-pass run without expecting this). | `tests/proposal-repository-pure.test.ts` — unit only; the rehearsal builds no proposal |
@@ -34,7 +34,7 @@ removing a half-built surface over shipping it behind a label.
34
34
  | D9 `--auto-accept` warn-and-ignore | **Shipped** (warns, and no longer poisons the scope positional) |
35
35
  | D10 extract migration machinery | Partial (`akm-migrate` bin exists; code still in-repo) |
36
36
  | D11 OKF foundational | Partial (the `okf` adapter has landed) |
37
- | D12 `placeNew()` wiring | **Deferred to 0.10** |
37
+ | D12 `placeNew()` wiring | **Dropped in 0.10** (method removed; see [#1163](https://github.com/itlackey/akm/issues/1163)) |
38
38
 
39
39
  ---
40
40
 
@@ -491,7 +491,11 @@ without changing AKM-native refs or classification.
491
491
 
492
492
  ## D12 — `BundleAdapter.placeNew()` stays unwired until 0.10
493
493
 
494
- **Status: DEFERRED to 0.10.** The interface declares `placeNew(c, conceptId)`
494
+ **Status: DROPPED in 0.10 (owner decision 2026-10-10).** `placeNew()` and its
495
+ implementations were removed; nothing ever called them. Restoring per-adapter
496
+ placement is only needed for writable bundles in a non-akm layout, tracked in
497
+ [#1163](https://github.com/itlackey/akm/issues/1163). The text below is the
498
+ 0.9.0 record. The interface declared `placeNew(c, conceptId)`
495
499
  OPTIONAL (`src/core/adapter/bundle-adapter.ts`), and 8 of the built-in adapter
496
500
  modules already implement it — `generic-files`, `akm`, `llm-wiki`,
497
501
  `agent-skills`, `dotenv`, `akm-workflow`, `akm-task`, and the shared
@@ -5,8 +5,8 @@
5
5
  > `scripts/refresh-canary-set.ts` were deleted with the improve ledger change
6
6
  > (migration 028 drops the tables); recorded telemetry shows no alert ever fired. The
7
7
  > generation and lexical-diversity merge checks, which never refused a merge,
8
- > went with it; only the random-cluster injection in
9
- > `src/commands/improve/anti-collapse.ts` remains. The history below is kept
8
+ > went with it, and so did the random-cluster injection
9
+ > (`consolidate.antiCollapse`, removed in 0.10, #1131). The history below is kept
10
10
  > for reference.
11
11
  >
12
12
  > **Earlier status:** Partially implemented. The COLLAPSE alert class (§1, three
@@ -121,7 +121,7 @@ steps:
121
121
  map:
122
122
  over: steps.intake.output.issues
123
123
  concurrency: 3
124
- unit: { isolation: worktree, retry: { max: 2, on: [timeout] } }
124
+ unit: { retry: { max: 2, on: [timeout] } }
125
125
  # `output` describes the REDUCER RESULT: the default `collect` reducer
126
126
  # folds per-item unit results into an array.
127
127
  output: { type: array }
@@ -131,11 +131,6 @@ steps:
131
131
  - id: verdict
132
132
  inputs: [steps.implement.output]
133
133
  output: { type: object }
134
- - id: pick-outcome
135
- route:
136
- input: steps.verdict.output.status
137
- when: [{ match: clean, step: announce }]
138
- default: escalate # both targets are LATER steps — routes are forward-only
139
134
  - id: escalate
140
135
  inputs: [steps.verdict.output]
141
136
  - id: announce
@@ -231,13 +231,12 @@ akm() { bun "$REPO/src/cli.ts" "$@"; }
231
231
 
232
232
  ### 3.2 Verify isolation before mutation
233
233
 
234
- `config path --all` is a read-only recovery surface and works before config
235
- exists.
234
+ `info` is read-only and works before config exists.
236
235
 
237
236
  ```sh
238
- akm config path --all --format json >"$AKM_SANDBOX/paths.json"
237
+ akm info --format json >"$AKM_SANDBOX/paths.json"
239
238
  jq -e --arg root "$AKM_SANDBOX/" \
240
- 'all(to_entries[]; (.value | tostring | startswith($root)))' \
239
+ '[.bundleDir, .configDir, .dataDir, .cacheDir, .stateDir] | all(startswith($root))' \
241
240
  "$AKM_SANDBOX/paths.json"
242
241
  ```
243
242
 
@@ -370,7 +369,7 @@ Copy this only after exact ranking assertions:
370
369
  cp -R "$REPO/tests/fixtures/manual-qa/bundle/." "$AKM_BUNDLE_DIR/"
371
370
  akm index
372
371
  akm show knowledge/qa-guide | jq -e '.name == "qa-guide"'
373
- akm show workflows/typed-route | jq -e '.type == "workflow"'
372
+ akm show workflows/typed-params | jq -e '.type == "workflow"'
374
373
  akm show tasks/manual-success | jq -e '.type == "task"'
375
374
  ```
376
375
 
@@ -536,7 +535,7 @@ for command in \
536
535
  or `propose`.
537
536
  - [ ] **[CORE]** Bare `akm` and `akm help` print the sectioned overview without
538
537
  reading or rewriting invalid config.
539
- - [ ] **[CORE]** `help agents`, `help agents --full`, `hints`, and
538
+ - [ ] **[CORE]** `hints` and
540
539
  `hints --detail brief` are nonempty and have the expected relative detail.
541
540
  - [ ] **[LOCAL]** `help bundle|env|secret|workflow|task|proposal` agrees with the
542
541
  corresponding `--help` command tree.
@@ -598,9 +597,8 @@ test ! -s "$AKM_SANDBOX/silent.out"
598
597
  never materialize values.
599
598
  - [ ] **[LOCAL]** Concurrent independent `config set` processes preserve both
600
599
  changes or one fails cleanly; JSON stays valid and no temp file remains.
601
- - [ ] **[LOCAL]** `config path --all` honors output formatting. Bare
602
- `config path` is a raw path primitive; if format/output are ignored it
603
- must warn explicitly. Silent ignoring is a failure.
600
+ - [ ] **[LOCAL]** Bare `config path` is a raw path primitive; if format/output
601
+ are ignored it must warn explicitly. Silent ignoring is a failure.
604
602
 
605
603
  ### 6.5 Help migration notes
606
604
 
@@ -810,9 +808,9 @@ grep -qi '<html' "$AKM_SANDBOX/info.html"
810
808
  ### 8.3 Shape, detail, and output destination
811
809
 
812
810
  - [ ] **[CORE]** Brief/normal/full increase detail without identity drift.
813
- - [ ] **[CORE]** Summary succeeds only on show; all other commands reject it
814
- before side effects with exit `2`, `INVALID_SHAPE_VALUE`.
815
- - [ ] **[LOCAL]** Agent shape keeps action fields and strips non-action metadata.
811
+ - [ ] **[CORE]** `--shape` is an unknown flag on every command: it exits `2`,
812
+ `UNKNOWN_FLAG`, before side effects.
813
+ - [ ] **[LOCAL]** `--detail agent` keeps action fields and strips non-action metadata.
816
814
  - [ ] **[LOCAL]** `show --format md --output <file>` writes file, empty stdout.
817
815
  - [ ] **[LOCAL]** Output replacement is atomic; unwritable/directory target does
818
816
  not truncate existing content.
@@ -837,7 +835,7 @@ expect_error 1 ASSET_NOT_FOUND show skills/does-not-exist
837
835
  expect_error 2 UNKNOWN_FLAG info --totally-bogus
838
836
  expect_error 2 INVALID_FORMAT_VALUE info --format xml
839
837
  expect_error 2 INVALID_DETAIL_VALUE info --detail maximum
840
- expect_error 2 INVALID_SHAPE_VALUE search docker --shape summary
838
+ expect_error 2 UNKNOWN_FLAG search docker --shape agent
841
839
  expect_error 2 MISSING_REQUIRED_ARGUMENT help migrate
842
840
  expect_error 2 INVALID_FLAG_VALUE completions --shell zsh
843
841
  ```
@@ -1618,7 +1616,7 @@ akm lint --type workflows --fail-on-flagged
1618
1616
 
1619
1617
  - [ ] **CORE** Create-print emits raw valid Markdown and writes nothing.
1620
1618
  - [ ] **CORE** Create-from writes/indexes valid workflow with preamble and exact sections.
1621
- - [ ] **LOCAL** Flat name plus safe path creates hierarchy; slash/traversal/duplicate step/missing section/unknown route/invalid params fail before write.
1619
+ - [ ] **LOCAL** Flat name plus safe path creates hierarchy; slash/traversal/duplicate step/missing section/invalid params fail before write.
1622
1620
  - [ ] **LOCAL** Existing workflow requires force plus from/reset; force alone fails.
1623
1621
  - [ ] **LOCAL** Lint catches structure without rewriting valid prose.
1624
1622
  - [ ] **CORE** A schema keyword outside the enforced subset (`format`, `pattern`,
@@ -1638,11 +1636,11 @@ akm lint --type workflows --fail-on-flagged
1638
1636
 
1639
1637
  ### 14.2 Typed params and partial run without an engine call
1640
1638
 
1641
- The `typed-route` fixture executes a route-only first step. `--max-steps 1`
1639
+ The `typed-params` fixture's first step is an exec unit (no engine). `--max-steps 1`
1642
1640
  tests parameter parsing and durable run creation without agent/LLM dispatch.
1643
1641
 
1644
1642
  ```sh
1645
- akm workflow run workflows/typed-route \
1643
+ akm workflow run workflows/typed-params \
1646
1644
  --include_processes=true --count 2 \
1647
1645
  --labels api --labels worker --max-steps 1 \
1648
1646
  > "$AKM_SANDBOX/typed-run.json"
@@ -1664,7 +1662,7 @@ jq -e '
1664
1662
  - [ ] **LOCAL** Params supplied to active run fail because creation-only.
1665
1663
  - [ ] **LOCAL** Invalid max-steps values fail; bounded partial exits `0`, remains active.
1666
1664
  - [ ] **LOCAL** `--max-steps` counts **finished spine steps**: a step's whole
1667
- bounded gate loop counts as one, and a route-skipped step consumes none.
1665
+ bounded gate loop counts as one.
1668
1666
  It does not bound the dispatches inside a single step's gate loop — pair
1669
1667
  it with `budget.max_units` when that is what you need capped.
1670
1668
 
@@ -1672,8 +1670,8 @@ jq -e '
1672
1670
 
1673
1671
  ```sh
1674
1672
  akm workflow status "$QA_RUN_ID"
1675
- akm workflow status workflows/typed-route
1676
- akm workflow list --active --ref workflows/typed-route
1673
+ akm workflow status workflows/typed-params
1674
+ akm workflow list --active --ref workflows/typed-params
1677
1675
  akm workflow status "$QA_RUN_ID" --units
1678
1676
  akm workflow abandon "$QA_RUN_ID"
1679
1677
  akm workflow resume "$QA_RUN_ID"
@@ -1826,7 +1824,7 @@ Environment scope and context:
1826
1824
  **before** the spawn, naming the variable, rather than surfacing a raw
1827
1825
  `E2BIG`.
1828
1826
 
1829
- `cwd` and isolation:
1827
+ `cwd`:
1830
1828
 
1831
1829
  - [ ] **LOCAL** `cwd` is relative and contained: absolute paths, drive letters,
1832
1830
  `~`, and `..` **segments** are rejected by the parser *and* the
@@ -1834,13 +1832,6 @@ Environment scope and context:
1834
1832
  base (symlinks included) immediately before the spawn.
1835
1833
  - [ ] **LOCAL** A directory whose *name* merely begins with dots (`..data`) is a
1836
1834
  legal contained `cwd` and must **not** fail `exec_cwd_escape`.
1837
- - [ ] **LOCAL** Under `isolation: worktree` each unit gets a fresh detached
1838
- worktree; a clean one is gone once the step resolves, a dirty one is
1839
- retained and its path logged.
1840
- - [ ] **LOCAL** When a dirty leftover is moved aside and the worktree then fails
1841
- to mint, the failure still reports **where the preserved work went**.
1842
- - [ ] **DESTRUCTIVE** The stale-worktree sweep removes abandoned trees after
1843
- seven days but leaves a tree that is still in use by a live run.
1844
1835
 
1845
1836
  Gates, retry, and reuse:
1846
1837
 
@@ -2196,7 +2187,7 @@ akm improve skills/k8s-deploy \
2196
2187
  - [ ] **AI** Lock contention without skip is a transient error, exit `75` (`IMPROVE_LOCK_HELD`); `--skip-if-locked` exits `0`, reports `skipped.reason:"lock-held"`, and emits no `improve_invoked`.
2197
2188
  - [ ] **AI** Stale lock is reclaimed with an observable recovery event; active lock is never stolen.
2198
2189
  - [ ] **AI** Timeout/SIGINT/SIGTERM/SIGHUP persist one terminated run with redacted reason and release locks/children.
2199
- - [ ] **AI** Default/quick/thorough/reflect-distill/consolidate/catchup/proactive-maintenance strategies enable exactly documented processes. Autonomy-gated mutations remain off without explicit experimental opt-in.
2190
+ - [ ] **AI** Default/consolidate/proactive-maintenance strategies enable exactly documented processes. Autonomy-gated mutations remain off without explicit experimental opt-in.
2200
2191
  - [ ] **AI** No sync/push occurs under explicit flags; git publication is tested separately only against disposable remote.
2201
2192
  - [ ] **AI** Credential/prompt/session values are absent from improve result, state DB, proposal provenance, health report, event stream, and logs.
2202
2193
 
@@ -3073,7 +3064,7 @@ and not a substitute for the full check.
3073
3064
  | Sources/registry/write-source | provider/write/publication suites, controlled service; LIVE git/npm/HTTPS when lifecycle changed |
3074
3065
  | Storage/migration/transactions | crash/concurrency/property/published-upgrade gates, section 19, destructive rehearsal for cutover changes |
3075
3066
  | Workflow | workflow unit/integration, gate fake agents, slow expansion, crash/contention when scheduler changed |
3076
- | Workflow exec units / subprocess capture | section 14.5 end to end on an engine-less install, plus the worktree isolation and stale-sweep gates; any capture, timeout, or process-group change also runs the agent spawn suites, which share that subprocess layer |
3067
+ | Workflow exec units / subprocess capture | section 14.5 end to end on an engine-less install; any capture, timeout, or process-group change also runs the agent spawn suites, which share that subprocess layer |
3077
3068
  | Workflow child environment / allowlist | section 14.5 environment gates plus section 11; a change to the shared floor is native Windows, not Linux emulation |
3078
3069
  | Task/scheduler | task suites, Linux standalone; native macOS/Windows for backend/quoting/binding; published upgrade for schema changes |
3079
3070
  | Env/secret/security path/archive/network | env/secret plus traversal/SSRF/archive/redaction/dangerous-key suites, section 20 |