akm-cli 0.9.14 → 0.9.15-beta.2

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 (120) hide show
  1. package/CHANGELOG.md +559 -0
  2. package/STABILITY.md +6 -3
  3. package/dist/akm +54 -1
  4. package/dist/akm-migrate +34 -1
  5. package/dist/assets/prompts/reflect-feedback-framing.md +1 -0
  6. package/dist/assets/prompts/reflect-llm-framed-contract.md +2 -0
  7. package/dist/assets/prompts/reflect-llm-schema-contract.md +2 -0
  8. package/dist/assets/tasks/core/improve.yml +1 -1
  9. package/dist/assets/tasks/core/index-refresh.yml +1 -1
  10. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +1 -1
  11. package/dist/assets/tasks/improve/akm-improve-catchup.yml +1 -1
  12. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +1 -1
  13. package/dist/assets/tasks/improve/akm-improve-frequent.yml +1 -1
  14. package/dist/assets/tasks/improve/akm-improve-nightly.yml +1 -1
  15. package/dist/cli/retired-commands.js +0 -1
  16. package/dist/cli/shared.js +9 -0
  17. package/dist/cli/unknown-flags.js +1 -0
  18. package/dist/cli.js +40 -3
  19. package/dist/commands/config-cli.js +85 -3
  20. package/dist/commands/env/env-cli.js +1 -42
  21. package/dist/commands/env/env.js +1 -1
  22. package/dist/commands/env/secret-cli.js +1 -2
  23. package/dist/commands/health/checks.js +357 -63
  24. package/dist/commands/health/engine-usage.js +45 -0
  25. package/dist/commands/health/improve-metrics.js +18 -0
  26. package/dist/commands/health/llm-usage.js +41 -1
  27. package/dist/commands/health/plugin-staleness.js +7 -3
  28. package/dist/commands/health/version-drift.js +93 -0
  29. package/dist/commands/health/windows.js +3 -1
  30. package/dist/commands/health.js +44 -9
  31. package/dist/commands/improve/consolidate/chunking.js +4 -2
  32. package/dist/commands/improve/improve-cli.js +99 -5
  33. package/dist/commands/improve/improve-report.js +154 -0
  34. package/dist/commands/improve/improve-result-file.js +45 -33
  35. package/dist/commands/improve/improve-strategies.js +133 -3
  36. package/dist/commands/improve/improve-usage-report.js +182 -0
  37. package/dist/commands/improve/improve.js +40 -3
  38. package/dist/commands/improve/locks.js +28 -78
  39. package/dist/commands/improve/planner.js +1 -0
  40. package/dist/commands/improve/preparation.js +9 -1
  41. package/dist/commands/improve/reflect.js +44 -4
  42. package/dist/commands/models-cli.js +50 -1
  43. package/dist/commands/proposal/repository.js +8 -3
  44. package/dist/commands/proposal/validators/proposal-quality-validators.js +41 -6
  45. package/dist/commands/proposal/validators/proposal-validators.js +24 -0
  46. package/dist/commands/read/search-cli.js +38 -2
  47. package/dist/commands/read/show.js +103 -4
  48. package/dist/commands/sources/info.js +5 -1
  49. package/dist/commands/sources/installed-stashes.js +58 -16
  50. package/dist/commands/sources/self-update.js +2 -2
  51. package/dist/commands/sources/stash-cli.js +48 -0
  52. package/dist/commands/tasks/tasks-cli.js +49 -2
  53. package/dist/commands/workflow-cli.js +86 -12
  54. package/dist/core/asset/markdown-fragments.js +35 -0
  55. package/dist/core/config/config-schema.js +14 -0
  56. package/dist/core/config/config.js +302 -24
  57. package/dist/core/config/schema/embedding.js +41 -0
  58. package/dist/core/env-secret-ref.js +58 -5
  59. package/dist/core/errors.js +30 -0
  60. package/dist/core/file-lock.js +49 -15
  61. package/dist/core/improve-result.js +51 -0
  62. package/dist/core/loopback.js +17 -0
  63. package/dist/core/parent-watchdog.js +64 -0
  64. package/dist/core/paths.js +11 -0
  65. package/dist/core/run-lock.js +107 -0
  66. package/dist/core/sensitive-marker-path.js +19 -0
  67. package/dist/core/state-db.js +74 -14
  68. package/dist/indexer/index-rebuild-lock.js +73 -0
  69. package/dist/indexer/index-writer-lock.js +40 -1
  70. package/dist/indexer/index-written-assets.js +29 -1
  71. package/dist/indexer/indexer.js +93 -29
  72. package/dist/indexer/materialize-embeddings.js +564 -48
  73. package/dist/indexer/search/db-search.js +49 -2
  74. package/dist/indexer/search/search-source.js +23 -1
  75. package/dist/integrations/agent/engine-resolution.js +96 -6
  76. package/dist/integrations/agent/execution-definitions.js +6 -15
  77. package/dist/integrations/agent/execution-lowering.js +6 -1
  78. package/dist/integrations/agent/execution-preparation.js +1 -1
  79. package/dist/integrations/agent/model-map.js +123 -20
  80. package/dist/integrations/agent/prompts.js +40 -8
  81. package/dist/integrations/agent/runner-dispatch.js +9 -3
  82. package/dist/integrations/agent/runner.js +2 -0
  83. package/dist/llm/client.js +8 -3
  84. package/dist/llm/embedder.js +20 -8
  85. package/dist/llm/embedders/local.js +10 -2
  86. package/dist/llm/embedders/remote.js +497 -32
  87. package/dist/output/shapes/helpers.js +38 -2
  88. package/dist/output/shapes/models-list.js +16 -0
  89. package/dist/output/shapes/passthrough.js +2 -0
  90. package/dist/output/shapes.js +4 -0
  91. package/dist/output/text/command-format.js +29 -0
  92. package/dist/output/text/helpers.js +1 -1
  93. package/dist/output/text/improve-report.js +27 -0
  94. package/dist/{commands/env/marker-path.js → output/text/models.js} +4 -3
  95. package/dist/output/text/show-format.js +4 -0
  96. package/dist/output/text.js +4 -0
  97. package/dist/scripts/akm-migrate-node.js +25146 -21759
  98. package/dist/scripts/akm-migrate.js +24271 -20885
  99. package/dist/storage/repositories/embedding-salvage-repository.js +184 -0
  100. package/dist/storage/repositories/improve-runs-repository.js +34 -0
  101. package/dist/storage/repositories/index-fts-repository.js +49 -6
  102. package/dist/storage/repositories/index-schema.js +16 -0
  103. package/dist/storage/repositories/index-vec-repository.js +30 -0
  104. package/dist/storage/repositories/workflow-runs-repository.js +55 -18
  105. package/dist/tasks/backends/cron.js +14 -7
  106. package/dist/tasks/run/run-native-task.js +23 -1
  107. package/dist/tasks/run/run-workflow-task.js +16 -0
  108. package/dist/workflows/exec/child-workflow.js +2 -2
  109. package/dist/workflows/exec/dispatch-redaction.js +21 -9
  110. package/dist/workflows/exec/run-workflow.js +6 -5
  111. package/dist/workflows/runtime/runs.js +33 -5
  112. package/docs/migration/release-notes/0.9.15.md +133 -0
  113. package/docs/migration/release-notes/README.md +5 -0
  114. package/docs/reference/cli.md +271 -30
  115. package/docs/reference/configuration.md +234 -21
  116. package/docs/reference/data-and-telemetry.md +8 -0
  117. package/docs/reference/tasks.md +16 -1
  118. package/docs/reference/workflow-schema.md +5 -1
  119. package/package.json +1 -1
  120. package/schemas/akm-config.json +47 -0
