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.
Files changed (261) hide show
  1. package/.claude-plugin/CLAUDE.md +29 -0
  2. package/.claude-plugin/marketplace.json +18 -0
  3. package/.env.example +3 -0
  4. package/.github/workflows/ci.yml +28 -0
  5. package/.mcp.json +9 -0
  6. package/CHANGELOG.md +51 -0
  7. package/CLAUDE.md +42 -0
  8. package/DEMO.md +94 -0
  9. package/EVAL.md +89 -0
  10. package/FAILURES.md +75 -0
  11. package/LICENSE +21 -0
  12. package/OPERATIONS.md +59 -0
  13. package/README.md +427 -0
  14. package/README_CN.md +425 -0
  15. package/ROADMAP.md +198 -0
  16. package/assets/recallnest-workbench.png +0 -0
  17. package/assets/ui/app.js +807 -0
  18. package/assets/ui/index.html +154 -0
  19. package/assets/ui/styles.css +605 -0
  20. package/assets/wechat-qr.jpg +0 -0
  21. package/config.json.example +38 -0
  22. package/daily-recall.sh +87 -0
  23. package/daily-reflections/2026-03-05.md +15 -0
  24. package/daily-reflections/2026-03-06.md +15 -0
  25. package/daily-reflections/2026-03-07.md +7 -0
  26. package/daily-reflections/2026-03-08.md +34 -0
  27. package/daily-reflections/2026-03-09.md +35 -0
  28. package/daily-reflections/2026-03-10.md +36 -0
  29. package/daily-reflections/2026-03-11.md +35 -0
  30. package/daily-reflections/2026-03-12.md +36 -0
  31. package/daily-reflections/2026-03-13.md +35 -0
  32. package/daily-reflections/2026-03-14.md +35 -0
  33. package/daily-reflections/2026-03-15.md +35 -0
  34. package/daily-reflections/2026-03-16.md +41 -0
  35. package/daily-reflections/2026-03-17.md +10 -0
  36. package/daily-reflections/2026-03-18.md +10 -0
  37. package/daily-reflections/2026-03-19.md +10 -0
  38. package/daily-reflections/2026-03-20.md +10 -0
  39. package/daily-reflections/2026-03-21.md +10 -0
  40. package/daily-reflections/2026-03-22.md +10 -0
  41. package/daily-reflections/2026-03-23.md +10 -0
  42. package/daily-reflections/2026-03-24.md +10 -0
  43. package/daily-reflections/2026-03-25.md +32 -0
  44. package/daily-reflections/2026-03-26.md +37 -0
  45. package/daily-reflections/2026-03-27.md +35 -0
  46. package/daily-reflections/2026-03-28.md +35 -0
  47. package/daily-reflections/2026-03-29.md +35 -0
  48. package/daily-reflections/2026-03-30.md +36 -0
  49. package/daily-reflections/2026-03-31.md +35 -0
  50. package/daily-reflections/2026-04-01.md +35 -0
  51. package/docs/api-reference.md +1291 -0
  52. package/docs/architecture.md +168 -0
  53. package/docs/memory-boundary-contract.md +226 -0
  54. package/docs/memory-categories.md +119 -0
  55. package/docs/pipeline-audit-ultramemory.md +177 -0
  56. package/docs/plans/2026-03-08-query-expansion.md +270 -0
  57. package/docs/plans/2026-03-16-continuity-layer.md +196 -0
  58. package/docs/plans/mcp-tiering-plan.md +126 -0
  59. package/docs/research/recall-quality-audit-2026-03-22.md +153 -0
  60. package/docs/self-evolution.md +198 -0
  61. package/docs/superpowers/plans/2026-03-19-ingest-pipeline-upgrade.md +782 -0
  62. package/docs/superpowers/specs/2026-03-19-ingest-pipeline-upgrade-design.md +77 -0
  63. package/docs/superpowers/specs/2026-03-23-pull-claude-desktop-design.md +95 -0
  64. package/eval/cases.json +85 -0
  65. package/eval/continuity/case-seeds.json +90 -0
  66. package/eval/continuity/cases.json +302 -0
  67. package/eval/continuity/memory-seeds.json +26 -0
  68. package/eval/continuity/pattern-seeds.json +100 -0
  69. package/eval/continuity/results-2026-03-16.md +117 -0
  70. package/eval/continuity/results-2026-03-19.md +453 -0
  71. package/eval/continuity/runbook.md +110 -0
  72. package/eval/continuity/seed-cases.md +54 -0
  73. package/eval/reports/2026-03-06-abstract-query-baseline.md +96 -0
  74. package/eval/reports/2026-03-06-aws-ssh-baseline.md +82 -0
  75. package/eval/reports/2026-03-06-baseline.md +82 -0
  76. package/eval/reports/query-expansion.md +138 -0
  77. package/integrations/README.md +57 -0
  78. package/integrations/claude-code/README.md +133 -0
  79. package/integrations/claude-code/claude-md-snippet.md +31 -0
  80. package/integrations/claude-code/setup.sh +66 -0
  81. package/integrations/claude-code/smoke-continuity.sh +452 -0
  82. package/integrations/codex/README.md +49 -0
  83. package/integrations/codex/agents-md-snippet.md +31 -0
  84. package/integrations/codex/setup.sh +58 -0
  85. package/integrations/examples/claude-agent-sdk/memory-agent.ts +182 -0
  86. package/integrations/examples/langchain/memory-chain.py +111 -0
  87. package/integrations/examples/openai-agents-sdk/memory-agent.py +109 -0
  88. package/integrations/gemini-cli/README.md +57 -0
  89. package/integrations/gemini-cli/gemini-md-snippet.md +31 -0
  90. package/integrations/gemini-cli/setup.sh +64 -0
  91. package/integrations/shared/install-managed-block.sh +51 -0
  92. package/lm +19 -0
  93. package/package.json +50 -0
  94. package/plan-v1.2-distributable.md +105 -0
  95. package/scripts/archive-distilled-junk.ts +116 -0
  96. package/scripts/backfill-l0l1l2.ts +98 -0
  97. package/scripts/blind-scan.ts +588 -0
  98. package/scripts/cleanup-step1-exact-dups.ts +117 -0
  99. package/scripts/cleanup-step2-template-dups.ts +113 -0
  100. package/scripts/cleanup-step3-stale-content.ts +149 -0
  101. package/scripts/cleanup-step4-orphan-scopes.ts +131 -0
  102. package/scripts/delete-distilled-junk.ts +88 -0
  103. package/scripts/distill-facts.ts +305 -0
  104. package/scripts/health-check.ts +343 -0
  105. package/scripts/incremental-ingest.sh +71 -0
  106. package/scripts/mlp-health-check.ts +382 -0
  107. package/scripts/pull-claude-desktop.ts +515 -0
  108. package/scripts/re-ingest-cc.sh +66 -0
  109. package/scripts/rebalance-memory-health.ts +181 -0
  110. package/scripts/run-desktop-ingest.sh +11 -0
  111. package/scripts/start-server.sh +12 -0
  112. package/scripts/viz-dashboard.html +423 -0
  113. package/scripts/viz-export.ts +703 -0
  114. package/src/__tests__/access-tracker-novelty.test.ts +228 -0
  115. package/src/__tests__/auto-consolidation.test.ts +168 -0
  116. package/src/__tests__/auto-recall.test.ts +131 -0
  117. package/src/__tests__/batch-dedup.test.ts +65 -0
  118. package/src/__tests__/capture-engine.test.ts +1142 -0
  119. package/src/__tests__/capture-heuristic.test.ts +171 -0
  120. package/src/__tests__/cascade-forget.test.ts +210 -0
  121. package/src/__tests__/category-min-scores.test.ts +33 -0
  122. package/src/__tests__/confidence-tracker.test.ts +196 -0
  123. package/src/__tests__/conflict-advisor.test.ts +206 -0
  124. package/src/__tests__/conflict-escalation.test.ts +149 -0
  125. package/src/__tests__/conflict-lifecycle.test.ts +83 -0
  126. package/src/__tests__/conflict-output.test.ts +85 -0
  127. package/src/__tests__/conflict-store.test.ts +374 -0
  128. package/src/__tests__/consolidation-engine.test.ts +181 -0
  129. package/src/__tests__/context-composer-pins.test.ts +64 -0
  130. package/src/__tests__/context-composer-task-selection.test.ts +24 -0
  131. package/src/__tests__/context-renderer.test.ts +91 -0
  132. package/src/__tests__/continuity-eval.test.ts +275 -0
  133. package/src/__tests__/continuity-schema.test.ts +97 -0
  134. package/src/__tests__/core-recall-eval.test.ts +235 -0
  135. package/src/__tests__/core-summary.test.ts +109 -0
  136. package/src/__tests__/dedup-actions.test.ts +58 -0
  137. package/src/__tests__/deterministic-id.test.ts +39 -0
  138. package/src/__tests__/doctor.test.ts +303 -0
  139. package/src/__tests__/drill-down.test.ts +108 -0
  140. package/src/__tests__/embedder.test.ts +122 -0
  141. package/src/__tests__/entity-resolver.test.ts +58 -0
  142. package/src/__tests__/event-segmenter.test.ts +153 -0
  143. package/src/__tests__/hotness-blend.test.ts +76 -0
  144. package/src/__tests__/ingest.test.ts +360 -0
  145. package/src/__tests__/kg-extractor.test.ts +184 -0
  146. package/src/__tests__/kg-store.test.ts +198 -0
  147. package/src/__tests__/llm-consolidation.test.ts +226 -0
  148. package/src/__tests__/memory-boundaries.test.ts +237 -0
  149. package/src/__tests__/memory-health-rebalance.test.ts +144 -0
  150. package/src/__tests__/memory-output.test.ts +215 -0
  151. package/src/__tests__/multi-vector.test.ts +242 -0
  152. package/src/__tests__/ppr-traversal.test.ts +256 -0
  153. package/src/__tests__/preference-guard-smoke.test.ts +211 -0
  154. package/src/__tests__/preference-matcher.test.ts +230 -0
  155. package/src/__tests__/preference-slots.test.ts +106 -0
  156. package/src/__tests__/prospective-memory.test.ts +283 -0
  157. package/src/__tests__/query-entity-detector.test.ts +171 -0
  158. package/src/__tests__/query-expander.test.ts +34 -0
  159. package/src/__tests__/recall-mode.test.ts +64 -0
  160. package/src/__tests__/result-synthesizer.test.ts +198 -0
  161. package/src/__tests__/resume-context.test.ts +4743 -0
  162. package/src/__tests__/retrieval-trace.test.ts +213 -0
  163. package/src/__tests__/retriever-boundary-weight.test.ts +136 -0
  164. package/src/__tests__/retroactive-boost.test.ts +223 -0
  165. package/src/__tests__/scope-inventory.test.ts +200 -0
  166. package/src/__tests__/scope-policy.test.ts +69 -0
  167. package/src/__tests__/session-checkpoint.test.ts +259 -0
  168. package/src/__tests__/session-gc.test.ts +215 -0
  169. package/src/__tests__/store-scope-audit.test.ts +81 -0
  170. package/src/__tests__/temporal-parser.test.ts +241 -0
  171. package/src/__tests__/term-registry.test.ts +121 -0
  172. package/src/__tests__/tool-output-compressor.test.ts +133 -0
  173. package/src/__tests__/version-manager.test.ts +213 -0
  174. package/src/__tests__/workflow-observation.test.ts +400 -0
  175. package/src/access-tracker.ts +316 -0
  176. package/src/adaptive-retrieval.ts +64 -0
  177. package/src/api-server.ts +667 -0
  178. package/src/asset-sync.ts +79 -0
  179. package/src/auto-consolidation.ts +133 -0
  180. package/src/auto-gc.ts +124 -0
  181. package/src/auto-recall.ts +95 -0
  182. package/src/capture-engine.ts +931 -0
  183. package/src/capture-heuristic.ts +177 -0
  184. package/src/cascade-forget.ts +135 -0
  185. package/src/chunker.ts +253 -0
  186. package/src/cli.ts +1917 -0
  187. package/src/confidence-tracker.ts +166 -0
  188. package/src/conflict-advisor.ts +433 -0
  189. package/src/conflict-engine.ts +181 -0
  190. package/src/conflict-escalation.ts +138 -0
  191. package/src/conflict-lifecycle.ts +67 -0
  192. package/src/conflict-output.ts +218 -0
  193. package/src/conflict-schema.ts +130 -0
  194. package/src/conflict-store.ts +127 -0
  195. package/src/consolidation-engine.ts +256 -0
  196. package/src/context-composer-pins.ts +183 -0
  197. package/src/context-composer-queries.ts +121 -0
  198. package/src/context-composer-scope.ts +56 -0
  199. package/src/context-composer-stable-selection.ts +200 -0
  200. package/src/context-composer-stable.ts +217 -0
  201. package/src/context-composer-task-fallbacks.ts +36 -0
  202. package/src/context-composer-task-ranking.ts +197 -0
  203. package/src/context-composer-task-results.ts +137 -0
  204. package/src/context-composer-task-selection.ts +342 -0
  205. package/src/context-composer-text.ts +57 -0
  206. package/src/context-composer.ts +300 -0
  207. package/src/context-renderer.ts +179 -0
  208. package/src/decay-engine.ts +189 -0
  209. package/src/doctor.ts +543 -0
  210. package/src/embedder.ts +557 -0
  211. package/src/entity-resolver.ts +131 -0
  212. package/src/eval.ts +632 -0
  213. package/src/event-segmenter.ts +216 -0
  214. package/src/ingest.ts +1822 -0
  215. package/src/kg-extractor.ts +217 -0
  216. package/src/kg-store.ts +353 -0
  217. package/src/legacy-scope-review.ts +53 -0
  218. package/src/llm-client.ts +629 -0
  219. package/src/llm-consolidation.ts +168 -0
  220. package/src/mcp-server.ts +1337 -0
  221. package/src/memory-assets.ts +475 -0
  222. package/src/memory-boundaries.ts +354 -0
  223. package/src/memory-health-rebalance.ts +220 -0
  224. package/src/memory-output.ts +474 -0
  225. package/src/memory-schema.ts +184 -0
  226. package/src/multi-vector.ts +234 -0
  227. package/src/noise-filter.ts +155 -0
  228. package/src/ppr-traversal.ts +279 -0
  229. package/src/preference-matcher.ts +174 -0
  230. package/src/preference-slots.ts +259 -0
  231. package/src/prospective-memory.ts +198 -0
  232. package/src/query-entity-detector.ts +207 -0
  233. package/src/query-expander.ts +98 -0
  234. package/src/result-synthesizer.ts +114 -0
  235. package/src/retrieval-profiles.ts +101 -0
  236. package/src/retrieval-trace.ts +94 -0
  237. package/src/retriever.ts +1338 -0
  238. package/src/retroactive-boost.ts +154 -0
  239. package/src/rif.ts +73 -0
  240. package/src/runtime-config.ts +172 -0
  241. package/src/schema-utils.ts +56 -0
  242. package/src/scope-inventory.ts +310 -0
  243. package/src/scope-policy.ts +142 -0
  244. package/src/session-engine.ts +168 -0
  245. package/src/session-output.ts +96 -0
  246. package/src/session-schema.ts +60 -0
  247. package/src/session-store.ts +293 -0
  248. package/src/stderr-log.ts +16 -0
  249. package/src/store.ts +903 -0
  250. package/src/temporal-parser.ts +318 -0
  251. package/src/term-registry.ts +791 -0
  252. package/src/tool-output-compressor.ts +115 -0
  253. package/src/tracker.ts +57 -0
  254. package/src/ui-server.ts +430 -0
  255. package/src/version-manager.ts +172 -0
  256. package/src/workflow-observation-engine.ts +316 -0
  257. package/src/workflow-observation-managed.ts +83 -0
  258. package/src/workflow-observation-output.ts +86 -0
  259. package/src/workflow-observation-schema.ts +87 -0
  260. package/src/workflow-observation-scope-review.ts +409 -0
  261. package/src/workflow-observation-store.ts +69 -0
