@mnemoai/core 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/dist/cli.d.ts +2 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +7 -0
  4. package/dist/cli.js.map +7 -0
  5. package/dist/index.d.ts +136 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/{index.ts → dist/index.js} +537 -1333
  8. package/dist/index.js.map +7 -0
  9. package/dist/src/access-tracker.d.ts +97 -0
  10. package/dist/src/access-tracker.d.ts.map +1 -0
  11. package/dist/src/access-tracker.js +184 -0
  12. package/dist/src/access-tracker.js.map +7 -0
  13. package/dist/src/adapters/chroma.d.ts +31 -0
  14. package/dist/src/adapters/chroma.d.ts.map +1 -0
  15. package/{src/adapters/chroma.ts → dist/src/adapters/chroma.js} +45 -107
  16. package/dist/src/adapters/chroma.js.map +7 -0
  17. package/dist/src/adapters/lancedb.d.ts +29 -0
  18. package/dist/src/adapters/lancedb.d.ts.map +1 -0
  19. package/{src/adapters/lancedb.ts → dist/src/adapters/lancedb.js} +41 -109
  20. package/dist/src/adapters/lancedb.js.map +7 -0
  21. package/dist/src/adapters/pgvector.d.ts +33 -0
  22. package/dist/src/adapters/pgvector.d.ts.map +1 -0
  23. package/{src/adapters/pgvector.ts → dist/src/adapters/pgvector.js} +42 -104
  24. package/dist/src/adapters/pgvector.js.map +7 -0
  25. package/dist/src/adapters/qdrant.d.ts +34 -0
  26. package/dist/src/adapters/qdrant.d.ts.map +1 -0
  27. package/dist/src/adapters/qdrant.js +132 -0
  28. package/dist/src/adapters/qdrant.js.map +7 -0
  29. package/dist/src/adaptive-retrieval.d.ts +14 -0
  30. package/dist/src/adaptive-retrieval.d.ts.map +1 -0
  31. package/dist/src/adaptive-retrieval.js +52 -0
  32. package/dist/src/adaptive-retrieval.js.map +7 -0
  33. package/dist/src/audit-log.d.ts +56 -0
  34. package/dist/src/audit-log.d.ts.map +1 -0
  35. package/dist/src/audit-log.js +139 -0
  36. package/dist/src/audit-log.js.map +7 -0
  37. package/dist/src/chunker.d.ts +45 -0
  38. package/dist/src/chunker.d.ts.map +1 -0
  39. package/dist/src/chunker.js +157 -0
  40. package/dist/src/chunker.js.map +7 -0
  41. package/dist/src/config.d.ts +70 -0
  42. package/dist/src/config.d.ts.map +1 -0
  43. package/dist/src/config.js +142 -0
  44. package/dist/src/config.js.map +7 -0
  45. package/dist/src/decay-engine.d.ts +73 -0
  46. package/dist/src/decay-engine.d.ts.map +1 -0
  47. package/dist/src/decay-engine.js +119 -0
  48. package/dist/src/decay-engine.js.map +7 -0
  49. package/dist/src/embedder.d.ts +94 -0
  50. package/dist/src/embedder.d.ts.map +1 -0
  51. package/{src/embedder.ts → dist/src/embedder.js} +119 -317
  52. package/dist/src/embedder.js.map +7 -0
  53. package/dist/src/extraction-prompts.d.ts +12 -0
  54. package/dist/src/extraction-prompts.d.ts.map +1 -0
  55. package/dist/src/extraction-prompts.js +311 -0
  56. package/dist/src/extraction-prompts.js.map +7 -0
  57. package/dist/src/license.d.ts +29 -0
  58. package/dist/src/license.d.ts.map +1 -0
  59. package/{src/license.ts → dist/src/license.js} +42 -113
  60. package/dist/src/license.js.map +7 -0
  61. package/dist/src/llm-client.d.ts +23 -0
  62. package/dist/src/llm-client.d.ts.map +1 -0
  63. package/{src/llm-client.ts → dist/src/llm-client.js} +22 -55
  64. package/dist/src/llm-client.js.map +7 -0
  65. package/dist/src/logger.d.ts +33 -0
  66. package/dist/src/logger.d.ts.map +1 -0
  67. package/dist/src/logger.js +35 -0
  68. package/dist/src/logger.js.map +7 -0
  69. package/dist/src/mcp-server.d.ts +16 -0
  70. package/dist/src/mcp-server.d.ts.map +1 -0
  71. package/{src/mcp-server.ts → dist/src/mcp-server.js} +81 -181
  72. package/dist/src/mcp-server.js.map +7 -0
  73. package/dist/src/memory-categories.d.ts +40 -0
  74. package/dist/src/memory-categories.d.ts.map +1 -0
  75. package/dist/src/memory-categories.js +33 -0
  76. package/dist/src/memory-categories.js.map +7 -0
  77. package/dist/src/memory-upgrader.d.ts +71 -0
  78. package/dist/src/memory-upgrader.d.ts.map +1 -0
  79. package/dist/src/memory-upgrader.js +238 -0
  80. package/dist/src/memory-upgrader.js.map +7 -0
  81. package/dist/src/migrate.d.ts +47 -0
  82. package/dist/src/migrate.d.ts.map +1 -0
  83. package/{src/migrate.ts → dist/src/migrate.js} +57 -165
  84. package/dist/src/migrate.js.map +7 -0
  85. package/dist/src/mnemo.d.ts +67 -0
  86. package/dist/src/mnemo.d.ts.map +1 -0
  87. package/dist/src/mnemo.js +66 -0
  88. package/dist/src/mnemo.js.map +7 -0
  89. package/dist/src/noise-filter.d.ts +23 -0
  90. package/dist/src/noise-filter.d.ts.map +1 -0
  91. package/dist/src/noise-filter.js +62 -0
  92. package/dist/src/noise-filter.js.map +7 -0
  93. package/dist/src/noise-prototypes.d.ts +40 -0
  94. package/dist/src/noise-prototypes.d.ts.map +1 -0
  95. package/dist/src/noise-prototypes.js +116 -0
  96. package/dist/src/noise-prototypes.js.map +7 -0
  97. package/dist/src/observability.d.ts +16 -0
  98. package/dist/src/observability.d.ts.map +1 -0
  99. package/dist/src/observability.js +53 -0
  100. package/dist/src/observability.js.map +7 -0
  101. package/dist/src/query-tracker.d.ts +27 -0
  102. package/dist/src/query-tracker.d.ts.map +1 -0
  103. package/dist/src/query-tracker.js +32 -0
  104. package/dist/src/query-tracker.js.map +7 -0
  105. package/dist/src/reflection-event-store.d.ts +44 -0
  106. package/dist/src/reflection-event-store.d.ts.map +1 -0
  107. package/dist/src/reflection-event-store.js +50 -0
  108. package/dist/src/reflection-event-store.js.map +7 -0
  109. package/dist/src/reflection-item-store.d.ts +58 -0
  110. package/dist/src/reflection-item-store.d.ts.map +1 -0
  111. package/dist/src/reflection-item-store.js +69 -0
  112. package/dist/src/reflection-item-store.js.map +7 -0
  113. package/dist/src/reflection-mapped-metadata.d.ts +47 -0
  114. package/dist/src/reflection-mapped-metadata.d.ts.map +1 -0
  115. package/dist/src/reflection-mapped-metadata.js +40 -0
  116. package/dist/src/reflection-mapped-metadata.js.map +7 -0
  117. package/dist/src/reflection-metadata.d.ts +11 -0
  118. package/dist/src/reflection-metadata.d.ts.map +1 -0
  119. package/dist/src/reflection-metadata.js +24 -0
  120. package/dist/src/reflection-metadata.js.map +7 -0
  121. package/dist/src/reflection-ranking.d.ts +13 -0
  122. package/dist/src/reflection-ranking.d.ts.map +1 -0
  123. package/{src/reflection-ranking.ts → dist/src/reflection-ranking.js} +12 -21
  124. package/dist/src/reflection-ranking.js.map +7 -0
  125. package/dist/src/reflection-retry.d.ts +30 -0
  126. package/dist/src/reflection-retry.d.ts.map +1 -0
  127. package/{src/reflection-retry.ts → dist/src/reflection-retry.js} +24 -64
  128. package/dist/src/reflection-retry.js.map +7 -0
  129. package/dist/src/reflection-slices.d.ts +42 -0
  130. package/dist/src/reflection-slices.d.ts.map +1 -0
  131. package/{src/reflection-slices.ts → dist/src/reflection-slices.js} +60 -136
  132. package/dist/src/reflection-slices.js.map +7 -0
  133. package/dist/src/reflection-store.d.ts +85 -0
  134. package/dist/src/reflection-store.d.ts.map +1 -0
  135. package/dist/src/reflection-store.js +407 -0
  136. package/dist/src/reflection-store.js.map +7 -0
  137. package/dist/src/resonance-state.d.ts +19 -0
  138. package/dist/src/resonance-state.d.ts.map +1 -0
  139. package/{src/resonance-state.ts → dist/src/resonance-state.js} +13 -42
  140. package/dist/src/resonance-state.js.map +7 -0
  141. package/dist/src/retriever.d.ts +228 -0
  142. package/dist/src/retriever.d.ts.map +1 -0
  143. package/dist/src/retriever.js +1006 -0
  144. package/dist/src/retriever.js.map +7 -0
  145. package/dist/src/scopes.d.ts +58 -0
  146. package/dist/src/scopes.d.ts.map +1 -0
  147. package/dist/src/scopes.js +252 -0
  148. package/dist/src/scopes.js.map +7 -0
  149. package/dist/src/self-improvement-files.d.ts +20 -0
  150. package/dist/src/self-improvement-files.d.ts.map +1 -0
  151. package/{src/self-improvement-files.ts → dist/src/self-improvement-files.js} +24 -49
  152. package/dist/src/self-improvement-files.js.map +7 -0
  153. package/dist/src/semantic-gate.d.ts +24 -0
  154. package/dist/src/semantic-gate.d.ts.map +1 -0
  155. package/dist/src/semantic-gate.js +86 -0
  156. package/dist/src/semantic-gate.js.map +7 -0
  157. package/dist/src/session-recovery.d.ts +9 -0
  158. package/dist/src/session-recovery.d.ts.map +1 -0
  159. package/{src/session-recovery.ts → dist/src/session-recovery.js} +40 -57
  160. package/dist/src/session-recovery.js.map +7 -0
  161. package/dist/src/smart-extractor.d.ts +107 -0
  162. package/dist/src/smart-extractor.d.ts.map +1 -0
  163. package/{src/smart-extractor.ts → dist/src/smart-extractor.js} +130 -383
  164. package/dist/src/smart-extractor.js.map +7 -0
  165. package/dist/src/smart-metadata.d.ts +103 -0
  166. package/dist/src/smart-metadata.d.ts.map +1 -0
  167. package/dist/src/smart-metadata.js +361 -0
  168. package/dist/src/smart-metadata.js.map +7 -0
  169. package/dist/src/storage-adapter.d.ts +102 -0
  170. package/dist/src/storage-adapter.d.ts.map +1 -0
  171. package/dist/src/storage-adapter.js +22 -0
  172. package/dist/src/storage-adapter.js.map +7 -0
  173. package/dist/src/store.d.ts +108 -0
  174. package/dist/src/store.d.ts.map +1 -0
  175. package/dist/src/store.js +939 -0
  176. package/dist/src/store.js.map +7 -0
  177. package/dist/src/tier-manager.d.ts +57 -0
  178. package/dist/src/tier-manager.d.ts.map +1 -0
  179. package/dist/src/tier-manager.js +80 -0
  180. package/dist/src/tier-manager.js.map +7 -0
  181. package/dist/src/tools.d.ts +43 -0
  182. package/dist/src/tools.d.ts.map +1 -0
  183. package/dist/src/tools.js +1075 -0
  184. package/dist/src/tools.js.map +7 -0
  185. package/dist/src/wal-recovery.d.ts +30 -0
  186. package/dist/src/wal-recovery.d.ts.map +1 -0
  187. package/{src/wal-recovery.ts → dist/src/wal-recovery.js} +26 -79
  188. package/dist/src/wal-recovery.js.map +7 -0
  189. package/package.json +21 -2
  190. package/openclaw.plugin.json +0 -815
  191. package/src/access-tracker.ts +0 -341
  192. package/src/adapters/README.md +0 -78
  193. package/src/adapters/qdrant.ts +0 -191
  194. package/src/adaptive-retrieval.ts +0 -90
  195. package/src/audit-log.ts +0 -238
  196. package/src/chunker.ts +0 -254
  197. package/src/config.ts +0 -271
  198. package/src/decay-engine.ts +0 -238
  199. package/src/extraction-prompts.ts +0 -339
  200. package/src/memory-categories.ts +0 -71
  201. package/src/memory-upgrader.ts +0 -388
  202. package/src/mnemo.ts +0 -142
  203. package/src/noise-filter.ts +0 -97
  204. package/src/noise-prototypes.ts +0 -164
  205. package/src/observability.ts +0 -81
  206. package/src/query-tracker.ts +0 -57
  207. package/src/reflection-event-store.ts +0 -98
  208. package/src/reflection-item-store.ts +0 -112
  209. package/src/reflection-mapped-metadata.ts +0 -84
  210. package/src/reflection-metadata.ts +0 -23
  211. package/src/reflection-store.ts +0 -602
  212. package/src/retriever.ts +0 -1510
  213. package/src/scopes.ts +0 -375
  214. package/src/semantic-gate.ts +0 -121
  215. package/src/smart-metadata.ts +0 -561
  216. package/src/storage-adapter.ts +0 -153
  217. package/src/store.ts +0 -1330
  218. package/src/tier-manager.ts +0 -189
  219. package/src/tools.ts +0 -1292
  220. package/test/core.test.mjs +0 -301