@@ -53,7 +53,7 @@
53
53
  * `driveRun` itself is never exported (B-N5's "no second executor" holds).
54
54
  */
55
55
  import { randomUUID } from "node:crypto";
56
- import { UsageError } from "../../core/errors.js";
56
+ import { TransientError } from "../../core/errors.js";
57
57
  import { withWorkflowRunsRepo } from "../../storage/repositories/workflow-runs-repository.js";
58
58
  import { validateWorkflowParams } from "../ir/params.js";
59
59
  import { canonicalPlanJson, computePlanHash } from "../ir/plan-hash.js";
@@ -76,7 +76,7 @@ function errorMessage(err) {
76
76
  }
77
77
  /** `acquireRunLease`'s exact refusal shape (run-workflow.ts) — matched by text, since this module cannot import that private helper. */
78
78
  function isLeaseBusyError(err) {
79
- return err instanceof UsageError && err.message.includes("is already being driven by engine");
79
+ return err instanceof TransientError && err.message.includes("is already being driven by engine");
80
80
  }
81
81
  /** §3.4's exact `child_workflow_failed` message. */
82
82
  function childWorkflowFailedMessage(input) {
@@ -15,23 +15,24 @@
15
15
  * @module workflows/exec/dispatch-redaction
16
16
  */
17
17
  import { collectSensitiveValues, isEnvPassthroughValueSafeToExpose, redactSensitiveText, redactSensitiveValue, } from "../../core/redaction.js";
18
- import { lookupApiKeyFileValue } from "../../integrations/agent/engine-resolution.js";
18
+ import { lookupApiKeyFileValue, lookupApiKeySecretRefValue } from "../../integrations/agent/engine-resolution.js";
19
19
  /**
20
20
  * Every exact value that must never survive into the journal from ONE frozen
21
21
  * dispatch: the resolved `env` bindings injected into the child, the selected
22
- * engine's (and its SDK fallback's) credential — an env value, or a
23
- * file-backed one (#905) — and any `envPassthrough` value the redaction
24
- * policy does not consider safe to expose.
22
+ * engine's (and its SDK fallback's) credential — an env value, a file-backed
23
+ * one (#905), or a secret-store-backed one (#953) — and any `envPassthrough`
24
+ * value the redaction policy does not consider safe to expose.
25
25
  *
26
26
  * Shared by the unit path and the gate-judge path. There is deliberately ONE
27
27
  * collector: a second, parallel implementation is exactly how a dispatch path
28
28
  * silently loses the scrub.
29
29
  *
30
- * The credential values are read from `process.env` (or disk, for a file-
31
- * backed one) AT CALL TIME, so a caller must collect no earlier than the
32
- * dispatch whose outcome it scrubs. A snapshot taken when the dispatch was
33
- * merely *planned* can predate a credential the dispatch then resolves live,
34
- * leaving the exact value it must remove out of the set.
30
+ * The credential values are read from `process.env` (or disk / the secret
31
+ * store, for a file- or store-backed one) AT CALL TIME, so a caller must
32
+ * collect no earlier than the dispatch whose outcome it scrubs. A snapshot
33
+ * taken when the dispatch was merely *planned* can predate a credential the
34
+ * dispatch then resolves live, leaving the exact value it must remove out of
35
+ * the set.
35
36
  */
36
37
  export function collectWorkflowDispatchSensitiveValues(dispatch, env) {
37
38
  const values = new Set([...Object.values(env ?? {}), ...(dispatch.sensitiveValues ?? [])]);
@@ -51,6 +52,12 @@ export function collectWorkflowDispatchSensitiveValues(dispatch, env) {
51
52
  if (value)
52
53
  values.add(value);
53
54
  }
55
+ // #953: secret-store-backed credential — same best-effort rationale.
56
+ if (runner.apiKeySecretRef) {
57
+ const value = lookupApiKeySecretRefValue(runner.apiKeySecretRef);
58
+ if (value)
59
+ values.add(value);
60
+ }
54
61
  return;
55
62
  }
56
63
  for (const name of runner.profile.envPassthrough ?? []) {
@@ -69,6 +76,11 @@ export function collectWorkflowDispatchSensitiveValues(dispatch, env) {
69
76
  if (value)
70
77
  values.add(value);
71
78
  }
79
+ if (runner.fallbackApiKeySecretRef) {
80
+ const value = lookupApiKeySecretRefValue(runner.fallbackApiKeySecretRef);
81
+ if (value)
82
+ values.add(value);
83
+ }
72
84
  }
73
85
  };
74
86
  addCredential(dispatch.runner);
@@ -15,7 +15,7 @@
15
15
  * full design history behind each of these invariants.
16
16
  */
17
17
  import { randomUUID } from "node:crypto";
18
- import { UsageError } from "../../core/errors.js";
18
+ import { TransientError, UsageError } from "../../core/errors.js";
19
19
  import { withMaintenanceStartBarrierAsync } from "../../core/maintenance-barrier.js";
20
20
  import { disposeDispatchResources } from "../../integrations/agent/runner-dispatch.js";
21
21
  import { withWorkflowRunsConnection, withWorkflowRunsRepo } from "../../storage/repositories/workflow-runs-repository.js";
@@ -183,16 +183,17 @@ function leaseExpiry() {
183
183
  return new Date(Date.now() + RUN_LEASE_TTL_MS).toISOString();
184
184
  }
185
185
  /**
186
- * Atomically claim the run lease or refuse with a UsageError naming the
187
- * current holder + expiry. The single-UPDATE claim in the repository is the
188
- * arbiter — two racing invocations cannot both win.
186
+ * Atomically claim the run lease or refuse with a TransientError naming the
187
+ * current holder + expiry (#948 addendum moved off UsageError, exit 75).
188
+ * The single-UPDATE claim in the repository is the arbiter — two racing
189
+ * invocations cannot both win.
189
190
  */
190
191
  async function acquireRunLease(runId, holder) {
191
192
  await withMaintenanceStartBarrierAsync(() => withWorkflowRunsRepo((repo) => {
192
193
  if (repo.acquireEngineLease(runId, holder, leaseExpiry(), new Date().toISOString()))
193
194
  return;
194
195
  const row = repo.getRunById(runId);
195
- throw new UsageError(`Workflow run ${runId} is already being driven by engine ${row?.engine_lease_holder ?? "(unknown)"} ` +
196
+ throw new TransientError(`Workflow run ${runId} is already being driven by engine ${row?.engine_lease_holder ?? "(unknown)"} ` +
196
197
  `(run lease expires ${row?.engine_lease_until ?? "(unknown)"}). A second \`akm workflow run\` would race it — ` +
197
198
  `wait for that invocation to finish or for the lease to expire.`, "RUN_LEASE_HELD");
198
199
  }));
@@ -4,7 +4,7 @@
4
4
  import { randomUUID } from "node:crypto";
5
5
  import { parseBundleRef } from "../../core/asset/asset-ref.js";
6
6
  import { loadConfig } from "../../core/config/config.js";
7
- import { ConfigError, NotFoundError, UsageError } from "../../core/errors.js";
7
+ import { ConfigError, NotFoundError, TransientError, UsageError } from "../../core/errors.js";
8
8
  import { appendEvent } from "../../core/events.js";
9
9
  import { warn } from "../../core/warn.js";
10
10
  import { insertEventOnce } from "../../storage/repositories/events-repository.js";
@@ -127,6 +127,25 @@ export async function startWorkflowRun(ref, params = {}, options) {
127
127
  // per the workflow-agent check-in ADR) so a stalled run can be
128
128
  // re-targeted with a `continue` directive. The agent harness + session id
129
129
  // are already resolved above (agentHarness/agentSessionId, from #501).
130
+ // #942: an active run of this ref may already exist in a DIFFERENT
131
+ // scope — the incident this issue reports (a scheduled task's cwd and a
132
+ // human's shell hash to different scope keys, so each believed it held
133
+ // no active run and each started one). The scope-local uniqueness guard
134
+ // stays scope-local (a documented, deliberate per-project partition —
135
+ // see storage-locations.md); this only warns, once, so the operator can
136
+ // resume or abandon the other run instead of silently accumulating a
137
+ // second one. `findActiveRunOutsideScope` excludes the caller's own
138
+ // scope IN SQL (never merely post-filtered) so the caller's own active
139
+ // run can never sort first under `LIMIT 1` and mask a genuinely different
140
+ // scope's run — the failure mode a same-scope-inclusive query plus a
141
+ // post-filter has with `--new`/`--force`.
142
+ const crossScopeActive = repo.findActiveRunOutsideScope(workflowRefs, scopeKey);
143
+ const crossScopeWarning = crossScopeActive
144
+ ? `Workflow ${asset.ref} already has an active run in another scope ` +
145
+ `(id ${crossScopeActive.id}, started ${crossScopeActive.created_at}, scope ${crossScopeActive.scope_key ?? "unknown"}); ` +
146
+ `starting a separate run here. Resume it from anywhere with "akm workflow run ${crossScopeActive.id}" ` +
147
+ `or free it with "akm workflow abandon ${crossScopeActive.id}".`
148
+ : undefined;
130
149
  repo.publishWorkflowRunV4({
131
150
  workflowRefs,
132
151
  ...(options?.force ? { force: true } : {}),
@@ -157,6 +176,8 @@ export async function startWorkflowRun(ref, params = {}, options) {
157
176
  revalidateSources: () => frozen.sourceCollector.revalidate(),
158
177
  });
159
178
  const result = await getWorkflowStatus(runId);
179
+ if (crossScopeWarning)
180
+ result.warnings = [...(result.warnings ?? []), crossScopeWarning];
160
181
  // #13: params are declared non-secret (they are copied verbatim into every
161
182
  // unit prompt and hashed into the unit identity, so they cannot be redacted
162
183
  // without breaking replay determinism). Surface a loud, best-effort warning
@@ -193,7 +214,7 @@ export async function getWorkflowStatus(runId, opts) {
193
214
  });
194
215
  }
195
216
  export async function listWorkflowRuns(input) {
196
- const scopeKey = getCurrentWorkflowScopeKey();
217
+ const scopeKey = input?.allScopes === true ? null : getCurrentWorkflowScopeKey();
197
218
  const activeOnly = input?.activeOnly === true;
198
219
  const includeChildren = input?.includeChildren === true;
199
220
  if (input?.workflowRef === undefined) {
@@ -205,6 +226,7 @@ export async function listWorkflowRuns(input) {
205
226
  ...(includeChildren ? { includeChildren: true } : {}),
206
227
  })
207
228
  .map(toWorkflowRunSummary),
229
+ scopeKey,
208
230
  }));
209
231
  }
