akm-cli 0.9.4 → 0.9.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 +265 -0
- package/dist/commands/env/env-cli.js +1 -2
- package/dist/commands/env/secret-cli.js +1 -5
- package/dist/commands/feedback-cli.js +0 -4
- package/dist/commands/health/checks.js +0 -32
- package/dist/commands/health/surfaces.js +2 -2
- package/dist/commands/health.js +6 -15
- package/dist/commands/improve/anti-collapse.js +4 -91
- package/dist/commands/improve/autonomy-gate.js +1 -1
- package/dist/commands/improve/consolidate.js +25 -26
- package/dist/commands/improve/distill.js +2 -2
- package/dist/commands/improve/extract.js +8 -1
- package/dist/commands/improve/improve.js +3 -5
- package/dist/commands/improve/locks.js +13 -5
- package/dist/commands/improve/loop-stages.js +8 -9
- package/dist/commands/improve/memory/memory-contradiction-detect.js +1 -19
- package/dist/commands/improve/outcome-loop.js +0 -6
- package/dist/commands/improve/reflect.js +20 -9
- package/dist/commands/lint/index.js +4 -6
- package/dist/commands/proposal/validators/proposal-quality-validators.js +0 -7
- package/dist/commands/proposal/validators/proposal-validators.js +12 -0
- package/dist/commands/read/search.js +14 -24
- package/dist/commands/sources/bundle-config-ops.js +9 -4
- package/dist/commands/sources/info.js +14 -10
- package/dist/commands/tasks/tasks-cli.js +56 -3
- package/dist/commands/tasks/tasks.js +101 -18
- package/dist/commands/workflow-cli.js +2 -2
- package/dist/core/adapter/adapters/akm-adapter.js +2 -0
- package/dist/core/common.js +61 -24
- package/dist/core/config/config-io.js +2 -2
- package/dist/core/config/config-sources.js +32 -2
- package/dist/core/config/config-version-shim.js +101 -0
- package/dist/core/config/config.js +7 -7
- package/dist/core/config/schema/engines.js +9 -7
- package/dist/core/config/schema/primitives.js +0 -5
- package/dist/core/config/schema/search.js +2 -1
- package/dist/core/file-lock.js +2 -1
- package/dist/core/improve-result.js +35 -14
- package/dist/core/maintenance-barrier.js +2 -14
- package/dist/core/paths.js +0 -3
- package/dist/core/redaction.js +2 -2
- package/dist/core/spawn-env.js +8 -12
- package/dist/core/state/migrations.js +1 -12
- package/dist/core/state-db.js +9 -27
- package/dist/core/write-source.js +12 -19
- package/dist/execution/directory-identity.js +36 -10
- package/dist/execution/guarded-source.js +0 -10
- package/dist/indexer/graph/graph-boost.js +0 -4
- package/dist/indexer/index-writer-lock.js +43 -24
- package/dist/indexer/index-written-assets.js +5 -6
- package/dist/indexer/indexer.js +2 -39
- package/dist/indexer/materialize-embeddings.js +85 -41
- package/dist/indexer/passes/metadata.js +12 -4
- package/dist/indexer/scan/doc-to-entry.js +2 -0
- package/dist/indexer/search/db-search.js +15 -48
- package/dist/indexer/search/ranking-contributors.js +0 -25
- package/dist/indexer/search/ranking.js +3 -13
- package/dist/indexer/search/search-fields.js +16 -1
- package/dist/integrations/agent/builder-shared.js +0 -25
- package/dist/integrations/agent/model-map.js +2 -60
- package/dist/integrations/harnesses/aider/agent-builder.js +1 -3
- package/dist/integrations/harnesses/amazonq/agent-builder.js +2 -7
- package/dist/integrations/harnesses/claude/agent-builder.js +1 -4
- package/dist/integrations/harnesses/codex/agent-builder.js +1 -4
- package/dist/integrations/harnesses/copilot/agent-builder.js +2 -6
- package/dist/integrations/harnesses/gemini/agent-builder.js +2 -7
- package/dist/integrations/harnesses/opencode/agent-builder.js +1 -4
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +2 -16
- package/dist/integrations/harnesses/openhands/agent-builder.js +1 -3
- package/dist/integrations/harnesses/pi/agent-builder.js +2 -4
- package/dist/integrations/session-logs/index.js +0 -9
- package/dist/llm/client.js +75 -42
- package/dist/llm/embedder.js +7 -3
- package/dist/llm/embedders/remote.js +141 -42
- package/dist/output/shapes/passthrough.js +17 -5
- package/dist/registry/network.js +5 -37
- package/dist/runtime.js +2 -10
- package/dist/scripts/akm-migrate-node.js +180 -238
- package/dist/scripts/akm-migrate.js +180 -238
- package/dist/setup/engine-config.js +2 -5
- package/dist/setup/registry-stash-loader.js +0 -8
- package/dist/setup/setup.js +9 -46
- package/dist/setup/steps/connection-shared.js +10 -13
- package/dist/sources/providers/git-install.js +1 -1
- package/dist/storage/engines/sqlite-migrations.js +20 -1
- package/dist/storage/repositories/index-entries-repository.js +0 -15
- package/dist/storage/repositories/proposals-repository.js +32 -6
- package/dist/tasks/backends/launchd.js +15 -20
- package/dist/tasks/backends/schtasks.js +18 -8
- package/dist/tasks/run/run-native-task.js +8 -6
- package/dist/tasks/scheduler-binding.js +15 -5
- package/dist/tasks/scheduler-sync-preview.js +4 -2
- package/dist/tasks/scheduler-sync.js +77 -42
- package/dist/tasks/source/bounded-document.js +2 -4
- package/dist/tasks/source/task-source-v3-frozen.js +5 -7
- package/dist/tasks/source/task-source-v4.js +5 -10
- package/dist/tasks/source/task-to-v3.js +29 -22
- package/dist/tasks/source/task-to-v4.js +1 -14
- package/dist/tasks/source-v3.js +6 -6
- package/dist/workflows/exec/native-executor.js +21 -31
- package/dist/workflows/exec/run-workflow.js +5 -6
- package/dist/workflows/exec/scheduler.js +3 -19
- package/dist/workflows/exec/step-work.js +1 -4
- package/dist/workflows/exec/unit-dispatch.js +2 -2
- package/dist/workflows/exec/worktree.js +1 -13
- package/dist/workflows/freeze/targets/child-workflow.js +2 -10
- package/dist/workflows/ir/plan-hash.js +4 -6
- package/dist/workflows/ir/schema-v4.js +0 -12
- package/dist/workflows/ir/schema.js +20 -31
- package/dist/workflows/parser.js +11 -52
- package/dist/workflows/renderer.js +2 -3
- package/dist/workflows/resource-limits.js +11 -41
- package/dist/workflows/runtime/runs.js +3 -4
- package/dist/workflows/source-ir/schema.js +14 -30
- package/dist/workflows/validator.js +1 -7
- package/docs/reference/cli.md +25 -3
- package/docs/reference/configuration.md +28 -7
- package/docs/reference/tasks.md +10 -0
- package/package.json +1 -1
- package/schemas/akm-config.json +4 -23
- package/schemas/akm-task.json +1 -2
- package/schemas/akm-workflow.json +1 -13
- package/dist/indexer/search/semantic-status.js +0 -142
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,271 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
6
|
|
|
7
|
+
## [0.9.6] - 2026-08-31
|
|
8
|
+
|
|
9
|
+
The deletion release: **net −2,100 lines**, almost all of it machinery that
|
|
10
|
+
gated, refused, verified, or cached a judgment. Nearly every guard removed
|
|
11
|
+
here had **zero confirmed firings** across 38,341 production telemetry events,
|
|
12
|
+
while several had already broken real installs.
|
|
13
|
+
|
|
14
|
+
The standard applied is now written down in `AGENTS.md` (`## Defensive Code`):
|
|
15
|
+
a guard survives only if it has demonstrably helped a real user, its failure
|
|
16
|
+
mode costs less than the hazard it prevents, and the operation is not already
|
|
17
|
+
gated behind a deliberate human command. "This hazard is conceivable" is not a
|
|
18
|
+
justification.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **Embedding no longer discards an entire index because one batch was too
|
|
23
|
+
big (#874).** Remote embeddings batched by document count (100) against a
|
|
24
|
+
fixed 30s timeout; a single oversized batch failed the whole phase, leaving
|
|
25
|
+
`embeddings` at 0 rows on a real 23,857-entry bundle and silently disabling
|
|
26
|
+
semantic search. Batching is now bounded by a token budget, failures are
|
|
27
|
+
skipped-and-reported per batch, and an oversized single document is a named
|
|
28
|
+
skip rather than a phase failure.
|
|
29
|
+
- **`akm lint` no longer silently skips user directories named `.cache` or
|
|
30
|
+
`registry`.** Two name-based exclusion sites remained after the 0.9.5 fix, so
|
|
31
|
+
a bundle's own `knowledge/registry/` was never linted and reported clean.
|
|
32
|
+
Exclusion is now anchored to akm's resolved registry-cache path.
|
|
33
|
+
- **One directory can no longer register as two bundles (#870).** When
|
|
34
|
+
`AKM_BUNDLE_DIR` pointed at a directory already configured under another id,
|
|
35
|
+
akm minted a second bundle for it; `akm migrate` then enumerated every task
|
|
36
|
+
file twice and failed with `duplicate task migration file path` (exit 70) —
|
|
37
|
+
permanently, while health checks kept passing. Bundle identity is now the
|
|
38
|
+
resolved content root (`path.resolve(entry.path, component.root ?? ".")`) at
|
|
39
|
+
both registration sites, existing duplicates reconcile instead of throwing,
|
|
40
|
+
and a genuinely irreconcilable pair reports both bundle ids and the shared
|
|
41
|
+
path.
|
|
42
|
+
|
|
43
|
+
### Removed
|
|
44
|
+
|
|
45
|
+
- **The index writer lease (#872).** It guarded a *regenerable cache*, had zero
|
|
46
|
+
lease events in telemetry, and a live-but-wedged holder stranded all indexing
|
|
47
|
+
for 12 hours — `probeLock` only reclaims a dead PID. It blocked legitimate
|
|
48
|
+
work twice in a single day of real use. `withAssetMutationLease` is **kept**
|
|
49
|
+
(it guards authored, git-backed asset writes) but its identical 12h
|
|
50
|
+
age-based stale reclaim is gone; only a verifiably-dead holder is reclaimed.
|
|
51
|
+
- **`semantic-status.ts` and its cached `blocked` verdict (#873).** A failed
|
|
52
|
+
probe was persisted with a 24h TTL, and search consulted that verdict
|
|
53
|
+
*before attempting semantic search at all* — so one failure silently
|
|
54
|
+
disabled a working feature for a day. Semantic search now attempts per query
|
|
55
|
+
and falls back to FTS with a live warning. The remaining pre-flight check is
|
|
56
|
+
a real-time embedding count, not a stored judgment.
|
|
57
|
+
- **The improve-lock 4h stale reclaim.** Same hole: `--skip-if-locked`
|
|
58
|
+
silently no-oped nightly `improve` for up to four hours and reported success.
|
|
59
|
+
- **The persisted `supportsJsonSchema` capability cache**, which was never
|
|
60
|
+
invalidated by `akm config set`; a stale `true` sent `response_format:
|
|
61
|
+
json_schema` to an incompatible endpoint with no fallback (`isRetryable`
|
|
62
|
+
excludes 4xx). Replaced by attempt-then-fallback held in memory for the
|
|
63
|
+
process only. The config field survives as an explicit user override.
|
|
64
|
+
- **Workflow authoring resource caps** — steps, params, route branches,
|
|
65
|
+
inputs, outputs, gate loops, retries, JSON depth/node, composition depth,
|
|
66
|
+
and exec argv/env caps. None ever fired; several duplicated OS limits.
|
|
67
|
+
- **`LIFETIME_UNIT_CAP` (10,000)**, which hard-aborted workflows mid-run. The
|
|
68
|
+
maximum ever observed was 14 units.
|
|
69
|
+
- **`state.db` open-path identity re-verification**, which ran on *every*
|
|
70
|
+
command and threw a bare `Error`.
|
|
71
|
+
- **The task-source 1 MiB cap** at four sites, restating the `guarded-source`
|
|
72
|
+
cap already deleted in 0.9.5.
|
|
73
|
+
- **TOCTOU identity checks** — `assertGitPublicationIdentity` (git's
|
|
74
|
+
`--force-with-lease` already covers it), `assertFrozenDirectoryIdentity`
|
|
75
|
+
(replaced with a path-containment recheck: containment and resolved-path
|
|
76
|
+
identity stay, device/inode comparison goes, so a remount or container
|
|
77
|
+
rebuild no longer aborts a dispatch), `assertTaskSourceExpectation`'s stat
|
|
78
|
+
fields (its content hash stays), and redundant repeat HEAD-generation
|
|
79
|
+
compares in `write-source`.
|
|
80
|
+
- **`assertNotFlag`** on persona/system-prompt content; **`MAX_ENV_BYTES`**,
|
|
81
|
+
**`MAX_SECRET_BYTES`**, **`MAX_FEEDBACK_TAGS`**, **`MAX_CONFIG_FILE_BYTES`**,
|
|
82
|
+
model-map JSON budgets, memory-contradiction family caps, and two launchd /
|
|
83
|
+
schtasks re-checks that duplicated an existing fallback. `map.concurrency`
|
|
84
|
+
now clamps instead of rejecting a human-authored value.
|
|
85
|
+
- **17 dead symbols**, including several whose docstrings described behavior
|
|
86
|
+
nothing implemented. Three "canonical" constants that call sites were
|
|
87
|
+
ignoring in favour of hardcoded literals were **wired in** rather than
|
|
88
|
+
deleted, closing the drift instead of removing the evidence of it.
|
|
89
|
+
- **Speculative flexibility** — `AKM_ABLATE_CONTRIBUTORS` ablation plumbing
|
|
90
|
+
and the unexercised `graphBoost.confidenceMode` branches.
|
|
91
|
+
|
|
92
|
+
### Changed
|
|
93
|
+
|
|
94
|
+
- **Drifted duplicate implementations consolidated.** HTTP retry/backoff (the
|
|
95
|
+
generic copy's `Retry-After` parsing was unbounded and numeric-only; the
|
|
96
|
+
capped, date-aware one now applies everywhere), the child-process env
|
|
97
|
+
allowlist (the opencode-sdk copy was missing `AKM_EVENT_SOURCE` and the
|
|
98
|
+
Windows HOME equivalents), portable synchronous sleep (five call sites had
|
|
99
|
+
been silently taking the Node fallback instead of the Bun fast path), a
|
|
100
|
+
stacked LLM chunk retry, and a duplicate `isProcessAlive`.
|
|
101
|
+
|
|
102
|
+
### Kept, deliberately
|
|
103
|
+
|
|
104
|
+
Not everything unused is disposable. `isVecFastPathReady` stays: a partial
|
|
105
|
+
`entries_vec` table does not throw, it silently returns wrong neighbours, so
|
|
106
|
+
there is no error for a fallback to catch. `assertSupportedKind` stays: it has
|
|
107
|
+
a proven independent bypass path and is the real last-line check, not a
|
|
108
|
+
duplicate. The maintenance barrier and registry TTL cache stay. The four
|
|
109
|
+
improve strategies with zero recorded invocations stay — that measures one
|
|
110
|
+
install's cron schedule, not their worth.
|
|
111
|
+
|
|
112
|
+
## [0.9.5] - 2026-08-30
|
|
113
|
+
|
|
114
|
+
### Action required after upgrading
|
|
115
|
+
|
|
116
|
+
- **Run `akm index --full` once (#862).** Search ranking previously counted
|
|
117
|
+
every search *hit* as a small win for that entry's utility score, whether
|
|
118
|
+
or not you ever opened it — a plain impression, not a selection. Over
|
|
119
|
+
enough repeat searches this compounded into a real feedback loop: appear
|
|
120
|
+
in results -> score goes up -> rank higher next time -> appear again. On
|
|
121
|
+
the install this was diagnosed against, one entry had been searched 19
|
|
122
|
+
times, opened 0 times, and still carried a utility score of 0.83 — on par
|
|
123
|
+
with entries a user had actually picked every time; the single
|
|
124
|
+
highest-utility entry in the whole table had an 8% select rate. That bias
|
|
125
|
+
is baked into every existing install's stored scores and does not go away
|
|
126
|
+
on its own — the live bump has been removed (search impressions no longer
|
|
127
|
+
write utility scores at all; only an actual `akm show`/select or explicit
|
|
128
|
+
`akm feedback` feeds the offline recompute now), but the already-inflated
|
|
129
|
+
numbers stay in `utility_scores`/`utility_scores_scoped` until you
|
|
130
|
+
recompute them. Run `akm index --full` once after upgrading to rebuild
|
|
131
|
+
scores purely from selection rate and feedback. **Search result order will
|
|
132
|
+
change after that rebuild — that is intended**, not a regression.
|
|
133
|
+
- **The first `akm task sync` after upgrading may report a large number of
|
|
134
|
+
updates.** This is a backlog of reconciles that were being silently
|
|
135
|
+
refused, not new changes to your tasks. Two independent bugs combined to
|
|
136
|
+
make `sync` refuse work it should have done: (1) an installed scheduler
|
|
137
|
+
entry written by a pre-`--bundle` akm release couldn't prove ownership
|
|
138
|
+
under the newer, stricter check and was reported `(unproven owner)`,
|
|
139
|
+
which made `sync` refuse the *entire* run rather than reconcile everything
|
|
140
|
+
else; (2) separately, one task or workflow source that failed to compile
|
|
141
|
+
(e.g. a task still on schema v2 in a shape the shim can't convert) also
|
|
142
|
+
blocked every other, unrelated task from reconciling. Both are fixed —
|
|
143
|
+
ownership is now re-derived from the entry's own akm markers instead of
|
|
144
|
+
requiring a literal `--bundle` token, and a source that fails to compile
|
|
145
|
+
is now excluded and reported in `failed`/`failures` while every source
|
|
146
|
+
that DID compile still reconciles. On the install this was diagnosed
|
|
147
|
+
against, all 18 scheduled tasks showed up as updates on the first `sync`
|
|
148
|
+
after the fix — that is the backlog, not a sign your tasks changed.
|
|
149
|
+
|
|
150
|
+
### Added
|
|
151
|
+
|
|
152
|
+
- **`akm task prune` reclaims orphaned scheduler entries `sync` cannot reach
|
|
153
|
+
(#851).** `akm task sync` only ever reconciles entries that resolve to a
|
|
154
|
+
desired task/workflow definition in a live bundle; an entry whose own
|
|
155
|
+
`--scheduler-context` descriptor is corrupt/missing, or whose owning
|
|
156
|
+
bundle directory has since been deleted, was permanently invisible to it
|
|
157
|
+
and had to be removed by hand-editing the crontab/launchd plist/Task
|
|
158
|
+
Scheduler. `akm task prune` finds those and nothing else: it never
|
|
159
|
+
touches an entry that still resolves to a live bundle, and there is no
|
|
160
|
+
`--force`/"remove everything silently" mode. Defaults to a dry-run
|
|
161
|
+
preview that makes zero scheduler writes and exits non-zero when it finds
|
|
162
|
+
removal candidates (usable as a CI/health-check guard, mirroring `task
|
|
163
|
+
sync --dry-run`'s exit-code convention); `--yes` executes the printed
|
|
164
|
+
plan; `--id a,b` narrows a run to specific binding ids and refuses (with
|
|
165
|
+
no writes) any id that isn't a current orphan candidate — including a
|
|
166
|
+
live entry, which cannot be pruned even if you name it explicitly.
|
|
167
|
+
- **`configVersion` read shim (#863).** Config loading now tolerates a
|
|
168
|
+
known older `configVersion` by upgrading the parsed document in memory
|
|
169
|
+
(never rewriting the file), with a one-line stderr warning naming the old
|
|
170
|
+
and new versions. The upgrade is silenced the next time any command
|
|
171
|
+
writes the config (`akm config set`, etc.), since every config write
|
|
172
|
+
already stamps the current version. Anything else — unknown, newer, or
|
|
173
|
+
malformed `configVersion` — still fails closed with the same actionable
|
|
174
|
+
error as before. No current release needed this shim yet (akm has only
|
|
175
|
+
ever shipped `configVersion: "0.9.0"`); it's in place now so the next
|
|
176
|
+
real bump doesn't break every existing config on upgrade.
|
|
177
|
+
- **Truncated indexed content is no longer silent.** The two indexer caps
|
|
178
|
+
that truncate an entry's content before it's written to the search index
|
|
179
|
+
(a markdown-body cap and a search-text cap) previously truncated without
|
|
180
|
+
any signal. Indexed entries now carry a `contentTruncated: true` flag
|
|
181
|
+
when either cap fired, and truncation logs a `--verbose` diagnostic
|
|
182
|
+
naming the entry, so an unexpectedly-worse search match on a very large
|
|
183
|
+
file has a visible cause instead of a silent one. The cap sizes
|
|
184
|
+
themselves are unchanged.
|
|
185
|
+
|
|
186
|
+
### Fixed
|
|
187
|
+
|
|
188
|
+
- **`akm health --report` and `akm proposal list --status accepted` no
|
|
189
|
+
longer crash, and `improve`'s accepted-proposal counts are no longer
|
|
190
|
+
silently zero, on proposals written before every optional envelope field
|
|
191
|
+
existed (#859).** A prior fix already tolerated a missing `changes` key;
|
|
192
|
+
this closes the same gap for `proposedTarget`, which was still hard-required
|
|
193
|
+
at decode and — on the real archive this was checked against — absent
|
|
194
|
+
from 93% of accepted rows. Decoding a proposal now tolerates a genuinely
|
|
195
|
+
*absent* `proposedTarget` (accept-time resolution already had a
|
|
196
|
+
ref-derived fallback for this case, previously unreachable because decode
|
|
197
|
+
threw first); a *present but malformed* value still throws, so real
|
|
198
|
+
corruption is never silently accepted. Writing a new (`pending`) proposal
|
|
199
|
+
still requires the full envelope — this only widens what can be *read*,
|
|
200
|
+
not what akm will *write*.
|
|
201
|
+
- **`akm task sync` no longer refuses to reconcile an entry it can't fully
|
|
202
|
+
re-prove ownership of, and no longer lets one broken task/workflow source
|
|
203
|
+
block every other source (#867, plus the scheduler-ownership fix
|
|
204
|
+
described above).** The v2 task-source shim also no longer hard-fails a
|
|
205
|
+
command wrapped in `env NAME=value... cmd`, a shape that's common for
|
|
206
|
+
cron entries (e.g. `env AKM_BIN=/path/akm bash script.sh`) — the shim now
|
|
207
|
+
looks past a leading `env` and its assignments to the real command before
|
|
208
|
+
deciding whether the conversion to v3/v4 is safe, instead of checking
|
|
209
|
+
`env` itself.
|
|
210
|
+
- **`akm task sync --dry-run` no longer crashes on a real install with a
|
|
211
|
+
pre-`--bundle` cron entry.** The reconcile fix above made those entries
|
|
212
|
+
reachable for the first time, and the dry-run preview's frozen result
|
|
213
|
+
object was then mutated in place while stamping output metadata, throwing
|
|
214
|
+
"Attempting to define property on object that is not extensible" (exit
|
|
215
|
+
70). Output stamping now copies instead of mutating, so it tolerates a
|
|
216
|
+
frozen (or any) result uniformly.
|
|
217
|
+
- **`akm migrate apply` no longer aborts an entire batch because one file in
|
|
218
|
+
it is blocked (#866).** The task v2->v3 and v3->v4 migrators refused to
|
|
219
|
+
write *any* file — including files with no problem at all — the moment a
|
|
220
|
+
single file in the batch was classified `blocked`. Both migrators now
|
|
221
|
+
skip and report the blocked file(s) and still migrate everything else;
|
|
222
|
+
the run still exits non-zero whenever anything was skipped.
|
|
223
|
+
- Two flaky integration/unit tests fixed at the root cause rather than
|
|
224
|
+
quarantined (#864): a `state.db` byte-identity assertion that raced
|
|
225
|
+
SQLite's own WAL checkpoint timing (now forces a checkpoint before
|
|
226
|
+
comparing, so the assertion reflects real durable mutations only), and a
|
|
227
|
+
"resolveProjectContext when cwd is the home directory" test that did
|
|
228
|
+
real, un-mocked filesystem walks against the actual `$HOME` (now isolates
|
|
229
|
+
`os.homedir()` and the stash/XDG dirs like the rest of the suite).
|
|
230
|
+
|
|
231
|
+
### Changed
|
|
232
|
+
|
|
233
|
+
- **`test:integration` is green-by-default (#861).** Verified there is no
|
|
234
|
+
CI mechanism silently swallowing a failing test (no `continue-on-error`,
|
|
235
|
+
no lost exit codes, no allowlist of "expected" failures) — the fixes in
|
|
236
|
+
this release were the actual cause of the prior red baseline. Raised the
|
|
237
|
+
integration suite's minimum-test-count floor (`AKM_MIN_INTEGRATION_TESTS`,
|
|
238
|
+
5500 -> 5700) so a large silent test loss is still caught with headroom
|
|
239
|
+
for ordinary future deletions, and documented in `AGENTS.md` that `TMPDIR`
|
|
240
|
+
must be a real `/tmp`-family path for the suite to pass — some guards
|
|
241
|
+
(stash-path safety, `akm-eval`'s Docker twin) intentionally hardcode that
|
|
242
|
+
assumption rather than reading `TMPDIR`.
|
|
243
|
+
- Simplified several areas flagged in the #866 complexity review without
|
|
244
|
+
behavior changes: `improve_runs.result_json`'s `schemaVersion` decoding is
|
|
245
|
+
now an explicit, extensible table instead of one large inline
|
|
246
|
+
conditional, ready for a future schema bump to add a case rather than
|
|
247
|
+
restructure the function.
|
|
248
|
+
|
|
249
|
+
### Removed
|
|
250
|
+
|
|
251
|
+
- **Three defensive checks that only ever refused work a human explicitly
|
|
252
|
+
asked for, with no evidence (via telemetry) they ever caught a real
|
|
253
|
+
problem, were removed:**
|
|
254
|
+
- The 1 MiB hard cap on reading a bundle file (command/agent/script/task/
|
|
255
|
+
workflow source, or a frozen workflow secret/env source) into memory.
|
|
256
|
+
Reads remain exact and integrity-checked (hashed, CAS-verified, fail-
|
|
257
|
+
closed on read-time mutation) — there is simply no longer a size ceiling
|
|
258
|
+
that refuses to read a file you put in your own bundle.
|
|
259
|
+
- The task migrator's inode/hard-link-count/change-time identity
|
|
260
|
+
fencing on top of its existing lockfile + backup-before-write +
|
|
261
|
+
byte-for-byte drift check. The extra fencing modeled a multi-tenant
|
|
262
|
+
race that doesn't apply to a single-user CLI holding an exclusive lock,
|
|
263
|
+
and its failure mode (refusing to migrate a file that's byte-identical
|
|
264
|
+
to what was already previewed, or refusing to touch a file merely
|
|
265
|
+
because it has a hard link elsewhere) was worse than the risk it
|
|
266
|
+
guarded against. Symlink/path-escape rejection — a real hazard — is
|
|
267
|
+
unchanged.
|
|
268
|
+
- Dead anti-collapse "hard refusal" guard functions
|
|
269
|
+
(`checkGenerationGuard`, `checkMergeInformationFloor`) that had zero
|
|
270
|
+
production callers.
|
|
271
|
+
|
|
7
272
|
## [0.9.4] - 2026-08-30
|
|
8
273
|
|
|
9
274
|
### Changed
|
|
@@ -127,8 +127,7 @@ const envCreateCommand = defineJsonCommand({
|
|
|
127
127
|
content = fs.readFileSync(fromFile, "utf8");
|
|
128
128
|
}
|
|
129
129
|
else {
|
|
130
|
-
const
|
|
131
|
-
const buf = await readStdin(MAX_ENV_BYTES, () => new UsageError("Env file exceeds 1 MB limit.", "INVALID_FLAG_VALUE"));
|
|
130
|
+
const buf = await readStdin();
|
|
132
131
|
content = buf.toString("utf8");
|
|
133
132
|
}
|
|
134
133
|
}
|
|
@@ -122,16 +122,12 @@ const secretSetCommand = defineJsonCommand({
|
|
|
122
122
|
if (fromEnv !== undefined && fromFile !== undefined) {
|
|
123
123
|
throw new UsageError("Pass only one of --from-file or --from-env (or use stdin).", "INVALID_FLAG_VALUE");
|
|
124
124
|
}
|
|
125
|
-
const MAX_SECRET_BYTES = 5 * 1024 * 1024; // 5 MB
|
|
126
125
|
let value;
|
|
127
126
|
if (fromFile !== undefined) {
|
|
128
127
|
if (!fs.existsSync(fromFile)) {
|
|
129
128
|
throw new NotFoundError(`File not found: ${fromFile}`, "FILE_NOT_FOUND");
|
|
130
129
|
}
|
|
131
130
|
value = fs.readFileSync(fromFile);
|
|
132
|
-
if (value.byteLength > MAX_SECRET_BYTES) {
|
|
133
|
-
throw new UsageError("Secret exceeds the 5 MB limit.");
|
|
134
|
-
}
|
|
135
131
|
}
|
|
136
132
|
else if (fromEnv !== undefined) {
|
|
137
133
|
const envVal = process.env[fromEnv];
|
|
@@ -144,7 +140,7 @@ const secretSetCommand = defineJsonCommand({
|
|
|
144
140
|
if (process.stdin.isTTY) {
|
|
145
141
|
process.stderr.write(`Enter value for secret "${name}" (Ctrl-D when done):\n`);
|
|
146
142
|
}
|
|
147
|
-
const stdinBuf = await readStdin(
|
|
143
|
+
const stdinBuf = await readStdin();
|
|
148
144
|
// Strip a single trailing newline so `echo "$TOKEN" | akm secret set`
|
|
149
145
|
// stores the token without the shell-added newline. Use --from-file for
|
|
150
146
|
// byte-exact storage of multi-line material (PEM keys, certs).
|
|
@@ -25,7 +25,6 @@ import { findEntryIdByRef, getEntryFilePathById, getItemRefById, } from "../stor
|
|
|
25
25
|
import { applyFeedbackToUtilityScore } from "../storage/repositories/index-utility-repository.js";
|
|
26
26
|
// ── Tag validation ────────────────────────────────────────────────────────────
|
|
27
27
|
const TAG_KEY_RE = /^[a-z_][a-z0-9_]*$/;
|
|
28
|
-
const MAX_FEEDBACK_TAGS = 10;
|
|
29
28
|
function validateFeedbackTags(raw) {
|
|
30
29
|
const seen = new Set();
|
|
31
30
|
const out = [];
|
|
@@ -43,9 +42,6 @@ function validateFeedbackTags(raw) {
|
|
|
43
42
|
seen.add(tag);
|
|
44
43
|
out.push(tag);
|
|
45
44
|
}
|
|
46
|
-
if (out.length > MAX_FEEDBACK_TAGS) {
|
|
47
|
-
throw new UsageError(`Too many tags: ${out.length}. Maximum is ${MAX_FEEDBACK_TAGS}.`, "INVALID_FLAG_VALUE");
|
|
48
|
-
}
|
|
49
45
|
return out;
|
|
50
46
|
}
|
|
51
47
|
// ── Lesson strength helper ────────────────────────────────────────────────────
|
|
@@ -607,38 +607,6 @@ export const HEALTH_CHECKS = [
|
|
|
607
607
|
};
|
|
608
608
|
},
|
|
609
609
|
},
|
|
610
|
-
{
|
|
611
|
-
name: "semantic-search-runtime",
|
|
612
|
-
channel: "advisory",
|
|
613
|
-
run: (ctx) => {
|
|
614
|
-
const blocked = ctx.semanticStatus?.status === "blocked";
|
|
615
|
-
// The generic "status: blocked" line is not actionable when the real
|
|
616
|
-
// problem is a configured remote embedding endpoint that is down while
|
|
617
|
-
// semanticSearchMode leaves semantic search enabled — every index run
|
|
618
|
-
// burns time failing against it and searches silently degrade to
|
|
619
|
-
// keyword-only. Name the endpoint and the two ways out.
|
|
620
|
-
const remoteReason = ctx.semanticStatus?.reason?.startsWith("remote-") === true;
|
|
621
|
-
const endpointAdvisory = blocked && remoteReason && ctx.embeddingEndpoint
|
|
622
|
-
? `Configured embedding endpoint ${ctx.embeddingEndpoint} is failing ` +
|
|
623
|
-
`(${ctx.semanticStatus?.reason}${ctx.semanticStatus?.message ? `: ${ctx.semanticStatus.message}` : ""}) ` +
|
|
624
|
-
`while semanticSearchMode is "${ctx.semanticSearchMode ?? "off"}". Searches fall back to keyword-only. ` +
|
|
625
|
-
`Restore the endpoint, or set semanticSearchMode to "off" (or remove embedding.endpoint to use the local model).`
|
|
626
|
-
: undefined;
|
|
627
|
-
return {
|
|
628
|
-
name: "semantic-search-runtime",
|
|
629
|
-
kind: "deterministic",
|
|
630
|
-
status: !ctx.semanticStatus || !blocked ? "pass" : "warn",
|
|
631
|
-
confidence: "medium",
|
|
632
|
-
message: endpointAdvisory ??
|
|
633
|
-
(ctx.semanticStatus
|
|
634
|
-
? `Semantic search status: ${ctx.semanticStatus.status}`
|
|
635
|
-
: "No semantic-search runtime status recorded yet."),
|
|
636
|
-
evidence: ctx.semanticStatus
|
|
637
|
-
? { ...ctx.semanticStatus, ...(ctx.embeddingEndpoint ? { embeddingEndpoint: ctx.embeddingEndpoint } : {}) }
|
|
638
|
-
: undefined,
|
|
639
|
-
};
|
|
640
|
-
},
|
|
641
|
-
},
|
|
642
610
|
{
|
|
643
611
|
name: "session-extraction",
|
|
644
612
|
channel: "advisory",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* the stash-exposure pattern. `egress-endpoints` is the one informational
|
|
15
15
|
* (pass-status) entry: it emits whenever any remote endpoint is configured.
|
|
16
16
|
*/
|
|
17
|
-
import {
|
|
17
|
+
import { readTextFile } from "../../core/common.js";
|
|
18
18
|
import { CURRENT_CONFIG_VERSION } from "../../core/config/config-schema.js";
|
|
19
19
|
import { compareConfigVersion } from "../../core/config/config-version.js";
|
|
20
20
|
import { formatRegistryUrl } from "../../core/registry-url.js";
|
|
@@ -28,7 +28,7 @@ import { formatRegistryUrl } from "../../core/registry-url.js";
|
|
|
28
28
|
export function collectConfigSkewAdvisory(configPath) {
|
|
29
29
|
let raw;
|
|
30
30
|
try {
|
|
31
|
-
raw = JSON.parse(
|
|
31
|
+
raw = JSON.parse(readTextFile(configPath, "Config file"));
|
|
32
32
|
}
|
|
33
33
|
catch {
|
|
34
34
|
return undefined;
|
package/dist/commands/health.js
CHANGED
|
@@ -13,7 +13,6 @@ import { classifyPathAccess, describeInaccessiblePath } from "../core/path-acces
|
|
|
13
13
|
import { getConfigPath, getDataDir, getDbPath, getStateDbPathInDataDir } from "../core/paths.js";
|
|
14
14
|
import { listExistingTableNames, openStateDatabase } from "../core/state-db.js";
|
|
15
15
|
import { DURATION_UNITS, parseDuration, parseSinceToIso } from "../core/time.js";
|
|
16
|
-
import { readSemanticStatus } from "../indexer/search/semantic-status.js";
|
|
17
16
|
import { closeDatabase, openReadonlyExistingDatabase } from "../storage/repositories/index-connection.js";
|
|
18
17
|
import { getAllEntries } from "../storage/repositories/index-entries-repository.js";
|
|
19
18
|
import { queryTaskHistory } from "../storage/repositories/task-history-repository.js";
|
|
@@ -183,25 +182,20 @@ function gatherStaleTxnJournalsPhase(now) {
|
|
|
183
182
|
}
|
|
184
183
|
}
|
|
185
184
|
/**
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
185
|
+
* Config fields the surfaces advisory needs. Best-effort: an unloadable
|
|
186
|
+
* config leaves the field undefined and the caller falls back to a generic
|
|
187
|
+
* message.
|
|
189
188
|
*/
|
|
190
|
-
function
|
|
191
|
-
const semanticStatus = readSemanticStatus();
|
|
192
|
-
let semanticSearchMode;
|
|
193
|
-
let embeddingEndpoint;
|
|
189
|
+
function gatherEgressConfigPhase() {
|
|
194
190
|
let egressConfigView;
|
|
195
191
|
try {
|
|
196
192
|
const config = loadConfig();
|
|
197
|
-
semanticSearchMode = config.semanticSearchMode;
|
|
198
|
-
embeddingEndpoint = config.embedding?.endpoint;
|
|
199
193
|
egressConfigView = config;
|
|
200
194
|
}
|
|
201
195
|
catch {
|
|
202
196
|
// fall through with undefined
|
|
203
197
|
}
|
|
204
|
-
return {
|
|
198
|
+
return { egressConfigView };
|
|
205
199
|
}
|
|
206
200
|
/**
|
|
207
201
|
* Assemble the window's improve-pipeline summary: invoked/completed/skipped
|
|
@@ -519,7 +513,7 @@ export function akmHealth(options = {}) {
|
|
|
519
513
|
const taskHistory = gatherTaskHistoryPhase(db, logsDb, since, stateDbPath, now);
|
|
520
514
|
const { tableNames, missingTables, probe } = taskHistory;
|
|
521
515
|
const staleTxnJournals = gatherStaleTxnJournalsPhase(now);
|
|
522
|
-
const {
|
|
516
|
+
const { egressConfigView } = gatherEgressConfigPhase();
|
|
523
517
|
const { improveSummary } = gatherImproveSummaryPhase(db, stateDbPath, since, now);
|
|
524
518
|
advisories.push(...gatherAncillaryAdvisories(db, stateDbPath, since, improveSummary, options, egressConfigView));
|
|
525
519
|
const engineProbes = runHealthEngineProbes();
|
|
@@ -542,9 +536,6 @@ export function akmHealth(options = {}) {
|
|
|
542
536
|
stuckActiveTasks: taskHistory.stuckActiveTasks,
|
|
543
537
|
worstTaskFailRate: taskHistory.worstTaskFailRate,
|
|
544
538
|
staleTxnJournals,
|
|
545
|
-
semanticStatus,
|
|
546
|
-
semanticSearchMode,
|
|
547
|
-
embeddingEndpoint,
|
|
548
539
|
sessionExtraction: improveSummary.sessionExtraction,
|
|
549
540
|
autoAccept: improveSummary.autoAccept,
|
|
550
541
|
engineProbes,
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
/**
|
|
5
5
|
* WS-3b Step 8 — Anti-collapse merge guards.
|
|
6
6
|
*
|
|
7
|
-
* (a) Generation counter: merged.generation = max(sources)+1;
|
|
8
|
-
*
|
|
7
|
+
* (a) Generation counter: merged.generation = max(sources)+1; merges cite
|
|
8
|
+
* sources. `over_generation_count` (collapse-detector.ts) tracks assets
|
|
9
|
+
* above the generation threshold as an advisory metric only — there is
|
|
10
|
+
* no merge-refusal path wired in.
|
|
9
11
|
* (b) Lexical-diversity check: low n-gram diversity ⇒ raise merge threshold.
|
|
10
|
-
* (c) Merge-information floor (R5 §4.2): provenance union must not shrink and
|
|
11
|
-
* the merged body must retain a minimum fraction of the source tokens.
|
|
12
12
|
* (d) Occasional random non-similar cluster in the pool.
|
|
13
13
|
*
|
|
14
14
|
* @module anti-collapse
|
|
@@ -37,93 +37,6 @@ export function computeMergedGeneration(sourceGenerations) {
|
|
|
37
37
|
return 1;
|
|
38
38
|
return Math.max(...sourceGenerations) + 1;
|
|
39
39
|
}
|
|
40
|
-
/**
|
|
41
|
-
* Check whether a merge of the given assets should be refused due to the
|
|
42
|
-
* anti-collapse generation guard.
|
|
43
|
-
*
|
|
44
|
-
* Returns `{ refused: true, reason }` when BOTH assets have generation > maxGeneration.
|
|
45
|
-
* Returns `{ refused: false }` when the merge is allowed.
|
|
46
|
-
*
|
|
47
|
-
* @param sourceGenerations - Generation values for all merge participants.
|
|
48
|
-
* @param config - Anti-collapse config.
|
|
49
|
-
*/
|
|
50
|
-
export function checkGenerationGuard(sourceGenerations, config) {
|
|
51
|
-
// R5: default ON — only an explicit opt-out disables the guard.
|
|
52
|
-
if (config.enabled === false)
|
|
53
|
-
return { refused: false };
|
|
54
|
-
const maxGen = config.maxGeneration ?? DEFAULT_MAX_GENERATION;
|
|
55
|
-
const highGenCount = sourceGenerations.filter((g) => g > maxGen).length;
|
|
56
|
-
if (highGenCount >= 2) {
|
|
57
|
-
return {
|
|
58
|
-
refused: true,
|
|
59
|
-
reason: `Anti-collapse: ${highGenCount} merge participants have generation > ${maxGen} (${sourceGenerations.join(", ")}); refusing to merge over-consolidated assets.`,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
return { refused: false };
|
|
63
|
-
}
|
|
64
|
-
/** Distinct-token retention floor default (R5 §4.2). */
|
|
65
|
-
export const DEFAULT_MIN_SPECIFICITY_RETENTION = 0.6;
|
|
66
|
-
function distinctTokens(text) {
|
|
67
|
-
// Same lowercase whitespace tokenization computeBigramDiversity uses.
|
|
68
|
-
return new Set(text
|
|
69
|
-
.toLowerCase()
|
|
70
|
-
.split(/\s+/)
|
|
71
|
-
.filter((w) => w.length > 0));
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* A merge must strictly increase information (R5 §4.2):
|
|
75
|
-
* 1. Provenance: the merged asset's `xrefs` must be a superset of the union of
|
|
76
|
-
* all participants' `xrefs` plus the participant refs
|
|
77
|
-
* themselves — provenance never shrinks through a merge.
|
|
78
|
-
* 2. Specificity: distinctTokens(mergedBody) ≥ minSpecificityRetention ×
|
|
79
|
-
* |union(distinctTokens(participant bodies))| — a merge that only
|
|
80
|
-
* shortens/genericizes fails.
|
|
81
|
-
*
|
|
82
|
-
* Pure and deterministic; ADVISORY in v1 (the caller counts violations, it
|
|
83
|
-
* does not refuse the merge). Returns `passed: true` immediately when the
|
|
84
|
-
* anti-collapse suite or the floor itself is opted out.
|
|
85
|
-
*/
|
|
86
|
-
export function checkMergeInformationFloor(mergedBody, mergedSourceRefs, participants, config) {
|
|
87
|
-
if (config.enabled === false || config.mergeInformationFloor === false || participants.length === 0) {
|
|
88
|
-
return { passed: true, provenanceBefore: 0, provenanceAfter: 0, specificityRetention: 1 };
|
|
89
|
-
}
|
|
90
|
-
// 1. Provenance union: participants + everything they already cited.
|
|
91
|
-
const required = new Set();
|
|
92
|
-
for (const p of participants) {
|
|
93
|
-
required.add(p.ref);
|
|
94
|
-
for (const xref of p.xrefs)
|
|
95
|
-
required.add(xref);
|
|
96
|
-
}
|
|
97
|
-
const after = new Set(mergedSourceRefs);
|
|
98
|
-
const missing = [...required].filter((r) => !after.has(r));
|
|
99
|
-
// 2. Specificity retention over the union of source tokens.
|
|
100
|
-
const sourceTokens = new Set();
|
|
101
|
-
for (const p of participants) {
|
|
102
|
-
for (const t of distinctTokens(p.body))
|
|
103
|
-
sourceTokens.add(t);
|
|
104
|
-
}
|
|
105
|
-
const mergedTokens = distinctTokens(mergedBody);
|
|
106
|
-
// Clamped at computation so the pass/fail decision, the reason string, and
|
|
107
|
-
// the reported field all describe the same value.
|
|
108
|
-
const specificityRetention = Math.min(1, sourceTokens.size === 0 ? 1 : mergedTokens.size / sourceTokens.size);
|
|
109
|
-
const minRetention = config.minSpecificityRetention ?? DEFAULT_MIN_SPECIFICITY_RETENTION;
|
|
110
|
-
const provenanceOk = missing.length === 0;
|
|
111
|
-
const specificityOk = specificityRetention >= minRetention;
|
|
112
|
-
const reasons = [];
|
|
113
|
-
if (!provenanceOk) {
|
|
114
|
-
reasons.push(`provenance shrank: merged xrefs missing ${missing.length} ref(s) (e.g. ${missing[0]})`);
|
|
115
|
-
}
|
|
116
|
-
if (!specificityOk) {
|
|
117
|
-
reasons.push(`specificity retention ${specificityRetention.toFixed(2)} < ${minRetention} (merge genericized/shortened)`);
|
|
118
|
-
}
|
|
119
|
-
return {
|
|
120
|
-
passed: provenanceOk && specificityOk,
|
|
121
|
-
provenanceBefore: required.size,
|
|
122
|
-
provenanceAfter: after.size,
|
|
123
|
-
specificityRetention,
|
|
124
|
-
...(reasons.length > 0 ? { reason: reasons.join("; ") } : {}),
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
40
|
/**
|
|
128
41
|
* Compute the bigram n-gram diversity of a text string.
|
|
129
42
|
* Returns a value in [0, 1] where 0 = all identical bigrams, 1 = all unique.
|
|
@@ -29,7 +29,7 @@ export function describeGatedLanes(lanes) {
|
|
|
29
29
|
}
|
|
30
30
|
/** Configured capabilities that tasks doctor reports behind the autonomy gate. */
|
|
31
31
|
export function configuredDirectAutonomyLanes() {
|
|
32
|
-
return [
|
|
32
|
+
return [...DIRECT_AUTONOMY_LANES];
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
35
|
* True when a lane may mutate. Used by the lane that bypasses the strategy
|
|
@@ -27,6 +27,7 @@ import { findEntryIdByRef, getAllEntries, getEntryById } from "../../storage/rep
|
|
|
27
27
|
import { getNeighborsByEntryId } from "../../storage/repositories/index-vec-repository.js";
|
|
28
28
|
import { isProposalSkipped, listProposals, listProposalsReadOnly, proposalContent, } from "../proposal/repository.js";
|
|
29
29
|
import { hasSupersededStatus, validateProposalFrontmatter } from "../proposal/validators/proposal-quality-validators.js";
|
|
30
|
+
import { DEFAULT_RANDOM_CLUSTER_FRACTION } from "./anti-collapse.js";
|
|
30
31
|
import { cacheHash } from "./content-hash.js";
|
|
31
32
|
import { resolveImproveLlmExecution } from "./execution.js";
|
|
32
33
|
import { resolveImproveStrategy, resolveProcessEnabled } from "./improve-strategies.js";
|
|
@@ -190,14 +191,15 @@ async function clusterMemoriesBySimilarity(memories, config, stateDb, signal) {
|
|
|
190
191
|
finally {
|
|
191
192
|
embedMs += Date.now() - embedStart;
|
|
192
193
|
}
|
|
193
|
-
// Upsert newly computed vectors into the cache.
|
|
194
|
+
// Upsert newly computed vectors into the cache. A skipped document
|
|
195
|
+
// (embedBatch reports it via `undefined` rather than throwing, #874) has
|
|
196
|
+
// no vector to cache — omit it rather than writing a bogus embedding.
|
|
194
197
|
if (stateDb && missVecs.length === missTexts.length) {
|
|
195
198
|
try {
|
|
196
|
-
const toUpsert = missIndices.
|
|
197
|
-
|
|
198
|
-
embedding:
|
|
199
|
-
|
|
200
|
-
}));
|
|
199
|
+
const toUpsert = missIndices.flatMap((idx, pos) => {
|
|
200
|
+
const embedding = missVecs[pos];
|
|
201
|
+
return embedding ? [{ contentHash: contentHashes[idx], embedding, modelId }] : [];
|
|
202
|
+
});
|
|
201
203
|
upsertBodyEmbeddings(stateDb, toUpsert);
|
|
202
204
|
}
|
|
203
205
|
catch {
|
|
@@ -783,26 +785,23 @@ async function judgeConsolidationChunks(args) {
|
|
|
783
785
|
...(opts.onNotices ? { onNotices: opts.onNotices } : {}),
|
|
784
786
|
});
|
|
785
787
|
};
|
|
786
|
-
|
|
788
|
+
// callChunkLlm already retries once internally (llm/client.ts's
|
|
789
|
+
// chatCompletion, jittered 200-800ms backoff) — a second, outer retry
|
|
790
|
+
// here stacked an uncoordinated fixed 2s backoff on top of it. Removed;
|
|
791
|
+
// only mark the chunk failed once the single retry the client already
|
|
792
|
+
// performs has been exhausted.
|
|
793
|
+
const raw = await callChunkLlm(`chunk ${chunkIdx + 1} failed`);
|
|
787
794
|
if (!raw.ok) {
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
// Account for the chunk's memories under the failed-chunk bucket.
|
|
799
|
-
// judgedNoAction does NOT run on this path (it's after the success
|
|
800
|
-
// guards) so without this the accounting invariant breaks on every
|
|
801
|
-
// chunk-level transport/parse failure.
|
|
802
|
-
accounting.failedChunkMemories += chunk.length;
|
|
803
|
-
continue;
|
|
804
|
-
}
|
|
805
|
-
raw = retry;
|
|
795
|
+
warn(raw.error ?? `chunk ${chunkIdx + 1} failed`);
|
|
796
|
+
warnings.push(raw.error ?? `chunk ${chunkIdx + 1} failed`);
|
|
797
|
+
totalChunksProcessed++;
|
|
798
|
+
accounting.totalChunksFailed++;
|
|
799
|
+
// Account for the chunk's memories under the failed-chunk bucket.
|
|
800
|
+
// judgedNoAction does NOT run on this path (it's after the success
|
|
801
|
+
// guards) so without this the accounting invariant breaks on every
|
|
802
|
+
// chunk-level transport/parse failure.
|
|
803
|
+
accounting.failedChunkMemories += chunk.length;
|
|
804
|
+
continue;
|
|
806
805
|
}
|
|
807
806
|
// C9 action 1: AKM_DEBUG_LLM was a separate, undocumented env var for this
|
|
808
807
|
// one diagnostic; folded into the standard AKM_VERBOSE gate (warnVerbose)
|
|
@@ -922,7 +921,7 @@ async function planConsolidation(opts, config, stashDir, _startMs, memories, war
|
|
|
922
921
|
const antiCollapseForCluster = getImproveProcessConfig("consolidate", opts.improveProfile)?.antiCollapse ??
|
|
923
922
|
{};
|
|
924
923
|
if (antiCollapseForCluster.enabled !== false && clusteredMemories.length > 2) {
|
|
925
|
-
const fraction = antiCollapseForCluster.randomClusterFraction ??
|
|
924
|
+
const fraction = antiCollapseForCluster.randomClusterFraction ?? DEFAULT_RANDOM_CLUSTER_FRACTION;
|
|
926
925
|
const randomCount = Math.max(1, Math.floor(clusteredMemories.length * fraction));
|
|
927
926
|
// Pick `randomCount` positions to inject random (un-clustered) members.
|
|
928
927
|
// Use a seeded-ish shuffle: sort by hash of the name so it's deterministic
|