recallnest 1.4.1 → 2.2.0

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.
Files changed (166) hide show
  1. package/.dockerignore +11 -0
  2. package/CLAUDE.md +7 -3
  3. package/Dockerfile +20 -0
  4. package/README.md +202 -130
  5. package/README_CN.md +263 -190
  6. package/ROADMAP.md +128 -81
  7. package/SKILL.md +250 -0
  8. package/assets/dashboard.png +0 -0
  9. package/assets/knowledge-graph.png +0 -0
  10. package/assets/screenshots/ui-full.png +0 -0
  11. package/assets/screenshots/ui-overview.png +0 -0
  12. package/assets/screenshots/ui-search.png +0 -0
  13. package/assets/ui/app.js +227 -3
  14. package/assets/ui/index.html +81 -11
  15. package/assets/ui/styles.css +178 -2
  16. package/bin/recallnest.mjs +62 -0
  17. package/bun.lock +337 -0
  18. package/daily-recall.sh +23 -0
  19. package/daily-reflections/2026-04-02.md +35 -0
  20. package/daily-reflections/2026-04-03.md +37 -0
  21. package/daily-reflections/2026-04-04.md +37 -0
  22. package/daily-reflections/2026-04-05.md +35 -0
  23. package/daily-reflections/2026-04-06.md +36 -0
  24. package/daily-reflections/2026-04-07.md +35 -0
  25. package/daily-reflections/2026-04-08.md +35 -0
  26. package/daily-reflections/2026-04-12.md +35 -0
  27. package/data/alias-map.example.json +22 -0
  28. package/docs/plans/2026-04-01-recall-boost.md +100 -0
  29. package/docs/superpowers/plans/2026-04-11-constructive-retrieval-emotion.md +1195 -0
  30. package/docs/superpowers/specs/2026-04-11-constructive-retrieval-emotion-design.md +398 -0
  31. package/glama.json +6 -0
  32. package/integrations/setup-all.sh +31 -0
  33. package/package.json +10 -3
  34. package/plan-philosophy-upgrades.md +363 -0
  35. package/plan-product-upgrades.md +155 -0
  36. package/plan.md +140 -0
  37. package/research-memory-evolution.md +440 -0
  38. package/research-rl-agent-memory.md +330 -0
  39. package/scripts/backfill-anchors.ts +130 -0
  40. package/scripts/diagnose-vector-search.ts +132 -0
  41. package/scripts/migrate-language.ts +75 -0
  42. package/scripts/verify-emotion-decay.ts +148 -0
  43. package/src/__tests__/activity-counter.test.ts +96 -0
  44. package/src/__tests__/admission-control.test.ts +206 -0
  45. package/src/__tests__/anchor-generator.test.ts +67 -0
  46. package/src/__tests__/babel-integration.test.ts +139 -0
  47. package/src/__tests__/capture-engine.test.ts +5 -3
  48. package/src/__tests__/cascade-forget.test.ts +1 -1
  49. package/src/__tests__/cc8-cc1-essential-context.test.ts +298 -0
  50. package/src/__tests__/circuit-breaker.test.ts +99 -0
  51. package/src/__tests__/cluster-consolidation.test.ts +712 -0
  52. package/src/__tests__/confidence-metadata.test.ts +125 -0
  53. package/src/__tests__/confidence-tracker.test.ts +53 -0
  54. package/src/__tests__/consolidation-engine.test.ts +1 -1
  55. package/src/__tests__/constructive-retrieval.test.ts +509 -0
  56. package/src/__tests__/context-collapse-renderer.test.ts +214 -0
  57. package/src/__tests__/context-reconstructor.test.ts +65 -0
  58. package/src/__tests__/conversation-importer.test.ts +692 -0
  59. package/src/__tests__/data-checkup.test.ts +225 -0
  60. package/src/__tests__/decay-exemption.test.ts +84 -0
  61. package/src/__tests__/dedup-guard.test.ts +39 -0
  62. package/src/__tests__/distill-lock.test.ts +176 -0
  63. package/src/__tests__/dream-pipeline.test.ts +232 -0
  64. package/src/__tests__/emotion-decay.test.ts +46 -0
  65. package/src/__tests__/emotion-detector.test.ts +56 -0
  66. package/src/__tests__/emotion-phase1-integration.test.ts +153 -0
  67. package/src/__tests__/emotion-retrieval.test.ts +56 -0
  68. package/src/__tests__/ethics-layer.test.ts +476 -0
  69. package/src/__tests__/feature-flags-integration.test.ts +54 -0
  70. package/src/__tests__/frequency-tracker.test.ts +113 -0
  71. package/src/__tests__/graph-export.test.ts +489 -0
  72. package/src/__tests__/interference-detector.test.ts +203 -0
  73. package/src/__tests__/mcp-efficiency.test.ts +288 -0
  74. package/src/__tests__/memory-evolution-b2-b3-e1.test.ts +243 -0
  75. package/src/__tests__/memory-evolution.test.ts +271 -0
  76. package/src/__tests__/memory-lint.test.ts +271 -0
  77. package/src/__tests__/narrative-architecture.test.ts +456 -0
  78. package/src/__tests__/pending-review.test.ts +77 -0
  79. package/src/__tests__/post-retrieval-filter.test.ts +178 -0
  80. package/src/__tests__/prediction-engine.test.ts +349 -0
  81. package/src/__tests__/predictive-prospective-memory.test.ts +416 -0
  82. package/src/__tests__/preference-slots.test.ts +99 -0
  83. package/src/__tests__/query-expander-alias.test.ts +89 -0
  84. package/src/__tests__/recall-governor.test.ts +227 -0
  85. package/src/__tests__/resume-context.test.ts +156 -1
  86. package/src/__tests__/salience-scoring.test.ts +106 -0
  87. package/src/__tests__/session-distiller.test.ts +611 -0
  88. package/src/__tests__/skill-engine.test.ts +546 -0
  89. package/src/__tests__/skill-promotion.test.ts +349 -0
  90. package/src/__tests__/supersede-chain.test.ts +154 -0
  91. package/src/__tests__/temporal-validity.test.ts +160 -0
  92. package/src/__tests__/topic-tag.test.ts +113 -0
  93. package/src/__tests__/trusted-memory.test.ts +828 -0
  94. package/src/__tests__/write-verifier.test.ts +122 -0
  95. package/src/access-tracker.ts +3 -2
  96. package/src/activity-counter.ts +109 -0
  97. package/src/adaptive-retrieval.ts +6 -5
  98. package/src/admission-control.ts +163 -0
  99. package/src/anchor-generator.ts +80 -0
  100. package/src/api-server.ts +10 -0
  101. package/src/audit-log.ts +122 -0
  102. package/src/auto-gc.ts +98 -29
  103. package/src/auto-recall.ts +33 -4
  104. package/src/capture-engine.ts +372 -23
  105. package/src/cascade-forget.ts +3 -2
  106. package/src/cli.ts +107 -5
  107. package/src/compat.ts +22 -0
  108. package/src/confidence-tracker.ts +210 -47
  109. package/src/conflict-store.ts +12 -3
  110. package/src/consolidation-engine.ts +296 -8
  111. package/src/context-collapse-renderer.ts +222 -0
  112. package/src/context-composer-stable-selection.ts +5 -1
  113. package/src/context-composer.ts +239 -4
  114. package/src/context-reconstructor.ts +404 -0
  115. package/src/conversation-importer.ts +441 -0
  116. package/src/data-checkup.ts +232 -0
  117. package/src/decay-engine.ts +84 -0
  118. package/src/distill-lock.ts +122 -0
  119. package/src/doctor.ts +8 -10
  120. package/src/dream-pipeline.ts +227 -0
  121. package/src/emotion-detector.ts +90 -0
  122. package/src/eval.ts +3 -2
  123. package/src/forget-engine.ts +269 -0
  124. package/src/frequency-tracker.ts +175 -0
  125. package/src/graph-export.ts +644 -0
  126. package/src/ingest.ts +36 -6
  127. package/src/interference-detector.ts +187 -0
  128. package/src/kg-extractor.ts +15 -24
  129. package/src/kg-store.ts +2 -1
  130. package/src/language-hook.ts +67 -0
  131. package/src/llm-client.ts +289 -6
  132. package/src/mcp-server.ts +707 -80
  133. package/src/memory-assets.ts +22 -5
  134. package/src/memory-evolution.ts +365 -0
  135. package/src/memory-lint.ts +429 -0
  136. package/src/memory-output.ts +69 -0
  137. package/src/memory-schema.ts +74 -2
  138. package/src/narrative-schema.ts +79 -0
  139. package/src/narrative-tagger.ts +164 -0
  140. package/src/pii-detector.ts +98 -0
  141. package/src/post-retrieval-filter.ts +76 -0
  142. package/src/prediction-engine.ts +253 -0
  143. package/src/preference-slots.ts +65 -2
  144. package/src/prospective-memory.ts +215 -0
  145. package/src/query-expander.ts +67 -0
  146. package/src/recall-governor.ts +183 -0
  147. package/src/retention-policy.ts +90 -0
  148. package/src/retriever.ts +828 -55
  149. package/src/rif.ts +34 -8
  150. package/src/runtime-config.ts +23 -8
  151. package/src/scope-inventory.ts +4 -3
  152. package/src/scope-policy.ts +4 -0
  153. package/src/session-distiller.ts +307 -0
  154. package/src/session-output.ts +40 -0
  155. package/src/session-schema.ts +37 -0
  156. package/src/session-store.ts +7 -2
  157. package/src/skill-engine.ts +208 -0
  158. package/src/skill-promotion.ts +293 -0
  159. package/src/skill-schema.ts +39 -0
  160. package/src/store.ts +96 -10
  161. package/src/topic-tag.ts +66 -0
  162. package/src/tracker.ts +3 -1
  163. package/src/ui-server.ts +80 -8
  164. package/src/workflow-observation-scope-review.ts +2 -1
  165. package/src/workflow-observation-store.ts +2 -1
  166. package/src/write-verifier.ts +126 -0
