UNPKG

akm-cli

Version:

akm (Agent Knowledge Manager) — a portable, local-first capability library for AI agents. Discover, load, share, and improve reusable skills, scripts, workflows, and knowledge across any shell-capable coding agent, including Claude Code, OpenCode, and Cur

233 lines (210 loc) • 14.4 kB
Migration notes for akm v0.9.15 `RUN_LEASE_HELD` (a workflow run-lease refusal) and the new `STATE_DB_CONTENDED` (state.db write contention) now exit 75 (`EX_TEMPFAIL`) instead of exit 2 and exit 70 respectively. Both are unrelated to a bad command line — they mean "another akm process is using this database or lease right now, retry shortly." If a script or scheduler wrapper special-cases exit 2 to detect a held lease, switch it to exit 75, or read the JSON envelope's `code` field instead. A concurrent `akm index` now also exits 75 instead of exit 78 or exit 70. A 2026-09-10 field report found a second `akm index` (no `--skip-if-locked`) colliding on the short internal barrier that registers the opt-in rebuild lock could fail with `{"code":"INVALID_CONFIG_FILE"}` at exit 78 — a config-error exit that told a supervisor to stop retrying ordinary contention between two legitimate runs. That barrier now retries briefly before giving up, and a busy barrier is reclassified as `TransientError` code `MAINTENANCE_BARRIER_BUSY` (exit 75) instead. Contention on index.db itself was already reclassified from a raw `database is locked` error (exit 70) to `TransientError` code `INDEX_DB_CONTENDED` (exit 75). Either way, the fix for a scheduled or opportunistic index run is the same: `akm index --skip-if-locked` steps aside (exit 0) instead of contending at all. Two more contention paths collided on the same "config error instead of transient" anti-pattern (#948 field follow-up, 2026-09-10). `akm improve`'s whole-run lock (only one `improve` runs at a time) used to fail a losing contender with `{"code":"INVALID_CONFIG_FILE"}` at exit 78 when no `--skip-if-locked` was passed — a config-error exit for ordinary contention between two legitimate `improve` invocations. It is now `TransientError` code `IMPROVE_LOCK_HELD` at exit 75, naming the current holder's pid and start time; `--skip-if-locked` is unchanged (still exit 0). Separately, the index rebuild's embedding-verification step (`[index:verify] Semantic search verification failed: ...`) could still surface a raw, unclassified `database is locked` message even though the acquisition path was already fixed — that message is now built through the same `INDEX_DB_CONTENDED` reclassification as the rest of the index path. This step does not throw, so it does not change `akm index`'s own exit code; only the message text changed. The six shipped scheduled `improve` task templates now run with `--require-engines`, which aborts (exit 78) before any index work when a process's engine or credential cannot be resolved in the task's own environment, instead of silently skipping that process. This only affects new installs and new `akm setup` task seeding — an existing install's already-materialized task files are not rewritten. To get the same protection on an existing scheduled task, add `--require-engines` to its `run:` command yourself, then run `akm task sync`. `--require-engines` now also runs a bounded reachability probe against each distinct engine endpoint (the same probe `akm health` already uses), not only a config/credential check. A field re-test found the flag let a run through to a fully dead endpoint, which then sat silent for minutes making no progress and no exit — the flag's exit-78 abort now catches that case up front, naming the unreachable engine and endpoint, before any index work. If a scheduled `--require-engines` run starts failing at exit 78 after upgrading, check that the engine's endpoint actually answers — this is the flag doing its documented job on a condition it previously missed, not a new failure mode. Separately, `--timeout-ms` and an engine's own configured timeout already aborted an in-flight request correctly, and SIGTERM/SIGINT already ended a run within its documented grace period — both confirmed, not changed, by this investigation. A live run now also prints one default-level line if it waits more than a few seconds on its first engine response, so a slow-but-alive run and a dead one are never indistinguishable from silence alone. `akm health --no-probe` now also skips the `cli-version` update check (a GitHub release lookup), alongside the engine-reachability checks it already skipped. An air-gapped or offline host's existing `--no-probe` habit now suppresses both network calls with no config change needed. Thinking-control wire forms (`chat_template_kwargs.enable_thinking` and `enable_thinking`) are now sent for every provider whenever `enableThinking` resolves to a value, not only when `provider: "vllm"` is set. If your engine sits behind Bifrost and the gateway does not honor either form, also set `reasoningEffort: "none"` on that engine. If your engine talks to a strict hosted API that rejects unknown request keys, leave `enableThinking` unset on that engine so neither wire form is sent. An `embedding.model` rename no longer forces a full re-embed by itself: `akm index` re-embeds a small sample first and keeps the existing vectors when they still verify against the endpoint. `akm index --reembed` forces a full re-embed when you don't trust that verdict. `akm index --skip-if-locked` lets a scheduled or opportunistic run step aside instead of contending with a rebuild already in progress; the shipped `index-refresh` scheduled task already passes it. `embedding.maxTokens`'s default (the per-request token budget) is now 6000, down from 8000: a field report on an 8192-token llama.cpp embedder showed the 4-chars-per-token estimator undercounts dense technical text by 7-55%, so the old default regularly overshot the endpoint's real context window. If you already set `embedding.maxTokens` explicitly, this default change does not affect you — your configured value is unchanged. `akm index` also now recovers automatically within a run: on the first request rejected for exceeding the endpoint's context window, it lowers its effective budget for the rest of that run (reported with one line) rather than continuing to hit the same wall on every following batch. `embedding.concurrency` (positive integer, 1-16) overrides the number of embedding requests kept in flight at once, which otherwise defaults to 1 for a loopback endpoint and 2 for a remote one. Set it only for an endpoint that genuinely serves parallel requests — a local model server started with a multi-slot flag (llama.cpp's `--parallel N`, vLLM) — since the default already protects an ordinary single-slot server from reload-thrash. Embedding throughput is still tuned first by `embedding.batchSize` (documents per request) and `embedding.maxTokens` (token budget per request); the concurrency override is a second lever for a server that can actually use it. `embedding.timeoutMs` bounds each embedding request (default 120s, up from a prior fixed 30s that cut off a slow local model server mid-response). It is the budget for a request at the full token budget — a smaller request gets a proportionally smaller timeout, so a dead endpoint is still detected in seconds on the common case of small documents. A request TIMEOUT no longer drops its batch immediately: akm now backs off (5s, doubling, capped at 60s) and retries the same request once, since field evidence showed the endpoint keeps computing an abandoned request regardless of the client giving up; a second timeout splits the batch in half and retries each half the same way, down to individual documents, and a single document that still times out is finally skipped. After 3 consecutive failures at single-document size (timeout or network error), or 3 consecutive network errors at any size — never a batch rejected only for exceeding the endpoint's context window — `akm index`'s embedding phase stops dispatching further requests and reports failure instead of grinding through every remaining batch against a dead endpoint — batches already committed are kept, and a rerun picks up where it left off. `akm bundle update` now durably commits its embedding pass instead of nesting it inside its own transaction: earlier releases ran the embedding phase inside the same transaction as content/lock/index/state, so every per-batch commit landed as an unobservable SAVEPOINT and a SIGKILL mid-run lost every embedding of the update, not just the one in flight. The embedding phase now runs on its own connection after the update's own commit; a failing pass (provider down) still leaves the update itself successful, with the new `index.semanticStatus` field on `akm bundle update`'s response the only sign semantic search fell behind. The published `akm`/`akm-migrate` launchers now forward SIGTERM/SIGINT/ SIGHUP to their child and exit alongside it, instead of leaving the child running as an orphan when only the launcher is signaled. No action needed — this is a drop-in fix for anyone running `akm` under a scheduler, supervisor, or hook that can time out or kill the launcher process directly. `embedding.maxInputTokens` (default 512) now caps how much of a single document's text is sent to the embedding provider, truncating to the head instead of ever failing a whole batch over one oversized document. - An existing install's already-stored vectors are untouched and stay valid — this only changes what happens for entries embedded *after* upgrading. - New embeddings (any entry indexed for the first time, or re-indexed after a content change) go through the new 512-token cap by default. If you were relying on documents longer than ~2000 characters being embedded in full, set `embedding.maxInputTokens` higher in `config.json`. - `akm index --reembed` re-embeds every entry under the new cap — run it if you want your entire existing index rebuilt against the new default (or a custom `embedding.maxInputTokens` you've set). - `embedding.contextLength` is Ollama's `num_ctx` only now; it no longer also sets the per-request token budget (`embedding.maxTokens`). If you had set `contextLength` specifically to control request batching (not your Ollama server's context window), set `embedding.maxTokens` instead. **Which token knob fixed the original 8k-context overflow.** A 0.9.15-beta field report described documents estimated under the request budget that still tokenized to 8.5k-12.4k real tokens against an 8192-token endpoint, because the 4-chars-per-token estimator undercounts dense technical text. `maxInputTokens`, `maxTokens`, and `contextLength` are easy to confuse, and only one of them makes that overflow structurally unreachable: - `embedding.maxInputTokens` (default `512`) is the per-DOCUMENT cap. It truncates a document's embedded text to its head before the document is ever counted toward a request, so no single document can contribute more than 512 estimated tokens. This is the fix: it makes the original single-document overflow structurally unreachable, independent of the other two knobs. - `embedding.maxTokens` (default `6000`) is the per-REQUEST budget — how many already-capped documents fit in one HTTP request — plus a same-run adaptive shrink on the first context-size rejection. It reduces how often a request lands near an endpoint's real limit, but a request-level budget alone cannot stop one oversized document from overflowing a request. - `embedding.contextLength` sets Ollama's `num_ctx` only. It no longer feeds the request token budget the way it used to (the two fields used to share this one value), and it has no effect at all against a non-Ollama endpoint. The field's exact 0.9.15-beta config — `contextLength: 8192` and `maxTokens: 8000` — produces no 400s on 0.9.15. `maxTokens` now defaults lower anyway (6000), but that is not why the overflow stopped: every document is truncated to `maxInputTokens` (512 tokens) before it is counted toward any request, so the 8.5k-12.4k-token documents that used to overflow an 8192-token endpoint can no longer reach the request budget in the first place. Set `embedding.maxInputTokens` higher only if you need documents longer than ~2000 characters embedded in full — for a corpus with such documents, size `embedding.maxTokens` to still fit the worst case, or the overflow risk returns. `akm index --full` and an index-generation bump no longer re-embed unchanged content: vectors about to be discarded are salvaged and handed back to unchanged entries at the start of the next embedding pass instead of every upgrade re-embedding the whole corpus once. No action needed — this is automatic; `akm index --reembed` still forces a full re-embed when you don't trust the salvaged vectors. A field report suspected `akm index` was sending embedding requests with no `Authorization` header despite `embedding.apiKey` being set to a `secret://` reference. Auditing every path that builds an embedding request found all of them already resolve `secret://` through the same store lookup, now pinned by integration and contract tests — this was not a bug in the code as it stands. `akm index` now prints one line before its first provider request naming the endpoint, model, and credential SOURCE (never the value), e.g. `[embed] endpoint http://.../v1/embeddings, model nomic-embed; credential: secret://lab-api-key (store)`. If you run an embedding gateway that enforces auth and still see unauthenticated requests after upgrading, compare this line's endpoint and credential source against what the gateway's own request log shows for the same request — a mismatch there (not in this line) is the next place to look. A config file can now inherit a shared base via `extends: <path|bundle//path>`, deep-merging under the local file so local keys always win. `akm config diff <path|bundle//path>` prints every leaf that differs between this instance's effective config and another config file or bundle-relative file. `akm config unset` now refuses to unset a key whose value comes only from an `extends`-inherited base, naming the source, since there would be nothing local to remove. The scheduler runs the binary path `akm task sync` recorded at sync time, not whichever akm your shell now resolves to. After upgrading akm through a different installer than the one active at your last `task sync` (e.g. npm-global to a standalone download), run `akm task sync` again so the schedule points at the new binary; `akm health --probe` now warns via a new `scheduler-binary` advisory when the two diverge.