akm-cli 0.9.30 → 0.9.32

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 (165) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +10 -10
  3. package/ROADMAP.md +112 -0
  4. package/dist/commands/feedback-cli.js +21 -13
  5. package/dist/commands/improve/improve-cli.js +33 -12
  6. package/dist/core/errors.js +1 -0
  7. package/dist/core/file-lock.js +10 -1
  8. package/dist/indexer/usage/usage-events.js +6 -2
  9. package/dist/llm/client.js +33 -3
  10. package/dist/scripts/akm-migrate-node.js +6 -1
  11. package/dist/scripts/akm-migrate.js +6 -1
  12. package/docs/README.md +42 -41
  13. package/docs/agents/README.md +15 -0
  14. package/docs/agents/agent-install.md +14 -0
  15. package/docs/architecture/README.md +67 -0
  16. package/docs/architecture/adapters.md +272 -0
  17. package/docs/architecture/akm-architecture-decision-history.md +2055 -0
  18. package/docs/architecture/akm-core-principles.md +126 -0
  19. package/docs/architecture/architecture.md +579 -0
  20. package/docs/architecture/comparisons/claude-code-vs-akm-workflows-full.md +550 -0
  21. package/docs/architecture/decisions/0001-no-interpolation-attached-structured-context.md +72 -0
  22. package/docs/architecture/decisions/0002-unit-reuse-and-input-hash-scope.md +142 -0
  23. package/docs/architecture/decisions/0003-child-env-allowlist-and-provenance.md +189 -0
  24. package/docs/architecture/decisions/0004-task-input-contract-and-flag-coercion.md +97 -0
  25. package/docs/architecture/decisions/0005-task-result-vocabulary-and-legacy-read-mapping.md +108 -0
  26. package/docs/architecture/decisions/0006-task-source-version-routing.md +124 -0
  27. package/docs/architecture/decisions/0007-workflow-composition-bounds.md +83 -0
  28. package/docs/architecture/decisions/0008-task-binding-normalization.md +88 -0
  29. package/docs/architecture/decisions/0009-child-run-publication-column-parity.md +80 -0
  30. package/docs/architecture/decisions/0010-driver-protocol-cut.md +36 -0
  31. package/docs/architecture/decisions/0011-engine-run-loop-invariants.md +125 -0
  32. package/docs/architecture/decisions/README.md +62 -0
  33. package/docs/architecture/improvement.md +440 -0
  34. package/docs/architecture/internals/classification.md +154 -0
  35. package/docs/architecture/internals/fresh-host-rebuild-runbook.md +40 -0
  36. package/docs/architecture/internals/functional-contract-patterns.md +340 -0
  37. package/docs/architecture/internals/health-advisories.md +110 -0
  38. package/docs/architecture/internals/improve-workflow.md +713 -0
  39. package/docs/architecture/internals/indexing.md +498 -0
  40. package/docs/architecture/internals/registry-network-boundary.md +57 -0
  41. package/docs/architecture/internals/search.md +249 -0
  42. package/docs/architecture/internals/storage-locations.md +943 -0
  43. package/docs/architecture/persisted-data-compat.md +59 -0
  44. package/docs/architecture/reviews/env-secret-access.md +319 -0
  45. package/docs/architecture/runtime-boundary-design.md +153 -0
  46. package/docs/architecture/specs/0.9.0-close-out-plan.md +361 -0
  47. package/docs/architecture/specs/0.9.0-decisions.md +528 -0
  48. package/docs/architecture/specs/0.9.0-docs-code-drift-register.md +745 -0
  49. package/docs/architecture/specs/0.9.0-open-items-register.md +135 -0
  50. package/docs/architecture/specs/0.9.0-public-api-issue-backlog.md +740 -0
  51. package/docs/architecture/specs/0.9.0-release-surface-review.md +636 -0
  52. package/docs/architecture/specs/agent-command-engine-model-design.md +513 -0
  53. package/docs/architecture/specs/akm-0.9.0-bundle-adapter-spec.md +501 -0
  54. package/docs/architecture/specs/akm-0.9.0-ref-grammar-decision.md +291 -0
  55. package/docs/architecture/specs/akm-format-neutral-bundle-workspace-spec.md +2059 -0
  56. package/docs/architecture/specs/di-seams-plan.md +539 -0
  57. package/docs/architecture/specs/driver-protocol-keep-or-cut.md +275 -0
  58. package/docs/architecture/specs/fact-asset-type.md +142 -0
  59. package/docs/architecture/specs/improve-collapse-churn-detector-design.md +634 -0
  60. package/docs/architecture/specs/okf-support.md +148 -0
  61. package/docs/architecture/specs/ref.md +329 -0
  62. package/docs/architecture/specs/reviews/task-workflow-unification/README.md +72 -0
  63. package/docs/architecture/specs/reviews/task-workflow-unification/findings-consolidated.md +152 -0
  64. package/docs/architecture/specs/reviews/task-workflow-unification/library-eval-alternatives.md +352 -0
  65. package/docs/architecture/specs/reviews/task-workflow-unification/library-eval-bunqueue.md +300 -0
  66. package/docs/architecture/specs/reviews/task-workflow-unification/library-eval-openworkflow.md +402 -0
  67. package/docs/architecture/specs/reviews/task-workflow-unification/opus-review-1.md +758 -0
  68. package/docs/architecture/specs/reviews/task-workflow-unification/opus-review-2.md +645 -0
  69. package/docs/architecture/specs/reviews/task-workflow-unification/opus-review-3.md +602 -0
  70. package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-adversarial.md +27 -0
  71. package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-architecture.md +27 -0
  72. package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-coverage.md +74 -0
  73. package/docs/architecture/specs/reviews/task-workflow-unification/panel-r1-security.md +15 -0
  74. package/docs/architecture/specs/reviews/task-workflow-unification/panel-r2-adversarial.md +132 -0
  75. package/docs/architecture/specs/reviews/task-workflow-unification/panel-r2-verification.md +147 -0
  76. package/docs/architecture/specs/stash-conventions-code-spec.md +275 -0
  77. package/docs/architecture/specs/stash-organization-conventions.md +377 -0
  78. package/docs/architecture/specs/task-workflow-format-unification.md +721 -0
  79. package/docs/architecture/specs/workflow-engine-buy-vs-build.md +231 -0
  80. package/docs/architecture/specs/workflow-format-unification.md +437 -0
  81. package/docs/architecture/testing/manual-testing-checklist.md +3420 -0
  82. package/docs/architecture/testing/okf-v0.2-conformance-runbook.md +924 -0
  83. package/docs/architecture/testing/task-workflow-manual-tests.md +1817 -0
  84. package/docs/architecture/testing/testing-workflow.md +446 -0
  85. package/docs/architecture/workflow-engine.md +274 -0
  86. package/docs/guides/README.md +33 -0
  87. package/docs/guides/author-bundles.md +310 -0
  88. package/docs/guides/author-workflows.md +520 -0
  89. package/docs/guides/bundles.md +321 -0
  90. package/docs/guides/capture-knowledge.md +136 -0
  91. package/docs/guides/claude-code-vs-akm-workflows.md +30 -0
  92. package/docs/guides/concepts.md +93 -0
  93. package/docs/guides/discover-and-load.md +158 -0
  94. package/docs/guides/getting-started.md +240 -0
  95. package/docs/guides/improve-the-library.md +246 -0
  96. package/docs/guides/knowledge-management.md +18 -0
  97. package/docs/guides/recipes/headless-install.md +97 -0
  98. package/docs/guides/recipes/website-source.md +28 -0
  99. package/docs/guides/run-workflows.md +279 -0
  100. package/docs/guides/scheduling.md +123 -0
  101. package/docs/guides/use-with-any-agent.md +129 -0
  102. package/docs/guides/wikis.md +81 -0
  103. package/docs/maintainers/README.md +19 -0
  104. package/docs/maintainers/curate-workmap.md +336 -0
  105. package/docs/maintainers/eval.md +3 -0
  106. package/docs/maintainers/local-development.md +108 -0
  107. package/docs/maintainers/release-checklist.md +103 -0
  108. package/docs/migration/README.md +2 -2
  109. package/docs/migration/release-notes/0.0.13.md +1 -1
  110. package/docs/migration/release-notes/0.1.0.md +1 -1
  111. package/docs/migration/release-notes/0.2.0.md +1 -1
  112. package/docs/migration/release-notes/0.3.0.md +1 -1
  113. package/docs/migration/release-notes/0.5.0.md +1 -1
  114. package/docs/migration/release-notes/0.6.0.md +1 -1
  115. package/docs/migration/release-notes/0.7.0.md +1 -1
  116. package/docs/migration/release-notes/0.7.3.md +2 -2
  117. package/docs/migration/release-notes/0.7.4.md +1 -1
  118. package/docs/migration/release-notes/0.7.5.md +1 -1
  119. package/docs/migration/release-notes/0.8.0.md +1 -1
  120. package/docs/migration/release-notes/0.9.0.md +1 -1
  121. package/docs/migration/release-notes/0.9.14.md +1 -1
  122. package/docs/migration/release-notes/0.9.15.md +1 -1
  123. package/docs/migration/release-notes/0.9.16.md +1 -1
  124. package/docs/migration/release-notes/0.9.17.md +1 -1
  125. package/docs/migration/release-notes/0.9.19.md +1 -1
  126. package/docs/migration/release-notes/0.9.2.md +1 -1
  127. package/docs/migration/release-notes/README.md +27 -31
  128. package/docs/migration/v0.5-to-v0.6.md +759 -0
  129. package/docs/migration/v0.8-to-v0.9.md +1 -1
  130. package/docs/plans/0.9.2-agent-command-workflow-plan.md +423 -0
  131. package/docs/posts/README.md +30 -0
  132. package/docs/posts/akm-command-tour.md +495 -0
  133. package/docs/posts/akm-logo-sized.webp +0 -0
  134. package/docs/posts/belief-aware-memory-12.md +139 -0
  135. package/docs/posts/config-v2-lessons-16.md +191 -0
  136. package/docs/posts/improve-loop-autonomous-10.md +225 -0
  137. package/docs/posts/improve-pipeline-debugging-0803.md +179 -0
  138. package/docs/posts/intro-part-01.md +76 -0
  139. package/docs/posts/intro-part-02.md +221 -0
  140. package/docs/posts/intro-part-03.md +190 -0
  141. package/docs/posts/intro-part-04.md +221 -0
  142. package/docs/posts/llm-mode-reflect-13.md +166 -0
  143. package/docs/posts/openpalm-built-on-akm.md +86 -0
  144. package/docs/posts/progressive-disclosure-06.md +129 -0
  145. package/docs/posts/proposal-safety-net-15.md +112 -0
  146. package/docs/posts/release-0.5.0.md +247 -0
  147. package/docs/posts/release-0.6.0.md +117 -0
  148. package/docs/posts/release-0.7.0.md +159 -0
  149. package/docs/posts/release-0.8.0.md +332 -0
  150. package/docs/posts/scattered-skills-05.md +100 -0
  151. package/docs/posts/task-assets-persistent-workflows-11.md +222 -0
  152. package/docs/posts/team-skills-07.md +198 -0
  153. package/docs/posts/wiki-agent-knowledge-08.md +223 -0
  154. package/docs/posts/workflows-vaults-09.md +321 -0
  155. package/docs/product-surface.md +54 -0
  156. package/docs/reference/README.md +1 -2
  157. package/docs/reference/asset-types.md +168 -0
  158. package/docs/reference/cli.md +5 -6
  159. package/docs/reference/data-and-telemetry.md +6 -0
  160. package/docs/reference/env-and-secrets.md +126 -0
  161. package/docs/reference/memory.md +124 -0
  162. package/docs/reference/refs.md +156 -0
  163. package/docs/reference/registry.md +489 -0
  164. package/docs/reference/website-sources.md +141 -0
  165. package/package.json +12 -15
