akm-cli 0.10.261016-beta → 0.10.261018-beta

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 (113) hide show
  1. package/CHANGELOG.md +144 -0
  2. package/README.md +8 -10
  3. package/SECURITY.md +7 -99
  4. package/STABILITY.md +57 -98
  5. package/dist/assets/opencode/akm-plugin.js +103 -1
  6. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +2 -2
  7. package/dist/assets/stash-skeleton/facts/conventions/organization.md +11 -17
  8. package/dist/cli.js +32 -41
  9. package/dist/commands/feedback-cli.js +5 -4
  10. package/dist/commands/health.js +15 -16
  11. package/dist/commands/proposal/proposal-cli.js +1 -1
  12. package/dist/commands/read/registry-search.js +4 -1
  13. package/dist/commands/read/search.js +6 -6
  14. package/dist/commands/sources/self-update.js +9 -5
  15. package/dist/commands/tasks/tasks.js +12 -13
  16. package/dist/core/adapter/adapters/agent-skills-adapter.js +7 -7
  17. package/dist/core/adapter/adapters/akm-task-adapter.js +1 -1
  18. package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
  19. package/dist/core/adapter/adapters/claude-adapter.js +1 -1
  20. package/dist/core/adapter/adapters/dotenv-adapter.js +1 -1
  21. package/dist/core/adapter/adapters/generic-files-adapter.js +1 -1
  22. package/dist/core/adapter/adapters/llm-wiki-adapter.js +1 -1
  23. package/dist/core/adapter/adapters/opencode-adapter.js +1 -1
  24. package/dist/core/adapter/adapters/website-snapshot-adapter.js +1 -1
  25. package/dist/core/bundle-rename.js +17 -7
  26. package/dist/core/config/convert-engine-references.js +23 -7
  27. package/dist/core/config/convert-engines.js +94 -28
  28. package/dist/core/config/convert-models-overlay.js +14 -2
  29. package/dist/indexer/bundle-identity-guard.js +6 -6
  30. package/dist/indexer/index-written-assets.js +3 -3
  31. package/dist/indexer/indexer.js +12 -8
  32. package/dist/indexer/materialize-embeddings.js +12 -2
  33. package/dist/indexer/passes/metadata.js +103 -46
  34. package/dist/indexer/search/ranking.js +7 -3
  35. package/dist/indexer/search/search-fields.js +1 -1
  36. package/dist/indexer/usage/usage-events.js +30 -8
  37. package/dist/indexer/walk/walker.js +1 -1
  38. package/dist/integrations/agent/spawn.js +7 -2
  39. package/dist/integrations/harnesses/opencode/agent-builder.js +10 -1
  40. package/dist/integrations/harnesses/opencode/runtime/binary.js +6 -3
  41. package/dist/integrations/harnesses/opencode/runtime/index.js +18 -9
  42. package/dist/integrations/harnesses/opencode/session-log-v2.js +11 -1
  43. package/dist/output/shapes/helpers.js +3 -3
  44. package/dist/output/text/command-format.js +5 -6
  45. package/dist/scripts/akm-migrate-node.js +122 -36
  46. package/dist/scripts/akm-migrate.js +122 -36
  47. package/dist/sources/providers/git-provider.js +2 -4
  48. package/dist/sources/providers/provider-utils.js +8 -13
  49. package/dist/sources/snapshot-fetchers/website-ingest.js +6 -7
  50. package/dist/storage/repositories/events-repository.js +15 -0
  51. package/dist/storage/repositories/improve-ledger-repository.js +13 -0
  52. package/dist/storage/repositories/index-entries-repository.js +43 -5
  53. package/dist/storage/repositories/outcome-repository.js +12 -0
  54. package/dist/storage/repositories/proposals-repository.js +5 -5
  55. package/dist/storage/repositories/salience-repository.js +13 -0
  56. package/dist/storage/repositories/task-history-repository.js +8 -7
  57. package/dist/storage/repositories/workflow-runs-repository.js +10 -8
  58. package/docs/README.md +24 -60
  59. package/docs/agents/README.md +3 -8
  60. package/docs/architecture/README.md +18 -20
  61. package/docs/architecture/adapters.md +40 -43
  62. package/docs/architecture/architecture.md +180 -154
  63. package/docs/architecture/improvement.md +95 -91
  64. package/docs/architecture/internals/classification.md +101 -119
  65. package/docs/architecture/internals/fresh-host-rebuild-runbook.md +33 -35
  66. package/docs/architecture/internals/health-advisories.md +59 -46
  67. package/docs/architecture/internals/improve-workflow.md +166 -693
  68. package/docs/architecture/internals/indexing.md +65 -465
  69. package/docs/architecture/internals/storage-locations.md +301 -848
  70. package/docs/architecture/persisted-data-compat.md +6 -6
  71. package/docs/architecture/specs/okf-support.md +67 -95
  72. package/docs/architecture/specs/ref.md +67 -125
  73. package/docs/architecture/testing/testing-workflow.md +108 -168
  74. package/docs/architecture/workflow-engine.md +34 -33
  75. package/docs/guides/README.md +5 -5
  76. package/docs/guides/author-bundles.md +72 -98
  77. package/docs/guides/author-workflows.md +11 -18
  78. package/docs/guides/bundles.md +91 -123
  79. package/docs/guides/capture-knowledge.md +10 -9
  80. package/docs/guides/concepts.md +45 -30
  81. package/docs/guides/discover-and-load.md +39 -32
  82. package/docs/guides/getting-started.md +24 -24
  83. package/docs/guides/improve-the-library.md +98 -80
  84. package/docs/guides/recipes/headless-install.md +4 -4
  85. package/docs/guides/recipes/website-source.md +9 -5
  86. package/docs/guides/run-workflows.md +10 -11
  87. package/docs/guides/scheduling.md +11 -17
  88. package/docs/guides/use-with-any-agent.md +15 -14
  89. package/docs/guides/wikis.md +6 -9
  90. package/docs/integration/bundling-akm.md +21 -26
  91. package/docs/maintainers/README.md +2 -2
  92. package/docs/maintainers/local-development.md +3 -16
  93. package/docs/migration/README.md +2 -1
  94. package/docs/migration/release-notes/0.10.0.md +196 -23
  95. package/docs/migration/release-notes/README.md +2 -2
  96. package/docs/migration/v0.9-to-v0.10.md +103 -12
  97. package/docs/product-surface.md +10 -8
  98. package/docs/reference/README.md +26 -15
  99. package/docs/reference/asset-types.md +11 -12
  100. package/docs/reference/bundle-types.md +6 -5
  101. package/docs/reference/cli.md +52 -28
  102. package/docs/reference/configuration.md +91 -73
  103. package/docs/reference/data-and-telemetry.md +204 -421
  104. package/docs/reference/env-and-secrets.md +80 -83
  105. package/docs/reference/memory.md +1 -1
  106. package/docs/reference/refs.md +23 -36
  107. package/docs/reference/registry.md +138 -348
  108. package/docs/reference/supported-formats.md +8 -8
  109. package/docs/reference/tasks.md +13 -19
  110. package/docs/reference/website-sources.md +23 -15
  111. package/docs/reference/workflow-schema.md +28 -47
  112. package/docs/reference/workflows.md +33 -37
  113. package/package.json +1 -1
