akm-cli 0.9.0-beta.5 → 0.9.0-beta.6
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
CHANGED
|
@@ -6,6 +6,54 @@ 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.0-beta.6] - 2026-06-12
|
|
10
|
+
|
|
11
|
+
Pipeline optimization: new per-process config fields wire up the consolidation
|
|
12
|
+
and improve pipeline knobs exposed by the optimization report — incremental
|
|
13
|
+
consolidation, pool caps, distill gating, and memory inference throttling.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **`consolidate.incrementalSince`** — profile config field that narrows the
|
|
18
|
+
consolidation candidate pool to memories modified within the given window
|
|
19
|
+
(e.g. `"1h"`, `"4h"`) plus their graph neighbours. Enables frequent
|
|
20
|
+
consolidation passes (e.g. `quick-shredder` every 15 min) without full-pool
|
|
21
|
+
sweeps. Absent = full-pool sweep (correct for nightly runs).
|
|
22
|
+
- **`consolidate.limit`** — hard cap on memories processed per consolidation
|
|
23
|
+
pass, applied after incremental narrowing. Prevents runaway full-pool sweeps
|
|
24
|
+
in the nightly default profile.
|
|
25
|
+
- **`consolidate.neighborsPerChanged`** — configurable graph-neighbour count
|
|
26
|
+
per changed memory during incremental consolidation (was hardcoded to 5).
|
|
27
|
+
`quick-shredder` sets this to 3 for a 40% candidate reduction per burst.
|
|
28
|
+
- **`distill.requirePlannedRefs`** — when `true`, the distill process is
|
|
29
|
+
skipped entirely for distill-only refs when the reflect phase produced zero
|
|
30
|
+
planned refs. Eliminates hundreds of `distill-skipped` events on quiet passes
|
|
31
|
+
where all refs are on reflect cooldown.
|
|
32
|
+
- **`memoryInference.minPendingCount`** — minimum pending split-parent memory
|
|
33
|
+
count below which the inference pass is skipped entirely (zero LLM calls).
|
|
34
|
+
Prevents lock acquisition on passes where there is nothing to infer.
|
|
35
|
+
- **`reflect.limit`** — per-process ref limit for the reflect/distill loop,
|
|
36
|
+
applied as the improve run limit when no CLI `--limit` is given.
|
|
37
|
+
- **New `reflect-distill` improve profile** — dedicated reflect + distill +
|
|
38
|
+
memoryInference + triage profile for the every-4h `akm-improve-frequent`
|
|
39
|
+
task. `reflect.limit: 25` bounds LLM cost per pass.
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- **`quick-shredder` profile tuned**: `incrementalSince` `4h` → `1h`,
|
|
44
|
+
`maxChunkSize` 25 → 35, added `minPoolSize: 10`, `neighborsPerChanged: 3`,
|
|
45
|
+
`memoryInference.minPendingCount: 5`. All `profile: "qwen-9b-shredder"`
|
|
46
|
+
process references removed — falls back to default LLM.
|
|
47
|
+
- **`default` improve profile** (nightly): extract disabled (dedicated
|
|
48
|
+
`akm-extract` task runs at 01:48), consolidate gets `limit: 500`,
|
|
49
|
+
reflect gets `limit: 100` and `allowedTypes`, distill gets
|
|
50
|
+
`requirePlannedRefs: true`, triage enabled at 50 accepts/run,
|
|
51
|
+
graphExtraction explicitly enabled.
|
|
52
|
+
- **Cron schedule optimised**: extract reverted to `8,28,48 * * * *` (3×/hr),
|
|
53
|
+
quick-shredder shifted to `4,19,34,49` (4-min extract gap), health-report
|
|
54
|
+
shifted to `:03` (avoids `:00` collision), `akm-improve-frequent` re-enabled
|
|
55
|
+
at `45 */4` with `reflect-distill` profile.
|
|
56
|
+
|
|
9
57
|
## [0.9.0-beta.3] - 2026-06-12
|
|
10
58
|
|
|
11
59
|
Stabilization batch closing the remaining 0.9.0 milestone: DB-locking and
|
|
@@ -809,7 +809,7 @@ export async function akmConsolidate(opts = {}) {
|
|
|
809
809
|
};
|
|
810
810
|
}
|
|
811
811
|
if (opts.incrementalSince) {
|
|
812
|
-
memories = narrowToIncrementalCandidates(memories, opts.incrementalSince, warnings);
|
|
812
|
+
memories = narrowToIncrementalCandidates(memories, opts.incrementalSince, warnings, opts.neighborsPerChanged);
|
|
813
813
|
if (memories.length === 0) {
|
|
814
814
|
return {
|
|
815
815
|
schemaVersion: 1,
|
|
@@ -828,6 +828,10 @@ export async function akmConsolidate(opts = {}) {
|
|
|
828
828
|
};
|
|
829
829
|
}
|
|
830
830
|
}
|
|
831
|
+
if (opts.limit !== undefined && memories.length > opts.limit) {
|
|
832
|
+
warnings.push(`Consolidation: pool capped at ${opts.limit} memories (limit option).`);
|
|
833
|
+
memories = memories.slice(0, opts.limit);
|
|
834
|
+
}
|
|
831
835
|
// Consolidation always uses the HTTP LLM client directly — never the agent
|
|
832
836
|
// CLI. The agent CLI is for interactive agent sessions (reflect, propose);
|
|
833
837
|
// structured JSON generation works better and faster via HTTP.
|
|
@@ -2004,7 +2008,7 @@ function parseSinceToIso(since) {
|
|
|
2004
2008
|
const multiplier = { m: 60_000, h: 3_600_000, d: 86_400_000 }[m[2]];
|
|
2005
2009
|
return new Date(Date.now() - parseInt(m[1], 10) * multiplier).toISOString();
|
|
2006
2010
|
}
|
|
2007
|
-
export function narrowToIncrementalCandidates(memories, since, warnings) {
|
|
2011
|
+
export function narrowToIncrementalCandidates(memories, since, warnings, neighborsPerChanged = 5) {
|
|
2008
2012
|
const sinceIso = parseSinceToIso(since);
|
|
2009
2013
|
const isChanged = (m) => {
|
|
2010
2014
|
try {
|
|
@@ -2019,7 +2023,6 @@ export function narrowToIncrementalCandidates(memories, since, warnings) {
|
|
|
2019
2023
|
return [];
|
|
2020
2024
|
if (changed.length === memories.length)
|
|
2021
2025
|
return memories;
|
|
2022
|
-
const NEIGHBORS_PER_CHANGED = 5;
|
|
2023
2026
|
const byName = new Map(memories.map((m) => [m.name, m]));
|
|
2024
2027
|
const keep = new Set(changed.map((m) => m.name));
|
|
2025
2028
|
let db;
|
|
@@ -2029,7 +2032,7 @@ export function narrowToIncrementalCandidates(memories, since, warnings) {
|
|
|
2029
2032
|
const id = findEntryIdByRef(db, `memory:${m.name}`);
|
|
2030
2033
|
if (id === undefined)
|
|
2031
2034
|
continue;
|
|
2032
|
-
for (const hit of getNeighborsByEntryId(db, id,
|
|
2035
|
+
for (const hit of getNeighborsByEntryId(db, id, neighborsPerChanged + 1)) {
|
|
2033
2036
|
if (hit.id === id)
|
|
2034
2037
|
continue;
|
|
2035
2038
|
const entry = getEntryById(db, hit.id);
|
|
@@ -20,7 +20,7 @@ import { closeDatabase, getAllEntries, getEntryCount, getRetrievalCounts, getUti
|
|
|
20
20
|
import { ensureIndex } from "../../indexer/ensure-index.js";
|
|
21
21
|
import { runGraphExtractionPass } from "../../indexer/graph/graph-extraction.js";
|
|
22
22
|
import { akmIndex } from "../../indexer/indexer.js";
|
|
23
|
-
import { runMemoryInferencePass } from "../../indexer/passes/memory-inference.js";
|
|
23
|
+
import { collectPendingMemories, runMemoryInferencePass, } from "../../indexer/passes/memory-inference.js";
|
|
24
24
|
import { runStalenessDetectionPass } from "../../indexer/passes/staleness-detect.js";
|
|
25
25
|
import { getWritableStashDirs, resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
26
26
|
import { countUsageEventsByType } from "../../indexer/usage/usage-events.js";
|
|
@@ -471,7 +471,9 @@ export async function akmImprove(options = {}) {
|
|
|
471
471
|
options = {
|
|
472
472
|
...options,
|
|
473
473
|
autoAccept: options.autoAccept ?? improveProfile.autoAccept,
|
|
474
|
-
limit
|
|
474
|
+
// Profile-level limit, then process-level reflect.limit as fallback.
|
|
475
|
+
// CLI --limit takes precedence over both.
|
|
476
|
+
limit: options.limit ?? improveProfile?.processes?.reflect?.limit ?? improveProfile.limit,
|
|
475
477
|
};
|
|
476
478
|
let primaryStashDir;
|
|
477
479
|
try {
|
|
@@ -1385,13 +1387,13 @@ async function runConsolidationPass(args) {
|
|
|
1385
1387
|
// Tie consolidate proposals back to this improve invocation so
|
|
1386
1388
|
// accept-rate-per-run aggregation works. Mirrors reflect/propose/extract.
|
|
1387
1389
|
sourceRun: `consolidate-${Date.now()}`,
|
|
1388
|
-
//
|
|
1389
|
-
//
|
|
1390
|
-
//
|
|
1391
|
-
//
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1390
|
+
// Pass profile-configured options. incrementalSince narrows the pool to
|
|
1391
|
+
// recently-changed memories + graph neighbours — use this for frequent
|
|
1392
|
+
// passes (quick-shredder). Leave absent in the nightly default profile for
|
|
1393
|
+
// a full-pool sweep that catches stale-but-unmerged duplicates.
|
|
1394
|
+
incrementalSince: improveProfile?.processes?.consolidate?.incrementalSince,
|
|
1395
|
+
limit: improveProfile?.processes?.consolidate?.limit,
|
|
1396
|
+
neighborsPerChanged: improveProfile?.processes?.consolidate?.neighborsPerChanged,
|
|
1395
1397
|
maxChunkSize: improveProfile?.processes?.consolidate?.maxChunkSize,
|
|
1396
1398
|
// Honor profile.autoAccept (already merged into options.autoAccept at the
|
|
1397
1399
|
// top of akmImprove). The CLI parser always supplies 90 when --auto-accept
|
|
@@ -2067,6 +2069,14 @@ async function runImproveLoopStage(args) {
|
|
|
2067
2069
|
// receives only its fair share of the wall-clock budget.
|
|
2068
2070
|
const remainingBudgetMs = () => Math.max(0, budgetMs - (Date.now() - startMs));
|
|
2069
2071
|
const RECENT_ERRORS_CAP = 3;
|
|
2072
|
+
// requirePlannedRefs guard: when the distill profile sets this flag, skip
|
|
2073
|
+
// distill for distill-only refs if the reflect phase produced no planned refs.
|
|
2074
|
+
// Prevents the distill loop from generating hundreds of distill-skipped events
|
|
2075
|
+
// on quiet passes (all refs on reflect cooldown, no new signal to distill).
|
|
2076
|
+
const requirePlannedRefs = improveProfile?.processes?.distill?.requirePlannedRefs === true;
|
|
2077
|
+
const _distillOnlyRefNames = new Set(distillOnlyRefs.map((r) => r.ref));
|
|
2078
|
+
const hasReflectEligibleRefs = loopRefs.some((r) => !_distillOnlyRefNames.has(r.ref));
|
|
2079
|
+
const skipDistillDueToRequirePlannedRefs = requirePlannedRefs && !hasReflectEligibleRefs;
|
|
2070
2080
|
// R-2 / #389: Self-Consistency multi-sample voting helpers.
|
|
2071
2081
|
// Wang et al. arXiv:2203.11171 — N=3 samples beat single-shot on reasoning tasks.
|
|
2072
2082
|
const SC_THRESHOLD = options.selfConsistencyThreshold ?? 0.7;
|
|
@@ -2364,6 +2374,18 @@ async function runImproveLoopStage(args) {
|
|
|
2364
2374
|
info(`[improve] ${completedCount}/${loopRefs.length} ${planned.ref}`);
|
|
2365
2375
|
continue;
|
|
2366
2376
|
}
|
|
2377
|
+
// requirePlannedRefs guard: skip distill for distill-only refs when no
|
|
2378
|
+
// reflect-eligible refs were planned this run, preventing mass skip events.
|
|
2379
|
+
if (skipDistillDueToRequirePlannedRefs && isDistillOnly) {
|
|
2380
|
+
actions.push({
|
|
2381
|
+
ref: planned.ref,
|
|
2382
|
+
mode: "distill-skipped",
|
|
2383
|
+
result: { ok: true, reason: "require_planned_refs" },
|
|
2384
|
+
});
|
|
2385
|
+
completedCount++;
|
|
2386
|
+
info(`[improve] ${completedCount}/${loopRefs.length} ${planned.ref}`);
|
|
2387
|
+
continue;
|
|
2388
|
+
}
|
|
2367
2389
|
// See `isDistillCandidateRef` — excludes `lesson:*` (and anything else in
|
|
2368
2390
|
// DISTILL_REFUSED_INPUT_TYPES) so distill never gets queued for an input
|
|
2369
2391
|
// it will refuse.
|
|
@@ -2634,9 +2656,23 @@ export async function runImproveMaintenancePasses(args) {
|
|
|
2634
2656
|
// candidates from the filesystem-of-truth. The this-run set is still
|
|
2635
2657
|
// logged as a hint but no longer used as a filter.
|
|
2636
2658
|
const memoryInferenceDisabledByProfile = improveProfile?.processes?.memoryInference?.enabled === false;
|
|
2659
|
+
const minPendingCount = improveProfile?.processes?.memoryInference?.minPendingCount;
|
|
2660
|
+
const pendingBelowMinCount = (() => {
|
|
2661
|
+
if (!primaryStashDir || minPendingCount === undefined || minPendingCount <= 0)
|
|
2662
|
+
return false;
|
|
2663
|
+
const pending = collectPendingMemories(primaryStashDir).length;
|
|
2664
|
+
if (pending < minPendingCount) {
|
|
2665
|
+
info(`[improve] memory inference skipped (${pending} pending < minPendingCount ${minPendingCount})`);
|
|
2666
|
+
return true;
|
|
2667
|
+
}
|
|
2668
|
+
return false;
|
|
2669
|
+
})();
|
|
2637
2670
|
if (memoryInferenceDisabledByProfile) {
|
|
2638
2671
|
info("[improve] memory inference skipped (disabled by improve profile)");
|
|
2639
2672
|
}
|
|
2673
|
+
else if (pendingBelowMinCount) {
|
|
2674
|
+
// skipped — message already emitted above
|
|
2675
|
+
}
|
|
2640
2676
|
else {
|
|
2641
2677
|
const hintRefs = memoryRefsForInference.size;
|
|
2642
2678
|
info(hintRefs > 0
|
|
@@ -144,6 +144,20 @@ export const ImproveProcessConfigSchema = z
|
|
|
144
144
|
// on the `extract` process.
|
|
145
145
|
minContentChars: z.number().int().min(0).optional(),
|
|
146
146
|
maxChunkSize: z.number().int().min(1).max(50).optional(),
|
|
147
|
+
// Consolidate process: narrow candidate pool to memories modified within
|
|
148
|
+
// this duration window plus their graph neighbours. Only meaningful on
|
|
149
|
+
// the `consolidate` process. Absent = full-pool sweep.
|
|
150
|
+
incrementalSince: z.string().optional(),
|
|
151
|
+
// Consolidate process: hard cap on memories processed per pass.
|
|
152
|
+
// Reflect/distill: max refs processed (same as profile-level `limit`).
|
|
153
|
+
limit: positiveInt.optional(),
|
|
154
|
+
// Consolidate process: graph neighbours per changed memory during
|
|
155
|
+
// incremental consolidation. Default 5. Only meaningful with incrementalSince.
|
|
156
|
+
neighborsPerChanged: z.number().int().min(1).optional(),
|
|
157
|
+
// Distill process: skip distill entirely when reflect produced zero planned refs.
|
|
158
|
+
requirePlannedRefs: z.boolean().optional(),
|
|
159
|
+
// MemoryInference process: minimum pending memory count to run the pass.
|
|
160
|
+
minPendingCount: z.number().int().min(0).optional(),
|
|
147
161
|
// Extract process: minimum number of new (unseen, in-window) candidate
|
|
148
162
|
// sessions below which the extract pass skips entirely (emits an
|
|
149
163
|
// `improve_skipped` event with `reason: "below_min_new_sessions"`). 0
|
|
@@ -15510,6 +15510,11 @@ var init_config_schema = __esm(() => {
|
|
|
15510
15510
|
maxTotalChars: positiveInt.optional(),
|
|
15511
15511
|
minContentChars: exports_external.number().int().min(0).optional(),
|
|
15512
15512
|
maxChunkSize: exports_external.number().int().min(1).max(50).optional(),
|
|
15513
|
+
incrementalSince: exports_external.string().optional(),
|
|
15514
|
+
limit: positiveInt.optional(),
|
|
15515
|
+
neighborsPerChanged: exports_external.number().int().min(1).optional(),
|
|
15516
|
+
requirePlannedRefs: exports_external.boolean().optional(),
|
|
15517
|
+
minPendingCount: exports_external.number().int().min(0).optional(),
|
|
15513
15518
|
minNewSessions: exports_external.number().int().min(0).optional(),
|
|
15514
15519
|
indexSessions: exports_external.boolean().optional(),
|
|
15515
15520
|
minSessionDuration: exports_external.number().min(0).optional(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akm-cli",
|
|
3
|
-
"version": "0.9.0-beta.
|
|
3
|
+
"version": "0.9.0-beta.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "akm (Agent Knowledge Management) — A package manager for AI agent skills, commands, tools, and knowledge. Works with Claude Code, OpenCode, Cursor, and any AI coding assistant.",
|
|
6
6
|
"keywords": [
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"scripts": {
|
|
53
53
|
"preinstall": "node -e \"var ua=process.env.npm_config_user_agent||'';var major=parseInt((process.versions.node||'0').split('.')[0],10);if(process.versions.bun||ua.startsWith('bun/')||process.env.BUN_INSTALL||major>=20){process.exit(0)}console.error('\\n ERROR: akm-cli requires the Bun runtime (https://bun.sh), Node.js >= 20, or the prebuilt binary.\\n Install options:\\n 1. Bun: curl -fsSL https://bun.sh/install | bash && bun install -g akm-cli\\n 2. Binary: curl -fsSL https://github.com/itlackey/akm/releases/latest/download/install.sh | bash\\n');process.exit(1)\"",
|
|
54
|
-
"build": "rm -rf dist && bun run tsc --project ./tsconfig.build.json && bun scripts/copy-assets.ts && bun scripts/fix-esm-extensions.ts",
|
|
54
|
+
"build": "rm -rf dist && bun scripts/gen-config-schema.ts &&bun run tsc --project ./tsconfig.build.json && bun scripts/copy-assets.ts && bun scripts/fix-esm-extensions.ts",
|
|
55
55
|
"check": "bun run lint && bunx tsc --noEmit && bun run test:unit && bun run test:integration",
|
|
56
56
|
"check:fast": "bun run lint && bunx tsc --noEmit && bun run test:unit",
|
|
57
57
|
"check:changed": "bun test tests/output-baseline.test.ts tests/integration/e2e.test.ts tests/stash-search.test.ts && bun run lint && bunx tsc --noEmit",
|