package/CHANGELOG.md CHANGED
@@ -6,6 +6,46 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.9.32] - 2026-10-13
10
+
11
+ A patch release: `akm feedback` no longer reports success for feedback it could not store.
12
+
13
+ ### Fixed
14
+
15
+ - **`akm feedback` fails when it cannot record the feedback.** When `state.db` was read-only to the process, as it is
16
+ inside an agent sandbox that only allows writes in the workspace, the feedback row was silently dropped and the
17
+ command still printed `"ok": true`. It now fails with a `DATA_DIR_NOT_WRITABLE` config error (exit 78) and records
18
+ nothing, so an agent knows its feedback was lost. Add akm's data directory to the sandbox's writable paths.
19
+
20
+ ## [0.9.31] - 2026-10-09
21
+
22
+ A patch release: concurrent config writes no longer lose a change, OpenAI's reasoning models work as `llm`
23
+ engines, `akm improve --require-engines` probes send API keys, and the npm package's docs links resolve.
24
+
25
+ ### Fixed
26
+
27
+ - **`akm improve --require-engines` sends each engine's API key with its probe.** The probe used the engine's
28
+ connection as resolved, where the key is still a symbolic reference that a dispatch reads only when it sends a
29
+ request, so every keyed endpoint (OpenAI, a gateway that enforces auth) answered 401 and the run stopped with
30
+ "engine completion path is not reachable". The probe now builds the connection as a dispatch does; a required key
31
+ that is missing is reported as that engine's failure.
32
+ - **OpenAI's reasoning models (gpt-5.x) work as `llm` engines.** They answer 400 to any `temperature` but the
33
+ default and to `max_tokens`, so every akm model call failed on them. Like the existing fallback for the
34
+ thinking-control fields, a 400 that names `temperature` is retried once without it, and one that asks for
35
+ `max_completion_tokens` is retried with the cap under that name; the connection keeps the working shape for
36
+ the rest of the process.
37
+ - **Every relative link in the npm package's docs resolves again.** #1118 made `docs/README.md`,
38
+ `docs/migration/README.md` and `docs/reference/cli.md` link by relative path to guides and pages the package did
39
+ not ship. npm always ships a folder's `README.md`, so those links were dead for npm users. The package now ships
40
+ the user-facing `docs/` folders (`agents`, `architecture`, `guides`, `integration`, `maintainers`, `migration`,
41
+ `posts`, `reference`), `docs/product-surface.md`, the one plan they link to, and `ROADMAP.md`. `docs/plans` stays
42
+ out. The link check skips link syntax inside code, which is an example, not a link.
43
+ - **Concurrent `akm config set` runs no longer lose each other's changes.** A lock file was created empty and its
44
+ owner's PID written a moment later; a process that looked in that gap read no PID, took the live lock for an
45
+ abandoned one and removed it, so two writers ran at once and one change was lost. A lock now appears with its
46
+ owner's PID already in it. Every akm lock file (config, index writer, scheduler, `akm.lock`, run, extract, secret)
47
+ is created this way.
48
+
9
49
  ## [0.9.30] - 2026-10-09
