akm-cli 0.9.14-beta.1 → 0.9.15-beta.1

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 (110) hide show
  1. package/CHANGELOG.md +398 -1
  2. package/STABILITY.md +6 -3
  3. package/dist/assets/prompts/reflect-feedback-framing.md +1 -0
  4. package/dist/assets/prompts/reflect-llm-framed-contract.md +2 -0
  5. package/dist/assets/prompts/reflect-llm-schema-contract.md +2 -0
  6. package/dist/assets/tasks/core/improve.yml +1 -1
  7. package/dist/assets/tasks/core/index-refresh.yml +1 -1
  8. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +1 -1
  9. package/dist/assets/tasks/improve/akm-improve-catchup.yml +1 -1
  10. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +1 -1
  11. package/dist/assets/tasks/improve/akm-improve-frequent.yml +1 -1
  12. package/dist/assets/tasks/improve/akm-improve-nightly.yml +1 -1
  13. package/dist/cli/retired-commands.js +0 -1
  14. package/dist/cli/shared.js +9 -0
  15. package/dist/cli/unknown-flags.js +1 -0
  16. package/dist/cli.js +3 -2
  17. package/dist/commands/config-cli.js +85 -3
  18. package/dist/commands/env/env-cli.js +1 -42
  19. package/dist/commands/env/env.js +1 -1
  20. package/dist/commands/env/secret-cli.js +1 -2
  21. package/dist/commands/health/checks.js +357 -63
  22. package/dist/commands/health/engine-usage.js +45 -0
  23. package/dist/commands/health/improve-metrics.js +18 -0
  24. package/dist/commands/health/llm-usage.js +41 -1
  25. package/dist/commands/health/plugin-staleness.js +7 -3
  26. package/dist/commands/health/version-drift.js +93 -0
  27. package/dist/commands/health/windows.js +3 -1
  28. package/dist/commands/health.js +44 -9
  29. package/dist/commands/improve/consolidate/chunking.js +4 -2
  30. package/dist/commands/improve/improve-cli.js +99 -5
  31. package/dist/commands/improve/improve-report.js +154 -0
  32. package/dist/commands/improve/improve-result-file.js +45 -33
  33. package/dist/commands/improve/improve-strategies.js +133 -3
  34. package/dist/commands/improve/improve-usage-report.js +182 -0
  35. package/dist/commands/improve/improve.js +40 -3
  36. package/dist/commands/improve/locks.js +27 -78
  37. package/dist/commands/improve/planner.js +1 -0
  38. package/dist/commands/improve/preparation.js +9 -1
  39. package/dist/commands/improve/reflect.js +44 -4
  40. package/dist/commands/models-cli.js +50 -1
  41. package/dist/commands/proposal/repository.js +8 -3
  42. package/dist/commands/proposal/validators/proposal-quality-validators.js +41 -6
  43. package/dist/commands/proposal/validators/proposal-validators.js +24 -0
  44. package/dist/commands/read/search-cli.js +38 -2
  45. package/dist/commands/read/show.js +103 -4
  46. package/dist/commands/sources/info.js +5 -1
  47. package/dist/commands/sources/self-update.js +2 -2
  48. package/dist/commands/sources/stash-cli.js +31 -0
  49. package/dist/commands/tasks/tasks-cli.js +49 -2
  50. package/dist/commands/workflow-cli.js +86 -12
  51. package/dist/core/asset/markdown-fragments.js +35 -0
  52. package/dist/core/config/config-schema.js +14 -0
  53. package/dist/core/config/config.js +302 -24
  54. package/dist/core/env-secret-ref.js +58 -5
  55. package/dist/core/errors.js +30 -0
  56. package/dist/core/improve-result.js +51 -0
  57. package/dist/core/loopback.js +17 -0
  58. package/dist/core/paths.js +11 -0
  59. package/dist/core/run-lock.js +96 -0
  60. package/dist/core/sensitive-marker-path.js +19 -0
  61. package/dist/core/state-db.js +74 -14
  62. package/dist/indexer/index-rebuild-lock.js +73 -0
  63. package/dist/indexer/index-writer-lock.js +40 -1
  64. package/dist/indexer/index-written-assets.js +21 -1
  65. package/dist/indexer/indexer.js +18 -17
  66. package/dist/indexer/materialize-embeddings.js +282 -32
  67. package/dist/indexer/search/db-search.js +49 -2
  68. package/dist/integrations/agent/engine-resolution.js +96 -6
  69. package/dist/integrations/agent/execution-definitions.js +6 -15
  70. package/dist/integrations/agent/execution-lowering.js +6 -1
  71. package/dist/integrations/agent/execution-preparation.js +1 -1
  72. package/dist/integrations/agent/model-map.js +123 -20
  73. package/dist/integrations/agent/prompts.js +40 -8
  74. package/dist/integrations/agent/runner-dispatch.js +9 -3
  75. package/dist/integrations/agent/runner.js +2 -0
  76. package/dist/llm/client.js +8 -3
  77. package/dist/llm/embedder.js +20 -8
  78. package/dist/llm/embedders/local.js +10 -2
  79. package/dist/llm/embedders/remote.js +188 -21
  80. package/dist/output/shapes/helpers.js +38 -2
  81. package/dist/output/shapes/models-list.js +16 -0
  82. package/dist/output/shapes/passthrough.js +2 -0
  83. package/dist/output/shapes.js +4 -0
  84. package/dist/output/text/command-format.js +29 -0
  85. package/dist/output/text/helpers.js +1 -1
  86. package/dist/output/text/improve-report.js +27 -0
  87. package/dist/{commands/env/marker-path.js → output/text/models.js} +4 -3
  88. package/dist/output/text/show-format.js +4 -0
  89. package/dist/output/text.js +4 -0
  90. package/dist/scripts/akm-migrate-node.js +24798 -21732
  91. package/dist/scripts/akm-migrate.js +23408 -20343
  92. package/dist/storage/repositories/improve-runs-repository.js +34 -0
  93. package/dist/storage/repositories/index-fts-repository.js +49 -6
  94. package/dist/storage/repositories/index-vec-repository.js +30 -0
  95. package/dist/storage/repositories/workflow-runs-repository.js +55 -18
  96. package/dist/tasks/backends/cron.js +14 -7
  97. package/dist/tasks/run/run-workflow-task.js +16 -0
  98. package/dist/workflows/exec/child-workflow.js +2 -2
  99. package/dist/workflows/exec/dispatch-redaction.js +21 -9
  100. package/dist/workflows/exec/run-workflow.js +6 -5
  101. package/dist/workflows/runtime/runs.js +33 -5
  102. package/docs/migration/release-notes/0.9.15.md +52 -0
  103. package/docs/migration/release-notes/README.md +4 -0
  104. package/docs/reference/cli.md +245 -29
  105. package/docs/reference/configuration.md +180 -19
  106. package/docs/reference/data-and-telemetry.md +8 -0
  107. package/docs/reference/tasks.md +16 -1
  108. package/docs/reference/workflow-schema.md +5 -1
  109. package/package.json +1 -1
  110. package/schemas/akm-config.json +8 -0
