recallnest 1.4.1 → 2.2.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/.env +4 -0
- package/CLAUDE.md +7 -3
- package/README.md +202 -130
- package/README_CN.md +263 -190
- package/bin/recallnest.mjs +62 -0
- package/config.json +55 -0
- package/daily-recall.sh +23 -0
- package/integrations/setup-all.sh +31 -0
- package/logs/daily-recall.error.log +0 -0
- package/logs/daily-recall.log +30 -0
- package/logs/ingest-2026-04-11.log +110 -0
- package/logs/ingest-2026-04-12.log +90 -0
- package/logs/launchagent-stderr.log +0 -0
- package/logs/launchagent-stdout.log +0 -0
- package/package.json +10 -3
- package/src/access-tracker.ts +3 -2
- package/src/activity-counter.ts +109 -0
- package/src/adaptive-retrieval.ts +6 -5
- package/src/admission-control.ts +163 -0
- package/src/anchor-generator.ts +80 -0
- package/src/api-server.ts +10 -0
- package/src/audit-log.ts +122 -0
- package/src/auto-gc.ts +98 -29
- package/src/auto-recall.ts +33 -4
- package/src/capture-engine.ts +372 -23
- package/src/cascade-forget.ts +3 -2
- package/src/cli.ts +107 -5
- package/src/compat.ts +22 -0
- package/src/confidence-tracker.ts +210 -47
- package/src/conflict-store.ts +12 -3
- package/src/consolidation-engine.ts +296 -8
- package/src/context-collapse-renderer.ts +222 -0
- package/src/context-composer-stable-selection.ts +5 -1
- package/src/context-composer.ts +239 -4
- package/src/context-reconstructor.ts +404 -0
- package/src/conversation-importer.ts +441 -0
- package/src/data-checkup.ts +232 -0
- package/src/decay-engine.ts +84 -0
- package/src/distill-lock.ts +122 -0
- package/src/doctor.ts +8 -10
- package/src/dream-pipeline.ts +227 -0
- package/src/emotion-detector.ts +90 -0
- package/src/eval.ts +3 -2
- package/src/forget-engine.ts +269 -0
- package/src/frequency-tracker.ts +175 -0
- package/src/graph-export.ts +644 -0
- package/src/ingest.ts +36 -6
- package/src/interference-detector.ts +187 -0
- package/src/kg-extractor.ts +15 -24
- package/src/kg-store.ts +2 -1
- package/src/language-hook.ts +67 -0
- package/src/llm-client.ts +289 -6
- package/src/mcp-server.ts +707 -80
- package/src/memory-assets.ts +22 -5
- package/src/memory-evolution.ts +365 -0
- package/src/memory-lint.ts +429 -0
- package/src/memory-output.ts +69 -0
- package/src/memory-schema.ts +74 -2
- package/src/narrative-schema.ts +79 -0
- package/src/narrative-tagger.ts +164 -0
- package/src/pii-detector.ts +98 -0
- package/src/post-retrieval-filter.ts +76 -0
- package/src/prediction-engine.ts +253 -0
- package/src/preference-slots.ts +65 -2
- package/src/prospective-memory.ts +215 -0
- package/src/query-expander.ts +67 -0
- package/src/recall-governor.ts +183 -0
- package/src/retention-policy.ts +90 -0
- package/src/retriever.ts +828 -55
- package/src/rif.ts +34 -8
- package/src/runtime-config.ts +23 -8
- package/src/scope-inventory.ts +4 -3
- package/src/scope-policy.ts +4 -0
- package/src/session-distiller.ts +307 -0
- package/src/session-output.ts +40 -0
- package/src/session-schema.ts +37 -0
- package/src/session-store.ts +7 -2
- package/src/skill-engine.ts +208 -0
- package/src/skill-promotion.ts +293 -0
- package/src/skill-schema.ts +39 -0
- package/src/store.ts +96 -10
- package/src/topic-tag.ts +66 -0
- package/src/tracker.ts +3 -1
- package/src/ui-server.ts +80 -8
- package/src/workflow-observation-scope-review.ts +2 -1
- package/src/workflow-observation-store.ts +2 -1
- package/src/write-verifier.ts +126 -0
- package/.claude-plugin/CLAUDE.md +0 -29
- package/.claude-plugin/marketplace.json +0 -18
- package/.github/workflows/ci.yml +0 -28
- package/.mcp.json +0 -9
- package/CHANGELOG.md +0 -51
- package/DEMO.md +0 -94
- package/EVAL.md +0 -89
- package/FAILURES.md +0 -75
- package/OPERATIONS.md +0 -59
- package/ROADMAP.md +0 -198
- package/assets/recallnest-workbench.png +0 -0
- package/assets/ui/app.js +0 -807
- package/assets/ui/index.html +0 -154
- package/assets/ui/styles.css +0 -605
- package/assets/wechat-qr.jpg +0 -0
- package/daily-reflections/2026-03-05.md +0 -15
- package/daily-reflections/2026-03-06.md +0 -15
- package/daily-reflections/2026-03-07.md +0 -7
- package/daily-reflections/2026-03-08.md +0 -34
- package/daily-reflections/2026-03-09.md +0 -35
- package/daily-reflections/2026-03-10.md +0 -36
- package/daily-reflections/2026-03-11.md +0 -35
- package/daily-reflections/2026-03-12.md +0 -36
- package/daily-reflections/2026-03-13.md +0 -35
- package/daily-reflections/2026-03-14.md +0 -35
- package/daily-reflections/2026-03-15.md +0 -35
- package/daily-reflections/2026-03-16.md +0 -41
- package/daily-reflections/2026-03-17.md +0 -10
- package/daily-reflections/2026-03-18.md +0 -10
- package/daily-reflections/2026-03-19.md +0 -10
- package/daily-reflections/2026-03-20.md +0 -10
- package/daily-reflections/2026-03-21.md +0 -10
- package/daily-reflections/2026-03-22.md +0 -10
- package/daily-reflections/2026-03-23.md +0 -10
- package/daily-reflections/2026-03-24.md +0 -10
- package/daily-reflections/2026-03-25.md +0 -32
- package/daily-reflections/2026-03-26.md +0 -37
- package/daily-reflections/2026-03-27.md +0 -35
- package/daily-reflections/2026-03-28.md +0 -35
- package/daily-reflections/2026-03-29.md +0 -35
- package/daily-reflections/2026-03-30.md +0 -36
- package/daily-reflections/2026-03-31.md +0 -35
- package/daily-reflections/2026-04-01.md +0 -35
- package/docs/api-reference.md +0 -1291
- package/docs/architecture.md +0 -168
- package/docs/memory-boundary-contract.md +0 -226
- package/docs/memory-categories.md +0 -119
- package/docs/pipeline-audit-ultramemory.md +0 -177
- package/docs/plans/2026-03-08-query-expansion.md +0 -270
- package/docs/plans/2026-03-16-continuity-layer.md +0 -196
- package/docs/plans/mcp-tiering-plan.md +0 -126
- package/docs/research/recall-quality-audit-2026-03-22.md +0 -153
- package/docs/self-evolution.md +0 -198
- package/docs/superpowers/plans/2026-03-19-ingest-pipeline-upgrade.md +0 -782
- package/docs/superpowers/specs/2026-03-19-ingest-pipeline-upgrade-design.md +0 -77
- package/docs/superpowers/specs/2026-03-23-pull-claude-desktop-design.md +0 -95
- package/eval/continuity/results-2026-03-16.md +0 -117
- package/eval/continuity/results-2026-03-19.md +0 -453
- package/eval/continuity/runbook.md +0 -110
- package/eval/continuity/seed-cases.md +0 -54
- package/eval/reports/2026-03-06-abstract-query-baseline.md +0 -96
- package/eval/reports/2026-03-06-aws-ssh-baseline.md +0 -82
- package/eval/reports/2026-03-06-baseline.md +0 -82
- package/eval/reports/query-expansion.md +0 -138
- package/lm +0 -19
- package/plan-v1.2-distributable.md +0 -105
- package/scripts/archive-distilled-junk.ts +0 -116
- package/scripts/backfill-l0l1l2.ts +0 -98
- package/scripts/blind-scan.ts +0 -588
- package/scripts/cleanup-step1-exact-dups.ts +0 -117
- package/scripts/cleanup-step2-template-dups.ts +0 -113
- package/scripts/cleanup-step3-stale-content.ts +0 -149
- package/scripts/cleanup-step4-orphan-scopes.ts +0 -131
- package/scripts/delete-distilled-junk.ts +0 -88
- package/scripts/distill-facts.ts +0 -305
- package/scripts/health-check.ts +0 -343
- package/scripts/incremental-ingest.sh +0 -71
- package/scripts/mlp-health-check.ts +0 -382
- package/scripts/pull-claude-desktop.ts +0 -515
- package/scripts/re-ingest-cc.sh +0 -66
- package/scripts/rebalance-memory-health.ts +0 -181
- package/scripts/run-desktop-ingest.sh +0 -11
- package/scripts/start-server.sh +0 -12
- package/scripts/viz-dashboard.html +0 -423
- package/scripts/viz-export.ts +0 -703
- package/src/__tests__/access-tracker-novelty.test.ts +0 -228
- package/src/__tests__/auto-consolidation.test.ts +0 -168
- package/src/__tests__/auto-recall.test.ts +0 -131
- package/src/__tests__/batch-dedup.test.ts +0 -65
- package/src/__tests__/capture-engine.test.ts +0 -1142
- package/src/__tests__/capture-heuristic.test.ts +0 -171
- package/src/__tests__/cascade-forget.test.ts +0 -210
- package/src/__tests__/category-min-scores.test.ts +0 -33
- package/src/__tests__/confidence-tracker.test.ts +0 -196
- package/src/__tests__/conflict-advisor.test.ts +0 -206
- package/src/__tests__/conflict-escalation.test.ts +0 -149
- package/src/__tests__/conflict-lifecycle.test.ts +0 -83
- package/src/__tests__/conflict-output.test.ts +0 -85
- package/src/__tests__/conflict-store.test.ts +0 -374
- package/src/__tests__/consolidation-engine.test.ts +0 -181
- package/src/__tests__/context-composer-pins.test.ts +0 -64
- package/src/__tests__/context-composer-task-selection.test.ts +0 -24
- package/src/__tests__/context-renderer.test.ts +0 -91
- package/src/__tests__/continuity-eval.test.ts +0 -275
- package/src/__tests__/continuity-schema.test.ts +0 -97
- package/src/__tests__/core-recall-eval.test.ts +0 -235
- package/src/__tests__/core-summary.test.ts +0 -109
- package/src/__tests__/dedup-actions.test.ts +0 -58
- package/src/__tests__/deterministic-id.test.ts +0 -39
- package/src/__tests__/doctor.test.ts +0 -303
- package/src/__tests__/drill-down.test.ts +0 -108
- package/src/__tests__/embedder.test.ts +0 -122
- package/src/__tests__/entity-resolver.test.ts +0 -58
- package/src/__tests__/event-segmenter.test.ts +0 -153
- package/src/__tests__/hotness-blend.test.ts +0 -76
- package/src/__tests__/ingest.test.ts +0 -360
- package/src/__tests__/kg-extractor.test.ts +0 -184
- package/src/__tests__/kg-store.test.ts +0 -198
- package/src/__tests__/llm-consolidation.test.ts +0 -226
- package/src/__tests__/memory-boundaries.test.ts +0 -237
- package/src/__tests__/memory-health-rebalance.test.ts +0 -144
- package/src/__tests__/memory-output.test.ts +0 -215
- package/src/__tests__/multi-vector.test.ts +0 -242
- package/src/__tests__/ppr-traversal.test.ts +0 -256
- package/src/__tests__/preference-guard-smoke.test.ts +0 -211
- package/src/__tests__/preference-matcher.test.ts +0 -230
- package/src/__tests__/preference-slots.test.ts +0 -106
- package/src/__tests__/prospective-memory.test.ts +0 -283
- package/src/__tests__/query-entity-detector.test.ts +0 -171
- package/src/__tests__/query-expander.test.ts +0 -34
- package/src/__tests__/recall-mode.test.ts +0 -64
- package/src/__tests__/result-synthesizer.test.ts +0 -198
- package/src/__tests__/resume-context.test.ts +0 -4743
- package/src/__tests__/retrieval-trace.test.ts +0 -213
- package/src/__tests__/retriever-boundary-weight.test.ts +0 -136
- package/src/__tests__/retroactive-boost.test.ts +0 -223
- package/src/__tests__/scope-inventory.test.ts +0 -200
- package/src/__tests__/scope-policy.test.ts +0 -69
- package/src/__tests__/session-checkpoint.test.ts +0 -259
- package/src/__tests__/session-gc.test.ts +0 -215
- package/src/__tests__/store-scope-audit.test.ts +0 -81
- package/src/__tests__/temporal-parser.test.ts +0 -241
- package/src/__tests__/term-registry.test.ts +0 -121
- package/src/__tests__/tool-output-compressor.test.ts +0 -133
- package/src/__tests__/version-manager.test.ts +0 -213
- package/src/__tests__/workflow-observation.test.ts +0 -400
package/src/memory-assets.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync,
|
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { basename, extname, join, resolve } from "node:path";
|
|
4
4
|
|
|
5
|
+
import { metaDir } from "./compat.js";
|
|
5
6
|
import type { MemoryEntry } from "./store.js";
|
|
6
7
|
import type { RetrievalResult } from "./retriever.js";
|
|
7
8
|
import type { RetrievalProfileName } from "./retrieval-profiles.js";
|
|
@@ -71,7 +72,7 @@ export interface DirtyBriefAssetRecord extends BriefAsset {
|
|
|
71
72
|
scope: string;
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
const DATA_DIR = resolve(import.meta
|
|
75
|
+
const DATA_DIR = resolve(metaDir(import.meta), "../data");
|
|
75
76
|
const PINS_DIR = join(DATA_DIR, "pins");
|
|
76
77
|
const ASSETS_DIR = join(DATA_DIR, "assets");
|
|
77
78
|
const ARCHIVE_DIR = join(DATA_DIR, "archive", "dirty-briefs");
|
|
@@ -165,7 +166,11 @@ export function buildPinAsset(result: RetrievalResult, options: {
|
|
|
165
166
|
export function savePinAsset(asset: PinAsset): string {
|
|
166
167
|
const dir = getPinsDir();
|
|
167
168
|
const path = join(dir, `${asset.id}.json`);
|
|
168
|
-
|
|
169
|
+
try {
|
|
170
|
+
writeFileSync(path, JSON.stringify(asset, null, 2) + "\n");
|
|
171
|
+
} catch (err) {
|
|
172
|
+
console.error("[recallnest] Failed to save pin asset:", err instanceof Error ? err.message : String(err));
|
|
173
|
+
}
|
|
169
174
|
return path;
|
|
170
175
|
}
|
|
171
176
|
|
|
@@ -198,7 +203,11 @@ export function buildBriefAsset(
|
|
|
198
203
|
export function saveBriefAsset(asset: BriefAsset): string {
|
|
199
204
|
const dir = getAssetsDir();
|
|
200
205
|
const path = join(dir, `${asset.id}.json`);
|
|
201
|
-
|
|
206
|
+
try {
|
|
207
|
+
writeFileSync(path, JSON.stringify(asset, null, 2) + "\n");
|
|
208
|
+
} catch (err) {
|
|
209
|
+
console.error("[recallnest] Failed to save brief asset:", err instanceof Error ? err.message : String(err));
|
|
210
|
+
}
|
|
202
211
|
return path;
|
|
203
212
|
}
|
|
204
213
|
|
|
@@ -332,7 +341,11 @@ export function writeExportArtifact(params: {
|
|
|
332
341
|
};
|
|
333
342
|
|
|
334
343
|
if (params.format === "json") {
|
|
335
|
-
|
|
344
|
+
try {
|
|
345
|
+
writeFileSync(outputPath, JSON.stringify(payload, null, 2) + "\n");
|
|
346
|
+
} catch (err) {
|
|
347
|
+
console.error("[recallnest] Failed to write JSON export:", err instanceof Error ? err.message : String(err));
|
|
348
|
+
}
|
|
336
349
|
} else {
|
|
337
350
|
const lines = [
|
|
338
351
|
`# Memory Export`,
|
|
@@ -365,7 +378,11 @@ export function writeExportArtifact(params: {
|
|
|
365
378
|
lines.push("");
|
|
366
379
|
});
|
|
367
380
|
|
|
368
|
-
|
|
381
|
+
try {
|
|
382
|
+
writeFileSync(outputPath, lines.join("\n"));
|
|
383
|
+
} catch (err) {
|
|
384
|
+
console.error("[recallnest] Failed to write markdown export:", err instanceof Error ? err.message : String(err));
|
|
385
|
+
}
|
|
369
386
|
}
|
|
370
387
|
|
|
371
388
|
return {
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Evolution — lifecycle tracking for memory entries.
|
|
3
|
+
*
|
|
4
|
+
* Adds status, version, access tracking, supersede/consolidation links,
|
|
5
|
+
* and decay scoring to each memory. Fields live inside the existing
|
|
6
|
+
* metadata JSON so the LanceDB schema is unchanged.
|
|
7
|
+
*
|
|
8
|
+
* Design principles (from arXiv 2512.13564 survey):
|
|
9
|
+
* - Archive-first, delete-never
|
|
10
|
+
* - Supersede-on-conflict (old stays, new links to it)
|
|
11
|
+
* - Composite decay = 0.2 time + 0.3 frequency + 0.5 importance (base)
|
|
12
|
+
* With emotion: 0.15 time + 0.25 frequency + 0.45 importance + 0.15 emotionSalience
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { parseEmotion, isEmotionScoringEnabled } from "./memory-schema.js";
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Types
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
export type EvolutionStatus = "active" | "superseded" | "archived" | "consolidated" | "pending_review";
|
|
22
|
+
|
|
23
|
+
export interface EvolutionMetadata {
|
|
24
|
+
status: EvolutionStatus;
|
|
25
|
+
version: number;
|
|
26
|
+
accessCount: number;
|
|
27
|
+
lastAccessedAt: number | null;
|
|
28
|
+
supersededBy: string | null;
|
|
29
|
+
/** HP-1: Reverse link — this memory supersedes an older one */
|
|
30
|
+
supersedes: string | null;
|
|
31
|
+
/** HP-1: Why this memory replaced the old one */
|
|
32
|
+
evolutionNote: string | null;
|
|
33
|
+
consolidatedInto: string | null;
|
|
34
|
+
/** HP-5: This memory contributed to a cross-memory pattern discovery */
|
|
35
|
+
contributedToPattern: string | null;
|
|
36
|
+
sourceMemories: string[];
|
|
37
|
+
validFrom: number;
|
|
38
|
+
validUntil: number | null;
|
|
39
|
+
/** F3: Event actual time (ms) — distinct from memory creation time (validFrom). */
|
|
40
|
+
eventTime: number | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// Defaults
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
export function defaultEvolution(now?: number): EvolutionMetadata {
|
|
48
|
+
const ts = now ?? Date.now();
|
|
49
|
+
return {
|
|
50
|
+
status: "active",
|
|
51
|
+
version: 1,
|
|
52
|
+
accessCount: 0,
|
|
53
|
+
lastAccessedAt: null,
|
|
54
|
+
supersededBy: null,
|
|
55
|
+
supersedes: null,
|
|
56
|
+
evolutionNote: null,
|
|
57
|
+
consolidatedInto: null,
|
|
58
|
+
contributedToPattern: null,
|
|
59
|
+
sourceMemories: [],
|
|
60
|
+
validFrom: ts,
|
|
61
|
+
validUntil: null,
|
|
62
|
+
eventTime: null,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Parse / Serialize
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Extract evolution metadata from a memory's metadata JSON string.
|
|
72
|
+
* Returns sensible defaults when the field is absent (backward compat).
|
|
73
|
+
*/
|
|
74
|
+
export function parseEvolution(metadata: string | undefined, fallbackTimestamp?: number): EvolutionMetadata {
|
|
75
|
+
if (!metadata) return defaultEvolution(fallbackTimestamp);
|
|
76
|
+
try {
|
|
77
|
+
const parsed = JSON.parse(metadata);
|
|
78
|
+
const evo = parsed?.evolution;
|
|
79
|
+
if (!evo) return defaultEvolution(fallbackTimestamp);
|
|
80
|
+
return {
|
|
81
|
+
status: evo.status ?? "active",
|
|
82
|
+
version: evo.version ?? 1,
|
|
83
|
+
accessCount: evo.accessCount ?? 0,
|
|
84
|
+
lastAccessedAt: evo.lastAccessedAt ?? null,
|
|
85
|
+
supersededBy: evo.supersededBy ?? null,
|
|
86
|
+
supersedes: evo.supersedes ?? null,
|
|
87
|
+
evolutionNote: evo.evolutionNote ?? null,
|
|
88
|
+
consolidatedInto: evo.consolidatedInto ?? null,
|
|
89
|
+
contributedToPattern: evo.contributedToPattern ?? null,
|
|
90
|
+
sourceMemories: Array.isArray(evo.sourceMemories) ? evo.sourceMemories : [],
|
|
91
|
+
validFrom: evo.validFrom ?? fallbackTimestamp ?? Date.now(),
|
|
92
|
+
validUntil: evo.validUntil ?? null,
|
|
93
|
+
eventTime: evo.eventTime ?? null,
|
|
94
|
+
};
|
|
95
|
+
} catch {
|
|
96
|
+
return defaultEvolution(fallbackTimestamp);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Patch evolution fields into an existing metadata JSON string.
|
|
102
|
+
* Merges with existing evolution — does not wipe unset fields.
|
|
103
|
+
*/
|
|
104
|
+
export function patchEvolution(
|
|
105
|
+
metadata: string | undefined,
|
|
106
|
+
patch: Partial<EvolutionMetadata>,
|
|
107
|
+
): string {
|
|
108
|
+
let parsed: Record<string, unknown> = {};
|
|
109
|
+
if (metadata) {
|
|
110
|
+
try { parsed = JSON.parse(metadata); } catch { /* keep empty */ }
|
|
111
|
+
}
|
|
112
|
+
const existing = parsed.evolution as Partial<EvolutionMetadata> | undefined;
|
|
113
|
+
parsed.evolution = { ...existing, ...patch };
|
|
114
|
+
return JSON.stringify(parsed);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
// Predicates
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
/** Is this memory currently active (not superseded/archived/consolidated)?
|
|
122
|
+
* HP-6: pending_review memories are also considered active for search purposes. */
|
|
123
|
+
export function isActiveMemory(metadata: string | undefined): boolean {
|
|
124
|
+
const evo = parseEvolution(metadata);
|
|
125
|
+
return evo.status === "active" || evo.status === "pending_review";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Access Tracking
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Record a retrieval hit: increment accessCount and update lastAccessedAt.
|
|
134
|
+
* Returns updated metadata JSON string.
|
|
135
|
+
*/
|
|
136
|
+
export function recordAccess(metadata: string | undefined): string {
|
|
137
|
+
const evo = parseEvolution(metadata);
|
|
138
|
+
return patchEvolution(metadata, {
|
|
139
|
+
accessCount: evo.accessCount + 1,
|
|
140
|
+
lastAccessedAt: Date.now(),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// Supersede / Consolidate helpers
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Mark a memory as superseded by a newer memory.
|
|
150
|
+
* Sets status, validUntil, and supersededBy link.
|
|
151
|
+
*/
|
|
152
|
+
export function buildSupersedeMetadata(
|
|
153
|
+
oldMetadata: string | undefined,
|
|
154
|
+
newMemoryId: string,
|
|
155
|
+
): string {
|
|
156
|
+
return patchEvolution(oldMetadata, {
|
|
157
|
+
status: "superseded",
|
|
158
|
+
validUntil: Date.now(),
|
|
159
|
+
supersededBy: newMemoryId,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* HP-1: Build evolution metadata for the NEW memory that supersedes an old one.
|
|
165
|
+
* Sets supersedes link + optional evolution note (bidirectional with buildSupersedeMetadata).
|
|
166
|
+
*/
|
|
167
|
+
export function buildSupersedeMetadataForNew(
|
|
168
|
+
newMetadata: string | undefined,
|
|
169
|
+
oldMemoryId: string,
|
|
170
|
+
evolutionNote?: string,
|
|
171
|
+
): string {
|
|
172
|
+
return patchEvolution(newMetadata, {
|
|
173
|
+
supersedes: oldMemoryId,
|
|
174
|
+
evolutionNote: evolutionNote ?? null,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* HP-1: Trace the evolution chain for a memory.
|
|
180
|
+
* Walks both directions: supersededBy (forward) and supersedes (backward).
|
|
181
|
+
* Returns an ordered timeline from oldest to newest.
|
|
182
|
+
*/
|
|
183
|
+
export interface EvolutionTraceEntry {
|
|
184
|
+
id: string;
|
|
185
|
+
direction: "predecessor" | "self" | "successor";
|
|
186
|
+
status: EvolutionStatus;
|
|
187
|
+
evolutionNote: string | null;
|
|
188
|
+
validFrom: number;
|
|
189
|
+
validUntil: number | null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export async function traceEvolution(
|
|
193
|
+
startId: string,
|
|
194
|
+
getEntry: (id: string) => Promise<{ metadata?: string; timestamp?: number } | null>,
|
|
195
|
+
maxDepth = 10,
|
|
196
|
+
): Promise<EvolutionTraceEntry[]> {
|
|
197
|
+
const predecessors: EvolutionTraceEntry[] = [];
|
|
198
|
+
const successors: EvolutionTraceEntry[] = [];
|
|
199
|
+
|
|
200
|
+
// Walk backward (supersedes chain)
|
|
201
|
+
let currentId: string | null = startId;
|
|
202
|
+
let depth = 0;
|
|
203
|
+
const startEntry = await getEntry(startId);
|
|
204
|
+
const startEvo = parseEvolution(startEntry?.metadata, startEntry?.timestamp);
|
|
205
|
+
|
|
206
|
+
// First, walk backward via supersedes
|
|
207
|
+
currentId = startEvo.supersedes;
|
|
208
|
+
while (currentId && depth < maxDepth) {
|
|
209
|
+
const entry = await getEntry(currentId);
|
|
210
|
+
if (!entry) break;
|
|
211
|
+
const evo = parseEvolution(entry.metadata, entry.timestamp);
|
|
212
|
+
predecessors.unshift({
|
|
213
|
+
id: currentId,
|
|
214
|
+
direction: "predecessor",
|
|
215
|
+
status: evo.status,
|
|
216
|
+
evolutionNote: evo.evolutionNote,
|
|
217
|
+
validFrom: evo.validFrom,
|
|
218
|
+
validUntil: evo.validUntil,
|
|
219
|
+
});
|
|
220
|
+
currentId = evo.supersedes;
|
|
221
|
+
depth++;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Self
|
|
225
|
+
const self: EvolutionTraceEntry = {
|
|
226
|
+
id: startId,
|
|
227
|
+
direction: "self",
|
|
228
|
+
status: startEvo.status,
|
|
229
|
+
evolutionNote: startEvo.evolutionNote,
|
|
230
|
+
validFrom: startEvo.validFrom,
|
|
231
|
+
validUntil: startEvo.validUntil,
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// Walk forward (supersededBy chain)
|
|
235
|
+
currentId = startEvo.supersededBy;
|
|
236
|
+
depth = 0;
|
|
237
|
+
while (currentId && depth < maxDepth) {
|
|
238
|
+
const entry = await getEntry(currentId);
|
|
239
|
+
if (!entry) break;
|
|
240
|
+
const evo = parseEvolution(entry.metadata, entry.timestamp);
|
|
241
|
+
successors.push({
|
|
242
|
+
id: currentId,
|
|
243
|
+
direction: "successor",
|
|
244
|
+
status: evo.status,
|
|
245
|
+
evolutionNote: evo.evolutionNote,
|
|
246
|
+
validFrom: evo.validFrom,
|
|
247
|
+
validUntil: evo.validUntil,
|
|
248
|
+
});
|
|
249
|
+
currentId = evo.supersededBy;
|
|
250
|
+
depth++;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return [...predecessors, self, ...successors];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Mark a memory as consolidated into a higher-level memory.
|
|
258
|
+
* The original is kept (archive-first) but linked to the consolidated entry.
|
|
259
|
+
*/
|
|
260
|
+
export function buildConsolidatedMetadata(
|
|
261
|
+
oldMetadata: string | undefined,
|
|
262
|
+
consolidatedMemoryId: string,
|
|
263
|
+
): string {
|
|
264
|
+
return patchEvolution(oldMetadata, {
|
|
265
|
+
status: "consolidated",
|
|
266
|
+
consolidatedInto: consolidatedMemoryId,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Mark a memory as archived (low decay score for extended period).
|
|
272
|
+
*/
|
|
273
|
+
export function buildArchivedMetadata(oldMetadata: string | undefined): string {
|
|
274
|
+
return patchEvolution(oldMetadata, { status: "archived" });
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* HP-6: Mark a memory as pending_review (low-confidence store).
|
|
279
|
+
* Pending memories participate in search but are prioritized for distill processing.
|
|
280
|
+
*/
|
|
281
|
+
export function buildPendingReviewMetadata(metadata: string | undefined): string {
|
|
282
|
+
return patchEvolution(metadata, { status: "pending_review" });
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** HP-6: Check if a memory is pending review. */
|
|
286
|
+
export function isPendingReview(metadata: string | undefined): boolean {
|
|
287
|
+
return parseEvolution(metadata).status === "pending_review";
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** HP-6: Resolve pending → active (after distill re-evaluation confirms it). */
|
|
291
|
+
export function resolvePendingReview(metadata: string | undefined): string {
|
|
292
|
+
return patchEvolution(metadata, { status: "active" });
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ---------------------------------------------------------------------------
|
|
296
|
+
// Decay Scoring
|
|
297
|
+
// ---------------------------------------------------------------------------
|
|
298
|
+
|
|
299
|
+
const TIME_HALF_LIFE_DAYS = 90;
|
|
300
|
+
|
|
301
|
+
// Base weights (no emotion): 0.2 + 0.3 + 0.5 = 1.0
|
|
302
|
+
const TIME_WEIGHT_BASE = 0.2;
|
|
303
|
+
const FREQUENCY_WEIGHT_BASE = 0.3;
|
|
304
|
+
const IMPORTANCE_WEIGHT_BASE = 0.5;
|
|
305
|
+
|
|
306
|
+
// Emotion-enabled weights: 0.15 + 0.25 + 0.45 + 0.15 = 1.0
|
|
307
|
+
const TIME_WEIGHT_EMO = 0.15;
|
|
308
|
+
const FREQUENCY_WEIGHT_EMO = 0.25;
|
|
309
|
+
const IMPORTANCE_WEIGHT_EMO = 0.45;
|
|
310
|
+
const EMOTION_WEIGHT = 0.15;
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Compute composite decay score (0–1, higher = more valuable to keep).
|
|
314
|
+
*
|
|
315
|
+
* Base: 0.2 × timeDecay + 0.3 × frequencyScore + 0.5 × importance
|
|
316
|
+
* Emotion: 0.15 × time + 0.25 × freq + 0.45 × importance + 0.15 × emotionSalience
|
|
317
|
+
*
|
|
318
|
+
* Time decay: exponential with 90-day half-life.
|
|
319
|
+
* Frequency: log(1 + accessCount) × recencyBoost, capped at 1.
|
|
320
|
+
* Importance: as-stored (0–1).
|
|
321
|
+
* Emotion salience: (|valence| + arousal) / 2 — composite mnemonic significance.
|
|
322
|
+
*
|
|
323
|
+
* @param metadata - Optional metadata JSON string for emotion extraction.
|
|
324
|
+
* When absent or emotion flag off, falls back to base weights.
|
|
325
|
+
*/
|
|
326
|
+
export function computeDecayScore(
|
|
327
|
+
evo: EvolutionMetadata,
|
|
328
|
+
importance: number,
|
|
329
|
+
now?: number,
|
|
330
|
+
metadata?: string,
|
|
331
|
+
): number {
|
|
332
|
+
const ts = now ?? Date.now();
|
|
333
|
+
|
|
334
|
+
// Time decay (Weibull-ish exponential)
|
|
335
|
+
const daysSinceCreation = Math.max(0, (ts - evo.validFrom) / 86_400_000);
|
|
336
|
+
const timeDecay = Math.pow(0.5, daysSinceCreation / TIME_HALF_LIFE_DAYS);
|
|
337
|
+
|
|
338
|
+
// Frequency score
|
|
339
|
+
const rawFreq = Math.log2(1 + evo.accessCount);
|
|
340
|
+
const recencyBoost = evo.lastAccessedAt
|
|
341
|
+
? Math.pow(0.5, Math.max(0, (ts - evo.lastAccessedAt) / 86_400_000) / 30) // 30-day half-life for recency
|
|
342
|
+
: 0.5; // never accessed → neutral
|
|
343
|
+
const frequencyScore = Math.min(1, rawFreq * recencyBoost);
|
|
344
|
+
|
|
345
|
+
const clampedImportance = Math.max(0, Math.min(1, importance));
|
|
346
|
+
|
|
347
|
+
// HP-emo: Emotion-aware decay scoring
|
|
348
|
+
const useEmotion = isEmotionScoringEnabled() && !!metadata;
|
|
349
|
+
if (useEmotion) {
|
|
350
|
+
const emotion = parseEmotion(metadata);
|
|
351
|
+
const salience = emotion?.salience ?? 0;
|
|
352
|
+
return (
|
|
353
|
+
TIME_WEIGHT_EMO * timeDecay +
|
|
354
|
+
FREQUENCY_WEIGHT_EMO * frequencyScore +
|
|
355
|
+
IMPORTANCE_WEIGHT_EMO * clampedImportance +
|
|
356
|
+
EMOTION_WEIGHT * salience
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return (
|
|
361
|
+
TIME_WEIGHT_BASE * timeDecay +
|
|
362
|
+
FREQUENCY_WEIGHT_BASE * frequencyScore +
|
|
363
|
+
IMPORTANCE_WEIGHT_BASE * clampedImportance
|
|
364
|
+
);
|
|
365
|
+
}
|