10
50
 
11
51
  A patch release: `akm upgrade` acts only on the `akm-opencode` spec OpenCode's config names.
package/README.md CHANGED
@@ -55,7 +55,7 @@ akm show scripts/deploy.sh # View details and run command
55
55
  - **One library for every agent** -- Use the same capability library from Claude Code, OpenCode, Cursor, Aider, Windsurf, or any assistant that can run shell commands.
56
56
  - **Load only what the task needs** -- Search or curate a shortlist, then load full content by ref. No giant startup prompt is required.
57
57
  - **Package complete capabilities** -- Install and share bundles containing skills, scripts, workflows, agents, instructions, memories, and knowledge -- not just prompt snippets.
58
- - **Improve through evidence, with review** -- Feedback influences retrieval and produces diffable proposals. Changes remain reviewable and target only writable bundles.
58
+ - **Improve through evidence, with review** -- Feedback re-ranks assets right away. A negative report can carry an exact text fix, and `akm improve` repairs metadata; every edit lands as a diffable proposal you accept, reject or revert, and only writable bundles are changed.
59
59
  - **Turn knowledge into repeatable work** -- Run persisted workflows with dispatch, gates, retries, budgets, and resume instead of reconstructing a process from prose every session.
60
60
 
61
61
  akm retrieves every supported capability type. It directly orchestrates
@@ -80,16 +80,16 @@ Or generate it directly: `akm help agents >> AGENTS.md`
80
80
 
81
81
  ## Documentation
82
82
 
