akm-cli 0.9.17-alpha.9 → 0.9.18
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.
- package/CHANGELOG.md +296 -2025
- package/dist/cli/unknown-flags.js +24 -1
- package/dist/cli.js +46 -1
- package/dist/commands/health/archive-usage.js +9 -15
- package/dist/commands/health/checks.js +6 -6
- package/dist/commands/health/improve-metrics.js +25 -12
- package/dist/commands/health.js +3 -3
- package/dist/commands/improve/consolidate/pair-pass.js +2 -2
- package/dist/commands/improve/consolidate.js +3 -3
- package/dist/commands/improve/distill.js +1 -1
- package/dist/commands/improve/memory/memory-improve.js +8 -15
- package/dist/commands/improve/preparation.js +2 -0
- package/dist/commands/improve/reflect.js +1 -1
- package/dist/commands/improve/stage.js +5 -3
- package/dist/commands/proposal/repository.js +5 -1
- package/dist/commands/sources/info.js +122 -18
- package/dist/commands/sources/stash-cli.js +21 -1
- package/dist/core/improve-result.js +6 -1
- package/dist/output/text/command-format.js +9 -0
- package/dist/scripts/akm-migrate-node.js +29 -2
- package/dist/scripts/akm-migrate.js +29 -2
- package/dist/sources/providers/git-stash.js +28 -0
- package/dist/storage/repositories/index-connection.js +5 -2
- package/dist/storage/sqlite-read-snapshot.js +46 -2
- package/dist/storage/state-db-integrity.js +12 -9
- package/docs/migration/README.md +1 -1
- package/docs/migration/release-notes/0.9.17.md +130 -41
- package/docs/migration/release-notes/README.md +7 -0
- package/docs/migration/v0.7-to-v0.8.md +2 -2
- package/docs/reference/cli.md +11 -2
- package/docs/reference/data-and-telemetry.md +5 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,2048 +6,319 @@ 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.
|
|
10
|
-
|
|
11
|
-
`akm improve` now forgets, reversibly and under review. A consolidation pair
|
|
12
|
-
pass compares each new or changed memory, flat knowledge file or lesson with
|
|
13
|
-
its nearest neighbours; where an LLM judge calls a pair duplicate, subsumed or
|
|
14
|
-
superseding, it mints a retire proposal that a person reviews (`akm proposal
|
|
15
|
-
list --generator consolidate-pair`). Accepting one archives the older or
|
|
16
|
-
contained copy, and `akm proposal revert` restores it exactly; an accepted
|
|
17
|
-
promotion now retires its source memory, so promotion no longer leaves a
|
|
18
|
-
duplicate. A continuity check flags a retirement whose survivor does not rank
|
|
19
|
-
where the retired asset did for its own past searches, and a flagged proposal
|
|
20
|
-
is never bulk-accepted. Archived files are purged 30 days after retirement,
|
|
21
|
-
only when git holds them unmodified. The per-run forgetting-safety lane, LLM
|
|
22
|
-
metadata enrichment and LLM entity-graph extraction are removed: each measured
|
|
23
|
-
no benefit. Decide pending retire proposals before downgrading to
|
|
24
|
-
0.9.17-alpha.8.
|
|
25
|
-
|
|
26
|
-
### Added
|
|
27
|
-
|
|
28
|
-
- **Consolidate pair pass: duplicate, subsumed and superseding memories are
|
|
29
|
-
now retired, review-gated.** A second pass inside `akmConsolidate`,
|
|
30
|
-
alongside the existing promote pass. It walks memory-tier assets (a
|
|
31
|
-
memory, base or `.derived`; a flat `knowledge/` asset; or a lesson) in the
|
|
32
|
-
retrieval scope that are new to the pass or whose body has changed since
|
|
33
|
-
their last full attempt — tracked by content hash, not a time window, so
|
|
34
|
-
an initiator the nightly cap or a pending-proposal collision leaves out
|
|
35
|
-
stays eligible rather than being marked settled, and the pass's own
|
|
36
|
-
ledger rows are never retrieval-scope evidence for the other improve
|
|
37
|
-
lanes — takes each one's nearest neighbours by stored vector (fetching 20,
|
|
38
|
-
keeping the first 5 that clear every filter; same bundle, memory tier
|
|
39
|
-
only — structured knowledge in subfolders is excluded), and judges every
|
|
40
|
-
pair at cosine >= `T_pair` (0.93) with one LLM call using the calibrated
|
|
41
|
-
relation prompt (`src/assets/prompts/consolidate-pair.md`, six labels:
|
|
42
|
-
`duplicate`, `subsumed`, `supersedes`, `contradicts`, `overlap`,
|
|
43
|
-
`unrelated`). An initiator with no prior attempt is held to a higher
|
|
44
|
-
`T_pair` >= 0.95, unless it is new material (git first-added within the
|
|
45
|
-
last 7 days), which judges at the ordinary 0.93. "Older"/"newer" for the
|
|
46
|
-
judge's own A/B labelling comes from one `git log` per run over the
|
|
47
|
-
bundle (first-add time, following renames so a moved or renamed file
|
|
48
|
-
keeps its original date), not frontmatter or file mtime — mtime is only
|
|
49
|
-
the fallback for a file git does not know, or a bundle with no `.git` at
|
|
50
|
-
all. At most 300 pairs are judged a night, admitted a whole initiator at
|
|
51
|
-
a time rather than by flat cosine rank: new-or-changed initiators first,
|
|
52
|
-
then the existing backlog by its own best cosine, each admitted only if
|
|
53
|
-
every one of its candidate pairs fits in what remains of the 300 — so an
|
|
54
|
-
initiator blocked on another pending decision never spends a slot doing
|
|
55
|
-
nothing, and a smaller initiator further down still fits when a larger
|
|
56
|
-
one ahead of it does not. `duplicate`, `subsumed` and `supersedes` mint a
|
|
57
|
-
reviewed `retire` proposal for the losing side (owner-calibrated
|
|
58
|
-
precision 20/22 = 0.91 [0.72, 0.97] against a second-rater baseline of
|
|
59
|
-
0.17 for `supersedes` alone); `contradicts` is counted but stays a human
|
|
60
|
-
decision, and `overlap`/`unrelated` get no proposal. Guards: never a
|
|
61
|
-
`captureMode: hot` memory, never a `.derived` memory whose parent still
|
|
62
|
-
exists, never a pair where either side already has a pending retire
|
|
63
|
-
proposal (as the retired ref or its successor), and never retiring or
|
|
64
|
-
reusing as a successor an asset already spent earlier in the same run.
|
|
65
|
-
(`src/commands/improve/consolidate/pair-pass.ts`,
|
|
66
|
-
`src/commands/improve/retrieval-scope.ts`,
|
|
67
|
-
`src/storage/repositories/improve-ledger-repository.ts`,
|
|
68
|
-
`src/assets/prompts/consolidate-pair.md`)
|
|
69
|
-
- **Retire proposals.** A pair-pass retire proposal mints under its own
|
|
70
|
-
source, `consolidate-pair` — kept apart from the promote pass's
|
|
71
|
-
`consolidate` proposals, so a bulk `accept`/`reject --generator
|
|
72
|
-
consolidate` never sweeps a retirement, and the reverse; a bare `akm
|
|
73
|
-
proposal accept <ref>` never resolves to one either (it matches the
|
|
74
|
-
newest non-retire proposal for the ref, if any — a retire is reached by
|
|
75
|
-
its own proposal id, or the bulk `--generator consolidate-pair` form),
|
|
76
|
-
and retention expiry never drops a pending one for age alone. A pair the owner
|
|
77
|
-
rejected or reverted is not proposed again while both sides are unchanged. Its primary
|
|
78
|
-
change deletes its target instead of writing content. Accepting one first
|
|
79
|
-
confirms the decision is still fresh — the successor still exists, and
|
|
80
|
-
both sides' recorded body hashes still match their current files, not
|
|
81
|
-
just the retired side's, so a decision a later accept elsewhere made
|
|
82
|
-
stale (an A->B/B->C chain, or A->B/B->A both minted) is refused cleanly
|
|
83
|
-
rather than partially applied — then archives the asset (and its
|
|
84
|
-
`.derived` twin, if one exists) through a generalized
|
|
85
|
-
`archiveCleanupCandidate` (now usable on any memory, knowledge or lesson
|
|
86
|
-
file, not only `.derived` memories): the same
|
|
87
|
-
`.akm/memory-cleanup/archive/` encoding memory cleanup already used,
|
|
88
|
-
never the dead `.akm/archive/`. A `supersedes` judgement first writes the
|
|
89
|
-
`supersededBy` edge on the older asset, then archives it. Triage never
|
|
90
|
-
auto-accepts a retire proposal, whatever `applyMode` says — it waits for
|
|
91
|
-
a direct `akm proposal accept`, reviewed the same way as any other
|
|
92
|
-
proposal (`akm proposal list`, `show`, `diff`, bulk `accept --generator
|
|
93
|
-
consolidate-pair`; `--max-diff-lines` counts a retire by its target's own
|
|
94
|
-
line count). `accept` is crash-safe: it records its full intent —
|
|
95
|
-
`backupContent` and which file is about to move — durably before moving
|
|
96
|
-
anything, so a crash partway through, including between a primary and its
|
|
97
|
-
`.derived` twin, resumes and finishes from what was recorded rather than
|
|
98
|
-
leaving an asset stranded or the decision unrecorded. `revert` needs no
|
|
99
|
-
intent of its own — it resumes from what `accept` already recorded, and
|
|
100
|
-
refuses instead of overwriting a path that was reused by an unrelated file
|
|
101
|
-
since (its current content no longer matching what was retired). `revert`
|
|
102
|
-
restores the archived file(s) byte-for-byte from the bytes recorded at
|
|
103
|
-
accept, even a file that had no trailing newline — YAML comments, key
|
|
104
|
-
order and any human-written edge all survive the round trip. A ref to a retired asset keeps resolving to its tombstone
|
|
105
|
-
(`isArchivedRelPath`) — fixed along the way: that resolver assumed only
|
|
106
|
-
memories are ever archived, so an xref to a retired knowledge or lesson
|
|
107
|
-
asset was wrongly reported `missing-ref` by `akm lint` until now. Pending
|
|
108
|
-
retire proposals must be accepted or rejected before downgrading to
|
|
109
|
-
0.9.17-alpha.8 or earlier — that release predates the retire shape
|
|
110
|
-
entirely and exits 70 on one in `show`/`diff`/`drain`, and drain's own
|
|
111
|
-
nightly pre-pass failing on the first one it meets stops that run's
|
|
112
|
-
auto-promotion for the whole stash. Downgrading also revives the
|
|
113
|
-
new-material starvation this same branch fixed forward-only: 0.9.17-alpha.8
|
|
114
|
-
counts a pair-pass ledger row as retrieval-scope evidence again, so its own
|
|
115
|
-
nightly attempts crowd new material back out of every other improve lane —
|
|
116
|
-
measured, two nights left only 341 of 2,183 new-only assets still in scope
|
|
117
|
-
once read under alpha.8
|
|
118
|
-
(`docs/architecture/persisted-data-compat.md`).
|
|
119
|
-
(`src/commands/proposal/repository.ts`,
|
|
120
|
-
`src/commands/improve/memory/memory-improve.ts`,
|
|
121
|
-
`src/commands/lint/base-linter.ts`)
|
|
122
|
-
- **A promotion retires its source memory (O1).** When `akm proposal accept`
|
|
123
|
-
promotes a consolidate `promote` proposal — by a person or by triage
|
|
124
|
-
auto-promotion — it now archives the source memory (and its `.derived`
|
|
125
|
-
twin) through the same retire-archive path, tombstoned `reason: promoted`,
|
|
126
|
-
provided the source's body still matches the hash recorded when the
|
|
127
|
-
promotion was minted; an edit since then leaves the source alone (a
|
|
128
|
-
proposal minted before this hash existed is never archived, for the same
|
|
129
|
-
reason). A promotion no longer leaves a memory/knowledge duplicate behind.
|
|
130
|
-
Best-effort: a failure to archive the source only warns; the promotion
|
|
131
|
-
itself is not undone. (`src/commands/improve/consolidate.ts`,
|
|
132
|
-
`src/commands/proposal/repository.ts`)
|
|
133
|
-
- **Retirement continuity check (rule R3).** Before the pair pass mints a
|
|
134
|
-
`retire` proposal, it replays up to five of the retired asset's own past
|
|
135
|
-
`search`/`curate` queries through akm's own search, in-process — the
|
|
136
|
-
ranking a user actually gets, no LLM. For every query where the retired
|
|
137
|
-
asset ranked in the top 10, the successor must too; compared directly,
|
|
138
|
-
since search itself returns at most the top 10 hits. A failing
|
|
139
|
-
query never blocks the mint — the
|
|
140
|
-
proposal's `retirement.continuityRisk` records the failing query count
|
|
141
|
-
and, per failing query, the retired asset's rank and the successor's
|
|
142
|
-
(`null` when the successor did not rank in the top 10 at all). A proposal
|
|
143
|
-
carrying `continuityRisk` is excluded from every bulk accept path (`accept
|
|
144
|
-
--generator …`, with or without `--yes`) but not from bulk reject —
|
|
145
|
-
declining a flagged proposal is always the safe direction; a person can
|
|
146
|
-
always accept one by id. An asset with no recorded queries is not checked
|
|
147
|
-
at all. A query that never ran (the search call threw) or that fell back
|
|
148
|
-
to keyword-only ranking (an unreachable embedding endpoint, most often)
|
|
149
|
-
is never silently trusted or silently dropped either: it counts as
|
|
150
|
-
"unverified" and, on its own, is enough to flag `continuityRisk` — an
|
|
151
|
-
endpoint outage reads as "risk unknown," never as "no risk found," for
|
|
152
|
-
every proposal checked while it stays down, not just the first. The
|
|
153
|
-
first fallback in a pair-pass run forces every later query in that same
|
|
154
|
-
run to skip the semantic attempt entirely, so a dead endpoint costs one
|
|
155
|
-
failed attempt total, not one per remaining query. Two fixes against false
|
|
156
|
-
flags, measured on a real night-1 admission (300 pairs, 6 flags, 3
|
|
157
|
-
spurious): replayed queries are the same cleaned set the retrieval
|
|
158
|
-
regression gate uses (`loadRetrievalQueries`) — stash-README boilerplate,
|
|
159
|
-
harness/tool envelopes, pastes over 2,000 characters, and near-duplicate
|
|
160
|
-
queries (equal once whitespace is collapsed) are dropped before replay,
|
|
161
|
-
not just capped at five raw entries; and the check does not run at all
|
|
162
|
-
when the retired and successor bodies are content-identical once
|
|
163
|
-
whitespace is collapsed — search's own content-dedupe already hides the
|
|
164
|
-
successor behind the retired asset for every such query, so a "successor
|
|
165
|
-
missing" finding would not be a real risk.
|
|
166
|
-
(`src/commands/improve/consolidate/continuity-check.ts`,
|
|
167
|
-
`src/commands/proposal/proposal-types.ts`,
|
|
168
|
-
`src/commands/proposal/proposal.ts`)
|
|
169
|
-
- **Continuity-risk visibility, and a `--generator` filter for `proposal
|
|
170
|
-
list`.** A retire proposal carrying `retirement.continuityRisk` now
|
|
171
|
-
shows `⚠ continuity-risk` inline in the default `akm proposal list`
|
|
172
|
-
output (and `--format text`), not just in `proposal show`. `proposal
|
|
173
|
-
show`'s text output now lists the actual failing query text and rank per
|
|
174
|
-
query, not just a count, and separately reports `unverifiedQueries` when
|
|
175
|
-
the risk is (also, or only) an unverified query rather than a rank
|
|
176
|
-
failure. `akm proposal accept --generator … --dry-run` (and a real bulk
|
|
177
|
-
run) now reports `skippedForContinuityRisk`, the count of otherwise
|
|
178
|
-
matching proposals excluded specifically for this reason, apart from an
|
|
179
|
-
ordinary `--max-diff-lines`/`--older-than` miss. `akm proposal list` gains
|
|
180
|
-
a `--generator <name>` filter, the same value `accept`/`reject
|
|
181
|
-
--generator` already take, so the (potentially large) backlog of one
|
|
182
|
-
generator's retire proposals can be reviewed as its own list.
|
|
183
|
-
(`src/commands/proposal/proposal.ts`, `src/commands/proposal/proposal-cli.ts`,
|
|
184
|
-
`src/output/text/proposal-format.ts`, `src/output/shapes/helpers.ts`)
|
|
185
|
-
- **Archive purge sweep.** Deterministic, no LLM, run once at the very start
|
|
186
|
-
of every `akm improve` invocation, ahead of index bootstrap and triage.
|
|
187
|
-
For a git-backed bundle, deletes the archived asset file(s) of a
|
|
188
|
-
retirement — never its `cleanup.md` tombstone — once `retiredAt` is more
|
|
189
|
-
than 30 days old (`RETIRE_GRACE_DAYS`) AND every file under that
|
|
190
|
-
retirement's archive directory is git-tracked, clean (`git ls-files` plus
|
|
191
|
-
`git status --porcelain -uall`), and verifiable (`git ls-files -v`: a
|
|
192
|
-
file marked `assume-unchanged` or `skip-worktree` hides its own edits
|
|
193
|
-
from `git status`, so it is never trusted as clean either) — each checked
|
|
194
|
-
once per sweep; git history keeps the bytes. `.git` presence alone is not
|
|
195
|
-
enough: `proposal accept` only commits for a `kind: "git"` write target,
|
|
196
|
-
and improve's own auto-sync stages only the paths its own run wrote, so a
|
|
197
|
-
filesystem-kind bundle can carry archived retirements that were never
|
|
198
|
-
committed — the tracked/clean/verifiable check is what keeps the sweep
|
|
199
|
-
from deleting the only surviving copy of those. A directory with even one
|
|
200
|
-
untracked, modified, or unverifiable file (tombstone included) is left
|
|
201
|
-
whole for a later sweep — and so is the ENTIRE archive for that sweep if
|
|
202
|
-
the underlying `git status` or `git ls-files` call itself fails (a broken
|
|
203
|
-
submodule, for instance, can fail `git status` while `git ls-files`
|
|
204
|
-
still succeeds): an empty result from a failed check is never treated as
|
|
205
|
-
"nothing to protect", and the sweep warns once rather than silently
|
|
206
|
-
purging nothing. A memory-cleanup family-prune archive carries no
|
|
207
|
-
`retiredAt`, so this sweep never touches that older archive class. Every
|
|
208
|
-
deleted file is journaled individually, so the end-of-run auto-sync
|
|
209
|
-
commits the removal the same way it commits the archive move itself.
|
|
210
|
-
(`src/commands/improve/memory/memory-improve.ts`,
|
|
211
|
-
`src/sources/providers/git-stash.ts`, `src/commands/improve/improve.ts`)
|
|
212
|
-
- **`akm health`'s `memory-cleanup-archive` advisory now covers every
|
|
213
|
-
bundle**, not just one with no `.git` at all. A bundle with no `.git` of
|
|
214
|
-
its own keeps every retirement's archived bytes forever (there is no
|
|
215
|
-
history to fall back on, so the purge sweep never runs there), and its
|
|
216
|
-
size and file count are reported as before. A git-backed bundle can ALSO
|
|
217
|
-
carry archived bytes the purge sweep will never remove — `.git` presence
|
|
218
|
-
alone never proved a retirement was committed — so this now runs the same
|
|
219
|
-
tracked/clean/verifiable check the purge sweep itself uses and reports
|
|
220
|
-
how many files and bytes of the archive cannot currently be purged
|
|
221
|
-
(untracked, modified, or unverifiable), alongside the total. Silent
|
|
222
|
-
whenever there is nothing to say: the archive is empty or absent, or (for
|
|
223
|
-
a git-backed bundle) every byte in it is purgeable once it ages out.
|
|
224
|
-
(`src/commands/health/archive-usage.ts`, `src/commands/health/data-dir-usage.ts`)
|
|
225
|
-
|
|
226
|
-
### Removed
|
|
227
|
-
|
|
228
|
-
- **LLM metadata enrichment (`index.metadataEnhance`) is retired.** On 49
|
|
229
|
-
stratified queries, with every eligible candidate enriched (1,968
|
|
230
|
-
entries): search nDCG@10 moved −0.0092 [−0.0324, +0.0165], curate P@5
|
|
231
|
-
+0.000 [−0.037, +0.045], and long prompts lost −0.054 [−0.093, −0.012]. A
|
|
232
|
-
Doc2Query-- filter made it worse (P@5 −0.020 [−0.045, −0.004]). The pass
|
|
233
|
-
replaced authored descriptions on 89% of the entries it rewrote, and a
|
|
234
|
-
full pass costs about 27 B70-hours (RS-D, owner ruling 2026-09-28). It was
|
|
235
|
-
already off by default and off in the maintainer's config. The LLM call
|
|
236
|
-
(`src/llm/metadata-enhance.ts`), its `akm index` dispatch, and the
|
|
237
|
-
`metadata_enhance` feature-gate key are gone; the deterministic metadata
|
|
238
|
-
pass, `quality: "generated"`, and memory inference are unaffected. A
|
|
239
|
-
config that still sets `index.metadataEnhance` loads, named once by the
|
|
240
|
-
same unknown-config-key path every other retired key uses: kept in
|
|
241
|
-
memory, round-trips through ordinary writes, and is dropped only by
|
|
242
|
-
`akm migrate apply`. The pass's `llm_enrichment_cache` rows (the default
|
|
243
|
-
`cache_variant`; graph and memory inference use their own named variants)
|
|
244
|
-
are deleted on the next writable open of `index.db`. An index built while
|
|
245
|
-
enrichment was on keeps its entries' LLM-written descriptions on
|
|
246
|
-
incremental runs — nothing rewrites an unchanged row; run
|
|
247
|
-
`akm index --full` once to replace them with the deterministic ones.
|
|
248
|
-
(`src/indexer/indexer.ts`, `src/llm/feature-gate.ts`,
|
|
249
|
-
`src/core/config/config.ts`, `src/core/config/schema/index-config.ts`,
|
|
250
|
-
`src/storage/repositories/index-schema.ts`)
|
|
251
|
-
- **The LLM entity-graph extraction pass.** `akm improve`'s per-file
|
|
252
|
-
entity/relation extraction, its persisted tables (`graph_meta`,
|
|
253
|
-
`graph_files`, `graph_file_entities`, `graph_file_relations`), and `akm
|
|
254
|
-
show`'s `related` list are gone. On the navigation eval, vector kNN beat
|
|
255
|
-
the LLM `related` list by 0.157 P@5 [0.051, 0.260]; the ranking boost it
|
|
256
|
-
once fed was already removed in 0.9.17-alpha.4. Declared links (#935,
|
|
257
|
-
alpha.8) are the only navigation surface `akm show` has now, and curate's
|
|
258
|
-
support refs already came from them, not the graph. index.db is a
|
|
259
|
-
regenerable cache, so the graph tables are dropped unconditionally on the
|
|
260
|
-
next writable open — nothing migrates or backs them up.
|
|
261
|
-
- **The `graph-refresh` improve strategy** and the `akm-graph-refresh-weekly`
|
|
262
|
-
task template are deleted. Naming `graph-refresh` via `--strategy` or a task
|
|
263
|
-
now fails with a message naming the retirement, unconditionally — even when
|
|
264
|
-
`improve.strategies["graph-refresh"]` still has a leftover override block
|
|
265
|
-
from customizing the built-in (the message names it; `akm migrate apply`
|
|
266
|
-
drops it). `defaults.improveStrategy: "graph-refresh"` still loads config
|
|
267
|
-
successfully — the refusal happens lazily, when the strategy is actually
|
|
268
|
-
resolved, not at every command's config load.
|
|
269
|
-
- **Retired config keys:** `index.graph.*` and every strategy's
|
|
270
|
-
`processes.graphExtraction.*`. An old config that still sets them keeps
|
|
271
|
-
loading and the keys are unread, but `index.graph` is now also named once
|
|
272
|
-
by the unknown-config-key warning (it previously validated silently
|
|
273
|
-
against the generic per-pass catchall) and, like any other retired key,
|
|
274
|
-
is dropped only by `akm migrate apply` — not by an ordinary config write.
|
|
275
|
-
- **`akm health` drops every graph metric** — the KPI card, summary-table
|
|
276
|
-
rows, per-run duration/entity/relation columns, and the
|
|
277
|
-
`improve.graphExtraction.failures` window-compare delta. `--window-compare`
|
|
278
|
-
and `--group-by run` still count every run, including a pre-alpha.9 one:
|
|
279
|
-
its stored `graphExtraction`/`graphExtractionDurationMs` result fields and
|
|
280
|
-
its `graph-extraction` `plan.stages` / `graphExtraction` `plan.processes`
|
|
281
|
-
entries still decode, read-only, same as any other retired field (AGENTS.md
|
|
282
|
-
"Reading persisted data") — they are just no longer rendered. The
|
|
283
|
-
`improve_completed` event's `graphExtractionExtractedFiles`,
|
|
284
|
-
`graphExtractionDurationMs`, `graphCoverage`, `graphDensity`, and
|
|
285
|
-
`graphEntities` metadata fields are no longer emitted.
|
|
286
|
-
- **The per-run forgetting-safety lane.** `scoreSalience`'s stash-wide
|
|
287
|
-
salience-rank comparison, `applyForgettingSafety`, and the
|
|
288
|
-
`improve_salience_rank_change` event are gone. It was a one-time WS-1
|
|
289
|
-
cutover guard from the June 2026 ranking-formula change that had kept
|
|
290
|
-
running on every improve run since; the last 30 days of events
|
|
291
|
-
(2026-08-30 to 2026-09-29: 47 `improve_salience_rank_change` events, 5
|
|
292
|
-
refs flagged across 4 runs — 09-05, 09-08 x2, 09-19, 09-28) showed no
|
|
293
|
-
marginal pick over the signal-delta lane and the retrieval scope: 4 of
|
|
294
|
-
the 5 flagged refs were also picked that same run by signal-delta
|
|
295
|
-
(adjacent event ids/timestamps, 2–26 minutes after the rank-change
|
|
296
|
-
event), and the 5th (`workflows/create-github-issues-from-spec`, flagged
|
|
297
|
-
09-05) has no `reflect_invoked` or `distill_invoked` event in the
|
|
298
|
-
retained history, but that run's `improve_runs.plannedRefs` shows it,
|
|
299
|
-
too, was planned under `signal-delta` — just not reflected (a
|
|
300
|
-
dispatch/budget limit that run, not a lane-exclusive pick). All 5
|
|
301
|
-
flagged refs were signal-delta picks; zero were forgetting-safety-only.
|
|
302
|
-
It also protected
|
|
303
|
-
`asset_salience.rank_score`, which only improve itself ever read — a rank
|
|
304
|
-
drop could not hide anything from search. `buildRankChangeReport` (its
|
|
305
|
-
comparator) is also gone: the new retirement continuity check (see
|
|
306
|
-
Added) compares ranks directly instead, and nothing else called it.
|
|
307
|
-
`forgetting-safety` stays a valid `eligibilitySource`/event-type
|
|
308
|
-
value so old proposals and events still decode, but nothing assigns or
|
|
309
|
-
emits it any more. (`src/commands/improve/preparation.ts`,
|
|
310
|
-
`src/commands/improve/salience.ts`, `src/core/events.ts`,
|
|
311
|
-
`src/storage/repositories/salience-repository.ts`)
|
|
312
|
-
- **`improve.strategies.<name>.processes.consolidate.incrementalSince` and
|
|
313
|
-
`.neighborsPerChanged`.** The consolidate pair pass is now the candidate
|
|
314
|
-
generator, narrowing per initiator through the improve ledger rather than
|
|
315
|
-
a global time window; neither key was set anywhere in the owner's config.
|
|
316
|
-
`narrowToIncrementalCandidates` goes with them, along with its
|
|
317
|
-
now-orphaned `parseSinceToIsoLenient` helper. A config that still sets
|
|
318
|
-
either key keeps loading under the retired-key contract: named once as
|
|
319
|
-
unknown, it survives an ordinary config write, and only `akm migrate
|
|
320
|
-
apply` drops it. (`src/core/config/schema/improve-processes.ts`,
|
|
321
|
-
`src/commands/improve/consolidate.ts`, `src/core/time.ts`,
|
|
322
|
-
`docs/reference/configuration.md`)
|
|
9
|
+
## [0.9.18] - 2026-09-29
|
|
323
10
|
|
|
324
11
|
### Fixed
|
|
325
12
|
|
|
326
|
-
- **
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
`
|
|
341
|
-
`
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
`akm index` now records the links a bundle already declares (`xrefs`,
|
|
346
|
-
`supersededBy`, a `.derived` memory's parent, wiki sources, page links, and
|
|
347
|
-
workflow and task targets) as typed links, with no model. `akm show` lists
|
|
348
|
-
them, and curate's support refs come from them instead of the LLM entity
|
|
349
|
-
graph. The index moves to layout 26 in place on its first writable open;
|
|
350
|
-
0.9.17-alpha.4 through alpha.7 cannot open it. `index.graph.enabled: false`
|
|
351
|
-
now stops graph extraction in `akm improve`, and a partly failed extraction is
|
|
352
|
-
retried. `akm migrate` converts a v2 or v3 task file to v4 in one pass, and
|
|
353
|
-
the launchers no longer lose a signal that arrives before their child starts.
|
|
13
|
+
- **A running `akm improve` no longer loses its SQLite file locks, which let
|
|
14
|
+
an older `sqlite3` delete `state.db`'s WAL.** Copying a database file
|
|
15
|
+
inside a process that also holds it open drops every POSIX lock the process
|
|
16
|
+
holds on it, and `akm improve` did exactly that (its read snapshots copy
|
|
17
|
+
`state.db`, hundreds of times a run). A peer on SQLite older than 3.51 (the
|
|
18
|
+
system `sqlite3` command, Python's `sqlite3` module) that then opened
|
|
19
|
+
`state.db` read-write, even just for `.backup`, saw no reader and deleted
|
|
20
|
+
`state.db-wal`/`-shm` at close, leaving colliding rowids, stale index
|
|
21
|
+
entries and lost rows. Affected: every release since 0.9.2 through the
|
|
22
|
+
snapshot, and 0.9.2 through 0.9.16 also on every `openStateDatabase` (fixed
|
|
23
|
+
in 0.9.17-alpha.4). The snapshot now copies in a child `cp` (Windows keeps
|
|
24
|
+
the in-process copy; its locks belong to the handle), and `improve` reads
|
|
25
|
+
proposals through its own connection instead of snapshotting per asset.
|
|
26
|
+
`akm health`'s `state-db-integrity` check now runs `PRAGMA integrity_check`,
|
|
27
|
+
since `quick_check` does not compare indexes with their tables and reported
|
|
28
|
+
this damage as `ok`. On earlier releases, open a live database only with
|
|
29
|
+
`sqlite3 -readonly`.
|
|
30
|
+
|
|
31
|
+
## [0.9.17] - 2026-09-29
|
|
354
32
|
|
|
355
33
|
### Added
|
|
356
34
|
|
|
357
|
-
- **Declared links (#935).**
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
`
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
`
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
`
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
`
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
- **Index layout 26.** The first writable open of an older index derives
|
|
423
|
-
every entry's links from its stored `document_json` in place, reading no
|
|
424
|
-
file: on the 23,979-entry snapshot index that takes 0.8 s and adds 3.5 MB.
|
|
425
|
-
Earlier layouts never stored a workflow's or a task's targets, so only the
|
|
426
|
-
directories holding workflows and tasks re-read on the next `akm index`.
|
|
427
|
-
The open leaves `index_meta.vacuumPending` like every layout migration.
|
|
428
|
-
0.9.17-alpha.4 through alpha.7 refuse an index at layout 26
|
|
429
|
-
(`INDEX_SCHEMA_INCOMPATIBLE`, naming the upgrade), for writing as well as
|
|
430
|
-
reading, so going back to one of them needs a new index: move `index.db`
|
|
431
|
-
aside and run `akm index` under that release (the LLM graph and enrichment
|
|
432
|
-
cache it held are not rebuilt by `akm index`).
|
|
433
|
-
(`src/storage/repositories/index-schema.ts`,
|
|
434
|
-
`src/storage/repositories/index-entry-schema.ts`)
|
|
435
|
-
|
|
436
|
-
### Fixed
|
|
437
|
-
|
|
438
|
-
- **`index.graph.enabled: false` stops graph extraction in `akm improve`.**
|
|
439
|
-
The switch was read only when improve had no strategy plan, which is never
|
|
440
|
-
the case in a real run, so the nightly and weekly graph tasks kept
|
|
441
|
-
extracting with it set. Improve now skips its graph extraction stage
|
|
442
|
-
whenever `index.graph.enabled` is `false`, whatever the strategy enables.
|
|
443
|
-
This also makes the graph ablation harness's "graph off" arm, which sets
|
|
444
|
-
exactly this key, turn extraction off. Improve still does not read
|
|
445
|
-
`index.defaults` when it picks the engine for graph extraction.
|
|
446
|
-
(`src/commands/improve/loop-stages.ts`)
|
|
447
|
-
- **Graph extraction never has more calls in flight than the run's
|
|
448
|
-
concurrency.** Batches run side by side up to the runner's concurrency, and
|
|
449
|
-
each batch also sent its per-file calls (long bodies, a non-array response)
|
|
450
|
-
up to that limit at once, so at a concurrency of 2 four calls could be in
|
|
451
|
-
flight. A batch now makes its per-file calls one at a time. At the default
|
|
452
|
-
concurrency of 1 nothing changes. (`src/llm/graph-extract.ts`)
|
|
453
|
-
- **A long document whose extraction partly failed is extracted again.** A
|
|
454
|
-
body over 1,600 characters is extracted in chunks. When some chunks failed
|
|
455
|
-
(a timeout, an error, an empty response) and others found entities, the
|
|
456
|
-
file was recorded and cached as extracted, so the failed chunks were never
|
|
457
|
-
retried. Such a file is now recorded as failed and not cached, and the next
|
|
458
|
-
run extracts it again, every chunk: partial failures are rare outside
|
|
459
|
-
provider outages, and keeping per-chunk results to skip the chunks that
|
|
460
|
-
succeeded would need a second cache. Until then the entities the other
|
|
461
|
-
chunks found are stored when the file had no graph rows yet; a file with
|
|
462
|
-
rows keeps them. (`src/llm/graph-extract.ts`)
|
|
463
|
-
- **`scripts/node-runtime/akm` could die from a raw signal instead of
|
|
464
|
-
forwarding it to its child.** The launcher registered its
|
|
465
|
-
SIGTERM/SIGINT/SIGHUP forwarding listeners only after spawning the child;
|
|
466
|
-
under load, a signal could arrive in that window and fall through to the
|
|
467
|
-
runtime's default (process-terminating) disposition, killing the launcher
|
|
468
|
-
before the child ever saw it. The listeners now go up before anything else
|
|
469
|
-
runs, with a small queue for a signal that arrives before the child exists.
|
|
470
|
-
- **`scripts/node-runtime/akm-migrate` carried the same pre-spawn signal
|
|
471
|
-
race** as `scripts/node-runtime/akm` above, for the same reason (listeners
|
|
472
|
-
registered only after `spawn()`), with no test covering it. Fixed the same
|
|
473
|
-
way, and added `tests/integration/akm-migrate-signal-forwarding.test.ts`
|
|
474
|
-
(modelled on `launcher-signal-forwarding.test.ts`) for its forwarding.
|
|
475
|
-
- **The launcher signal tests failed intermittently because of their own
|
|
476
|
-
fixture.** The fake child wrote its ready file before it registered its
|
|
477
|
-
signal handler, so a forwarded signal could reach it in between and kill it,
|
|
478
|
-
and the launcher then reported that signal. Each fixture now registers its
|
|
479
|
-
handler first. The launcher's pre-spawn window above could not cause this:
|
|
480
|
-
the tests signal only after the child is running.
|
|
481
|
-
|
|
482
|
-
## [0.9.17-alpha.7] - 2026-09-28
|
|
483
|
-
|
|
484
|
-
A scheduled task is now just a command and a schedule. Each native row
|
|
485
|
-
carries its own `AKM_BUNDLE_DIR` instead of pointing at a descriptor file,
|
|
486
|
-
and the runtime reads only v4 task files; older files convert once with
|
|
487
|
-
`akm migrate apply`. The first `akm task sync` after upgrading rewrites each
|
|
488
|
-
row once, keeping every task and schedule. `akm improve` reworks only assets
|
|
489
|
-
that retrieval returned or that are new, and reflect refuses a rewrite that
|
|
490
|
-
grades worse on the asset's own searches. `--require-engines` no longer
|
|
491
|
-
skips a run because the LLM endpoint is busy.
|
|
492
|
-
|
|
493
|
-
### Changed
|
|
494
|
-
|
|
495
|
-
- **akm reads only task source v4.** A `version: 2` or `version: 3` task
|
|
496
|
-
file, or a `version: 4` file that still carries 0.9.15's retired
|
|
497
|
-
`schedule[].enabled`, now fails on its own with a message naming
|
|
498
|
-
`akm migrate apply`, which converts it once, under a backup (`akm upgrade`
|
|
499
|
-
runs it after an install). Until now every read converted such a file in
|
|
500
|
-
memory. `akm task sync` reports each one as a failure, leaves its installed
|
|
501
|
-
row as it is, and keeps reconciling every other task; `akm task run`,
|
|
502
|
-
`akm lint` and `akm task validate` report it the same way, and
|
|
503
|
-
`akm task validate`'s `converts` outcome is gone (such a file is
|
|
504
|
-
`blocked`). `akm migrate apply` now also converts the tasks of the stash
|
|
505
|
-
`AKM_BUNDLE_DIR` selects when no configured bundle names it, since the
|
|
506
|
-
runtime reads those too, and a root whose top-level task files are all
|
|
507
|
-
v2/v3 is still detected as an `akm-task` bundle, so they are found and
|
|
508
|
-
converted. A host whose task files are all v4 (`akm migrate status`
|
|
509
|
-
reports `current`) sees no difference.
|
|
510
|
-
(`src/tasks/source/parse-task-source.ts`, `src/commands/tasks/validate.ts`,
|
|
511
|
-
`scripts/akm-migrate/task-migrate.ts`,
|
|
512
|
-
`src/core/adapter/adapters/akm-task-adapter.ts`)
|
|
513
|
-
- **A scheduled row is its command plus its schedule, and carries its own
|
|
514
|
-
context.** Rows no longer name a `--scheduler-context` descriptor file;
|
|
515
|
-
they set what it held themselves. Every row sets `AKM_BUNDLE_DIR` to the
|
|
516
|
-
working stash of the shell that ran `akm task sync`, plus any
|
|
517
|
-
`AKM_CONFIG_DIR`, `AKM_DATA_DIR`, `AKM_CACHE_DIR` or `AKM_STATE_DIR` that
|
|
518
|
-
shell set explicitly: a `VAR=value` prefix in the crontab, an
|
|
519
|
-
`EnvironmentVariables` entry in a launchd plist, a `$env:VAR='value';`
|
|
520
|
-
assignment ahead of the command in Task Scheduler (its action has no
|
|
521
|
-
environment of its own). Scheduled runs see the same environment as
|
|
522
|
-
before, and sync still tells installations sharing a crontab apart by
|
|
523
|
-
that path (#846).
|
|
524
|
-
**What hosts see:** the first `akm task sync` after upgrading rewrites
|
|
525
|
-
every akm row once. `akm task sync --dry-run` lists each one as an update,
|
|
526
|
-
never an add or a remove; each keeps its launcher and its schedule, and
|
|
527
|
-
the `--scheduler-context <file>` argument becomes an inline
|
|
528
|
-
`AKM_BUNDLE_DIR=<working stash>`. On a host whose working stash is
|
|
529
|
-
`/home/u/akm` a row changes from
|
|
530
|
-
|
|
531
|
-
```text
|
|
532
|
-
30 8 * * * /home/u/.bun/bin/bun /home/u/.bun/lib/node_modules/akm-cli/dist/akm --scheduler-context /home/u/.local/share/akm/tasks/context/e898….json task run capture --bundle akm --scheduled > /home/u/.cache/akm/tasks/logs/capture.log 2>&1
|
|
533
|
-
```
|
|
534
|
-
|
|
535
|
-
to
|
|
536
|
-
|
|
537
|
-
```text
|
|
538
|
-
30 8 * * * AKM_BUNDLE_DIR=/home/u/akm /home/u/.bun/bin/bun /home/u/.bun/lib/node_modules/akm-cli/dist/akm task run capture --bundle akm --scheduled > /home/u/.cache/akm/tasks/logs/capture.log 2>&1
|
|
539
|
-
```
|
|
540
|
-
|
|
541
|
-
Rows written by 0.9.0 through 0.9.17-alpha.6 keep firing until that sync:
|
|
542
|
-
the CLI still accepts `--scheduler-context <file>` and applies the file's
|
|
543
|
-
environment (PATH included, for a 0.9.16 row). The files under
|
|
544
|
-
`$DATA/tasks/context/` are no longer written, and the uid, mode, symlink
|
|
545
|
-
and content-hash checks made on every scheduled run are gone. A sync
|
|
546
|
-
leaves some rows as they are (one whose task file failed to load, one a
|
|
547
|
-
`--bundle` sync did not cover), and those still name their file: once
|
|
548
|
-
`akm task doctor` lists no binding with a `contextPath`, nothing reads
|
|
549
|
-
them and they can be deleted. `akm task prune` now
|
|
550
|
-
finds rows whose `AKM_BUNDLE_DIR` names a directory that is gone, and older
|
|
551
|
-
rows whose descriptor cannot be read; `akm task doctor` lists `contextPath`
|
|
552
|
-
only for an older row. (`src/tasks/scheduler-invocation.ts`,
|
|
553
|
-
`src/tasks/backends/cron.ts`, `src/tasks/backends/launchd.ts`,
|
|
554
|
-
`src/tasks/backends/schtasks.ts`, `src/tasks/scheduler-sync.ts`,
|
|
555
|
-
`src/commands/tasks/tasks.ts`)
|
|
556
|
-
- **`akm improve` reworks only what gets read (#986).** An asset with fresh
|
|
557
|
-
feedback, or one you name (`akm improve skills/x`), is handled as before.
|
|
558
|
-
Every other pick must now be in the retrieval scope. That covers the
|
|
559
|
-
proactive-maintenance, high-salience and forgetting-safety lanes, and the
|
|
560
|
-
memories consolidation judges. An asset is in scope if a user `search`,
|
|
561
|
-
`curate` or `show` returned it, or user `feedback` named it, in the last 90
|
|
562
|
-
days, which is the usage log's retention. A hit on a `.derived` memory counts
|
|
563
|
-
for its parent. New material that no improve stage has processed yet is also
|
|
564
|
-
in scope. There is no new config key.
|
|
565
|
-
|
|
566
|
-
Measured with `akm improve --dry-run` on a copy of the maintainer's bundle
|
|
567
|
-
(19,870 assets), against 0.9.17-alpha.6:
|
|
568
|
-
- The fallback lanes pick from 6,450 assets instead of 15,686, and 9,236
|
|
569
|
-
refs are left out. No lane setting can reach the unread tail any more. In
|
|
570
|
-
July the proactive lane rewrote 3,069 assets, and 3,059 of them had not
|
|
571
|
-
been retrieved since the usage log began on 1 July.
|
|
572
|
-
- Consolidation judges 59 memories instead of 69.
|
|
573
|
-
- The high-salience lane no longer admits distill outputs nobody has read (2
|
|
574
|
-
today).
|
|
575
|
-
- Under the scheduled caps, today's nightly work is unchanged. The default
|
|
576
|
-
strategy selects the same 50 feedback-driven refs, and weekly proactive
|
|
577
|
-
maintenance selects the same 25, because salience ranking already puts
|
|
578
|
-
retrieved assets first.
|
|
579
|
-
|
|
580
|
-
`akm improve --dry-run` and the run result report the left-out refs as a new
|
|
581
|
-
`retrieval` gate. Health reports them under the skip reason `not_retrieved`.
|
|
582
|
-
Improve results stored by earlier releases, which have no such gate, still
|
|
583
|
-
decode. (`src/commands/improve/retrieval-scope.ts`,
|
|
584
|
-
`src/commands/improve/preparation.ts`, `src/commands/improve/consolidate.ts`)
|
|
585
|
-
|
|
586
|
-
- **Reflect refuses a rewrite that makes an asset worse for its own searches
|
|
587
|
-
(#722).** Before reflect proposes a rewrite of an existing asset, it grades
|
|
588
|
-
the old and the new content on up to five of the queries that actually
|
|
589
|
-
retrieved the asset (user `search` and `curate`). It uses the retrieval
|
|
590
|
-
eval's relevance prompt, which agrees with human grades at kappa 0.83. When
|
|
591
|
-
the new content grades lower on average, the rewrite is refused the same way
|
|
592
|
-
a quality-judge rejection is: `quality_rejected`, with the 14-day reflect
|
|
593
|
-
window. An asset without retrieval queries is not graded.
|
|
594
|
-
|
|
595
|
-
This was measured before it was built. Of 60 accepted rewrites since July,
|
|
596
|
-
judged this way, 14 graded lower (23%, 95% CI 14–35%) and 12 graded higher.
|
|
597
|
-
The gate was built because the lower bound cleared the 10% threshold set
|
|
598
|
-
before any judging. It costs two judge calls per query on the engine that
|
|
599
|
-
already runs the quality judge. On the maintainer's 2026-09-28 nightly run,
|
|
600
|
-
whose 30 rewrites had 102 usable queries, that is 204 calls, about 6 more
|
|
601
|
-
minutes on a 73-minute run. (`src/commands/improve/retrieval-gate.ts`,
|
|
602
|
-
`src/commands/improve/reflect.ts`)
|
|
603
|
-
|
|
604
|
-
### Fixed
|
|
605
|
-
|
|
606
|
-
- **A cron row too long for one line is seen by `akm task sync` again.** A
|
|
607
|
-
command over 1,000 bytes runs from a wrapper script, and sync could not
|
|
608
|
-
read which task such a row ran: every sync, and every `--dry-run`, showed
|
|
609
|
-
it as an add and wrote it again. Sync now reads the script, so the row is
|
|
610
|
-
unchanged or an update like any other. (`src/tasks/backends/cron.ts`)
|
|
611
|
-
- **A `$` or a backslash in a scheduled row's value is kept.** launchd and
|
|
612
|
-
Task Scheduler rows passed their values through a string replacement that
|
|
613
|
-
read `$'`, `$&` and `$$` as patterns, so a path such as a Windows admin
|
|
614
|
-
share (`\\nas\share\akm$`) came out corrupted; reading a crontab row
|
|
615
|
-
back dropped a backslash inside a single-quoted value.
|
|
616
|
-
(`src/tasks/backends/launchd.ts`, `src/tasks/backends/schtasks.ts`,
|
|
617
|
-
`src/tasks/backends/cron.ts`)
|
|
618
|
-
- **`akm improve --require-engines` no longer skips a run because the LLM
|
|
619
|
-
endpoint is busy.** Its reachability probe, one short completion, gave up
|
|
620
|
-
after 3 seconds, so a local server busy with another job looked
|
|
621
|
-
unreachable and the whole scheduled run failed (all four scheduled runs on
|
|
622
|
-
2026-09-27). The probe now waits up to the engine's own `timeoutMs`, at
|
|
623
|
-
most two minutes, so a busy server can answer while a hung one still fails
|
|
624
|
-
fast. The error's hint now says to check the endpoint rather than to run
|
|
625
|
-
`akm setup`.
|
|
626
|
-
(`src/commands/improve/improve-cli.ts`)
|
|
627
|
-
|
|
628
|
-
## [0.9.17-alpha.6] - 2026-09-27
|
|
629
|
-
|
|
630
|
-
Graph extraction stops losing and wasting work. A timed-out extraction is
|
|
631
|
-
retried instead of cached as empty. Long documents are extracted once, and
|
|
632
|
-
per-file calls respect the run's concurrency. `akm improve` honors
|
|
633
|
-
`index.graph`. `akm curate` returns nothing for harness and tool envelopes,
|
|
634
|
-
and search and curate show identical content once. Lazy graph extraction,
|
|
635
|
-
which never ran under Bun, is removed.
|
|
636
|
-
|
|
637
|
-
### Changed
|
|
638
|
-
|
|
639
|
-
- **`akm curate` returns nothing, on purpose, for input that is not a task.**
|
|
640
|
-
A harness or tool envelope (input that starts with an XML-style tag and
|
|
641
|
-
contains a closing tag, such as `<task-notification>…</task-notification>`,
|
|
642
|
-
`<system-reminder>…` or `<cross-session-message …>…`) and the stash README
|
|
643
|
-
line each used to get `--limit` unrelated assets. Every caller of
|
|
644
|
-
`akm curate` (the CLI, the OpenCode plugin, other harnesses) now gets an
|
|
645
|
-
empty `items` list with a `summary` that starts with `Curate abstained` and
|
|
646
|
-
names the reason, and a `tip`. On the retrieval suite curate abstains on 57
|
|
647
|
-
of 60 recorded non-task inputs and on none of the 221 real queries (nor on
|
|
648
|
-
any of 5,725 mined task queries). Length is not a reason to abstain: the
|
|
649
|
-
other 3 are task prompts of 2,431–5,531 characters, and in a judged sample
|
|
650
|
-
of 30 inputs over 2,000 characters the top 5 held a relevant asset for 24
|
|
651
|
-
of them (P@5 0.42, against 0.46 for prompts of 400–2,000 characters).
|
|
652
|
-
(`src/commands/read/curate.ts`)
|
|
653
|
-
- **Search and curate return identical content once.** Of entries whose
|
|
654
|
-
indexed content is identical (the same body saved under another name, as
|
|
655
|
-
both a memory and a knowledge doc, or in another bundle), only the
|
|
656
|
-
highest-ranked is kept, and the next candidate takes the freed slot. On the
|
|
657
|
-
retrieval suite such copies filled 7.5% of curate's top 5. Unique
|
|
658
|
-
precision@5, where a copy of a higher-ranked result earns nothing, rises
|
|
659
|
-
from 0.467 to 0.514 (+0.046, 95% CI [+0.028, +0.067]), and the share of
|
|
660
|
-
top-5 slots that repeat a higher-ranked result falls from 0.131 to 0.055.
|
|
661
|
-
Plain P@5 (0.553 → 0.551) and nDCG@10 stay within noise: they counted each
|
|
662
|
-
copy as another relevant result. Latency is unchanged.
|
|
663
|
-
(`src/indexer/search/db-search.ts`)
|
|
664
|
-
|
|
665
|
-
### Removed
|
|
666
|
-
|
|
667
|
-
- **The unused `utility_scores_scoped` index table is gone.** It shipped in
|
|
668
|
-
0.9.17-alpha.5 for per-project scoped utility scores, but no code ever read
|
|
669
|
-
or wrote a row. An index database drops it on its next writable open, the
|
|
670
|
-
same way other retired derived tables are dropped, with no layout-version
|
|
671
|
-
change. (`src/storage/repositories/index-schema.ts`)
|
|
672
|
-
- **Lazy graph extraction in `akm show` and `akm curate`.** With
|
|
673
|
-
`index.graph.lazyGraphExtraction: true`, `show` extracted an asset's graph
|
|
674
|
-
after building its response, so only the next `show` saw it. `curate`
|
|
675
|
-
queued assets for a later pass, which drained only the working bundle's
|
|
676
|
-
queue, and extractions made this way wrote no cache entry. Under Bun neither
|
|
677
|
-
path ever ran: the "already has a graph" check read a missing row as
|
|
678
|
-
present. Graph extraction now runs only in `akm improve`. The
|
|
679
|
-
`graph_extraction_queue` table is dropped the next time the index is opened
|
|
680
|
-
for writing. A config that still sets the key loads, and the key is named
|
|
681
|
-
once as unknown. (`src/commands/read/show.ts`,
|
|
682
|
-
`src/commands/read/curate.ts`, `src/indexer/graph/graph-extraction.ts`,
|
|
683
|
-
`src/storage/repositories/index-schema.ts`)
|
|
684
|
-
|
|
685
|
-
### Fixed
|
|
686
|
-
|
|
687
|
-
- **`index.metadataEnhance`'s default is no longer contradicted by dead
|
|
688
|
-
code.** Metadata enhancement has always defaulted to off
|
|
689
|
-
(`isLlmFeatureEnabled`); a second, unreachable code path in
|
|
690
|
-
`isProcessEnabled` claimed the opposite default and had no caller. Removed,
|
|
691
|
-
so one default remains. (`src/llm/feature-gate.ts`)
|
|
692
|
-
- **Eval tooling and docs catch up to the current config and index shape.**
|
|
693
|
-
`scripts/akm-eval/src/curate-bench.ts` wrote the retired `sources` config
|
|
694
|
-
key and called a nonexistent `akm index --dir`; it now seeds its sandbox
|
|
695
|
-
the same way the other akm-eval scripts and integration tests do, and
|
|
696
|
-
drops `--dir`. The graph A/B ablation harness
|
|
697
|
-
(`scripts/akm-eval/src/graph-ablation.ts`) planted its "graph off" config
|
|
698
|
-
where the sandboxed `akm` never read it, with config keys that didn't gate
|
|
699
|
-
anything (one of them a type error); it now writes
|
|
700
|
-
`index.graph.enabled: false` to the sandbox's actual `AKM_CONFIG_DIR`.
|
|
701
|
-
Updated `scripts/akm-eval/README.md` and `docs/maintainers/eval.md` to
|
|
702
|
-
match, and corrected stale `docs/architecture/architecture.md` references
|
|
703
|
-
to `db-backup`, `staleness-detect`, and `src/commands/graph/`.
|
|
704
|
-
- **Scheduled graph extraction reads `index.graph`.** `akm improve` passed
|
|
705
|
-
graph extraction a batch size of 4 and the `memory` and `knowledge` types
|
|
706
|
-
whenever the strategy's `processes.graphExtraction` did not set them, so
|
|
707
|
-
`index.graph.graphExtractionBatchSize` and `graphExtractionIncludeTypes`
|
|
708
|
-
never applied. It did not read `index.graph`'s `engine`, `model`,
|
|
709
|
-
`timeoutMs` or `llm` either, so a setting such as
|
|
710
|
-
`index.graph.llm.enableThinking: false` had no effect on improve runs. A
|
|
711
|
-
value in the strategy's `processes.graphExtraction` still wins. A setting it
|
|
712
|
-
leaves unset now comes from `index.graph`, then from the built-in default.
|
|
713
|
-
Where `index.graph` asks for something improve did not use before, the
|
|
714
|
-
extractor changes and cached extractions stop applying, so those files are
|
|
715
|
-
extracted again. (`src/commands/improve/loop-stages.ts`,
|
|
716
|
-
`src/commands/improve/execution.ts`,
|
|
717
|
-
`src/commands/improve/improve-strategies.ts`)
|
|
718
|
-
- **A graph extraction that times out is retried, not cached as empty.** A
|
|
719
|
-
call that ran past the engine's `timeoutMs` was recorded as "no entities"
|
|
720
|
-
and cached, so the file was never extracted again. It is now recorded as
|
|
721
|
-
failed, and the next run retries it; timeouts also count toward the run's
|
|
722
|
-
failure-rate abort. A batch that times out fails its files without then
|
|
723
|
-
calling the model once per file. An empty response is likewise recorded as
|
|
724
|
-
failed. (`src/llm/graph-extract.ts`)
|
|
725
|
-
- **Long bodies are extracted once after batching turns itself off.** Two
|
|
726
|
-
non-array batch responses turn batching off for the rest of a run. From
|
|
727
|
-
then on, a body over 1,600 characters was extracted on its own and then
|
|
728
|
-
again with the rest of its batch. Each body is now extracted once.
|
|
729
|
-
(`src/llm/graph-extract.ts`)
|
|
730
|
-
- **A batch's per-file calls respect the run's concurrency.** When a batch
|
|
731
|
-
fell back to one call per file (long bodies, a non-array response, batching
|
|
732
|
-
turned off), those calls all went out at once, up to the batch size. Local
|
|
733
|
-
endpoints serve one or two requests at a time. The calls now run within the
|
|
734
|
-
limit the run applies to its batches, one at a time by default.
|
|
735
|
-
(`src/llm/graph-extract.ts`)
|
|
736
|
-
- **`related` counts a shared entity once.** `akm show`'s `related` list,
|
|
737
|
-
and curate's support refs taken from it, ranked files by the number of
|
|
738
|
-
matching entity rows. A file holding two case forms of one entity, as rows
|
|
739
|
-
from older extractors can, counted it twice and could outrank a file that
|
|
740
|
-
shared two entities. `related` now counts distinct entities. Extraction also
|
|
741
|
-
keeps one form of each entity before writing. The stored key `related`
|
|
742
|
-
matches on is now the one extraction deduplicates on, which also drops
|
|
743
|
-
surrounding quotes and backticks.
|
|
744
|
-
(`src/indexer/graph/graph-related.ts`,
|
|
745
|
-
`src/indexer/graph/graph-extraction.ts`, `src/indexer/db/graph-db.ts`)
|
|
746
|
-
- **A config change that re-extracts the graph says so.** Cached graph
|
|
747
|
-
extractions are keyed by extractor: model, batch size, included asset types
|
|
748
|
-
and prompt version. Changing any of them made every cached file extract
|
|
749
|
-
again without a word. The first run after such a change now warns once,
|
|
750
|
-
naming the change and the number of cached files it will extract again, and
|
|
751
|
-
records the warning in the run's result.
|
|
752
|
-
(`src/indexer/graph/graph-extraction.ts`)
|
|
753
|
-
- **Graph extraction reports what its parser filtered.** A run's graph
|
|
754
|
-
telemetry, part of `akm improve`'s result, now carries
|
|
755
|
-
`filteredGenericEntities`, `filteredInvalidRelations`,
|
|
756
|
-
`filteredLowConfidenceRelations` and `contextBatchRetries`. The pass
|
|
757
|
-
computed them and dropped them, and did not count batch responses at all.
|
|
758
|
-
(`src/indexer/graph/graph-extraction.ts`, `src/llm/graph-extract.ts`)
|
|
759
|
-
- **An unknown key under `index.<pass>` is kept and named once.** It was
|
|
760
|
-
dropped from the loaded config and named twice. It is now handled like an
|
|
761
|
-
unknown key anywhere else in config. (`src/core/config/schema/index-config.ts`)
|
|
762
|
-
|
|
763
|
-
## [0.9.17-alpha.5] - 2026-09-27
|
|
764
|
-
|
|
765
|
-
`akm show` works again for a memory that has a `.derived.md` child (835 of them
|
|
766
|
-
in one real bundle), and `akm bundle add --provider … --name` holds to the same
|
|
767
|
-
`--name` contract as every other add.
|
|
768
|
-
|
|
769
|
-
### Fixed
|
|
770
|
-
|
|
771
|
-
- **`akm show` works for a memory that has a `.derived.md` child.** When
|
|
772
|
-
`memories/X.md` and `memories/X.derived.md` both existed, `akm show
|
|
773
|
-
memories/X`, with or without a `#fragment`, failed with
|
|
774
|
-
`RESOURCE_ALREADY_EXISTS` ("multiple physical owners"); `akm curate`
|
|
775
|
-
previewed such a memory from its description alone, and `akm curate --pack`
|
|
776
|
-
left it out. The index gives the derived child its own ref,
|
|
777
|
-
`memories/X.derived`, but the ref lookup also counted `X.derived.md` as a
|
|
778
|
-
file for `memories/X`. The lookup now follows the index: `memories/X` is
|
|
779
|
-
`X.md` and `memories/X.derived` is `X.derived.md`. A derived child whose
|
|
780
|
-
parent file is gone no longer answers for the parent's ref either, so it
|
|
781
|
-
cannot hide a real `X.md` in a lower-priority bundle. `akm lint` and
|
|
782
|
-
`--xref` / `--supersedes` validation still accept a ref to `memories/X`
|
|
783
|
-
when only `X.derived.md` remains. Broken since 0.9.7.
|
|
784
|
-
(`src/core/asset/asset-placement.ts`, `src/commands/lint/base-linter.ts`)
|
|
785
|
-
- **`akm bundle add --provider … --name` keeps the `--name` contract too.**
|
|
786
|
-
Since 0.9.17-alpha.4 an explicit `--name` that is not a legal bundle slug,
|
|
787
|
-
or is taken by another bundle, fails with exit 2, and re-adding a source
|
|
788
|
-
under a different name points at `akm bundle rename`. A declarative add
|
|
789
|
-
(`akm bundle add <target> --provider npm|git|website`) still replaced such
|
|
790
|
-
a name with a derived one and exited 0. It now fails the same way, before
|
|
791
|
-
any write. (`src/commands/sources/source-manage.ts`)
|
|
792
|
-
|
|
793
|
-
## [0.9.17-alpha.4] - 2026-09-27
|
|
794
|
-
|
|
795
|
-
Search and curate are rebuilt on measured evidence. On a 221-query suite of real
|
|
796
|
-
akm queries judged for relevance, search nDCG@10 goes from 0.346 to 0.556 and
|
|
797
|
-
curate precision@5 from 0.350 to 0.551, with search p50 falling from 787 ms to
|
|
798
|
-
about 400 ms and a fresh index shrinking from 560 MB to 340 MB.
|
|
799
|
-
|
|
800
|
-
Upgrades stop breaking because the machinery that broke them is gone, not
|
|
801
|
-
because more was added: this release removes the scheduler-grant layer, the
|
|
802
|
-
filesystem transaction journals, the maintenance barrier and lock mutex, the
|
|
803
|
-
strict config schemas and their retired-key registry, and every per-key
|
|
804
|
-
config migration, and it lands with fewer lines in `src/` than 0.9.17-alpha.3.
|
|
35
|
+
- **Declared links (#935).** `akm index` now records the relations a bundle
|
|
36
|
+
already declares — `xrefs:`, `supersededBy:`, `contradictedBy:`,
|
|
37
|
+
`currentBeliefRefs:`, a `.derived` memory's parent, a wiki's `sources:`,
|
|
38
|
+
resolved wiki/OKF page links, and a workflow step's or task's target — as
|
|
39
|
+
typed links, read from what it already parses, with no model call. `akm
|
|
40
|
+
show` lists an asset's links grouped `outgoing`/`incoming`/`unresolved` (up
|
|
41
|
+
to 10 per kind, with a total), and `akm info` reports link counts and how
|
|
42
|
+
many are unresolved. Curate's support refs (at most two per item) now come
|
|
43
|
+
from declared links instead of the LLM entity graph's `related` list (see
|
|
44
|
+
Removed): on a 23,979-entry snapshot, `related` supplied support refs for
|
|
45
|
+
5.4% of curate items against 29.6% for declared links, and judged
|
|
46
|
+
usefulness rose from about 7 to about 24 useful support refs per 100 items.
|
|
47
|
+
- **Consolidate pair pass and retire proposals.** A second consolidation pass
|
|
48
|
+
compares each new-or-changed memory, flat `knowledge/` file, or lesson
|
|
49
|
+
against its nearest neighbours (the best 5 of 20 candidates, same bundle,
|
|
50
|
+
memory tier only) and has an LLM judge label each pair `duplicate`,
|
|
51
|
+
`subsumed`, `supersedes`, `contradicts`, `overlap`, or `unrelated` (judged
|
|
52
|
+
at cosine ≥ 0.93, or ≥ 0.95 for an asset the pass has never attempted —
|
|
53
|
+
unless git added it within the last 7 days, which still judges at 0.93).
|
|
54
|
+
The first three labels mint a reviewed `retire` proposal for the losing side
|
|
55
|
+
(owner-calibrated precision 20/22 = 0.91 [0.72, 0.97]); `contradicts` is
|
|
56
|
+
counted but left to a human; at most 300 pairs are judged a night. Retire
|
|
57
|
+
proposals mint under their own generator, `consolidate-pair` — kept apart
|
|
58
|
+
from the promote pass's `consolidate` proposals in bulk accept/reject — and
|
|
59
|
+
are reviewed like any other proposal (`akm proposal list --generator
|
|
60
|
+
consolidate-pair`, `show`, `diff`, `accept`, `reject`). Accepting one
|
|
61
|
+
archives the losing asset (and its `.derived` twin) instead of deleting it;
|
|
62
|
+
`akm proposal revert` restores the archived bytes exactly, including a file
|
|
63
|
+
with no trailing newline. Triage never auto-accepts a retire proposal,
|
|
64
|
+
whatever `applyMode` says.
|
|
65
|
+
- **Retirement continuity check (rule R3).** Before the pair pass mints a
|
|
66
|
+
retire proposal, it replays up to five of the retired asset's own past
|
|
67
|
+
`search`/`curate` queries and requires the successor to rank in the top 10
|
|
68
|
+
everywhere the retired asset did. A failing or unverifiable query
|
|
69
|
+
(including one where the embedding endpoint is unreachable) flags the
|
|
70
|
+
proposal's `retirement.continuityRisk`, which excludes it from every bulk
|
|
71
|
+
accept path — but not bulk reject, and a person can still accept it by id.
|
|
72
|
+
`akm proposal list`/`show` surface the flag and the failing queries;
|
|
73
|
+
`akm proposal accept --generator ... --dry-run` reports how many
|
|
74
|
+
otherwise-matching proposals were skipped for this reason. `akm proposal
|
|
75
|
+
list` also gained a `--generator <name>` filter.
|
|
76
|
+
- **A promotion now retires its source memory (O1).** When `akm proposal
|
|
77
|
+
accept` promotes a consolidate `promote` proposal — by a person or by
|
|
78
|
+
triage auto-promotion — it archives the source memory (and its `.derived`
|
|
79
|
+
twin) through the same retire-archive path, provided the source is
|
|
80
|
+
unchanged since the promotion was minted. A promotion no longer leaves a
|
|
81
|
+
memory/knowledge duplicate behind. Best-effort: a failure to archive only
|
|
82
|
+
warns.
|
|
83
|
+
- **Archive purge sweep.** At the very start of every `akm improve` run, a
|
|
84
|
+
deterministic pass with no LLM call deletes an archived retirement's files
|
|
85
|
+
(never its `cleanup.md` tombstone) once it is more than 30 days old and
|
|
86
|
+
every file under its archive directory is git-tracked, clean, and
|
|
87
|
+
verifiable; anything less than that — or a bundle with no git history at
|
|
88
|
+
all — is left in place for a later sweep. A new `akm health` advisory,
|
|
89
|
+
`memory-cleanup-archive`, reports how many archived files and bytes are
|
|
90
|
+
not yet purgeable — untracked, modified, or unverifiable for a git-backed
|
|
91
|
+
bundle; everything, forever, for one with no git history at all.
|
|
92
|
+
- **`akm bundle rename <old> <new>`.** Properly re-keys a bundle instead of
|
|
93
|
+
requiring a hand-edit of `config.json`: it rewrites `bundles`,
|
|
94
|
+
`defaultBundle`/`defaultWriteTarget`, and every scheduler ref that name the
|
|
95
|
+
old id, re-keys the indexed entries and enrichment cache in `index.db`,
|
|
96
|
+
rewrites this tool's own state rows that name the old bundle, and re-syncs
|
|
97
|
+
native scheduler rows under the new name. Refs inside asset content
|
|
98
|
+
(cross-references, a task's `uses:`) are reported, never silently
|
|
99
|
+
rewritten. `--dry-run` shows the full plan without writing anything.
|
|
805
100
|
|
|
806
101
|
### Changed
|
|
807
102
|
|
|
808
|
-
- **Search
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
`
|
|
828
|
-
`
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
`
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
`
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
every later sync preserved it, and the nightly improve run then held its
|
|
938
|
-
locks where no interactive command could see them. PATH moves into the
|
|
939
|
-
native artifact, where it is visible and editable: a `PATH=` line inside a
|
|
940
|
-
`# akm:env BEGIN`/`END` section written directly above the first akm task
|
|
941
|
-
block (cron applies it to the rows that follow it; akm rewrites the line on
|
|
942
|
-
every crontab write and removes it with the last task block), and an
|
|
943
|
-
`EnvironmentVariables` entry in each launchd plist. Task Scheduler runs a
|
|
944
|
-
task with the account's own environment and carries no PATH. Plain
|
|
945
|
-
`akm task sync` recomputes the descriptor on every run — an installed row
|
|
946
|
-
whose descriptor no longer matches is updated while its launcher is kept as
|
|
947
|
-
before (only `--rebind` moves that) — so one sync after upgrading rewrites
|
|
948
|
-
every row written under the old policy. Descriptors an older release wrote
|
|
949
|
-
still load, PATH included, until that sync. (`src/tasks/scheduler-invocation.ts`,
|
|
950
|
-
`src/tasks/backends/cron.ts`, `src/tasks/backends/launchd.ts`,
|
|
951
|
-
`src/tasks/scheduler-sync.ts`.)
|
|
952
|
-
- **The write path keeps only what its callers use** (`src/core/write-source.ts`,
|
|
953
|
-
1,485 → 602 lines). The git transaction chain — publication identity capture,
|
|
954
|
-
path, worktree and commit snapshot validation, base-HEAD assertions,
|
|
955
|
-
transaction-commit discovery, a per-repo pending-mutation registry and a
|
|
956
|
-
plan/begin/publish API — lost its last callers when the proposal
|
|
957
|
-
transaction journals were removed and survived only because one test
|
|
958
|
-
imported it; its 15 exports and their private helpers are gone. A write to a
|
|
959
|
-
git-backed bundle now writes the file atomically inside the bundle root,
|
|
960
|
-
records the exact path, and the boundary commits exactly those paths and
|
|
961
|
-
pushes with `--force-with-lease`. A dirty or gitignored destination is no
|
|
962
|
-
longer refused: an ignored path stays local with a warning instead of the
|
|
963
|
-
command throwing after the file had already landed, and an upstream that
|
|
964
|
-
cannot be inspected during preparation warns instead of aborting. Path
|
|
965
|
-
containment, the symlink-escape refusal and the detached-HEAD refusal stay.
|
|
966
|
-
- **Readers tolerate everything older releases wrote.** No config object is
|
|
967
|
-
strict any more: a key this release does not know — retired, misspelled,
|
|
968
|
-
or written by a newer release — is kept in memory and named once
|
|
969
|
-
(`unknownConfigKeyPaths`, `src/core/config/config.ts`, found by walking
|
|
970
|
-
the schema), round-trips through ordinary writes so a newer release's
|
|
971
|
-
settings survive a downgrade, and is dropped only by `akm migrate apply`.
|
|
972
|
-
The retired-keys registry, its read shim, and the schema-compatibility
|
|
973
|
-
lint are removed; nothing needs registering for a key to be tolerated.
|
|
974
|
-
- **`akm migrate apply` has one config step.** `configFile`
|
|
975
|
-
(`normalizeConfigFile`) reads config.json through the same pipeline every
|
|
976
|
-
load runs (`configVersion` read, legacy source shape, `extraParams` lift)
|
|
977
|
-
and writes the current shape back under a backup, dropping unknown keys.
|
|
978
|
-
It replaces the per-key `configLegacySourceShape`, `configExtraParams`,
|
|
979
|
-
`configRetiredKeys` and `configSchedulerSourceIds` steps, the
|
|
980
|
-
`schedulerActivation` and `staleTxns` steps, and the `--host-local` mode. A
|
|
981
|
-
pending config lift is now `ready`, never a blocker for the other steps.
|
|
982
|
-
The `deadResidue` step also removes the transaction-journal,
|
|
983
|
-
maintenance-barrier, lock-mutex and version-stamp files older releases left
|
|
984
|
-
under `$DATA`, `$STATE` and `$CONFIG`, and runs whether or not a bundle is
|
|
985
|
-
configured.
|
|
986
|
-
- **Scheduling is one list.** `scheduler.enabled` holds the fully-qualified
|
|
987
|
-
refs this host schedules (`bundle//tasks/x`). It is still written in the
|
|
988
|
-
`{kind, ref, sourceId}` shape 0.9.16 reads, so that release keeps working
|
|
989
|
-
against a config this one wrote; either shape is read as the ref.
|
|
990
|
-
A config with no list at all (every release before 0.9.17) means "keep
|
|
991
|
-
what is installed": the first `akm task sync` (or `setup`, `task enable`,
|
|
992
|
-
`task disable`, `task add`) takes the akm-written rows already in the
|
|
993
|
-
native scheduler as the host's choice, writes the list, and says so;
|
|
994
|
-
`task sync --dry-run` reports it without writing. An explicit list, empty
|
|
995
|
-
or not, is never second-guessed. Grants, source identities, the
|
|
996
|
-
carry-forward, the scheduler-activation and source-id migrations and the
|
|
997
|
-
fire-time re-check are gone (`src/tasks/activation-config.ts`).
|
|
998
|
-
- **Proposal accept and revert write directly.** The asset file is written
|
|
999
|
-
(temp file + rename), committed through the ordinary write-target
|
|
1000
|
-
boundary, then the proposal row and its event are recorded in one
|
|
1001
|
-
state.db transaction and the file is indexed best-effort. A crash in
|
|
1002
|
-
between leaves a re-acceptable pending proposal, nothing corrupt. The
|
|
1003
|
-
filesystem transaction journals (`src/core/fs-txn.ts`) with their
|
|
1004
|
-
recovery, quarantine, deferral and fencing are removed, along with the
|
|
1005
|
-
`txn-quarantine`/`txn-awaiting-recovery` health advisories.
|
|
1006
|
-
- **`akm bundle update` publishes, records the lock entry, then reindexes —
|
|
1007
|
-
with no rollback transaction.** An update still fetches into a staging
|
|
1008
|
-
directory beside the cache and audits the staged bytes for dangerous env
|
|
1009
|
-
keys before anything goes live; a blocked or failed audit changes nothing.
|
|
1010
|
-
It then publishes with one rename (a fast-forward for a writable Git
|
|
1011
|
-
checkout), writes the lock entry, and reindexes. If the reindex fails, the
|
|
1012
|
-
new content and lock entry stay for the next `akm index`, and the previous
|
|
1013
|
-
install directory is kept. The config, staged-content, lockfile-byte and
|
|
1014
|
-
checkout-HEAD fences and the lockfile compare-and-swap restore are gone, so
|
|
1015
|
-
an update no longer fails with "changed concurrently" or "changed after its
|
|
1016
|
-
staged bytes were audited": it already runs under the asset-mutation lease,
|
|
1017
|
-
and Git refuses a fast-forward that would overwrite local work. A website
|
|
1018
|
-
source refreshes through its mirror's own snapshot staging, so a killed
|
|
1019
|
-
refresh still keeps the previous mirror.
|
|
1020
|
-
- **A lock file is one `O_EXCL` create** (`src/core/file-lock.ts`). The
|
|
1021
|
-
SQLite lock-operation mutex, the maintenance barrier (a lock guarding lock
|
|
1022
|
-
registration) and its per-open activity registry — the source of the
|
|
1023
|
-
lock-sidecar leak that grew `$STATE` by hundreds of megabytes — are
|
|
1024
|
-
removed. `MAINTENANCE_BARRIER_BUSY` no longer exists; contention is
|
|
1025
|
-
reported as `INDEX_DB_CONTENDED`, `STATE_DB_CONTENDED` or
|
|
1026
|
-
`IMPROVE_LOCK_HELD`, as before.
|
|
1027
|
-
- **Removed from 0.9.17-alpha:** startup version reconciliation
|
|
1028
|
-
(`version-reconcile.json`), the akm-install enumerator and `akm upgrade
|
|
1029
|
-
--version`/`--tag` with its other-install mover, the `version-reconcile`,
|
|
1030
|
-
`scheduler-grants`, `scheduled-startup-failures` and `akm-installs` health
|
|
1031
|
-
advisories, and the `akm info` `compat` manifest with its
|
|
1032
|
-
`PLUGIN_PROTOCOL_VERSION`. `akm upgrade` is what it was in 0.9.16.
|
|
1033
|
-
- **Documented the persisted-data compatibility contract.** Added
|
|
1034
|
-
`docs/architecture/persisted-data-compat.md`: the four-sentence contract a
|
|
1035
|
-
reader owes data an earlier release wrote, plus a per-format table (config,
|
|
1036
|
-
`state.db`, `index.db`, task source, workflow IR, native scheduler rows,
|
|
1037
|
-
proposal and task-history metadata, lock payloads, `.akm` residue) naming
|
|
1038
|
-
where each is written, its version marker, its older/newer-data behavior,
|
|
1039
|
-
and which gate covers it — with explicit `Gap:` notes where the code does
|
|
1040
|
-
not meet the contract yet. Registered in `docs/architecture/README.md`.
|
|
1041
|
-
`AGENTS.md`'s "Reading persisted data" section now points at this doc
|
|
1042
|
-
instead of a deleted file.
|
|
1043
|
-
|
|
1044
|
-
- **Scheduler writes hold one lock and apply row by row.** `akm task sync`,
|
|
1045
|
-
`add`, `enable`, `disable` and `prune --yes` hold one `O_EXCL` lock,
|
|
1046
|
-
`$STATE/locks/scheduler.lock` (`src/tasks/scheduler-lock.ts`), for the
|
|
1047
|
-
whole read–plan–write; a second scheduler command exits 75
|
|
1048
|
-
(`SCHEDULER_LOCK_HELD`), and a lock left by a dead process is reclaimed.
|
|
1049
|
-
Under it, sync reads the installed rows once and diffs by native id: a
|
|
1050
|
-
missing row is installed, a changed row rewritten, a row whose source is
|
|
1051
|
-
gone or no longer enabled removed. A row that fails to install or remove,
|
|
1052
|
-
or two sources claiming one native id, is reported in `failures` (exit 1)
|
|
1053
|
-
while every other row applies; the per-row compare-and-swap expectations
|
|
1054
|
-
and whole-set rollback are gone. A task whose source stops parsing keeps
|
|
1055
|
-
its installed row instead of being unscheduled by a YAML typo.
|
|
1056
|
-
(`src/tasks/scheduler-sync.ts`, `src/commands/tasks/tasks.ts`)
|
|
1057
|
-
- **`akm task add` is "write, enable, sync".** It validates the task and
|
|
1058
|
-
refuses an id already scheduled from another bundle before writing
|
|
1059
|
-
anything, then writes the source, adds the ref to `scheduler.enabled`
|
|
1060
|
-
(unless `--disabled`) and syncs the bundle. When the row cannot be
|
|
1061
|
-
installed, add fails naming the cause and the task stays written and
|
|
1062
|
-
enabled for the next `akm task sync` to retry; it no longer restores the
|
|
1063
|
-
prior source and rows byte-for-byte. `--force` with fewer schedules removes
|
|
1064
|
-
the dropped schedules' rows through the same sync, and `--rebind` means
|
|
1065
|
-
what it means for `task sync`.
|
|
1066
|
-
- **Improve records what it tried in one ledger** (`improve_ledger`,
|
|
1067
|
-
`src/storage/repositories/improve-ledger-repository.ts`). One row per
|
|
1068
|
-
stash, ref and stage holds the last attempt, its outcome and when the ref
|
|
1069
|
-
is next eligible, from one cadence table:
|
|
1070
|
-
|
|
1071
|
-
| Outcome | Next eligible | Lifted early by newer feedback? |
|
|
1072
|
-
| --- | --- | --- |
|
|
1073
|
-
| rejected, quality_rejected | 14 d reflect, 30 d distill, 7 d other stages | no |
|
|
1074
|
-
| expired | 1 d | no |
|
|
1075
|
-
| proposed, review_needed, unchanged, judged_no_action | 7 d | yes |
|
|
1076
|
-
| accepted, failed | immediately | — |
|
|
1077
|
-
|
|
1078
|
-
Every stage reads it before any LLM call. It replaces proposal
|
|
1079
|
-
fingerprints, the per-stage cooldowns, the distill reject files and the
|
|
1080
|
-
event-timestamp cursors, which disagreed with one another (quality
|
|
1081
|
-
rejections never reached the fingerprints; consolidate re-judged promoted
|
|
1082
|
-
memories). Distill and consolidate now key by their input refs, so each
|
|
1083
|
-
such input may be attempted once more after upgrading. Schema repair paces
|
|
1084
|
-
itself with the ledger too, replacing its private 7-day cooldown and
|
|
1085
|
-
3-attempts-per-30-days cap. Every stage — reflect, distill, consolidate,
|
|
1086
|
-
extract, triage, memory inference, graph extraction — runs through one
|
|
1087
|
-
shared path (`src/commands/improve/stage.ts`): pick the runner, call the
|
|
1088
|
-
model, judge the output, mint the proposal, record the usage.
|
|
1089
|
-
- **`akm proposal drain` has one rule.** A proposal the quality judge passed
|
|
1090
|
-
(a `staged` gate decision whose content hash still matches) is accepted, an
|
|
1091
|
-
empty diff is rejected, and everything else goes to the judgment tier
|
|
1092
|
-
(`processes.triage.judgment`) or waits for review. Extract and consolidate
|
|
1093
|
-
proposals, which the `personal-stash` policy auto-accepted on size alone,
|
|
1094
|
-
carry no judge stamp, so they now go to the judgment tier — or wait for
|
|
1095
|
-
review when none is configured — instead of being accepted. The policies
|
|
1096
|
-
and their flags are retired (see Removed). `--dry-run` now predicts what a
|
|
1097
|
-
real drain does: a proposal whose target already holds its content (an
|
|
1098
|
-
accept that wrote the file but was interrupted before recording it) is
|
|
1099
|
-
reported as promoted, as the real drain finishes it, instead of as a
|
|
1100
|
-
stale-target rejection.
|
|
1101
|
-
- **State migration `028-improve-ledger` creates the ledger and drops six
|
|
1102
|
-
tables.** It backfills the ledger from each ref's latest proposal and drops
|
|
1103
|
-
`proposal_fingerprints`, `improve_gate_thresholds`, `proposal_fs_imports`,
|
|
1104
|
-
`consolidation_judged`, `improve_cycle_metrics` and `canary_queries`.
|
|
1105
|
-
Because it drops schema, the first open after upgrading copies the
|
|
1106
|
-
database to `state.db.pre-028-improve-ledger.bak` before it runs.
|
|
1107
|
-
- **Upgrading no longer rebuilds or re-embeds the search index (index layout
|
|
1108
|
-
24).** The first writable open applies a layout change in place — added
|
|
1109
|
-
columns, and a one-time rebuild of the two full-text tables from the stored
|
|
1110
|
-
entries (about 2–3 s for 24k entries); embeddings, utility scores, the
|
|
1111
|
-
enrichment cache and the graph are never dropped, and only a corrupt file
|
|
1112
|
-
is rebuilt from scratch (#865). Both FTS5 tables are contentless, so
|
|
1113
|
-
indexed text is stored once (153 MB of a 980 MB index on a 23.9k-entry
|
|
1114
|
-
stash; a SQLite older than 3.43 keeps the previous layout). Each vector
|
|
1115
|
-
records its model (`embeddings.model`): a model change re-embeds only the
|
|
1116
|
-
entries missing a vector for the configured model, per batch and
|
|
1117
|
-
resumably, replacing the purge, the #955 re-embed canary and
|
|
1118
|
-
`embedding_salvage`. `akm index --full` keeps unchanged entries' vectors,
|
|
1119
|
-
and a one-file change in a large directory re-persists only that file.
|
|
1120
|
-
Readers serve an older layout as-is and say so once on stderr. An akm
|
|
1121
|
-
older than this release refuses a layout-24 index and asks to be upgraded.
|
|
1122
|
-
- **`--verbose` embedding output lists each document's size without a
|
|
1123
|
-
predicted batch number.** The per-batch lines already report every
|
|
1124
|
-
provider request's document and token counts, and skipped documents are
|
|
1125
|
-
listed at the end of the pass.
|
|
1126
|
-
- **Workflow runs are never refused for their plan's version or hash.**
|
|
1127
|
-
Markdown and the GitHub-shaped YAML subset compile straight to one plan
|
|
1128
|
-
type, and new runs record plan `irVersion` 6. A stored plan that decodes
|
|
1129
|
-
runs whatever release froze it — irVersion 4 and 5 plans are read
|
|
1130
|
-
tolerantly, and a key this release does not know is ignored instead of
|
|
1131
|
-
abandoning the run; one that does not decode is marked abandoned and `akm
|
|
1132
|
-
workflow run <ref>` starts afresh; only a plan a newer akm froze is
|
|
1133
|
-
refused, with "Upgrade akm" (`WORKFLOW_IR_VERSION_UNSUPPORTED` is gone).
|
|
1134
|
-
One driver per run is a lock file,
|
|
1135
|
-
`<data dir>/workflow-run-locks/<run id>.lock`: a second `akm workflow run`
|
|
1136
|
-
exits 75 (`RUN_LEASE_HELD`) naming the holder's pid, and a dead pid's lock
|
|
1137
|
-
is reclaimed at once — the database run lease, its heartbeat and the
|
|
1138
|
-
check-ins are gone. Resume reuses every completed unit whatever its
|
|
1139
|
-
recorded input hash, and warns once when the workflow file's sha256
|
|
1140
|
-
differs from the one recorded at freeze, then continues on the frozen
|
|
1141
|
-
plan. Executable identity (realpath, inode and hash captured at freeze,
|
|
1142
|
-
checked at dispatch) is gone, so upgrading `claude` mid-run no longer
|
|
1143
|
-
strands a run.
|
|
1144
|
-
- **Every execution goes through three plain functions:** `resolveExecution`
|
|
1145
|
-
→ `buildExecution` → `runExecution` (`src/integrations/agent/execution.ts`,
|
|
1146
|
-
`runner-dispatch.ts`), replacing a 12-hop pipeline across 14 modules — the
|
|
1147
|
-
cascade planner, authorized-plan and provenance checks, lowerer registry
|
|
1148
|
-
and dispatch lease. Two behaviour changes: credentials are read at each
|
|
1149
|
-
dispatch, so a key rotated mid-run is used on the next call instead of a
|
|
1150
|
-
snapshot taken at the start; and an explicit `engine: null` in a task,
|
|
1151
|
-
workflow or command layer means "no preference here" and falls through to
|
|
1152
|
-
`defaults.engine` instead of forcing the `opencode-sdk` fallback.
|
|
1153
|
-
- **`state.db` opens on one connection.** The open creates the parent
|
|
1154
|
-
directory, opens the file, applies the pragmas, reads the migration ledger
|
|
1155
|
-
and runs every pending migration in one `BEGIN IMMEDIATE`; the read-only
|
|
1156
|
-
preflight connection, the `/proc/self/fd` alias and the refusal of an empty
|
|
1157
|
-
"unversioned" file are gone. Before a migration that drops schema runs on
|
|
1158
|
-
an existing database, it is copied to `state.db.pre-<id>.bak`. Since any
|
|
1159
|
-
open applies pending migrations, `akm health`'s `state-db-migrations` check
|
|
1160
|
-
now reports what its own open applied (`evidence.applied`,
|
|
1161
|
-
`evidence.backupPath`) and fails only when a migration could not be
|
|
1162
|
-
applied.
|
|
1163
|
-
- **`akm health` drops checks nothing acted on.** Removed: the
|
|
1164
|
-
`task-log-backing` hard check, the `pool-saturation` advisory, the six
|
|
103
|
+
- **Search and curate are rebuilt on measured evidence.** Search now ranks by
|
|
104
|
+
reciprocal rank fusion (RRF) of two 100-candidate lists — whole-document
|
|
105
|
+
BM25 and nearest document vectors — replacing the previous require-every-
|
|
106
|
+
word keyword ladder plus a dozen additional ranking-signal boosts (exact-
|
|
107
|
+
name, type, belief-state, tag, graph, utility, and more): on a 221-query
|
|
108
|
+
LLM-judged retrieval suite, plain whole-document BM25 alone beat that whole
|
|
109
|
+
boosted pipeline by 0.156 nDCG@10. A hit's `score` is now its fused RRF
|
|
110
|
+
value (at most 2/61 ≈ 0.033, not comparable to an old score), keyword
|
|
111
|
+
matching no longer does prefix matching (a search for `dock` no longer
|
|
112
|
+
matches `docker`), and a slow embedding endpoint falls back to keyword-only
|
|
113
|
+
ranking after `embedding.queryTimeoutMs` (default 3000 ms) instead of
|
|
114
|
+
blocking the search. `akm curate` is now the top hits of that same fused
|
|
115
|
+
search, enriched with a preview and up to two support refs, rather than its
|
|
116
|
+
own layer of second-guessing fallback searches and nudges. Query embedding
|
|
117
|
+
now uses the template a model actually expects (Qwen3's retrieval
|
|
118
|
+
instruction, `search_query:`/`search_document:` for nomic,
|
|
119
|
+
`query:`/`passage:` for E5, and others — overridable with
|
|
120
|
+
`embedding.queryTemplate`/`documentTemplate`) instead of one generic
|
|
121
|
+
prefix; a nomic-embed or E5 configuration re-embeds every entry on the next
|
|
122
|
+
`akm index` because the new document template changes what gets embedded
|
|
123
|
+
(set `embedding.documentTemplate: ""` to keep the old vectors instead).
|
|
124
|
+
Measured on the suite: search nDCG@10 rises from 0.346 to 0.556 and curate
|
|
125
|
+
precision@5 from 0.350 to 0.551; search p50 latency falls from about 787 ms
|
|
126
|
+
to about 400 ms, and a freshly built index shrinks from 560 MB to 340 MB.
|
|
127
|
+
Content indexed under two names (a memory also promoted verbatim to
|
|
128
|
+
knowledge, say) now returns only the higher-ranked copy instead of both,
|
|
129
|
+
and `akm curate` returns an empty, explained result for input that is not
|
|
130
|
+
a real query (a harness/tool envelope, a bare stash README) instead of
|
|
131
|
+
`--limit` unrelated items.
|
|
132
|
+
- **Index layout 26.** Vectors are stored once — the sqlite-vec mirror, the
|
|
133
|
+
fragment full-text table, and the stored embedding-input text are dropped
|
|
134
|
+
in favor of a derived hash — and every relation `akm index` already parses
|
|
135
|
+
is stored as a typed link (see Added). An index an older 0.9.17 prerelease
|
|
136
|
+
or 0.9.16 wrote migrates to layout 26 in place on the first writable open —
|
|
137
|
+
a 0.9.16 index also gets a one-time full-text rebuild along the way, a few
|
|
138
|
+
seconds for a 24k-entry index — and the run ends with a VACUUM that
|
|
139
|
+
reclaims the space the migration frees (in one measurement, a 601 MB index
|
|
140
|
+
built one layer short of layout 26 dropped to 377 MB; actual savings vary
|
|
141
|
+
with how much of an existing index was already free space). An index this
|
|
142
|
+
release cannot read (written by a newer akm) is refused, naming the
|
|
143
|
+
upgrade, instead of being silently reinterpreted.
|
|
144
|
+
- **A scheduled task is just a command and a schedule.** Each native
|
|
145
|
+
crontab/launchd/Task Scheduler row now carries its own `AKM_BUNDLE_DIR`
|
|
146
|
+
(plus any `AKM_CONFIG_DIR`/`AKM_DATA_DIR`/`AKM_CACHE_DIR`/`AKM_STATE_DIR`
|
|
147
|
+
the syncing shell set explicitly) inline, instead of pointing at a
|
|
148
|
+
`--scheduler-context <file>` descriptor; a crontab now carries its PATH the
|
|
149
|
+
same way, in a `# akm:env` block akm writes and rewrites next to its task
|
|
150
|
+
rows, instead of inside each row's own descriptor file. The first `akm task
|
|
151
|
+
sync` after upgrading rewrites every akm-managed row once, in place — same
|
|
152
|
+
launcher, same schedule, nothing added or removed — and once `akm task
|
|
153
|
+
doctor` lists no binding still pointing at a descriptor, the old
|
|
154
|
+
`$DATA/tasks/context/` files it leaves behind can be deleted. Rows written
|
|
155
|
+
by 0.9.0 through 0.9.16 keep firing until that sync.
|
|
156
|
+
- **`akm improve` reworks only what retrieval actually returned, or what's
|
|
157
|
+
new.** The proactive-maintenance and high-salience lanes, and the memory
|
|
158
|
+
consolidation judge, are now scoped to assets a real `search`, `curate`,
|
|
159
|
+
`show`, or `feedback` touched in the last 90 days, plus material no
|
|
160
|
+
improve stage has processed yet — not the whole stash.
|
|
161
|
+
Measured on a 19,870-asset copy of the maintainer's bundle: the fallback
|
|
162
|
+
lanes' candidate pool drops from 15,686 to 6,450 assets, and in July the
|
|
163
|
+
proactive lane had rewritten 3,069 assets, 3,059 of which had never been
|
|
164
|
+
retrieved since usage logging began. Left-out assets are reported under a
|
|
165
|
+
new `retrieval` gate (`akm improve --dry-run`, and health's `not_retrieved`
|
|
166
|
+
skip reason).
|
|
167
|
+
- **Reflect refuses a rewrite that grades worse on the asset's own searches
|
|
168
|
+
(#722).** Before proposing a rewrite of existing content, reflect grades
|
|
169
|
+
the old and new versions on up to five queries that actually retrieved the
|
|
170
|
+
asset, using the same relevance judge the retrieval eval uses (kappa 0.83
|
|
171
|
+
against human grades); a rewrite that grades lower on average is refused as
|
|
172
|
+
`quality_rejected`. Of 60 accepted rewrites reviewed this way after the
|
|
173
|
+
fact, 23% [14-35%] graded lower than the content they replaced.
|
|
174
|
+
- **Config is fully tolerant of what an older or newer release wrote.** No
|
|
175
|
+
config object is strict any more: an unknown key at any depth — retired,
|
|
176
|
+
misspelled, or written by a newer release — is kept in memory, named once,
|
|
177
|
+
round-trips through ordinary writes, and is dropped only by `akm migrate
|
|
178
|
+
apply`. `akm migrate apply` itself collapses to one config step that reads
|
|
179
|
+
`config.json` through the normal load pipeline and writes the current
|
|
180
|
+
shape back under a backup.
|
|
181
|
+
- **Locking and writes are simpler.** A lock file is now one `O_EXCL`
|
|
182
|
+
create. A write to a git-backed bundle writes the file directly and
|
|
183
|
+
commits exactly that path. `akm proposal accept`/`revert` write the asset
|
|
184
|
+
file, then record the proposal and its event in one `state.db`
|
|
185
|
+
transaction, so a crash in between leaves a re-acceptable pending proposal
|
|
186
|
+
rather than something corrupt.
|
|
187
|
+
- **Scheduling is one list.** `scheduler.enabled` in `config.json` holds the
|
|
188
|
+
fully-qualified refs a host schedules; it is still written and read in the
|
|
189
|
+
`{kind, ref, sourceId}` shape 0.9.16 used. A config with no list at all —
|
|
190
|
+
0.9.15 and earlier — is read on the first sync after upgrading as "keep
|
|
191
|
+
what's already installed," and the list is written from there.
|
|
192
|
+
- **A frozen workflow plan carries `irVersion` 6.** Every workflow — Markdown
|
|
193
|
+
and the GitHub-shaped YAML subset alike — compiles to the one plan type
|
|
194
|
+
that was previously irVersion 4/5's target; a stored irVersion 4 or 5 plan
|
|
195
|
+
is still read and run tolerantly (an unrecognized key in it is ignored
|
|
196
|
+
instead of abandoning the run), and only a plan a *newer* akm froze is
|
|
197
|
+
refused. An explicit `engine: null` on a task, workflow, or command layer
|
|
198
|
+
now means "no preference here" and falls through to `defaults.engine`,
|
|
199
|
+
instead of forcing the `opencode-sdk` fallback.
|
|
200
|
+
- **The quality judge that gates reflect and distill scores each criterion
|
|
201
|
+
separately** instead of one blended float, no longer scores an
|
|
202
|
+
ACTIONABILITY criterion that measured no better than chance (AUC 0.46), and
|
|
203
|
+
now runs at a pinned temperature of 0 — at the previous effective default
|
|
204
|
+
of 0.3, 10 of 16 identical inputs had flipped verdict. Consolidate's own
|
|
205
|
+
prompt and schema now ask only for `promote`; `merge`/`delete`/`contradict`
|
|
206
|
+
were advisory-only and had not actually executed since July regardless.
|
|
207
|
+
- **A pasted credential in a search or curate query is redacted before it's
|
|
208
|
+
stored.** A password, bearer token, JWT, `ghp_…` token, PEM key, and
|
|
209
|
+
similar patterns are replaced with `[REDACTED]` in `state.db`'s usage and
|
|
210
|
+
event logs; a scan of mined queries had found 22 stored verbatim. Existing
|
|
211
|
+
rows are not rewritten.
|
|
212
|
+
- **One bad item no longer aborts a whole `akm task sync` or `akm migrate`
|
|
213
|
+
run.** A binding or bundle that fails to reconcile, or a migration step
|
|
214
|
+
that throws, is now reported individually (`failures`/`failedSteps`) while
|
|
215
|
+
every other task, bundle, or step still completes.
|
|
216
|
+
- **A one-file change in a large directory no longer costs `akm index` tens
|
|
217
|
+
of minutes.** Both full-text tables' per-entry deletes were unindexed table
|
|
218
|
+
scans; on a 23.9k-entry index, one touched file in a 13.7k-entry directory
|
|
219
|
+
took 26-31 minutes before this release and well under a minute after.
|
|
220
|
+
- **Output shapes changed along with the features above.** A search hit
|
|
221
|
+
drops `selectedRef`, `parentRef`, `fragmentOrdinal`, `fragmentCount`, its
|
|
222
|
+
fragment line/size fields, `matchStage`, and `graph` (fragments no longer
|
|
223
|
+
compete as search candidates; `akm show <ref>#<fragment>` still resolves a
|
|
224
|
+
section). `akm index`/`akm info` drop `vecAvailable`, and
|
|
225
|
+
`semanticStatus` no longer reports `ready-vec`. `akm migrate status`'s
|
|
226
|
+
separate `taskV3Migration`/`taskV4Migration` sections are now one
|
|
227
|
+
`taskFiles` section. `akm workflow plan` drops its `sourceReadSet` block.
|
|
228
|
+
- **`akm health` gets a new hard `state-db-integrity` check** (a read-only
|
|
229
|
+
SQLite `PRAGMA quick_check` against `state.db`, plus a freelist-ratio
|
|
230
|
+
warning above 50%), and drops eight checks nothing acted on: the
|
|
231
|
+
`task-log-backing` hard check, the `pool-saturation` advisory, and six
|
|
1165
232
|
research advisories (`outcome-proxy-adequacy`, `outcome-proxy-dead`,
|
|
1166
233
|
`salience-uniformity-collapse`, `enrichment-lane-minting`,
|
|
1167
|
-
`improve-churn-ratio`, `collapse-churn-detector`)
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
`considered`/`yieldRate`, graph-extraction `failures`, distill
|
|
1172
|
-
`skipped`/`queued`/`llmFailed`, `orphansPurged`); `--group-by run` and
|
|
1173
|
-
`--format md` are unchanged.
|
|
1174
|
-
- **`configVersion` is read, never gated on.** A missing field or `"0.9.0"`
|
|
1175
|
-
loads silently; any other value is named once and read as `0.9.0`.
|
|
1176
|
-
`UNSUPPORTED_CONFIG_VERSION` and `src/core/config/config-version-shim.ts`
|
|
1177
|
-
are gone.
|
|
1178
|
-
- **`akm migrate` converts a task file in one step, whatever its version.**
|
|
1179
|
-
One planner (`scripts/akm-migrate/migrate/task-files.ts`) takes a v2, v3 or
|
|
1180
|
-
v4 file still carrying `schedule[].enabled` to v4 in one pass, with one
|
|
1181
|
-
backup directory per run (`$DATA/backups/tasks/<ts>-<uuid>`); `akm migrate
|
|
1182
|
-
status` reports one `taskFiles` section instead of
|
|
1183
|
-
`taskV3Migration`/`taskV4Migration`. The per-generation steps, their
|
|
1184
|
-
convergence checks and backup pruning, and the writer-relocation step are
|
|
1185
|
-
gone.
|
|
1186
|
-
- **Registry requests use plain `fetch()`.** DNS pinning — a Node child
|
|
1187
|
-
process per request that resolved each registry host, rejected private
|
|
1188
|
-
addresses and pinned the connection — is removed: a registry URL is the
|
|
1189
|
-
built-in one or one an operator configured. `src/registry/network.ts`
|
|
1190
|
-
retries network failures, timeouts, 429 and 5xx with backoff, caps the
|
|
1191
|
-
body, and reports every failure as a classified error, never exit 70:
|
|
1192
|
-
`REGISTRY_NOT_FOUND` and `REGISTRY_RESPONSE_INVALID` exit 1,
|
|
1193
|
-
`REGISTRY_UNREACHABLE` exits 75, `REGISTRY_URL_INVALID` exits 78. A static
|
|
1194
|
-
index whose `version` is not 2 or 3 is read with one warning instead of
|
|
1195
|
-
refused.
|
|
1196
|
-
|
|
1197
|
-
### Added
|
|
1198
|
-
|
|
1199
|
-
- **Upgrade rehearsal gate** (`tests/integration/upgrade-rehearsal/`,
|
|
1200
|
-
`AKM_UPGRADE_REHEARSAL=1`): installs the previous published `akm-cli`
|
|
1201
|
-
release as a real global npm package, drives it to build a realistic home
|
|
1202
|
-
(a filesystem, git, website, and npm bundle; scheduled and manual tasks; a
|
|
1203
|
-
synced fake crontab), then installs the candidate build OVER it in place —
|
|
1204
|
-
the same prefix a real `npm i -g`/`bun add -g` upgrade replaces — and runs
|
|
1205
|
-
the candidate against that home — `migrate status`/`apply`, `bundle list`
|
|
1206
|
-
with every bundle confirmed enabled, `search`, `show`, plain `task sync`
|
|
1207
|
-
(dry-run and real, no `--rebind`, as an upgrading user actually runs it),
|
|
1208
|
-
executing the generated cron command and confirming it ran the candidate,
|
|
1209
|
-
`health`, `improve --plan` — and finally installs a separate untouched copy
|
|
1210
|
-
of the previous release and runs it back against the candidate-written
|
|
1211
|
-
home. Wired into CI (`.github/workflows/ci.yml`'s new `upgrade-rehearsal`
|
|
1212
|
-
job) and `tests/release-check.sh` (right after packing the release
|
|
1213
|
-
candidate). `.github/workflows/ci.yml` also now runs on pushes to
|
|
1214
|
-
`release/*` branches, which previously had no CI coverage at all.
|
|
1215
|
-
It also proves the fix for the defect above (Fixed, below) two
|
|
1216
|
-
ways: a new first assertion in the "previous"-origin suite runs
|
|
1217
|
-
scheduled-a's generated cron command BEFORE any `migrate` call and
|
|
1218
|
-
confirms `akm-migrate status --host-local` then reports `current` with no
|
|
1219
|
-
manual step in between; and a second, dedicated origin,
|
|
1220
|
-
`KNOWN_UPGRADE_ORIGINS`' fixed `"0.9.15"` (the last release before
|
|
1221
|
-
source-bound scheduler grants), builds a minimal home whose crontab row
|
|
1222
|
-
carries no host-local grant at all — the exact 2026-09-24 shape — and
|
|
1223
|
-
confirms the candidate carries the grant forward and a plain `task sync`
|
|
1224
|
-
afterward does not remove it.
|
|
1225
|
-
- **`akm bundle rename <old> <new>`.** Renaming a bundle used to mean
|
|
1226
|
-
hand-editing the `bundles` key in `config.json`, which stranded every
|
|
1227
|
-
durable ref the tool had minted under the old id — the index and state
|
|
1228
|
-
databases kept the old `<old>//` prefix while config named the new one
|
|
1229
|
-
(the exact hand-rename signature `warnOnBundleRenameDrift` already
|
|
1230
|
-
detected and warned about, with "there is no rekey command in 0.9.0").
|
|
1231
|
-
`akm bundle rename` is that command: under the config lock it rewrites the
|
|
1232
|
-
`bundles` key, `defaultBundle`/`defaultWriteTarget` when they name the old
|
|
1233
|
-
id, and every `scheduler.enabled[].ref` with the old `//` prefix; then it
|
|
1234
|
-
renames the lockfile entry, re-keys every indexed entry's
|
|
1235
|
-
`bundle_id`/`item_ref` and the metadata-enrichment LLM cache's
|
|
1236
|
-
`asset_ref` (in the same `index.db` write, so a rename can't land between
|
|
1237
|
-
the two and strand the cache — the next `akm index` would otherwise treat
|
|
1238
|
-
every renamed asset as stale and re-enrich it through the LLM from
|
|
1239
|
-
scratch), and rewrites this tool's own state rows that name the old bundle
|
|
1240
|
-
(`proposals.ref`, a pending proposal's `proposedTarget.source`, and
|
|
1241
|
-
workflow `task_history.target_ref`). It then re-syncs native scheduler
|
|
1242
|
-
rows under the new name (`akmTasksSync`, run from the command handler and
|
|
1243
|
-
reported in the result's `taskSync` field, never thrown, since
|
|
1244
|
-
config/index/state are already renamed by then), so a scheduled task or
|
|
1245
|
-
workflow stops invoking `<old>//…` the moment the rename applies instead of
|
|
1246
|
-
waiting on a manual `akm task sync`. `taskSync.ok` is `false` both when
|
|
1247
|
-
the sync call itself fails and when it comes back with one or more
|
|
1248
|
-
`taskSync.result.failures` — a binding that failed to prepare has already
|
|
1249
|
-
lost its old native row and is not scheduled again until a retry, so
|
|
1250
|
-
`akm bundle rename` never reports a partial re-sync as a clean one. Refs
|
|
1251
|
-
inside the bundle's own CONTENT
|
|
1252
|
-
(cross-references, a task's `uses:`, `supersededBy`) are reported, never
|
|
1253
|
-
rewritten — the result's `contentRefs` lists the indexed files that still
|
|
1254
|
-
spell the old prefix. `--dry-run` shows the full plan (row counts,
|
|
1255
|
-
scheduler refs, content files, and the installed native scheduler rows a
|
|
1256
|
-
real run's sync would replace) without writing anything.
|
|
1257
|
-
|
|
1258
|
-
### Removed
|
|
1259
|
-
|
|
1260
|
-
- **Every ranking signal besides the two fused lists.** Search no longer
|
|
1261
|
-
applies exact-name tiers, type, belief-state, tag, search-hint, alias,
|
|
1262
|
-
description, metadata, graph, capture-mode, lesson-strength, pinned-fact or
|
|
1263
|
-
project-context boosts, the utility multiplier, the relaxed-query score
|
|
1264
|
-
ceiling, or the cosine floor on vector-only hits, and it no longer loads
|
|
1265
|
-
the graph snapshot. On the retrieval suite plain whole-document BM25 alone
|
|
1266
|
-
beat the boosted pipeline by 0.156 nDCG@10, and applying the belief-state
|
|
1267
|
-
weights to the fused score lowered nDCG@10 by 0.010 [−0.020, −0.001], so
|
|
1268
|
-
`--belief current` is the way to leave out contradicted or superseded
|
|
1269
|
-
entries. Usage events and utility scores are still recorded (improve's
|
|
1270
|
-
salience and graph extraction read them), and the graph still backs
|
|
1271
|
-
`akm show`'s `related` list and curate's support refs.
|
|
1272
|
-
- **The require-every-word keyword ladder and prefix matching.** The strict
|
|
1273
|
-
AND query, its prefix-AND retry and the OR recovery behind them are gone
|
|
1274
|
-
(OR matching measured 0.108 nDCG@10 better), so a word fragment such as
|
|
1275
|
-
`dock` no longer matches `docker`.
|
|
1276
|
-
- **Fragment hits in search.** Markdown fragments no longer compete as search
|
|
1277
|
-
candidates (whole documents measured 0.059 nDCG@10 better), so search
|
|
1278
|
-
returns whole-document refs and its hits drop `selectedRef`, `parentRef`,
|
|
1279
|
-
`fragmentOrdinal`, `fragmentCount`, the fragment line and size fields and
|
|
1280
|
-
`matchStage`; `akm show <ref>#<fragment>` still selects a section.
|
|
1281
|
-
- **Curate's second-guessing of search:** the per-keyword fallback searches
|
|
1282
|
-
and their max-score merge, the intent and type nudges, skill-family
|
|
1283
|
-
collapse (and the family support refs it produced), and the close-score
|
|
1284
|
-
comparator.
|
|
1285
|
-
- **Retired options.** `akm search --no-project-context` now fails as an
|
|
1286
|
-
unknown flag (exit 2). The config keys `search.minScore`,
|
|
1287
|
-
`search.graphBoost.*` and `improve.utilityDecay.*` have no effect and are
|
|
1288
|
-
kept as unknown keys. Search hits no longer carry the `graph` field, and
|
|
1289
|
-
usage events no longer record `graphExtraction` attribution.
|
|
1290
|
-
- **Guarded source reads around workflow runs.** `akm workflow run` no
|
|
1291
|
-
longer records a read set of every source it touched or re-checks those
|
|
1292
|
-
sources before publishing the run, so editing a command, task, script or
|
|
1293
|
-
env file while a run is being created no longer fails creation; a source
|
|
1294
|
-
that resolves outside its bundle is still refused. `akm workflow plan` no
|
|
1295
|
-
longer prints a `read set:` block, and its JSON drops `sourceReadSet`. At
|
|
1296
|
-
dispatch an env file is re-read from its recorded path (a changed key set
|
|
1297
|
-
is still refused), so replacing or re-cloning the bundle directory no
|
|
1298
|
-
longer fails a unit with "environment owner root physical identity
|
|
1299
|
-
changed". The resume check that refused a run whose stored params row had
|
|
1300
|
-
been edited is gone.
|
|
1301
|
-
- **Drain policies.** `processes.triage.policy` and
|
|
1302
|
-
`processes.triage.maxDiffLines` (config) and `akm proposal drain --policy`
|
|
1303
|
-
/ `--max-diff-lines` are retired with `drain-policies.ts`; the flags now
|
|
1304
|
-
fail as unknown (exit 2) and the keys are kept as unknown config keys.
|
|
1305
|
-
- **Improve machinery with no remaining reader:** the collapse detector with
|
|
1306
|
-
its canary set (`scripts/refresh-canary-set.ts`) and cycle metrics, replay
|
|
1307
|
-
selection, the outcome-proxy events, and the never-called anti-collapse
|
|
1308
|
-
merge guards. Retired config keys (kept as unknown keys):
|
|
1309
|
-
`processes.consolidate.antiCollapse.{maxGeneration, lexicalDiversityCheck,
|
|
1310
|
-
mergeInformationFloor, minSpecificityRetention}`,
|
|
1311
|
-
`processes.consolidate.contradictionDetection`,
|
|
1312
|
-
`improve.salience.replayBudget` and `improve.collapseDetector`. Retired
|
|
1313
|
-
events: `improve_salience_first_run`, `improve_replay_selected`,
|
|
1314
|
-
`collapse_detector_alert`, `improve_cycle_metrics_purged`,
|
|
1315
|
-
`outcome_proxy_dead` and `outcome_proxy_inverted`.
|
|
1316
|
-
|
|
1317
|
-
### Fixed
|
|
1318
|
-
|
|
1319
|
-
- **Re-extracting an unchanged note now replaces its stored graph rows.**
|
|
1320
|
-
`replaceStoredGraph` refreshed only a file's status, reason and run id when
|
|
1321
|
-
its body hash was unchanged, so an extraction of the same body — after a
|
|
1322
|
-
model or prompt change, or after a failed first attempt — never reached
|
|
1323
|
-
`graph_file_entities` or `graph_file_relations`. One install had 1,389 files
|
|
1324
|
-
marked `extracted` with no entity rows while `llm_enrichment_cache` held
|
|
1325
|
-
their extractions. A file's rows are now rewritten whenever its entities or
|
|
1326
|
-
relations differ from the stored ones, so the next graph pass refills such
|
|
1327
|
-
files from the cache without a model call. (`src/indexer/db/graph-db.ts`)
|
|
1328
|
-
- **A graph pass that stops early no longer shrinks the stored graph.** A
|
|
1329
|
-
full scan wrote back only the files it reached, so a budget abort, a
|
|
1330
|
-
failure-rate abort or `processes.graphExtraction.topN` deleted the stored
|
|
1331
|
-
rows of every other file: the 2026-09-26 backfill hit its 4 h budget after
|
|
1332
|
-
3,358 of 15,165 eligible files, and that prefix became the whole graph. The
|
|
1333
|
-
pass now keeps the rows of every eligible file it did not reach, and of a
|
|
1334
|
-
file whose extraction attempt failed. It drops rows only for a file that
|
|
1335
|
-
left the eligible set — deleted, emptied, now `inferred: true`, or of a type
|
|
1336
|
-
no longer included — which candidate-scoped runs never did; a scan that
|
|
1337
|
-
could not read part of the stash drops nothing. Because kept rows can come
|
|
1338
|
-
from an older extractor, the sweep no longer reuses a stored graph node as a
|
|
1339
|
-
cache hit: only `llm_enrichment_cache`, keyed by extractor, answers for the
|
|
1340
|
-
current one. (`src/indexer/graph/graph-extraction.ts`)
|
|
1341
|
-
- **`graph_meta` counts describe the stored rows.** The extraction pass
|
|
1342
|
-
wrote counts from its in-memory graph (22,304 entities reported against
|
|
1343
|
-
15,833 stored on one install), and deleting entries overwrote them with raw
|
|
1344
|
-
row counts. Each write now derives them from the stored rows, one meaning
|
|
1345
|
-
each: stored files, files with entity rows, distinct case-folded entities and
|
|
1346
|
-
distinct case-folded relations. The pass result, and with it the
|
|
1347
|
-
`akm improve` summary, reports the same counts. The entries-delete recompute
|
|
1348
|
-
and the in-memory graph deduplicator (`src/indexer/graph/graph-dedup.ts`)
|
|
1349
|
-
are gone. (`src/indexer/db/graph-db.ts`,
|
|
1350
|
-
`src/indexer/graph/graph-extraction.ts`,
|
|
1351
|
-
`src/storage/repositories/index-entries-repository.ts`)
|
|
1352
|
-
- **`akm health` counts graph-extracted files per run.** Its
|
|
1353
|
-
`graphExtraction.extractedFiles` added the whole stored graph's file count
|
|
1354
|
-
once per improve run in the window; it now adds the files each run
|
|
1355
|
-
extracted, as `entities` and `relations` already did.
|
|
1356
|
-
(`src/commands/health/improve-metrics.ts`)
|
|
1357
|
-
- **`akm show`'s `related` refs no longer depend on index row order.** When
|
|
1358
|
-
two entries index the same file, the ref shown for it was whichever row
|
|
1359
|
-
SQLite returned last; the lowest concept id now wins, and shared entity
|
|
1360
|
-
names are read in a fixed order. The ranking itself (most shared entities,
|
|
1361
|
-
then path) was already deterministic. (`src/indexer/graph/graph-related.ts`)
|
|
1362
|
-
- **`akm search`/`akm curate` no longer store a pasted credential verbatim in
|
|
1363
|
-
`state.db`.** The Claude Code hook curates every user prompt, so a
|
|
1364
|
-
credential pasted into a prompt (`PASSWORD=…`, `TOKEN=…`, `SECRET=…`, a
|
|
1365
|
-
`Bearer` header, a JWT, a `ghp_…`/`xox…`/`AKIA…` token, a PEM private key, a
|
|
1366
|
-
`user:pass@` URL, …) flowed straight into the query text and was persisted
|
|
1367
|
-
as-is in both `usage_events.query` and the `events` table's
|
|
1368
|
-
`metadata_json` — a scan of mined queries found 22 credential-like values
|
|
1369
|
-
stored this way. `logSearchEvent`/`logCurateEvent` now redact the query
|
|
1370
|
-
with `redactCredentialPatterns` (extended with the shapes above, plus a
|
|
1371
|
-
`NAME=value`/`NAME: value` pass for names containing password, passwd,
|
|
1372
|
-
secret, token, auth, credential(s), or an api/private key — the value is
|
|
1373
|
-
replaced with `[REDACTED]`, the name is kept so queries stay useful for
|
|
1374
|
-
evaluation) before either write, so a `show`/`select` event tracing back to
|
|
1375
|
-
the search — which copies the search event's already-persisted `query`
|
|
1376
|
-
metadata — inherits the same redacted text. Existing rows already written
|
|
1377
|
-
are not rewritten. (`src/core/redaction.ts`, `src/commands/read/search.ts`,
|
|
1378
|
-
`src/commands/read/curate.ts`)
|
|
1379
|
-
- **`engines.<name>.supportsJsonSchema` on a `kind: "llm"` engine is a known
|
|
1380
|
-
key again.** `LlmConnectionConfigSchema` declares it and `llm/client.ts`
|
|
1381
|
-
reads it, but the named-engine object (`LlmEngineSchema`) never listed it,
|
|
1382
|
-
so this release's unknown-key walk named it on every load and `akm migrate
|
|
1383
|
-
apply` would have deleted a live setting from config.json.
|
|
1384
|
-
(`src/core/config/schema/engines.ts`)
|
|
1385
|
-
- **`akm migrate` finds the leaked activity registry where earlier releases
|
|
1386
|
-
actually wrote it.** The `deadResidue` step looked for
|
|
1387
|
-
`maintenance-activities/` under `$STATE`; the maintenance barrier created it
|
|
1388
|
-
next to its own lock under `$DATA`, so the directory that had grown to
|
|
1389
|
-
229,943 four-kilobyte sidecars (927 MB) on one host was never reported or
|
|
1390
|
-
removed. Both roots are checked, the registry is reported as one entry rather
|
|
1391
|
-
than once per sidecar, and a directory already listed whole is not descended
|
|
1392
|
-
into by the sidecar scan. (`scripts/akm-migrate/migrate/dead-residue.ts`)
|
|
1393
|
-
- **`akm bundle add`'s `--name` is now a contract on every add path (local,
|
|
1394
|
-
website, registry), not a hint.** An explicit `--name` that is not a legal
|
|
1395
|
-
bundle slug, or that is already taken by a different bundle, used to fall
|
|
1396
|
-
back silently — `deriveBundleId` minted a derived name, or a `-<hash>`
|
|
1397
|
-
suffix — so `akm bundle add ... --name my.bundle` installed under a name
|
|
1398
|
-
the caller never asked for, without saying so. It now fails with a
|
|
1399
|
-
`UsageError` (exit 2) naming the rule, before any write (config, lock, or
|
|
1400
|
-
network sync). Re-adding an already-installed ref under a *different*
|
|
1401
|
-
`--name` than it already carries used to keep the existing key and say
|
|
1402
|
-
nothing; it now fails the same way, naming the existing key and
|
|
1403
|
-
`akm bundle rename <old> <new>`. A DERIVED name (no `--name` given) is
|
|
1404
|
-
unaffected and keeps `deriveBundleId`'s forgiving `-<hash>` uniqueness
|
|
1405
|
-
fallback. Every `akm bundle add` result (local, website, and registry) now
|
|
1406
|
-
also carries `bundleId` (the resolved bundle key), and a registry add's
|
|
1407
|
-
result always carries `registryId` (the registry install id) rather than
|
|
1408
|
-
only when it happens to differ from `bundleId`, so a caller no longer has
|
|
1409
|
-
to reconstruct the key from `sourceAdded`/`installed`.
|
|
1410
|
-
- **`akm bundle add <registry ref> --name <name>` now keys the bundle by
|
|
1411
|
-
`<name>`.** For npm, `github:` and Git refs, `--name` was accepted and then
|
|
1412
|
-
dropped before the bundle key was derived, so the bundle was keyed by the
|
|
1413
|
-
basename of its materialized cache directory instead — `extracted`, or
|
|
1414
|
-
`extracted-<hash>` once that was taken — and its assets were only
|
|
1415
|
-
addressable as `extracted//…`. The name now goes through the same
|
|
1416
|
-
slug-legality and uniqueness rules as a local or website add. The install's
|
|
1417
|
-
registry id is still recorded as `registryId`, so `akm bundle update` and
|
|
1418
|
-
`akm bundle remove` keep resolving the original ref. Re-adding a ref that is
|
|
1419
|
-
already installed keeps its existing key, as local and website re-adds do.
|
|
1420
|
-
- **A registry bundle added without `--name` is keyed by its package or repo
|
|
1421
|
-
name instead of `extracted`.** `akm bundle add npm:<pkg>` now creates bundle
|
|
1422
|
-
`<pkg>` (`npm:@scope/pkg` → `pkg`), and `github:owner/repo` or a Git URL
|
|
1423
|
-
ending in `/repo` creates `repo` — the mapping the bundle schema already
|
|
1424
|
-
documented for `registryId`. The key used to come from the basename of the
|
|
1425
|
-
cache directory the package was unpacked into, which is always `extracted`,
|
|
1426
|
-
so every registry bundle after the first was `extracted-<hash>`. A dotted
|
|
1427
|
-
or mixed-case name is slugged like a directory name (`Foo.js` → `foo-js`).
|
|
1428
|
-
Bundles that are already installed keep their current key, including
|
|
1429
|
-
`extracted`, because every recorded `extracted//…` ref depends on it.
|
|
1430
|
-
- **A one-file change in a large directory no longer costs `akm index` half
|
|
1431
|
-
an hour.** Both full-text tables keyed their per-entry deletes on
|
|
1432
|
-
`entry_id`, an unindexed FTS5 column, so every upsert scanned the whole
|
|
1433
|
-
full-text index, twice per entry per run. On a 23.9k-entry index, one
|
|
1434
|
-
touched file in a flat `knowledge/` directory of 13.7k entries took 26
|
|
1435
|
-
minutes (task run `2026-09-24T20-30-01-663Z`); on backup copies of that
|
|
1436
|
-
index the same rescan took 31 minutes before this change and takes 38 s
|
|
1437
|
-
after it. FTS rows are keyed by rowid, and the
|
|
1438
|
-
first writable open after upgrading realigns an existing index in place,
|
|
1439
|
-
about 10 s and ~1.1 GB peak memory at that size, with no index-generation
|
|
1440
|
-
bump, so an older binary keeps reading it. A writable open realigns again
|
|
1441
|
-
if an older binary sharing the generation has written rows since.
|
|
1442
|
-
- **One bad scheduler-sync item, or one bad migration step, no longer fails
|
|
1443
|
-
the whole operation.** `akm task sync` used to throw and abort the entire
|
|
1444
|
-
reconciliation over one binding it could not reconcile or one bundle whose
|
|
1445
|
-
sources failed to read; that binding or bundle is now reported in the sync
|
|
1446
|
-
result's `failures: [{path, ref?, reason}]` (documented in
|
|
1447
|
-
`docs/reference/cli.md`) while every other one still syncs (see Changed).
|
|
1448
|
-
`akm-migrate`'s
|
|
1449
|
-
`runMigration` (`scripts/akm-migrate/run-migrate.ts`) now runs every step
|
|
1450
|
-
under its own catch too: a step's own throw (or, under `apply`, its
|
|
1451
|
-
read-only fallback failing as well) is recorded in the plan's new
|
|
1452
|
-
`failedSteps: [{step, error}]` and forces `status: "blocked"` instead of
|
|
1453
|
-
ending the run with no plan at all — the remaining steps still run in
|
|
1454
|
-
order. `akm migrate status|apply` (`scripts/akm-migrate/main.ts`) already
|
|
1455
|
-
exits 1 for any blocked plan, so a poisoned step no longer exits the
|
|
1456
|
-
internal-error code 70 with nothing printed.
|
|
1457
|
-
- **The legacy `stashDir`/`sources[]`/`installed` config shape is persisted
|
|
1458
|
-
by `akm migrate apply`, and an empty one no longer fails every command**
|
|
1459
|
-
(#863). `migrateLegacySourceShape`
|
|
1460
|
-
(`src/core/config/legacy-source-shape-shim.ts`) has always converted a
|
|
1461
|
-
usable `stashDir`/`sources[]`/`installed` in memory on every load and told
|
|
1462
|
-
the user to run `akm migrate apply` to make that stick, but nothing on disk
|
|
1463
|
-
ever did; the migrator's `configFile` step now writes that current shape
|
|
1464
|
-
back once, under a backup. Separately, through 0.9.16 and 0.9.17-alpha.3 a
|
|
1465
|
-
config whose `sources` was `[]` (what 0.8.9's `akm source remove` writes
|
|
1466
|
-
after the last source is removed) or whose `stashDir` was empty or
|
|
1467
|
-
unusable failed every command with exit 78; it now loads, with the shim's
|
|
1468
|
-
one-time warning.
|
|
1469
|
-
- **A `version: 2` or `version: 3` task source reads and runs again instead
|
|
1470
|
-
of failing closed on upgrade.** `e413af024` deleted the in-memory
|
|
1471
|
-
v2/v3 -> v4 read shim on the argument that "untrusted source cannot carry
|
|
1472
|
-
obsolete activation semantics" — but activation had already moved to
|
|
1473
|
-
host-local `scheduler.enabled` in that same commit, so the shim never
|
|
1474
|
-
carried activation in the first place, and deleting it just reintroduced
|
|
1475
|
-
the exact upgrade break 0.9.4 originally shipped the shim to fix ("would
|
|
1476
|
-
have broken every pre-0.9.4 scheduled task headlessly on upgrade").
|
|
1477
|
-
`parseTaskSource` (`src/tasks/source/parse-task-source.ts`) once again
|
|
1478
|
-
routes `version: 2`/`version: 3` through the SAME pure planners
|
|
1479
|
-
`akm migrate apply` uses, entirely in memory, with a one-line stderr
|
|
1480
|
-
deprecation warning (once per file per process) and no disk write; the
|
|
1481
|
-
parsed document never carries a source-owned `enabled` field, since the
|
|
1482
|
-
v3->v4 planner already never hoists `akm.enabled` or a schedule entry's
|
|
1483
|
-
`enabled` key. Only a v2/v3 document the deterministic conversion itself
|
|
1484
|
-
cannot resolve still fails with `TASK_SCHEMA_VERSION_UNSUPPORTED`, naming
|
|
1485
|
-
the specific blocked reason. The same in-memory shim now also tolerates a
|
|
1486
|
-
declared `version: 4` document whose `schedule[]` still carries a
|
|
1487
|
-
per-entry `enabled` key — 0.9.15's v4 grammar accepted it (`akm task add
|
|
1488
|
-
--disabled` wrote it), this release's does not, and without this the
|
|
1489
|
-
upgrade break above recurs for every 0.9.15-authored scheduled task. The
|
|
1490
|
-
key is stripped without ever being read — `enabled: false` cannot
|
|
1491
|
-
suppress a granted task and `enabled: true` cannot schedule an ungranted
|
|
1492
|
-
one, since activation stays host-local `scheduler.enabled`. `akm task
|
|
1493
|
-
validate` reports such a file `converts` (`sourceVersion` still `4`)
|
|
1494
|
-
instead of `valid`, since it read through the shim rather than the direct
|
|
1495
|
-
v4 path.
|
|
1496
|
-
- **Lock contention exits 75, like `state.db` contention.** Another process
|
|
1497
|
-
holding `akm.lock`'s write sentinel (`LOCKFILE_CONTENDED`, was a config
|
|
1498
|
-
error, exit 78) or the asset-mutation writer lease past its wait
|
|
1499
|
-
(`ASSET_MUTATION_LEASE_HELD`, was an unclassified error, exit 70) is now a
|
|
1500
|
-
retry-shortly `TransientError`, exit 75.
|
|
1501
|
-
- **`akm health`'s `state.db` repair steps no longer corrupt the rebuilt
|
|
1502
|
-
file.** `state-db-integrity` used to suggest `.dump` into a new file with
|
|
1503
|
-
no writer stop; it now says to back up `state.db`, `.recover` it into
|
|
1504
|
-
`state.new.db`, confirm that passes `quick_check`, stop every akm process,
|
|
1505
|
-
delete `state.db-wal` and `state.db-shm`, then swap the new file in — a
|
|
1506
|
-
leftover WAL replays onto the new database and corrupts it.
|
|
1507
|
-
- **The package launcher (`dist/akm`) passes `--scheduler-context` through to
|
|
1508
|
-
the CLI** instead of re-validating the descriptor with a stale copy of its
|
|
1509
|
-
schema, which rejected every descriptor 0.9.17 writes.
|
|
1510
|
-
(`scripts/node-runtime/akm`)
|
|
1511
|
-
- **A `task_history` row with a malformed `engine` value decodes.** The
|
|
1512
|
-
decoder used to reject the whole row when `engine` was present but not a
|
|
1513
|
-
string or `null`; it now drops the bad value and decodes the rest, the
|
|
1514
|
-
tolerance it already applied to every other unrecognized field.
|
|
1515
|
-
(`src/storage/repositories/task-history-repository.ts`)
|
|
1516
|
-
- **The LLM enrichment budget warning prints for every index run.** When the
|
|
1517
|
-
metadata-enrichment pass ran out of its wall-clock budget during an index
|
|
1518
|
-
another command started (`akm bundle update`, `akm setup`, `akm bundle
|
|
1519
|
-
add`, improve's preflight, a read command's auto-index), it stopped
|
|
1520
|
-
silently; it now prints the same "LLM enrichment budget exceeded" warning
|
|
1521
|
-
`akm index` does.
|
|
1522
|
-
|
|
1523
|
-
## [0.9.17-alpha.3] - 2026-09-24
|
|
1524
|
-
|
|
1525
|
-
### Fixed
|
|
1526
|
-
|
|
1527
|
-
- **Unscoped `akm task sync` no longer aborts the whole host on the first
|
|
1528
|
-
bundle root that happens to contain any symlink.** `captureGuardedDirectoryManifest`
|
|
1529
|
-
threw for every symbolic directory entry it listed, even one the scheduler
|
|
1530
|
-
never reads (e.g. a third-party skill repo's `CLAUDE.md -> AGENTS.md`) —
|
|
1531
|
-
`SchedulerSourceCollector` manifests every scanned bundle's root, so one
|
|
1532
|
-
such bundle among many enabled ones failed sync entirely, dry-run included.
|
|
1533
|
-
A symlink that stays inside its bundle root is now recorded in the guarded
|
|
1534
|
-
directory manifest as its own `"symlink"` kind, identified without
|
|
1535
|
-
following it (its `readlink` text plus its no-follow `lstat` identity), so
|
|
1536
|
-
change detection still works; it is never read or descended into. A symlink
|
|
1537
|
-
sitting exactly where a task or workflow source lives (a `.yml` under
|
|
1538
|
-
`tasks/`, any `.yml` under an `akm-task` bundle, or a workflow-named file
|
|
1539
|
-
under `workflows/`) is reported as its own per-source failure — "is a
|
|
1540
|
-
symbolic source; guarded reads require a regular no-follow owner" — and its
|
|
1541
|
-
ref is not scheduled, even when a real sibling file shares that ref, while
|
|
1542
|
-
every other task and workflow still reconciles. A
|
|
1543
|
-
symlink that resolves outside the bundle root, or one that is broken and
|
|
1544
|
-
cannot be identified safely, is still refused, and a bundle root whose
|
|
1545
|
-
`tasks` or `workflows` entry is itself a symlink still refuses loudly,
|
|
1546
|
-
since that is a schedulable source location.
|
|
1547
|
-
|
|
1548
|
-
## [0.9.17-alpha.2] - 2026-09-24
|
|
1549
|
-
|
|
1550
|
-
### Fixed
|
|
1551
|
-
|
|
1552
|
-
- **A config carrying the retired `experimental.workflowEngine` key no longer
|
|
1553
|
-
fails to load.** `ExperimentalConfigSchema` moved from `.passthrough()` to
|
|
1554
|
-
`.strict()` in 0.9.16 (`cc6152e02`), after `workflowEngine` had already been
|
|
1555
|
-
removed from it in `e0655d13c`; a real config a 0.9.15 install wrote (whose
|
|
1556
|
-
passthrough still accepted the key) then failed every command with
|
|
1557
|
-
`Invalid config: experimental: Unrecognized key(s) in object: 'workflowEngine'`.
|
|
1558
|
-
The config loader now strips known-retired `experimental.*` keys in memory
|
|
1559
|
-
before validation, warning once and naming `akm migrate apply`; a genuinely
|
|
1560
|
-
unknown/misspelled key (e.g. `improveAutonomyy`) still fails closed.
|
|
1561
|
-
`akm migrate apply` removes the retired key from `config.json` on disk
|
|
1562
|
-
(with the usual backup), and `--dry-run` reports the pending removal.
|
|
1563
|
-
- **Unscoped `akm task sync` no longer crashes when an enabled website or npm
|
|
1564
|
-
bundle is configured.** The sync plan loop resolved every active source
|
|
1565
|
-
through the write-target resolver, which rejects any kind other than
|
|
1566
|
-
`filesystem`/`git` outright (writes, and therefore scheduler state, are
|
|
1567
|
-
undefined for those kinds — the same rejection `akm task enable` already
|
|
1568
|
-
hit). Unscoped sync now skips non-filesystem/git bundles when building
|
|
1569
|
-
install operations — they never carried schedulable tasks — while
|
|
1570
|
-
inactive-bundle removal/revocation still sees them. A scoped
|
|
1571
|
-
`akm task sync --bundle <website-or-npm-bundle>` now fails with a clear
|
|
1572
|
-
usage error instead of the write-target `ConfigError`.
|
|
1573
|
-
|
|
1574
|
-
## [0.9.17-alpha.1] - 2026-09-24
|
|
1575
|
-
|
|
1576
|
-
### Added
|
|
1577
|
-
|
|
1578
|
-
- **`akm improve --require-engines` now records its reachability probe on the
|
|
1579
|
-
run result (R17).** `assertRequiredEnginesReachable` only ever reported a
|
|
1580
|
-
failure (abort, exit 78); a probe that passed — including a slow or
|
|
1581
|
-
flapping gateway that still answered in time — left no trace once the run
|
|
1582
|
-
proceeded. It now returns one outcome per probed target (`process`,
|
|
1583
|
-
`engine`, `endpoint`, `reachable`, `latencyMs`), threaded through a new
|
|
1584
|
-
`AkmImproveOptions.engineProbe` and copied onto the persisted result as
|
|
1585
|
-
`AkmImproveResult.engineProbe`. Omitted entirely when `--require-engines`
|
|
1586
|
-
was not passed; a result persisted without it (every run before this
|
|
1587
|
-
change) still decodes. `--require-engines --dry-run` results carry it too.
|
|
1588
|
-
- **Reflect had no way to exclude raw wiki-ingest snapshots, which are the
|
|
1589
|
-
longest generations in the ledger (89.5s/161.8s observed).** `wikis/articles/raw/*.md`
|
|
1590
|
-
website snapshots index as `knowledge/wikis/articles/raw/<slug>`, and
|
|
1591
|
-
reflect's `allowedTypes` filter is type-only, so it can't exclude a subset
|
|
1592
|
-
of the `knowledge` type. `processes.reflect` now accepts an optional
|
|
1593
|
-
`excludeRefPrefixes: string[]` — conceptId prefixes, matched after
|
|
1594
|
-
stripping an optional `bundle//` from both the ref and each prefix.
|
|
1595
|
-
`shouldSkipRef` skips a matching ref with reason `exclude-filter`, for
|
|
1596
|
-
reflect only (distill and consolidate are memory-only and reject the key).
|
|
1597
|
-
A trailing `/` on a prefix is ignored, so
|
|
1598
|
-
`"knowledge/wikis/articles/raw/"` excludes the same refs as
|
|
1599
|
-
`"knowledge/wikis/articles/raw"`.
|
|
1600
|
-
|
|
1601
|
-
- **`akm health` now checks state.db's own SQLite integrity.** A new hard
|
|
1602
|
-
`state-db-integrity` check runs a read-only `PRAGMA quick_check` against
|
|
1603
|
-
`state.db` and fails, naming the returned diagnostic lines and the repair
|
|
1604
|
-
steps (back up, dump/restore via `sqlite3`, verify, swap in), when it
|
|
1605
|
-
reports anything other than `ok`. The same check reports state.db's
|
|
1606
|
-
freelist ratio (the fraction of pages `VACUUM` could reclaim) and warns
|
|
1607
|
-
above 50%. Previously nothing in `akm health` looked past a successful
|
|
1608
|
-
append/read round trip, which stays true on a database that is corrupt at
|
|
1609
|
-
the SQLite level.
|
|
1610
|
-
- **The retention purge (`akm improve`) now VACUUMs state.db when more than
|
|
1611
|
-
half its pages are free**, immediately after the events/improve_runs/
|
|
1612
|
-
cycle-metrics purge, recording a `state_db_vacuumed` event with pages
|
|
1613
|
-
before/after. Opportunistic: a locked/busy database is skipped, not
|
|
1614
|
-
raised, so it never fails the purge pass it follows.
|
|
1615
|
-
|
|
1616
|
-
### Changed
|
|
1617
|
-
|
|
1618
|
-
- **The orphan-state GC pass no longer probes index.db once per pending
|
|
1619
|
-
row.** `runOrphanStateGcPass` used to call `getEntryByRef` (up to two
|
|
1620
|
-
statements each, via its bare-ref fallback) for every pending
|
|
1621
|
-
`asset_salience` / `asset_outcome` row — 2,101 pending rows cost 83–100s
|
|
1622
|
-
per run. It now builds one snapshot of every live `item_ref` in index.db up
|
|
1623
|
-
front and matches every pending row against it in memory: O(1) index.db
|
|
1624
|
-
queries per run instead of one probe per row, with the same live/orphan
|
|
1625
|
-
resolution (including the bundle-qualified-exact and bare-conceptId-suffix
|
|
1626
|
-
fallback) as before.
|
|
1627
|
-
- **Memory inference no longer forces a full reindex for the file(s) it
|
|
1628
|
-
writes.** The post-inference maintenance step used to call the full
|
|
1629
|
-
`reindexFn` (42–220s per run, typically for one written derived fact)
|
|
1630
|
-
whenever memory inference split a parent. `runMemoryInferencePass` now
|
|
1631
|
-
reports the exact paths it wrote or rewrote (`writtenPaths`, sourced from
|
|
1632
|
-
the run's write-provenance journal), and the maintenance pass indexes just
|
|
1633
|
-
those files with `indexWrittenAssets` instead — closing and reopening the
|
|
1634
|
-
shared index.db handle around the call with the same discipline the full
|
|
1635
|
-
reindex used (#584). The separate post-consolidation full reindex is
|
|
1636
|
-
removed outright rather than re-gated: it used to fire whenever
|
|
1637
|
-
`consolidation.processed > 0` (memories the LLM judged), but
|
|
1638
|
-
merge/delete/contradict ops are advisory and never auto-applied, and the
|
|
1639
|
-
one op that does execute — promote — writes a proposal to state.db, not to
|
|
1640
|
-
the stash. Consolidation therefore cannot change a file the index reads,
|
|
1641
|
-
so the reindex had no precondition it could ever satisfy.
|
|
1642
|
-
- **The improve loop's reflect dispatch now checks the proposal
|
|
1643
|
-
fingerprint/rejection-backoff guard *before* calling reflect, not just
|
|
1644
|
-
after.** `fingerprint_match` and `rejection_backoff` were evaluated only
|
|
1645
|
-
inside `createProposal`, which runs after reflect's full generation and
|
|
1646
|
-
quality-judge call — so a ref already guaranteed to be skipped still paid
|
|
1647
|
-
the LLM cost (measured: 2–16% of reflect LLM seconds spent on refs the
|
|
1648
|
-
guard then discarded). The guard's fingerprint is an input fingerprint
|
|
1649
|
-
(target ref, source, before-hash, model id), computable before dispatch, so
|
|
1650
|
-
`checkProposalGuard` (`src/commands/proposal/repository.ts`) exposes the
|
|
1651
|
-
identical check `createProposal` runs post-generation — the two share one
|
|
1652
|
-
implementation and can never disagree. `runLoopReflectPass`
|
|
1653
|
-
(`src/commands/improve/loop-stages.ts`) now calls it first; a hit skips
|
|
1654
|
-
`reflectFn` entirely and lands in the existing `reflect-cooldown` bucket
|
|
1655
|
-
with the same `reflect_invoked` event the signal-delta cursor
|
|
1656
|
-
(`buildLatestProposalTsMap`) reads, so cursor advancement and run-result
|
|
1657
|
-
classification are unchanged. `createProposal`'s post-generation check
|
|
1658
|
-
remains the authoritative gate.
|
|
1659
|
-
- **Consolidate's plan schema and prompt are promote-only.** The apply loop
|
|
1660
|
-
only ever executed `promote` — `merge`/`delete`/`contradict` were advisory
|
|
1661
|
-
by design and never applied — but the schema still asked for all four ops
|
|
1662
|
-
plus a free-text `warnings` array, and completion tokens rose from 7–8k to
|
|
1663
|
-
21–30k per run after the 35B-A3B model switch with no change in
|
|
1664
|
-
promotions. `CONSOLIDATE_PLAN_JSON_SCHEMA` and `consolidate-system.md` now
|
|
1665
|
-
request only `promote` (with `reason` capped at 200 chars), and `isValidOp`
|
|
1666
|
-
rejects any other op shape — e.g. from a model that ignores the schema —
|
|
1667
|
-
with the existing "skipping invalid operation" warning instead of treating
|
|
1668
|
-
it as an actionable plan entry. `ConsolidateResult.merged` / `deleted` /
|
|
1669
|
-
`contradicted` and the `planned` op breakdown are unchanged in shape and
|
|
1670
|
-
stay zero.
|
|
1671
|
-
- **`improve-maintenance-passes.test.ts` moved under `tests/integration/`.**
|
|
1672
|
-
The suite opens a real `state.db` via `openStateDatabase`, which AGENTS.md's
|
|
1673
|
-
ORG-03..06 rule places under `tests/integration/`, not `tests/`; no content
|
|
1674
|
-
change. Also corrected
|
|
1675
|
-
`docs/architecture/specs/improve-collapse-churn-detector-design.md` §2.5,
|
|
1676
|
-
which described the post-loop collapse-detector gate as `consolidationRan
|
|
1677
|
-
OR recombination.processed > 0` — no `recombination` value is plumbed into
|
|
1678
|
-
`runImprovePostLoopStage` and no recombine pass exists in the codebase, so
|
|
1679
|
-
the spec now matches the shipped `consolidationRan`-only gate and notes
|
|
1680
|
-
that the recombine-triggered pass is not implemented.
|
|
1681
|
-
- **Graph-extraction relations are now compact `[from, type, to]` triples
|
|
1682
|
-
instead of `{"from","to","type"}` objects, and the batch graph-extraction
|
|
1683
|
-
call now sends a `responseSchema`.** The object-keyed form cost 10+ tokens
|
|
1684
|
-
per relation for no signal, and completion tokens cost far more than
|
|
1685
|
-
prompt tokens; a compact triple form measured −51% / −18% completion
|
|
1686
|
-
tokens on two chunks. `graph-extract.ts`'s single-asset and batch prompts
|
|
1687
|
-
and JSON schemas now ask for `["from", "type", "to"]` (`type` may be `""`);
|
|
1688
|
-
`parseGraphExtraction` accepts both the triple form and the legacy object
|
|
1689
|
-
form (a relation-level `confidence` is still read from a legacy object,
|
|
1690
|
-
though the schema no longer offers it — the prompt never asked for one).
|
|
1691
|
-
Separately, production runs graph extraction batched
|
|
1692
|
-
(`processes.graphExtraction.batchSize`), and `extractGraphFromBodies` sent
|
|
1693
|
-
no `responseSchema` at all, so the R12b output-bounding schema only ever
|
|
1694
|
-
reached the single-asset path. The batch call now sends the same
|
|
1695
|
-
`maxItems`-bounded schema (scoped to the batch's asset count) through the
|
|
1696
|
-
same `supportsJsonSchema`-gated `responseSchema` field the single-asset
|
|
1697
|
-
call uses. `GRAPH_EXTRACT_PROMPT_VERSION` bumps `v2` → `v3`, so every file
|
|
1698
|
-
re-extracts once on the next graph pass — entity semantics, caps, chunking
|
|
1699
|
-
and batch sizing are unchanged.
|
|
234
|
+
`improve-churn-ratio`, `collapse-churn-detector`). `processes.reflect` also
|
|
235
|
+
gains `excludeRefPrefixes: string[]` to skip a ref prefix (a raw
|
|
236
|
+
wiki-ingest snapshot tree, say) that a type-only `allowedTypes` filter
|
|
237
|
+
can't carve out on its own.
|
|
1700
238
|
|
|
1701
239
|
### Removed
|
|
1702
240
|
|
|
1703
|
-
- **The
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
`
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
241
|
+
- **The LLM entity-graph extraction pass.** `akm improve`'s per-file
|
|
242
|
+
entity/relation extraction, its tables, and `akm show`'s `related` list are
|
|
243
|
+
gone: on the navigation eval, vector kNN beat `related` by 0.157 P@5
|
|
244
|
+
[0.051, 0.260], and its only other consumer, curate's support refs, moved
|
|
245
|
+
to declared links (see Added). The tables are dropped unconditionally on
|
|
246
|
+
an index's next writable open. The `graph-refresh` improve strategy and its
|
|
247
|
+
`akm-graph-refresh-weekly` task template are retired with it — naming
|
|
248
|
+
`graph-refresh` (via `--strategy` or a task) now fails outright, naming the
|
|
249
|
+
retirement. `akm health` drops every graph metric (KPI card, summary rows,
|
|
250
|
+
per-run duration/entity/relation columns).
|
|
251
|
+
- **LLM metadata enrichment (`index.metadataEnhance`).** On a 49-query,
|
|
252
|
+
1,968-entry measurement it moved search nDCG@10 by -0.0092 [-0.0324,
|
|
253
|
+
+0.0165] and long-prompt curate P@5 by -0.054 [-0.093, -0.012] — no
|
|
254
|
+
measurable benefit for a full pass costing about 27 B70-hours. It was
|
|
255
|
+
already off by default.
|
|
256
|
+
- **The per-run forgetting-safety lane.** A one-time cutover guard from a
|
|
257
|
+
June 2026 ranking-formula change that had kept running on every improve run
|
|
258
|
+
since. Thirty days of events showed no marginal pick over the signal-delta
|
|
259
|
+
lane once the new retrieval scope (above) applied: 4 of its last 5 flagged
|
|
260
|
+
refs were also picked by signal-delta, and the 5th was independently
|
|
261
|
+
planned under signal-delta the same run.
|
|
262
|
+
- **Removed flags and drain policies.** `akm search --no-project-context` and
|
|
263
|
+
`akm proposal drain --policy`/`--max-diff-lines` now fail as unknown flags
|
|
264
|
+
(exit 2), and the `personal-stash`/`conservative`/`manual` drain policies
|
|
265
|
+
are gone. Drain, and improve's triage pre-pass, now accept only a proposal
|
|
266
|
+
the quality judge passed on its exact content and reject an empty diff;
|
|
267
|
+
everything else goes to `processes.triage.judgment` or waits for review.
|
|
268
|
+
Extract and consolidate proposals, which `personal-stash` auto-accepted on
|
|
269
|
+
size alone, are no longer auto-accepted.
|
|
270
|
+
- **Retired config keys** — kept and tolerated, dropped only by `akm migrate
|
|
271
|
+
apply`: `index.graph.*`, `index.metadataEnhance`, `search.minScore`,
|
|
272
|
+
`search.graphBoost.*`, `improve.utilityDecay.*`, `improve.collapseDetector`,
|
|
273
|
+
`improve.salience.replayBudget`, `processes.triage.policy`,
|
|
274
|
+
`processes.triage.maxDiffLines`, `processes.consolidate.contradictionDetection`,
|
|
275
|
+
the retired `processes.consolidate.antiCollapse` merge guards
|
|
276
|
+
(`maxGeneration`, `lexicalDiversityCheck`, `mergeInformationFloor`,
|
|
277
|
+
`minSpecificityRetention` — `antiCollapse` itself, and its
|
|
278
|
+
`randomClusterFraction` mixing, are unaffected), and every improve
|
|
279
|
+
strategy's `processes.graphExtraction.*` and
|
|
280
|
+
`processes.consolidate.incrementalSince`/`.neighborsPerChanged` (the pair
|
|
281
|
+
pass replaces incremental-window candidate selection with the improve
|
|
282
|
+
ledger). A leftover `improve.strategies["graph-refresh"]` override block is
|
|
283
|
+
also dropped this way; `defaults.improveStrategy: "graph-refresh"` is not —
|
|
284
|
+
change that one by hand.
|
|
285
|
+
- **Also removed, superseded by the simpler mechanisms above:** the
|
|
286
|
+
scheduler source-grant layer and its fire-time re-check, the filesystem
|
|
287
|
+
transaction journals used by proposal accept/revert, the maintenance
|
|
288
|
+
barrier and its per-process activity registry, the SQLite lock-operation
|
|
289
|
+
mutex, the strict per-key config schemas and the retired-key registry, and
|
|
290
|
+
the health advisories tied to all of the above. `akm migrate apply` deletes
|
|
291
|
+
the files they left behind: `$DATA/txn/`, `$DATA/txn-quarantine/`,
|
|
292
|
+
`maintenance.barrier.lock`, and the `maintenance-activities/` registry,
|
|
293
|
+
which leaked an entry per process (one host had 229,943 entries, 927 MB).
|
|
1718
294
|
|
|
1719
295
|
### Fixed
|
|
1720
296
|
|
|
1721
|
-
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
`
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
`defaults.llmEngine` via `resolveImproveLlmExecution` with no profile/process
|
|
1747
|
-
layer, so that key was never read. The warning now names only
|
|
1748
|
-
`defaults.llmEngine`.
|
|
1749
|
-
- **The distill/reflect LLM-as-judge quality gate inherited the generation
|
|
1750
|
-
runner's temperature, and its averaged score hid which criterion actually
|
|
1751
|
-
failed.** `runQualityJudge`'s request only pinned `enableThinking: false`,
|
|
1752
|
-
so the judge ran at whatever temperature generation used — measured at 0.3,
|
|
1753
|
-
the verdict flipped on 10/16 identical inputs, vs. 0/16 at temperature 0.
|
|
1754
|
-
The request now also pins `temperature: 0`, for both the distill and
|
|
1755
|
-
reflect judges that share this function, independent of the runner's
|
|
1756
|
-
configured temperature. Separately, both judge prompts asked for one
|
|
1757
|
-
averaged float, so a criterion carrying no signal was invisible in
|
|
1758
|
-
production. They now ask for per-criterion integer scores
|
|
1759
|
-
(`buildJudgePrompt`: novelty/actionability/nonRedundancy;
|
|
1760
|
-
`buildReflectJudgePrompt`: feedbackAlignment/preservation/quality), averaged
|
|
1761
|
-
in code to the same `score` the unchanged 3.5/2.5 thresholds gate on. The
|
|
1762
|
-
parser accepts this new `{"scores": {...}, "reason"}` shape and still
|
|
1763
|
-
accepts the old `{"score": <float>, "reason"}` shape a model may return;
|
|
1764
|
-
each criterion (or the bare score) must be a finite number in 1..5 or the
|
|
1765
|
-
response routes to review exactly as a parse failure does today. The
|
|
1766
|
-
per-criterion scores, when present, are now carried through
|
|
1767
|
-
`QualityJudgeResult.criteria` into the `distill_invoked` event metadata and
|
|
1768
|
-
rejection-envelope frontmatter `writeQualityRejection` writes, and into
|
|
1769
|
-
reflect's `reflect_completed` rejection event as `qualityCriteria`.
|
|
1770
|
-
- **The judge parser accepted a partial `scores` object and auto-passed it.**
|
|
1771
|
-
`parseJudgeResponse` validated only that whatever criterion keys arrived
|
|
1772
|
-
held finite 1-5 values, then averaged over those keys alone — so a
|
|
1773
|
-
truncated judge response like `{"scores": {"novelty": 5}, "reason": "…"}`
|
|
1774
|
-
parsed to `score: 5.0` and `pass: true`, promoting content the judge never
|
|
1775
|
-
finished evaluating on its other criteria. `runQualityJudge` now passes the
|
|
1776
|
-
criterion key set its prompt asked for (`buildJudgePrompt`:
|
|
1777
|
-
novelty/actionability/nonRedundancy; `buildReflectJudgePrompt`:
|
|
1778
|
-
feedbackAlignment/preservation/quality) down to `parseJudgeResponse`, which
|
|
1779
|
-
returns a parse failure — routed to review, exactly as a malformed response
|
|
1780
|
-
is today — when any expected key is missing from `scores`.
|
|
1781
|
-
- **The reflect pre-generation proposal-guard skip (R9) emitted `reflect_invoked` with no paired `reflect_completed`.** `runLoopReflectPass`'s guard-skip branch in `loop-stages.ts` appended a synthetic `reflect_invoked` event to advance the signal-delta cursor, but never called `reflectFn`, so `reflect.ts`'s own `reflect_completed` emission never ran either — a new, permanent source of unpaired `reflect_invoked` rows for every fingerprint/backoff hit, violating the invoke/complete pairing invariant `buildReflectEventEmitters` documents. The branch now also appends a matching `reflect_completed` (`ok:false`, `reason:"cooldown"`, `subreason:"pre_generation_guard"`), mirroring `emitFailed`'s shape.
|
|
1782
|
-
- **R9's pre-generation proposal guard covered reflect only — distill paid for a full generation + judge call before the same fingerprint/backoff guard could reject it.** `runLoopDistillPass` had no equivalent of `runLoopReflectPass`'s pre-check, even though `createProposal`'s post-generation guard (and every rejected row R10 now mints under `source: "distill"`) applies to distill just as much. `runLoopDistillPass` now calls `checkProposalGuard` against the derived lesson/knowledge ref (distill's real `createProposal` call never targets the input ref) before dispatching `distillFn`; a hit routes to the pass's existing `distill-skipped` bucket and emits `distill_invoked` with a `skipped` outcome so `buildLatestProposalTsMap`'s signal cursor still advances.
|
|
1783
|
-
- **The distill pre-generation proposal guard could suppress a legitimate dispatch by checking a ref distill would never target.** For a memory input, distill's real `createProposal` call targets one of two refs decided at dispatch time inside `planMemoryKnowledgePromotion` — the derived knowledge ref when the deterministic promotion heuristic fires, the derived lesson ref otherwise — but `runLoopDistillPass`'s pre-check checked both candidate refs and skipped on the FIRST guard hit, so a stale fingerprint/backoff hit on the ref distill would NOT have targeted silently suppressed dispatch until that ref's fingerprint happened to change. The pre-check now resolves the SAME target `planMemoryKnowledgePromotion` would via `wouldPromoteMemoryToKnowledge` (`distill/promote-memory.ts`) — a thin wrapper that delegates to `planMemoryKnowledgePromotion` itself so the classification can never drift from the real dispatch decision, with no LLM call — and checks only that ref; content and the classification's `durableInputRef` are read via `planned.ref` alone, matching `akmDistill`'s real dispatch, while `planned.itemRef ?? planned.ref` feeds only the feedback-events query.
|
|
1784
|
-
- **The `akm improve` triage pre-pass drain's judgment LLM calls were unattributed in the usage report.** `runTriagePrePass`'s `drainProposalsFn` call dispatched judgment calls with no `withLlmStage` wrapper, unlike the standalone `akm proposal drain` CLI path, so they landed in `byProcessEngineModel` as unattributed (5 calls, 24s per run) instead of under a `triage` stage. The pre-pass drain is now wrapped in `withLlmStage("triage", …, { engine, process: "triage.judgment" })`, mirroring the CLI path.
|
|
1785
|
-
- **The batch graph-extraction provider-storm guard only recognized one error
|
|
1786
|
-
code.** After a failed batch call, `extractGraphFromBodies` skipped the
|
|
1787
|
-
per-asset fallback retry only for `LlmCallError`s coded `provider_error` —
|
|
1788
|
-
but a dead endpoint more often raises `network_error` (a dropped
|
|
1789
|
-
connection) or `provider_html_error` (a provider serving an HTML error
|
|
1790
|
-
page), both of which still paid the full per-asset fallback storm the
|
|
1791
|
-
guard exists to prevent. The predicate is now `isTransportFailure`
|
|
1792
|
-
(`src/llm/client.ts`), shared with `chatCompletion`'s retry classifier so
|
|
1793
|
-
the two cannot drift apart, and covers `provider_error`, `network_error`,
|
|
1794
|
-
and `provider_html_error`.
|
|
1795
|
-
- **`akm health`'s `state-db-integrity` check no longer crashes when the freelist/page-count read fails.** `getStateDbFreelistInfo` had a `finally` but no `catch` around its read-only open and pragma reads, unlike its sibling `runStateDbQuickCheck` — a throw there (e.g. an unopenable state.db) escaped `akm health` as an unclassified exit 70 on exactly the damaged database the check exists to report. It now returns a zeroed `StateDbFreelistInfo` with an `error` field, and the check renders that as a failed check instead of throwing.
|
|
1796
|
-
- **The post-purge VACUUM's `state_db_vacuumed` event now honors the caller's `EventsContext`.** `vacuumStateDbIfReclaimable` appended its event with a direct `insertEvent` call, bypassing `EventsContext.readOnly` and the injectable clock its sibling purge events (`events_purged`, `improve_runs_purged`, `improve_cycle_metrics_purged`) use in the same `runRetentionPurgePass` callback. It now appends the event via `appendEvent` with the caller's `EventsContext` plumbed through.
|
|
1797
|
-
- **Consolidate's per-chunk prompt excerpt truncated the raw file (frontmatter
|
|
1798
|
-
+ body) instead of the body.** `buildChunkPrompt` sliced `body.slice(0,
|
|
1799
|
-
bodyTruncation)` off the unstripped file; a memory whose frontmatter alone
|
|
1800
|
-
exceeded the excerpt length was judged on metadata only and never showed
|
|
1801
|
-
its own body text. The excerpt now truncates `stripFrontmatterBody(body)`;
|
|
1802
|
-
hot/queued detection is unchanged and still reads the raw body.
|
|
1803
|
-
- **Consolidate's chunk prompt carried an unused ~14k-char standards block and
|
|
1804
|
-
a header the model sometimes echoed back as a bogus `ref`.** Every chunk
|
|
1805
|
-
prompt resolved and injected a "Standards to follow" section
|
|
1806
|
-
(`resolveStandardsContext("memories/_consolidated", ...)`), but the chunk
|
|
1807
|
-
output is a promote-only op list that never reads it. Separately, the
|
|
1808
|
-
chunk header (`Chunk N of M, memories <first>–<last>:`) named the chunk's
|
|
1809
|
-
boundary memories with an en dash between two `memories/<name>` refs; on
|
|
1810
|
-
2026-09-24 the judge model returned promote ops whose `ref` was exactly
|
|
1811
|
-
that `memories/<first>–memories/<last>` range, naming a memory that does
|
|
1812
|
-
not exist and losing the promotion. `buildChunkPrompt` no longer takes a
|
|
1813
|
-
`standardsContext` and the header is now
|
|
1814
|
-
`Chunk N of M (<count> memories):` — no refs in it.
|
|
1815
|
-
- **Consolidate re-judged memories that were already promoted verbatim into
|
|
1816
|
-
`knowledge/`.** That duplication was previously discovered only after the
|
|
1817
|
-
LLM (`shouldSkipPromotionBodyDuplicate`), so a pool where the large
|
|
1818
|
-
majority of memories were already-promoted duplicates still paid the full
|
|
1819
|
-
chunk/LLM cost on all of them before being skipped.
|
|
1820
|
-
`inspectConsolidationPool` now drops those memories before any chunking or
|
|
1821
|
-
LLM work, sharing one `loadExistingKnowledgeBodyHashes` call and the same
|
|
1822
|
-
`cacheHash` domain with the post-LLM check so the two cannot disagree. The
|
|
1823
|
-
dropped count is reported as `prefilteredAlreadyPromoted` on the
|
|
1824
|
-
consolidate result and in a warning line. The pre-filter also now runs
|
|
1825
|
-
*before* the `consolidate.limit` cap (previously after), so a run with a
|
|
1826
|
-
limit set selects its oldest-modified window from the pre-filtered pool
|
|
1827
|
-
instead of re-selecting and re-dropping the same permanently-undeletable
|
|
1828
|
-
duplicates every run while fresh memories past the cap went unreached; the
|
|
1829
|
-
preview/eligibility path (`preparation.ts`) computes and passes the same
|
|
1830
|
-
hash set so the reported candidate pool agrees with what the run will act
|
|
1831
|
-
on. A live (non-preview) `akm improve` run reuses that same hash set for
|
|
1832
|
-
the actual `akmConsolidate` call instead of recomputing it, so a run still
|
|
1833
|
-
walks `knowledge/` only once.
|
|
1834
|
-
- **`improve`'s start-of-run index rescan ran after triage dirtied the stash,
|
|
1835
|
-
not before it.** Proposal triage promotes accepted proposals straight into
|
|
1836
|
-
the flat `knowledge/` root, and the blocking `ensureIndex` call that is
|
|
1837
|
-
supposed to give the run a current index ran only afterward (inside
|
|
1838
|
-
`collectEligibleRefs`'s setup), so every triage promotion guaranteed the
|
|
1839
|
-
very full rescan it should have preceded — up to ~27 minutes, holding the
|
|
1840
|
-
index lock against co-scheduled writers. `ensureIndex` now runs before the
|
|
1841
|
-
triage pre-pass, and triage's own writes are indexed incrementally
|
|
1842
|
-
(`indexWrittenAssets`) so `collectEligibleRefs` still sees them without a
|
|
1843
|
-
second full walk. Because `indexWrittenAssets` upserts a file's
|
|
1844
|
-
`content_hash` without bumping `builtAt`, index staleness detection
|
|
1845
|
-
(`ensure-index.ts`) is now per-file: a file newer than the last build is
|
|
1846
|
-
only treated as stale when its current content actually differs from what
|
|
1847
|
-
is indexed, so incrementally-reindexed content stops re-triggering the
|
|
1848
|
-
same full rescan on every subsequent run. The implicit reindex's timing
|
|
1849
|
-
breakdown (walk/llm/embed/finalize), previously discarded, is now logged
|
|
1850
|
-
at verbose level and surfaced on the improve result as `ensureIndexDurationMs`.
|
|
1851
|
-
- **Distill quality rejections vanished instead of persisting, so backoff and
|
|
1852
|
-
Reflexion never saw them and the same ref was re-selected and re-rejected
|
|
1853
|
-
on every run** (two refs were rejected 11× and 10×). `writeQualityRejection`
|
|
1854
|
-
wrote only a `$STATE`-side file and an event, never a `proposals` row, so
|
|
1855
|
-
`rejection_backoff`/`fingerprint_match` (proposal/repository.ts) and the
|
|
1856
|
-
Reflexion "previously rejected" context had nothing to find; the distill
|
|
1857
|
-
signal-delta cursor (`buildLatestProposalTsMap`) also only advanced for
|
|
1858
|
-
`queued`/`skipped`/`validation_failed` outcomes, so a rejected ref stayed
|
|
1859
|
-
eligible forever. `writeQualityRejection` now mints a real proposal through
|
|
1860
|
-
the same `createProposal`/`archiveProposal` path every other proposal
|
|
1861
|
-
source uses: a `quality_rejected` outcome is minted pending then archived
|
|
1862
|
-
to `rejected` carrying the judge's reason; a `review_needed` outcome stays
|
|
1863
|
-
`pending` in the normal queue, where triage — a human, or the drain's
|
|
1864
|
-
judgment tier when one is configured — decides, the same path every other
|
|
1865
|
-
pending distill proposal (including quality-gate passes) already takes.
|
|
1866
|
-
The cursor now also advances on both outcomes (still excluding
|
|
1867
|
-
`llm_failed`, where no real attempt produced anything). A retry for the
|
|
1868
|
-
same target, source, and model is skipped by `fingerprint_match` (the
|
|
1869
|
-
input fingerprint recorded at mint, retained `archiveRetentionDays`,
|
|
1870
|
-
default 90 days); the 30-day `rejection_backoff` window only applies once
|
|
1871
|
-
the target's before-hash or the model differs. Because these machine
|
|
1872
|
-
rejections are now real `rejected` rows under `source: "distill"`, `akm
|
|
1873
|
-
health`'s distill accept rate (`computeAcceptRateBySource`,
|
|
1874
|
-
src/commands/health/accept-rate.ts) drops relative to earlier releases and
|
|
1875
|
-
no longer measures reviewer acceptance alone. Nothing gates on that
|
|
1876
|
-
metric.
|
|
1877
|
-
- **`writeQualityRejection` could throw instead of returning a rejection
|
|
1878
|
-
result.** Minting the proposal row above runs the mint-time canonical
|
|
1879
|
-
validator (`createProposal` → `rejectProposal`,
|
|
1880
|
-
`src/commands/proposal/repository.ts`), which throws `UsageError` for
|
|
1881
|
-
structurally-invalid content — e.g. a `lessons/` ref whose body lacks
|
|
1882
|
-
`description`/`when_to_use`. `writeQualityRejection` is the terminal,
|
|
1883
|
-
non-throwing rejection path and none of its callers handled a throw. The
|
|
1884
|
-
proposal row is bookkeeping for backoff/Reflexion, never the authoritative
|
|
1885
|
-
record of the rejection, so a validator throw now degrades to "no row
|
|
1886
|
-
minted" — the envelope file and `distill_invoked` event are still written,
|
|
1887
|
-
matching the existing fingerprint/backoff skip behavior.
|
|
1888
|
-
- **A `review_needed` quality-gate rejection could be auto-promoted by the
|
|
1889
|
-
triage drain's judgment tier with no human ever seeing it.**
|
|
1890
|
-
`writeQualityRejection` minted a `review_needed` outcome as an ordinary
|
|
1891
|
-
pending proposal under `source: "distill"` (knowledge promotions from
|
|
1892
|
-
`promote-memory.ts` take the same path); the `personal-stash` drain policy
|
|
1893
|
-
defers `distill` proposals to the judgment tier, which can auto-accept
|
|
1894
|
-
under `applyMode: promote` + `experimental.improveAutonomy` — so content
|
|
1895
|
-
the quality judge explicitly refused to auto-queue (the 2.5–3.5
|
|
1896
|
-
review-needed band) could be promoted without a human in the loop.
|
|
1897
|
-
`writeQualityRejection` now stamps a `review_needed` mint with a
|
|
1898
|
-
`{ outcome: "deferred", reason: "quality-review", gate: "quality-gate" }`
|
|
1899
|
-
gate decision (best-effort: a stamp failure warns and continues, like the
|
|
1900
|
-
existing mint/archive tolerance), and `classifyPendingProposals`
|
|
1901
|
-
(`proposal/drain.ts`) skips any pending row carrying it — leaving it
|
|
1902
|
-
pending and untouched, before the drain's own policy-deferred re-stamp
|
|
1903
|
-
loop would otherwise overwrite the stamp.
|
|
1904
|
-
- **Consolidate's post-LLM promote-dedup hash double-stripped frontmatter.**
|
|
1905
|
-
`shouldSkipPromotionBodyDuplicate`'s `bodyHash` was computed as
|
|
1906
|
-
`cacheHash(parseFrontmatter(memoryContent).content.trim())` — the body was
|
|
1907
|
-
already frontmatter-stripped before being handed to `cacheHash`, which
|
|
1908
|
-
strips it again internally — diverging from the single-strip
|
|
1909
|
-
`cacheHash(raw)` domain `loadExistingKnowledgeBodyHashes` and the pre-filter
|
|
1910
|
-
use for a source memory body that begins with its own `---` block. The
|
|
1911
|
-
check now hashes `cacheHash(memoryContent)` directly, so the two sides of
|
|
1912
|
-
the dedup comparison agree.
|
|
1913
|
-
- **Consolidate's per-chunk prompt still warned against proposing `delete`
|
|
1914
|
-
for `(captureMode: hot)` memories.** The consolidate op schema and system
|
|
1915
|
-
prompt dropped `delete` (along with `merge`/`contradict`), leaving
|
|
1916
|
-
`buildChunkPrompt`'s top-of-prompt hot-ref block as the only remaining
|
|
1917
|
-
mention of `delete` anywhere in the prompt — a retired op name that
|
|
1918
|
-
`isValidOp` now rejects if the model echoes it back, wasting tokens on
|
|
1919
|
-
"skipping invalid operation" warnings. The block and the `hotRefs`
|
|
1920
|
-
collection that fed it are removed; the inline `(captureMode: hot)`
|
|
1921
|
-
annotation on each memory line is unchanged.
|
|
1922
|
-
- **Graph extraction sent no `json_schema` and no per-asset chunk cap, so a
|
|
1923
|
-
long file could pay for dozens of LLM calls whose output was then sliced
|
|
1924
|
-
down to the same 32-entity/32-relation limit anyway** (one file spent 21 of
|
|
1925
|
-
27 calls and 12.9k completion tokens this way). The single-asset extraction
|
|
1926
|
-
call (`extractGraphFromBody`) now sends a `responseSchema` (entities/
|
|
1927
|
-
relations capped at 32 each, `additionalProperties: false` otherwise), via
|
|
1928
|
-
the same `supportsJsonSchema`-gated request path memory-infer.ts uses — no
|
|
1929
|
-
`maxTokens` is sent; cost is bounded by the schema's `maxItems` caps alone,
|
|
1930
|
-
per AGENTS.md's "LLM Defaults" (a hardcoded cap risked silent truncation
|
|
1931
|
-
with zero headroom for JSON punctuation or reasoning tokens). A body
|
|
1932
|
-
chunked beyond the new
|
|
1933
|
-
`processes.graphExtraction.maxChunksPerAsset` (default 8) now stops after
|
|
1934
|
-
the first N chunks instead of processing every one; the skipped chunks are
|
|
1935
|
-
reported as `truncatedChunks` in the run's graph-extraction telemetry so
|
|
1936
|
-
the coverage loss is visible rather than silently absorbed. The `improve`
|
|
1937
|
-
loop's dispatch (`loop-stages.ts`) now also forwards a configured
|
|
1938
|
-
`maxChunksPerAsset` to the extraction call, mirroring the existing
|
|
1939
|
-
`topN`/`batchSize` wiring — without this the config key had no effect in a
|
|
1940
|
-
real `akm improve` run and the default of 8 always applied.
|
|
1941
|
-
- **The graph-extraction `responseSchema` forbade the `confidence` field the
|
|
1942
|
-
parser itself reads.** `additionalProperties: false` on both the root
|
|
1943
|
-
object and each relation item made `confidence` impossible on a
|
|
1944
|
-
`supportsJsonSchema` provider, even though `parseGraphExtraction` uses
|
|
1945
|
-
`rel.confidence` to drop relations below `MIN_RELATION_CONFIDENCE` and
|
|
1946
|
-
`item.confidence` to feed the merged extraction confidence — silently
|
|
1947
|
-
turning the confidence filter into dead code on exactly the providers the
|
|
1948
|
-
schema targets. `confidence: {"type": "number"}` is now allowed at both
|
|
1949
|
-
levels; `additionalProperties: false` still forbids anything else.
|
|
1950
|
-
- **A pending proposal went stale the moment akm's own bookkeeping touched
|
|
1951
|
-
its target, and promote refused it forever (R20).** `resolveProposalTargetInfo`
|
|
1952
|
-
captured the target's raw `beforeHash` at mint; the SAME nightly run's
|
|
1953
|
-
`writeSalienceToFrontmatter` (distill) and memory inference's
|
|
1954
|
-
`inferenceProcessed` stamp then rewrote the target's frontmatter before
|
|
1955
|
-
promote ran, so `promoteProposalWithLease`'s guard (`repository.ts` ~L2406)
|
|
1956
|
-
and `drain.ts`'s dry-run mirror (`assertProposalTargetFresh`) refused every
|
|
1957
|
-
affected proposal with "target changed after proposal was created" — the
|
|
1958
|
-
same 11+ reflect proposals, every day, on splinter. `resolveProposalTargetInfo`
|
|
1959
|
-
now also captures `beforeHashNormalized` (`core/asset/frontmatter.ts`'s new
|
|
1960
|
-
`computeNormalizedContentHash`, over the target with
|
|
1961
|
-
`BOOKKEEPING_FRONTMATTER_KEYS` — `salience`/`salienceInputs`/`inferenceProcessed`
|
|
1962
|
-
— stripped and the remaining frontmatter canonically re-serialized); the
|
|
1963
|
-
promote guard and its dry-run mirror both prefer it over the raw
|
|
1964
|
-
`beforeHash` when present, so a bookkeeping-only rewrite no longer stales a
|
|
1965
|
-
proposal out while a real content change still refuses. Promotion also now
|
|
1966
|
-
carries the live target's bookkeeping keys forward
|
|
1967
|
-
(`carryForwardBookkeepingFrontmatter`) when the proposal's own frontmatter
|
|
1968
|
-
doesn't set them, so accepting never drops `inferenceProcessed` and forces
|
|
1969
|
-
memory inference to reprocess the memory. A legacy proposal minted before
|
|
1970
|
-
this field existed keeps its exact original raw-hash check.
|
|
1971
|
-
`computeNormalizedContentHash` also normalizes the body boundary the same
|
|
1972
|
-
way `assembleAssetFromString` does (leading newlines stripped, exactly one
|
|
1973
|
-
trailing newline) before hashing, and treats an empty frontmatter block as
|
|
1974
|
-
`{}` instead of falling back to the raw hash — both `writeSalienceToFrontmatter`
|
|
1975
|
-
and the memory-inference `assembleAsset` rewrite shift where the body starts,
|
|
1976
|
-
which without this normalization still staled a proposal out unless the
|
|
1977
|
-
target's frontmatter was already in that exact on-disk shape.
|
|
1978
|
-
- **A stale-target promote failure was retried, and refused, identically
|
|
1979
|
-
every drain run forever (R20).** The drain already categorized a
|
|
1980
|
-
"target changed/was created after proposal" failure as `stale-target`
|
|
1981
|
-
(`categorizeDrainFailure`), but left the row pending either way — so the
|
|
1982
|
-
same proposals failed the same way on every subsequent `akm proposal
|
|
1983
|
-
drain` / triage pass. Both promote-failure sites (`drainProposals`'s
|
|
1984
|
-
deterministic loop and `runJudgmentTier`) now auto-reject a stale-target
|
|
1985
|
-
failure once, stamping `gateDecision: { outcome: "auto-rejected", reason:
|
|
1986
|
-
"stale-target" }` instead of leaving it to retry. This is not a merit
|
|
1987
|
-
rejection, so `checkFingerprintAndBackoff`'s rejection-backoff window
|
|
1988
|
-
(`repository.ts`) now excludes stale-target rows — the ref stays
|
|
1989
|
-
re-proposable against its current content — and the Reflexion
|
|
1990
|
-
"previously rejected" context (`reflect.ts`'s `readRejectedProposals`,
|
|
1991
|
-
`distill.ts`'s `buildDistillMessages`) and the accept-rate health metric
|
|
1992
|
-
(`health/accept-rate.ts`) now exclude stale-target rejections too, so a
|
|
1993
|
-
procedural refusal doesn't misrepresent content quality. `--dry-run` now
|
|
1994
|
-
predicts the same outcome: a stale-target promote failure it detects is
|
|
1995
|
-
reported under `rejected`, matching what a real run does, instead of under
|
|
1996
|
-
`failed`.
|
|
1997
|
-
|
|
1998
|
-
- **Reflect quality-gate rejections were mislabelled `parse_error` and fed
|
|
1999
|
-
back into later prompts as learned "avoid" patterns.** When the reflect
|
|
2000
|
-
quality judge rejected an otherwise well-parsed proposal, the result
|
|
2001
|
-
carried `reason: "parse_error"` — a real parse failure and a judge
|
|
2002
|
-
rejection were indistinguishable. The improve loop injects non-excluded
|
|
2003
|
-
reflect failures into the next reflect prompt's "Avoid These Patterns"
|
|
2004
|
-
block, so a single gate rejection could poison every subsequent candidate
|
|
2005
|
-
in the run. Judge rejections now carry a distinct `quality_rejected`
|
|
2006
|
-
reason, stay in the `reflect-failed` metrics bucket, and are excluded from
|
|
2007
|
-
that avoid-patterns injection like the existing deterministic skips.
|
|
2008
|
-
- **Legacy rejected proposals no longer throw before reflect/distill prompt
|
|
2009
|
-
dispatch.** `readRejectedProposals` (reflect.ts) and the equivalent mapper
|
|
2010
|
-
in distill.ts built their "previously rejected" context via
|
|
2011
|
-
`proposalContent(p)`, which throws when a proposal's `changes[0]?.after` is
|
|
2012
|
-
undefined — the shape `storedToChanges` deliberately returns for rows
|
|
2013
|
-
archived before the `changes` field existed (the large majority of
|
|
2014
|
-
real-world rejected-proposal history). The throw happened before the
|
|
2015
|
-
signal cursor advanced, so a ref with any such legacy rejection errored on
|
|
2016
|
-
every run instead of ever completing. Both call sites now read the preview
|
|
2017
|
-
from `payload.content`, which is populated for every row regardless of
|
|
2018
|
-
its `changes` shape.
|
|
2019
|
-
- **Failed graph extractions are no longer cached as permanent hits.** A
|
|
2020
|
-
provider outage upserted thousands of `{"entities":[],"status":"failed"}`
|
|
2021
|
-
results into `llm_enrichment_cache` and the persisted graph, and both
|
|
2022
|
-
cache-hit paths (the DB lookup and reuse from the previous graph) treated
|
|
2023
|
-
them as valid hits forever after — the affected files never retried.
|
|
2024
|
-
`status: "failed"` results are now treated as a miss and are never written
|
|
2025
|
-
to the cache; existing rows are left on disk and are overwritten naturally
|
|
2026
|
-
on the next successful extraction. `src/llm/graph-extract.ts` also no
|
|
2027
|
-
longer falls back to a per-asset retry for every body in a batch after a
|
|
2028
|
-
`provider_error` — the provider has already demonstrated it is failing, so
|
|
2029
|
-
each asset in that batch is recorded as failed directly. Graph extraction
|
|
2030
|
-
now aborts the rest of the run (returning the partial results already
|
|
2031
|
-
extracted) once the failure rate crosses 50% over at least 4 attempted
|
|
2032
|
-
extraction dispatches, mirroring consolidate's existing failure-rate guard.
|
|
2033
|
-
The abort counts one attempt per `extractGraphFromBodies` dispatch, not per
|
|
2034
|
-
file inside its batch — per-file counting let a single batched
|
|
2035
|
-
`provider_error` trip the guard after one HTTP failure whenever
|
|
2036
|
-
`graphExtractionBatchSize` was at its default of 4.
|
|
2037
|
-
- **Memory consolidation's cooldown could never engage.** `consolidate_completed`
|
|
2038
|
-
was only emitted when a run planned zero merge/delete/contradict operations —
|
|
2039
|
-
advisory ops the model plans daily and that are never auto-applied — so the
|
|
2040
|
-
event had, in practice, never fired and the pool-delta gate stayed
|
|
2041
|
-
permanently in its bootstrap "run every time" state. The event now fires
|
|
2042
|
-
whenever the LLM pass itself completes, recording the unapplied advisory op
|
|
2043
|
-
count (`advisoryOpsUnapplied`) instead of withholding the event. Separately,
|
|
2044
|
-
the memory-volume override (`memoryVolumeConsolidationThreshold`, forcing a
|
|
2045
|
-
run when the eligible pool exceeds the threshold) is now bootstrap-only: once
|
|
2046
|
-
a `consolidate_completed` event exists for the source, the pool-delta gate
|
|
2047
|
-
governs on its own, even when the pool is large. `akm improve --plan`'s
|
|
2048
|
-
`consolidation.gates.delta.reason` no longer reports "memory pool has work"
|
|
2049
|
-
for both a real pool delta and the bootstrap case (no `consolidate_completed`
|
|
2050
|
-
event yet, so no delta was evaluated) — bootstrap now reports its own reason.
|
|
297
|
+
- **`akm info` now always reports and exits 0, like a help command.** An
|
|
298
|
+
invalid `config.json`, an unreadable or missing bundle directory, and a
|
|
299
|
+
locked, newer, or corrupt `index.db` are each named in the report instead
|
|
300
|
+
of failing the command or showing unexplained zeros.
|
|
301
|
+
- **`akm health` no longer silently drops every improve run recorded before
|
|
302
|
+
#947** (2026-09-09): a `plan` object with no `processes` key failed to
|
|
303
|
+
decode entirely, excluding those runs from `--window-compare`, `--group-by
|
|
304
|
+
run`, and the HTML/MD reports. On a real 30-day window this had dropped 11
|
|
305
|
+
of 55 runs.
|
|
306
|
+
- **`akm show` works again for a memory with a `.derived.md` child** (835 of
|
|
307
|
+
them on one real bundle) — broken since 0.9.7.
|
|
308
|
+
- **`akm bundle add --name` is a contract on every add path, not a hint.**
|
|
309
|
+
An explicit `--name` that is not a legal bundle slug, or is already taken,
|
|
310
|
+
now fails before any write instead of silently falling back to a derived
|
|
311
|
+
name (`akm bundle add --provider ... --name` had its own gap in this same
|
|
312
|
+
check, now closed). A registry add with no `--name` is keyed by its
|
|
313
|
+
package or repo name instead of the fixed name `extracted` every registry
|
|
314
|
+
bundle after the first used to collide on.
|
|
315
|
+
- **`akm improve --require-engines` no longer aborts a scheduled run because
|
|
316
|
+
a local LLM endpoint was merely busy.** Its reachability probe now waits up
|
|
317
|
+
to the engine's own timeout (at most two minutes) instead of a flat 3
|
|
318
|
+
seconds.
|
|
319
|
+
- **A scheduled row's value survives a `$` or a backslash**, and a cron
|
|
320
|
+
command too long for one line is recognized by `akm task sync` again
|
|
321
|
+
instead of being rewritten on every sync.
|
|
2051
322
|
|
|
2052
323
|
## [0.9.16] - 2026-09-22
|
|
2053
324
|
|