akm-cli 0.9.5 → 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 +105 -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/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/sources/bundle-config-ops.js +9 -4
- package/dist/commands/sources/info.js +14 -10
- package/dist/commands/tasks/tasks.js +12 -16
- package/dist/commands/workflow-cli.js +2 -2
- 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.js +1 -1
- 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/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/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/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/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/registry/network.js +5 -37
- package/dist/runtime.js +2 -10
- package/dist/scripts/akm-migrate-node.js +60 -126
- package/dist/scripts/akm-migrate.js +60 -126
- 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/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/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 +5 -10
- package/dist/tasks/source/task-to-v4.js +1 -4
- 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/configuration.md +1 -1
- 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,111 @@ 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
|
+
|
|
7
112
|
## [0.9.5] - 2026-08-30
|
|
8
113
|
|
|
9
114
|
### Action required after upgrading
|
|
@@ -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,
|
|
@@ -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
|
|
@@ -73,7 +73,7 @@ import { stripFrontmatterBody as stripBodyForFidelity } from "./content-hash.js"
|
|
|
73
73
|
import { autoRepairLessonFrontmatter, autoSwapDescriptionWhenToUse, collectLessonQualityFindings, repairLessonDescriptionTruncation, } from "./distill/content-repair.js";
|
|
74
74
|
import { memoryKnowledgePromotionRequiresDispatch, planMemoryKnowledgePromotion, promoteMemoryToKnowledge, } from "./distill/promote-memory.js";
|
|
75
75
|
import { fetchTopSimilarLessons, persistOutputEncodingSalience, runLessonQualityJudge, writeQualityRejection, } from "./distill/quality-gate.js";
|
|
76
|
-
import { buildClsContext, checkDistillFidelity } from "./distill-guards.js";
|
|
76
|
+
import { buildClsContext, checkDistillFidelity, DEFAULT_CLS_ADJACENT_COUNT } from "./distill-guards.js";
|
|
77
77
|
import { deriveKnowledgeRef } from "./distill-promotion-policy.js";
|
|
78
78
|
import { buildRefVocabulary, scoreEncodingSalience } from "./encoding-salience.js";
|
|
79
79
|
import { resolveImproveLlmExecution } from "./execution.js";
|
|
@@ -1239,7 +1239,7 @@ async function buildDistillMessages(args) {
|
|
|
1239
1239
|
let clsContext = "";
|
|
1240
1240
|
if (clsConfig.enabled) {
|
|
1241
1241
|
try {
|
|
1242
|
-
const adjacentCount = clsConfig.adjacentCount ??
|
|
1242
|
+
const adjacentCount = clsConfig.adjacentCount ?? DEFAULT_CLS_ADJACENT_COUNT;
|
|
1243
1243
|
// Use the asset content or input ref as the query for adjacent retrieval.
|
|
1244
1244
|
const clsQuery = assetContent ? assetContent.slice(0, 500) : inputRef;
|
|
1245
1245
|
const adjacentItems = await fetchSimilarLessonsFn(clsQuery, adjacentCount);
|
|
@@ -44,6 +44,7 @@ import { indexWrittenAssets } from "../../indexer/index-written-assets.js";
|
|
|
44
44
|
import { disposeLoweredExecutionDispatchLease, } from "../../integrations/agent/execution-lowering.js";
|
|
45
45
|
import { getAvailableHarnesses } from "../../integrations/session-logs/index.js";
|
|
46
46
|
import { preFilterSession } from "../../integrations/session-logs/pre-filter.js";
|
|
47
|
+
import { isJsonSchemaKnownUnsupported } from "../../llm/client.js";
|
|
47
48
|
import { callStructured, preflightStructuredLlmRunner } from "../../llm/structured-call.js";
|
|
48
49
|
import { sha256Hex } from "../../runtime.js";
|
|
49
50
|
import { getExtractedSessionsMap, getLastExtractRunAt, shouldSkipAlreadyExtractedSession, upsertExtractedSession, } from "../../storage/repositories/extract-sessions-repository.js";
|
|
@@ -563,7 +564,13 @@ async function runSessionExtractionLlmCall(args) {
|
|
|
563
564
|
return payload.parseFailure ? undefined : payload;
|
|
564
565
|
},
|
|
565
566
|
validate: (payload) => ({ ok: true, value: payload }),
|
|
566
|
-
|
|
567
|
+
// One attempt when structured output is expected to work (not explicitly
|
|
568
|
+
// disabled, and this connection hasn't already proven otherwise this
|
|
569
|
+
// process — see `isJsonSchemaKnownUnsupported`); two when it's known
|
|
570
|
+
// unsupported and extraction is relying on looser prompt-contract JSON.
|
|
571
|
+
maxAttempts: llmRunner.connection.supportsJsonSchema !== false && !isJsonSchemaKnownUnsupported(llmRunner.connection)
|
|
572
|
+
? 1
|
|
573
|
+
: 2,
|
|
567
574
|
buildFeedback: () => "Your previous response did not contain a valid extraction payload. Respond with ONLY a JSON object matching the requested schema, with a candidates array and no prose or code fences.",
|
|
568
575
|
});
|
|
569
576
|
if (result.ok)
|
|
@@ -35,7 +35,7 @@ import { akmDistill } from "./distill.js";
|
|
|
35
35
|
import { buildLatestProposalTsMap, collectEligibleRefs, collectEligibleRefsReadOnly, memoryCleanupParentRef, resolveImproveScope, shouldAnalyzeMemoryCleanup, } from "./eligibility.js";
|
|
36
36
|
import { countEvalCases } from "./eval-cases.js";
|
|
37
37
|
import { resolveImprovePlan, resolveImproveStrategy } from "./improve-strategies.js";
|
|
38
|
-
import { improveLockPath,
|
|
38
|
+
import { improveLockPath, releaseImproveLock, tryAcquireImproveLock } from "./locks.js";
|
|
39
39
|
// The cycle loop / post-loop / maintenance stages live in ./loop-stages.
|
|
40
40
|
import { runImproveLoopStage, runImprovePostLoopStage } from "./loop-stages.js";
|
|
41
41
|
import { analyzeMemoryCleanup } from "./memory/memory-improve.js";
|
|
@@ -115,7 +115,7 @@ function createRunWriteJournal() {
|
|
|
115
115
|
export async function akmImprove(options = {}) {
|
|
116
116
|
const setup = resolveImproveRunSetup(options);
|
|
117
117
|
options = setup.options;
|
|
118
|
-
const { budgetMs, budgetAbortController, scope, selectedStrategy, syncRepoDir, resolvedStateDbPath, resolvedLockPath,
|
|
118
|
+
const { budgetMs, budgetAbortController, scope, selectedStrategy, syncRepoDir, resolvedStateDbPath, resolvedLockPath, } = setup;
|
|
119
119
|
let clearBudgetTimer = () => { };
|
|
120
120
|
let initialGitPaths = new Set();
|
|
121
121
|
const runJournal = createRunWriteJournal();
|
|
@@ -164,7 +164,7 @@ export async function akmImprove(options = {}) {
|
|
|
164
164
|
}
|
|
165
165
|
try {
|
|
166
166
|
if (!options.dryRun) {
|
|
167
|
-
const acquisition = tryAcquireImproveLock(resolvedLockPath,
|
|
167
|
+
const acquisition = tryAcquireImproveLock(resolvedLockPath, options.skipIfLocked, {
|
|
168
168
|
// R25: C2 boundary-pinned path — the long-lived handle doesn't exist yet.
|
|
169
169
|
dbPath: resolvedStateDbPath,
|
|
170
170
|
});
|
|
@@ -484,7 +484,6 @@ function resolveImproveRunSetup(options) {
|
|
|
484
484
|
// triage, indexing, proposal work, maintenance, and final stash sync.
|
|
485
485
|
const lockBaseDir = primaryStashDir ? path.join(primaryStashDir, ".akm") : path.join(options.stashDir ?? ".", ".akm");
|
|
486
486
|
const resolvedLockPath = improveLockPath(lockBaseDir);
|
|
487
|
-
const lockStaleAfterMs = Math.max(MIN_IMPROVE_LOCK_STALE_MS, budgetMs + 10 * 60 * 1000);
|
|
488
487
|
const effectiveSync = { ...improveProfile.sync, ...options.sync };
|
|
489
488
|
return {
|
|
490
489
|
startMs,
|
|
@@ -514,7 +513,6 @@ function resolveImproveRunSetup(options) {
|
|
|
514
513
|
syncRepoDir,
|
|
515
514
|
resolvedStateDbPath,
|
|
516
515
|
resolvedLockPath,
|
|
517
|
-
lockStaleAfterMs,
|
|
518
516
|
effectiveSync,
|
|
519
517
|
};
|
|
520
518
|
}
|
|
@@ -9,13 +9,12 @@ import { createLockPayload, probeLock, reclaimStaleLock, releaseLock, tryAcquire
|
|
|
9
9
|
import { tryWithMaintenanceStartBarrier, withMaintenanceStartBarrier } from "../../core/maintenance-barrier.js";
|
|
10
10
|
import { describeInaccessiblePath } from "../../core/path-access.js";
|
|
11
11
|
import { warn } from "../../core/warn.js";
|
|
12
|
-
export const MIN_IMPROVE_LOCK_STALE_MS = 4 * 60 * 60 * 1000;
|
|
13
12
|
export function improveLockPath(lockBaseDir) {
|
|
14
13
|
return path.join(lockBaseDir, "improve.lock");
|
|
15
14
|
}
|
|
16
|
-
export function tryAcquireImproveLock(lockPath,
|
|
15
|
+
export function tryAcquireImproveLock(lockPath, skipIfLocked, eventsCtx) {
|
|
17
16
|
let recoveryEvent;
|
|
18
|
-
const acquire = () => tryAcquireImproveLockUnlocked(lockPath,
|
|
17
|
+
const acquire = () => tryAcquireImproveLockUnlocked(lockPath, skipIfLocked, (event) => {
|
|
19
18
|
recoveryEvent = event;
|
|
20
19
|
});
|
|
21
20
|
const result = skipIfLocked ? tryWithMaintenanceStartBarrier(acquire) : withMaintenanceStartBarrier(acquire);
|
|
@@ -37,14 +36,23 @@ export function tryAcquireImproveLock(lockPath, staleAfterMs, skipIfLocked, even
|
|
|
37
36
|
}
|
|
38
37
|
return result;
|
|
39
38
|
}
|
|
40
|
-
function tryAcquireImproveLockUnlocked(lockPath,
|
|
39
|
+
function tryAcquireImproveLockUnlocked(lockPath, skipIfLocked, onRecovered) {
|
|
41
40
|
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
42
41
|
const lockPayload = () => createLockPayload({ startedAt: new Date().toISOString() });
|
|
43
42
|
let ownership = tryAcquireLockSync(lockPath, lockPayload());
|
|
44
43
|
if (ownership) {
|
|
45
44
|
return { state: "acquired", ownership };
|
|
46
45
|
}
|
|
47
|
-
|
|
46
|
+
// No `staleAfterMs`: only a verifiably dead holder is ever reclaimed. A
|
|
47
|
+
// wedged-but-alive `akm improve` (SQLite WAL + busy_timeout + BEGIN
|
|
48
|
+
// IMMEDIATE already serialize concurrent writes to state.db at the
|
|
49
|
+
// correctness layer, so this lock only avoids duplicate LOGICAL work) must
|
|
50
|
+
// not have its lease silently taken away purely because a clock elapsed —
|
|
51
|
+
// that was the #872-shaped hazard here: a live holder passed the
|
|
52
|
+
// PID-liveness check forever, so only a multi-hour age window could ever
|
|
53
|
+
// free it, stranding every `akm improve --skip-if-locked` invocation for
|
|
54
|
+
// up to that long while reporting success.
|
|
55
|
+
const probe = probeLock(lockPath);
|
|
48
56
|
// Race: the holder released the lock between our failed `tryAcquireLockSync`
|
|
49
57
|
// and this probe, so the probe sees no file (`absent`). Retry acquisition once
|
|
50
58
|
// rather than falling through to the contended skip/throw below — otherwise we
|
|
@@ -13,7 +13,6 @@ import { getDbPath } from "../../core/paths.js";
|
|
|
13
13
|
import { withStateDb } from "../../core/state-db.js";
|
|
14
14
|
import { info } from "../../core/warn.js";
|
|
15
15
|
import { DEFAULT_GRAPH_EXTRACTION_INCLUDE_TYPES, runGraphExtractionPass, } from "../../indexer/graph/graph-extraction.js";
|
|
16
|
-
import { withIndexWriterLease } from "../../indexer/index-writer-lock.js";
|
|
17
16
|
import { deriveWritableBundleIds } from "../../indexer/installations.js";
|
|
18
17
|
import { collectPendingMemories, runMemoryInferencePass, } from "../../indexer/passes/memory-inference.js";
|
|
19
18
|
import { resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
@@ -679,13 +678,13 @@ export async function runImproveMaintenancePasses(args) {
|
|
|
679
678
|
graphExtractionFn,
|
|
680
679
|
reindexWithIndexDbReleased,
|
|
681
680
|
};
|
|
682
|
-
const collected = await
|
|
681
|
+
const collected = await runMaintenancePassesUnderLease(ctx, dbCell, {
|
|
683
682
|
actionableRefs: args.actionableRefs,
|
|
684
683
|
memoryRefsForInference,
|
|
685
684
|
consolidationRan: args.consolidationRan,
|
|
686
685
|
allWarnings,
|
|
687
686
|
openIndexDb,
|
|
688
|
-
})
|
|
687
|
+
});
|
|
689
688
|
return {
|
|
690
689
|
...(collected.memoryInference ? { memoryInference: collected.memoryInference } : {}),
|
|
691
690
|
...(collected.graphExtraction ? { graphExtraction: collected.graphExtraction } : {}),
|
|
@@ -697,12 +696,12 @@ export async function runImproveMaintenancePasses(args) {
|
|
|
697
696
|
};
|
|
698
697
|
}
|
|
699
698
|
/**
|
|
700
|
-
* The maintenance sequence
|
|
701
|
-
*
|
|
702
|
-
* reindex-after-inference → graph extraction →
|
|
703
|
-
* expiration) → retention purges. Each pass
|
|
704
|
-
* this orchestrator folds warnings into the
|
|
705
|
-
* same points the inline code pushed them.
|
|
699
|
+
* The maintenance sequence (formerly the ~389-line anonymous
|
|
700
|
+
* `withIndexWriterLease` callback, before #872 removed the index-rebuild
|
|
701
|
+
* lease): memory inference → reindex-after-inference → graph extraction →
|
|
702
|
+
* proposal hygiene (orphan purge, expiration) → retention purges. Each pass
|
|
703
|
+
* returns its results and warnings; this orchestrator folds warnings into the
|
|
704
|
+
* caller's `allWarnings` sink at the same points the inline code pushed them.
|
|
706
705
|
*/
|
|
707
706
|
async function runMaintenancePassesUnderLease(ctx, dbCell, args) {
|
|
708
707
|
const { allWarnings } = args;
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* # Algorithm
|
|
15
15
|
*
|
|
16
16
|
* 1. Collect all derived memories grouped by `parentRef` family.
|
|
17
|
-
* 2. For each family, enumerate candidate pairs
|
|
17
|
+
* 2. For each family, enumerate candidate pairs.
|
|
18
18
|
* 3. For each pair, call the LLM to judge whether the two memories are in
|
|
19
19
|
* direct factual conflict.
|
|
20
20
|
* 4. For confirmed contradictions, write `contradictedBy` edges directly to
|
|
@@ -43,16 +43,6 @@ import { callStructured, preflightStructuredLlmRunner } from "../../../llm/struc
|
|
|
43
43
|
import { resolveImproveLlmExecution } from "../execution.js";
|
|
44
44
|
import { isDerivedMemory, memoryIdentityRef, resolveParentRef } from "./derived-ref.js";
|
|
45
45
|
// ── Constants ────────────────────────────────────────────────────────────────
|
|
46
|
-
/**
|
|
47
|
-
* Maximum family size for pairwise contradiction checking. Families larger
|
|
48
|
-
* than this are skipped to bound the LLM call count (O(n²) pairs).
|
|
49
|
-
*/
|
|
50
|
-
const MAX_FAMILY_SIZE = 8;
|
|
51
|
-
/**
|
|
52
|
-
* Maximum number of contradiction pairs to check per improve run, across all
|
|
53
|
-
* families. Prevents runaway LLM usage on stashes with many memories.
|
|
54
|
-
*/
|
|
55
|
-
const MAX_PAIRS_PER_RUN = 20;
|
|
56
46
|
/**
|
|
57
47
|
* Minimum confidence required to write a contradiction edge. Below this
|
|
58
48
|
* threshold the LLM may be flagging topic-overlap rather than genuine logical
|
|
@@ -218,15 +208,9 @@ export async function detectAndWriteContradictions(stashDir, config, chat, strat
|
|
|
218
208
|
for (const [, family] of byParent) {
|
|
219
209
|
if (family.length < 2)
|
|
220
210
|
continue;
|
|
221
|
-
if (family.length > MAX_FAMILY_SIZE) {
|
|
222
|
-
result.warnings.push(`Skipping contradiction check for family of ${family.length} members (exceeds MAX_FAMILY_SIZE=${MAX_FAMILY_SIZE})`);
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
225
211
|
result.familiesExamined++;
|
|
226
212
|
for (let i = 0; i < family.length - 1; i++) {
|
|
227
213
|
for (let j = i + 1; j < family.length; j++) {
|
|
228
|
-
if (candidatePairs.length >= MAX_PAIRS_PER_RUN)
|
|
229
|
-
break;
|
|
230
214
|
const a = family[i];
|
|
231
215
|
const b = family[j];
|
|
232
216
|
if (!a || !b)
|
|
@@ -245,8 +229,6 @@ export async function detectAndWriteContradictions(stashDir, config, chat, strat
|
|
|
245
229
|
continue;
|
|
246
230
|
candidatePairs.push({ a, b, loser, winnerRef });
|
|
247
231
|
}
|
|
248
|
-
if (candidatePairs.length >= MAX_PAIRS_PER_RUN)
|
|
249
|
-
break;
|
|
250
232
|
}
|
|
251
233
|
}
|
|
252
234
|
if (candidatePairs.length === 0) {
|
|
@@ -9,12 +9,6 @@ import { getAllAssetOutcomes, getAssetOutcome, getOutcomeScoresByRef, upsertAsse
|
|
|
9
9
|
* At α = 0.3 the window is ≈ 3 cycles.
|
|
10
10
|
*/
|
|
11
11
|
export const OUTCOME_EMA_ALPHA = 0.3;
|
|
12
|
-
/**
|
|
13
|
-
* Maximum K improve cycles for the eligibility-trace window. Retrievals older
|
|
14
|
-
* than K cycles contribute via the EMA decay naturally (they are already baked
|
|
15
|
-
* into `expected_retrieval_rate`).
|
|
16
|
-
*/
|
|
17
|
-
export const OUTCOME_TRACE_CYCLES = 5;
|
|
18
12
|
/**
|
|
19
13
|
* Warm-start cap: the maximum `outcome_score` a brand-new row can be seeded with
|
|
20
14
|
* (from the utility EMA). Prevents a `[0,1]`-range utility value from generating
|