210
232
  const exactRef = input.workflowRef.trim();
@@ -227,6 +249,7 @@ export async function listWorkflowRuns(input) {
227
249
  throw error;
228
250
  return {
229
251
  runs: exactRows.filter((row) => !activeOnly || row.status === "active").map(toWorkflowRunSummary),
252
+ scopeKey,
230
253
  };
231
254
  }
232
255
  if (!(error instanceof NotFoundError))
@@ -241,6 +264,7 @@ export async function listWorkflowRuns(input) {
241
264
  ...(includeChildren ? { includeChildren: true } : {}),
242
265
  })
243
266
  .map(toWorkflowRunSummary),
267
+ scopeKey,
244
268
  }));
245
269
  }
246
270
  export async function getNextWorkflowStep(specifier, params, options) {
@@ -558,7 +582,8 @@ async function resolveRunSpecifier(repo, specifier, params, parameterFlags, forc
558
582
  catch (error) {
559
583
  if (detached) {
560
584
  if (hasParameters) {
561
- throw new UsageError(`Workflow parameter flags can only be set on a new run; ${specifier} is already active.`, "INVALID_FLAG_VALUE", `Pass --new to start a separate run, or run "akm workflow abandon ${detached.id}" to free up ${specifier} first.`);
585
+ throw new UsageError(`Workflow parameter flags can only be set on a new run; ${specifier} is already active ` +
586
+ `(id ${detached.id}, scope ${scopeKey}).`, "INVALID_FLAG_VALUE", `Pass --new to start a separate run, or run "akm workflow abandon ${detached.id}" to free up ${specifier} first.`);
562
587
  }
563
588
  return { run: detached, autoStarted: false, resumed: true };
564
589
  }
@@ -570,7 +595,8 @@ async function resolveRunSpecifier(repo, specifier, params, parameterFlags, forc
570
595
  const active = forceNew ? undefined : repo.getActiveRunRowForScope(await workflowRunRefSet(ref, exactRef), scopeKey);
571
596
  if (active) {
572
597
  if (hasParameters) {
573
- throw new UsageError(`Workflow parameter flags can only be set on a new run; ${ref} is already active.`, "INVALID_FLAG_VALUE", `Pass --new to start a separate run, or run "akm workflow abandon ${active.id}" to free up ${ref} first.`);
598
+ throw new UsageError(`Workflow parameter flags can only be set on a new run; ${ref} is already active ` +
599
+ `(id ${active.id}, scope ${scopeKey}).`, "INVALID_FLAG_VALUE", `Pass --new to start a separate run, or run "akm workflow abandon ${active.id}" to free up ${ref} first.`);
574
600
  }
575
601
  return { run: active, autoStarted: false, resumed: true };
576
602
  }
@@ -808,7 +834,9 @@ function assertLeaseAllowsSpineAdvance(run, leaseHolder) {
808
834
  return;
809
835
  if (run.engine_lease_until < new Date().toISOString())
810
836
  return; // expired ⇒ claimable, not live
811
- throw new UsageError(`Workflow run ${run.id} is being driven by engine ${run.engine_lease_holder} ` +
837
+ // #948 addendum: moved off UsageError (exit 75, not exit 2) a held lease
838
+ // is ordinary contention, not a bad command line.
839
+ throw new TransientError(`Workflow run ${run.id} is being driven by engine ${run.engine_lease_holder} ` +
812
840
  `(run lease expires ${run.engine_lease_until}). The engine owns the step spine while it runs — ` +
813
841
  `wait for it to finish or for the lease to expire before advancing steps manually.`, "RUN_LEASE_HELD");
814
842
  }
@@ -0,0 +1,133 @@
1
+ Migration notes for akm v0.9.15
2
+
3
+ `RUN_LEASE_HELD` (a workflow run-lease refusal) and the new
4
+ `STATE_DB_CONTENDED` (state.db write contention) now exit 75 (`EX_TEMPFAIL`)
5
+ instead of exit 2 and exit 70 respectively. Both are unrelated to a bad command
6
+ line — they mean "another akm process is using this database or lease right
7
+ now, retry shortly." If a script or scheduler wrapper special-cases exit 2 to
8
+ detect a held lease, switch it to exit 75, or read the JSON envelope's `code`
9
+ field instead.
10
+
11
+ The six shipped scheduled `improve` task templates now run with
12
+ `--require-engines`, which aborts (exit 78) before any index work when a
13
+ process's engine or credential cannot be resolved in the task's own
14
+ environment, instead of silently skipping that process. This only affects new
15
+ installs and new `akm setup` task seeding — an existing install's
16
+ already-materialized task files are not rewritten. To get the same protection
17
+ on an existing scheduled task, add `--require-engines` to its `run:` command
18
+ yourself, then run `akm task sync`.
19
+
20
+ `akm health --no-probe` now also skips the `cli-version` update check (a GitHub
21
+ release lookup), alongside the engine-reachability checks it already skipped.
22
+ An air-gapped or offline host's existing `--no-probe` habit now suppresses both
23
+ network calls with no config change needed.
24
+
25
+ Thinking-control wire forms (`chat_template_kwargs.enable_thinking` and
26
+ `enable_thinking`) are now sent for every provider whenever `enableThinking`
27
+ resolves to a value, not only when `provider: "vllm"` is set. If your engine
28
+ sits behind Bifrost and the gateway does not honor either form, also set
29
+ `reasoningEffort: "none"` on that engine. If your engine talks to a strict
30
+ hosted API that rejects unknown request keys, leave `enableThinking` unset on
31
+ that engine so neither wire form is sent.
32
+
33
+ An `embedding.model` rename no longer forces a full re-embed by itself: `akm
34
+ index` re-embeds a small sample first and keeps the existing vectors when they
35
+ still verify against the endpoint. `akm index --reembed` forces a full re-embed
36
+ when you don't trust that verdict. `akm index --skip-if-locked` lets a
37
+ scheduled or opportunistic run step aside instead of contending with a rebuild
38
+ already in progress; the shipped `index-refresh` scheduled task already passes
39
+ it.
40
+
41
+ `embedding.concurrency` (positive integer, 1-16) overrides the number of
42
+ embedding requests kept in flight at once, which otherwise defaults to 1 for
43
+ a loopback endpoint and 2 for a remote one. Set it only for an endpoint that
44
+ genuinely serves parallel requests — a local model server started with a
45
+ multi-slot flag (llama.cpp's `--parallel N`, vLLM) — since the default
46
+ already protects an ordinary single-slot server from reload-thrash.
47
+ Embedding throughput is still tuned first by `embedding.batchSize`
48
+ (documents per request) and `embedding.maxTokens` (token
49
+ budget per request); the concurrency override is a second lever for a
50
+ server that can actually use it.
51
+
52
+ `embedding.timeoutMs` bounds each embedding request (default 120s, up from a
53
+ prior fixed 30s that cut off a slow local model server mid-response). It is
54
+ the budget for a request at the full token budget — a smaller request gets a
55
+ proportionally smaller timeout, so a dead endpoint is still detected in
56
+ seconds on the common case of small documents. A request TIMEOUT no longer
57
+ drops its batch immediately: akm now backs off (5s, doubling, capped at 60s)
58
+ and retries the same request once, since field evidence showed the endpoint
59
+ keeps computing an abandoned request regardless of the client giving up; a
60
+ second timeout splits the batch in half and retries each half the same way,
61
+ down to individual documents, and a single document that still times out is
62
+ finally skipped. After 3 consecutive failures at single-document size
63
+ (timeout or network error), or 3 consecutive network errors at any size —
64
+ never a batch rejected only for exceeding the endpoint's context window —
65
+ `akm index`'s embedding phase stops dispatching further requests and reports
66
+ failure instead of grinding through every remaining batch against a dead
67
+ endpoint — batches already committed are kept, and a rerun picks up where it
68
+ left off.
69
+
70
+ `akm bundle update` now durably commits its embedding pass instead of
71
+ nesting it inside its own transaction: earlier releases ran the embedding
72
+ phase inside the same transaction as content/lock/index/state, so every
73
+ per-batch commit landed as an unobservable SAVEPOINT and a SIGKILL mid-run
74
+ lost every embedding of the update, not just the one in flight. The
75
+ embedding phase now runs on its own connection after the update's own
76
+ commit; a failing pass (provider down) still leaves the update itself
77
+ successful, with the new `index.semanticStatus` field on `akm bundle
78
+ update`'s response the only sign semantic search fell behind.
79
+
80
+ The published `akm`/`akm-migrate` launchers now forward SIGTERM/SIGINT/
81
+ SIGHUP to their child and exit alongside it, instead of leaving the child
82
+ running as an orphan when only the launcher is signaled. No action needed —
83
+ this is a drop-in fix for anyone running `akm` under a scheduler,
84
+ supervisor, or hook that can time out or kill the launcher process
85
+ directly.
86
+
87
+ `embedding.maxInputTokens` (default 512) now caps how much of a single
88
+ document's text is sent to the embedding provider, truncating to the head
89
+ instead of ever failing a whole batch over one oversized document.
90
+
91
+ - An existing install's already-stored vectors are untouched and stay
92
+ valid — this only changes what happens for entries embedded *after*
93
+ upgrading.
94
+ - New embeddings (any entry indexed for the first time, or re-indexed after
95
+ a content change) go through the new 512-token cap by default. If you
96
+ were relying on documents longer than ~2000 characters being embedded in
97
+ full, set `embedding.maxInputTokens` higher in `config.json`.
98
+ - `akm index --reembed` re-embeds every entry under the new cap — run it if
99
+ you want your entire existing index rebuilt against the new default (or a
100
+ custom `embedding.maxInputTokens` you've set).
101
+ - `embedding.contextLength` is Ollama's `num_ctx` only now; it no longer
102
+ also sets the per-request token budget (`embedding.maxTokens`). If you had
103
+ set `contextLength` specifically to control request batching (not your
104
+ Ollama server's context window), set `embedding.maxTokens` instead.
105
+
106
+ `akm index --full` and an index-generation bump no longer re-embed
107
+ unchanged content: vectors about to be discarded are salvaged and handed
108
+ back to unchanged entries at the start of the next embedding pass instead
109
+ of every upgrade re-embedding the whole corpus once. No action needed —
110
+ this is automatic; `akm index --reembed` still forces a full re-embed when
111
+ you don't trust the salvaged vectors.
112
+
113
+ A field report suspected `akm index` was sending embedding requests with no
114
+ `Authorization` header despite `embedding.apiKey` being set to a
115
+ `secret://` reference. Auditing every path that builds an embedding request
116
+ found all of them already resolve `secret://` through the same store
117
+ lookup, now pinned by integration and contract tests — this was not a bug
118
+ in the code as it stands. `akm index` now prints one line before its first
119
+ provider request naming the endpoint, model, and credential SOURCE (never
120
+ the value), e.g. `[embed] endpoint http://.../v1/embeddings, model
121
+ nomic-embed; credential: secret://lab-api-key (store)`. If you run an
122
+ embedding gateway that enforces auth and still see unauthenticated requests
123
+ after upgrading, compare this line's endpoint and credential source against
124
+ what the gateway's own request log shows for the same request — a
125
+ mismatch there (not in this line) is the next place to look.
126
+
127
+ A config file can now inherit a shared base via `extends: <path|bundle//path>`,
128
+ deep-merging under the local file so local keys always win. `akm config diff
129
+ <path|bundle//path>` prints every leaf that differs between this instance's
130
+ effective config and another config file or bundle-relative file. `akm config
131
+ unset` now refuses to unset a key whose value comes only from an
132
+ `extends`-inherited base, naming the source, since there would be nothing local
133
+ to remove.
@@ -7,6 +7,11 @@ live one level up in `docs/migration/`.
7
7
 
8
8
  ## Available notes
9
9
 
10
+ - [0.9.15](0.9.15.md) — exit-code 75 for lease/state.db contention,
11
+ `--require-engines` scheduled task templates, `--no-probe` cli-version
12
+ skip, thinking-control wire forms, embedding re-embed safety and
13
+ cross-rebuild vector salvage, launcher signal forwarding, a default
14
+ per-document embedding cap, and `extends` config inheritance
10
15
  - [0.9.14](0.9.14.md) — index v22-to-v23 derived-cache rebuild, lexical
11
16
  fragments, and collapse-detector canary re-minting
12
17
  - [0.9.2](0.9.2.md) — task source v4 migration, workflow source IR v1 and