package/CHANGELOG.md CHANGED
@@ -4,7 +4,404 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
- ## [0.9.14-beta.1] - 2026-09-04
7
+ ## [0.9.15-beta.1] - 2026-09-09
8
+
9
+ ### Added
10
+
11
+ - **`akm workflow list`/`status --all-scopes` (#942).** Workflow runs are
12
+ partitioned by `scope_key`, a hash of the working directory a run was started
13
+ from, so a scheduled task and a human shell working in different directories
14
+ can each believe a ref has no active run and start one, while `akm workflow
15
+ list` from either scope shows only its own. `--all-scopes` searches every scope
16
+ instead of only the current one, and both commands' JSON envelope now carries a
17
+ top-level `scopeKey` naming the scope that was searched (`null` under
18
+ `--all-scopes`), so an empty result is no longer indistinguishable from
19
+ "nothing anywhere." Default output is unchanged.
20
+ - **`akm health`'s `task-fail-rate` advisory names the dominant command-task
21
+ failure reason (#943).** Evidence now always includes
22
+ `agentFailureReasonCounts`, a breakdown of `detail.reason` values (`timeout`,
23
+ `non_zero_exit`, `spawn_failed`, …) across command-task failures in the window,
24
+ and when the check is already `warn` and one reason covers at least half of
25
+ those failures the message names it, e.g. `(timeout-dominant: 9/12 command-task
26
+ failures)`. Investigating the reported issue found the underlying propagation
27
+ was already correct on this branch — a command dispatch that times out, is
28
+ killed, or returns a non-zero/`ok:false` result was already recorded `failed`
29
+ with its reason, and `akm task run` already exited non-zero for it — so that
30
+ contract is now pinned by a regression test instead of left implicit.
31
+ - **Every real `improve` run persists a `usageReport` field summarizing LLM usage
32
+ by process, engine, and model (#944).** `usageReport: {byProcessEngineModel,
33
+ noCalls}` folds a new process x engine x model cross-tab of the run's
34
+ `llm_usage` events together with the resolved process routing table (#947); it
35
+ is omitted from the result when both halves would be empty, matching the
36
+ envelope's existing convention. `noCalls` lists every LLM-backed process the
37
+ active strategy enabled but that made zero calls, each with a `reason` drawn
38
+ from the existing skip-reason vocabulary (`"engine_unavailable"`,
39
+ `"autonomy_gated"`, `"strategy_filtered_all_passes"`, a reflect/distill
40
+ dominant skip reason, or `"no_signal"`) — never a fabricated category. The same
41
+ table is printed to stderr after every real run.
42
+ - **`akm improve report [--run <id>] [--since <window>]` reads a run's LLM usage
43
+ back without hand-written SQLite against `state.db` (#944).** With no flags it
44
+ shows the most recent real run; `--run <id>` targets one specific
45
+ `improve_runs` row; `--since <window>` sums `usageReport` across every real run
46
+ started in the window. `"report"` is a reserved `improve` scope value matching
47
+ zero real assets, so it dispatches to the report before any lock, log, or index
48
+ side effect. A run recorded before 0.9.15 (or one whose result cannot be
49
+ decoded) has no persisted `usageReport`: the command recomputes the cross-tab
50
+ from that run's own `llm_usage` events and adds a `notes` entry explaining the
51
+ degradation instead of erroring or fabricating eligibility reasons.
52
+ `--run`/`--since` only mean anything with this scope, so passing either with
53
+ any other scope (or none) is now rejected instead of being silently ignored.
54
+ - **The health `llm_usage` aggregate (`akm health --since <window>`'s `llmUsage`
55
+ field) now tracks a `failures` count alongside calls and token totals (#944).**
56
+ Every existing breakdown (`byStage`, `byProcess`, `byEngine`, and the window
57
+ total) gains this count for free from the same aggregator; a call whose
58
+ `llm_usage` event records an error outcome increments it.
59
+ - **A config file can inherit a shared base via `extends` (#945).** Setting
60
+ `extends: <path|bundle//path>` deep-merges a base config underneath the local
61
+ file — local keys always win. The base resolves without the search index: a
62
+ filesystem path relative to the declaring file (`~` expanding), or an
63
+ already-synced bundle-relative file (`bundle//<path>`, resolved against that
64
+ bundle's content root, needing no asset type). It runs through its own version
65
+ and legacy-shape shims independently and may itself set `extends` (chained); a
66
+ cycle is a `ConfigError` naming the ref. A fleet of instances can now keep one
67
+ shared `engines`/`improve.strategies` block instead of hand-syncing it across
68
+ hosts.
69
+ - **`akm config get --show-source` and `akm config diff` inspect an effective,
70
+ `extends`-merged config (#945).** `config get <key> --show-source` wraps the
71
+ value as `{ value, source }`, reporting whether it came from `local`,
72
+ `extends:<ref>`, or `default`. `config diff <path|bundle//path>` prints sorted
73
+ `{ path, local, other }` rows for every leaf that differs between this
74
+ instance's effective config and another config file or bundle-relative file
75
+ (loaded through the same loader, so its own `extends` is honoured too),
76
+ redacting secrets on both sides first. Both are additive: `config get`'s
77
+ default bare-value shape is unchanged, and `config set`/`unset` still edit only
78
+ the local file.
79
+ - **A model-map column can borrow its model from a configured engine (#946).** A
80
+ `models.json` profile may now set `"engine": "<name>"` instead of a literal
81
+ `model` string, e.g. `"fast": { "opencode": { "engine": "local-fast" } }`, to
82
+ reuse `engines.local-fast`'s own `model` (and, for an `llm`-kind engine, its
83
+ `temperature`/`maxTokens`/`enableThinking`/etc. as `inference` defaults) rather
84
+ than hand-typing the value a second time. `model` and `engine` are mutually
85
+ exclusive on one profile; a reference to a missing engine, or one with no
86
+ usable model, fails with the same "a usable model is required after overlay"
87
+ error, now checked at `akm health` as well as at dispatch. Engine selection
88
+ itself is unchanged: `engine` is only an indirection for the model/inference
89
+ *value* of one column, never an override of which engine
90
+ `--engine`/`defaults.engine` actually dispatches to.
91
+ - **`akm models list` shows the effective model-alias table (#946).** It prints
92
+ every resolved (alias, column) pair with its `model`, `source` (`default` vs
93
+ `user`), and `via` (`literal` vs `engine`, naming the engine when applicable),
94
+ so an operator can see at a glance which mappings a local `models.json`
95
+ overrides and which fall through to AKM's installed defaults.
96
+ - **`akm improve --dry-run` reports the resolved process -> engine -> model
97
+ routing before anything runs, in a new `plan.processes` field (#947).** One row
98
+ per improve process (`reflect`, `distill`, `consolidate`, `memoryInference`,
99
+ `graphExtraction`, `extract`, `validation`, `triage`, `proactiveMaintenance`),
100
+ plus a `triage.judgment` row when configured: `enabled`, the resolved
101
+ `engine`/`model` for llm-backed processes, this process's own lowering
102
+ `notices`, an `unavailable: {configKey, reason}` when the engine or its
103
+ credential could not be resolved, and — for reflect/distill/consolidate —
104
+ `eligibleRefs`, the count of this run's effective refs the process would act
105
+ on. None of this is new resolution: the plan already computes it,
106
+ dispatch-free, before every invocation (dry or live); this only projects it
107
+ into the result. `--strategy` overrides are honored for free, unlike `akm
108
+ health`'s `active-improve-strategy` check, which still reads the configured
109
+ default strategy (`defaults.improveStrategy`) and reports no model.
110
+ - **`--plan` is a new alias for `--dry-run`, for previewing `plan.processes`
111
+ (#947).** It sets the exact same internal flag — no separate code path, no
112
+ additional network reachability probe; pair with `akm health --probe` to check
113
+ whether a named engine actually answers.
114
+ - **`akm workflow run --skip-if-locked` (#948).** Extends `improve
115
+ --skip-if-locked`'s skip-gracefully-instead-of-failing semantics to `workflow
116
+ run`: when another engine invocation already holds the run's lease
117
+ (`RUN_LEASE_HELD`) or `state.db` is contended (`STATE_DB_CONTENDED`), the flag
118
+ turns the failure into one warn line plus `{ ok: true, skipped: { reason:
119
+ "lock-held" | "state-db-contended" } }` at exit 0 instead of exit 75. Every
120
+ other failure — a bad flag, an unresolvable target — still fails loudly
121
+ regardless of the flag. Use it on high-frequency scheduled workflow runs so
122
+ they don't pile up failures while a longer-running invocation is in progress.
123
+ Not extended to `task run`.
124
+ - **`akm health` reports engines whose thinking-off control was ignored (#949).**
125
+ A new advisory, `thinking-control`, warns per configured LLM engine with
126
+ `enableThinking: false` when the report window's recorded usage shows reasoning
127
+ tokens anyway. It is passive — it re-reads the existing `llm_usage` window
128
+ aggregate rather than issuing a completion of its own, so a cold local model is
129
+ never woken just to run `akm health`. `unknown` when no engine sets
130
+ `enableThinking: false`, or when a configured one made no calls in the window.
131
+ - **`akm health` reports version drift, idle-but-bound engines, and which env
132
+ asset supplies a missing credential (#950).** Three new checks close a
133
+ fleet-awareness gap where `akm health` could pass on a stale host running
134
+ behind its peers, or on an engine that is configured and reachable but has not
135
+ actually been invoked in weeks. The new `cli-version` advisory compares the
136
+ installed akm-cli version against the latest GitHub release (the same source
137
+ `akm upgrade` already trusts) and warns when a newer release exists; it is
138
+ gated behind `--probe`/`--no-probe` like the engine-reachability checks, and
139
+ degrades to `unknown` (never a false warn) when offline or rate-limited. The
140
+ new `engine-last-used` advisory folds `llm_usage` events over a fixed 30-day
141
+ lookback — independent of `--since` — against the active improve strategy's
142
+ process-to-engine bindings, and warns when a bound engine has no recorded use
143
+ in that window; it stays `unknown` rather than warning until at least one
144
+ improve run has been recorded (started) in the same window, so a fresh install
145
+ is not noisy. Finally, when `default-llm-engine` or `configured-engines`
146
+ reports a required credential missing from the shell, the warn now names the
147
+ env asset (never the variable name) that supplies the same key when one exists
148
+ — for example `env asset env/lab supplies it — run under it (akm env run
149
+ env/lab -- ...)` — instead of a bare "unavailable" that hides the real remedy
150
+ for an operator whose normal workflow is `akm env run env/lab -- akm improve`.
151
+ - **`akm task list` is an alias for `akm search --type task` (#951).** 0.9.0
152
+ removed the `task list` command as a redundant second implementation of task
153
+ listing; this reintroduces only the spelling, not the logic. It delegates to
154
+ the same `akmSearch`/`parseSearchSource` path `akm search --type task` uses,
155
+ passing the query, `--limit`, and `--from` flags through unchanged and
156
+ returning the exact same envelope, including the `results` alias. `task show`
157
+ and `task remove` stay retired.
158
+ - **`akm info` exposes `dataDir`, `configDir`, `cacheDir`, and `stateDir`
159
+ (#951).** These are akm's resolved data/config/cache/state directories, so a
160
+ script can read `akm info --format json | jq -r .dataDir` instead of hardcoding
161
+ a path that differs between a host install and a container.
162
+ - **`akm index --reembed` forces a full re-embed (#955).** Bypasses the
163
+ compatibility check above entirely and purges + regenerates every stored
164
+ embedding, for the rare case where the check's verdict should not be trusted. A
165
+ targeted post-write embedding pass (after `akm remember`, `akm improve`, etc.)
166
+ never forces a rebuild on its own.
167
+ - **`akm index --skip-if-locked` lets a scheduled or opportunistic run step aside
168
+ instead of contending with one already in progress (#956).** Every explicit
169
+ `akm index` run now acquires an opt-in, PID-liveness-only rebuild lock (no
170
+ age-based stale reclaim — the same #872 lesson `akm improve`'s lock already
171
+ applies) and releases it on exit. This is **not** the blocking index-rebuild
172
+ lock #872 removed: a plain `akm index` with no flag is never gated by it — a
173
+ held lock only warns and the run proceeds unlocked, exactly as before.
174
+ `--skip-if-locked` mirrors `akm improve --skip-if-locked`: when the lock is
175
+ already held by a live process it skips gracefully (exit 0, `{ ok: true,
176
+ skipped: { reason: "lock-held", pid, startedAt } }`) instead of piling up
177
+ behind the other run. The shipped `index-refresh` scheduled task now passes it.
178
+ - **`akm improve` reports which processes it skipped for an unavailable engine,
179
+ instead of dispatching with a doomed credential (#957).** A process whose
180
+ engine was configured but whose credential could not be resolved in this
181
+ process's own environment (a scheduler that strips `env/user.env` from the task
182
+ environment, for example) used to keep its runner and proceed to an
183
+ unauthenticated dispatch, with no run-level signal beyond a stderr warning. The
184
+ plan builder now folds a resolved-but-uncredentialed engine (including the
185
+ triage judgment engine) into the same `engineUnavailable` handling as "no
186
+ engine selected," and the result carries a new `skippedProcesses: [{process,
187
+ configKey, reason}]` field (omitted when nothing was skipped) naming which
188
+ engine and which credential reference — never its value — is missing. `ok` and
189
+ the exit code are unchanged, matching the `extract`/`skipReasons` contract
190
+ (#912): a scheduler must not start failing because one LLM process's credential
191
+ is temporarily missing while others still run. `--dry-run`/`--plan` never
192
+ dispatches, so a preview no longer aborts when every enabled process is
193
+ credential-unavailable either: it reports the affected processes in
194
+ `skippedProcesses` and shows them as unavailable (with their structurally
195
+ resolved engine and model) in `plan.processes`, the same as a run that does
196
+ dispatch.
197
+ - **`--require-engines` opts a run out of that degrade-and-continue behavior, and
198
+ is now set on all six shipped scheduled improve task templates (#957).** It
199
+ aborts (exit 78) right after the plan resolves, before any lock, log, or index
200
+ side effect, listing every unavailable process and its unresolved credential
201
+ reference — recommended alongside `--skip-if-locked` for scheduled runs, since
202
+ the operator's own shell can pass config validation while a scheduler's
203
+ stripped-down environment cannot.
204
+
205
+ ### Changed
206
+
207
+ - **BREAKING: `RUN_LEASE_HELD` now exits 75, not 2 (#948).** A held workflow
208
+ run-lease refusal — `akm workflow run` or `akm workflow complete` racing
209
+ another engine invocation on the same run — was a `UsageError` (exit 2), which
210
+ schedulers and cron wrappers read as "fix the command line" rather than "try
211
+ again shortly." It is now a `TransientError` (exit 75, sysexits `EX_TEMPFAIL`);
212
+ the message, hint, and `RUN_LEASE_HELD` code are unchanged. A script or
213
+ scheduler that special-cased exit 2 to detect a held lease must switch to exit
214
+ 75, or check the JSON envelope's `code` field instead.
215
+ - **Thinking-control wire forms no longer depend on `provider` (#949).**
216
+ `chat_template_kwargs.enable_thinking` was previously sent only when `provider:
217
+ "vllm"` was set; every other provider (including none) got a bare top-level
218
+ `enable_thinking` that nothing was observed to honor. AKM now sends both wire
219
+ forms whenever `enableThinking` resolves to a value, regardless of `provider`.
220
+ An engine relying on `provider: "vllm"` purely for this side effect keeps
221
+ working identically. An engine configured with `provider: "openai"`, another
222
+ provider name, or no `provider` at all now also receives
223
+ `chat_template_kwargs`, which it did not before — this is what lets the same
224
+ engine block turn thinking off consistently behind a direct vhost, freellmapi,
225
+ or Bifrost, without a gateway silently dropping the one wire form it happened
226
+ to send.
227
+ - **`akm health --no-probe` now also skips the version-drift check (#950).**
228
+ `--probe`/`--no-probe` previously gated only LLM engine reachability; it now
229
+ also gates the `cli-version` advisory's GitHub release lookup, so an offline or
230
+ air-gapped host's existing `--no-probe` habit suppresses both network calls.
231
+ This is the second deliberate network exception in `akm health`, alongside the
232
+ pre-existing `plugin-version` advisory's `git ls-remote`.
233
+ - **The direct-LLM reflect path sizes its asset-content budget from the target
234
+ engine's `contextLength` instead of a flat 12,000-character cap (#952).** The
235
+ flat cap only exists to keep the prompt under OS `ARG_MAX` when passed as CLI
236
+ argv to an agent or SDK runner; the direct-LLM HTTP path never touches argv, so
237
+ agent and SDK runners are unaffected. The budget reserves half of the usable
238
+ window for the model's response, since a reflect rewrite returns a body roughly
239
+ the size of the input. Configure `engines.<name>.contextLength` to raise the
240
+ budget for a given engine; unconfigured engines keep today's effective
241
+ ~12,000-character behavior.
242
+ - **`akm index` commits embeddings per provider batch instead of buffering the
243
+ whole run for one final write (#954).** Earlier releases wrote every generated
244
+ vector in a single `db.transaction()` after the entire embedding pass finished,
245
+ so an interruption partway through (a competing indexer collision, a killed
246
+ process, any thrown provider error) discarded every embedding already computed,
247
+ not just the ones still in flight. Each request batch now commits inside its
248
+ own short transaction as it lands.
249
+ - **A batch rejected for exceeding the endpoint's context window is split and
250
+ retried instead of skipped outright (#954).** `akm index`'s embedding pass now
251
+ recognizes HTTP 413 and known context-size error bodies and halves the failing
252
+ batch, retrying each half recursively down to individual documents. Only a
253
+ single document that still fails this way is skipped, as
254
+ `context-window-exceeded`; every other failure (network error, 5xx, malformed
255
+ response) keeps the prior skip-the-whole-batch behavior.
256
+ - **Embedding requests are now dispatched through a small, fixed in-flight window
257
+ instead of strictly sequentially (#954).** The window is 1 request at a time
258
+ for a loopback endpoint and 2 for a remote one, and is not configurable — the
259
+ actual throughput knob is request size, via the existing `embedding.batchSize`
260
+ (document cap) and `embedding.maxTokens`/`contextLength` (token budget), since
261
+ a larger batch takes about the same wall time as a single one.
262
+ - **`akm index` reports embedding progress and throughput as it runs (#954).** A
263
+ progress line is printed every 500 stored entries, and a final line reports
264
+ throughput (`entries/s`, `tokens/s`) once the embedding pass completes.
265
+ - **A rename of `embedding.model` no longer forces a full re-embed by itself
266
+ (#955).** `akm index` used to purge and rebuild the entire vector index on any
267
+ change to the fingerprint it derives from `embedding.model`, including a pure
268
+ config rename that still resolves to the same underlying model (for example a
269
+ gateway prefixing `provider/model` onto an unchanged server). On a mismatch,
270
+ `akm index` now re-embeds a small sample of already-stored entries and keeps
271
+ the index when either the endpoint's reported model identity matches what it
272
+ reported last time, or the median cosine similarity between stored and freshly
273
+ re-embedded vectors is at least 0.999; otherwise it purges and rebuilds as
274
+ before, logging why. A genuine dimension change bypasses this check entirely
275
+ and still rebuilds unconditionally.
276
+ - **A canary that cannot reach the embedding endpoint leaves the index untouched
277
+ instead of purging it (#955).** When the fingerprint-rename canary's re-embed
278
+ attempt fails outright, `akm index` keeps the existing vectors and the old
279
+ fingerprint and reports the run as `unverifiable`, rather than destroying a
280
+ working index because the server happened to be down. The next `akm index` run
281
+ retries the canary once the endpoint is reachable again.
282
+ - **A purge now writes the new fingerprint before any embedding request, so an
283
+ interrupted rebuild resumes instead of restarting from zero (#955).**
284
+ Previously the fingerprint was only written at the very end of a successful
285
+ embedding pass, so an interruption partway through a fingerprint-triggered
286
+ rebuild left the old fingerprint in place — the next `akm index` saw the same
287
+ mismatch and purged again, discarding whatever had already been re-embedded.
288
+ The new fingerprint (and the observed identity) are now written in the same
289
+ transaction as the purge, before any vectors are requested; a restart then sees
290
+ a matching fingerprint and only re-embeds the entries still missing a vector.
291
+ - **A write-path index update (`akm remember`, `akm import`, `akm proposal
292
+ accept`, `akm source clone`, extract session assets) never contends with a full
293
+ rebuild in progress; it skips and lets the rebuild heal the entry instead
294
+ (#956).** These commands make a just-written asset searchable immediately via a
295
+ targeted index upsert that opens `index.db` under a 5-second busy timeout — far
296
+ shorter than a full rebuild's single transaction. It now checks the new rebuild
297
+ lock first: a live holder means the upsert and embedding are skipped outright
298
+ (one log line naming the pid and the file that will be indexed by the next
299
+ pass), and every caller — including `akm proposal accept`, which used to report
300
+ a spurious "index finalization failed" — treats the skip as success, since the
301
+ file write itself already completed. A rebuild lock left by a dead process is
302
+ not treated as held here — reclaiming it stays `akm index`'s job.
303
+ - **A blocked `akm remember` (and other synchronous asset-mutation-lease writers)
304
+ now prints a wait notice instead of hanging silently for up to ten minutes
305
+ (#956).** The sync lease boundary (`withAssetMutationLeaseSync`) had no
306
+ progress feedback at all; a contended lease looked identical to a hang until it
307
+ either acquired or timed out. It now logs `waiting for <holder purpose> (pid N,
308
+ started T) — Ns` every 15 seconds, naming who actually holds the lease — the
309
+ same cadence the async path's `onWait` hook already had, but wired to an actual
310
+ warning since nothing called that hook. The 10-minute wait bound is unchanged;
311
+ this only makes an existing wait visible.
312
+ - **`akm health`'s `active-improve-strategy` check fails, not warns, when the
313
+ active strategy's LLM-backed work would be a total no-op (#957).** Previously
314
+ this check stayed `warn` regardless of how many of the strategy's enabled
315
+ processes were unavailable, so a nightly `improve` job that could not run a
316
+ single LLM-backed process still reported the same severity as one with a single
317
+ missing credential and several working ones. It now escalates to `fail`
318
+ specifically when every enabled `capability: "llm"` process in the strategy is
319
+ unavailable; a partial failure with at least one working process stays `warn`.
320
+ The check is also a direct projection of the same credential-aware plan
321
+ `improve` itself now builds (see Added, above), rather than a separate
322
+ re-derivation that could disagree with what a real run in the same environment
323
+ would do.
324
+
325
+ ### Fixed
326
+
327
+ - **Starting a workflow ref that already has an active run in a different scope
328
+ now warns instead of silently duplicating it (#942).** `akm workflow run
329
+ <ref>`'s per-scope concurrency guard is unchanged by design — two unrelated
330
+ projects sharing one `state.db` can still run the same-named workflow
331
+ independently — but before starting a new run it now also checks for an active
332
+ run of the same ref in any OTHER scope and, if one exists, warns with that
333
+ run's id, scope, and start time, plus the same `akm workflow run <id>` / `akm
334
+ workflow abandon <id>` remedy. The existing "already active" guard errors now
335
+ also name the blocking run's scope, not just its id. `akm workflow
336
+ status`/`resume`/`abandon <run-id>` already worked from any scope (#919) and
337
+ are unchanged.
338
+ - **`akm workflow status <ref>` names the scope it searched when nothing is found
339
+ there (#942).** When the ref lookup finds no runs in the current scope and
340
+ `--all-scopes` was not passed, the not-found error now names the scope that was
341
+ searched and suggests retrying with `--all-scopes`, instead of a bare "no runs
342
+ found."
343
+ - **Concurrent akm commands writing `state.db` no longer crash with a bare
344
+ "database is locked" (#948).** An unrelated `akm improve` run, `akm workflow
345
+ run`, or scheduled task writing `state.db` at the same time used to exhaust the
346
+ write retry window and throw the raw SQLite driver error, surfacing as
347
+ `{"ok":false,"error":"database is locked"}` at exit 70 (internal/unclassified)
348
+ instead of a retryable failure. On exhaustion the error is now reclassified
349
+ into a `TransientError` with a dedicated `STATE_DB_CONTENDED` code (exit 75),
350
+ modelled on `RUN_LEASE_HELD`'s precedent (#924), with the original driver text
351
+ preserved as `cause`; a genuinely unrelated error (real corruption, a
352
+ body-thrown failure) is never reclassified and still surfaces as itself. The
353
+ improve run's own `improve_runs` ledger writes, previously bare
354
+ single-statement writes with no retry, now go through the same retry path.
355
+ - **`akm show env/<name>` lists key names in plain-text output (#951).** The
356
+ plain-text `show` renderer never read the response's `keys` field, so the
357
+ default (non-`--format json`) output for an env asset gave no way to audit
358
+ which keys a script depends on; `--format json` already carried them correctly.
359
+ - **The cron scheduler backend truncates its raw per-task log instead of
360
+ appending forever (#951).** The crontab entry installed for each task
361
+ redirected stdout/stderr with `>>` (append), so the log file grew without
362
+ bound; it is only a bootstrap safety net, since akm's own per-run log already
363
+ separates runs and keeps history in `logs.db`, so the redirect now truncates
364
+ (`>`) and the file holds exactly the latest run's raw output. An
365
+ already-installed `>>` row still parses correctly. `launchd`'s equivalent log
366
+ path is OS-managed append-only with no truncate mode, so it is left unchanged —
367
+ a wrapper-script rewrite is not justified by evidence that was Linux-only.
368
+ - **Removed the false `akm curate --rerank` / "curate reranks by intent" claims
369
+ from the docs and the `curate` command's own description (#951).** `akm curate`
370
+ never implemented reranking; a rerank engine kind is deferred to its own issue,
371
+ and the referenced Discord health-report embed script is outside this
372
+ repository.
373
+ - **Reflect no longer treats feedback lines as verified facts to insert into the
374
+ rewrite (#952).** A harness run against the reflect prompt on two model quants
375
+ showed the model inventing whole new sections — fabricated incident dates,
376
+ ports, disk layouts — whenever a feedback line asserted a claim the source
377
+ asset never made. Feedback is now framed as a signal to investigate, not a fact
378
+ to insert, and missing information gets a `TODO: verify …` placeholder instead
379
+ of an invented answer.
380
+ - **A leaked content-truncation marker can no longer end up in a proposal body
381
+ (#952).** Asset content over the active budget is capped with a marker the
382
+ model is told never to echo back; when a model echoes it anyway, the proposal
383
+ is now deferred for human review (`reflect-truncation-leak`) instead of
384
+ shipping silently. As a second layer, `proposal accept` — including drain
385
+ promotion — now rejects any reflect-sourced proposal whose body still contains
386
+ the marker, since a truncated body silently replacing a full asset is data
387
+ loss; re-run reflect on the ref to clear it.
388
+ - **`secret://<name>` engine credentials now resolve on the `akm improve` /
389
+ agent-dispatch and health-probe paths (#953).** 0.9.13's #917 CHANGELOG entry
390
+ claimed engine credentials could resolve from the secret store, but that was
391
+ only ever true for direct LLM calls (`llm/client.ts`) and, after a same-week
392
+ follow-up fix, embedding calls — `engine-resolution.ts`, the sole path `akm
393
+ improve`, workflow LLM steps, and `akm health`'s engine probes use, still threw
394
+ `Engine "<name>" has an invalid symbolic apiKey reference.` for any `secret://`
395
+ value and aborted the run. `secret://<name>` now resolves through the same
396
+ store lookup as those other call sites, deferred to actual dispatch so frozen
397
+ plans stay secret-free, and `akm health`'s credential check now probes the
398
+ store instead of reporting a `secret://`-backed engine as available
399
+ unconditionally.
400
+ - **An unset or empty `$VAR` referenced by an engine's `apiKey` now warns once,
401
+ naming the variable (#953).** Previously it silently sent an empty
402
+ `Authorization` header instead of surfacing the misconfiguration.
403
+
404
+ ## [0.9.14] - 2026-09-04
8
405
 
9
406
  ### Added
10
407
 
package/STABILITY.md CHANGED
@@ -57,9 +57,9 @@ enumeration of the whole `proposal` noun group.
57
57
  | `akm search` | Stable | |
58
58
  | `akm curate` | Stable | |
59
59
  | `akm show` | Stable | |
60
- | `akm workflow status` | Stable | |
60
+ | `akm workflow status` | Stable | `--all-scopes` added in 0.9.15 (#942), additive. |
61
61
  | `akm workflow plan` | Evolving | New in 0.9.2; secret-free provenance output; envelope shape may change. |
62
- | `akm workflow list` | Stable | |
62
+ | `akm workflow list` | Stable | `--all-scopes` and a top-level `scopeKey` envelope field added in 0.9.15 (#942), both additive. |
63
63
  | `akm workflow create` | Stable | |
64
64
  | `akm workflow resume` | Stable | |
65
65
  | `akm workflow abandon` | Stable | |
@@ -75,9 +75,10 @@ enumeration of the whole `proposal` noun group.
75
75
  | `akm migrate apply` | Internal | Forwards to the standalone `akm-migrate` tool; renders its result through the normal `--format` pipeline (not exempt — see below). Listed (not hidden) in `--help`/completions. |
76
76
  | `akm config path` | Stable | |
77
77
  | `akm config list` | Stable | |
78
- | `akm config get` | Stable | |
78
+ | `akm config get` | Stable | Default bare-value shape unchanged; `--show-source` (0.9.15, opt-in) wraps it as `{ value, source }`. |
79
79
  | `akm config set` | Stable | |
80
80
  | `akm config unset` | Stable | |
81
+ | `akm config diff` | Evolving | New in 0.9.15; compares effective (post-`extends`) configs, secrets redacted. |
81
82
  | `akm feedback` | Stable | |
82
83
  | `akm log` | Evolving | |
83
84
  | `akm agent` | Evolving | |
@@ -113,6 +114,7 @@ enumeration of the whole `proposal` noun group.
113
114
  | `akm task doctor` | Evolving | |
114
115
  | `akm task explain` | Evolving | New in 0.9.2; secret-shaped values in provenance output are redacted on a best-effort heuristic basis (not a guarantee). |
115
116
  | `akm task validate` | Evolving | New in 0.9.11; read-only, and the only `task` subcommand that takes a bare filesystem path instead of a ref — the file need not belong to any configured bundle. |
117
+ | `akm task list` | Evolving | New in 0.9.15 (#951); a pure delegating alias for `akm search --type task` — same envelope, no separate implementation. |
116
118
 
117
119
  ## Stable
118
120
 
@@ -218,6 +220,7 @@ enumeration of the whole `proposal` noun group.
218
220
  | `2` | Usage / bad input |
219
221
  | `4` | Health warning (`akm health` only) |
220
222
  | `70` | Internal / unclassified |
223
+ | `75` | Transient — retry shortly (`TransientError`, sysexits `EX_TEMPFAIL`); another akm process holds a lock or is writing `state.db` right now |
221
224
  | `78` | Configuration error |
222
225
 
223
226
  **From 0.9.12**, every success envelope produced by the passthrough stamp
@@ -0,0 +1 @@
1
+ Feedback describes what a reader found missing or wrong. It is a signal to investigate, not a fact to insert. Do not add claims, numbers, dates, paths, ports, or incidents that are not already present in the asset content. If feedback asks for information the asset lacks, add a clearly marked `TODO: verify …` placeholder or leave the section unchanged.
@@ -9,3 +9,5 @@ AKM_REFLECT_CONTENT_END
9
9
  The first begin marker and final end marker delimit the body; marker lines between them are literal content. Put the complete markdown body between those outer markers. Quotes, Markdown fences, and backslashes inside the body are literal content; do not JSON-escape them. Emit the body only, without YAML frontmatter, because AKM preserves and merges the source frontmatter itself.
10
10
 
11
11
  The frontmatter patch must be a one-line JSON object with exactly `description` and `when_to_use`. Keep a field `null` when it should not change. Supply a non-empty string only when adding or correcting that field; AKM merges those values through its existing sanitizer.
12
+
13
+ Never include the truncation marker (the literal text `{{TRUNCATION_MARKER}}`) or any other text from outside the fenced asset content shown to you, anywhere in the body.
@@ -1,3 +1,5 @@
1
1
  Respond only through the provider's native JSON schema. {{FIELD_RULE}}
2
2
 
3
3
  `content` must contain the complete improved markdown body only, without YAML frontmatter. `frontmatterPatch` must contain exactly `description` and `when_to_use`; set either field to `null` when it should not change, or to a non-empty string when adding or correcting it. AKM merges that narrow patch with the source frontmatter and preserves target identity itself. `confidence` is your honest self-rated quality confidence from 0 to 1. Do not add prose or Markdown fences around the JSON response.
4
+
5
+ Never include the truncation marker (the literal text `{{TRUNCATION_MARKER}}`) or any other text from outside the quoted asset content shown to you, anywhere in `content`.
@@ -1,4 +1,4 @@
1
1
  version: 4
2
- run: akm improve
2
+ run: akm improve --require-engines
3
3
  description: Run improve pipeline nightly
4
4
  schedule: "0 2 * * *"
@@ -1,4 +1,4 @@
1
1
  version: 4
2
- run: akm index
2
+ run: akm index --skip-if-locked
3
3
  description: Nightly incremental index refresh
4
4
  schedule: "0 4 * * *"
@@ -1,4 +1,4 @@
1
1
  version: 4
2
- run: akm improve --strategy graph-refresh --skip-if-locked
2
+ run: akm improve --strategy graph-refresh --skip-if-locked --require-engines
3
3
  description: Full-corpus graph rebuild (weekly Sunday 3:10am)
4
4
  schedule: "10 3 * * 0"
@@ -1,5 +1,5 @@
1
1
  version: 4
2
- run: akm improve --strategy catchup --skip-if-locked
2
+ run: akm improve --strategy catchup --skip-if-locked --require-engines
3
3
  description: Manual recovery — consolidation + triage drain (run on demand via `akm task run akm-improve-catchup`)
4
4
  # Manual-recovery task: ships disabled (the retired registerDefaultTasks
5
5
  # marked it enableMode: "manual"). `akm task run` works while disabled;
@@ -1,4 +1,4 @@
1
1
  version: 4
2
- run: akm improve --strategy consolidate --skip-if-locked
2
+ run: akm improve --strategy consolidate --skip-if-locked --require-engines
3
3
  description: Consolidation-only pass (every 4h at :20)
4
4
  schedule: "20 */4 * * *"
@@ -1,4 +1,4 @@
1
1
  version: 4
2
- run: akm improve --strategy reflect-distill --skip-if-locked
2
+ run: akm improve --strategy reflect-distill --skip-if-locked --require-engines
3
3
  description: Frequent learning pass (hourly at :40) — reflect, distill, memory inference, judged triage
4
4
  schedule: "40 * * * *"
@@ -1,4 +1,4 @@
1
1
  version: 4
2
- run: akm improve --strategy thorough --skip-if-locked
2
+ run: akm improve --strategy thorough --skip-if-locked --require-engines
3
3
  description: Full nightly quality sweep (daily 2:15am; suggested for server installs)
4
4
  schedule: "15 2 * * *"
@@ -65,7 +65,6 @@ const RETIRED_COMMAND_HINTS = {
65
65
  "task enable": "`akm task enable` was removed in 0.9 — set `enabled: true` in the task YAML, then `akm task sync`.",
66
66
  "task disable": "`akm task disable` was removed in 0.9 — set `enabled: false` in the task YAML, then `akm task sync`.",
67
67
  "task init": "`akm task init` was removed in 0.9 — `akm setup` seeds the default schedules.",
68
- "task list": "there is no `task list` — task files are indexed assets; use `akm search --type task`.",
69
68
  "task show": "there is no `task show` — task files are indexed assets; use `akm show <ref>`.",
70
69
  "task remove": "there is no `task remove` — delete the task YAML, then run `akm task sync` to unbind it.",
71
70
  // `improve canary` is NOT here: `akm improve` is a leaf command (a
@@ -31,6 +31,8 @@ export { parseAllFlagValues };
31
31
  * 2 usage error
32
32
  * 4 health warn (health command only)
33
33
  * 70 internal / unclassified (sysexits EX_SOFTWARE — akm threw unexpectedly)
34
+ * 75 transient (sysexits EX_TEMPFAIL — retry shortly; another akm process
35
+ * holds a lock or is writing state.db right now, not a bad command line)
34
36
  * 78 config error
35
37
  */
36
38
  export const EXIT_CODES = {
@@ -41,6 +43,11 @@ export const EXIT_CODES = {
41
43
  // sysexits.h EX_SOFTWARE. Distinct from GENERAL(1) so scripts can tell an
42
44
  // expected "not found" outcome from akm itself throwing an unexpected error.
43
45
  INTERNAL: 70,
46
+ // sysexits.h EX_TEMPFAIL (#948 addendum). Distinct from USAGE(2): a
47
+ // scheduler or cron wrapper classifies 2 as "fix the command line", but a
48
+ // TransientError means "try again in a few seconds" — a different retry
49
+ // contract callers can branch on.
50
+ TEMPFAIL: 75,
44
51
  CONFIG: 78,
45
52
  };
46
53
  // ── Helpers ──────────────────────────────────────────────────────────────────
@@ -64,6 +71,8 @@ function classifyExitCode(error) {
64
71
  return EXIT_CODES.CONFIG;
65
72
  case "not-found":
66
73
  return EXIT_CODES.GENERAL;
74
+ case "transient":
75
+ return EXIT_CODES.TEMPFAIL;
67
76
  default:
68
77
  return assertNever(error.kind, "classifyExitCode");
69
78
  }
@@ -51,6 +51,7 @@ const SELF_DIAGNOSED_FLAGS = new Map(Object.entries({
51
51
  "proposal drain": ["profile"], // retired, points at --strategy
52
52
  search: ["source"], // renamed to --from
53
53
  curate: ["source"], // renamed to --from
54
+ "task list": ["source"], // renamed to --from (alias of `search --type task`, #951)
54
55
  remember: ["target"], // renamed to --bundle
55
56
  clone: ["target"], // renamed to --bundle
56
57
  improve: ["auto-accept", "target"], // retired in 0.9.0 / renamed to --bundle
package/dist/cli.js CHANGED
@@ -293,8 +293,8 @@ const healthCommand = defineCommand({
293
293
  probe: {
294
294
  type: "boolean",
295
295
  default: true,
296
- description: "Probe default-llm-engine / configured-engines reachability (on by default).",
297
- negativeDescription: "Skip the reachability probes (for an offline or air-gapped host).",
296
+ description: "Probe default-llm-engine / configured-engines reachability and check for a newer akm release (on by default).",
297
+ negativeDescription: "Skip the reachability probes and the update check (for an offline or air-gapped host).",
298
298
  },
299
299
  },
300
300
  async run({ args }) {
@@ -554,6 +554,7 @@ export const main = defineCommand({
554
554
  " 2 usage error\n" +
555
555
  " 4 health warn (akm health only)\n" +
556
556
  " 70 internal / unclassified error\n" +
557
+ " 75 transient (retry shortly — another akm process holds a lock or is writing state.db)\n" +
557
558
  " 78 config error",
558
559
  },
559
560
  args: {