package/STABILITY.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Stability policy
2
2
 
3
3
  `akm-cli` follows [Semantic Versioning](https://semver.org/) on the 0.x line
4
- **with one caveat**: until 1.0, minor releases (0.x → 0.x+1) may include
5
- breaking changes.
4
+ **with one caveat**: while the version is 0.x, minor releases (0.x → 0.x+1)
5
+ may include breaking changes.
6
6
 
7
7
  **0.9.x series exception.** The 0.9.x releases are a deliberate refactoring
8
8
  and clean-up series: the goal is to pay off all remaining technical debt and
@@ -87,23 +87,15 @@ please file it.
87
87
  | --- | --- |
88
88
  | **Stable** | Scripted use is supported. Changes are additive within a minor release; breaking changes are called out in the CHANGELOG with a migration note. |
89
89
  | **Evolving** | Available across minor releases, but flag names, prompts, and payload shapes may shift. Breaking changes are flagged in the CHANGELOG. |
90
- | **Experimental** | Subject to change without notice. Not recommended for scripted use. Some experimental surfaces additionally require an explicit opt-in — see [`akm improve` autonomy](#akm-improve-autonomy--opt-in-in-090). |
90
+ | **Experimental** | Subject to change without notice. Not recommended for scripted use. Some experimental surfaces additionally require an explicit opt-in — see [`akm improve` autonomy](#akm-improve-autonomy). |
91
91
  | **Internal** | Not a public interface. May change or disappear in any release, without a CHANGELOG note. Listed here only so you can recognize it. |
92
92
 
93
93
  ## Command tier index
94
94
 
95
95
  The canonical index: every command and subcommand group in the current tree
96
96
  (enumerated from `main.subCommands` in `src/cli.ts` and each group's own
97
- `subCommands`), with its tier. The prose sections below remain the detailed
98
- explanation of *why*; this table is the lookup. Two spots have no single
99
- explicit sentence naming their tier and were resolved by reading neighbors:
100
- `akm bundle show` is assigned Evolving because it is discussed only inside
101
- the Evolving "Bundles & the workspace model" bullet, alongside the still-
102
- shifting adapter set, unlike its sibling `akm bundle list`, which the Stable
103
- section names explicitly; and `akm proposal list` is assigned Stable — the
104
- Stable section names it explicitly ("list filters"), which takes precedence
105
- over its incidental mention inside the Evolving "Improvement loop" bullet's
106
- enumeration of the whole `proposal` noun group.
97
+ `subCommands`), with its tier. The sections below explain what each tier
98
+ covers beyond the table.
107
99
 
108
100
  | Command | Tier | Notes |
109
101
  | --- | --- | --- |
@@ -116,10 +108,11 @@ enumeration of the whole `proposal` noun group.
116
108
  | `akm bundle create` | Stable | |
117
109
  | `akm bundle add` | Stable | |
118
110
  | `akm bundle list` | Stable | |
119
- | `akm bundle show` | Evolving | See note above. |
111
+ | `akm bundle show` | Evolving | |
112
+ | `akm bundle rename` | Stable | |
120
113
  | `akm bundle remove` | Stable | |
121
114
  | `akm bundle update` | Stable | |
122
- | `akm upgrade` | Stable | Including the plugin step and `--next` (0.9.28), verified on real hosts, in empty containers and on failure paths (#1099). |
115
+ | `akm upgrade` | Stable | Including the plugin step and `--next`. Checksum verification is not optional; the recovery hatch is the `AKM_UPGRADE_SKIP_CHECKSUM` environment variable (Internal), not a flag. |
123
116
  | `akm search` | Stable | |
124
117
  | `akm curate` | Stable | |
125
118
  | `akm show` | Stable | |
@@ -137,14 +130,14 @@ enumeration of the whole `proposal` noun group.
137
130
  | `akm registry list` | Evolving | |
138
131
  | `akm registry add` | Evolving | |
139
132
  | `akm registry remove` | Evolving | |
140
- | `akm migrate status` | 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. |
141
- | `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. |
133
+ | `akm migrate status` | Internal | Forwards to the standalone `akm-migrate` tool and renders its result through the normal `--format` pipeline. |
134
+ | `akm migrate apply` | Internal | Forwards to the standalone `akm-migrate` tool and renders its result through the normal `--format` pipeline. |
142
135
  | `akm config path` | Stable | |
143
136
  | `akm config list` | Stable | |
144
- | `akm config get` | Stable | Default bare-value shape unchanged; `--show-source` (0.9.15, opt-in) wraps it as `{ value, source }`. |
137
+ | `akm config get` | Stable | Default bare-value shape unchanged; `--show-source` (opt-in) wraps it as `{ value, source }`. |
145
138
  | `akm config set` | Stable | |
146
139
  | `akm config unset` | Stable | |
147
- | `akm config diff` | Evolving | New in 0.9.15; compares effective (post-`extends`) configs, secrets redacted. |
140
+ | `akm config diff` | Evolving | Compares effective (post-`extends`) configs. |
148
141
  | `akm feedback` | Stable | |
149
142
  | `akm log` | Evolving | |
150
143
  | `akm agent` | Evolving | |
@@ -154,12 +147,12 @@ enumeration of the whole `proposal` noun group.
154
147
  | `akm improve` | Evolving | Review-first by default; mutating lanes require `experimental.improveAutonomy` — see below. |
155
148
  | `akm improve report` | Evolving | Subcommand since 0.10 (was a magic `scope` value); owns `--run` and `--since`. |
156
149
  | `akm improve judge` | Evolving | Subcommand since 0.10 (was a magic `scope` value); reads one revision as JSON on stdin and writes nothing. |
157
- | `akm proposal list` | Stable | See reconciliation note above. |
150
+ | `akm proposal list` | Stable | |
158
151
  | `akm proposal show` | Evolving | |
159
152
  | `akm proposal diff` | Evolving | |
160
153
  | `akm proposal accept` | Evolving | |
161
154
  | `akm proposal reject` | Evolving | |
162
- | `akm proposal reopen` | Evolving | New in 0.9.19; undoes a rejection. |
155
+ | `akm proposal reopen` | Evolving | Undoes a rejection. |
163
156
  | `akm proposal revert` | Evolving | |
164
157
  | `akm proposal drain` | Evolving | |
165
158
  | `akm proposal extract` | Evolving | Former top-level `akm extract`. Promoted from Experimental in 0.10 (#1133): the extract eval measures it. Evolving, not Stable, because 0.10 is still settling the improve defaults. |
@@ -178,9 +171,12 @@ enumeration of the whole `proposal` noun group.
178
171
  | `akm secret run` | Stable | Read-and-inject surface. |
179
172
  | `akm secret set` | Experimental | Write verb. |
180
173
  | `akm task add` | Evolving | |
174
+ | `akm task enable` | Evolving | |
175
+ | `akm task disable` | Evolving | |
181
176
  | `akm task run` | Evolving | |
182
177
  | `akm task history` | Evolving | |
183
178
  | `akm task sync` | Evolving | |
179
+ | `akm task prune` | Evolving | |
184
180
  | `akm task doctor` | Evolving | |
185
181
  | `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). |
186
182
 
@@ -202,12 +198,11 @@ enumeration of the whole `proposal` noun group.
202
198
  the core resolves it as a section selector — `akm show <ref>#<heading-slug>`
203
199
  returns that one section, no fragment returns the whole item, and an
204
200
  unmatched fragment lists the available slugs. A whole document's `show`
205
- carries `toc`, every heading with the slug that selects it. Elsewhere it is an
206
- adapter-owned selector opaque to the core. See
201
+ carries `toc`, every heading with the slug that selects it. On any other item
202
+ `akm show` ignores the fragment with a warning and shows the whole item. See
207
203
  [`docs/architecture/specs/ref.md`](https://github.com/itlackey/akm/blob/main/docs/architecture/specs/ref.md).
208
204
  - **Refs in prose** must be fully qualified (`bundle//conceptId`) or a native
209
- adapter link form. A bare conceptId in prose is ordinary text, not a ref,
210
- and no akm tool rewrites it.
205
+ adapter link form. A bare conceptId in prose is ordinary text, not a ref.
211
206
  - **ConceptId-prefix enumeration** — `akm search "memories/"`,
212
207
  `"memories/projecta/"`, `"bundle//"`, `"bundle//skills/"`. A trailing `/` is
213
208
  required. The prefix matches the **conceptId** — the spelling every emitted
@@ -228,16 +223,11 @@ enumeration of the whole `proposal` noun group.
228
223
  `akm bundle create`, `akm setup`, `akm remember`, `akm feedback`,
229
224
  `akm config set`, `akm config unset`.
230
225
  - **Renames are delete + create** — moving or renaming an item gives it a new
231
- identity; learned state does not follow it. Cross-bundle movement is
232
- copy/import plus delete. The procedure is a plain filesystem move, then
226
+ identity; learned state (feedback, usage, salience) does not follow it.
227
+ Cross-bundle movement is copy/import plus delete. Move the file, then run
233
228
  `akm index`, then `akm lint` to catch inbound refs the move left dangling.
234
- `akm mv` was **removed in 0.9.0**: it claimed to preserve identity across a
235
- rename, but its inbound-ref rewriting targeted bare conceptIds rather than
236
- the anchored `bundle//conceptId` prose form, so it could rewrite non-refs
237
- while leaving real refs dangling. To carry an asset's earned signal
238
- (feedback, usage, salience/outcome history) across a rename, the maintainer
239
- script `scripts/rekey-asset-ref.ts` re-keys those rows old -> new; it is
240
- Internal tooling, not a supported command surface.
229
+ There is no `akm mv`; `scripts/rekey-asset-ref.ts` re-keys earned signal from
230
+ an old ref to a new one and is Internal tooling.
241
231
  - **Asset `type` is a free-form, open string** — `--type` filtering is an
242
232
  exact match against an open set and is deliberately **not validated**: an
243
233
  unrecognized type returns zero hits, not an error. Adapters emit types
@@ -288,14 +278,11 @@ enumeration of the whole `proposal` noun group.
288
278
  | `2` | Usage / bad input |
289
279
  | `4` | Health warning (`akm health` only) |
290
280
  | `70` | Internal / unclassified |
291
- | `75` | Transient — retry shortly (`TransientError`, sysexits `EX_TEMPFAIL`); another akm process holds a lock or is writing `state.db` right now |
281
+ | `75` | Transient — retry shortly (`TransientError`, sysexits `EX_TEMPFAIL`); another akm process holds a lock or is writing `state.db` or `index.db` right now |
292
282
  | `78` | Configuration error |
293
283
 
294
- **From 0.9.12**, every success envelope produced by the passthrough stamp
295
- (`config`, `clone`, `models`, `task-*`, `workflow-*`, `registry-*`, …) also
296
- carries `ok: true`; a command that already computes its own `ok` keeps it.
297
- - **Install scripts** — `install.sh` and `install.ps1` URLs; the `--prefix`
298
- / `AKM_INSTALL_DIR` environment override.
284
+ - **Install scripts** — the `install.sh` and `install.ps1` URLs and the
285
+ `AKM_INSTALL_DIR` environment override.
299
286
  - **Runtime** — the npm package requires Node.js >= 22 as its bootstrap and
300
287
  prefers a working Bun >= 1.0 for execution when both are available; old,
301
288
  unusable, or absent Bun installations fall back to Node.js. Standalone
@@ -323,7 +310,7 @@ CHANGELOG with a migration note.
323
310
  options or tighten validation across releases. `akm improve` stays on by
324
311
  default and is **review-first**: the lanes that mutate assets without review
325
312
  require `experimental.improveAutonomy` — see
326
- [`akm improve` autonomy](#akm-improve-autonomy--opt-in-in-090).
313
+ [`akm improve` autonomy](#akm-improve-autonomy).
327
314
  `--auto-accept` was removed in 0.9.0 and is an unknown flag (a usage error,
328
315
  exit 2) since 0.10. The replacement is
329
316
  `akm improve && akm proposal drain --promote --yes`, or a `triage` block
@@ -333,9 +320,9 @@ CHANGELOG with a migration note.
333
320
  routine sessions left empty, planted instructions refused) is its gate. It
334
321
  is Evolving, not Stable, because 0.10 is still settling the improve
335
322
  defaults; what it extracts and its output may still be tuned.
336
- - **Tasks** — `akm task` subcommand surface (`add | run | sync | doctor |
337
- history | explain | validate`; no alias, no
338
- `list`/`remove`/`init`/`enable`/`disable`); task source v4 YAML (typed
323
+ - **Tasks** — `akm task` subcommand surface (`add | enable | disable | run |
324
+ explain | history | sync | prune | doctor`; no alias, no
325
+ `list`/`remove`/`init`/`validate`); task source v4 YAML (typed
339
326
  `inputs:`, optional `schedule:`) is the only accepted version — task v3 and
340
327
  task v2 sources are converted by `akm migrate apply`. Command tasks use
341
328
  a named connection or harness, and task history metadata is versioned. Schema additions in
@@ -367,7 +354,7 @@ CHANGELOG with a migration note.
367
354
  `akm bundle list` / `akm bundle show <name>` and enumerated through
368
355
  `akm search "bundle//"`. (An earlier `akm bundle items` noun group was
369
356
  removed in 0.9.0 as duplicative of `akm search`; the current `akm bundle`
370
- group — `create | add | list | show | remove | update` — is the
357
+ group — `create | add | list | show | rename | remove | update` — is the
371
358
  lifecycle-management surface consolidated from the former top-level
372
359
  `init`/`add`/`list`/`remove`/`update` commands, not a revival of that one.)
373
360
  OKF is the
@@ -397,11 +384,6 @@ CHANGELOG with a migration note.
397
384
  `akm search --from registry` (0.9.0: `registry search` was folded into
398
385
  `search`). Building a registry index is maintainer tooling (Internal) and
399
386
  lives outside the CLI, in `scripts/build-registry-index.ts`.
400
- - **Upgrade** — `akm upgrade`. Checksum verification is not optional; the
401
- recovery hatch is the `AKM_UPGRADE_SKIP_CHECKSUM` environment variable
402
- (Internal), not a flag. The harness plugin step and `--next` (new in 0.9.28)
403
- are targeted to become Stable before the 0.9 series ends, once verified on
404
- real hosts.
405
387
  - **Lint** — `akm lint`. The rule set and finding shapes iterate; the
406
388
  `--fail-on-flagged` CI contract and the exit codes are stable.
407
389
  - **Health** — `akm health` and its exit codes (0 pass / 4 warn / 1 fail) are
@@ -457,8 +439,8 @@ for scripted use.
457
439
  **write** verbs (`create`, `set`, `unset`, `remove`) are Experimental; the
458
440
  `list` / `path` / `run` / `export` read-and-inject surface is Stable.
459
441
  - **Memory belief-state transitions** — `captureMode`, `beliefState`,
460
- contradiction edges, and the consolidate journal are observable but
461
- the algorithm that writes them is tuning across patch releases.
442
+ contradiction edges, and the consolidate journal are observable, but the
443
+ algorithm that writes them is tuning across patch releases.
462
444
  - **Improve built-in strategies and lanes** — the built-in strategies other
463
445
  than `default` and `consolidate` and the proactive-maintenance lane. 0.10 measures each and keeps, folds or removes it
464
446
  (#1094). `quick`, `reflect-distill`, `thorough` and `catchup` were removed
@@ -470,17 +452,18 @@ for scripted use.
470
452
  added, renamed, or dropped in any 0.9.x or 0.10.x release**. The `akm
471
453
  improve` *command* surface is Evolving (above); its tuning config is not.
472
454
 
473
- ### `akm improve` autonomy — opt-in in 0.9.0
455
+ ### `akm improve` autonomy
474
456
 
475
- **`akm improve` is review-first by default in 0.9.0.** The command itself is ON
476
- — its schedules and reflect/distill proposals all run — but the lanes that
477
- mutate assets *without* review require an explicit opt-in:
457
+ `akm improve` is review-first by default. The command is on — its schedules and
458
+ reflect/distill proposals all run — but the lanes that change assets *without*
459
+ review require an explicit opt-in:
478
460
 
479
461
  ```sh
480
462
  akm config set experimental.improveAutonomy true
481
463
  ```
482
464
 
483
- Without it, these three lanes are downgraded (the triage lane only when its
465
+ An absent `experimental` section, an absent key and an explicit `false` all
466
+ read as off. Without the opt-in, these three lanes are downgraded (the triage lane only when its
484
467
  judgment tier is on), and each downgrade is **reported,
485
468
  not silent**: it warns on stderr naming the lane and the key, appends an
486
469
  `improve_skipped` event with `reason: "autonomy_gated"`, is counted in
@@ -517,39 +500,28 @@ Because the gate is applied before the LLM preflight, a review-first workspace
517
500
  also needs fewer connections or harnesses configured: a strategy whose only
518
501
  model-backed process is a gated lane resolves without one at all.
519
502
 
520
- **Three direct writes are deliberately NOT gated**, because they are additive or
521
- already independently controlled:
503
+ Reflect, distill, extract candidates, validation and proactive-maintenance
504
+ selection only stage proposals. These writes are not gated by the opt-in:
522
505
 
523
- | Write | Why it stays ungated |
506
+ | Write | What it does |
524
507
  | --- | --- |
525
- | `extract` session indexing | Additive `sessions/**` writes; nothing is overwritten or deleted |
526
- | distill's encoding-salience stamp | Frontmatter metadata only |
527
- | `sync.push` | Publishes already-committed content to a remote the user configured for that purpose, and has its own `improve.strategies.<name>.sync.push: false` and `--no-push` |
528
-
529
- Autonomy is never inferred: an absent `experimental` section, an absent key, and
530
- an explicit `false` all read as off, so a partially-written or older config is
531
- review-first rather than accidentally permissive.
532
-
533
- Reflect, distill, extract candidates, validation, and proactive-maintenance
534
- selection are proposal-only and never write assets directly. Two further
535
- direct writes are ungated by design: `extract`'s session
536
- indexing (additive `sessions/**` writes,
537
- `processes.extract.indexSessions`, default on) and distill's
538
- encoding-salience frontmatter stamp (metadata only).
508
+ | `extract` session indexing | Writes `sessions/**` assets (`processes.extract.indexSessions`, default on) |
509
+ | distill's encoding-salience stamp | Writes a salience score to asset frontmatter |
510
+ | Graced archive purge | At the start of every improve run, removes retirement archives past the grace period |
511
+ | Accept commits | Accepting a proposal commits its paths when the bundle is a git repository |
512
+ | `sync.push` | Pushes already-committed content to the configured remote; turn it off with `improve.strategies.<name>.sync.push: false` or `--no-push` |
539
513
 
540
514
  ## Internal
541
515
 
542
516
  Not public interfaces. Listed so you can recognize them, not so you can rely
543
517
  on them.
544
518
 
545
- - **Migration surfaces** — the standalone `akm-migrate` tool owns the one-time
546
- 0.8→0.9 cutover, storage migration, and recovery backups (`akm-migrate
547
- backup` / `restore`). `akm migrate` is a thin process forwarder; `akm backup`
548
- and `akm config migrate` are removed. `akm help migrate <version>` is Stable
549
- and only renders release notes.
519
+ - **Migration surfaces** — the standalone `akm-migrate` tool (`status`,
520
+ `apply`) rewrites config, `state.db`, task files and old on-disk residue to
521
+ the current shape, taking a backup first. `akm migrate` is a thin process
522
+ forwarder for it.
550
523
  - **`bun scripts/build-registry-index.ts`** — maintainer tooling for building a
551
- registry index. It is a repository script, not a CLI command (the former
552
- `akm registry build-index` subcommand was removed).
524
+ registry index. It is a repository script, not a CLI command.
553
525
  - **Environment variables** — see the table below.
554
526
 
555
527
  ### Environment variables
@@ -560,10 +532,9 @@ on them.
560
532
  | --- | --- |
561
533
  | `AKM_BUNDLE_DIR`, `AKM_CONFIG_DIR`, `AKM_DATA_DIR`, `AKM_CACHE_DIR` | Filesystem layout overrides |
562
534
  | `AKM_VERBOSE`, `AKM_DEBUG` | Diagnostics |
563
- | `AKM_REGISTRY_URL` | akm registry mirror override |
564
- | `AKM_NPM_REGISTRY` | npm mirror override — redirects BOTH the trusted-tarball host allowlist and package metadata lookups (`npm view`-equivalent resolution) to the given registry base, replacing `registry.npmjs.org` wholesale rather than merging with it |
535
+ | `AKM_REGISTRY_URL` | Comma-separated registry URL override; replaces the configured registries in every registry search akm performs (`akm registry list` and `akm info` still show the configured list) |
536
+ | `AKM_NPM_REGISTRY` | npm mirror base URL; replaces `registry.npmjs.org` for package lookups |
565
537
  | `AKM_SQLITE_JOURNAL_MODE` | SQLite journal mode (network filesystems) |
566
- | `AKM_BIN` | Absolute `akm` path for scheduler registration |
567
538
  | `AKM_INSTALL_DIR` | Install-script prefix |
568
539
  | `AKM_UPGRADE_SKIP_CHECKSUM` | Recovery hatch for a broken upgrade checksum |
569
540
 
@@ -572,18 +543,6 @@ on them.
572
543
  `AKM_EMBED_DETERMINISTIC`, `AKM_CLAUDE_PROJECTS_DIR`,
573
544
  `AKM_FORCE_INIT_TMP_STASH`, `AKM_STATE_DIR`.
574
545
 
575
- Anything matching `AKM_TEST_*` is test-only fault injection. Never set it.
576
- `AKM_VERSION` is the only variable actually stripped from release builds, via
577
- `bun build --define` (see `.github/workflows/release.yml`); no `src/` code
578
- reads an `AKM_TEST_*` variable, so the compiled `akm` binary itself carries
579
- none. That said, three fault-injection hooks are NOT compiled out and DO ship
580
- in every install: `AKM_TEST_MIGRATION_FAIL_INDEX_QUARANTINE`,
581
- `AKM_TEST_MIGRATION_FAIL_WORKFLOW_DELETE`, and
582
- `AKM_TEST_MIGRATION_FAIL_RESTORE_AFTER` (`scripts/akm-migrate/`). The migration
583
- tool ships separately from the compiled `akm` binary, so these throw-only hooks
584
- remain physically present. At rest they are runtime-guarded no-ops unless the
585
- exact internal test value is set. Never set them.
586
-
587
546
  ## On the horizon
588
547
 
589
548
  These changes are planned and will land in a known future release. They
@@ -619,8 +578,8 @@ worth fixing.
619
578
  ## See also
620
579
 
621
580
  - [`CHANGELOG.md`](./CHANGELOG.md) — every release's behavior changes.
622
- - [`SECURITY.md`](./SECURITY.md) — security supported-version policy
623
- (independent of the feature-stability policy above).
581
+ - [`SECURITY.md`](./SECURITY.md) — supported versions and how to report a
582
+ vulnerability.
624
583
  - [`docs/architecture/specs/ref.md`](https://github.com/itlackey/akm/blob/main/docs/architecture/specs/ref.md) — the
625
584
  normative ref grammar.
626
585
  - [`docs/reference/data-and-telemetry.md`](./docs/reference/data-and-telemetry.md) — what
@@ -18,6 +18,13 @@
18
18
  // transport sends them. OpenCode sizes every request's output cap from the model's limit and sends it as
19
19
  // `max_tokens` or `max_completion_tokens`; that is its own accounting, not a cap anyone asked for, so it is
20
20
  // dropped, and the connection's fields carry a cap only when the connection writes one.
21
+ // - A run that has stopped getting anywhere: when the model has made the same tool calls three turns in a row and
22
+ // been given the same results each time, the next request carries a message from the user that says so and asks it to
23
+ // use the result it has or take another route, and a temperature it carries below 0.7 is raised to 0.7, because a
24
+ // model that is sampled greedily answers the same thing to the same context however it is asked; if the model then
25
+ // makes the same calls again, the run fails with a message that says so, before the next model request is sent.
26
+ // OpenCode has no such check of its own (its `doom_loop` setting is read and ignored), and a run that never ends has
27
+ // nothing else to stop it when the connection sets no `timeoutMs`.
21
28
 
22
29
  import { execFile } from "node:child_process";
23
30
 
@@ -128,6 +135,69 @@ function withFields(base, over) {
128
135
  return merged;
129
136
  }
130
137
 
138
+ /** How many turns in a row may make the same tool calls and get the same results before the model is told so. */
139
+ const REPEATED_TURNS = 3;
140
+
141
+ /**
142
+ * The temperature the one request that tells the model it is repeating itself is raised to, when it is sent at a lower
143
+ * one. A Qwen 27B model sampled greedily (temperature 0) gave the same call again to every wording of the message in
144
+ * every try (0 of 16 replays of a captured request); sampled at 0.7 it left the repeat in about half of them (22 of 44).
145
+ * A request that carries no temperature is left to the endpoint's default: some endpoints refuse any temperature but
146
+ * their own (OpenAI's reasoning models answer anything but 1 with a 400).
147
+ */
148
+ const RECOVERY_TEMPERATURE = 0.7;
149
+
150
+ /**
151
+ * A tool result as the check compares it. OpenCode saves an output too long to send whole to a file of its own for each
152
+ * call and names that file in the result (`[showing … of …; full output saved to <file>]`), so the name is left out:
153
+ * the same long output is the same result.
154
+ */
155
+ const comparable = (content) =>
156
+ JSON.stringify(content).replace(/full output saved to [^\]]*\]/g, "full output saved to <file>]");
157
+
158
+ /**
159
+ * What the last turns of a chat request repeat, when REPEATED_TURNS or more of them in a row made the same calls (the
160
+ * same name and arguments, ids ignored, parallel calls as a set) and were answered with the same results, else
161
+ * undefined: how many turns, the names of the tools called, the calls and results themselves (`signature`) and the id
162
+ * of the first call of the oldest turn (`since`), which tells one run of repeats from the next. A model that does this
163
+ * has settled on one answer to what it is told and does not leave it; a call whose result changes (a poll) is not
164
+ * repeated.
165
+ */
166
+ function repeatedToolCalls(messages) {
167
+ if (!Array.isArray(messages)) return undefined;
168
+ const turns = [];
169
+ let names = [];
170
+ let since;
171
+ let results = new Map();
172
+ // From the newest message back: the tool results, then the assistant message that made the calls they answer.
173
+ for (let i = messages.length - 1; i >= 0; i--) {
174
+ const message = messages[i];
175
+ if (message?.role === "tool") {
176
+ results.set(message.tool_call_id, comparable(message.content));
177
+ continue;
178
+ }
179
+ if (message?.role !== "assistant" || !Array.isArray(message.tool_calls) || message.tool_calls.length === 0) break;
180
+ const calls = message.tool_calls.map((call) => [
181
+ call?.function?.name,
182
+ call?.function?.arguments,
183
+ results.get(call?.id),
184
+ ]);
185
+ if (calls.some(([name, , result]) => name === undefined || result === undefined)) break;
186
+ const turn = calls
187
+ .map((call) => JSON.stringify(call))
188
+ .sort()
189
+ .join("\n");
190
+ if (turns.length > 0 && turn !== turns[0]) break;
191
+ if (turns.length === 0) names = [...new Set(calls.map(([name]) => name))];
192
+ turns.push(turn);
193
+ since = message.tool_calls[0]?.id;
194
+ results = new Map();
195
+ }
196
+ return turns.length >= REPEATED_TURNS ? { turns: turns.length, names, signature: turns[0], since } : undefined;
197
+ }
198
+
199
+ const quoted = (names) => names.map((name) => `\`${name}\``).join(", ");
200
+
131
201
  export default {
132
202
  id: "akm",
133
203
  setup: async (ctx) => {
@@ -161,6 +231,9 @@ export default {
161
231
  }
162
232
 
163
233
  const fields = ctx.options?.requestFields;
234
+ // The repeats the model has been told about, by session and what they repeat: where they began and how many turns
235
+ // there were. One process runs a run's every session (a subagent's too), and each session's model is told for itself.
236
+ const told = new Map();
164
237
  await ctx.session.hook("http.request", async (event) => {
165
238
  const request = event.request;
166
239
  if (request.body === null) return;
@@ -172,12 +245,41 @@ export default {
172
245
  }
173
246
  if (!isObject(body)) return;
174
247
  const { max_tokens: _own, max_completion_tokens: _ownCompletion, ...kept } = body;
248
+ const stuck = repeatedToolCalls(body.messages);
249
+ if (stuck) {
250
+ const key = `${event.sessionID}\n${stuck.signature}`;
251
+ const was = told.get(key);
252
+ // The model has been told about these calls and made them again, or has come back to them since.
253
+ if (was && (was.since !== stuck.since || stuck.turns > was.turns)) {
254
+ // Measured on OpenCode 2.2.0: an error thrown from this hook fails the run's turn before the request is sent,
255
+ // and `opencode run` exits 1 with the error's message (an unknown error in the session, which akm reports as
256
+ // a non-zero exit). Answering the request with a 4xx instead also ends the run, but only after the request
257
+ // has gone to the model.
258
+ throw new Error(
259
+ `akm stopped this run: the model called ${quoted(stuck.names)} with the same input ${stuck.turns} times in a row and got the same result each time, although it had been told it was repeating itself.`,
260
+ );
261
+ }
262
+ // A request that is sent again unchanged (a retry) is told again, the same way.
263
+ told.set(key, { since: stuck.since, turns: stuck.turns });
264
+ // OpenCode keeps no trace of this message: it does not store what a request's body is changed to.
265
+ kept.messages = [
266
+ ...kept.messages,
267
+ {
268
+ role: "user",
269
+ content: `You have called ${quoted(stuck.names)} with the same input ${stuck.turns} times in a row and got the same result each time, so calling it again will not give you anything new. Use the result you already have, or take a different approach to finish the task. If you call it again with the same input, this run will be stopped.`,
270
+ },
271
+ ];
272
+ }
273
+ const forwarded = isObject(fields) ? withFields(kept, fields) : kept;
274
+ if (stuck && typeof forwarded.temperature === "number" && forwarded.temperature < RECOVERY_TEMPERATURE) {
275
+ forwarded.temperature = RECOVERY_TEMPERATURE;
276
+ }
175
277
  const headers = new Headers(request.headers);
176
278
  headers.delete("content-length");
177
279
  event.request = new Request(request.url, {
178
280
  method: request.method,
179
281
  headers,
180
- body: JSON.stringify(isObject(fields) ? withFields(kept, fields) : kept),
282
+ body: JSON.stringify(forwarded),
181
283
  signal: request.signal,
182
284
  });
183
285
  });
@@ -72,8 +72,8 @@ xrefs:
72
72
  ## Self-situating headers and canonical naming
73
73
 
74
74
  Put the one-line orientation in `description:` and the trigger conditions in
75
- `when_to_use:` — those remain the higher-weight indexed fields. Bounded body
76
- prose is indexed in the lowest-weight `content` field, so
75
+ `when_to_use:`; both are indexed, and search weighs every indexed field
76
+ equally. Bounded body prose is indexed in the `content` field.
77
77
  `description:`/`when_to_use:` remain the primary orientation channel. Then
78
78
  open the body with a plain title plus a one-line
79
79
  orientation naming what it is, its scope/domain, and its key entities in
@@ -23,11 +23,10 @@ path is therefore the one facet you **cannot express twice and cannot change
23
23
  without breaking the ref** — so spend it deliberately, on exactly one axis.
24
24
 
25
25
  Retrieval is search, not browse: no one walks these folders at query time. A
26
- subdirectory buys you three things: its tokens are indexed as part of the
27
- asset's **name** (the highest-weighted search field), they are auto-added to
28
- `tags` (even when you set `tags` explicitly), and for scope-born types a slug
29
- matching the current repo's name earns an automatic in-project ranking boost.
30
- Every other facet belongs in frontmatter, where the index can actually read it.
26
+ subdirectory buys you two things: its tokens are indexed as part of the
27
+ asset's **name**, and they are auto-added to `tags` (even when you set `tags`
28
+ explicitly). Search weighs every indexed field equally. Every other facet
29
+ belongs in frontmatter, where the index can actually read it.
31
30
 
32
31
  ## Choose the partition axis by asset TYPE, not per-asset judgment
33
32
 
@@ -38,9 +37,8 @@ differently and bake the wrong guess into an immutable ref. Decide by type:
38
37
  `memory`, `lesson`, `task`, `env`, `secret`. These are born bound to the work
39
38
  in front of you, so the working context *is* the answer — no judgment needed.
40
39
  When the scope is a single repo, use its repo/package name as the slug (as
41
- `git remote`/package.json spell it) — ranking auto-boosts assets whose name
42
- or tags match the current repo. Client/team slugs get no such boost; just
43
- reuse the existing spelling.
40
+ `git remote`/package.json spell it). For a client or team slug, reuse the
41
+ existing spelling.
44
42
  - `memories/projectA/auth-token-refresh`
45
43
  - `lessons/clientX/migration-rollback-gotcha`
46
44
  - `secrets/clientX/api-key`
@@ -58,11 +56,8 @@ differently and bake the wrong guess into an immutable ref. Decide by type:
58
56
  the type root — if any single project would break when it changes, it is
59
57
  scope-born.
60
58
 
61
- **Why reuse types don't take the project axis:** project relevance is already
62
- recovered at query time — AKM blends a per-project usage signal into ranking
63
- (scoped utility, keyed off the current repo, not the asset's path). Free the
64
- scarce path segment for the domain — the *only* handle that co-locates
65
- cross-project reuse.
59
+ **Why reuse types don't take the project axis:** free the scarce path segment
60
+ for the domain — the *only* handle that co-locates cross-project reuse.
66
61
 
67
62
  ## Placement rules
68
63
 
@@ -86,16 +81,15 @@ cross-project reuse.
86
81
  (`acme` vs `acme-corp`) so the prefix does not fragment. Keep the domain
87
82
  vocabulary in `facts/conventions/domains`.
88
83
  - **Off-axis facets go in `tags:`, not a bare field.** The indexed FTS fields are
89
- name, description, tags, hints, and bounded low-weight body content — there is **no `project`
84
+ name, description, tags, hints, and bounded body content — there is **no `project`
90
85
  field**, so `project: projectA` in
91
86
  frontmatter is invisible to search. Put the off-axis facet in `tags` instead
92
87
  (a project-scoped memory adds `tags: [auth]`; a domain-scoped asset genuinely
93
88
  tied to a project adds `tags: [projectA]` sparingly).
94
89
  - **Directory tokens join `tags` on their own.** The scope/domain segments of
95
90
  the path are always auto-added to `tags` — even when you set `tags`
96
- explicitly — so there is no need to restate them; the tag-match ranking
97
- boost fires for the scope token either way. Filename tokens are auto-added
98
- only when `tags` is empty.
91
+ explicitly — so there is no need to restate them. Filename tokens are
92
+ auto-added only when `tags` is empty.
99
93
 
100
94
  ## Renames and evolution
101
95