akm-cli 0.9.5 → 0.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +295 -0
- package/dist/assets/hints/cli-hints-full.md +3 -3
- package/dist/assets/improve-strategies/catchup.json +40 -11
- package/dist/assets/improve-strategies/thorough.json +45 -7
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +2 -2
- package/dist/commands/agent/contribute-cli.js +11 -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-cli.js +1 -1
- package/dist/commands/improve/improve-strategies.js +0 -4
- 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/memory/memory-improve.js +2 -1
- package/dist/commands/improve/outcome-loop.js +0 -6
- package/dist/commands/improve/preparation.js +1 -1
- package/dist/commands/improve/reflect.js +21 -10
- package/dist/commands/lint/base-linter.js +141 -18
- package/dist/commands/lint/index.js +21 -10
- package/dist/commands/proposal/validators/proposal-quality-validators.js +0 -7
- package/dist/commands/read/curate.js +47 -0
- package/dist/commands/read/search-cli.js +24 -1
- 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/asset/asset-placement.js +13 -2
- package/dist/core/asset/frontmatter.js +116 -0
- package/dist/core/asset/memory-archive.js +97 -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.js +1 -1
- package/dist/core/config/engine-semantics.js +0 -2
- 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 +73 -133
- package/dist/scripts/akm-migrate.js +73 -133
- 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/sources/snapshot-fetchers/website-ingest.js +126 -0
- package/dist/storage/engines/sqlite-migrations.js +20 -1
- package/dist/storage/repositories/index-connection.js +45 -3
- package/dist/storage/repositories/index-entries-repository.js +0 -15
- package/dist/tasks/backends/cron.js +49 -9
- package/dist/tasks/backends/launchd.js +15 -20
- package/dist/tasks/backends/schtasks.js +18 -8
- package/dist/tasks/resolve-akm-bin.js +17 -2
- package/dist/tasks/run/run-native-task.js +8 -6
- package/dist/tasks/scheduler-invocation.js +8 -1
- package/dist/tasks/source/bounded-document.js +2 -4
- package/dist/tasks/source/parse-task-source.js +23 -9
- 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/cli.md +5 -1
- 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/assets/improve-strategies/frequent.json +0 -15
- package/dist/assets/improve-strategies/memory-focus.json +0 -15
- package/dist/indexer/search/semantic-status.js +0 -142
|
@@ -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)
|
|
@@ -114,7 +114,7 @@ export const improveCommand = defineCommand({
|
|
|
114
114
|
},
|
|
115
115
|
strategy: {
|
|
116
116
|
type: "string",
|
|
117
|
-
description: "Named improve strategy from improve.strategies or built-in strategies (catchup, consolidate, default,
|
|
117
|
+
description: "Named improve strategy from improve.strategies or built-in strategies (catchup, consolidate, default, graph-refresh, proactive-maintenance, quick, reflect-distill, thorough). Controls which sub-processes run and which asset types are processed.",
|
|
118
118
|
},
|
|
119
119
|
sync: {
|
|
120
120
|
type: "boolean",
|
|
@@ -4,9 +4,7 @@
|
|
|
4
4
|
import catchup from "../../assets/improve-strategies/catchup.json" with { type: "json" };
|
|
5
5
|
import consolidate from "../../assets/improve-strategies/consolidate.json" with { type: "json" };
|
|
6
6
|
import defaultStrategy from "../../assets/improve-strategies/default.json" with { type: "json" };
|
|
7
|
-
import frequent from "../../assets/improve-strategies/frequent.json" with { type: "json" };
|
|
8
7
|
import graphRefresh from "../../assets/improve-strategies/graph-refresh.json" with { type: "json" };
|
|
9
|
-
import memoryFocus from "../../assets/improve-strategies/memory-focus.json" with { type: "json" };
|
|
10
8
|
import proactiveMaintenance from "../../assets/improve-strategies/proactive-maintenance.json" with { type: "json" };
|
|
11
9
|
import quick from "../../assets/improve-strategies/quick.json" with { type: "json" };
|
|
12
10
|
import reflectDistill from "../../assets/improve-strategies/reflect-distill.json" with { type: "json" };
|
|
@@ -45,9 +43,7 @@ const BUILTIN_STRATEGIES = {
|
|
|
45
43
|
default: defaultStrategy,
|
|
46
44
|
quick,
|
|
47
45
|
thorough,
|
|
48
|
-
"memory-focus": memoryFocus,
|
|
49
46
|
"graph-refresh": graphRefresh,
|
|
50
|
-
frequent,
|
|
51
47
|
consolidate,
|
|
52
48
|
catchup,
|
|
53
49
|
"reflect-distill": reflectDistill,
|
|
@@ -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) {
|
|
@@ -5,6 +5,7 @@ import fs from "node:fs";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { assembleAsset } from "../../../core/asset/asset-serialize.js";
|
|
7
7
|
import { mutateFrontmatter, parseFrontmatter } from "../../../core/asset/frontmatter.js";
|
|
8
|
+
import { MEMORY_ARCHIVE_REL } from "../../../core/asset/memory-archive.js";
|
|
8
9
|
import { conceptIdFromTypeName } from "../../../core/asset/resolve-ref.js";
|
|
9
10
|
import { asNonEmptyString, groupBy, stringArray } from "../../../core/common.js";
|
|
10
11
|
import { DERIVED_SUFFIX } from "../../../core/recognition-util.js";
|
|
@@ -553,7 +554,7 @@ function priorBeliefStateForArchive(candidate) {
|
|
|
553
554
|
}
|
|
554
555
|
function createArchiveDir(stashDir, ref, archivedAt) {
|
|
555
556
|
const baseName = `${archivedAt.replace(/[:.]/g, "-")}-${sanitizeRef(ref)}`;
|
|
556
|
-
const root = path.join(stashDir,
|
|
557
|
+
const root = path.join(stashDir, MEMORY_ARCHIVE_REL);
|
|
557
558
|
fs.mkdirSync(root, { recursive: true });
|
|
558
559
|
let attempt = 0;
|
|
559
560
|
while (true) {
|
|
@@ -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
|
|
@@ -760,7 +760,7 @@ async function runPreparationPrelude(args) {
|
|
|
760
760
|
};
|
|
761
761
|
}
|
|
762
762
|
export async function runImprovePreparationStage(args) {
|
|
763
|
-
const { scope, options,
|
|
763
|
+
const { scope, options, primaryStashDir, eventsCtx, initialCleanupWarnings, improveProfile, resolvedPlan, planOnly = options.dryRun === true, } = args;
|
|
764
764
|
const actions = [];
|
|
765
765
|
const cleanupWarnings = initialCleanupWarnings ? [...initialCleanupWarnings] : [];
|
|
766
766
|
const { memoryIndexHealth, consolidationPass, extractPlan, extractResults, appliedCleanup, postCleanupRefs, cleanupGate, validationFailures, validationFailureRefs, schemaRepairs, } = await runPreparationPrelude({ ...args, planOnly, actions, cleanupWarnings });
|
|
@@ -44,7 +44,7 @@ import { prepareInlineExecution, prepareInlineExecutionWithRunner } from "../../
|
|
|
44
44
|
import { buildReflectOutputRepairPrompt, buildReflectPrompt, extractDraftConfidence, parseAgentProposalPayload, } from "../../integrations/agent/prompts.js";
|
|
45
45
|
import { runnerIsLlm, runnerSupportsFileWrite } from "../../integrations/agent/runner.js";
|
|
46
46
|
import { collectDispatchSensitiveValues } from "../../integrations/agent/runner-dispatch.js";
|
|
47
|
-
import { LlmCallError } from "../../llm/client.js";
|
|
47
|
+
import { isJsonSchemaKnownUnsupported, LlmCallError } from "../../llm/client.js";
|
|
48
48
|
import { callStructured } from "../../llm/structured-call.js";
|
|
49
49
|
import { baseFailureFields, enoentHintMessage, isEnoentFailure } from "../agent/agent-support.js";
|
|
50
50
|
import { isProposalSkipped, listProposalsReadOnly, proposalContent, } from "../proposal/repository.js";
|
|
@@ -578,8 +578,9 @@ export function sanitizeReflectPayload(payload, sourceContent, targetRef) {
|
|
|
578
578
|
}
|
|
579
579
|
/**
|
|
580
580
|
* JSON Schema for structured reflect output. Passed to `chatCompletion` when
|
|
581
|
-
*
|
|
582
|
-
* JSON object containing only the target-scoped
|
|
581
|
+
* {@link wantsJsonSchemaOutput} selects `outputMode: "json_schema"`, so the
|
|
582
|
+
* model returns a strict JSON object containing only the target-scoped
|
|
583
|
+
* fields AKM cannot derive.
|
|
583
584
|
*/
|
|
584
585
|
const REFLECT_FRONTMATTER_PATCH_JSON_SCHEMA = {
|
|
585
586
|
type: "object",
|
|
@@ -626,6 +627,17 @@ const REFLECT_UNSCOPED_JSON_SCHEMA = {
|
|
|
626
627
|
frontmatterPatch: REFLECT_FRONTMATTER_PATCH_JSON_SCHEMA,
|
|
627
628
|
},
|
|
628
629
|
};
|
|
630
|
+
/**
|
|
631
|
+
* Whether to frame the reflect prompt for structured JSON output on this
|
|
632
|
+
* connection. Optimistic by default — `chatCompletion` attempts
|
|
633
|
+
* `response_format: json_schema` fresh on every call and falls back once on
|
|
634
|
+
* a 4xx, so there is no persisted verdict to consult here. `false` only when
|
|
635
|
+
* a human/workflow explicitly disabled it, or a real call already proved
|
|
636
|
+
* this connection rejects it earlier in the same process.
|
|
637
|
+
*/
|
|
638
|
+
function wantsJsonSchemaOutput(connection) {
|
|
639
|
+
return connection.supportsJsonSchema !== false && !isJsonSchemaKnownUnsupported(connection);
|
|
640
|
+
}
|
|
629
641
|
/** Critique prompt injected between prior draft and refinement request (Self-Refine loop). */
|
|
630
642
|
const REFLECT_CRITIQUE_PROMPT = "Your previous proposal is shown above. Review it critically and provide an improved version that is more specific, actionable, and avoids any issues with the previous attempt. Return only the improved response using the output contract from the original prompt.";
|
|
631
643
|
/**
|
|
@@ -778,7 +790,7 @@ function parseDirectReflectOutput(raw, mode, targetRef) {
|
|
|
778
790
|
export async function runReflectViaLlm(opts) {
|
|
779
791
|
const start = Date.now();
|
|
780
792
|
let repairAttempts = 0;
|
|
781
|
-
const
|
|
793
|
+
const _connection = opts.runner.connection;
|
|
782
794
|
const messages = [{ role: "user", content: opts.prompt ?? "" }];
|
|
783
795
|
const configuredTimeout = Object.hasOwn(opts, "timeoutMs")
|
|
784
796
|
? (opts.timeoutMs ?? null)
|
|
@@ -1354,21 +1366,20 @@ async function resolveReflectSource(options, stash, emitReflectFailed) {
|
|
|
1354
1366
|
}
|
|
1355
1367
|
/**
|
|
1356
1368
|
* Run the agent with the optional Self-Refine loop (R-1 / #372): up to
|
|
1357
|
-
*
|
|
1369
|
+
* `maxRefineIters` invocations, each injecting the prior draft as self-critique
|
|
1358
1370
|
* context and exiting early on a no-op refinement. Synthesizes per-iteration
|
|
1359
1371
|
* draft paths into `draftPathsToCleanup` (mutated) and returns the final agent
|
|
1360
1372
|
* result + last draft path. Extracted verbatim from `akmReflect`.
|
|
1361
1373
|
*/
|
|
1362
1374
|
async function runReflectRefineIterations(args) {
|
|
1363
1375
|
const { options, parsedRef, assetContent, feedback, schemaHints, relatedLessons, rejectedProposals, standardsContext, runnerSpec, lease, agentEnv, draftPathsToCleanup, onNotices, } = args;
|
|
1364
|
-
const
|
|
1365
|
-
const maxRefineIters = Math.min(Math.max(1, options.maxRefineIters ?? 1), MAX_REFINE_ITERS);
|
|
1376
|
+
const maxRefineIters = Math.max(1, options.maxRefineIters ?? 1);
|
|
1366
1377
|
// Determine whether this dispatch can honour the file-write contract.
|
|
1367
1378
|
// Agent CLI + OpenCode SDK runners both have filesystem access; the direct
|
|
1368
1379
|
// LLM HTTP runner does NOT.
|
|
1369
1380
|
const canRunnerWriteFile = runnerSupportsFileWrite(runnerSpec);
|
|
1370
1381
|
const outputMode = runnerIsLlm(runnerSpec)
|
|
1371
|
-
? runnerSpec.connection
|
|
1382
|
+
? wantsJsonSchemaOutput(runnerSpec.connection)
|
|
1372
1383
|
? "json_schema"
|
|
1373
1384
|
: "framed_markdown"
|
|
1374
1385
|
: undefined;
|
|
@@ -1421,10 +1432,10 @@ async function runReflectRefineIterations(args) {
|
|
|
1421
1432
|
...(options.signal ? { signal: options.signal } : {}),
|
|
1422
1433
|
priorDraft,
|
|
1423
1434
|
iteration: iter,
|
|
1424
|
-
...(
|
|
1435
|
+
...(outputMode === "json_schema"
|
|
1425
1436
|
? { responseSchema: options.ref ? REFLECT_JSON_SCHEMA : REFLECT_UNSCOPED_JSON_SCHEMA }
|
|
1426
1437
|
: {}),
|
|
1427
|
-
outputMode:
|
|
1438
|
+
outputMode: outputMode ?? "framed_markdown",
|
|
1428
1439
|
...(options.ref ? { targetRef: options.ref } : {}),
|
|
1429
1440
|
allowRepair: repairAttempts === 0,
|
|
1430
1441
|
...(options.chat ? { chat: options.chat } : {}),
|