recallnest 1.4.1
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/.claude-plugin/CLAUDE.md +29 -0
- package/.claude-plugin/marketplace.json +18 -0
- package/.env.example +3 -0
- package/.github/workflows/ci.yml +28 -0
- package/.mcp.json +9 -0
- package/CHANGELOG.md +51 -0
- package/CLAUDE.md +42 -0
- package/DEMO.md +94 -0
- package/EVAL.md +89 -0
- package/FAILURES.md +75 -0
- package/LICENSE +21 -0
- package/OPERATIONS.md +59 -0
- package/README.md +427 -0
- package/README_CN.md +425 -0
- package/ROADMAP.md +198 -0
- package/assets/recallnest-workbench.png +0 -0
- package/assets/ui/app.js +807 -0
- package/assets/ui/index.html +154 -0
- package/assets/ui/styles.css +605 -0
- package/assets/wechat-qr.jpg +0 -0
- package/config.json.example +38 -0
- package/daily-recall.sh +87 -0
- package/daily-reflections/2026-03-05.md +15 -0
- package/daily-reflections/2026-03-06.md +15 -0
- package/daily-reflections/2026-03-07.md +7 -0
- package/daily-reflections/2026-03-08.md +34 -0
- package/daily-reflections/2026-03-09.md +35 -0
- package/daily-reflections/2026-03-10.md +36 -0
- package/daily-reflections/2026-03-11.md +35 -0
- package/daily-reflections/2026-03-12.md +36 -0
- package/daily-reflections/2026-03-13.md +35 -0
- package/daily-reflections/2026-03-14.md +35 -0
- package/daily-reflections/2026-03-15.md +35 -0
- package/daily-reflections/2026-03-16.md +41 -0
- package/daily-reflections/2026-03-17.md +10 -0
- package/daily-reflections/2026-03-18.md +10 -0
- package/daily-reflections/2026-03-19.md +10 -0
- package/daily-reflections/2026-03-20.md +10 -0
- package/daily-reflections/2026-03-21.md +10 -0
- package/daily-reflections/2026-03-22.md +10 -0
- package/daily-reflections/2026-03-23.md +10 -0
- package/daily-reflections/2026-03-24.md +10 -0
- package/daily-reflections/2026-03-25.md +32 -0
- package/daily-reflections/2026-03-26.md +37 -0
- package/daily-reflections/2026-03-27.md +35 -0
- package/daily-reflections/2026-03-28.md +35 -0
- package/daily-reflections/2026-03-29.md +35 -0
- package/daily-reflections/2026-03-30.md +36 -0
- package/daily-reflections/2026-03-31.md +35 -0
- package/daily-reflections/2026-04-01.md +35 -0
- package/docs/api-reference.md +1291 -0
- package/docs/architecture.md +168 -0
- package/docs/memory-boundary-contract.md +226 -0
- package/docs/memory-categories.md +119 -0
- package/docs/pipeline-audit-ultramemory.md +177 -0
- package/docs/plans/2026-03-08-query-expansion.md +270 -0
- package/docs/plans/2026-03-16-continuity-layer.md +196 -0
- package/docs/plans/mcp-tiering-plan.md +126 -0
- package/docs/research/recall-quality-audit-2026-03-22.md +153 -0
- package/docs/self-evolution.md +198 -0
- package/docs/superpowers/plans/2026-03-19-ingest-pipeline-upgrade.md +782 -0
- package/docs/superpowers/specs/2026-03-19-ingest-pipeline-upgrade-design.md +77 -0
- package/docs/superpowers/specs/2026-03-23-pull-claude-desktop-design.md +95 -0
- package/eval/cases.json +85 -0
- package/eval/continuity/case-seeds.json +90 -0
- package/eval/continuity/cases.json +302 -0
- package/eval/continuity/memory-seeds.json +26 -0
- package/eval/continuity/pattern-seeds.json +100 -0
- package/eval/continuity/results-2026-03-16.md +117 -0
- package/eval/continuity/results-2026-03-19.md +453 -0
- package/eval/continuity/runbook.md +110 -0
- package/eval/continuity/seed-cases.md +54 -0
- package/eval/reports/2026-03-06-abstract-query-baseline.md +96 -0
- package/eval/reports/2026-03-06-aws-ssh-baseline.md +82 -0
- package/eval/reports/2026-03-06-baseline.md +82 -0
- package/eval/reports/query-expansion.md +138 -0
- package/integrations/README.md +57 -0
- package/integrations/claude-code/README.md +133 -0
- package/integrations/claude-code/claude-md-snippet.md +31 -0
- package/integrations/claude-code/setup.sh +66 -0
- package/integrations/claude-code/smoke-continuity.sh +452 -0
- package/integrations/codex/README.md +49 -0
- package/integrations/codex/agents-md-snippet.md +31 -0
- package/integrations/codex/setup.sh +58 -0
- package/integrations/examples/claude-agent-sdk/memory-agent.ts +182 -0
- package/integrations/examples/langchain/memory-chain.py +111 -0
- package/integrations/examples/openai-agents-sdk/memory-agent.py +109 -0
- package/integrations/gemini-cli/README.md +57 -0
- package/integrations/gemini-cli/gemini-md-snippet.md +31 -0
- package/integrations/gemini-cli/setup.sh +64 -0
- package/integrations/shared/install-managed-block.sh +51 -0
- package/lm +19 -0
- package/package.json +50 -0
- package/plan-v1.2-distributable.md +105 -0
- package/scripts/archive-distilled-junk.ts +116 -0
- package/scripts/backfill-l0l1l2.ts +98 -0
- package/scripts/blind-scan.ts +588 -0
- package/scripts/cleanup-step1-exact-dups.ts +117 -0
- package/scripts/cleanup-step2-template-dups.ts +113 -0
- package/scripts/cleanup-step3-stale-content.ts +149 -0
- package/scripts/cleanup-step4-orphan-scopes.ts +131 -0
- package/scripts/delete-distilled-junk.ts +88 -0
- package/scripts/distill-facts.ts +305 -0
- package/scripts/health-check.ts +343 -0
- package/scripts/incremental-ingest.sh +71 -0
- package/scripts/mlp-health-check.ts +382 -0
- package/scripts/pull-claude-desktop.ts +515 -0
- package/scripts/re-ingest-cc.sh +66 -0
- package/scripts/rebalance-memory-health.ts +181 -0
- package/scripts/run-desktop-ingest.sh +11 -0
- package/scripts/start-server.sh +12 -0
- package/scripts/viz-dashboard.html +423 -0
- package/scripts/viz-export.ts +703 -0
- package/src/__tests__/access-tracker-novelty.test.ts +228 -0
- package/src/__tests__/auto-consolidation.test.ts +168 -0
- package/src/__tests__/auto-recall.test.ts +131 -0
- package/src/__tests__/batch-dedup.test.ts +65 -0
- package/src/__tests__/capture-engine.test.ts +1142 -0
- package/src/__tests__/capture-heuristic.test.ts +171 -0
- package/src/__tests__/cascade-forget.test.ts +210 -0
- package/src/__tests__/category-min-scores.test.ts +33 -0
- package/src/__tests__/confidence-tracker.test.ts +196 -0
- package/src/__tests__/conflict-advisor.test.ts +206 -0
- package/src/__tests__/conflict-escalation.test.ts +149 -0
- package/src/__tests__/conflict-lifecycle.test.ts +83 -0
- package/src/__tests__/conflict-output.test.ts +85 -0
- package/src/__tests__/conflict-store.test.ts +374 -0
- package/src/__tests__/consolidation-engine.test.ts +181 -0
- package/src/__tests__/context-composer-pins.test.ts +64 -0
- package/src/__tests__/context-composer-task-selection.test.ts +24 -0
- package/src/__tests__/context-renderer.test.ts +91 -0
- package/src/__tests__/continuity-eval.test.ts +275 -0
- package/src/__tests__/continuity-schema.test.ts +97 -0
- package/src/__tests__/core-recall-eval.test.ts +235 -0
- package/src/__tests__/core-summary.test.ts +109 -0
- package/src/__tests__/dedup-actions.test.ts +58 -0
- package/src/__tests__/deterministic-id.test.ts +39 -0
- package/src/__tests__/doctor.test.ts +303 -0
- package/src/__tests__/drill-down.test.ts +108 -0
- package/src/__tests__/embedder.test.ts +122 -0
- package/src/__tests__/entity-resolver.test.ts +58 -0
- package/src/__tests__/event-segmenter.test.ts +153 -0
- package/src/__tests__/hotness-blend.test.ts +76 -0
- package/src/__tests__/ingest.test.ts +360 -0
- package/src/__tests__/kg-extractor.test.ts +184 -0
- package/src/__tests__/kg-store.test.ts +198 -0
- package/src/__tests__/llm-consolidation.test.ts +226 -0
- package/src/__tests__/memory-boundaries.test.ts +237 -0
- package/src/__tests__/memory-health-rebalance.test.ts +144 -0
- package/src/__tests__/memory-output.test.ts +215 -0
- package/src/__tests__/multi-vector.test.ts +242 -0
- package/src/__tests__/ppr-traversal.test.ts +256 -0
- package/src/__tests__/preference-guard-smoke.test.ts +211 -0
- package/src/__tests__/preference-matcher.test.ts +230 -0
- package/src/__tests__/preference-slots.test.ts +106 -0
- package/src/__tests__/prospective-memory.test.ts +283 -0
- package/src/__tests__/query-entity-detector.test.ts +171 -0
- package/src/__tests__/query-expander.test.ts +34 -0
- package/src/__tests__/recall-mode.test.ts +64 -0
- package/src/__tests__/result-synthesizer.test.ts +198 -0
- package/src/__tests__/resume-context.test.ts +4743 -0
- package/src/__tests__/retrieval-trace.test.ts +213 -0
- package/src/__tests__/retriever-boundary-weight.test.ts +136 -0
- package/src/__tests__/retroactive-boost.test.ts +223 -0
- package/src/__tests__/scope-inventory.test.ts +200 -0
- package/src/__tests__/scope-policy.test.ts +69 -0
- package/src/__tests__/session-checkpoint.test.ts +259 -0
- package/src/__tests__/session-gc.test.ts +215 -0
- package/src/__tests__/store-scope-audit.test.ts +81 -0
- package/src/__tests__/temporal-parser.test.ts +241 -0
- package/src/__tests__/term-registry.test.ts +121 -0
- package/src/__tests__/tool-output-compressor.test.ts +133 -0
- package/src/__tests__/version-manager.test.ts +213 -0
- package/src/__tests__/workflow-observation.test.ts +400 -0
- package/src/access-tracker.ts +316 -0
- package/src/adaptive-retrieval.ts +64 -0
- package/src/api-server.ts +667 -0
- package/src/asset-sync.ts +79 -0
- package/src/auto-consolidation.ts +133 -0
- package/src/auto-gc.ts +124 -0
- package/src/auto-recall.ts +95 -0
- package/src/capture-engine.ts +931 -0
- package/src/capture-heuristic.ts +177 -0
- package/src/cascade-forget.ts +135 -0
- package/src/chunker.ts +253 -0
- package/src/cli.ts +1917 -0
- package/src/confidence-tracker.ts +166 -0
- package/src/conflict-advisor.ts +433 -0
- package/src/conflict-engine.ts +181 -0
- package/src/conflict-escalation.ts +138 -0
- package/src/conflict-lifecycle.ts +67 -0
- package/src/conflict-output.ts +218 -0
- package/src/conflict-schema.ts +130 -0
- package/src/conflict-store.ts +127 -0
- package/src/consolidation-engine.ts +256 -0
- package/src/context-composer-pins.ts +183 -0
- package/src/context-composer-queries.ts +121 -0
- package/src/context-composer-scope.ts +56 -0
- package/src/context-composer-stable-selection.ts +200 -0
- package/src/context-composer-stable.ts +217 -0
- package/src/context-composer-task-fallbacks.ts +36 -0
- package/src/context-composer-task-ranking.ts +197 -0
- package/src/context-composer-task-results.ts +137 -0
- package/src/context-composer-task-selection.ts +342 -0
- package/src/context-composer-text.ts +57 -0
- package/src/context-composer.ts +300 -0
- package/src/context-renderer.ts +179 -0
- package/src/decay-engine.ts +189 -0
- package/src/doctor.ts +543 -0
- package/src/embedder.ts +557 -0
- package/src/entity-resolver.ts +131 -0
- package/src/eval.ts +632 -0
- package/src/event-segmenter.ts +216 -0
- package/src/ingest.ts +1822 -0
- package/src/kg-extractor.ts +217 -0
- package/src/kg-store.ts +353 -0
- package/src/legacy-scope-review.ts +53 -0
- package/src/llm-client.ts +629 -0
- package/src/llm-consolidation.ts +168 -0
- package/src/mcp-server.ts +1337 -0
- package/src/memory-assets.ts +475 -0
- package/src/memory-boundaries.ts +354 -0
- package/src/memory-health-rebalance.ts +220 -0
- package/src/memory-output.ts +474 -0
- package/src/memory-schema.ts +184 -0
- package/src/multi-vector.ts +234 -0
- package/src/noise-filter.ts +155 -0
- package/src/ppr-traversal.ts +279 -0
- package/src/preference-matcher.ts +174 -0
- package/src/preference-slots.ts +259 -0
- package/src/prospective-memory.ts +198 -0
- package/src/query-entity-detector.ts +207 -0
- package/src/query-expander.ts +98 -0
- package/src/result-synthesizer.ts +114 -0
- package/src/retrieval-profiles.ts +101 -0
- package/src/retrieval-trace.ts +94 -0
- package/src/retriever.ts +1338 -0
- package/src/retroactive-boost.ts +154 -0
- package/src/rif.ts +73 -0
- package/src/runtime-config.ts +172 -0
- package/src/schema-utils.ts +56 -0
- package/src/scope-inventory.ts +310 -0
- package/src/scope-policy.ts +142 -0
- package/src/session-engine.ts +168 -0
- package/src/session-output.ts +96 -0
- package/src/session-schema.ts +60 -0
- package/src/session-store.ts +293 -0
- package/src/stderr-log.ts +16 -0
- package/src/store.ts +903 -0
- package/src/temporal-parser.ts +318 -0
- package/src/term-registry.ts +791 -0
- package/src/tool-output-compressor.ts +115 -0
- package/src/tracker.ts +57 -0
- package/src/ui-server.ts +430 -0
- package/src/version-manager.ts +172 -0
- package/src/workflow-observation-engine.ts +316 -0
- package/src/workflow-observation-managed.ts +83 -0
- package/src/workflow-observation-output.ts +86 -0
- package/src/workflow-observation-schema.ts +87 -0
- package/src/workflow-observation-scope-review.ts +409 -0
- package/src/workflow-observation-store.ts +69 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { Embedder } from "./embedder.js";
|
|
2
|
+
import type { MemoryStore } from "./store.js";
|
|
3
|
+
import type { BriefAsset, MemoryAsset, PinAsset } from "./memory-assets.js";
|
|
4
|
+
|
|
5
|
+
function buildPinnedAssetText(asset: PinAsset): string {
|
|
6
|
+
const tags = asset.tags.join(", ");
|
|
7
|
+
return [
|
|
8
|
+
`[Pinned Asset] ${asset.title}`,
|
|
9
|
+
`Summary: ${asset.summary}`,
|
|
10
|
+
`Snippet: ${asset.snippet}`,
|
|
11
|
+
`Original Scope: ${asset.source.scope}`,
|
|
12
|
+
tags ? `Tags: ${tags}` : "",
|
|
13
|
+
].filter(Boolean).join("\n");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function buildBriefAssetText(asset: BriefAsset): string {
|
|
17
|
+
const tags = asset.tags.join(", ");
|
|
18
|
+
return [
|
|
19
|
+
`[Memory Brief] ${asset.title}`,
|
|
20
|
+
`Query: ${asset.query}`,
|
|
21
|
+
`Profile: ${asset.profile}`,
|
|
22
|
+
`Summary: ${asset.summary}`,
|
|
23
|
+
asset.takeaways.length > 0 ? `Takeaways: ${asset.takeaways.join(" | ")}` : "",
|
|
24
|
+
asset.reusableCandidates.length > 0 ? `Reusable: ${asset.reusableCandidates.join(" | ")}` : "",
|
|
25
|
+
asset.sources.length > 0 ? `Sources: ${asset.sources.map((item) => `${item.source}(${item.hits})`).join(", ")}` : "",
|
|
26
|
+
tags ? `Tags: ${tags}` : "",
|
|
27
|
+
].filter(Boolean).join("\n");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function buildAssetText(asset: MemoryAsset): string {
|
|
31
|
+
return asset.type === "memory-brief"
|
|
32
|
+
? buildBriefAssetText(asset)
|
|
33
|
+
: buildPinnedAssetText(asset);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function assetImportance(asset: MemoryAsset): number {
|
|
37
|
+
return asset.type === "memory-brief" ? 0.72 : 0.96;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function indexAsset(
|
|
41
|
+
store: MemoryStore,
|
|
42
|
+
embedder: Embedder,
|
|
43
|
+
asset: MemoryAsset,
|
|
44
|
+
): Promise<void> {
|
|
45
|
+
const text = buildAssetText(asset);
|
|
46
|
+
const vector = await embedder.embedPassage(text);
|
|
47
|
+
const metadata = asset.type === "memory-brief"
|
|
48
|
+
? {
|
|
49
|
+
source: "asset",
|
|
50
|
+
assetId: asset.id,
|
|
51
|
+
assetType: asset.type,
|
|
52
|
+
title: asset.title,
|
|
53
|
+
query: asset.query,
|
|
54
|
+
profile: asset.profile,
|
|
55
|
+
tags: asset.tags,
|
|
56
|
+
sources: asset.sources.map((item) => item.source),
|
|
57
|
+
}
|
|
58
|
+
: {
|
|
59
|
+
source: "asset",
|
|
60
|
+
assetId: asset.id,
|
|
61
|
+
assetType: asset.type,
|
|
62
|
+
title: asset.title,
|
|
63
|
+
originalMemoryId: asset.source.memoryId,
|
|
64
|
+
originalScope: asset.source.scope,
|
|
65
|
+
tags: asset.tags,
|
|
66
|
+
};
|
|
67
|
+
await store.store({
|
|
68
|
+
text,
|
|
69
|
+
vector,
|
|
70
|
+
category: "decision",
|
|
71
|
+
scope: asset.type === "memory-brief"
|
|
72
|
+
? `asset:brief:${asset.id.slice(0, 8)}`
|
|
73
|
+
: `asset:${asset.id.slice(0, 8)}`,
|
|
74
|
+
importance: assetImportance(asset),
|
|
75
|
+
metadata: JSON.stringify(metadata),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const indexPinnedAsset = indexAsset;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto Consolidation — Condition-driven consolidation trigger.
|
|
3
|
+
*
|
|
4
|
+
* Agent-First reasoning: manual consolidation is unsustainable. Trigger
|
|
5
|
+
* automatically when enough new memories have accumulated AND enough time
|
|
6
|
+
* has passed since the last run. Same dual-gate pattern as auto-gc.ts.
|
|
7
|
+
*
|
|
8
|
+
* Brain-science label: "NREM triple-coupling" — multiple conditions must
|
|
9
|
+
* align before consolidation fires. The label is for branding; the real
|
|
10
|
+
* reason is operational hygiene.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { MemoryStore } from "./store.js";
|
|
14
|
+
import { ConsolidationEngine, type ConsolidationConfig, type ConsolidationResult, DEFAULT_CONSOLIDATION_CONFIG } from "./consolidation-engine.js";
|
|
15
|
+
import type { LLMClient } from "./llm-client.js";
|
|
16
|
+
import { isLLMConsolidationEnabled, evaluateCluster, executeMergeDecisions } from "./llm-consolidation.js";
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Config
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
export interface AutoConsolidationConfig {
|
|
23
|
+
/** Minimum new memories since last consolidation before triggering (default: 50) */
|
|
24
|
+
minNewMemories: number;
|
|
25
|
+
/** Minimum hours since last consolidation (default: 12) */
|
|
26
|
+
minHoursSinceLastRun: number;
|
|
27
|
+
/** Consolidation engine config (cluster/merge thresholds) */
|
|
28
|
+
consolidation: ConsolidationConfig;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const DEFAULT_AUTO_CONSOLIDATION_CONFIG: AutoConsolidationConfig = {
|
|
32
|
+
minNewMemories: 50,
|
|
33
|
+
minHoursSinceLastRun: 12,
|
|
34
|
+
consolidation: DEFAULT_CONSOLIDATION_CONFIG,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// State
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
let lastRunTimestamp = 0;
|
|
42
|
+
let lastRunMemoryCount = 0;
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Core
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
export interface AutoConsolidationResult {
|
|
49
|
+
triggered: boolean;
|
|
50
|
+
reason?: string;
|
|
51
|
+
consolidation?: ConsolidationResult;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Check conditions and run consolidation if thresholds are met.
|
|
56
|
+
* Returns immediately if conditions not met (no-op).
|
|
57
|
+
*/
|
|
58
|
+
export async function maybeConsolidate(
|
|
59
|
+
store: MemoryStore,
|
|
60
|
+
scope: string,
|
|
61
|
+
config: AutoConsolidationConfig = DEFAULT_AUTO_CONSOLIDATION_CONFIG,
|
|
62
|
+
/** Tier 3.7: Optional LLM client for semantic consolidation decisions */
|
|
63
|
+
llm?: LLMClient | null,
|
|
64
|
+
): Promise<AutoConsolidationResult> {
|
|
65
|
+
const stats = await store.stats([scope]);
|
|
66
|
+
const currentCount = stats.total ?? 0;
|
|
67
|
+
|
|
68
|
+
// Condition 1: enough new memories since last run
|
|
69
|
+
const newSinceLastRun = currentCount - lastRunMemoryCount;
|
|
70
|
+
if (newSinceLastRun < config.minNewMemories) {
|
|
71
|
+
return { triggered: false, reason: "insufficient_new_memories" };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Condition 2: enough time since last run
|
|
75
|
+
const hoursSinceLastRun = (Date.now() - lastRunTimestamp) / 3_600_000;
|
|
76
|
+
if (hoursSinceLastRun < config.minHoursSinceLastRun) {
|
|
77
|
+
return { triggered: false, reason: "too_soon" };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Both conditions met — run consolidation
|
|
81
|
+
lastRunTimestamp = Date.now();
|
|
82
|
+
lastRunMemoryCount = currentCount;
|
|
83
|
+
|
|
84
|
+
const engine = new ConsolidationEngine(store, config.consolidation);
|
|
85
|
+
const result = await engine.run(scope);
|
|
86
|
+
|
|
87
|
+
// Tier 3.7: LLM post-processing of linked clusters.
|
|
88
|
+
// When enabled, LLM evaluates "clustered_with" relations and may
|
|
89
|
+
// upgrade them to version groups (merges) based on semantic judgment.
|
|
90
|
+
if (isLLMConsolidationEnabled() && llm && result.relationsAdded > 0) {
|
|
91
|
+
let llmMerges = 0;
|
|
92
|
+
// Find entries with clustered_with that aren't already in version groups
|
|
93
|
+
const allEntries = await store.list([scope], undefined, config.consolidation.maxEntriesPerRun ?? 500);
|
|
94
|
+
const clusterMap = new Map<string, MemoryEntry[]>();
|
|
95
|
+
|
|
96
|
+
for (const entry of allEntries) {
|
|
97
|
+
try {
|
|
98
|
+
const meta = JSON.parse(entry.metadata || "{}");
|
|
99
|
+
const members = meta.cluster_members as string[] | undefined;
|
|
100
|
+
if (Array.isArray(members) && members.length > 0 && !meta.version_group) {
|
|
101
|
+
const cluster = [entry];
|
|
102
|
+
for (const memberId of members) {
|
|
103
|
+
const member = await store.getById(memberId);
|
|
104
|
+
if (member) cluster.push(member);
|
|
105
|
+
}
|
|
106
|
+
if (cluster.length >= 2) {
|
|
107
|
+
clusterMap.set(entry.id, cluster);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} catch { /* skip */ }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for (const [, cluster] of clusterMap) {
|
|
114
|
+
const decision = await evaluateCluster(llm, cluster);
|
|
115
|
+
const merges = await executeMergeDecisions(store, cluster, decision, scope);
|
|
116
|
+
llmMerges += merges;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (llmMerges > 0) {
|
|
120
|
+
result.mergedCount += llmMerges;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return { triggered: true, consolidation: result };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Reset state (for testing).
|
|
129
|
+
*/
|
|
130
|
+
export function resetConsolidationState(): void {
|
|
131
|
+
lastRunTimestamp = 0;
|
|
132
|
+
lastRunMemoryCount = 0;
|
|
133
|
+
}
|
package/src/auto-gc.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto Garbage Collection — Brain-inspired active forgetting.
|
|
3
|
+
*
|
|
4
|
+
* Condition-driven trigger (not cron): runs when memory count exceeds threshold
|
|
5
|
+
* AND enough time has passed since last GC. Inspired by NREM triple-coupling:
|
|
6
|
+
* multiple conditions must be satisfied before consolidation fires.
|
|
7
|
+
*
|
|
8
|
+
* Wraps existing cleanup scripts into a programmatic API.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { MemoryStore } from "./store.js";
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Config
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
export interface AutoGcConfig {
|
|
18
|
+
/** Minimum memories before GC can trigger (default: 1000) */
|
|
19
|
+
minMemoryCount: number;
|
|
20
|
+
/** Minimum hours since last GC (default: 24) */
|
|
21
|
+
minHoursSinceLastGc: number;
|
|
22
|
+
/** Score below which memories are candidates for archival (default: 0.2) */
|
|
23
|
+
staleScoreThreshold: number;
|
|
24
|
+
/** Max entries to archive per run (default: 100) */
|
|
25
|
+
maxArchivePerRun: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const DEFAULT_AUTO_GC_CONFIG: AutoGcConfig = {
|
|
29
|
+
minMemoryCount: 1000,
|
|
30
|
+
minHoursSinceLastGc: 24,
|
|
31
|
+
staleScoreThreshold: 0.2,
|
|
32
|
+
maxArchivePerRun: 100,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// State
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
let lastGcTimestamp = 0;
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Core
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
export interface GcResult {
|
|
46
|
+
triggered: boolean;
|
|
47
|
+
reason?: string;
|
|
48
|
+
archivedCount: number;
|
|
49
|
+
totalChecked: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Check conditions and run GC if all thresholds are met.
|
|
54
|
+
* Returns immediately if conditions not met (no-op).
|
|
55
|
+
*/
|
|
56
|
+
export async function maybeRunGc(
|
|
57
|
+
store: MemoryStore,
|
|
58
|
+
config: AutoGcConfig = DEFAULT_AUTO_GC_CONFIG,
|
|
59
|
+
): Promise<GcResult> {
|
|
60
|
+
const stats = await store.stats();
|
|
61
|
+
const totalMemories = stats.total ?? 0;
|
|
62
|
+
|
|
63
|
+
// Condition 1: enough memories to warrant GC
|
|
64
|
+
if (totalMemories < config.minMemoryCount) {
|
|
65
|
+
return { triggered: false, reason: "below_memory_threshold", archivedCount: 0, totalChecked: 0 };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Condition 2: enough time since last GC
|
|
69
|
+
const hoursSinceLastGc = (Date.now() - lastGcTimestamp) / 3_600_000;
|
|
70
|
+
if (hoursSinceLastGc < config.minHoursSinceLastGc) {
|
|
71
|
+
return { triggered: false, reason: "too_soon", archivedCount: 0, totalChecked: 0 };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// All conditions met — run GC
|
|
75
|
+
lastGcTimestamp = Date.now();
|
|
76
|
+
|
|
77
|
+
// Find stale peripheral memories with low importance and no recent access
|
|
78
|
+
const cutoffMs = Date.now() - 60 * 86_400_000; // 60 days ago
|
|
79
|
+
let archivedCount = 0;
|
|
80
|
+
let totalChecked = 0;
|
|
81
|
+
|
|
82
|
+
// Scan memories by listing them
|
|
83
|
+
const entries = await store.list({ limit: 5000 });
|
|
84
|
+
totalChecked = entries.length;
|
|
85
|
+
|
|
86
|
+
for (const entry of entries) {
|
|
87
|
+
if (archivedCount >= config.maxArchivePerRun) break;
|
|
88
|
+
|
|
89
|
+
let meta: Record<string, any> = {};
|
|
90
|
+
try { meta = JSON.parse(entry.metadata || "{}"); } catch { /* skip */ }
|
|
91
|
+
|
|
92
|
+
const isArchived = meta.archived === true;
|
|
93
|
+
if (isArchived) continue;
|
|
94
|
+
|
|
95
|
+
const tier = meta.tier || "peripheral";
|
|
96
|
+
const accessCount = meta.accessCount || 0;
|
|
97
|
+
const importance = entry.importance ?? 0;
|
|
98
|
+
const age = Date.now() - entry.timestamp;
|
|
99
|
+
const ageDays = age / 86_400_000;
|
|
100
|
+
|
|
101
|
+
// Archive criteria: peripheral + old + low importance + rarely accessed
|
|
102
|
+
if (
|
|
103
|
+
tier === "peripheral" &&
|
|
104
|
+
ageDays > 60 &&
|
|
105
|
+
importance < config.staleScoreThreshold &&
|
|
106
|
+
accessCount < 2
|
|
107
|
+
) {
|
|
108
|
+
meta.archived = true;
|
|
109
|
+
meta.archivedAt = new Date().toISOString();
|
|
110
|
+
meta.archivedReason = "auto-gc:stale-peripheral";
|
|
111
|
+
await store.update(entry.id, { metadata: JSON.stringify(meta) });
|
|
112
|
+
archivedCount++;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return { triggered: true, archivedCount, totalChecked };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Reset last GC timestamp (for testing).
|
|
121
|
+
*/
|
|
122
|
+
export function resetGcTimestamp(): void {
|
|
123
|
+
lastGcTimestamp = 0;
|
|
124
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { composeResumeContext, type ResumeContextDeps } from "./context-composer.js";
|
|
2
|
+
import type { DurableMemoryCategory } from "./memory-schema.js";
|
|
3
|
+
import type { RetrievalResult } from "./retriever.js";
|
|
4
|
+
import { buildRetrievalContext, resolveScopeSelection } from "./scope-policy.js";
|
|
5
|
+
import type { RetrievalProfileName, ResumeContextResponse } from "./session-schema.js";
|
|
6
|
+
|
|
7
|
+
export interface AutoRecallRequest {
|
|
8
|
+
message: string;
|
|
9
|
+
task?: string;
|
|
10
|
+
scope?: string;
|
|
11
|
+
sessionId?: string;
|
|
12
|
+
allScopes?: boolean;
|
|
13
|
+
limit?: number;
|
|
14
|
+
limitPerSection?: number;
|
|
15
|
+
includeLatestCheckpoint?: boolean;
|
|
16
|
+
category?: DurableMemoryCategory;
|
|
17
|
+
profile?: RetrievalProfileName;
|
|
18
|
+
env?: NodeJS.ProcessEnv;
|
|
19
|
+
operation?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface AutoRecallResponse {
|
|
23
|
+
mode: "resume-only" | "resume+search";
|
|
24
|
+
resolvedScope?: string;
|
|
25
|
+
resume: ResumeContextResponse;
|
|
26
|
+
results: RetrievalResult[];
|
|
27
|
+
searchSkippedReason?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function runAutoRecall(
|
|
31
|
+
deps: ResumeContextDeps,
|
|
32
|
+
request: AutoRecallRequest,
|
|
33
|
+
): Promise<AutoRecallResponse> {
|
|
34
|
+
const message = request.message.replace(/\s+/g, " ").trim();
|
|
35
|
+
const task = (request.task || message).replace(/\s+/g, " ").trim();
|
|
36
|
+
|
|
37
|
+
if (!message) {
|
|
38
|
+
throw new Error("message is required");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const scopeSelection = resolveScopeSelection({
|
|
42
|
+
scope: request.scope,
|
|
43
|
+
sessionId: request.sessionId,
|
|
44
|
+
allScopes: request.allScopes,
|
|
45
|
+
operation: request.operation || "auto_recall",
|
|
46
|
+
env: request.env,
|
|
47
|
+
allowUnscoped: true,
|
|
48
|
+
});
|
|
49
|
+
const resumeScope = scopeSelection.inferredFrom === "sessionId"
|
|
50
|
+
? undefined
|
|
51
|
+
: scopeSelection.resolvedScope;
|
|
52
|
+
|
|
53
|
+
const resume = await composeResumeContext(deps, {
|
|
54
|
+
task,
|
|
55
|
+
scope: resumeScope,
|
|
56
|
+
sessionId: request.sessionId,
|
|
57
|
+
limitPerSection: request.limitPerSection,
|
|
58
|
+
includeLatestCheckpoint: request.includeLatestCheckpoint,
|
|
59
|
+
profile: request.profile,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const resolvedScope = request.allScopes
|
|
63
|
+
? undefined
|
|
64
|
+
: (resume.resolvedScope || resume.latestCheckpoint?.resolvedScope || scopeSelection.resolvedScope);
|
|
65
|
+
if (!request.allScopes && !resolvedScope) {
|
|
66
|
+
return {
|
|
67
|
+
mode: "resume-only",
|
|
68
|
+
resolvedScope,
|
|
69
|
+
resume,
|
|
70
|
+
results: [],
|
|
71
|
+
searchSkippedReason: "No explicit or inferred scope available for focused recall; returning resume context only.",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const results = await deps.retriever.retrieve(buildRetrievalContext({
|
|
76
|
+
query: message,
|
|
77
|
+
limit: request.limit || 5,
|
|
78
|
+
category: request.category,
|
|
79
|
+
scope: resolvedScope,
|
|
80
|
+
sessionId: request.sessionId,
|
|
81
|
+
allScopes: request.allScopes,
|
|
82
|
+
source: "auto-recall",
|
|
83
|
+
}, {
|
|
84
|
+
operation: request.operation || "auto_recall",
|
|
85
|
+
env: request.env,
|
|
86
|
+
allowUnscoped: request.allScopes === true,
|
|
87
|
+
}));
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
mode: "resume+search",
|
|
91
|
+
resolvedScope,
|
|
92
|
+
resume,
|
|
93
|
+
results,
|
|
94
|
+
};
|
|
95
|
+
}
|