package/src/auto-gc.ts CHANGED
@@ -5,10 +5,20 @@
5
5
  * AND enough time has passed since last GC. Inspired by NREM triple-coupling:
6
6
  * multiple conditions must be satisfied before consolidation fires.
7
7
  *
8
- * Wraps existing cleanup scripts into a programmatic API.
8
+ * B-2 upgrade: Uses evolution system's computeDecayScore + buildArchivedMetadata
9
+ * instead of raw age/importance heuristics. Archive-first, delete-never.
9
10
  */
10
11
 
11
12
  import type { MemoryStore } from "./store.js";
13
+ import {
14
+ parseEvolution,
15
+ computeDecayScore,
16
+ buildArchivedMetadata,
17
+ isActiveMemory,
18
+ } from "./memory-evolution.js";
19
+ import { loadRetentionPolicy, shouldArchiveByPolicy } from "./retention-policy.js";
20
+ import type { RetentionPolicy } from "./retention-policy.js";
21
+ import type { AuditLogger } from "./audit-log.js";
12
22
 
13
23
  // ---------------------------------------------------------------------------
14
24
  // Config
@@ -19,17 +29,20 @@ export interface AutoGcConfig {
19
29
  minMemoryCount: number;
20
30
  /** Minimum hours since last GC (default: 24) */
21
31
  minHoursSinceLastGc: number;
22
- /** Score below which memories are candidates for archival (default: 0.2) */
23
- staleScoreThreshold: number;
32
+ /** Decay score below which memories are archive candidates (default: 0.15) */
33
+ decayScoreThreshold: number;
24
34
  /** Max entries to archive per run (default: 100) */
25
35
  maxArchivePerRun: number;
36
+ /** Minimum age in days before a memory can be archived (default: 30) */
37
+ minAgeDays: number;
26
38
  }