83
- Full docs, CLI reference, and guides are available on [GitHub](https://github.com/itlackey/akm):
83
+ Full docs, CLI reference, and guides are at **[akm.fwdslsh.dev](https://akm.fwdslsh.dev/)**:
84
84
 
85
- - [Getting Started](https://github.com/itlackey/akm/blob/main/docs/guides/getting-started.md)
86
- - [Supported Formats](https://github.com/itlackey/akm/blob/main/docs/reference/supported-formats.md)
87
- - [CLI Reference](https://github.com/itlackey/akm/blob/main/docs/reference/cli.md)
88
- - [Task v3 Reference](https://github.com/itlackey/akm/blob/main/docs/reference/tasks.md)
89
- - [0.9.2 Migration Guide](https://github.com/itlackey/akm/blob/main/docs/migration/v0.9.1-to-v0.9.2.md)
90
- - [Configuration](https://github.com/itlackey/akm/blob/main/docs/reference/configuration.md)
91
- - [Bundle Authoring Guide](https://github.com/itlackey/akm/blob/main/docs/guides/author-bundles.md)
92
- - [Registry](https://github.com/itlackey/akm/blob/main/docs/reference/registry.md)
85
+ - [Getting Started](https://akm.fwdslsh.dev/docs/guides/getting-started/)
86
+ - [Supported Formats](https://akm.fwdslsh.dev/docs/reference/supported-formats/)
87
+ - [CLI Reference](https://akm.fwdslsh.dev/docs/reference/cli/)
88
+ - [Task v3 Reference](https://akm.fwdslsh.dev/docs/reference/tasks/)
89
+ - [0.9.2 Migration Guide](https://akm.fwdslsh.dev/docs/migration/v0.9.1-to-v0.9.2/)
90
+ - [Configuration](https://akm.fwdslsh.dev/docs/reference/configuration/)
91
+ - [Bundle Authoring Guide](https://akm.fwdslsh.dev/docs/guides/author-bundles/)
92
+ - [Registry](https://akm.fwdslsh.dev/docs/reference/registry/)
93
93
 
94
94
  ## License
95
95
 
package/ROADMAP.md ADDED
@@ -0,0 +1,112 @@
1
+ # Roadmap
2
+
3
+ This page outlines the high-level focus for the next two releases.
4
+
5
+ It is intentionally directional rather than exhaustive. Detailed command,
6
+ schema, and migration behavior still lives in the release notes, the
7
+ migration guides, and [STABILITY.md](STABILITY.md), which classifies
8
+ today's public contract by surface. The internal design specs behind the
9
+ 0.9.0 architecture consolidation live under
10
+ [`docs/architecture/specs/`](docs/architecture/specs/).
11
+
12
+ ## 0.8 Foundation
13
+
14
+ Release `0.8.0` already established much of the product shape that `0.9` and
15
+ `1.0` build on.
16
+
17
+ - The CLI and config surface were substantially redesigned.
18
+ - `akm improve` became the main self-improvement and maintenance entry point.
19
+ - Task assets, proposal workflows, health reporting, and agent command-building
20
+ all landed as first-class capabilities.
21
+ - Storage, indexing, graph extraction, and migration paths were tightened to
22
+ support the v1 contract freeze.
23
+
24
+ That means the next two releases are less about inventing a new direction and
25
+ more about hardening, simplifying, and freezing what `0.8.0` put in place.
26
+
27
+ ## 0.9
28
+
29
+ Release `0.9` is the architecture-consolidation release ahead of the `1.0`
30
+ contract freeze. It replaces the flat asset-type registry with a
31
+ **bundle / adapter** model and settles the durable contracts `1.0` will freeze.
32
+
33
+ - **One canonical ref grammar.** Refs are `[bundle//]conceptId` — a
34
+ subdir-qualified concept id such as `skills/code-review` or `memories/vpn-note`
35
+ — replacing the pre-`0.9` `type:name` spelling. Durable state stores the
36
+ fully-qualified form; the short bundle-omitted form is input sugar. This is a
37
+ deliberate, one-time break, taken pre-`1.0` while it is still cheap.
38
+ - **Format-neutral bundles.** Installed sources are bundles, each recognized by
39
+ a built-in adapter (Agent Skills, Claude/OpenCode commands and agents,
40
+ knowledge, YAML workflows, tasks, env/secret files, scripts, OKF and LLM-wiki
41
+ knowledge bases) instead of a closed asset-type list.
42
+ - **Storage consolidation.** The durable databases collapse from four to three
43
+ (`state.db` / `index.db` / a separate `logs.db`); config migrates from the
44
+ flat `stashDir` / `sources` / `installed` keys to `bundles` / `defaultBundle`.
45
+ The one-time cutover is journaled and crash-resumable via `akm migrate apply`,
46
+ with a verified backup taken first.
47
+ - **Debt paydown.** The improve / proposal / workflow internals are decomposed
48
+ onto one file-change transaction model and an ambient run context, the
49
+ deprecated `0.8` CLI aliases are removed, and the `vault` asset type is
50
+ retired in favor of `env` / `secret`.
51
+
52
+ In short: `0.9` reduces ambiguity, pays down pre-`1.0` debt, and arrives at a
53
+ small, durable core ready for the `1.0` freeze.
54
+
55
+ ### The 0.9.x series
56
+
57
+ `0.9.x` is a series of refactoring and clean-up releases. The intent is to
58
+ have **all technical debt paid off and all planned breaking changes handled
59
+ before the 0.10.x series begins**. To get there quickly, 0.9.x patch releases
60
+ may include breaking changes (see [STABILITY.md](STABILITY.md)); every
61
+ break ships with a CHANGELOG migration note.
62
+
63
+ Alongside the bug fixes and compatibility improvements, `0.9.x` also focuses
64
+ on **tuning the improve processes** — reflect, distill, consolidate, memory
65
+ inference, graph extraction, and proactive maintenance — and **settling the
66
+ final design of those features** so the `0.10.0` release starts from a
67
+ finished improve surface rather than one still being reshaped.
68
+
69
+ ## 0.10
70
+
71
+ The `0.10.x` series is focused on **bug fixes and tuning** on top of the
72
+ debt-free 0.9 core — no new architecture. With the clean-up done, 0.10.x
73
+ attempts to return to the normal semver discipline: breaking changes only in
74
+ major and minor releases, never in patches.
75
+
76
+ ## 0.X
77
+
78
+ There may be other minor releases between 0.10.x and 1.0.0. The goal is to minimize the number of minor releases after 0.10.x, and they will only be used for addressing breaking changes that are justifiable before the 1.0.0 release.
79
+
80
+ ## 1.0
81
+
82
+ Release `1.0` is focused on freezing the public contract and shipping the first
83
+ stable ecosystem layer around that core.
84
+
85
+ - Freeze the supported source model, the `[bundle//]conceptId` ref format,
86
+ search behavior, and write-target rules — the surfaces
87
+ [STABILITY.md](STABILITY.md)'s own 1.0 contract-freeze section
88
+ already names — and settle the surfaces it currently classifies as
89
+ Evolving or Experimental.
90
+ - Carry the `0.8` agent and improvement workflow forward as a stable,
91
+ documented product surface rather than a collection of pre-release features.
92
+ - Ship an official akm SDK for building integrations against the stable v1
93
+ contract instead of reverse-engineering CLI behavior.
94
+ - Ship in-process akm plugins for Claude Code and OpenCode, built on that SDK,
95
+ so those tools can integrate more deeply without changing the CLI-first core
96
+ model that akm itself relies on.
97
+ - Keep the CLI as the canonical foundation so the SDK and plugin story extends
98
+ the existing system rather than forking it into separate integration models.
99
+
100
+ In short: `1.0` is where akm stops being only a strong CLI and becomes a stable
101
+ platform for both direct use and deeper tool integration.
102
+
103
+ ## Scope Notes
104
+
105
+ - The CLI remains the foundation in both releases.
106
+ - `0.9` prioritizes hardening over expansion.
107
+ - `1.0` adds the SDK and in-process plugin story on top of a frozen core, not
108
+ instead of it.
109
+ - The roadmap assumes the `0.8.0` redesign is the baseline; later releases are
110
+ about stabilizing and extending that baseline, not replacing it.
111
+ - Specific features may move between releases as implementation and testing
112
+ shake out, but the themes above are the intended direction.
@@ -13,16 +13,16 @@ import { parseFrontmatter, parseFrontmatterBlock, spliceFrontmatterLine } from "
13
13
  import { conceptIdFromTypeName, parseRefInput, typeNameFromConceptId } from "../core/asset/resolve-ref.js";
14
14
  import { isWithin, resolveStashDir, safeRealpath, writeFileAtomic } from "../core/common.js";
15
15
  import { loadConfig } from "../core/config/config.js";
16
- import { NotFoundError, UsageError } from "../core/errors.js";
16
+ import { ConfigError, NotFoundError, UsageError } from "../core/errors.js";
17
17
  import { appendEvent } from "../core/events.js";
18
18
  import { resolveMutationTarget } from "../core/mutation-target.js";
19
19
  import { isPathAbsent } from "../core/path-access.js";
20
20
  import { getDbPath } from "../core/paths.js";
21
- import { withStateDb } from "../core/state-db.js";
21
+ import { getStateDbPath, withStateDb } from "../core/state-db.js";
22
22
  import { warn } from "../core/warn.js";
23
23
  import { withWriteTargetMutation } from "../core/write-source.js";
24
24
  import { resolveSourceEntries } from "../indexer/search/search-source.js";
25
- import { countFeedbackSignals, insertUsageEvent, resolveUsageEventSource } from "../indexer/usage/usage-events.js";
25
+ import { countFeedbackSignals, insertUsageEventStrict, resolveUsageEventSource } from "../indexer/usage/usage-events.js";
26
26
  import { resolveSourcesForOrigin } from "../registry/origin-resolve.js";
27
27
  import { closeDatabase, openExistingDatabase } from "../storage/repositories/index-connection.js";
28
28
  import { findEntryIdByRef, getEntryFilePathById, getItemRefById, } from "../storage/repositories/index-entries-repository.js";
@@ -157,8 +157,9 @@ function buildLessonStrengthUpdate(rawBytes, feedbackRef) {
157
157
  * `task`, `audit`, `unknown`) — an anti-self-reinforcement guard that stops an
158
158
  * agent or automated pipeline from boosting its own picks. This is by design
159
159
  * and must not be removed; R-034 only asks that the skip be surfaced to the
160
- * caller rather than passing silently. Best-effort: a utility-update failure
161
- * never fails the feedback record.
160
+ * caller rather than passing silently. A failed usage-event write fails the
161
+ * command (DATA_DIR_NOT_WRITABLE); a utility-update failure never fails the
162
+ * feedback record.
162
163
  */
163
164
  function recordFeedbackUsage(indexDb, entryId, durableEntryRef, signal, metadataStr) {
164
165
  let utilityResult;
@@ -166,14 +167,21 @@ function recordFeedbackUsage(indexDb, entryId, durableEntryRef, signal, metadata
166
167
  let rankingUpdateSkippedReason;
167
168
  const eventSource = resolveUsageEventSource();
168
169
  withStateDb((stateDb) => {
169
- insertUsageEvent(stateDb, {
170
- event_type: "feedback",
171
- entry_ref: durableEntryRef,
172
- entry_id: entryId,
173
- signal,
174
- metadata: metadataStr,
175
- source: eventSource,
176
- });
170
+ // The usage event IS the feedback record, so a failed write fails the command
171
+ // instead of reporting ok while nothing was stored.
172
+ try {
173
+ insertUsageEventStrict(stateDb, {
174
+ event_type: "feedback",
175
+ entry_ref: durableEntryRef,
176
+ entry_id: entryId,
177
+ signal,
178
+ metadata: metadataStr,
179
+ source: eventSource,
180
+ });
181
+ }
182
+ catch (err) {
183
+ throw new ConfigError(`Feedback was not recorded: could not write ${getStateDbPath()} (${err instanceof Error ? err.message : String(err)}).`, "DATA_DIR_NOT_WRITABLE");
184
+ }
177
185
  if (eventSource !== "user") {
178
186
  rankingUpdateSkippedReason =
179
187
  `feedback source is "${eventSource}", not "user" — ranking updates only apply to user-attributed ` +
@@ -16,7 +16,8 @@ import { clearLogFile, setLogFile, warn } from "../../core/warn.js";
16
16
  import { resolveWriteTarget } from "../../core/write-source.js";
17
17
  import { DEFAULT_LLM_TIMEOUT_MS } from "../../integrations/agent/config.js";
18
18
  import { defaultWhich } from "../../integrations/agent/detect.js";
19
- import { collectEngineCredentialValues } from "../../integrations/agent/engine-resolution.js";
19
+ import { collectEngineCredentialValues, materializeLlmConnection, } from "../../integrations/agent/engine-resolution.js";
20
+ import { llmUseFromRunner, sdkFallbackUseFromRunner } from "../../integrations/agent/runner.js";
20
21
  import { probeLlmReachable } from "../../llm/client.js";
21
22
  import { getOutputMode } from "../../output/context.js";
22
23
  import { deliverRendered } from "../../output/html-render.js";
@@ -81,18 +82,36 @@ function collectRequiredEngineTargets(plan) {
81
82
  const runners = Object.entries(plan.processes)
82
83
  .flatMap(([processName, process]) => (process.runner ? [[processName, process.runner]] : []))
83
84
  .concat(plan.triageJudgment ? [["triage.judgment", plan.triageJudgment]] : []);
84
- return runners.map(([processName, runner]) => ({
85
- process: processName,
86
- engine: runner.engine,
87
- ...(runner.kind === "llm" ? { connection: probeConnection(runner) } : { bin: runner.profile.bin }),
88
- ...(runner.kind === "sdk" && runner.fallbackConnection
89
- ? { connection: probeConnection({ connection: runner.fallbackConnection, timeoutMs: runner.fallbackTimeoutMs }) }
90
- : {}),
91
- }));
85
+ return runners.map(([processName, runner]) => {
86
+ // The runner's own timeout, else the connection's (sdkFallbackUseFromRunner already does this).
87
+ const use = runner.kind === "llm"
88
+ ? {
89
+ ...llmUseFromRunner(runner),
90
+ timeoutMs: runner.timeoutMs !== undefined ? runner.timeoutMs : (runner.connection.timeoutMs ?? null),
91
+ }
92
+ : runner.kind === "sdk"
93
+ ? sdkFallbackUseFromRunner(runner)
94
+ : undefined;
95
+ return {
96
+ process: processName,
97
+ engine: runner.engine,
98
+ ...(runner.kind === "llm" ? {} : { bin: runner.profile.bin }),
99
+ ...(use ? probeConnection(use) : {}),
100
+ };
101
+ });
92
102
  }
93
- /** The resolved engine keeps its request timeout beside the connection (the runtime merges it in); the probe needs it on the connection. */
94
- function probeConnection(runner) {
95
- return runner.timeoutMs !== undefined ? { ...runner.connection, timeoutMs: runner.timeoutMs } : runner.connection;
103
+ /**
104
+ * The connection the probe sends, built as a dispatch builds it: the runner
105
+ * keeps its credential symbolic, so the key is read and injected here, or the
106
+ * probe would go out without one.
107
+ */
108
+ function probeConnection(use) {
109
+ try {
110
+ return { connection: materializeLlmConnection(use) };
111
+ }
112
+ catch (err) {
113
+ return { connection: use.connection, error: err instanceof Error ? err.message : String(err) };
114
+ }
96
115
  }
97
116
  /**
98
117
  * The bound on `--require-engines`' probe: the connection's own request
@@ -133,6 +152,8 @@ export async function assertRequiredEnginesReachable(plan, probeReachable = (con
133
152
  }
134
153
  if (!target.connection)
135
154
  return { ...target, reach: { reachable: true }, latencyMs: 0 };
155
+ if (target.error)
156
+ return { ...target, reach: { reachable: false, error: target.error }, latencyMs: 0 };
136
157
  return { ...target, ...(await probeConnectionOnce(target.connection)) };
137
158
  }));
138
159
  const unreachable = probed.filter((item) => !item.reach.reachable);
@@ -10,6 +10,7 @@ const CONFIG_HINTS = {
10
10
  STASH_DIR_NOT_A_DIRECTORY: "The configured default bundle path exists but isn't a directory. Update it to point at a folder.",
11
11
  STASH_DIR_UNREADABLE: "Check the path exists and your user has read permission, or update the default bundle path.",
12
12
  DATA_DIR_UNREADABLE: "The data directory is not readable by the user running akm. Check its owner and mode, or point AKM_DATA_DIR / XDG_DATA_HOME somewhere this user owns.",
13
+ DATA_DIR_NOT_WRITABLE: "Nothing was recorded. Make the akm data directory writable for this process (for an agent sandbox, add it to the writable paths), or point AKM_DATA_DIR / XDG_DATA_HOME somewhere this user can write, then retry.",
13
14
  INDEX_SCHEMA_INCOMPATIBLE: "Run `akm index --full` to rebuild the derived index from the currently materialized sources.",
14
15
  LLM_NOT_CONFIGURED: 'Run `akm setup` or configure an `engines` entry with `kind: "llm"`, then select it with `defaults.llmEngine`.',
15
16
  TEST_ISOLATION_MISSING: "Under bun test, when AKM_BUNDLE_DIR is set you MUST also set XDG_DATA_HOME (or AKM_DATA_DIR) and XDG_STATE_HOME (or AKM_STATE_DIR) to temp directories so the test does not touch the developer's real ~/.local/share/akm or ~/.local/state/akm.",
@@ -51,14 +51,23 @@ function releaseLockRaw(lockPath) {
51
51
  }
52
52
  /** Create the lock file atomically; `undefined` when another holder already has it. */
53
53
  export function tryAcquireLockSync(lockPath, payload) {
54
+ // Write the payload under a private name, then hard-link it into place: the
55
+ // link is the exclusive step and the lock never exists without its payload.
56
+ // A `wx` write creates the file empty and fills it in afterwards; a contender
57
+ // probing in that gap reads no pid, calls the live lock stale and reclaims it.
58
+ const tmpPath = `${lockPath}.tmp-${process.pid}-${randomUUID()}`;
59
+ fs.writeFileSync(tmpPath, payload, { flag: "wx" });
54
60
  try {
55
- fs.writeFileSync(lockPath, payload, { flag: "wx" });
61
+ fs.linkSync(tmpPath, lockPath);
56
62
  }
57
63
  catch (err) {
58
64
  if (err.code === "EEXIST")
59
65
  return undefined;
60
66
  throw err;
61
67
  }
68
+ finally {
69
+ releaseLockRaw(tmpPath);
70
+ }
62
71
  let snapshot;
63
72
  try {
64
73
  snapshot = readLockSnapshot(lockPath);
@@ -54,14 +54,18 @@ export function ensureUsageEventsSchema(db) {
54
54
  */
55
55
  export function insertUsageEvent(db, event) {
56
56
  try {
57
- db.prepare(`INSERT INTO usage_events (event_type, query, entry_id, entry_ref, signal, metadata, source)
58
- VALUES (?, ?, ?, ?, ?, ?, ?)`).run(event.event_type, event.query ?? null, event.entry_id ?? null, event.entry_ref ?? null, event.signal ?? null, event.metadata ?? null, event.source ?? "unknown");
57
+ insertUsageEventStrict(db, event);
59
58
  }
60
59
  catch (error) {
61
60
  rethrowIfTestIsolationError(error);
62
61
  /* fire-and-forget: silently ignore errors */
63
62
  }
64
63
  }
64
+ /** Insert a usage event and let a failed write throw, for callers whose record is the event itself. */
65
+ export function insertUsageEventStrict(db, event) {
66
+ db.prepare(`INSERT INTO usage_events (event_type, query, entry_id, entry_ref, signal, metadata, source)
67
+ VALUES (?, ?, ?, ?, ?, ?, ?)`).run(event.event_type, event.query ?? null, event.entry_id ?? null, event.entry_ref ?? null, event.signal ?? null, event.metadata ?? null, event.source ?? "unknown");
68
+ }
65
69
  // ── Query ────────────────────────────────────────────────────────────────────
66
70
  /**
67
71
  * Aggregate positive/negative feedback counts for a single entry.
@@ -227,6 +227,12 @@ const jsonSchemaUnsupportedConnections = new Set();
227
227
  * `enable_thinking`): a strict hosted API answers 400 `Unknown parameter`.
228
228
  */
229
229
  const thinkingControlRejectedConnections = new Set();
230
+ /**
231
+ * And for OpenAI's reasoning models: they answer 400 to any `temperature` but
232
+ * the default, and to `max_tokens`, naming `max_completion_tokens` instead.
233
+ */
234
+ const temperatureRejectedConnections = new Set();
235
+ const maxCompletionTokensConnections = new Set();
230
236
  function connectionKey(config) {
231
237
  return `${config.endpoint}|${config.model}`;
232
238
  }
@@ -234,6 +240,8 @@ function connectionKey(config) {
234
240
  export function _resetEndpointSupportTrackersForTests() {
235
241
  jsonSchemaUnsupportedConnections.clear();
236
242
  thinkingControlRejectedConnections.clear();
243
+ temperatureRejectedConnections.clear();
244
+ maxCompletionTokensConnections.clear();
237
245
  }
238
246
  /**
239
247
  * Whether this connection has already been proven, this process, not to
@@ -263,6 +271,10 @@ function isRejectedRequest(err) {
263
271
  }
264
272
  /** A strict API names the field it does not know: `Unknown parameter: 'chat_template_kwargs'.` */
265
273
  const THINKING_FIELD_NAMED = /chat_template_kwargs|enable_thinking/;
274
+ /** `Unsupported value: 'temperature' does not support 0 with this model. Only the default (1) value is supported.` */
275
+ const TEMPERATURE_NAMED = /'temperature'/;
276
+ /** `Unsupported parameter: 'max_tokens' is not supported with this model. Use 'max_completion_tokens' instead.` */
277
+ const MAX_TOKENS_REPLACED = /max_completion_tokens/;
266
278
  /**
267
279
  * A single chat-completion attempt: one HTTP request/response cycle, with an
268
280
  * inline fallback-once when a schema was requested and the provider 4xx's
@@ -275,7 +287,10 @@ const THINKING_FIELD_NAMED = /chat_template_kwargs|enable_thinking/;
275
287
  *
276
288
  * The thinking-control fields get the same treatment (#1045): a 4xx that
277
289
  * names `chat_template_kwargs` or `enable_thinking` is answered by one retry
278
- * without them, which may in turn fall back without the schema.
290
+ * without them, which may in turn fall back without the schema. A 4xx that
291
+ * names `temperature`, or asks for `max_completion_tokens`, is answered the
292
+ * same way: the connection stops sending `temperature`, or sends the token cap
293
+ * as `max_completion_tokens`, for the rest of the process.
279
294
  */
280
295
  async function chatCompletionAttempt(config, messages, options, timeoutMs, includeThinking = (options?.enableThinking ?? config.enableThinking) !== undefined &&
281
296
  !isThinkingControlKnownRejected(config)) {
@@ -286,6 +301,17 @@ async function chatCompletionAttempt(config, messages, options, timeoutMs, inclu
286
301
  catch (err) {
287
302
  if (!isRejectedRequest(err))
288
303
  throw err;
304
+ const key = connectionKey(config);
305
+ if (TEMPERATURE_NAMED.test(err.message) && !temperatureRejectedConnections.has(key)) {
306
+ warnVerbose(`[akm] LLM rejected temperature (${err.statusCode}); retrying once without it: ${err.message}`);
307
+ temperatureRejectedConnections.add(key);
308
+ return chatCompletionAttempt(config, messages, options, timeoutMs, includeThinking);
309
+ }
310
+ if (MAX_TOKENS_REPLACED.test(err.message) && !maxCompletionTokensConnections.has(key)) {
311
+ warnVerbose(`[akm] LLM rejected max_tokens (${err.statusCode}); retrying once with max_completion_tokens: ${err.message}`);
312
+ maxCompletionTokensConnections.add(key);
313
+ return chatCompletionAttempt(config, messages, options, timeoutMs, includeThinking);
314
+ }
289
315
  if (includeThinking && THINKING_FIELD_NAMED.test(err.message)) {
290
316
  warnVerbose(`[akm] LLM rejected chat_template_kwargs/enable_thinking (${err.statusCode}); retrying once without them: ${err.message}`);
291
317
  const fallback = await chatCompletionAttempt(config, messages, options, timeoutMs, false);
@@ -344,11 +370,15 @@ async function chatCompletionAttemptOnce(config, messages, options, timeoutMs, i
344
370
  ? {}
345
371
  : { chat_template_kwargs: { enable_thinking: resolvedEnableThinking }, enable_thinking: resolvedEnableThinking };
346
372
  const reasoningEffortParams = config.reasoningEffort === undefined ? {} : { reasoning_effort: config.reasoningEffort };
373
+ const key = connectionKey(config);
374
+ const maxTokensField = maxCompletionTokensConnections.has(key) ? "max_completion_tokens" : "max_tokens";
347
375
  const requestBody = JSON.stringify({
348
376
  model: config.model,
349
377
  messages,
350
- temperature: options?.temperature ?? config.temperature ?? 0.3,
351
- ...(resolvedMaxTokens !== undefined ? { max_tokens: resolvedMaxTokens } : {}),
378
+ ...(temperatureRejectedConnections.has(key)
379
+ ? {}
380
+ : { temperature: options?.temperature ?? config.temperature ?? 0.3 }),
381
+ ...(resolvedMaxTokens !== undefined ? { [maxTokensField]: resolvedMaxTokens } : {}),
352
382
  ...config.extraParams,
353
383
  ...responseFormat,
354
384
  ...thinkingParams,
@@ -25664,6 +25664,7 @@ var CONFIG_HINTS = {
25664
25664
  STASH_DIR_NOT_A_DIRECTORY: "The configured default bundle path exists but isn't a directory. Update it to point at a folder.",
25665
25665
  STASH_DIR_UNREADABLE: "Check the path exists and your user has read permission, or update the default bundle path.",
25666
25666
  DATA_DIR_UNREADABLE: "The data directory is not readable by the user running akm. Check its owner and mode, or point AKM_DATA_DIR / XDG_DATA_HOME somewhere this user owns.",
25667
+ DATA_DIR_NOT_WRITABLE: "Nothing was recorded. Make the akm data directory writable for this process (for an agent sandbox, add it to the writable paths), or point AKM_DATA_DIR / XDG_DATA_HOME somewhere this user can write, then retry.",
25667
25668
  INDEX_SCHEMA_INCOMPATIBLE: "Run `akm index --full` to rebuild the derived index from the currently materialized sources.",
25668
25669
  LLM_NOT_CONFIGURED: 'Run `akm setup` or configure an `engines` entry with `kind: "llm"`, then select it with `defaults.llmEngine`.',
25669
25670
  TEST_ISOLATION_MISSING: "Under bun test, when AKM_BUNDLE_DIR is set you MUST also set XDG_DATA_HOME (or AKM_DATA_DIR) and XDG_STATE_HOME (or AKM_STATE_DIR) to temp directories so the test does not touch the developer's real ~/.local/share/akm or ~/.local/state/akm.",
@@ -36842,12 +36843,16 @@ function releaseLockRaw(lockPath) {
36842
36843
  } catch {}
36843
36844
  }
36844
36845
  function tryAcquireLockSync(lockPath, payload) {
36846
+ const tmpPath = `${lockPath}.tmp-${process.pid}-${randomUUID()}`;
36847
+ fs7.writeFileSync(tmpPath, payload, { flag: "wx" });
36845
36848
  try {
36846
- fs7.writeFileSync(lockPath, payload, { flag: "wx" });
36849
+ fs7.linkSync(tmpPath, lockPath);
36847
36850
  } catch (err) {
36848
36851
  if (err.code === "EEXIST")
36849
36852
  return;
36850
36853
  throw err;
36854
+ } finally {
36855
+ releaseLockRaw(tmpPath);
36851
36856
  }
36852
36857
  let snapshot;
36853
36858
  try {
@@ -24992,6 +24992,7 @@ var CONFIG_HINTS = {
24992
24992
  STASH_DIR_NOT_A_DIRECTORY: "The configured default bundle path exists but isn't a directory. Update it to point at a folder.",
24993
24993
  STASH_DIR_UNREADABLE: "Check the path exists and your user has read permission, or update the default bundle path.",
24994
24994
  DATA_DIR_UNREADABLE: "The data directory is not readable by the user running akm. Check its owner and mode, or point AKM_DATA_DIR / XDG_DATA_HOME somewhere this user owns.",
24995
+ DATA_DIR_NOT_WRITABLE: "Nothing was recorded. Make the akm data directory writable for this process (for an agent sandbox, add it to the writable paths), or point AKM_DATA_DIR / XDG_DATA_HOME somewhere this user can write, then retry.",
24995
24996
  INDEX_SCHEMA_INCOMPATIBLE: "Run `akm index --full` to rebuild the derived index from the currently materialized sources.",
24996
24997
  LLM_NOT_CONFIGURED: 'Run `akm setup` or configure an `engines` entry with `kind: "llm"`, then select it with `defaults.llmEngine`.',
24997
24998
  TEST_ISOLATION_MISSING: "Under bun test, when AKM_BUNDLE_DIR is set you MUST also set XDG_DATA_HOME (or AKM_DATA_DIR) and XDG_STATE_HOME (or AKM_STATE_DIR) to temp directories so the test does not touch the developer's real ~/.local/share/akm or ~/.local/state/akm.",
@@ -36170,12 +36171,16 @@ function releaseLockRaw(lockPath) {
36170
36171
  } catch {}
36171
36172
  }
36172
36173
  function tryAcquireLockSync(lockPath, payload) {
36174
+ const tmpPath = `${lockPath}.tmp-${process.pid}-${randomUUID()}`;
36175
+ fs7.writeFileSync(tmpPath, payload, { flag: "wx" });
36173
36176
  try {
36174
- fs7.writeFileSync(lockPath, payload, { flag: "wx" });
36177
+ fs7.linkSync(tmpPath, lockPath);
36175
36178
  } catch (err) {
36176
36179
  if (err.code === "EEXIST")
36177
36180
  return;
36178
36181
  throw err;
36182
+ } finally {
36183
+ releaseLockRaw(tmpPath);
36179
36184
  }
36180
36185
  let snapshot;
36181
36186
  try {