akm-cli 0.9.17-alpha.5 → 0.9.17-alpha.7
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 +281 -0
- package/STABILITY.md +2 -2
- package/dist/akm +7 -7
- package/dist/assets/prompts/retrieval-relevance-judge.md +6 -0
- package/dist/commands/improve/consolidate.js +11 -0
- package/dist/commands/improve/execution.js +5 -5
- package/dist/commands/improve/improve-cli.js +27 -7
- package/dist/commands/improve/improve-strategies.js +3 -0
- package/dist/commands/improve/ledger.js +7 -3
- package/dist/commands/improve/loop-stages.js +5 -4
- package/dist/commands/improve/preparation.js +40 -10
- package/dist/commands/improve/reflect.js +46 -22
- package/dist/commands/improve/retrieval-gate.js +127 -0
- package/dist/commands/improve/retrieval-scope.js +77 -0
- package/dist/commands/read/curate.js +15 -49
- package/dist/commands/read/show.js +2 -81
- package/dist/commands/tasks/tasks-cli.js +10 -12
- package/dist/commands/tasks/tasks.js +57 -56
- package/dist/commands/tasks/validate.js +27 -46
- package/dist/core/adapter/adapters/akm-task-adapter.js +29 -8
- package/dist/core/config/config.js +1 -1
- package/dist/core/config/schema/improve-processes.js +4 -3
- package/dist/core/config/schema/index-config.js +4 -23
- package/dist/core/improve-result.js +4 -1
- package/dist/core/non-task-input.js +20 -0
- package/dist/core/paths.js +0 -4
- package/dist/indexer/db/graph-db.js +8 -81
- package/dist/indexer/graph/graph-extraction.js +74 -227
- package/dist/indexer/graph/graph-related.js +5 -4
- package/dist/indexer/indexer.js +1 -3
- package/dist/indexer/search/db-search.js +10 -1
- package/dist/indexer/usage/usage-events.js +34 -0
- package/dist/llm/feature-gate.js +0 -3
- package/dist/llm/graph-extract.js +81 -41
- package/dist/scripts/akm-migrate-node.js +7148 -7174
- package/dist/scripts/akm-migrate.js +8718 -8744
- package/dist/storage/repositories/index-entries-repository.js +5 -7
- package/dist/storage/repositories/index-schema.js +16 -34
- package/dist/storage/repositories/proposals-repository.js +4 -0
- package/dist/tasks/backends/cron.js +80 -43
- package/dist/tasks/backends/launchd.js +28 -15
- package/dist/tasks/backends/schtasks.js +25 -10
- package/dist/tasks/run/load-task.js +1 -1
- package/dist/tasks/scheduler-binding.js +4 -2
- package/dist/tasks/scheduler-invocation.js +127 -235
- package/dist/tasks/scheduler-sync.js +13 -8
- package/dist/tasks/source/parse-task-source.js +22 -126
- package/dist/tasks/source/task-to-v3.js +1 -55
- package/dist/tasks/source/task-to-v4.js +1 -13
- package/docs/migration/v0.9.1-to-v0.9.2.md +7 -3
- package/docs/reference/cli.md +13 -4
- package/docs/reference/configuration.md +12 -0
- package/docs/reference/tasks.md +58 -40
- package/package.json +1 -1
- package/schemas/akm-config.json +0 -6
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,287 @@ 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.17-alpha.7] - 2026-09-28
|
|
10
|
+
|
|
11
|
+
A scheduled task is now just a command and a schedule. Each native row
|
|
12
|
+
carries its own `AKM_BUNDLE_DIR` instead of pointing at a descriptor file,
|
|
13
|
+
and the runtime reads only v4 task files; older files convert once with
|
|
14
|
+
`akm migrate apply`. The first `akm task sync` after upgrading rewrites each
|
|
15
|
+
row once, keeping every task and schedule. `akm improve` reworks only assets
|
|
16
|
+
that retrieval returned or that are new, and reflect refuses a rewrite that
|
|
17
|
+
grades worse on the asset's own searches. `--require-engines` no longer
|
|
18
|
+
skips a run because the LLM endpoint is busy.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **akm reads only task source v4.** A `version: 2` or `version: 3` task
|
|
23
|
+
file, or a `version: 4` file that still carries 0.9.15's retired
|
|
24
|
+
`schedule[].enabled`, now fails on its own with a message naming
|
|
25
|
+
`akm migrate apply`, which converts it once, under a backup (`akm upgrade`
|
|
26
|
+
runs it after an install). Until now every read converted such a file in
|
|
27
|
+
memory. `akm task sync` reports each one as a failure, leaves its installed
|
|
28
|
+
row as it is, and keeps reconciling every other task; `akm task run`,
|
|
29
|
+
`akm lint` and `akm task validate` report it the same way, and
|
|
30
|
+
`akm task validate`'s `converts` outcome is gone (such a file is
|
|
31
|
+
`blocked`). `akm migrate apply` now also converts the tasks of the stash
|
|
32
|
+
`AKM_BUNDLE_DIR` selects when no configured bundle names it, since the
|
|
33
|
+
runtime reads those too, and a root whose top-level task files are all
|
|
34
|
+
v2/v3 is still detected as an `akm-task` bundle, so they are found and
|
|
35
|
+
converted. A host whose task files are all v4 (`akm migrate status`
|
|
36
|
+
reports `current`) sees no difference.
|
|
37
|
+
(`src/tasks/source/parse-task-source.ts`, `src/commands/tasks/validate.ts`,
|
|
38
|
+
`scripts/akm-migrate/task-migrate.ts`,
|
|
39
|
+
`src/core/adapter/adapters/akm-task-adapter.ts`)
|
|
40
|
+
- **A scheduled row is its command plus its schedule, and carries its own
|
|
41
|
+
context.** Rows no longer name a `--scheduler-context` descriptor file;
|
|
42
|
+
they set what it held themselves. Every row sets `AKM_BUNDLE_DIR` to the
|
|
43
|
+
working stash of the shell that ran `akm task sync`, plus any
|
|
44
|
+
`AKM_CONFIG_DIR`, `AKM_DATA_DIR`, `AKM_CACHE_DIR` or `AKM_STATE_DIR` that
|
|
45
|
+
shell set explicitly: a `VAR=value` prefix in the crontab, an
|
|
46
|
+
`EnvironmentVariables` entry in a launchd plist, a `$env:VAR='value';`
|
|
47
|
+
assignment ahead of the command in Task Scheduler (its action has no
|
|
48
|
+
environment of its own). Scheduled runs see the same environment as
|
|
49
|
+
before, and sync still tells installations sharing a crontab apart by
|
|
50
|
+
that path (#846).
|
|
51
|
+
**What hosts see:** the first `akm task sync` after upgrading rewrites
|
|
52
|
+
every akm row once. `akm task sync --dry-run` lists each one as an update,
|
|
53
|
+
never an add or a remove; each keeps its launcher and its schedule, and
|
|
54
|
+
the `--scheduler-context <file>` argument becomes an inline
|
|
55
|
+
`AKM_BUNDLE_DIR=<working stash>`. On a host whose working stash is
|
|
56
|
+
`/home/u/akm` a row changes from
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
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
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
to
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
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
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Rows written by 0.9.0 through 0.9.17-alpha.6 keep firing until that sync:
|
|
69
|
+
the CLI still accepts `--scheduler-context <file>` and applies the file's
|
|
70
|
+
environment (PATH included, for a 0.9.16 row). The files under
|
|
71
|
+
`$DATA/tasks/context/` are no longer written, and the uid, mode, symlink
|
|
72
|
+
and content-hash checks made on every scheduled run are gone. A sync
|
|
73
|
+
leaves some rows as they are (one whose task file failed to load, one a
|
|
74
|
+
`--bundle` sync did not cover), and those still name their file: once
|
|
75
|
+
`akm task doctor` lists no binding with a `contextPath`, nothing reads
|
|
76
|
+
them and they can be deleted. `akm task prune` now
|
|
77
|
+
finds rows whose `AKM_BUNDLE_DIR` names a directory that is gone, and older
|
|
78
|
+
rows whose descriptor cannot be read; `akm task doctor` lists `contextPath`
|
|
79
|
+
only for an older row. (`src/tasks/scheduler-invocation.ts`,
|
|
80
|
+
`src/tasks/backends/cron.ts`, `src/tasks/backends/launchd.ts`,
|
|
81
|
+
`src/tasks/backends/schtasks.ts`, `src/tasks/scheduler-sync.ts`,
|
|
82
|
+
`src/commands/tasks/tasks.ts`)
|
|
83
|
+
- **`akm improve` reworks only what gets read (#986).** An asset with fresh
|
|
84
|
+
feedback, or one you name (`akm improve skills/x`), is handled as before.
|
|
85
|
+
Every other pick must now be in the retrieval scope. That covers the
|
|
86
|
+
proactive-maintenance, high-salience and forgetting-safety lanes, and the
|
|
87
|
+
memories consolidation judges. An asset is in scope if a user `search`,
|
|
88
|
+
`curate` or `show` returned it, or user `feedback` named it, in the last 90
|
|
89
|
+
days, which is the usage log's retention. A hit on a `.derived` memory counts
|
|
90
|
+
for its parent. New material that no improve stage has processed yet is also
|
|
91
|
+
in scope. There is no new config key.
|
|
92
|
+
|
|
93
|
+
Measured with `akm improve --dry-run` on a copy of the maintainer's bundle
|
|
94
|
+
(19,870 assets), against 0.9.17-alpha.6:
|
|
95
|
+
- The fallback lanes pick from 6,450 assets instead of 15,686, and 9,236
|
|
96
|
+
refs are left out. No lane setting can reach the unread tail any more. In
|
|
97
|
+
July the proactive lane rewrote 3,069 assets, and 3,059 of them had not
|
|
98
|
+
been retrieved since the usage log began on 1 July.
|
|
99
|
+
- Consolidation judges 59 memories instead of 69.
|
|
100
|
+
- The high-salience lane no longer admits distill outputs nobody has read (2
|
|
101
|
+
today).
|
|
102
|
+
- Under the scheduled caps, today's nightly work is unchanged. The default
|
|
103
|
+
strategy selects the same 50 feedback-driven refs, and weekly proactive
|
|
104
|
+
maintenance selects the same 25, because salience ranking already puts
|
|
105
|
+
retrieved assets first.
|
|
106
|
+
|
|
107
|
+
`akm improve --dry-run` and the run result report the left-out refs as a new
|
|
108
|
+
`retrieval` gate. Health reports them under the skip reason `not_retrieved`.
|
|
109
|
+
Improve results stored by earlier releases, which have no such gate, still
|
|
110
|
+
decode. (`src/commands/improve/retrieval-scope.ts`,
|
|
111
|
+
`src/commands/improve/preparation.ts`, `src/commands/improve/consolidate.ts`)
|
|
112
|
+
|
|
113
|
+
- **Reflect refuses a rewrite that makes an asset worse for its own searches
|
|
114
|
+
(#722).** Before reflect proposes a rewrite of an existing asset, it grades
|
|
115
|
+
the old and the new content on up to five of the queries that actually
|
|
116
|
+
retrieved the asset (user `search` and `curate`). It uses the retrieval
|
|
117
|
+
eval's relevance prompt, which agrees with human grades at kappa 0.83. When
|
|
118
|
+
the new content grades lower on average, the rewrite is refused the same way
|
|
119
|
+
a quality-judge rejection is: `quality_rejected`, with the 14-day reflect
|
|
120
|
+
window. An asset without retrieval queries is not graded.
|
|
121
|
+
|
|
122
|
+
This was measured before it was built. Of 60 accepted rewrites since July,
|
|
123
|
+
judged this way, 14 graded lower (23%, 95% CI 14–35%) and 12 graded higher.
|
|
124
|
+
The gate was built because the lower bound cleared the 10% threshold set
|
|
125
|
+
before any judging. It costs two judge calls per query on the engine that
|
|
126
|
+
already runs the quality judge. On the maintainer's 2026-09-28 nightly run,
|
|
127
|
+
whose 30 rewrites had 102 usable queries, that is 204 calls, about 6 more
|
|
128
|
+
minutes on a 73-minute run. (`src/commands/improve/retrieval-gate.ts`,
|
|
129
|
+
`src/commands/improve/reflect.ts`)
|
|
130
|
+
|
|
131
|
+
### Fixed
|
|
132
|
+
|
|
133
|
+
- **A cron row too long for one line is seen by `akm task sync` again.** A
|
|
134
|
+
command over 1,000 bytes runs from a wrapper script, and sync could not
|
|
135
|
+
read which task such a row ran: every sync, and every `--dry-run`, showed
|
|
136
|
+
it as an add and wrote it again. Sync now reads the script, so the row is
|
|
137
|
+
unchanged or an update like any other. (`src/tasks/backends/cron.ts`)
|
|
138
|
+
- **A `$` or a backslash in a scheduled row's value is kept.** launchd and
|
|
139
|
+
Task Scheduler rows passed their values through a string replacement that
|
|
140
|
+
read `$'`, `$&` and `$$` as patterns, so a path such as a Windows admin
|
|
141
|
+
share (`\\nas\share\akm$`) came out corrupted; reading a crontab row
|
|
142
|
+
back dropped a backslash inside a single-quoted value.
|
|
143
|
+
(`src/tasks/backends/launchd.ts`, `src/tasks/backends/schtasks.ts`,
|
|
144
|
+
`src/tasks/backends/cron.ts`)
|
|
145
|
+
- **`akm improve --require-engines` no longer skips a run because the LLM
|
|
146
|
+
endpoint is busy.** Its reachability probe, one short completion, gave up
|
|
147
|
+
after 3 seconds, so a local server busy with another job looked
|
|
148
|
+
unreachable and the whole scheduled run failed (all four scheduled runs on
|
|
149
|
+
2026-09-27). The probe now waits up to the engine's own `timeoutMs`, at
|
|
150
|
+
most two minutes, so a busy server can answer while a hung one still fails
|
|
151
|
+
fast. The error's hint now says to check the endpoint rather than to run
|
|
152
|
+
`akm setup`.
|
|
153
|
+
(`src/commands/improve/improve-cli.ts`)
|
|
154
|
+
|
|
155
|
+
## [0.9.17-alpha.6] - 2026-09-27
|
|
156
|
+
|
|
157
|
+
Graph extraction stops losing and wasting work. A timed-out extraction is
|
|
158
|
+
retried instead of cached as empty. Long documents are extracted once, and
|
|
159
|
+
per-file calls respect the run's concurrency. `akm improve` honors
|
|
160
|
+
`index.graph`. `akm curate` returns nothing for harness and tool envelopes,
|
|
161
|
+
and search and curate show identical content once. Lazy graph extraction,
|
|
162
|
+
which never ran under Bun, is removed.
|
|
163
|
+
|
|
164
|
+
### Changed
|
|
165
|
+
|
|
166
|
+
- **`akm curate` returns nothing, on purpose, for input that is not a task.**
|
|
167
|
+
A harness or tool envelope (input that starts with an XML-style tag and
|
|
168
|
+
contains a closing tag, such as `<task-notification>…</task-notification>`,
|
|
169
|
+
`<system-reminder>…` or `<cross-session-message …>…`) and the stash README
|
|
170
|
+
line each used to get `--limit` unrelated assets. Every caller of
|
|
171
|
+
`akm curate` (the CLI, the OpenCode plugin, other harnesses) now gets an
|
|
172
|
+
empty `items` list with a `summary` that starts with `Curate abstained` and
|
|
173
|
+
names the reason, and a `tip`. On the retrieval suite curate abstains on 57
|
|
174
|
+
of 60 recorded non-task inputs and on none of the 221 real queries (nor on
|
|
175
|
+
any of 5,725 mined task queries). Length is not a reason to abstain: the
|
|
176
|
+
other 3 are task prompts of 2,431–5,531 characters, and in a judged sample
|
|
177
|
+
of 30 inputs over 2,000 characters the top 5 held a relevant asset for 24
|
|
178
|
+
of them (P@5 0.42, against 0.46 for prompts of 400–2,000 characters).
|
|
179
|
+
(`src/commands/read/curate.ts`)
|
|
180
|
+
- **Search and curate return identical content once.** Of entries whose
|
|
181
|
+
indexed content is identical (the same body saved under another name, as
|
|
182
|
+
both a memory and a knowledge doc, or in another bundle), only the
|
|
183
|
+
highest-ranked is kept, and the next candidate takes the freed slot. On the
|
|
184
|
+
retrieval suite such copies filled 7.5% of curate's top 5. Unique
|
|
185
|
+
precision@5, where a copy of a higher-ranked result earns nothing, rises
|
|
186
|
+
from 0.467 to 0.514 (+0.046, 95% CI [+0.028, +0.067]), and the share of
|
|
187
|
+
top-5 slots that repeat a higher-ranked result falls from 0.131 to 0.055.
|
|
188
|
+
Plain P@5 (0.553 → 0.551) and nDCG@10 stay within noise: they counted each
|
|
189
|
+
copy as another relevant result. Latency is unchanged.
|
|
190
|
+
(`src/indexer/search/db-search.ts`)
|
|
191
|
+
|
|
192
|
+
### Removed
|
|
193
|
+
|
|
194
|
+
- **The unused `utility_scores_scoped` index table is gone.** It shipped in
|
|
195
|
+
0.9.17-alpha.5 for per-project scoped utility scores, but no code ever read
|
|
196
|
+
or wrote a row. An index database drops it on its next writable open, the
|
|
197
|
+
same way other retired derived tables are dropped, with no layout-version
|
|
198
|
+
change. (`src/storage/repositories/index-schema.ts`)
|
|
199
|
+
- **Lazy graph extraction in `akm show` and `akm curate`.** With
|
|
200
|
+
`index.graph.lazyGraphExtraction: true`, `show` extracted an asset's graph
|
|
201
|
+
after building its response, so only the next `show` saw it. `curate`
|
|
202
|
+
queued assets for a later pass, which drained only the working bundle's
|
|
203
|
+
queue, and extractions made this way wrote no cache entry. Under Bun neither
|
|
204
|
+
path ever ran: the "already has a graph" check read a missing row as
|
|
205
|
+
present. Graph extraction now runs only in `akm improve`. The
|
|
206
|
+
`graph_extraction_queue` table is dropped the next time the index is opened
|
|
207
|
+
for writing. A config that still sets the key loads, and the key is named
|
|
208
|
+
once as unknown. (`src/commands/read/show.ts`,
|
|
209
|
+
`src/commands/read/curate.ts`, `src/indexer/graph/graph-extraction.ts`,
|
|
210
|
+
`src/storage/repositories/index-schema.ts`)
|
|
211
|
+
|
|
212
|
+
### Fixed
|
|
213
|
+
|
|
214
|
+
- **`index.metadataEnhance`'s default is no longer contradicted by dead
|
|
215
|
+
code.** Metadata enhancement has always defaulted to off
|
|
216
|
+
(`isLlmFeatureEnabled`); a second, unreachable code path in
|
|
217
|
+
`isProcessEnabled` claimed the opposite default and had no caller. Removed,
|
|
218
|
+
so one default remains. (`src/llm/feature-gate.ts`)
|
|
219
|
+
- **Eval tooling and docs catch up to the current config and index shape.**
|
|
220
|
+
`scripts/akm-eval/src/curate-bench.ts` wrote the retired `sources` config
|
|
221
|
+
key and called a nonexistent `akm index --dir`; it now seeds its sandbox
|
|
222
|
+
the same way the other akm-eval scripts and integration tests do, and
|
|
223
|
+
drops `--dir`. The graph A/B ablation harness
|
|
224
|
+
(`scripts/akm-eval/src/graph-ablation.ts`) planted its "graph off" config
|
|
225
|
+
where the sandboxed `akm` never read it, with config keys that didn't gate
|
|
226
|
+
anything (one of them a type error); it now writes
|
|
227
|
+
`index.graph.enabled: false` to the sandbox's actual `AKM_CONFIG_DIR`.
|
|
228
|
+
Updated `scripts/akm-eval/README.md` and `docs/maintainers/eval.md` to
|
|
229
|
+
match, and corrected stale `docs/architecture/architecture.md` references
|
|
230
|
+
to `db-backup`, `staleness-detect`, and `src/commands/graph/`.
|
|
231
|
+
- **Scheduled graph extraction reads `index.graph`.** `akm improve` passed
|
|
232
|
+
graph extraction a batch size of 4 and the `memory` and `knowledge` types
|
|
233
|
+
whenever the strategy's `processes.graphExtraction` did not set them, so
|
|
234
|
+
`index.graph.graphExtractionBatchSize` and `graphExtractionIncludeTypes`
|
|
235
|
+
never applied. It did not read `index.graph`'s `engine`, `model`,
|
|
236
|
+
`timeoutMs` or `llm` either, so a setting such as
|
|
237
|
+
`index.graph.llm.enableThinking: false` had no effect on improve runs. A
|
|
238
|
+
value in the strategy's `processes.graphExtraction` still wins. A setting it
|
|
239
|
+
leaves unset now comes from `index.graph`, then from the built-in default.
|
|
240
|
+
Where `index.graph` asks for something improve did not use before, the
|
|
241
|
+
extractor changes and cached extractions stop applying, so those files are
|
|
242
|
+
extracted again. (`src/commands/improve/loop-stages.ts`,
|
|
243
|
+
`src/commands/improve/execution.ts`,
|
|
244
|
+
`src/commands/improve/improve-strategies.ts`)
|
|
245
|
+
- **A graph extraction that times out is retried, not cached as empty.** A
|
|
246
|
+
call that ran past the engine's `timeoutMs` was recorded as "no entities"
|
|
247
|
+
and cached, so the file was never extracted again. It is now recorded as
|
|
248
|
+
failed, and the next run retries it; timeouts also count toward the run's
|
|
249
|
+
failure-rate abort. A batch that times out fails its files without then
|
|
250
|
+
calling the model once per file. An empty response is likewise recorded as
|
|
251
|
+
failed. (`src/llm/graph-extract.ts`)
|
|
252
|
+
- **Long bodies are extracted once after batching turns itself off.** Two
|
|
253
|
+
non-array batch responses turn batching off for the rest of a run. From
|
|
254
|
+
then on, a body over 1,600 characters was extracted on its own and then
|
|
255
|
+
again with the rest of its batch. Each body is now extracted once.
|
|
256
|
+
(`src/llm/graph-extract.ts`)
|
|
257
|
+
- **A batch's per-file calls respect the run's concurrency.** When a batch
|
|
258
|
+
fell back to one call per file (long bodies, a non-array response, batching
|
|
259
|
+
turned off), those calls all went out at once, up to the batch size. Local
|
|
260
|
+
endpoints serve one or two requests at a time. The calls now run within the
|
|
261
|
+
limit the run applies to its batches, one at a time by default.
|
|
262
|
+
(`src/llm/graph-extract.ts`)
|
|
263
|
+
- **`related` counts a shared entity once.** `akm show`'s `related` list,
|
|
264
|
+
and curate's support refs taken from it, ranked files by the number of
|
|
265
|
+
matching entity rows. A file holding two case forms of one entity, as rows
|
|
266
|
+
from older extractors can, counted it twice and could outrank a file that
|
|
267
|
+
shared two entities. `related` now counts distinct entities. Extraction also
|
|
268
|
+
keeps one form of each entity before writing. The stored key `related`
|
|
269
|
+
matches on is now the one extraction deduplicates on, which also drops
|
|
270
|
+
surrounding quotes and backticks.
|
|
271
|
+
(`src/indexer/graph/graph-related.ts`,
|
|
272
|
+
`src/indexer/graph/graph-extraction.ts`, `src/indexer/db/graph-db.ts`)
|
|
273
|
+
- **A config change that re-extracts the graph says so.** Cached graph
|
|
274
|
+
extractions are keyed by extractor: model, batch size, included asset types
|
|
275
|
+
and prompt version. Changing any of them made every cached file extract
|
|
276
|
+
again without a word. The first run after such a change now warns once,
|
|
277
|
+
naming the change and the number of cached files it will extract again, and
|
|
278
|
+
records the warning in the run's result.
|
|
279
|
+
(`src/indexer/graph/graph-extraction.ts`)
|
|
280
|
+
- **Graph extraction reports what its parser filtered.** A run's graph
|
|
281
|
+
telemetry, part of `akm improve`'s result, now carries
|
|
282
|
+
`filteredGenericEntities`, `filteredInvalidRelations`,
|
|
283
|
+
`filteredLowConfidenceRelations` and `contextBatchRetries`. The pass
|
|
284
|
+
computed them and dropped them, and did not count batch responses at all.
|
|
285
|
+
(`src/indexer/graph/graph-extraction.ts`, `src/llm/graph-extract.ts`)
|
|
286
|
+
- **An unknown key under `index.<pass>` is kept and named once.** It was
|
|
287
|
+
dropped from the loaded config and named twice. It is now handled like an
|
|
288
|
+
unknown key anywhere else in config. (`src/core/config/schema/index-config.ts`)
|
|
289
|
+
|
|
9
290
|
## [0.9.17-alpha.5] - 2026-09-27
|
|
10
291
|
|
|
11
292
|
`akm show` works again for a memory that has a `.derived.md` child (835 of them
|
package/STABILITY.md
CHANGED
|
@@ -281,8 +281,8 @@ CHANGELOG with a migration note.
|
|
|
281
281
|
print unredacted. `akm task validate <path>` (new in 0.9.11) is the same
|
|
282
282
|
kind of zero-write introspection as `explain`, but takes a bare filesystem
|
|
283
283
|
path rather than a bundle-qualified ref — it reports whether that ONE file
|
|
284
|
-
would parse cleanly (`valid`),
|
|
285
|
-
|
|
284
|
+
would parse cleanly (`valid`), need `akm migrate apply` first (`blocked`:
|
|
285
|
+
task v2/v3, or a retired `schedule[].enabled`),
|
|
286
286
|
fail schema validation (`invalid`), or isn't a task source at all
|
|
287
287
|
(`not-a-task`) — exactly the diagnostic `akm task sync` would produce for
|
|
288
288
|
it, before the file is ever wired into a bundle or the scheduler.
|
package/dist/akm
CHANGED
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
import { spawn, spawnSync } from "node:child_process";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
9
|
+
// A scheduled row sets its environment itself (a cron `VAR=value` prefix, a
|
|
10
|
+
// launchd plist's EnvironmentVariables), and the scheduler supplies PATH, so
|
|
11
|
+
// runtime selection below needs nothing from it. A row written by 0.9.0 –
|
|
12
|
+
// 0.9.17-alpha.6 passes `--scheduler-context <descriptor>` instead; it passes
|
|
13
|
+
// through untouched, and the CLI applies it (`consumeSchedulerContextArg` in
|
|
14
|
+
// src/cli.ts). This launcher used to re-validate the descriptor with its own
|
|
15
|
+
// copy of the schema, and that copy rejected every descriptor 0.9.17 wrote.
|
|
16
16
|
|
|
17
17
|
process.env.AKM_LAUNCHER_NODE = process.execPath;
|
|
18
18
|
process.env.AKM_LAUNCHER_PATH = fileURLToPath(import.meta.url);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
You are grading search-and-retrieval results for an AI coding agent's knowledge base (the akm tool). For the given query and ONE candidate asset, grade how useful loading this asset would be, on this scale:
|
|
2
|
+
3 = exactly the asset an agent should load for this query or task; it directly answers or performs the request.
|
|
3
|
+
2 = relevant and clearly useful, though not the single best asset for the query.
|
|
4
|
+
1 = same general topic as the query, but would not actually help complete this specific query or task.
|
|
5
|
+
0 = unrelated to the query.
|
|
6
|
+
Reply with ONLY a JSON object: {"grade": <integer 0-3>, "reason": "<=25 words"}.
|
|
@@ -26,6 +26,7 @@ import { warn, warnVerbose } from "../../core/warn.js";
|
|
|
26
26
|
import { resolveWriteTarget } from "../../core/write-source.js";
|
|
27
27
|
import { deriveInstallations } from "../../indexer/installations.js";
|
|
28
28
|
import { resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
29
|
+
import { USAGE_EVENT_RETENTION_DAYS } from "../../indexer/usage/usage-events.js";
|
|
29
30
|
import { assertRunnerCredentials } from "../../integrations/agent/runner-dispatch.js";
|
|
30
31
|
import { cosineSimilarity, embedBatch, resolveEmbeddingModelId } from "../../llm/embedder.js";
|
|
31
32
|
import { getBodyEmbeddings, upsertBodyEmbeddings } from "../../storage/repositories/embeddings-repository.js";
|
|
@@ -39,6 +40,7 @@ import { sanitizeMergedContent } from "./consolidate/sanitize.js";
|
|
|
39
40
|
import { contentHash } from "./content-hash.js";
|
|
40
41
|
import { resolveImproveStrategy, resolveProcessEnabled } from "./improve-strategies.js";
|
|
41
42
|
import { isLedgerBlocked, ledgerKey, loadLedgerSnapshot, recordLedgerAttempt } from "./ledger.js";
|
|
43
|
+
import { isInRetrievalScope, loadRetrievalScope } from "./retrieval-scope.js";
|
|
42
44
|
import { callStage, mintProposal, noticeSet, stageRunner } from "./stage.js";
|
|
43
45
|
/** A plan op worth acting on. Retired advisory ops (merge/delete/contradict) are dropped, never thrown on. */
|
|
44
46
|
export function isValidOp(op) {
|
|
@@ -428,6 +430,11 @@ export function inspectConsolidationPool(opts, stashDir, warnings, existingKnowl
|
|
|
428
430
|
});
|
|
429
431
|
}
|
|
430
432
|
const judgedUnchanged = poolSize - memories.length;
|
|
433
|
+
// Only what retrieval returned or new material improve never processed (#986).
|
|
434
|
+
const retrievalScope = loadRetrievalScope({ proposalsCtx: opts.proposalsCtx, readOnly }, stashDir);
|
|
435
|
+
const beforeScope = memories.length;
|
|
436
|
+
memories = memories.filter((memory) => isInRetrievalScope(retrievalScope, conceptIdFromTypeName("memory", memory.name), memory.filePath));
|
|
437
|
+
const outsideRetrievalScope = beforeScope - memories.length;
|
|
431
438
|
if (opts.incrementalSince && memories.length > 0) {
|
|
432
439
|
memories = narrowToIncrementalCandidates(memories, opts.incrementalSince, warnings, opts.neighborsPerChanged, readOnly);
|
|
433
440
|
}
|
|
@@ -465,6 +472,7 @@ export function inspectConsolidationPool(opts, stashDir, warnings, existingKnowl
|
|
|
465
472
|
memories,
|
|
466
473
|
prefilteredAlreadyPromoted,
|
|
467
474
|
judgedUnchanged,
|
|
475
|
+
outsideRetrievalScope,
|
|
468
476
|
};
|
|
469
477
|
}
|
|
470
478
|
const ABORT_MIN_CHUNKS = 4;
|
|
@@ -641,6 +649,9 @@ async function consolidate(opts, config, stashDir, startMs, stateDb) {
|
|
|
641
649
|
if (pool.judgedUnchanged > 0) {
|
|
642
650
|
warnings.push(`Consolidation: skipped ${pool.judgedUnchanged} ${plural(pool.judgedUnchanged)} judged within the revisit window and unchanged since.`);
|
|
643
651
|
}
|
|
652
|
+
if (pool.outsideRetrievalScope > 0) {
|
|
653
|
+
warnings.push(`Consolidation: skipped ${pool.outsideRetrievalScope} ${plural(pool.outsideRetrievalScope)} already judged that retrieval has not returned in the last ${USAGE_EVENT_RETENTION_DAYS} days.`);
|
|
654
|
+
}
|
|
644
655
|
if (prefilteredAlreadyPromoted > 0) {
|
|
645
656
|
warnings.push(`Consolidation: pre-filtered ${prefilteredAlreadyPromoted} ${plural(prefilteredAlreadyPromoted)} whose body already exists verbatim in knowledge/ before chunking.`);
|
|
646
657
|
}
|
|
@@ -20,19 +20,19 @@ function mergeDefaults(farther, nearer) {
|
|
|
20
20
|
return deepMergeConfig(farther, nearer);
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
|
-
* Resolve improve-owned model work through the canonical execution cascade
|
|
24
|
-
*
|
|
25
|
-
* defaults.llmEngine -> strategy -> process -> current invocation.
|
|
23
|
+
* Resolve improve-owned model work through the canonical execution cascade:
|
|
24
|
+
* defaults.llmEngine -> strategy -> index.<pass> -> process -> current invocation.
|
|
26
25
|
*/
|
|
27
26
|
export function resolveImproveExecution(options) {
|
|
28
27
|
const defaultEngine = options.config.defaults?.llmEngine;
|
|
29
28
|
const profileDefaults = cascadeDefaults(options.profile);
|
|
29
|
+
const indexDefaults = cascadeDefaults(options.index);
|
|
30
30
|
const processDefaults = cascadeDefaults(options.process);
|
|
31
31
|
const currentDefaults = cascadeDefaults(options.current);
|
|
32
|
-
const selectedEngine = currentDefaults.engine ?? processDefaults.engine ?? profileDefaults.engine ?? defaultEngine;
|
|
32
|
+
const selectedEngine = currentDefaults.engine ?? processDefaults.engine ?? indexDefaults.engine ?? profileDefaults.engine ?? defaultEngine;
|
|
33
33
|
if (selectedEngine === undefined || selectedEngine === null)
|
|
34
34
|
return null;
|
|
35
|
-
const invocationDefaults = mergeDefaults(defaultEngine ? { engine: defaultEngine } : {}, profileDefaults);
|
|
35
|
+
const invocationDefaults = mergeDefaults(mergeDefaults(defaultEngine ? { engine: defaultEngine } : {}, profileDefaults), indexDefaults);
|
|
36
36
|
const current = mergeDefaults(processDefaults, currentDefaults);
|
|
37
37
|
const prepared = resolveExecution({
|
|
38
38
|
content: `improve ${options.processName} execution selection`,
|
|
@@ -14,6 +14,7 @@ import { getCacheDir } from "../../core/paths.js";
|
|
|
14
14
|
import { redactSensitiveText } from "../../core/redaction.js";
|
|
15
15
|
import { clearLogFile, setLogFile, warn } from "../../core/warn.js";
|
|
16
16
|
import { resolveWriteTarget } from "../../core/write-source.js";
|
|
17
|
+
import { DEFAULT_LLM_TIMEOUT_MS } from "../../integrations/agent/config.js";
|
|
17
18
|
import { collectEngineCredentialValues } from "../../integrations/agent/engine-resolution.js";
|
|
18
19
|
import { probeLlmReachable } from "../../llm/client.js";
|
|
19
20
|
import { getOutputMode } from "../../output/context.js";
|
|
@@ -77,25 +78,44 @@ function collectRequiredEngineTargets(plan) {
|
|
|
77
78
|
const targets = [];
|
|
78
79
|
for (const [processName, process] of Object.entries(plan.processes)) {
|
|
79
80
|
if (process.runner) {
|
|
80
|
-
targets.push({
|
|
81
|
+
targets.push({
|
|
82
|
+
process: processName,
|
|
83
|
+
engine: process.runner.engine,
|
|
84
|
+
connection: probeConnection(process.runner),
|
|
85
|
+
});
|
|
81
86
|
}
|
|
82
87
|
}
|
|
83
88
|
if (plan.triageJudgment?.kind === "llm") {
|
|
84
89
|
targets.push({
|
|
85
90
|
process: "triage.judgment",
|
|
86
91
|
engine: plan.triageJudgment.engine,
|
|
87
|
-
connection: plan.triageJudgment
|
|
92
|
+
connection: probeConnection(plan.triageJudgment),
|
|
88
93
|
});
|
|
89
94
|
}
|
|
90
95
|
return targets;
|
|
91
96
|
}
|
|
97
|
+
/** The resolved engine keeps its request timeout beside the connection (the runtime merges it in); the probe needs it on the connection. */
|
|
98
|
+
function probeConnection(runner) {
|
|
99
|
+
return runner.timeoutMs !== undefined ? { ...runner.connection, timeoutMs: runner.timeoutMs } : runner.connection;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The bound on `--require-engines`' probe: the connection's own request
|
|
103
|
+
* timeout, at most two minutes. A local server busy with another job queues
|
|
104
|
+
* the probe behind that job, and a fixed 3s bound failed every scheduled
|
|
105
|
+
* improve run on 2026-09-27 against a reachable endpoint; the cap still ends a
|
|
106
|
+
* hung endpoint (#957) long before a run's own multi-minute calls would.
|
|
107
|
+
*/
|
|
108
|
+
export function requiredEngineProbeTimeoutMs(connection) {
|
|
109
|
+
return Math.min(connection.timeoutMs ?? DEFAULT_LLM_TIMEOUT_MS, REQUIRED_ENGINE_PROBE_MAX_MS);
|
|
110
|
+
}
|
|
111
|
+
const REQUIRED_ENGINE_PROBE_MAX_MS = 120_000;
|
|
92
112
|
/**
|
|
93
113
|
* `--require-engines`, live: probe each connection's real completion path
|
|
94
|
-
* (a gateway can list a model whose completion route is dead, #980)
|
|
95
|
-
*
|
|
96
|
-
* result (R17); an unreachable one fails the run.
|
|
114
|
+
* (a gateway can list a model whose completion route is dead, #980), once per
|
|
115
|
+
* endpoint + model, within {@link requiredEngineProbeTimeoutMs}. Returns each
|
|
116
|
+
* target's latency for the run result (R17); an unreachable one fails the run.
|
|
97
117
|
*/
|
|
98
|
-
export async function assertRequiredEnginesReachable(plan, probeReachable = (connection) => probeLlmReachable(connection,
|
|
118
|
+
export async function assertRequiredEnginesReachable(plan, probeReachable = (connection) => probeLlmReachable(connection, requiredEngineProbeTimeoutMs(connection))) {
|
|
99
119
|
const targets = collectRequiredEngineTargets(plan);
|
|
100
120
|
if (targets.length === 0)
|
|
101
121
|
return [];
|
|
@@ -117,7 +137,7 @@ export async function assertRequiredEnginesReachable(plan, probeReachable = (con
|
|
|
117
137
|
const unreachable = probed.filter((item) => !item.reach.reachable);
|
|
118
138
|
if (unreachable.length > 0) {
|
|
119
139
|
const lines = unreachable.map((item) => ` - ${item.process} (engine "${item.engine}", ${item.connection.endpoint}): ${item.reach.error ?? "did not respond"}`);
|
|
120
|
-
throw new ConfigError(`--require-engines: ${unreachable.length} improve process${unreachable.length === 1 ? "" : "es"} cannot run because ${unreachable.length === 1 ? "its" : "their"} engine completion path is not reachable:\n${lines.join("\n")}`, "LLM_NOT_CONFIGURED");
|
|
140
|
+
throw new ConfigError(`--require-engines: ${unreachable.length} improve process${unreachable.length === 1 ? "" : "es"} cannot run because ${unreachable.length === 1 ? "its" : "their"} engine completion path is not reachable:\n${lines.join("\n")}`, "LLM_NOT_CONFIGURED", "Check that each listed endpoint is up and serves its model. The probe is one short completion, bounded by the engine's timeoutMs (at most two minutes).");
|
|
121
141
|
}
|
|
122
142
|
return probed.map((item) => ({
|
|
123
143
|
process: item.process,
|
|
@@ -10,6 +10,7 @@ import quick from "../../assets/improve-strategies/quick.json" with { type: "jso
|
|
|
10
10
|
import reflectDistill from "../../assets/improve-strategies/reflect-distill.json" with { type: "json" };
|
|
11
11
|
import thorough from "../../assets/improve-strategies/thorough.json" with { type: "json" };
|
|
12
12
|
import { conceptIdFromTypeName, parseRefInput } from "../../core/asset/resolve-ref.js";
|
|
13
|
+
import { getIndexPassConfig, } from "../../core/config/config.js";
|
|
13
14
|
import { ImproveProfileConfigSchema } from "../../core/config/config-schema.js";
|
|
14
15
|
import { deepMergeConfig } from "../../core/config/deep-merge.js";
|
|
15
16
|
import { BUILTIN_IMPROVE_STRATEGY_NAMES, IMPROVE_PROCESS_ENGINE_CAPABILITIES, } from "../../core/config/engine-semantics.js";
|
|
@@ -211,6 +212,8 @@ function buildImprovePlan(strategy, config, options) {
|
|
|
211
212
|
profile: strategy.config,
|
|
212
213
|
process: sourceProcessConfig,
|
|
213
214
|
processName,
|
|
215
|
+
// Graph extraction's standing engine, model, timeout and llm settings (GR-D15).
|
|
216
|
+
...(processName === "graphExtraction" ? { index: getIndexPassConfig(config.index, "graph") } : {}),
|
|
214
217
|
});
|
|
215
218
|
runner = resolved?.runner ?? null;
|
|
216
219
|
notices = resolved?.notices ?? [];
|
|
@@ -82,13 +82,17 @@ export function recordLedgerAttempt(access, inputs) {
|
|
|
82
82
|
export function ledgerKey(source, ref) {
|
|
83
83
|
return `${source}\0${ref}`;
|
|
84
84
|
}
|
|
85
|
+
/** Read state.db through `fn` without creating it: `undefined` when there is none yet. */
|
|
86
|
+
export function readLedgerDb(access, fn) {
|
|
87
|
+
if (!access?.eventsCtx?.db && !fs.existsSync(ledgerDbPath(access) ?? getStateDbPath()))
|
|
88
|
+
return undefined;
|
|
89
|
+
return withLedgerDb(access, fn);
|
|
90
|
+
}
|
|
85
91
|
/** Every ledger row for `sources` in one query; no state.db yet means nothing was attempted. */
|
|
86
92
|
export function loadLedgerSnapshot(access, stashDir, sources) {
|
|
87
93
|
const out = new Map();
|
|
88
|
-
if (!access?.eventsCtx?.db && !fs.existsSync(ledgerDbPath(access) ?? getStateDbPath()))
|
|
89
|
-
return out;
|
|
90
94
|
try {
|
|
91
|
-
|
|
95
|
+
readLedgerDb(access, (db) => {
|
|
92
96
|
for (const row of listImproveLedgerRows(db, stashDir, sources))
|
|
93
97
|
out.set(ledgerKey(row.source, row.ref), row);
|
|
94
98
|
});
|
|
@@ -6,14 +6,14 @@ import fs from "node:fs";
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { parseRefInput } from "../../core/asset/resolve-ref.js";
|
|
8
8
|
import { daysToMs } from "../../core/common.js";
|
|
9
|
-
import {
|
|
9
|
+
import { loadConfig } from "../../core/config/config.js";
|
|
10
10
|
import { UsageError } from "../../core/errors.js";
|
|
11
11
|
import { appendEvent } from "../../core/events.js";
|
|
12
12
|
import { openLogsDatabase, purgeOldTaskLogs } from "../../core/logs-db.js";
|
|
13
13
|
import { getDbPath, getTaskLogDir } from "../../core/paths.js";
|
|
14
14
|
import { withStateDb } from "../../core/state-db.js";
|
|
15
15
|
import { info } from "../../core/warn.js";
|
|
16
|
-
import {
|
|
16
|
+
import { runGraphExtractionPass } from "../../indexer/graph/graph-extraction.js";
|
|
17
17
|
import { indexWrittenAssets } from "../../indexer/index-written-assets.js";
|
|
18
18
|
import { deriveWritableBundleIds } from "../../indexer/installations.js";
|
|
19
19
|
import { collectPendingMemories, runMemoryInferencePass, } from "../../indexer/passes/memory-inference.js";
|
|
@@ -514,8 +514,9 @@ export async function runGraphExtractionMaintenancePass(ctx, dbCell, args) {
|
|
|
514
514
|
},
|
|
515
515
|
options: {
|
|
516
516
|
candidatePaths,
|
|
517
|
-
|
|
518
|
-
|
|
517
|
+
// Only what the strategy sets: the pass falls back to index.graph, then its defaults (GR-D15).
|
|
518
|
+
...(settings?.includeTypes ? { includeTypes: settings.includeTypes } : {}),
|
|
519
|
+
...(settings?.batchSize != null ? { batchSize: settings.batchSize } : {}),
|
|
519
520
|
...(settings?.topN != null ? { topN: settings.topN } : {}),
|
|
520
521
|
...(settings?.maxChunksPerAsset != null ? { maxChunksPerAsset: settings.maxChunksPerAsset } : {}),
|
|
521
522
|
},
|