27
39
 
28
40
  export const DEFAULT_AUTO_GC_CONFIG: AutoGcConfig = {
29
41
  minMemoryCount: 1000,
30
42
  minHoursSinceLastGc: 24,
31
- staleScoreThreshold: 0.2,
43
+ decayScoreThreshold: 0.15,
32
44
  maxArchivePerRun: 100,
45
+ minAgeDays: 30,
33
46
  };
34
47
 
35
48
  // ---------------------------------------------------------------------------
@@ -52,10 +65,18 @@ export interface GcResult {
52
65
  /**
53
66
  * Check conditions and run GC if all thresholds are met.
54
67
  * Returns immediately if conditions not met (no-op).
68
+ *
69
+ * Archive criteria (B-2 evolution-aware):
70
+ * - Memory must be "active" (not already superseded/archived/consolidated)
71
+ * - Composite decay_score < threshold (default 0.15)
72
+ * - Age > minAgeDays (default 30 days)
73
+ * - Not pinned (importance >= 0.95 is considered pinned)
55
74
  */
56
75
  export async function maybeRunGc(
57
76
  store: MemoryStore,
58
77
  config: AutoGcConfig = DEFAULT_AUTO_GC_CONFIG,
78
+ retentionConfigDir?: string,
79
+ auditLogger?: AuditLogger,
59
80
  ): Promise<GcResult> {
60
81
  const stats = await store.stats();
61
82
  const totalMemories = stats.total ?? 0;
@@ -74,8 +95,7 @@ export async function maybeRunGc(
74
95
  // All conditions met — run GC
75
96
  lastGcTimestamp = Date.now();
76
97
 
77
- // Find stale peripheral memories with low importance and no recent access
78
- const cutoffMs = Date.now() - 60 * 86_400_000; // 60 days ago
98
+ const now = Date.now();
79
99
  let archivedCount = 0;
80
100
  let totalChecked = 0;
81
101
 
@@ -83,33 +103,82 @@ export async function maybeRunGc(
83
103
  const entries = await store.list({ limit: 5000 });
84
104
  totalChecked = entries.length;
85
105
 
106
+ // Pre-compute per-scope active memory counts and cache retention policies.
107
+ // This avoids repeated file reads inside the loop.
108
+ const activeCounts = new Map<string, number>();
109
+ const policyCache = new Map<string, RetentionPolicy>();
110
+ const scopesSeen = new Set<string>();
111
+
112
+ for (const entry of entries) {
113
+ const scope = entry.scope ?? "";
114
+ scopesSeen.add(scope);
115
+ if (isActiveMemory(entry.metadata)) {
116
+ activeCounts.set(scope, (activeCounts.get(scope) ?? 0) + 1);
117
+ }
118
+ }
119
+
120
+ // Batch-load retention policies for all scopes (one file read per scope)
121
+ for (const scope of scopesSeen) {
122
+ policyCache.set(scope, loadRetentionPolicy(scope, retentionConfigDir));
123
+ }
124
+
86
125
  for (const entry of entries) {
87
126
  if (archivedCount >= config.maxArchivePerRun) break;
88
127
 
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) });
128
+ // Only archive active memories
129
+ if (!isActiveMemory(entry.metadata)) continue;
130
+
131
+ // Never archive pinned memories (importance >= 0.95)
132
+ const importance = entry.importance ?? 0.5;
133
+ if (importance >= 0.95) continue;
134
+
135
+ // Check minimum age
136
+ const ageDays = (now - entry.timestamp) / 86_400_000;
137
+
138
+ // Decay-based archival (existing logic)
139
+ let shouldArchive = false;
140
+ if (ageDays >= config.minAgeDays) {
141
+ const evo = parseEvolution(entry.metadata, entry.timestamp);
142
+ let decayScore = computeDecayScore(evo, importance, now, entry.metadata);
143
+ // F3: Expired memories (validUntil < now) get 2x decay acceleration
144
+ if (evo.validUntil != null && evo.validUntil < now) {
145
+ decayScore *= 0.5; // Halve the score → archives faster
146
+ }
147
+ if (decayScore < config.decayScoreThreshold) {
148
+ shouldArchive = true;
149
+ }
150
+ }
151
+
152
+ // Retention-policy-based archival (F-2): OR with decay-based
153
+ if (!shouldArchive) {
154
+ const scope = entry.scope ?? "";
155
+ const policy = policyCache.get(scope) ?? loadRetentionPolicy(scope, retentionConfigDir);
156
+ const activeCount = activeCounts.get(scope) ?? 0;
157
+ const policyCheck = shouldArchiveByPolicy(policy, ageDays, activeCount);
158
+ if (policyCheck.archive) {
159
+ shouldArchive = true;
160
+ }
161
+ }
162
+
163
+ if (shouldArchive) {
164
+ const archivedMeta = buildArchivedMetadata(entry.metadata);
165
+ await store.update(entry.id, { metadata: archivedMeta });
112
166
  archivedCount++;
167
+ // F-1: Audit log — record archive operation (silent on failure)
168
+ try {
169
+ auditLogger?.log({
170
+ operation: "archive",
171
+ memoryId: entry.id,
172
+ actor: "system",
173
+ details: `auto-gc: age=${Math.floor(ageDays)}d`,
174
+ });
175
+ } catch { /* Audit must never block GC */ }
176
+ // Decrement active count for the scope (memory is no longer active)
177
+ const scope = entry.scope ?? "";
178
+ const prev = activeCounts.get(scope) ?? 0;
179
+ if (prev > 0) {
180
+ activeCounts.set(scope, prev - 1);
181
+ }
113
182
  }
114
183
  }
115
184
 
@@ -1,5 +1,7 @@
1
1
  import { composeResumeContext, type ResumeContextDeps } from "./context-composer.js";
2
+ import { resolveTier } from "./decay-engine.js";
2
3
  import type { DurableMemoryCategory } from "./memory-schema.js";
4
+ import { governResults, truncateQuery, type GovernorConfig, type GovernorSession } from "./recall-governor.js";
3
5
  import type { RetrievalResult } from "./retriever.js";
4
6
  import { buildRetrievalContext, resolveScopeSelection } from "./scope-policy.js";
5
7
  import type { RetrievalProfileName, ResumeContextResponse } from "./session-schema.js";
@@ -17,6 +19,10 @@ export interface AutoRecallRequest {
17
19
  profile?: RetrievalProfileName;
18
20
  env?: NodeJS.ProcessEnv;
19
21
  operation?: string;
22
+ /** Governor config overrides (charBudget, maxItems, maxQueryChars) */
23
+ governor?: Partial<GovernorConfig>;
24
+ /** Governor session for cross-call dedup within a single conversation turn */
25
+ governorSession?: GovernorSession;
20
26
  }
21
27
 
22
28
  export interface AutoRecallResponse {
@@ -31,8 +37,10 @@ export async function runAutoRecall(
31
37
  deps: ResumeContextDeps,
32
38
  request: AutoRecallRequest,
33
39
  ): Promise<AutoRecallResponse> {
34
- const message = request.message.replace(/\s+/g, " ").trim();
35
- const task = (request.task || message).replace(/\s+/g, " ").trim();
40
+ const govCfg = request.governor;
41
+ const maxQ = govCfg?.maxQueryChars ?? 1000;
42
+ const message = truncateQuery(request.message.replace(/\s+/g, " ").trim(), maxQ);
43
+ const task = truncateQuery((request.task || message).replace(/\s+/g, " ").trim(), maxQ);
36
44
 
37
45
  if (!message) {
38
46
  throw new Error("message is required");
@@ -72,9 +80,11 @@ export async function runAutoRecall(
72
80
  };
73
81
  }
74
82
 
75
- const results = await deps.retriever.retrieve(buildRetrievalContext({
83
+ const effectiveLimit = request.limit || 5;
84
+ // Fetch extra candidates so tier filtering still yields enough results
85
+ const rawResults = await deps.retriever.retrieve(buildRetrievalContext({
76
86
  query: message,
77
- limit: request.limit || 5,
87
+ limit: effectiveLimit * 3,
78
88
  category: request.category,
79
89
  scope: resolvedScope,
80
90
  sessionId: request.sessionId,
@@ -86,6 +96,25 @@ export async function runAutoRecall(
86
96
  allowUnscoped: request.allScopes === true,
87
97
  }));
88
98
 
99
+ // Tier-aware filtering: prefer core/working memories, backfill with peripheral.
100
+ // Peripheral memories get a 15% score discount to reduce noise injection.
101
+ const promoted: RetrievalResult[] = [];
102
+ const demoted: RetrievalResult[] = [];
103
+ for (const r of rawResults) {
104
+ const tier = resolveTier(r.entry.metadata);
105
+ if (tier === "core" || tier === "working") {
106
+ promoted.push(r);
107
+ } else {
108
+ demoted.push({ ...r, score: r.score * 0.85 });
109
+ }
110
+ }
111
+ const tierFiltered = [...promoted, ...demoted]
112
+ .sort((a, b) => b.score - a.score)
113
+ .slice(0, effectiveLimit);
114
+
115
+ // LME-7: Recall Governor — evolution filter, budget control, session dedup
116
+ const results = governResults(tierFiltered, request.governorSession, govCfg);
117
+
89
118
  return {
90
119
  mode: "resume+search",
91
120
  resolvedScope,