akm-cli 0.9.1 → 0.9.2-alpha.2
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 +103 -28
- package/README.md +3 -1
- package/SECURITY.md +1 -1
- package/STABILITY.md +1 -1
- package/dist/akm +2 -2
- package/dist/akm-migrate +2 -2
- package/dist/assets/hints/cli-hints-full.md +14 -9
- package/dist/assets/improve-strategies/proactive-maintenance.json +1 -1
- package/dist/assets/improve-strategies/reflect-distill.json +1 -1
- package/dist/assets/models.json +35 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +3 -4
- package/dist/assets/stash-skeleton/facts/conventions/organization.md +1 -3
- package/dist/assets/tasks/core/extract.yml +6 -5
- package/dist/assets/tasks/core/improve.yml +6 -5
- package/dist/assets/tasks/core/index-refresh.yml +6 -5
- package/dist/assets/tasks/core/sync.yml +6 -5
- package/dist/assets/tasks/core/version-check.yml +6 -5
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-consolidate.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-nightly.yml +6 -5
- package/dist/cli/confirm.js +2 -2
- package/dist/cli/parse-args.js +3 -24
- package/dist/cli/retired-commands.js +1 -1
- package/dist/cli/shared.js +2 -2
- package/dist/cli.js +11 -9
- package/dist/commands/agent/agent-dispatch.js +55 -89
- package/dist/commands/agent/contribute-cli.js +12 -45
- package/dist/commands/command/builtin-action.js +32 -0
- package/dist/commands/command/command-cli.js +99 -0
- package/dist/commands/command/command-execution.js +308 -0
- package/dist/commands/command/execution-source-loader.js +176 -0
- package/dist/commands/command/portable-template.js +60 -0
- package/dist/commands/config-cli.js +10 -4
- package/dist/commands/env/env.js +4 -2
- package/dist/commands/feedback-cli.js +1 -1
- package/dist/commands/health/checks.js +241 -29
- package/dist/commands/health/html-report.js +0 -14
- package/dist/commands/health/report-view-model.js +0 -1
- package/dist/commands/health/surfaces.js +6 -7
- package/dist/commands/health/types.js +0 -2
- package/dist/commands/health.js +63 -18
- package/dist/commands/improve/collapse-detector.js +5 -6
- package/dist/commands/improve/consolidate.js +251 -214
- package/dist/commands/improve/distill/promote-memory.js +71 -34
- package/dist/commands/improve/distill/quality-gate.js +17 -5
- package/dist/commands/improve/distill.js +232 -155
- package/dist/commands/improve/eligibility.js +112 -79
- package/dist/commands/improve/execution.js +57 -0
- package/dist/commands/improve/extract-cli.js +5 -5
- package/dist/commands/improve/extract-prompt.js +64 -22
- package/dist/commands/improve/extract.js +608 -360
- package/dist/commands/improve/improve-strategies.js +43 -14
- package/dist/commands/improve/improve.js +249 -29
- package/dist/commands/improve/loop-stages.js +11 -17
- package/dist/commands/improve/memory/memory-contradiction-detect.js +90 -66
- package/dist/commands/improve/outcome-loop.js +22 -38
- package/dist/commands/improve/planner.js +134 -0
- package/dist/commands/improve/preparation.js +730 -409
- package/dist/commands/improve/reflect.js +386 -223
- package/dist/commands/improve/run-context.js +3 -4
- package/dist/commands/improve/salience.js +6 -58
- package/dist/commands/improve/session-asset.js +12 -12
- package/dist/commands/lint/index.js +101 -29
- package/dist/commands/migrate-cli.js +11 -69
- package/dist/commands/migration-tool.js +6 -9
- package/dist/commands/models-cli.js +27 -0
- package/dist/commands/proposal/drain.js +258 -186
- package/dist/commands/proposal/proposal-cli.js +32 -10
- package/dist/commands/proposal/proposal.js +2 -5
- package/dist/commands/proposal/propose.js +192 -172
- package/dist/commands/proposal/repository.js +54 -91
- package/dist/commands/proposal/validators/proposal-validators.js +9 -7
- package/dist/commands/read/curate.js +53 -22
- package/dist/commands/read/registry-search.js +25 -9
- package/dist/commands/read/remember-cli.js +14 -2
- package/dist/commands/read/search.js +10 -4
- package/dist/commands/read/show.js +139 -153
- package/dist/commands/registry-cli.js +16 -7
- package/dist/commands/remember.js +33 -18
- package/dist/commands/sources/add-cli.js +19 -178
- package/dist/commands/sources/bundle-cli.js +15 -3
- package/dist/commands/sources/dangerous-env-audit.js +135 -0
- package/dist/commands/sources/info.js +2 -1
- package/dist/commands/sources/installed-stashes.js +901 -177
- package/dist/commands/sources/schema-repair.js +174 -95
- package/dist/commands/sources/self-update.js +30 -74
- package/dist/commands/sources/source-add.js +3 -5
- package/dist/commands/sources/sources-cli.js +2 -15
- package/dist/commands/sources/update-transaction.js +220 -0
- package/dist/commands/tasks/tasks-cli.js +3 -3
- package/dist/commands/tasks/tasks.js +736 -317
- package/dist/commands/workflow-cli.js +2 -2
- package/dist/core/adapter/adapters/agent-skills-adapter.js +3 -0
- package/dist/core/adapter/adapters/akm-adapter.js +85 -35
- package/dist/core/adapter/adapters/akm-lint.js +54 -39
- package/dist/core/adapter/adapters/akm-metadata.js +45 -45
- package/dist/core/adapter/adapters/akm-task-adapter.js +32 -49
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +38 -23
- package/dist/core/adapter/adapters/dotenv-adapter.js +30 -1
- package/dist/core/adapter/adapters/generic-files-adapter.js +11 -0
- package/dist/core/adapter/adapters/index.js +0 -9
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +4 -0
- package/dist/core/adapter/adapters/okf-adapter.js +4 -0
- package/dist/core/adapter/adapters/opencode-adapter.js +5 -8
- package/dist/core/adapter/adapters/tool-dir-shared.js +63 -6
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -0
- package/dist/core/adapter/execution-source.js +308 -0
- package/dist/core/adapter/recognize-match.js +36 -13
- package/dist/core/adapter/registry.js +0 -9
- package/dist/core/asset/stash-meta.js +94 -4
- package/dist/core/common.js +6 -11
- package/dist/core/config/config-io.js +3 -3
- package/dist/core/config/config-schema.js +18 -40
- package/dist/core/config/config-sources.js +11 -21
- package/dist/core/config/config-walker.js +31 -13
- package/dist/core/config/config.js +23 -26
- package/dist/core/config/schema/engines.js +8 -7
- package/dist/core/config/schema/improve-processes.js +29 -5
- package/dist/core/config/schema/index-config.js +0 -27
- package/dist/core/config/schema/primitives.js +1 -23
- package/dist/core/config/schema/sources-bundles.js +13 -16
- package/dist/core/errors.js +2 -0
- package/dist/core/events.js +68 -32
- package/dist/core/extra-params.js +1 -0
- package/dist/core/improve-result.js +315 -0
- package/dist/core/lesson-lint.js +0 -6
- package/dist/core/maintenance-barrier.js +4 -4
- package/dist/core/network-policy.js +152 -0
- package/dist/core/paths.js +1 -1
- package/dist/core/recognition-util.js +4 -4
- package/dist/core/registry-url.js +456 -0
- package/dist/core/state/migrations.js +161 -47
- package/dist/core/state-db.js +453 -80
- package/dist/core/system-error.js +32 -0
- package/dist/core/time.js +2 -12
- package/dist/core/write-source.js +0 -18
- package/dist/execution/directory-identity.js +52 -0
- package/dist/execution/executable-identity.js +107 -0
- package/dist/execution/guarded-source.js +398 -0
- package/dist/execution/json.js +95 -0
- package/dist/{commands/health/types-session-log.js → execution/limits.js} +2 -1
- package/dist/execution/record.js +55 -0
- package/dist/execution/resolved-request.js +730 -0
- package/dist/execution/source.js +320 -0
- package/dist/indexer/bundle-identity-guard.js +5 -4
- package/dist/indexer/db/graph-db.js +33 -0
- package/dist/indexer/graph/graph-boost.js +3 -4
- package/dist/indexer/graph/graph-extraction.js +562 -373
- package/dist/indexer/index-written-assets.js +78 -39
- package/dist/indexer/indexer.js +471 -432
- package/dist/indexer/installations.js +6 -0
- package/dist/indexer/lookup/adapter-concept-owner.js +283 -0
- package/dist/indexer/materialize-embeddings.js +155 -0
- package/dist/indexer/passes/memory-inference.js +227 -174
- package/dist/indexer/passes/metadata.js +263 -118
- package/dist/indexer/scan/doc-to-entry.js +7 -10
- package/dist/indexer/scan/drain-dir.js +51 -23
- package/dist/indexer/search/db-search.js +156 -50
- package/dist/indexer/search/fts-query.js +40 -40
- package/dist/indexer/search/ranking.js +36 -1
- package/dist/indexer/search/search-attribution.js +3 -1
- package/dist/indexer/search/search-fields.js +23 -14
- package/dist/indexer/search/search-hit-enrichers.js +1 -1
- package/dist/indexer/search/search-source.js +7 -16
- package/dist/indexer/search/semantic-status.js +10 -1
- package/dist/indexer/usage/show-usage.js +105 -0
- package/dist/indexer/usage/usage-events.js +7 -2
- package/dist/indexer/walk/matchers.js +40 -10
- package/dist/indexer/walk/path-resolver.js +5 -2
- package/dist/indexer/walk/walker.js +20 -2
- package/dist/integrations/agent/builder-shared.js +3 -6
- package/dist/integrations/agent/conversation-fallback.js +16 -0
- package/dist/integrations/agent/engine-resolution.js +87 -87
- package/dist/integrations/agent/execution-cascade.js +566 -0
- package/dist/integrations/agent/execution-definitions.js +211 -0
- package/dist/integrations/agent/execution-lowering.js +811 -0
- package/dist/integrations/agent/execution-preparation.js +67 -0
- package/dist/integrations/agent/index.js +0 -2
- package/dist/integrations/agent/inline-execution.js +74 -0
- package/dist/integrations/agent/model-map.js +515 -0
- package/dist/integrations/agent/persona-fallback.js +30 -0
- package/dist/integrations/agent/request-lowering.js +186 -0
- package/dist/integrations/agent/runner-dispatch.js +230 -37
- package/dist/integrations/agent/runner.js +12 -83
- package/dist/integrations/harnesses/aider/agent-builder.js +8 -0
- package/dist/integrations/harnesses/aider/index.js +0 -1
- package/dist/integrations/harnesses/amazonq/agent-builder.js +8 -0
- package/dist/integrations/harnesses/amazonq/index.js +0 -1
- package/dist/integrations/harnesses/claude/agent-builder.js +14 -1
- package/dist/integrations/harnesses/claude/index.js +1 -5
- package/dist/integrations/harnesses/claude/session-log.js +3 -33
- package/dist/integrations/harnesses/codex/agent-builder.js +8 -0
- package/dist/integrations/harnesses/codex/index.js +0 -1
- package/dist/integrations/harnesses/copilot/agent-builder.js +8 -0
- package/dist/integrations/harnesses/copilot/index.js +0 -1
- package/dist/integrations/harnesses/gemini/agent-builder.js +8 -0
- package/dist/integrations/harnesses/gemini/index.js +0 -1
- package/dist/integrations/harnesses/index.js +4 -44
- package/dist/integrations/harnesses/opencode/agent-builder.js +16 -9
- package/dist/integrations/harnesses/opencode/index.js +0 -2
- package/dist/integrations/harnesses/opencode/session-log.js +14 -204
- package/dist/integrations/harnesses/opencode-sdk/harness.js +12 -1
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +40 -42
- package/dist/integrations/harnesses/openhands/agent-builder.js +8 -0
- package/dist/integrations/harnesses/openhands/index.js +0 -1
- package/dist/integrations/harnesses/pi/agent-builder.js +8 -0
- package/dist/integrations/harnesses/pi/index.js +0 -1
- package/dist/integrations/harnesses/shared.js +0 -1
- package/dist/integrations/harnesses/types.js +1 -3
- package/dist/integrations/lockfile.js +82 -79
- package/dist/integrations/session-logs/index.js +6 -17
- package/dist/integrations/session-logs/provider-base.js +1 -29
- package/dist/llm/client.js +10 -5
- package/dist/llm/embedder.js +6 -7
- package/dist/llm/embedders/local.js +37 -88
- package/dist/llm/embedders/types.js +1 -1
- package/dist/llm/graph-extract.js +75 -50
- package/dist/llm/index-passes.js +43 -5
- package/dist/llm/memory-infer.js +8 -6
- package/dist/llm/metadata-enhance.js +5 -3
- package/dist/llm/structured-call.js +122 -25
- package/dist/output/format-exempt.js +1 -1
- package/dist/output/render-registry.js +0 -16
- package/dist/output/renderers.js +12 -7
- package/dist/output/shapes/curate.js +1 -0
- package/dist/output/shapes/helpers.js +10 -2
- package/dist/output/shapes/passthrough.js +2 -0
- package/dist/output/text/command-format.js +31 -33
- package/dist/output/text/health-format.js +1 -29
- package/dist/output/text/migrate.js +6 -56
- package/dist/output/text/proposal-format.js +16 -1
- package/dist/output/text/workflow-format.js +16 -0
- package/dist/registry/network.js +279 -0
- package/dist/registry/pinned-request-helper.js +247 -0
- package/dist/registry/pinned-transport.js +717 -0
- package/dist/registry/providers/skills-sh.js +18 -6
- package/dist/registry/providers/static-index.js +20 -7
- package/dist/registry/resolve.js +53 -28
- package/dist/scripts/akm-migrate-node.js +19334 -52269
- package/dist/scripts/akm-migrate.js +19270 -51612
- package/dist/setup/registry-stash-loader.js +64 -20
- package/dist/setup/semantic-assets.js +9 -34
- package/dist/setup/setup.js +12 -30
- package/dist/setup/source-identity.js +17 -0
- package/dist/setup/steps/sources.js +36 -15
- package/dist/setup/steps/tasks.js +39 -11
- package/dist/sources/providers/git-provider.js +3 -3
- package/dist/sources/providers/npm.js +2 -2
- package/dist/sources/providers/provider-utils.js +4 -3
- package/dist/sources/providers/website.js +11 -7
- package/dist/sources/snapshot-fetchers/host-guard.js +9 -136
- package/dist/sources/snapshot-fetchers/website-ingest.js +25 -109
- package/dist/sources/website-url.js +73 -0
- package/dist/storage/engines/sqlite-migrations.js +81 -26
- package/dist/storage/managed-db.js +27 -24
- package/dist/storage/repositories/events-repository.js +3 -0
- package/dist/storage/repositories/index-connection.js +42 -10
- package/dist/storage/repositories/index-entries-repository.js +203 -229
- package/dist/storage/repositories/index-entry-mapper.js +8 -12
- package/dist/storage/repositories/index-entry-schema.js +255 -0
- package/dist/storage/repositories/index-fts-repository.js +64 -71
- package/dist/storage/repositories/index-llm-cache-repository.js +8 -13
- package/dist/storage/repositories/index-meta-repository.js +0 -11
- package/dist/storage/repositories/index-schema.js +74 -350
- package/dist/storage/repositories/index-utility-repository.js +12 -17
- package/dist/storage/repositories/index-vec-repository.js +56 -7
- package/dist/storage/repositories/proposals-repository.js +4 -127
- package/dist/storage/repositories/registry-cache.js +2 -1
- package/dist/storage/repositories/task-history-repository.js +20 -40
- package/dist/storage/repositories/workflow-runs-repository.js +228 -129
- package/dist/storage/sqlite-read-snapshot.js +148 -0
- package/dist/tasks/backends/cron.js +170 -42
- package/dist/tasks/backends/index.js +1 -1
- package/dist/tasks/backends/launchd.js +787 -202
- package/dist/tasks/backends/schtasks.js +282 -83
- package/dist/tasks/embedded.js +7 -7
- package/dist/tasks/frozen-script.js +50 -0
- package/dist/tasks/resolve-akm-bin.js +5 -1
- package/dist/tasks/runner.js +239 -251
- package/dist/tasks/runtime-v3.js +281 -0
- package/dist/tasks/scheduler-binding.js +272 -0
- package/dist/tasks/scheduler-invocation.js +57 -43
- package/dist/tasks/scheduler-sync.js +654 -0
- package/dist/tasks/source-v3.js +752 -0
- package/dist/tasks/standalone-script-entry.js +5 -0
- package/dist/tasks/task-id.js +29 -0
- package/dist/workflows/authoring/authoring.js +15 -32
- package/dist/workflows/exec/dispatch-redaction.js +14 -8
- package/dist/workflows/exec/exec-unit.js +7 -28
- package/dist/workflows/exec/frozen-judge.js +57 -89
- package/dist/workflows/exec/lowering-notices.js +23 -0
- package/dist/workflows/exec/native-executor.js +301 -458
- package/dist/workflows/exec/param-secrets.js +4 -3
- package/dist/workflows/exec/run-workflow.js +26 -32
- package/dist/workflows/exec/step-work.js +105 -109
- package/dist/workflows/exec/unit-dispatch.js +103 -27
- package/dist/workflows/exec/unit-writer.js +3 -3
- package/dist/workflows/exec/worktree.js +2 -2
- package/dist/workflows/ir/compile.js +86 -72
- package/dist/workflows/ir/environment-v4.js +328 -0
- package/dist/workflows/ir/freeze-v4.js +122 -0
- package/dist/workflows/ir/plan-hash.js +13 -7
- package/dist/workflows/ir/schema-v4.js +525 -0
- package/dist/workflows/ir/schema.js +25 -284
- package/dist/workflows/ir/source-freeze-v4.js +506 -0
- package/dist/workflows/parser.js +27 -24
- package/dist/workflows/program/schema.js +1 -2
- package/dist/workflows/renderer.js +42 -29
- package/dist/workflows/resource-limits.js +4 -5
- package/dist/workflows/runtime/agent-identity.js +11 -13
- package/dist/workflows/runtime/plan-classifier.js +8 -8
- package/dist/workflows/runtime/runs.js +27 -43
- package/dist/workflows/runtime/workflow-asset-loader.js +45 -205
- package/dist/workflows/source-files.js +373 -0
- package/dist/workflows/source-ir/compile.js +196 -0
- package/dist/workflows/source-ir/github-yaml.js +577 -0
- package/dist/workflows/source-ir/ordering.js +38 -0
- package/dist/workflows/source-ir/program.js +50 -0
- package/dist/workflows/source-ir/result.js +26 -0
- package/dist/workflows/source-ir/schema.js +772 -0
- package/dist/workflows/source-ir/semantics.js +242 -0
- package/dist/workflows/source-ir/uses.js +14 -0
- package/docs/README.md +2 -0
- package/docs/migration/README.md +3 -1
- package/docs/migration/release-notes/0.9.2.md +55 -0
- package/docs/migration/release-notes/README.md +5 -0
- package/docs/migration/v0.8-to-v0.9.md +76 -1077
- package/docs/migration/v0.9.0-troubleshooting.md +104 -516
- package/docs/migration/v0.9.1-to-v0.9.2.md +150 -0
- package/docs/reference/README.md +1 -0
- package/docs/reference/cli.md +230 -98
- package/docs/reference/configuration.md +159 -36
- package/docs/reference/data-and-telemetry.md +19 -1
- package/docs/reference/supported-formats.md +23 -3
- package/docs/reference/tasks.md +182 -0
- package/docs/reference/workflow-schema.md +91 -40
- package/docs/reference/workflows.md +33 -6
- package/package.json +10 -6
- package/schemas/akm-config.json +372 -224
- package/schemas/akm-task.json +324 -80
- package/schemas/akm-workflow.json +6 -9
- package/dist/core/migration-operation.js +0 -75
- package/dist/integrations/agent/model-aliases.js +0 -74
- package/dist/tasks/parser.js +0 -380
- package/dist/tasks/schema.js +0 -123
- package/dist/tasks/validator.js +0 -80
- package/dist/workflows/ir/freeze.js +0 -320
- package/dist/workflows/runtime/document-cache.js +0 -13
|
@@ -16,17 +16,19 @@ import { openStateDatabase } from "../../core/state-db.js";
|
|
|
16
16
|
import { parseSinceToIsoLenient } from "../../core/time.js";
|
|
17
17
|
import { warn, warnVerbose } from "../../core/warn.js";
|
|
18
18
|
import { resolveWriteTarget } from "../../core/write-source.js";
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
19
|
+
import { deriveInstallations } from "../../indexer/installations.js";
|
|
20
|
+
import { resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
21
|
+
import { disposeLoweredExecutionDispatchLease, } from "../../integrations/agent/execution-lowering.js";
|
|
21
22
|
import { cosineSimilarity, embedBatch, resolveEmbeddingModelId } from "../../llm/embedder.js";
|
|
22
|
-
import { callStructured } from "../../llm/structured-call.js";
|
|
23
|
+
import { callStructured, preflightStructuredLlmRunner } from "../../llm/structured-call.js";
|
|
23
24
|
import { getBodyEmbeddings, upsertBodyEmbeddings } from "../../storage/repositories/embeddings-repository.js";
|
|
24
|
-
import { closeDatabase, openExistingDatabase } from "../../storage/repositories/index-connection.js";
|
|
25
|
+
import { closeDatabase, openExistingDatabase, openReadonlyExistingDatabase, } from "../../storage/repositories/index-connection.js";
|
|
25
26
|
import { findEntryIdByRef, getAllEntries, getEntryById } from "../../storage/repositories/index-entries-repository.js";
|
|
26
27
|
import { getNeighborsByEntryId } from "../../storage/repositories/index-vec-repository.js";
|
|
27
|
-
import { isProposalSkipped, listProposals, proposalContent } from "../proposal/repository.js";
|
|
28
|
+
import { isProposalSkipped, listProposals, listProposalsReadOnly, proposalContent, } from "../proposal/repository.js";
|
|
28
29
|
import { hasSupersededStatus, validateProposalFrontmatter } from "../proposal/validators/proposal-quality-validators.js";
|
|
29
30
|
import { cacheHash } from "./content-hash.js";
|
|
31
|
+
import { resolveImproveLlmExecution } from "./execution.js";
|
|
30
32
|
import { resolveImproveStrategy, resolveProcessEnabled } from "./improve-strategies.js";
|
|
31
33
|
import { emitProposal } from "./proposal-envelope.js";
|
|
32
34
|
import { createRunContext } from "./run-context.js";
|
|
@@ -271,7 +273,7 @@ async function clusterMemoriesBySimilarity(memories, config, stateDb, signal) {
|
|
|
271
273
|
function loadPendingConsolidateProposalHashes(stashDir) {
|
|
272
274
|
const hashes = new Set();
|
|
273
275
|
try {
|
|
274
|
-
const pending =
|
|
276
|
+
const pending = listProposalsReadOnly(stashDir, { status: "pending" }).filter((proposal) => proposal.source === "consolidate");
|
|
275
277
|
for (const p of pending) {
|
|
276
278
|
try {
|
|
277
279
|
hashes.add(cacheHash(proposalContent(p)));
|
|
@@ -350,30 +352,36 @@ function promoteProvenanceXrefs(existing, sourceRef) {
|
|
|
350
352
|
}
|
|
351
353
|
// ── LLM resolution ──────────────────────────────────────────────────────────
|
|
352
354
|
/**
|
|
353
|
-
* Resolve the LLM
|
|
355
|
+
* Resolve the symbolic LLM runner for the consolidate pass.
|
|
354
356
|
*
|
|
355
357
|
* Priority order (mirrors extract / reflect / distill — see
|
|
356
358
|
* `resolveExtractRunConfig` in `src/commands/improve/extract.ts` and the
|
|
357
|
-
* canonical
|
|
359
|
+
* canonical improve execution-cascade pattern):
|
|
358
360
|
*
|
|
359
361
|
* 1. `improve.strategies.<name>.processes.consolidate.engine`
|
|
360
|
-
* via
|
|
362
|
+
* via the common execution planner. Lets the user pin
|
|
361
363
|
* a dedicated model (e.g. `ministral-3b`) for consolidation instead of
|
|
362
364
|
* whatever `defaults.llmEngine` happens to be.
|
|
363
|
-
* 2.
|
|
365
|
+
* 2. the baseline default LLM engine.
|
|
364
366
|
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
* `processes.consolidate.profile`, sending every chunk to the default LLM
|
|
368
|
-
* (often a long-context model loaded with a smaller runtime `n_ctx`, causing
|
|
369
|
-
* silent 400s from LM Studio). The investigation lives at
|
|
370
|
-
* `/tmp/akm-health-investigations/consolidation-no-op.md`.
|
|
367
|
+
* All consolidate execution crosses the same improve engine-resolution
|
|
368
|
+
* boundary as extract, reflect, and distill.
|
|
371
369
|
*/
|
|
372
|
-
function
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
370
|
+
function resolveConsolidateLlmRunner(config, activeProfile) {
|
|
371
|
+
return resolveImproveLlmExecution({
|
|
372
|
+
config,
|
|
373
|
+
profile: activeProfile,
|
|
374
|
+
process: getImproveProcessConfig("consolidate", activeProfile),
|
|
375
|
+
processName: "consolidate",
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
function consolidateRunnerFromOptions(opts, config) {
|
|
379
|
+
if (Object.hasOwn(opts, "llmRunner"))
|
|
380
|
+
return opts.llmRunner ?? undefined;
|
|
381
|
+
const resolved = resolveConsolidateLlmRunner(config, opts.improveProfile);
|
|
382
|
+
if (resolved)
|
|
383
|
+
opts.onNotices?.(resolved.notices);
|
|
384
|
+
return resolved?.runner;
|
|
377
385
|
}
|
|
378
386
|
/**
|
|
379
387
|
* Build a {@link ConsolidateResult} from partial overrides, filling the envelope
|
|
@@ -434,8 +442,24 @@ export async function akmConsolidate(opts = {}) {
|
|
|
434
442
|
const config = opts.config ?? loadConfig();
|
|
435
443
|
const writeTarget = resolveConsolidationWriteTarget(opts, config);
|
|
436
444
|
opts = { ...opts, target: writeTarget.source.name, writeTarget };
|
|
437
|
-
|
|
445
|
+
const activeProfile = opts.improveProfile ?? resolveImproveStrategy(undefined, config).config;
|
|
446
|
+
opts = { ...opts, improveProfile: activeProfile };
|
|
438
447
|
const stashDir = writeTarget.source.path;
|
|
448
|
+
const executionNotices = new Map();
|
|
449
|
+
const externalOnNotices = opts.onNotices;
|
|
450
|
+
const collectNotices = (notices) => {
|
|
451
|
+
for (const notice of notices)
|
|
452
|
+
executionNotices.set(JSON.stringify(notice), notice);
|
|
453
|
+
externalOnNotices?.(notices);
|
|
454
|
+
};
|
|
455
|
+
opts = { ...opts, onNotices: collectNotices };
|
|
456
|
+
const consolidateEnabled = resolveProcessEnabled("consolidate", activeProfile);
|
|
457
|
+
const frozenLlmRunner = consolidateEnabled ? consolidateRunnerFromOptions(opts, config) : undefined;
|
|
458
|
+
// Own the field even when no runner exists. Every downstream reader now
|
|
459
|
+
// observes this one symbolic snapshot instead of re-running config/model-map
|
|
460
|
+
// selection during the same invocation.
|
|
461
|
+
opts = { ...opts, llmRunner: frozenLlmRunner ?? null };
|
|
462
|
+
const withNotices = (result) => executionNotices.size > 0 ? { ...result, notices: Object.freeze([...executionNotices.values()]) } : result;
|
|
439
463
|
// WI-9.10: construct this run's RunContext from values already resolved
|
|
440
464
|
// above (sourceRun, config, stashDir) — no second config load, no new db
|
|
441
465
|
// handle. consolidate.ts has no `eventsCtx`/proposals-`ctx` option at all
|
|
@@ -443,31 +467,21 @@ export async function akmConsolidate(opts = {}) {
|
|
|
443
467
|
// called with the default, seam-less ProposalsContext — see
|
|
444
468
|
// emitPromotionProposal below), so both get the safe empty-object default,
|
|
445
469
|
// behaviorally identical to `undefined` (EventsContext/ProposalsContext
|
|
446
|
-
// fields are all optional-chained by their consumers).
|
|
447
|
-
//
|
|
448
|
-
// verbatim but lazily and independently — nothing calls `ctx.getLlmConfig`
|
|
449
|
-
// yet this stage, so this never duplicates real work, only the (pure,
|
|
450
|
-
// side-effect-free) resolution logic if invoked. consolidate has no `chat`
|
|
451
|
-
// seam (it drives the LLM directly via the HTTP client path, never through
|
|
452
|
-
// `chatCompletion`), so that field is left to its default.
|
|
470
|
+
// fields are all optional-chained by their consumers). LLM work uses the
|
|
471
|
+
// already-frozen symbolic runner through the shared dispatch seam.
|
|
453
472
|
const runContext = createRunContext({
|
|
454
473
|
stashDir,
|
|
455
474
|
config,
|
|
456
475
|
eventsCtx: {},
|
|
457
476
|
proposalsCtx: {},
|
|
458
|
-
|
|
459
|
-
const resolved = Object.hasOwn(opts, "llmConfig")
|
|
460
|
-
? (opts.llmConfig ?? undefined)
|
|
461
|
-
: resolveConsolidateLlmConfig(config, opts.improveProfile);
|
|
462
|
-
return resolved ?? null;
|
|
463
|
-
},
|
|
477
|
+
getLlmRunner: () => opts.llmRunner ?? null,
|
|
464
478
|
sourceRun,
|
|
465
479
|
dryRun: opts.dryRun ?? false,
|
|
466
480
|
signal: opts.signal,
|
|
467
481
|
});
|
|
468
482
|
const warnings = [];
|
|
469
|
-
if (!
|
|
470
|
-
return makeConsolidateResult({
|
|
483
|
+
if (!consolidateEnabled) {
|
|
484
|
+
return withNotices(makeConsolidateResult({
|
|
471
485
|
// Sourced from runContext (identical value to `opts.dryRun ?? false`)
|
|
472
486
|
// so the constructed RunContext has a genuine downstream reference —
|
|
473
487
|
// consolidate's own content-read sites are out of this stage's stated
|
|
@@ -476,21 +490,23 @@ export async function akmConsolidate(opts = {}) {
|
|
|
476
490
|
target: opts.target ?? stashDir,
|
|
477
491
|
durationMs: Date.now() - startMs,
|
|
478
492
|
warnings,
|
|
479
|
-
});
|
|
493
|
+
}));
|
|
480
494
|
}
|
|
481
495
|
// WS-3a: open one state.db handle shared by the body-embedding cache (dedup
|
|
482
496
|
// + cluster) and the judged-state cache. All callers in the function body
|
|
483
497
|
// receive this handle; it is closed in the `finally` block below.
|
|
484
498
|
// Fail-open: any open error leaves it `undefined` and all cache paths skip.
|
|
485
499
|
let sharedStateDb;
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
500
|
+
if (config.embedding) {
|
|
501
|
+
try {
|
|
502
|
+
sharedStateDb = openStateDatabase();
|
|
503
|
+
}
|
|
504
|
+
catch {
|
|
505
|
+
// State DB unavailable → skip the embedding cache for this run.
|
|
506
|
+
}
|
|
491
507
|
}
|
|
492
508
|
try {
|
|
493
|
-
return await akmConsolidateInner(opts, config, stashDir, startMs, warnings, sharedStateDb);
|
|
509
|
+
return withNotices(await akmConsolidateInner(opts, config, stashDir, startMs, warnings, sharedStateDb));
|
|
494
510
|
}
|
|
495
511
|
finally {
|
|
496
512
|
sharedStateDb?.close();
|
|
@@ -530,6 +546,65 @@ function createConsolidateAccounting() {
|
|
|
530
546
|
};
|
|
531
547
|
return acc;
|
|
532
548
|
}
|
|
549
|
+
function resolveConsolidationSourceOwner(opts, stashDir) {
|
|
550
|
+
const targetRoot = path.resolve(opts.writeTarget?.source.path ?? stashDir);
|
|
551
|
+
try {
|
|
552
|
+
const sources = resolveSourceEntries(stashDir, opts.config);
|
|
553
|
+
const installations = deriveInstallations(sources);
|
|
554
|
+
const targetIndex = sources.findIndex((source) => path.resolve(source.path) === targetRoot);
|
|
555
|
+
const target = installations[targetIndex];
|
|
556
|
+
if (!target)
|
|
557
|
+
return undefined;
|
|
558
|
+
return {
|
|
559
|
+
bundleId: target.id,
|
|
560
|
+
sourceRoot: targetRoot,
|
|
561
|
+
excludedSourceRoots: new Set(sources
|
|
562
|
+
.filter((_, index) => index !== targetIndex)
|
|
563
|
+
.map((source) => path.resolve(source.path))
|
|
564
|
+
.filter((sourceRoot) => sourceRoot.startsWith(`${targetRoot}${path.sep}`))),
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
catch {
|
|
568
|
+
return undefined;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Read and narrow the exact pool the live pass consumes, without embedding,
|
|
573
|
+
* LLM, proposal, event, or asset writes. Used by both preview and execution.
|
|
574
|
+
*/
|
|
575
|
+
export function inspectConsolidationPool(opts, stashDir, warnings, access) {
|
|
576
|
+
const readOnly = access?.readOnly === true;
|
|
577
|
+
const sourceOwner = resolveConsolidationSourceOwner(opts, stashDir);
|
|
578
|
+
let memories = loadMemoriesForSource(sourceOwner, warnings, readOnly);
|
|
579
|
+
const staleCount = memories.filter((memory) => !fs.existsSync(memory.filePath)).length;
|
|
580
|
+
if (staleCount > 0) {
|
|
581
|
+
warnings.push(`Pre-flight: filtered ${staleCount} stale DB entr${staleCount === 1 ? "y" : "ies"} (file absent on disk) from memory pool before chunking.`);
|
|
582
|
+
}
|
|
583
|
+
memories = memories.filter((memory) => fs.existsSync(memory.filePath));
|
|
584
|
+
const poolSize = memories.length;
|
|
585
|
+
if (opts.incrementalSince && memories.length > 0) {
|
|
586
|
+
memories = narrowToIncrementalCandidates(memories, opts.incrementalSince, warnings, opts.neighborsPerChanged, readOnly);
|
|
587
|
+
}
|
|
588
|
+
const dedupPoolSize = memories.length;
|
|
589
|
+
if (opts.limit === undefined && memories.length > 150) {
|
|
590
|
+
warnings.push(`Consolidation: pool has ${memories.length} memories and no limit is set. Consider adding a limit to your consolidate config to prevent timeouts on slow LLM endpoints.`);
|
|
591
|
+
}
|
|
592
|
+
if (opts.limit !== undefined && memories.length > opts.limit) {
|
|
593
|
+
const mtimeOf = (memory) => {
|
|
594
|
+
try {
|
|
595
|
+
return fs.statSync(memory.filePath).mtimeMs;
|
|
596
|
+
}
|
|
597
|
+
catch {
|
|
598
|
+
return 0;
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
const mtimeCache = new Map(memories.map((memory) => [memory.filePath, mtimeOf(memory)]));
|
|
602
|
+
memories = [...memories].sort((a, b) => (mtimeCache.get(a.filePath) ?? 0) - (mtimeCache.get(b.filePath) ?? 0));
|
|
603
|
+
warnings.push(`Consolidation: pool capped at ${opts.limit} of ${memories.length} memories (limit option, oldest-modified first).`);
|
|
604
|
+
memories = memories.slice(0, opts.limit);
|
|
605
|
+
}
|
|
606
|
+
return { poolSize, candidatePoolSize: memories.length, dedupPoolSize, memories };
|
|
607
|
+
}
|
|
533
608
|
/**
|
|
534
609
|
* Pass 1 — narrow the memory pool before any LLM work: drop stale DB entries,
|
|
535
610
|
* apply incremental-since narrowing, and cap to `opts.limit` (oldest-modified
|
|
@@ -538,17 +613,8 @@ function createConsolidateAccounting() {
|
|
|
538
613
|
* consume. Behavior-identical to the former inlined narrowing block.
|
|
539
614
|
*/
|
|
540
615
|
async function narrowConsolidationPool(opts, stashDir, startMs, warnings) {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
// disk. Without this, memories deleted by a prior run (but not yet
|
|
544
|
-
// reindexed) appear in chunk prompts, causing the LLM to generate plans
|
|
545
|
-
// against ghost refs and wasting tokens. Filtering here ensures the chunk
|
|
546
|
-
// pool and memoryByRef are authoritative against the actual filesystem state.
|
|
547
|
-
const staleCount = memories.filter((m) => !fs.existsSync(m.filePath)).length;
|
|
548
|
-
if (staleCount > 0) {
|
|
549
|
-
warnings.push(`Pre-flight: filtered ${staleCount} stale DB entr${staleCount === 1 ? "y" : "ies"} (file absent on disk) from memory pool before chunking.`);
|
|
550
|
-
}
|
|
551
|
-
memories = memories.filter((m) => fs.existsSync(m.filePath));
|
|
616
|
+
const snapshot = inspectConsolidationPool(opts, stashDir, warnings);
|
|
617
|
+
const memories = snapshot.memories;
|
|
552
618
|
// (The former WS-3b Step 0a homeostatic demotion pass was removed — R4:
|
|
553
619
|
// it was default-off and self-undoing (the next salience recompute
|
|
554
620
|
// unconditionally overwrote the demoted values). Continuous decay now lives
|
|
@@ -564,50 +630,7 @@ async function narrowConsolidationPool(opts, stashDir, startMs, warnings) {
|
|
|
564
630
|
}),
|
|
565
631
|
};
|
|
566
632
|
}
|
|
567
|
-
|
|
568
|
-
memories = narrowToIncrementalCandidates(memories, opts.incrementalSince, warnings, opts.neighborsPerChanged);
|
|
569
|
-
if (memories.length === 0) {
|
|
570
|
-
return {
|
|
571
|
-
done: true,
|
|
572
|
-
result: makeConsolidateResult({
|
|
573
|
-
dryRun: opts.dryRun ?? false,
|
|
574
|
-
target: opts.target ?? stashDir,
|
|
575
|
-
warnings,
|
|
576
|
-
durationMs: Date.now() - startMs,
|
|
577
|
-
}),
|
|
578
|
-
};
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
// WS-5 perf telemetry: `dedupPoolSize` = memories entering the LLM pool
|
|
582
|
-
// (after incremental narrowing, before the limit cap). `llmPoolSize` =
|
|
583
|
-
// memories actually sent to the LLM. `embedMs/cacheHits/cacheMisses` =
|
|
584
|
-
// accumulated from clusterMemoriesBySimilarity.
|
|
585
|
-
const dedupPoolSize = memories.length;
|
|
586
|
-
if (opts.limit === undefined && memories.length > 150) {
|
|
587
|
-
warnings.push(`Consolidation: pool has ${memories.length} memories and no limit is set. Consider adding a limit to your consolidate config to prevent timeouts on slow LLM endpoints.`);
|
|
588
|
-
}
|
|
589
|
-
if (opts.limit !== undefined && memories.length > opts.limit) {
|
|
590
|
-
// Order oldest-modified-first before capping so the limit selects the
|
|
591
|
-
// stalest memories rather than a fixed head of the (rowid-ordered) DB
|
|
592
|
-
// query. Consolidation rewrites surviving files, bumping their mtime, so
|
|
593
|
-
// processed memories drift to the back of the queue and the cap rotates
|
|
594
|
-
// across the whole corpus over successive runs instead of revisiting the
|
|
595
|
-
// same slice every time. Fail-open to 0 (front of queue) when a file can
|
|
596
|
-
// no longer be stat'd.
|
|
597
|
-
const mtimeOf = (m) => {
|
|
598
|
-
try {
|
|
599
|
-
return fs.statSync(m.filePath).mtimeMs;
|
|
600
|
-
}
|
|
601
|
-
catch {
|
|
602
|
-
return 0;
|
|
603
|
-
}
|
|
604
|
-
};
|
|
605
|
-
const mtimeCache = new Map(memories.map((m) => [m.filePath, mtimeOf(m)]));
|
|
606
|
-
memories = [...memories].sort((a, b) => (mtimeCache.get(a.filePath) ?? 0) - (mtimeCache.get(b.filePath) ?? 0));
|
|
607
|
-
warnings.push(`Consolidation: pool capped at ${opts.limit} of ${memories.length} memories (limit option, oldest-modified first).`);
|
|
608
|
-
memories = memories.slice(0, opts.limit);
|
|
609
|
-
}
|
|
610
|
-
return { done: false, memories, dedupPoolSize };
|
|
633
|
+
return { done: false, memories, dedupPoolSize: snapshot.dedupPoolSize };
|
|
611
634
|
}
|
|
612
635
|
/**
|
|
613
636
|
* Pass 2 — turn the narrowed pool into an executable plan. Sizes chunks to the
|
|
@@ -656,7 +679,7 @@ function recordChunkJudgedNoAction(chunk, ops, accounting) {
|
|
|
656
679
|
* are byte-identical, and every counter-increment point is unmoved.
|
|
657
680
|
*/
|
|
658
681
|
async function judgeConsolidationChunks(args) {
|
|
659
|
-
const { chunks, opts, config,
|
|
682
|
+
const { chunks, opts, config, llmRunner, lease, sourceName, bodyTruncation, pendingProposalBodyHashes, standardsContext, warnings, accounting, } = args;
|
|
660
683
|
const chunkOpsArrays = [];
|
|
661
684
|
// judgedNoAction tracks memories the LLM saw inside a chunk but proposed
|
|
662
685
|
// no op for. Computed per chunk as `chunk.length − unique(targetRefs in ops)`.
|
|
@@ -733,13 +756,14 @@ async function judgeConsolidationChunks(args) {
|
|
|
733
756
|
const callChunkLlm = async (fallbackError) => {
|
|
734
757
|
// The gate runs with enabled:true (always open), so this guard is
|
|
735
758
|
// exactly the envelope the gated fn used to return first thing.
|
|
736
|
-
if (!
|
|
759
|
+
if (!llmRunner)
|
|
737
760
|
return { ok: false, error: "No LLM configured for consolidation" };
|
|
738
761
|
return callStructured({
|
|
739
762
|
feature: "memory_consolidation",
|
|
740
763
|
akmConfig: config,
|
|
741
764
|
enabled: true,
|
|
742
|
-
|
|
765
|
+
runner: llmRunner,
|
|
766
|
+
...(lease ? { lease } : {}),
|
|
743
767
|
messages: [
|
|
744
768
|
{ role: "system", content: CONSOLIDATE_SYSTEM_PROMPT },
|
|
745
769
|
{ role: "user", content: userPrompt },
|
|
@@ -747,7 +771,7 @@ async function judgeConsolidationChunks(args) {
|
|
|
747
771
|
request: {
|
|
748
772
|
responseSchema: CONSOLIDATE_PLAN_JSON_SCHEMA,
|
|
749
773
|
enableThinking: false,
|
|
750
|
-
timeoutMs:
|
|
774
|
+
timeoutMs: llmRunner.timeoutMs,
|
|
751
775
|
signal: opts.signal,
|
|
752
776
|
},
|
|
753
777
|
parse: (raw) => ({ ok: true, content: raw ?? "" }),
|
|
@@ -756,6 +780,7 @@ async function judgeConsolidationChunks(args) {
|
|
|
756
780
|
// reproduces that. The fallback fires only on wrapper timeout.
|
|
757
781
|
onError: (_cls, e) => ({ ok: false, error: String(e) }),
|
|
758
782
|
fallback: { ok: false, error: fallbackError },
|
|
783
|
+
...(opts.onNotices ? { onNotices: opts.onNotices } : {}),
|
|
759
784
|
});
|
|
760
785
|
};
|
|
761
786
|
let raw = await callChunkLlm(`chunk ${chunkIdx + 1} failed`);
|
|
@@ -824,11 +849,8 @@ async function planConsolidation(opts, config, stashDir, _startMs, memories, war
|
|
|
824
849
|
// CLI. The agent CLI is for interactive agent sessions (reflect, propose);
|
|
825
850
|
// structured JSON generation works better and faster via HTTP.
|
|
826
851
|
//
|
|
827
|
-
//
|
|
828
|
-
|
|
829
|
-
const llmConfig = Object.hasOwn(opts, "llmConfig")
|
|
830
|
-
? (opts.llmConfig ?? undefined)
|
|
831
|
-
: resolveConsolidateLlmConfig(config, opts.improveProfile);
|
|
852
|
+
// The outer invocation freezes standalone and improve-owned selection once.
|
|
853
|
+
const llmRunner = opts.llmRunner ?? undefined;
|
|
832
854
|
// Chunk sizing: derive a safe chunk size from the configured model context
|
|
833
855
|
// window so that the full prompt (system prompt + chunk user prompt) never
|
|
834
856
|
// exceeds the model's n_ctx limit. When no context length is configured we
|
|
@@ -840,7 +862,7 @@ async function planConsolidation(opts, config, stashDir, _startMs, memories, war
|
|
|
840
862
|
// keep it fixed and let computeSafeChunkSize vary the number of memories
|
|
841
863
|
// per chunk instead.
|
|
842
864
|
const bodyTruncation = 500;
|
|
843
|
-
const modelContextLength =
|
|
865
|
+
const modelContextLength = llmRunner?.connection.contextLength ?? DEFAULT_CONTEXT_LENGTH_TOKENS;
|
|
844
866
|
const chunkSize = computeSafeChunkSize(modelContextLength, bodyTruncation, opts.maxChunkSize);
|
|
845
867
|
// -- Phase A: plan generation -----------------------------------------------
|
|
846
868
|
const sourceName = opts.target ?? stashDir;
|
|
@@ -874,102 +896,117 @@ async function planConsolidation(opts, config, stashDir, _startMs, memories, war
|
|
|
874
896
|
}
|
|
875
897
|
// WS-5: capture llmPoolSize after every pre-LLM cap.
|
|
876
898
|
const llmPoolSize = budgetedMemories.length;
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
899
|
+
const dispatchingChunks = [];
|
|
900
|
+
for (let i = 0; i < budgetedMemories.length; i += chunkSize) {
|
|
901
|
+
const chunk = budgetedMemories.slice(i, i + chunkSize);
|
|
902
|
+
if (chunk.length > 0 && !chunk.every((memory) => isHotCapturedMemory(memory.filePath))) {
|
|
903
|
+
dispatchingChunks.push(chunk);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
const dispatchLease = llmRunner && dispatchingChunks.length > 0 ? await preflightStructuredLlmRunner(llmRunner) : undefined;
|
|
907
|
+
try {
|
|
908
|
+
// C-1 / #380: Pre-cluster memories by embedding similarity before chunking.
|
|
909
|
+
// This ensures that semantically similar memories land in the same LLM
|
|
910
|
+
// context window, allowing the model to detect and merge duplicates that
|
|
911
|
+
// would otherwise be split across chunks and survive indefinitely.
|
|
912
|
+
// mem0 arXiv:2504.19413, A-MEM arXiv:2502.12110.
|
|
913
|
+
// Fails open: if embeddings are unavailable or fail, original order is used.
|
|
914
|
+
const { ordered: clusteredMemories, embedTelemetry } = await clusterMemoriesBySimilarity(budgetedMemories, config, sharedStateDb, opts.signal);
|
|
915
|
+
// WS-3b Anti-collapse step 8c: inject random (non-similar) clusters.
|
|
916
|
+
// A small fraction (default 5%) of the pool is shuffled into random positions
|
|
917
|
+
// so the pipeline isn't PURELY similarity-driven. This prevents rich-get-richer
|
|
918
|
+
// entrenchment where only the most-retrieved assets ever get consolidated.
|
|
919
|
+
// DEFAULT ON since R5 — opt out via antiCollapse.enabled: false.
|
|
920
|
+
let finalClusteredMemories = clusteredMemories;
|
|
921
|
+
{
|
|
922
|
+
const antiCollapseForCluster = getImproveProcessConfig("consolidate", opts.improveProfile)?.antiCollapse ??
|
|
923
|
+
{};
|
|
924
|
+
if (antiCollapseForCluster.enabled !== false && clusteredMemories.length > 2) {
|
|
925
|
+
const fraction = antiCollapseForCluster.randomClusterFraction ?? 0.05;
|
|
926
|
+
const randomCount = Math.max(1, Math.floor(clusteredMemories.length * fraction));
|
|
927
|
+
// Pick `randomCount` positions to inject random (un-clustered) members.
|
|
928
|
+
// Use a seeded-ish shuffle: sort by hash of the name so it's deterministic
|
|
929
|
+
// per run but not strictly similarity-driven.
|
|
930
|
+
const shuffled = [...clusteredMemories].sort((a, b) => {
|
|
931
|
+
// Deterministic shuffle: compare sha256-ish (use name hash as proxy).
|
|
932
|
+
const ha = a.name.split("").reduce((acc, c) => ((acc << 5) - acc + c.charCodeAt(0)) | 0, 0);
|
|
933
|
+
const hb = b.name.split("").reduce((acc, c) => ((acc << 5) - acc + c.charCodeAt(0)) | 0, 0);
|
|
934
|
+
return ha - hb;
|
|
935
|
+
});
|
|
936
|
+
const randomSlice = shuffled.slice(0, randomCount);
|
|
937
|
+
const randomSet = new Set(randomSlice.map((m) => m.name));
|
|
938
|
+
// Insert random members at intervals through the clustered sequence.
|
|
939
|
+
const withRandom = [];
|
|
940
|
+
const interval = Math.max(2, Math.floor(clusteredMemories.length / randomCount));
|
|
941
|
+
let randomIdx = 0;
|
|
942
|
+
for (let i = 0; i < clusteredMemories.length; i++) {
|
|
943
|
+
const m = clusteredMemories[i];
|
|
944
|
+
if (m && !randomSet.has(m.name))
|
|
945
|
+
withRandom.push(m);
|
|
946
|
+
if (i > 0 && i % interval === 0 && randomIdx < randomSlice.length) {
|
|
947
|
+
const r = randomSlice[randomIdx++];
|
|
948
|
+
if (r)
|
|
949
|
+
withRandom.push(r);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
// Append any remaining random members not yet inserted.
|
|
953
|
+
while (randomIdx < randomSlice.length) {
|
|
916
954
|
const r = randomSlice[randomIdx++];
|
|
917
955
|
if (r)
|
|
918
956
|
withRandom.push(r);
|
|
919
957
|
}
|
|
958
|
+
finalClusteredMemories = withRandom;
|
|
959
|
+
warnings.push(`Anti-collapse: injected ${randomCount} random (non-similarity-driven) cluster member(s) into consolidation pool (fraction=${fraction}).`);
|
|
920
960
|
}
|
|
921
|
-
// Append any remaining random members not yet inserted.
|
|
922
|
-
while (randomIdx < randomSlice.length) {
|
|
923
|
-
const r = randomSlice[randomIdx++];
|
|
924
|
-
if (r)
|
|
925
|
-
withRandom.push(r);
|
|
926
|
-
}
|
|
927
|
-
finalClusteredMemories = withRandom;
|
|
928
|
-
warnings.push(`Anti-collapse: injected ${randomCount} random (non-similarity-driven) cluster member(s) into consolidation pool (fraction=${fraction}).`);
|
|
929
961
|
}
|
|
962
|
+
const chunks = [];
|
|
963
|
+
for (let i = 0; i < finalClusteredMemories.length; i += chunkSize) {
|
|
964
|
+
chunks.push(finalClusteredMemories.slice(i, i + chunkSize));
|
|
965
|
+
}
|
|
966
|
+
// 2026-05-27 prompt-context fix: precompute body-hashes of pending
|
|
967
|
+
// consolidate proposals once, so the per-chunk prompt can annotate
|
|
968
|
+
// memories whose body would just produce a deterministic
|
|
969
|
+
// `dedup_pending_proposal` skip. Cuts ~110 wasted LLM proposals per
|
|
970
|
+
// 4h on this user's stack. See
|
|
971
|
+
// /tmp/akm-health-investigations/tuning-reasons-investigation.md §Q3.
|
|
972
|
+
const pendingProposalBodyHashes = loadPendingConsolidateProposalHashes(stashDir);
|
|
973
|
+
warn(`[consolidate] ${budgetedMemories.length} memories / ${chunks.length} chunk(s) / chunk_size=${chunkSize}` +
|
|
974
|
+
` / pending-proposal hashes: ${pendingProposalBodyHashes.size}`);
|
|
975
|
+
// Consolidate output merges memories (non-wiki) → stash authoring standards.
|
|
976
|
+
// Resolved ONCE per run and passed to each chunk prompt (facts not re-read
|
|
977
|
+
// per chunk).
|
|
978
|
+
const standardsContext = resolveStandardsContext("memories/_consolidated", stashDir);
|
|
979
|
+
const chunkOpsArrays = await judgeConsolidationChunks({
|
|
980
|
+
chunks,
|
|
981
|
+
opts,
|
|
982
|
+
config,
|
|
983
|
+
llmRunner,
|
|
984
|
+
lease: dispatchLease,
|
|
985
|
+
sourceName,
|
|
986
|
+
bodyTruncation,
|
|
987
|
+
pendingProposalBodyHashes,
|
|
988
|
+
standardsContext,
|
|
989
|
+
warnings,
|
|
990
|
+
accounting,
|
|
991
|
+
});
|
|
992
|
+
// Build the known-refs set from the already-filtered memory pool so
|
|
993
|
+
// mergePlans() can reject LLM-hallucinated primary refs before execution.
|
|
994
|
+
const knownRefs = new Set(budgetedMemories.map((m) => conceptIdFromTypeName("memory", m.name)));
|
|
995
|
+
const { ops: allOps, warnings: mergeWarnings } = mergePlans(chunkOpsArrays, knownRefs);
|
|
996
|
+
warnings.push(...mergeWarnings);
|
|
997
|
+
return {
|
|
998
|
+
allOps,
|
|
999
|
+
totalChunks: chunks.length,
|
|
1000
|
+
llmPoolSize,
|
|
1001
|
+
deferredMemories: memories.length - budgetedMemories.length,
|
|
1002
|
+
embedTelemetry,
|
|
1003
|
+
sourceName,
|
|
1004
|
+
};
|
|
930
1005
|
}
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
1006
|
+
finally {
|
|
1007
|
+
if (dispatchLease)
|
|
1008
|
+
disposeLoweredExecutionDispatchLease(dispatchLease);
|
|
934
1009
|
}
|
|
935
|
-
// 2026-05-27 prompt-context fix: precompute body-hashes of pending
|
|
936
|
-
// consolidate proposals once, so the per-chunk prompt can annotate
|
|
937
|
-
// memories whose body would just produce a deterministic
|
|
938
|
-
// `dedup_pending_proposal` skip. Cuts ~110 wasted LLM proposals per
|
|
939
|
-
// 4h on this user's stack. See
|
|
940
|
-
// /tmp/akm-health-investigations/tuning-reasons-investigation.md §Q3.
|
|
941
|
-
const pendingProposalBodyHashes = loadPendingConsolidateProposalHashes(stashDir);
|
|
942
|
-
warn(`[consolidate] ${budgetedMemories.length} memories / ${chunks.length} chunk(s) / chunk_size=${chunkSize}` +
|
|
943
|
-
` / pending-proposal hashes: ${pendingProposalBodyHashes.size}`);
|
|
944
|
-
// Consolidate output merges memories (non-wiki) → stash authoring standards.
|
|
945
|
-
// Resolved ONCE per run and passed to each chunk prompt (facts not re-read
|
|
946
|
-
// per chunk).
|
|
947
|
-
const standardsContext = resolveStandardsContext("memories/_consolidated", stashDir);
|
|
948
|
-
const chunkOpsArrays = await judgeConsolidationChunks({
|
|
949
|
-
chunks,
|
|
950
|
-
opts,
|
|
951
|
-
config,
|
|
952
|
-
llmConfig,
|
|
953
|
-
sourceName,
|
|
954
|
-
bodyTruncation,
|
|
955
|
-
pendingProposalBodyHashes,
|
|
956
|
-
standardsContext,
|
|
957
|
-
warnings,
|
|
958
|
-
accounting,
|
|
959
|
-
});
|
|
960
|
-
// Build the known-refs set from the already-filtered memory pool so
|
|
961
|
-
// mergePlans() can reject LLM-hallucinated primary refs before execution.
|
|
962
|
-
const knownRefs = new Set(budgetedMemories.map((m) => conceptIdFromTypeName("memory", m.name)));
|
|
963
|
-
const { ops: allOps, warnings: mergeWarnings } = mergePlans(chunkOpsArrays, knownRefs);
|
|
964
|
-
warnings.push(...mergeWarnings);
|
|
965
|
-
return {
|
|
966
|
-
allOps,
|
|
967
|
-
totalChunks: chunks.length,
|
|
968
|
-
llmPoolSize,
|
|
969
|
-
deferredMemories: memories.length - budgetedMemories.length,
|
|
970
|
-
embedTelemetry,
|
|
971
|
-
sourceName,
|
|
972
|
-
};
|
|
973
1010
|
}
|
|
974
1011
|
async function akmConsolidateInner(opts, config, stashDir, startMs, warnings, sharedStateDb) {
|
|
975
1012
|
// -- Pass 1: narrow the memory pool (may early-return an envelope) ----------
|
|
@@ -1020,9 +1057,7 @@ async function akmConsolidateInner(opts, config, stashDir, startMs, warnings, sh
|
|
|
1020
1057
|
promotionFailures,
|
|
1021
1058
|
warnings,
|
|
1022
1059
|
pushSkipReason: accounting.pushSkipReason,
|
|
1023
|
-
|
|
1024
|
-
? (opts.llmConfig ?? null)
|
|
1025
|
-
: (resolveConsolidateLlmConfig(config, opts.improveProfile) ?? null),
|
|
1060
|
+
llmRunner: opts.llmRunner ?? null,
|
|
1026
1061
|
};
|
|
1027
1062
|
for (const op of allOps) {
|
|
1028
1063
|
if (op.op === "promote")
|
|
@@ -1228,7 +1263,7 @@ export async function emitPromotionProposal(op, ctx) {
|
|
|
1228
1263
|
source: "consolidate",
|
|
1229
1264
|
sourceRun,
|
|
1230
1265
|
// §23.6 fingerprint model-id term (WI-6.4).
|
|
1231
|
-
...(ctx.
|
|
1266
|
+
...(ctx.llmRunner?.connection.model ? { modelId: ctx.llmRunner.connection.model } : {}),
|
|
1232
1267
|
payload: {
|
|
1233
1268
|
content: promotedAssetContent,
|
|
1234
1269
|
frontmatter: { description, xrefs: [canonicalXref(op.ref)] },
|
|
@@ -1332,7 +1367,7 @@ async function checkPreEmitDedup(opts) {
|
|
|
1332
1367
|
* everything changed or the index can't answer (fail-open to preserve merge
|
|
1333
1368
|
* correctness). `since` is an ISO timestamp.
|
|
1334
1369
|
*/
|
|
1335
|
-
export function narrowToIncrementalCandidates(memories, since, warnings, neighborsPerChanged = 5) {
|
|
1370
|
+
export function narrowToIncrementalCandidates(memories, since, warnings, neighborsPerChanged = 5, readOnly = false) {
|
|
1336
1371
|
// Lenient by design: garbage `since` passes through unchanged and the ISO
|
|
1337
1372
|
// string comparison below then selects nothing (see core/time.ts doc).
|
|
1338
1373
|
const sinceIso = parseSinceToIsoLenient(since);
|
|
@@ -1353,7 +1388,9 @@ export function narrowToIncrementalCandidates(memories, since, warnings, neighbo
|
|
|
1353
1388
|
const keep = new Set(changed.map((m) => m.name));
|
|
1354
1389
|
let db;
|
|
1355
1390
|
try {
|
|
1356
|
-
db = openExistingDatabase();
|
|
1391
|
+
db = readOnly ? openReadonlyExistingDatabase(undefined, { isolatedSnapshot: true }) : openExistingDatabase();
|
|
1392
|
+
if (!db)
|
|
1393
|
+
return memories;
|
|
1357
1394
|
for (const m of changed) {
|
|
1358
1395
|
const id = findEntryIdByRef(db, conceptIdFromTypeName("memory", m.name));
|
|
1359
1396
|
if (id === undefined)
|
|
@@ -1382,19 +1419,17 @@ export function narrowToIncrementalCandidates(memories, since, warnings, neighbo
|
|
|
1382
1419
|
warnings.push(`Incremental consolidation: ${changed.length} changed + neighbours → ${candidates.length}/${memories.length} memories considered (since ${since}${sinceIso !== since ? ` = ${sinceIso}` : ""}).`);
|
|
1383
1420
|
return candidates;
|
|
1384
1421
|
}
|
|
1385
|
-
function loadMemoriesForSource(source,
|
|
1422
|
+
function loadMemoriesForSource(source, warnings, readOnly) {
|
|
1386
1423
|
// Load from DB first
|
|
1387
1424
|
let memories = [];
|
|
1388
1425
|
let db;
|
|
1389
1426
|
try {
|
|
1390
|
-
db = openExistingDatabase();
|
|
1427
|
+
db = readOnly ? openReadonlyExistingDatabase(undefined, { isolatedSnapshot: true }) : openExistingDatabase();
|
|
1428
|
+
if (!db)
|
|
1429
|
+
throw new Error("index unavailable");
|
|
1391
1430
|
const entries = getAllEntries(db, "memory");
|
|
1392
1431
|
memories = entries
|
|
1393
|
-
.filter((
|
|
1394
|
-
if (!source)
|
|
1395
|
-
return true;
|
|
1396
|
-
return path.resolve(e.stashDir) === path.resolve(source);
|
|
1397
|
-
})
|
|
1432
|
+
.filter((entry) => source !== undefined && entry.bundleId === source.bundleId)
|
|
1398
1433
|
.filter((e) => isConsolidationEligibleMemoryName(e.entry.name))
|
|
1399
1434
|
// Skip stale DB entries whose file was deleted by a prior run but not yet
|
|
1400
1435
|
// re-indexed. Without this guard the deleted file's ref appears in chunks
|
|
@@ -1407,7 +1442,7 @@ function loadMemoriesForSource(source, stashDir, warnings) {
|
|
|
1407
1442
|
filePath: e.filePath,
|
|
1408
1443
|
description: e.entry.description ?? "",
|
|
1409
1444
|
tags: e.entry.tags ?? [],
|
|
1410
|
-
stashDir:
|
|
1445
|
+
stashDir: source?.sourceRoot ?? "",
|
|
1411
1446
|
}));
|
|
1412
1447
|
}
|
|
1413
1448
|
catch {
|
|
@@ -1417,10 +1452,10 @@ function loadMemoriesForSource(source, stashDir, warnings) {
|
|
|
1417
1452
|
if (db)
|
|
1418
1453
|
closeDatabase(db);
|
|
1419
1454
|
}
|
|
1420
|
-
if (memories.length === 0) {
|
|
1455
|
+
if (memories.length === 0 && source) {
|
|
1421
1456
|
// DB fallback: walk filesystem
|
|
1422
|
-
const memoriesDir = path.join(source
|
|
1423
|
-
const fsStashDir = source
|
|
1457
|
+
const memoriesDir = path.join(source.sourceRoot, "memories");
|
|
1458
|
+
const fsStashDir = source.sourceRoot;
|
|
1424
1459
|
if (fs.existsSync(memoriesDir)) {
|
|
1425
1460
|
const pending = [memoriesDir];
|
|
1426
1461
|
while (pending.length > 0) {
|
|
@@ -1428,6 +1463,8 @@ function loadMemoriesForSource(source, stashDir, warnings) {
|
|
|
1428
1463
|
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
1429
1464
|
const filePath = path.join(current, entry.name);
|
|
1430
1465
|
if (entry.isDirectory()) {
|
|
1466
|
+
if (source.excludedSourceRoots.has(path.resolve(filePath)))
|
|
1467
|
+
continue;
|
|
1431
1468
|
pending.push(filePath);
|
|
1432
1469
|
continue;
|
|
1433
1470
|
}
|