@@ -1,341 +0,0 @@
1
- // SPDX-License-Identifier: LicenseRef-Mnemo-Pro
2
- /**
3
- * Access Tracker
4
- *
5
- * Tracks memory access patterns to support reinforcement-based decay.
6
- * Frequently accessed memories decay more slowly (longer effective half-life).
7
- *
8
- * Key exports:
9
- * - parseAccessMetadata — extract accessCount/lastAccessedAt from metadata JSON
10
- * - buildUpdatedMetadata — merge access fields into existing metadata JSON
11
- * - computeEffectiveHalfLife — compute reinforced half-life from access history
12
- * - AccessTracker — debounced write-back tracker for batch metadata updates
13
- */
14
-
15
- import type { MemoryStore } from "./store.js";
16
-
17
- // ============================================================================
18
- // Types
19
- // ============================================================================
20
-
21
- export interface AccessMetadata {
22
- readonly accessCount: number;
23
- readonly lastAccessedAt: number;
24
- }
25
-
26
- export interface AccessTrackerOptions {
27
- readonly store: MemoryStore;
28
- readonly logger: {
29
- warn: (...args: unknown[]) => void;
30
- info?: (...args: unknown[]) => void;
31
- };
32
- readonly debounceMs?: number;
33
- }
34
-
35
- // ============================================================================
36
- // Constants
37
- // ============================================================================
38
-
39
- const MIN_ACCESS_COUNT = 0;
40
- const MAX_ACCESS_COUNT = 10_000;
41
-
42
- /** Access count itself decays with a 30-day half-life */
43
- const ACCESS_DECAY_HALF_LIFE_DAYS = 30;
44
-
45
- // ============================================================================
46
- // Utility
47
- // ============================================================================
48
-
49
- function clampAccessCount(value: number): number {
50
- if (!Number.isFinite(value)) return MIN_ACCESS_COUNT;
51
- return Math.min(
52
- MAX_ACCESS_COUNT,
53
- Math.max(MIN_ACCESS_COUNT, Math.floor(value)),
54
- );
55
- }
56
-
57
- // ============================================================================
58
- // Metadata Parsing
59
- // ============================================================================
60
-
61
- /**
62
- * Parse access-related fields from a metadata JSON string.
63
- *
64
- * Handles: undefined, empty string, malformed JSON, negative numbers,
65
- * numbers exceeding 10000. Always returns a valid AccessMetadata.
66
- */
67
- export function parseAccessMetadata(
68
- metadata: string | undefined,
69
- ): AccessMetadata {
70
- if (metadata === undefined || metadata === "") {
71
- return { accessCount: 0, lastAccessedAt: 0 };
72
- }
73
-
74
- let parsed: unknown;
75
- try {
76
- parsed = JSON.parse(metadata);
77
- } catch {
78
- return { accessCount: 0, lastAccessedAt: 0 };
79
- }
80
-
81
- if (typeof parsed !== "object" || parsed === null) {
82
- return { accessCount: 0, lastAccessedAt: 0 };
83
- }
84
-
85
- const obj = parsed as Record<string, unknown>;
86
-
87
- // Support both camelCase and snake_case keys (beta smart-memory uses snake_case).
88
- const rawCountAny = obj.accessCount ?? obj.access_count;
89
- const rawCount =
90
- typeof rawCountAny === "number" ? rawCountAny : Number(rawCountAny ?? 0);
91
-
92
- const rawLastAny = obj.lastAccessedAt ?? obj.last_accessed_at;
93
- const rawLastAccessed =
94
- typeof rawLastAny === "number" ? rawLastAny : Number(rawLastAny ?? 0);
95
-
96
- return {
97
- accessCount: clampAccessCount(rawCount),
98
- lastAccessedAt:
99
- Number.isFinite(rawLastAccessed) && rawLastAccessed >= 0
100
- ? rawLastAccessed
101
- : 0,
102
- };
103
- }
104
-
105
- // ============================================================================
106
- // Metadata Building
107
- // ============================================================================
108
-
109
- /**
110
- * Merge an access-count increment into existing metadata JSON.
111
- *
112
- * Preserves ALL existing fields in the metadata object — only overwrites
113
- * `accessCount` and `lastAccessedAt`. Returns a new JSON string.
114
- */
115
- export function buildUpdatedMetadata(
116
- existingMetadata: string | undefined,
117
- accessDelta: number,
118
- ): string {
119
- let existing: Record<string, unknown> = {};
120
-
121
- if (existingMetadata !== undefined && existingMetadata !== "") {
122
- try {
123
- const parsed = JSON.parse(existingMetadata);
124
- if (typeof parsed === "object" && parsed !== null) {
125
- existing = { ...parsed };
126
- }
127
- } catch {
128
- // malformed JSON — start fresh but preserve nothing
129
- }
130
- }
131
-
132
- const prev = parseAccessMetadata(existingMetadata);
133
- const newCount = clampAccessCount(prev.accessCount + accessDelta);
134
-
135
- const now = Date.now();
136
-
137
- return JSON.stringify({
138
- ...existing,
139
- // Write both camelCase and snake_case for compatibility.
140
- accessCount: newCount,
141
- lastAccessedAt: now,
142
- access_count: newCount,
143
- last_accessed_at: now,
144
- });
145
- }
146
-
147
- // ============================================================================
148
- // Effective Half-Life Computation
149
- // ============================================================================
150
-
151
- /**
152
- * Compute the effective half-life for a memory based on its access history.
153
- *
154
- * The access count itself decays over time (30-day half-life for access
155
- * freshness), so stale accesses contribute less reinforcement. The extension
156
- * uses a logarithmic curve (`Math.log1p`) to provide diminishing returns.
157
- *
158
- * @param baseHalfLife - Base half-life in days (e.g. 30)
159
- * @param accessCount - Raw number of times the memory was accessed
160
- * @param lastAccessedAt - Timestamp (ms) of last access
161
- * @param reinforcementFactor - Scaling factor for reinforcement (0 = disabled)
162
- * @param maxMultiplier - Hard cap: result <= baseHalfLife * maxMultiplier
163
- * @returns Effective half-life in days
164
- */
165
- export function computeEffectiveHalfLife(
166
- baseHalfLife: number,
167
- accessCount: number,
168
- lastAccessedAt: number,
169
- reinforcementFactor: number,
170
- maxMultiplier: number,
171
- ): number {
172
- // Short-circuit: no reinforcement or no accesses
173
- if (reinforcementFactor === 0 || accessCount <= 0) {
174
- return baseHalfLife;
175
- }
176
-
177
- const now = Date.now();
178
- const daysSinceLastAccess = Math.max(
179
- 0,
180
- (now - lastAccessedAt) / (1000 * 60 * 60 * 24),
181
- );
182
-
183
- // Access freshness decays exponentially with 30-day half-life
184
- const accessFreshness = Math.exp(
185
- -daysSinceLastAccess * (Math.LN2 / ACCESS_DECAY_HALF_LIFE_DAYS),
186
- );
187
-
188
- // Effective access count after freshness decay
189
- const effectiveAccessCount = accessCount * accessFreshness;
190
-
191
- // Logarithmic extension for diminishing returns
192
- const extension =
193
- baseHalfLife * reinforcementFactor * Math.log1p(effectiveAccessCount);
194
-
195
- const result = baseHalfLife + extension;
196
-
197
- // Hard cap
198
- const cap = baseHalfLife * maxMultiplier;
199
- return Math.min(result, cap);
200
- }
201
-
202
- // ============================================================================
203
- // AccessTracker Class
204
- // ============================================================================
205
-
206
- /**
207
- * Debounced write-back tracker for memory access events.
208
- *
209
- * `recordAccess()` is synchronous (Map update only, no I/O). Pending deltas
210
- * accumulate until `flush()` is called (or by a future scheduled callback).
211
- * On flush, each pending entry is read via `store.getById()`, its metadata
212
- * is merged with the accumulated access delta, and written back via
213
- * `store.update()`.
214
- */
215
- export class AccessTracker {
216
- private readonly pending: Map<string, number> = new Map();
217
- private debounceTimer: ReturnType<typeof setTimeout> | null = null;
218
- private flushPromise: Promise<void> | null = null;
219
- private readonly debounceMs: number;
220
- private readonly store: MemoryStore;
221
- private readonly logger: {
222
- warn: (...args: unknown[]) => void;
223
- info?: (...args: unknown[]) => void;
224
- };
225
-
226
- constructor(options: AccessTrackerOptions) {
227
- this.store = options.store;
228
- this.logger = options.logger;
229
- this.debounceMs = options.debounceMs ?? 5_000;
230
- }
231
-
232
- /**
233
- * Record one access for each of the given memory IDs.
234
- * Synchronous — only updates the in-memory pending map.
235
- */
236
- recordAccess(ids: readonly string[]): void {
237
- for (const id of ids) {
238
- const current = this.pending.get(id) ?? 0;
239
- this.pending.set(id, current + 1);
240
- }
241
-
242
- // Reset debounce timer
243
- this.resetTimer();
244
- }
245
-
246
- /**
247
- * Return a snapshot of all pending (id -> delta) entries.
248
- */
249
- getPendingUpdates(): Map<string, number> {
250
- return new Map(this.pending);
251
- }
252
-
253
- /**
254
- * Flush pending access deltas to the store.
255
- *
256
- * If a flush is already in progress, awaits the current flush to complete.
257
- * If new pending data accumulated during the in-flight flush, a follow-up
258
- * flush is automatically triggered.
259
- */
260
- async flush(): Promise<void> {
261
- this.clearTimer();
262
-
263
- // If a flush is in progress, wait for it to finish
264
- if (this.flushPromise) {
265
- await this.flushPromise;
266
- // After the in-flight flush completes, check if new data accumulated
267
- if (this.pending.size > 0) {
268
- return this.flush();
269
- }
270
- return;
271
- }
272
-
273
- if (this.pending.size === 0) return;
274
-
275
- this.flushPromise = this.doFlush();
276
- try {
277
- await this.flushPromise;
278
- } finally {
279
- this.flushPromise = null;
280
- }
281
-
282
- // If new data accumulated during flush, schedule a follow-up
283
- if (this.pending.size > 0) {
284
- this.resetTimer();
285
- }
286
- }
287
-
288
- /**
289
- * Tear down the tracker — cancel timers and clear pending state.
290
- */
291
- destroy(): void {
292
- this.clearTimer();
293
- if (this.pending.size > 0) {
294
- this.logger.warn(
295
- `access-tracker: destroying with ${this.pending.size} pending writes`,
296
- );
297
- }
298
- this.pending.clear();
299
- }
300
-
301
- // --------------------------------------------------------------------------
302
- // Internal helpers
303
- // --------------------------------------------------------------------------
304
-
305
- private async doFlush(): Promise<void> {
306
- const batch = new Map(this.pending);
307
- this.pending.clear();
308
-
309
- for (const [id, delta] of batch) {
310
- try {
311
- const current = await this.store.getById(id);
312
- if (!current) continue;
313
-
314
- const updatedMeta = buildUpdatedMetadata(current.metadata, delta);
315
- await this.store.update(id, { metadata: updatedMeta });
316
- } catch (err) {
317
- // Requeue failed delta for retry on next flush
318
- const existing = this.pending.get(id) ?? 0;
319
- this.pending.set(id, existing + delta);
320
- this.logger.warn(
321
- `access-tracker: write-back failed for ${id.slice(0, 8)}:`,
322
- err,
323
- );
324
- }
325
- }
326
- }
327
-
328
- private resetTimer(): void {
329
- this.clearTimer();
330
- this.debounceTimer = setTimeout(() => {
331
- void this.flush();
332
- }, this.debounceMs);
333
- }
334
-
335
- private clearTimer(): void {
336
- if (this.debounceTimer !== null) {
337
- clearTimeout(this.debounceTimer);
338
- this.debounceTimer = null;
339
- }
340
- }
341
- }
@@ -1,78 +0,0 @@
1
- # Storage Adapters
2
-
3
- Mnemo supports pluggable storage backends via the `StorageAdapter` interface.
4
-
5
- ## Available Adapters
6
-
7
- | Adapter | Status | BM25/FTS | Install | Best For |
8
- |---------|--------|----------|---------|----------|
9
- | **LanceDB** | Stable (default) | Built-in | — | Embedded, edge, single-node |
10
- | **Qdrant** | Stable | No (vector only) | `npm i @qdrant/js-client-rest` | High-perf filtering, Rust speed |
11
- | **Chroma** | Stable | Yes (queryTexts) | `npm i chromadb` | Prototyping, Python ecosystem |
12
- | **PGVector** | Stable | Yes (pg tsvector) | `npm i pg pgvector` | Existing Postgres, full SQL |
13
- | Weaviate | Planned | — | — | Hybrid search, GraphQL |
14
- | Milvus | Planned | — | — | Billion-scale, GPU |
15
- | Pinecone | Planned | — | — | Fully managed SaaS |
16
- | SQLite-vec | Planned | — | — | Ultra-lightweight, edge |
17
-
18
- ## Quick Start
19
-
20
- ```typescript
21
- import { createMnemo } from '@mnemo/core';
22
-
23
- // Default — LanceDB (embedded, zero config)
24
- const mnemo = await createMnemo({ storage: 'lancedb' });
25
-
26
- // Qdrant (self-hosted or Qdrant Cloud)
27
- const mnemo = await createMnemo({
28
- storage: 'qdrant',
29
- storageConfig: { url: 'http://localhost:6333' },
30
- });
31
-
32
- // Chroma (embedded or server mode)
33
- const mnemo = await createMnemo({
34
- storage: 'chroma',
35
- storageConfig: { url: 'http://localhost:8000' },
36
- });
37
-
38
- // PGVector (existing PostgreSQL)
39
- const mnemo = await createMnemo({
40
- storage: 'pgvector',
41
- storageConfig: { connectionString: 'postgres://user:pass@localhost:5432/mnemo' },
42
- });
43
- ```
44
-
45
- ## Choosing a Backend
46
-
47
- | Scenario | Recommended |
48
- |----------|-------------|
49
- | Getting started / prototyping | **LanceDB** (zero setup) |
50
- | Already running PostgreSQL | **PGVector** (no extra service) |
51
- | Need fastest vector search | **Qdrant** (Rust, HNSW) |
52
- | Python-heavy stack | **Chroma** (pip install) |
53
- | Need BM25 + vector | **LanceDB** or **PGVector** |
54
- | Air-gapped / edge deployment | **LanceDB** (embedded) |
55
-
56
- ## Creating a Custom Adapter
57
-
58
- Implement the `StorageAdapter` interface and register it:
59
-
60
- ```typescript
61
- import { StorageAdapter, registerAdapter } from '@mnemo/core/storage-adapter';
62
-
63
- class MyAdapter implements StorageAdapter {
64
- readonly name = 'my-backend';
65
- async connect(dbPath: string) { /* ... */ }
66
- async ensureTable(dim: number) { /* ... */ }
67
- async add(records: MemoryRecord[]) { /* ... */ }
68
- async vectorSearch(vector, limit, minScore, scopeFilter) { /* ... */ }
69
- async fullTextSearch(query, limit, scopeFilter) { /* ... */ }
70
- // ... implement all methods from StorageAdapter
71
- }
72
-
73
- registerAdapter('my-backend', () => new MyAdapter());
74
- ```
75
-
76
- ## Interface Reference
77
-
78
- See `storage-adapter.ts` for the full `StorageAdapter` interface with JSDoc.
@@ -1,191 +0,0 @@
1
- // SPDX-License-Identifier: MIT
2
- /**
3
- * Qdrant Storage Adapter for Mnemo
4
- *
5
- * Requirements:
6
- * npm install @qdrant/js-client-rest
7
- *
8
- * Config:
9
- * storage: "qdrant"
10
- * storageConfig: { url: "http://localhost:6333", apiKey?: "..." }
11
- */
12
-
13
- import type {
14
- StorageAdapter,
15
- MemoryRecord,
16
- SearchResult,
17
- QueryOptions,
18
- } from "../storage-adapter.js";
19
- import { registerAdapter } from "../storage-adapter.js";
20
-
21
- const COLLECTION = "mnemo_memories";
22
-
23
- export class QdrantAdapter implements StorageAdapter {
24
- readonly name = "qdrant";
25
-
26
- private client: any = null;
27
- private url: string = "http://localhost:6333";
28
- private apiKey?: string;
29
- private vectorDim = 0;
30
-
31
- constructor(config?: Record<string, unknown>) {
32
- if (config?.url) this.url = config.url as string;
33
- if (config?.apiKey) this.apiKey = config.apiKey as string;
34
- }
35
-
36
- async connect(): Promise<void> {
37
- const { QdrantClient } = await import("@qdrant/js-client-rest");
38
- this.client = new QdrantClient({
39
- url: this.url,
40
- ...(this.apiKey ? { apiKey: this.apiKey } : {}),
41
- });
42
- }
43
-
44
- async ensureTable(vectorDimensions: number): Promise<void> {
45
- this.vectorDim = vectorDimensions;
46
- const collections = await this.client.getCollections();
47
- const exists = collections.collections.some((c: any) => c.name === COLLECTION);
48
-
49
- if (!exists) {
50
- await this.client.createCollection(COLLECTION, {
51
- vectors: { size: vectorDimensions, distance: "Cosine" },
52
- });
53
- // Create payload indices for filtering
54
- await this.client.createPayloadIndex(COLLECTION, {
55
- field_name: "scope",
56
- field_schema: "keyword",
57
- });
58
- await this.client.createPayloadIndex(COLLECTION, {
59
- field_name: "category",
60
- field_schema: "keyword",
61
- });
62
- }
63
- }
64
-
65
- async add(records: MemoryRecord[]): Promise<void> {
66
- const points = records.map((r) => ({
67
- id: r.id,
68
- vector: r.vector,
69
- payload: {
70
- text: r.text,
71
- timestamp: r.timestamp,
72
- scope: r.scope,
73
- importance: r.importance,
74
- category: r.category,
75
- metadata: r.metadata,
76
- },
77
- }));
78
- await this.client.upsert(COLLECTION, { points });
79
- }
80
-
81
- async update(id: string, record: MemoryRecord): Promise<void> {
82
- await this.add([record]);
83
- }
84
-
85
- async delete(filter: string): Promise<void> {
86
- // Parse simple "id = 'xxx'" filter
87
- const idMatch = filter.match(/id\s*=\s*'([^']+)'/);
88
- if (idMatch) {
89
- await this.client.delete(COLLECTION, {
90
- points: [idMatch[1]],
91
- });
92
- }
93
- }
94
-
95
- async vectorSearch(
96
- vector: number[],
97
- limit: number,
98
- minScore = 0,
99
- scopeFilter?: string[],
100
- ): Promise<SearchResult[]> {
101
- const filter = scopeFilter?.length
102
- ? { must: [{ key: "scope", match: { any: scopeFilter } }] }
103
- : undefined;
104
-
105
- const results = await this.client.search(COLLECTION, {
106
- vector,
107
- limit,
108
- with_payload: true,
109
- score_threshold: minScore,
110
- ...(filter ? { filter } : {}),
111
- });
112
-
113
- return results.map((r: any) => ({
114
- record: this.toRecord(r.id, r.payload),
115
- score: r.score,
116
- }));
117
- }
118
-
119
- async fullTextSearch(
120
- _query: string,
121
- _limit: number,
122
- _scopeFilter?: string[],
123
- ): Promise<SearchResult[]> {
124
- // Qdrant doesn't have native BM25 — fall back to empty
125
- // Users should pair with a separate FTS engine or use vector search
126
- return [];
127
- }
128
-
129
- async query(options: QueryOptions): Promise<MemoryRecord[]> {
130
- const filter = options.where
131
- ? this.parseFilter(options.where)
132
- : undefined;
133
-
134
- const result = await this.client.scroll(COLLECTION, {
135
- limit: options.limit || 100,
136
- with_payload: true,
137
- with_vectors: true,
138
- ...(filter ? { filter } : {}),
139
- });
140
-
141
- return result.points.map((p: any) => this.toRecord(p.id, p.payload, p.vector));
142
- }
143
-
144
- async count(filter?: string): Promise<number> {
145
- const result = await this.client.count(COLLECTION, {
146
- ...(filter ? { filter: this.parseFilter(filter) } : {}),
147
- exact: true,
148
- });
149
- return result.count;
150
- }
151
-
152
- async ensureFullTextIndex(): Promise<void> {
153
- // Qdrant uses payload indices, not FTS indices
154
- // Text search via Qdrant requires external FTS or payload keyword match
155
- }
156
-
157
- hasFullTextSearch(): boolean {
158
- return false; // Qdrant doesn't have native BM25
159
- }
160
-
161
- async close(): Promise<void> {
162
- this.client = null;
163
- }
164
-
165
- // ── Helpers ──
166
-
167
- private toRecord(id: string, payload: any, vector?: number[]): MemoryRecord {
168
- return {
169
- id,
170
- text: payload.text ?? "",
171
- vector: vector ? Array.from(vector) : [],
172
- timestamp: payload.timestamp ?? 0,
173
- scope: payload.scope ?? "global",
174
- importance: payload.importance ?? 0.5,
175
- category: payload.category ?? "other",
176
- metadata: payload.metadata ?? "{}",
177
- };
178
- }
179
-
180
- private parseFilter(where: string): any {
181
- // Simple parser for common filters
182
- const scopeMatch = where.match(/scope\s+IN\s*\(([^)]+)\)/i);
183
- if (scopeMatch) {
184
- const scopes = scopeMatch[1].split(",").map((s) => s.trim().replace(/'/g, ""));
185
- return { must: [{ key: "scope", match: { any: scopes } }] };
186
- }
187
- return undefined;
188
- }
189
- }
190
-
191
- registerAdapter("qdrant", (config) => new QdrantAdapter(config));
@@ -1,90 +0,0 @@
1
- // SPDX-License-Identifier: MIT
2
- /**
3
- * Adaptive Retrieval
4
- * Determines whether a query needs memory retrieval at all.
5
- * Skips retrieval for greetings, commands, simple instructions, and system messages.
6
- * Saves embedding API calls and reduces noise injection.
7
- */
8
-
9
- // Queries that are clearly NOT memory-retrieval candidates
10
- const SKIP_PATTERNS = [
11
- // Greetings & pleasantries
12
- /^(hi|hello|hey|good\s*(morning|afternoon|evening|night)|greetings|yo|sup|howdy|what'?s up)\b/i,
13
- // System/bot commands
14
- /^\//, // slash commands
15
- /^(run|build|test|ls|cd|git|npm|pip|docker|curl|cat|grep|find|make|sudo)\b/i,
16
- // Simple affirmations/negations
17
- /^(yes|no|yep|nope|ok|okay|sure|fine|thanks|thank you|thx|ty|got it|understood|cool|nice|great|good|perfect|awesome)\s*[.!]?$/i,
18
- // Continuation prompts
19
- /^(go ahead|continue|proceed|do it|start|begin|next)\s*[.!]?$/i,
20
- // Pure emoji
21
- /^[\p{Emoji}\s]+$/u,
22
- // Heartbeat/system (match anywhere, not just at start, to handle prefixed formats)
23
- /HEARTBEAT/i,
24
- /^\[System/i,
25
- // Single-word utility pings
26
- /^(ping|pong|test|debug)\s*[.!?]?$/i,
27
- ];
28
-
29
- // Queries that SHOULD trigger retrieval even if short
30
- const FORCE_RETRIEVE_PATTERNS = [
31
- /\b(remember|recall|forgot|memory|memories)\b/i,
32
- /\b(last time|before|previously|earlier|yesterday|ago)\b/i,
33
- /\b(my (name|email|phone|address|birthday|preference))\b/i,
34
- /\b(what did (i|we)|did i (tell|say|mention))\b/i,
35
- ];
36
-
37
- /**
38
- * Normalize the raw prompt before applying skip/force rules.
39
- */
40
- function normalizeQuery(query: string): string {
41
- let s = query.trim();
42
-
43
- // 1. Strip injected metadata headers.
44
- const metadataPattern = /^(Conversation info|Sender) \(untrusted metadata\):[\s\S]*?\n\s*\n/gim;
45
- s = s.replace(metadataPattern, "");
46
-
47
- // 2. Strip cron wrapper prefix.
48
- s = s.trim().replace(/^\[cron:[^\]]+\]\s*/i, "");
49
-
50
- // 3. Strip timestamp prefix [Mon 2026-03-02 04:21 GMT+8].
51
- s = s.trim().replace(/^\[[A-Za-z]{3}\s\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}\s[^\]]+\]\s*/, "");
52
-
53
- const result = s.trim();
54
- return result;
55
- }
56
-
57
- /**
58
- * Determine if a query should skip memory retrieval.
59
- * Returns true if retrieval should be skipped.
60
- * @param query The raw prompt text
61
- * @param minLength Optional minimum length override (if set, overrides built-in thresholds)
62
- */
63
- export function shouldSkipRetrieval(query: string, minLength?: number): boolean {
64
- const trimmed = normalizeQuery(query);
65
-
66
- // Force retrieve if query has memory-related intent (checked FIRST,
67
- // before length check, so short CJK queries aren't skipped)
68
- if (FORCE_RETRIEVE_PATTERNS.some(p => p.test(trimmed))) return false;
69
-
70
- // Too short to be meaningful
71
- if (trimmed.length < 5) return true;
72
-
73
- // Skip if matches any skip pattern
74
- if (SKIP_PATTERNS.some(p => p.test(trimmed))) return true;
75
-
76
- // If caller provides a custom minimum length, use it
77
- if (minLength !== undefined && minLength > 0) {
78
- if (trimmed.length < minLength && !trimmed.includes('?')) return true;
79
- return false;
80
- }
81
-
82
- // Skip very short non-question messages (likely commands or affirmations)
83
- // CJK characters carry more meaning per character, so use a lower threshold
84
- const hasCJK = /[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/.test(trimmed);
85
- const defaultMinLength = hasCJK ? 6 : 15;
86
- if (trimmed.length < defaultMinLength && !trimmed.includes('?')) return true;
87
-
88
- // Default: do retrieve
89
- return false;
90
- }