@@ -0,0 +1,316 @@
1
+ /**
2
+ * Access Tracker — "用进废退" reinforcement for memory decay.
3
+ *
4
+ * Tracks how often memories are recalled and extends their effective
5
+ * time-decay half-life accordingly. Frequently accessed memories
6
+ * decay slower; rarely accessed ones decay at the base rate.
7
+ *
8
+ * v1.1: Added tier promotion/demotion on flush (borrowed from
9
+ * memory-lancedb-pro v1.1.0 tier-manager).
10
+ */
11
+
12
+ import type { MemoryStore } from "./store.js";
13
+ import { evaluateTierChange, resolveTier, type MemoryTier } from "./decay-engine.js";
14
+ import { logInfo, logWarn } from "./stderr-log.js";
15
+
16
+ // ============================================================================
17
+ // Configuration
18
+ // ============================================================================
19
+
20
+ export interface AccessTrackerConfig {
21
+ /** Debounce interval for flushing access counts to store (ms). Default: 5000 */
22
+ flushIntervalMs: number;
23
+ /** Reinforcement factor: how much access extends half-life. Default: 0.5 */
24
+ reinforcementFactor: number;
25
+ /** Maximum multiplier for half-life extension. Default: 3.0 */
26
+ maxMultiplier: number;
27
+ /** Half-life for access count freshness decay (days). Default: 30 */
28
+ accessFreshnessHalfLifeDays: number;
29
+ /** Novelty threshold: only reinforce when query-result distance > this (default: 0.35).
30
+ * Higher = stricter, fewer reinforcements. 0 = disabled (always reinforce). */
31
+ noveltyThreshold: number;
32
+ /** Cooldown period per entry ID in ms. Same entry won't be reinforced twice within this window (default: 300000 = 5 min). */
33
+ cooldownMs: number;
34
+ }
35
+
36
+ export const DEFAULT_ACCESS_TRACKER_CONFIG: AccessTrackerConfig = {
37
+ flushIntervalMs: 5000,
38
+ reinforcementFactor: 0.5,
39
+ maxMultiplier: 3.0,
40
+ accessFreshnessHalfLifeDays: 30,
41
+ noveltyThreshold: 0.35,
42
+ cooldownMs: 300_000, // 5 minutes
43
+ };
44
+
45
+ // ============================================================================
46
+ // Core
47
+ // ============================================================================
48
+
49
+ export class AccessTracker {
50
+ private pending = new Map<string, number>();
51
+ private flushTimer: ReturnType<typeof setTimeout> | null = null;
52
+ private flushPromise: Promise<void> | null = null;
53
+ /** Cooldown map: entryId → last reinforced timestamp */
54
+ private cooldownMap = new Map<string, number>();
55
+
56
+ constructor(
57
+ private store: MemoryStore,
58
+ private config: AccessTrackerConfig = DEFAULT_ACCESS_TRACKER_CONFIG,
59
+ ) {}
60
+
61
+ /**
62
+ * Determine whether a retrieval result should trigger access reinforcement.
63
+ * Gated by two conditions:
64
+ * 1. Novelty: the result's similarity score must indicate sufficient distance
65
+ * (score ≤ 1 - noveltyThreshold) — i.e. not a near-exact repeat of the query.
66
+ * Lower similarity = higher novelty = worth reinforcing.
67
+ * 2. Cooldown: the same entry ID must not have been reinforced within cooldownMs.
68
+ *
69
+ * @param entryId The memory entry ID
70
+ * @param similarityScore Cosine similarity between query and result (0-1)
71
+ * @returns true if this entry should be reinforced
72
+ */
73
+ shouldReinforce(entryId: string, similarityScore: number): boolean {
74
+ const { noveltyThreshold, cooldownMs } = this.config;
75
+
76
+ // Gate 1: Novelty — only reinforce if the retrieval is "surprising" enough.
77
+ // A very high similarity means the user is asking nearly the same thing as
78
+ // what's stored, so there's no new learning signal.
79
+ if (noveltyThreshold > 0) {
80
+ const novelty = 1 - similarityScore;
81
+ if (novelty < noveltyThreshold) return false;
82
+ }
83
+
84
+ // Gate 2: Cooldown — prevent same entry from being reinforced too frequently
85
+ if (cooldownMs > 0) {
86
+ const lastReinforced = this.cooldownMap.get(entryId);
87
+ if (lastReinforced && Date.now() - lastReinforced < cooldownMs) return false;
88
+ }
89
+
90
+ return true;
91
+ }
92
+
93
+ /**
94
+ * Record that these memory IDs were returned in a search result.
95
+ * Accumulates deltas in memory; flushed to store after debounce.
96
+ *
97
+ * @param ids Memory entry IDs to record access for
98
+ * @param scores Optional parallel array of similarity scores for novelty gating.
99
+ * When provided, only entries passing shouldReinforce() are recorded.
100
+ */
101
+ recordAccess(ids: string[], scores?: number[]): void {
102
+ if (ids.length === 0) return;
103
+
104
+ const now = Date.now();
105
+
106
+ for (let i = 0; i < ids.length; i++) {
107
+ const id = ids[i];
108
+ const score = scores?.[i];
109
+
110
+ // If scores provided, apply novelty + cooldown gating
111
+ if (score !== undefined && !this.shouldReinforce(id, score)) {
112
+ continue;
113
+ }
114
+
115
+ this.pending.set(id, (this.pending.get(id) || 0) + 1);
116
+ this.cooldownMap.set(id, now);
117
+ }
118
+
119
+ // Nothing passed the gate → skip flush scheduling
120
+ if (this.pending.size === 0) return;
121
+
122
+ // Debounce: schedule flush if not already pending
123
+ if (!this.flushTimer) {
124
+ this.flushTimer = setTimeout(() => {
125
+ this.flushTimer = null;
126
+ this.flush().catch(err => {
127
+ logWarn("AccessTracker flush failed:", err);
128
+ });
129
+ }, this.config.flushIntervalMs);
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Write accumulated access deltas to store metadata.
135
+ * Also evaluates tier promotion/demotion based on access patterns.
136
+ * Concurrent flush protection: prevents data corruption from overlapping flushes.
137
+ */
138
+ async flush(): Promise<void> {
139
+ if (this.flushPromise) {
140
+ await this.flushPromise;
141
+ if (this.pending.size > 0) return this.flush();
142
+ return;
143
+ }
144
+ if (this.pending.size === 0) return;
145
+
146
+ this.flushPromise = this.doFlush();
147
+ try {
148
+ await this.flushPromise;
149
+ } finally {
150
+ this.flushPromise = null;
151
+ }
152
+ }
153
+
154
+ private async doFlush(): Promise<void> {
155
+ // Snapshot and clear pending
156
+ const batch = new Map(this.pending);
157
+ this.pending.clear();
158
+
159
+ const now = Date.now();
160
+ let promotions = 0;
161
+ let demotions = 0;
162
+
163
+ for (const [id, delta] of batch) {
164
+ try {
165
+ const entry = await this.store.getById(id);
166
+ if (!entry) continue;
167
+
168
+ const meta = safeParseMetadata(entry.metadata);
169
+ const prevCount = typeof meta.accessCount === "number" ? meta.accessCount : 0;
170
+ const newCount = prevCount + delta;
171
+ const importance = entry.importance ?? 0.6;
172
+ const lastAccessedAt = now;
173
+
174
+ // Evaluate tier change
175
+ const currentTier = resolveTier(entry.metadata);
176
+ const newTier = evaluateTierChange(
177
+ currentTier,
178
+ newCount,
179
+ importance,
180
+ lastAccessedAt,
181
+ );
182
+
183
+ const tierChanged = newTier !== currentTier;
184
+ if (tierChanged) {
185
+ if (TIER_ORDER[newTier] > TIER_ORDER[currentTier]) {
186
+ promotions++;
187
+ logInfo(`[INFO] Tier promotion: ${id.slice(0, 8)} ${currentTier} → ${newTier} (access=${newCount})`);
188
+ } else {
189
+ demotions++;
190
+ logInfo(`[INFO] Tier demotion: ${id.slice(0, 8)} ${currentTier} → ${newTier}`);
191
+ }
192
+ }
193
+
194
+ const updatedMeta = {
195
+ ...meta,
196
+ accessCount: newCount,
197
+ lastAccessedAt,
198
+ ...(tierChanged ? { tier: newTier } : {}),
199
+ };
200
+
201
+ await this.store.update(id, { metadata: JSON.stringify(updatedMeta) });
202
+ } catch {
203
+ // Re-queue failed entries for next flush
204
+ this.pending.set(id, (this.pending.get(id) || 0) + delta);
205
+ }
206
+ }
207
+
208
+ if (promotions + demotions > 0) {
209
+ logInfo(`[INFO] Tier changes: ${promotions} promotions, ${demotions} demotions`);
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Compute effective half-life for a memory entry based on access history.
215
+ *
216
+ * Formula:
217
+ * freshness = exp(-accessAgeDays / accessFreshnessHalfLife)
218
+ * effectiveCount = accessCount * freshness
219
+ * extension = baseHalfLife * reinforcementFactor * log1p(effectiveCount)
220
+ * effectiveHalfLife = min(baseHalfLife + extension, baseHalfLife * maxMultiplier)
221
+ */
222
+ computeEffectiveHalfLife(baseHalfLife: number, metadata?: string): number {
223
+ if (baseHalfLife <= 0) return baseHalfLife;
224
+
225
+ const meta = safeParseMetadata(metadata);
226
+ const accessCount = typeof meta.accessCount === "number" ? meta.accessCount : 0;
227
+ const lastAccessedAt = typeof meta.lastAccessedAt === "number" ? meta.lastAccessedAt : 0;
228
+
229
+ if (accessCount <= 0 || lastAccessedAt <= 0) return baseHalfLife;
230
+
231
+ const accessAgeDays = (Date.now() - lastAccessedAt) / 86_400_000;
232
+ const freshness = Math.exp(-accessAgeDays / this.config.accessFreshnessHalfLifeDays);
233
+ const effectiveCount = accessCount * freshness;
234
+
235
+ const extension = baseHalfLife * this.config.reinforcementFactor * Math.log1p(effectiveCount);
236
+ const maxHalfLife = baseHalfLife * this.config.maxMultiplier;
237
+
238
+ return Math.min(baseHalfLife + extension, maxHalfLife);
239
+ }
240
+
241
+ /**
242
+ * Clean up timers and cooldown state.
243
+ */
244
+ destroy(): void {
245
+ if (this.flushTimer) {
246
+ clearTimeout(this.flushTimer);
247
+ this.flushTimer = null;
248
+ }
249
+ this.cooldownMap.clear();
250
+ }
251
+
252
+ get pendingCount(): number {
253
+ return this.pending.size;
254
+ }
255
+ }
256
+
257
+ // ============================================================================
258
+ // Hotness Score
259
+ // ============================================================================
260
+
261
+ /**
262
+ * Compute a hotness score (0-1) for a memory entry.
263
+ *
264
+ * Combines access frequency (sigmoid of log1p) with recency of last access
265
+ * (exponential decay). Inspired by OpenViking memory_lifecycle.py.
266
+ *
267
+ * @param accessCount Number of times this memory was recalled
268
+ * @param lastAccessMs Timestamp (ms) of last access
269
+ * @param decayRate Decay rate (default 0.1 ≈ 7-day half-life)
270
+ * @returns 0-1 score. High = frequently + recently accessed.
271
+ */
272
+ export function computeHotnessScore(
273
+ accessCount: number,
274
+ lastAccessMs: number,
275
+ decayRate = 0.1,
276
+ ): number {
277
+ if (accessCount <= 0) return 0;
278
+
279
+ // Frequency component: sigmoid of log1p(count) → 0.5..1.0
280
+ const freq = 1 / (1 + Math.exp(-Math.log1p(accessCount)));
281
+
282
+ // Recency component: exponential decay from last access
283
+ const ageDays = Math.max(0, (Date.now() - lastAccessMs) / 86_400_000);
284
+ const recency = Math.exp(-decayRate * ageDays);
285
+
286
+ return freq * recency;
287
+ }
288
+
289
+ /**
290
+ * Parse access metadata from a raw metadata string.
291
+ */
292
+ export function parseAccessMetadata(raw?: string): { accessCount: number; lastAccessedAt: number } {
293
+ const meta = safeParseMetadata(raw);
294
+ return {
295
+ accessCount: typeof meta.accessCount === "number" ? meta.accessCount : 0,
296
+ lastAccessedAt: typeof meta.lastAccessedAt === "number" ? meta.lastAccessedAt : 0,
297
+ };
298
+ }
299
+
300
+ // ============================================================================
301
+ // Helpers
302
+ // ============================================================================
303
+
304
+ const TIER_ORDER: Record<MemoryTier, number> = {
305
+ peripheral: 0,
306
+ working: 1,
307
+ core: 2,
308
+ };
309
+
310
+ function safeParseMetadata(raw?: string): Record<string, unknown> {
311
+ try {
312
+ return JSON.parse(raw || "{}") as Record<string, unknown>;
313
+ } catch {
314
+ return {};
315
+ }
316
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Adaptive Retrieval — skip trivial queries to save embedding API calls.
3
+ * Synced from memory-lancedb-pro upstream with CJK-aware length + force-retrieve.
4
+ */
5
+
6
+ // Force-retrieve: queries with memory-related intent should ALWAYS trigger retrieval
7
+ // even if they are short (e.g. "你记得吗" = 4 chars, would otherwise be skipped)
8
+ const FORCE_RETRIEVE_PATTERNS = [
9
+ /\b(remember|recall|forgot|memory|memories)\b/i,
10
+ /\b(last time|before|previously|earlier|yesterday|ago)\b/i,
11
+ /\b(my (name|email|phone|address|birthday|preference))\b/i,
12
+ /\b(what did (i|we)|did i (tell|say|mention))\b/i,
13
+ /(你记得|记不记得|之前|上次|以前|还记得|提到过|说过)/,
14
+ /(我叫|我的名字|我是谁)/,
15
+ ];
16
+
17
+ const SKIP_PATTERNS = [
18
+ // Greetings / acks
19
+ /^(hi|hello|hey|good\s*(morning|afternoon|evening|night)|greetings|yo|sup|howdy)\b/i,
20
+ // System / bot commands
21
+ /^\//,
22
+ /^(run|build|test|ls|cd|git|npm|pip|docker|curl|cat|grep|find|make|sudo)\b/i,
23
+ // Short acks (end-anchored to avoid matching real content)
24
+ /^(yes|no|yep|nope|ok|okay|sure|fine|thanks|thank you|thx|ty|got it|understood|cool|nice|great|good|perfect|awesome)\s*[.!]?$/i,
25
+ // Continuations
26
+ /^(go ahead|continue|proceed|do it|start|begin|next|开始|继续|好的|可以|行)\s*[.!]?$/i,
27
+ // Session boilerplate
28
+ /^(fresh session|new session|\/new|\/compact|\/restart)/i,
29
+ /HEARTBEAT/i,
30
+ /^\[System/i,
31
+ // Single emoji or punctuation
32
+ /^[\p{Emoji}\s.,!?。!?,、]+$/u,
33
+ // Single-word pings
34
+ /^(ping|pong|test|debug)\s*[.!?]?$/i,
35
+ ];
36
+
37
+ /**
38
+ * Returns true if the query is too trivial to warrant an embedding API call.
39
+ * CJK-aware: uses lower length threshold for Chinese/Japanese/Korean text.
40
+ */
41
+ export function shouldSkipRetrieval(query: string, minLength?: number): boolean {
42
+ const trimmed = query.trim();
43
+
44
+ // Force retrieve if query has memory-related intent (BEFORE length check)
45
+ if (FORCE_RETRIEVE_PATTERNS.some(p => p.test(trimmed))) return false;
46
+
47
+ // Very short = skip
48
+ if (trimmed.length < 4) return true;
49
+
50
+ // Pattern-based skip
51
+ if (SKIP_PATTERNS.some(p => p.test(trimmed))) return true;
52
+
53
+ // CJK-aware minimum length: Chinese chars carry more semantic density
54
+ const hasCJK = /[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff]/.test(trimmed);
55
+ const effectiveMinLength = minLength ?? (hasCJK ? 6 : 15);
56
+
57
+ // Short non-question messages are skipped; questions (? ?) are always worth checking
58
+ if (trimmed.length < effectiveMinLength &&
59
+ !trimmed.includes('?') && !trimmed.includes('?')) {
60
+ return true;
61
+ }
62
+
63
+ return false;
64
+ }