@melandlabs/memory-store 0.2.7 → 0.3.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.
@@ -0,0 +1,470 @@
1
+ /**
2
+ * Pure helpers used by the unified memory search pipeline.
3
+ *
4
+ * Kept side-effect free so they can be reused by tests, MCP tools,
5
+ * and the HTTP daemon without pulling in any of the heavier
6
+ * storage/search implementations.
7
+ */
8
+ type UnifiedMemorySearchSource = "memory" | "insights" | "knowledge";
9
+ type UnifiedMemoryReasoningStrategy = "none" | "rewrite" | "iterative";
10
+ interface UnifiedMemorySearchWarning {
11
+ source: UnifiedMemorySearchSource;
12
+ code: string;
13
+ message: string;
14
+ }
15
+ interface UnifiedMemoryReasoningInfo {
16
+ strategy: UnifiedMemoryReasoningStrategy;
17
+ /**
18
+ * True when the caller asked for a non-`"none"` strategy but the
19
+ * corresponding reasoning provider was not configured (or its LLM
20
+ * call failed), so the unified search silently fell back to the default
21
+ * one-shot semantic + lexical path. Callers can use this to surface a
22
+ * degraded-mode warning to their users.
23
+ */
24
+ degraded?: boolean;
25
+ /** Query variants produced by the rewriter (original + rewritten). */
26
+ rewrittenQueries?: string[];
27
+ /** Number of planner iterations executed (iterative mode only). */
28
+ iterations?: number;
29
+ /** Number of evidence items collected by the planner (iterative mode only). */
30
+ evidenceCount?: number;
31
+ /** Date range that was applied to the memory source, if any. */
32
+ dateRange?: {
33
+ from?: string;
34
+ to?: string;
35
+ };
36
+ }
37
+ interface UnifiedMemorySearchInput {
38
+ userId: string;
39
+ query: string;
40
+ sources?: UnifiedMemorySearchSource[];
41
+ limit?: number;
42
+ threshold?: number;
43
+ authToken?: string;
44
+ includeArchivedInsights?: boolean;
45
+ botIds?: string[];
46
+ documentIds?: string[];
47
+ /**
48
+ * Optional ISO-8601 timestamp. When set, graph-aware retrieval and other
49
+ * snapshot consumers drop nodes / edges whose `applicability.validFrom` /
50
+ * `validUntil` window is closed at that instant. Backward-compatible:
51
+ * omitted `asOf` keeps the legacy behaviour exactly.
52
+ *
53
+ * This is a single-point snapshot, not a date range. For interval filtering
54
+ * over memory timestamps use `dateFrom` / `dateTo`.
55
+ */
56
+ asOf?: string;
57
+ /**
58
+ * Optional inclusive start date (ISO-8601). When set, the memory source
59
+ * filters out candidates whose timestamp is before this boundary. The
60
+ * iterative planner also receives this bound and may emit narrower ranges.
61
+ *
62
+ * This only affects the `memory` source; `insights` and `knowledge` are
63
+ * not filtered by this range. Candidates without a recognised timestamp are
64
+ * retained.
65
+ */
66
+ dateFrom?: string;
67
+ /**
68
+ * Optional inclusive end date (ISO-8601). When set, the memory source
69
+ * filters out candidates whose timestamp is after this boundary. Date-only
70
+ * strings are treated as the end of that day.
71
+ *
72
+ * This only affects the `memory` source; `insights` and `knowledge` are
73
+ * not filtered by this range. Candidates without a recognised timestamp are
74
+ * retained.
75
+ */
76
+ dateTo?: string;
77
+ /**
78
+ * How to merge per-source result lists. Defaults to `"similarity"` —
79
+ * the legacy global sort by similarity, then type/id tie-break. The
80
+ * alternate value `"rrf"` invokes reciprocal-rank fusion across the
81
+ * lists fed via the (internal) `rankedLists` option.
82
+ */
83
+ mergeStrategy?: UnifiedMemoryMergeStrategy;
84
+ /**
85
+ * Optional reasoning strategy. `"rewrite"` rewrites the query before
86
+ * embedding; `"iterative"` runs an LLM planner that searches, notes
87
+ * evidence, and searches again. Defaults to `"none"`.
88
+ */
89
+ reasoningStrategy?: UnifiedMemoryReasoningStrategy;
90
+ }
91
+ type UnifiedMemoryMergeStrategy = "similarity" | "rrf";
92
+ /**
93
+ * A single ranked list from one retrieval channel. Used by the RRF merge to
94
+ * combine multiple signals (semantic + lexical + insights + knowledge) without
95
+ * flattening them into a single similarity score first.
96
+ */
97
+ interface UnifiedMemoryRankedList {
98
+ name: "memory-bm25" | "memory-semantic" | "insights" | "knowledge";
99
+ hits: UnifiedMemorySearchResult[];
100
+ }
101
+ interface UnifiedMemorySearchResult {
102
+ type: "memory" | "insight" | "knowledge";
103
+ id: string;
104
+ content: string;
105
+ similarity: number;
106
+ metadata: Record<string, unknown>;
107
+ }
108
+ interface UnifiedMemorySearchOutput {
109
+ query: string;
110
+ sources: UnifiedMemorySearchSource[];
111
+ results: UnifiedMemorySearchResult[];
112
+ count: number;
113
+ warnings: UnifiedMemorySearchWarning[];
114
+ /**
115
+ * Diagnostic information about reasoning and date filtering. Present
116
+ * when (a) a non-`"none"` reasoning strategy was requested, or (b) a
117
+ * `dateFrom` / `dateTo` filter was applied to the memory source.
118
+ */
119
+ reasoning?: UnifiedMemoryReasoningInfo;
120
+ }
121
+ declare function normalizeUnifiedMemorySearchSources(sources: unknown): UnifiedMemorySearchSource[];
122
+ declare function clampUnifiedMemorySearchLimit(limit: unknown): number;
123
+ declare function clampUnifiedMemorySearchThreshold(threshold: unknown): number;
124
+ declare function mergeUnifiedMemorySearchResults(results: UnifiedMemorySearchResult[], limit: number, options?: {
125
+ strategy?: UnifiedMemoryMergeStrategy;
126
+ rankedLists?: UnifiedMemoryRankedList[];
127
+ }): UnifiedMemorySearchResult[];
128
+ declare function normalizeUnifiedMemoryMergeStrategy(value: unknown): UnifiedMemoryMergeStrategy;
129
+ declare function normalizeUnifiedMemoryReasoningStrategy(value: unknown, defaultStrategy?: UnifiedMemoryReasoningStrategy): UnifiedMemoryReasoningStrategy;
130
+ /**
131
+ * Reciprocal Rank Fusion across multiple ranked lists. Each list contributes
132
+ * `1 / (k + rank + 1)` to a hit's accumulated score. Hits are de-duplicated by
133
+ * `(type, id)` so the same message surfaced by both lexical and semantic
134
+ * channels naturally outranks single-channel matches. Tie-break is
135
+ * deterministic: descending RRF score, then `(type, id)` lexical.
136
+ */
137
+ declare function mergeUnifiedMemorySearchResultsRrf(lists: UnifiedMemoryRankedList[], limit: number, k?: number): UnifiedMemorySearchResult[];
138
+ declare function toKnowledgeResult(result: {
139
+ chunkId: string;
140
+ documentId: string;
141
+ documentName: string;
142
+ content: string;
143
+ similarity: number;
144
+ chunkIndex: number;
145
+ }): UnifiedMemorySearchResult;
146
+ declare function toMemoryResult(result: {
147
+ id: string;
148
+ content: string;
149
+ similarity: number;
150
+ metadata: Record<string, unknown>;
151
+ }): UnifiedMemorySearchResult;
152
+ declare function isRawMemorySemanticResult(result: unknown): result is {
153
+ id: string;
154
+ content: string;
155
+ similarity: number;
156
+ metadata: Record<string, unknown>;
157
+ };
158
+
159
+ /**
160
+ * Iterative, LLM-driven memory retrieval.
161
+ *
162
+ * Instead of issuing one fixed query, a small planner model examines the
163
+ * original question and the evidence collected so far, then decides:
164
+ * - search again with different keywords / time bounds
165
+ * - save some of the last results as evidence
166
+ * - finish gathering
167
+ *
168
+ * This trades latency and token cost for better evidence location on
169
+ * multi-hop, temporal, or underspecified questions.
170
+ *
171
+ * The module is provider-agnostic: callers inject a `complete` callback.
172
+ */
173
+ interface IterativeRecallCandidate {
174
+ id: string;
175
+ content: string;
176
+ similarity: number;
177
+ metadata: Record<string, unknown>;
178
+ }
179
+ interface IterativeRecallSearchRequest {
180
+ keywords: string[];
181
+ dateFrom?: string;
182
+ dateTo?: string;
183
+ }
184
+ interface IterativeRecallSearchResult {
185
+ candidates: IterativeRecallCandidate[];
186
+ }
187
+ interface IterativeRecallExecutor {
188
+ search(request: IterativeRecallSearchRequest): Promise<IterativeRecallSearchResult>;
189
+ }
190
+ interface IterativeRecallStats {
191
+ iterations: number;
192
+ searches: number;
193
+ notes: number;
194
+ }
195
+ interface IterativeRecallResult {
196
+ /** Evidence selected by the planner, in the order it was collected. */
197
+ evidence: IterativeRecallCandidate[];
198
+ /** Diagnostic statistics. */
199
+ stats: IterativeRecallStats;
200
+ }
201
+ interface IterativeRecallPlanner {
202
+ plan(input: {
203
+ query: string;
204
+ executor: IterativeRecallExecutor;
205
+ options?: IterativeRecallPlannerOptions;
206
+ /**
207
+ * Optional absolute date range for the search. The planner passes these
208
+ * bounds to the executor and may also emit narrower ranges in later actions.
209
+ */
210
+ dateFrom?: string;
211
+ dateTo?: string;
212
+ }): Promise<IterativeRecallResult>;
213
+ /**
214
+ * Optional. When implemented, returns true iff the most recent `plan()`
215
+ * could not run the planner as designed and fell back to a degraded
216
+ * result — for example the LLM call threw, every iteration produced an
217
+ * unparseable action, or the result relied on the fallback path
218
+ * (recent-hits or baseline search) because the planner never committed
219
+ * any evidence via a `note` action.
220
+ */
221
+ lastDegraded?: () => boolean;
222
+ }
223
+ interface IterativeRecallPlannerOptions {
224
+ /** Maximum planner actions. @default 4 */
225
+ maxIterations?: number;
226
+ /** Results exposed to the planner per internal search. @default 5 */
227
+ searchTopK?: number;
228
+ /** When true, skip planning and return no evidence. @default false */
229
+ disabled?: boolean;
230
+ /**
231
+ * When true (default), if the planner finishes without noting any
232
+ * evidence the planner falls back to either the most recent hits or a
233
+ * baseline keyword search against the original query, so the call is
234
+ * never a total loss. Set to false to surface empty results honestly
235
+ * when the planner never committed anything.
236
+ * @default true
237
+ */
238
+ fallbackToBaseline?: boolean;
239
+ }
240
+ interface IterativeRecallPlannerDeps {
241
+ /** LLM completion callback. */
242
+ complete: (prompt: string) => Promise<string>;
243
+ /** Default options. */
244
+ options?: IterativeRecallPlannerOptions;
245
+ }
246
+ declare function createIterativeRecallPlanner(deps: IterativeRecallPlannerDeps): IterativeRecallPlanner;
247
+ /**
248
+ * A no-op planner that never runs iterative retrieval.
249
+ */
250
+ declare function createIdentityIterativePlanner(): IterativeRecallPlanner;
251
+
252
+ /**
253
+ * Query rewriting for memory retrieval.
254
+ *
255
+ * The memory corpus is usually a first-person chat log ("I told you ...",
256
+ * "I prefer ..."). A query asked in the assistant's register ("What does the
257
+ * user prefer?") often misses the best evidence. A rewriter rephrases the
258
+ * question into the register the memory was written in, improving dense
259
+ * retrieval without changing the underlying index.
260
+ *
261
+ * The module is provider-agnostic: callers inject a `complete` callback that
262
+ * talks to whatever LLM they already use.
263
+ */
264
+ interface QueryRewriterInput {
265
+ query: string;
266
+ userId: string;
267
+ authToken?: string;
268
+ }
269
+ interface QueryRewriter {
270
+ /**
271
+ * Return one or more query variants. The first element is always the
272
+ * original query; subsequent elements are rewritten variants.
273
+ */
274
+ rewrite(input: QueryRewriterInput): Promise<string[]>;
275
+ /**
276
+ * Optional hook that reports whether the most recent `rewrite` call
277
+ * silently degraded to the original query (for example because the LLM
278
+ * call failed or produced no usable variants).
279
+ */
280
+ lastDegraded?(): boolean;
281
+ }
282
+ interface QueryRewriterOptions {
283
+ /**
284
+ * LLM completion callback. Receives the full prompt and returns the raw
285
+ * model output. The rewriter parses the output and falls back to the
286
+ * original query on any failure.
287
+ */
288
+ complete: (prompt: string) => Promise<string>;
289
+ /**
290
+ * Maximum number of rewritten variants to return in addition to the
291
+ * original query. Use `0` to return only the original. Higher values ask
292
+ * the LLM for more alternatives and multiply embedding/search cost
293
+ * proportionally, so callers should bound it (typical: 1–3).
294
+ * @default 1
295
+ */
296
+ maxVariants?: number;
297
+ /**
298
+ * When true, the rewriter returns only the original query and skips the
299
+ * LLM call. Useful as a kill-switch.
300
+ * @default false
301
+ */
302
+ disabled?: boolean;
303
+ }
304
+ declare function createUserVoiceRewriter(options: QueryRewriterOptions): QueryRewriter;
305
+ /**
306
+ * A no-op rewriter that always returns the original query.
307
+ */
308
+ declare function createIdentityRewriter(): QueryRewriter;
309
+
310
+ interface MemoryStoreDb {
311
+ /** Resolve the active Drizzle DB handle. Must be server-side. */
312
+ getDb(): unknown;
313
+ /** Whether the DB has finished initializing (optional, for startup gating). */
314
+ isDbInitialized?(): boolean;
315
+ /** Initialize the DB (optional, called by `createMemoryStore` if present). */
316
+ initDb?(): unknown;
317
+ /**
318
+ * Drizzle table references for the postgres backend. Required when
319
+ * running outside Tauri mode without a registered factory. The host
320
+ * application owns the actual schema.
321
+ */
322
+ tables?: {
323
+ rawMessages?: unknown;
324
+ memorySummaries?: unknown;
325
+ };
326
+ }
327
+ /**
328
+ * Placeholder for the historical host-injected environment. The fields
329
+ * it used to expose (`isTauriMode`, `getTauriDbPath`, `getTauriDataDir`)
330
+ * were Tauri-specific and have been removed. Backend selection is now
331
+ * driven entirely by the `OPENCONTEXT_MEMORY_STORE_BACKEND` env var.
332
+ *
333
+ * The interface is kept (empty) so existing `env?: MemoryStoreEnv`
334
+ * parameters on the public surface still type-check, but new code
335
+ * should not need to construct a value of this type.
336
+ */
337
+ interface MemoryStoreEnv {
338
+ }
339
+ type VectorBackend = "sqlite-vec" | "chroma";
340
+ interface MemoryStoreVectorConfig {
341
+ /** Active backend selector. Defaults to env-based detection. */
342
+ backend?: VectorBackend;
343
+ /** SQLite-vec options (used when backend === "sqlite-vec"). */
344
+ sqliteVec?: {
345
+ dbPath?: string;
346
+ insightsCollection?: string;
347
+ };
348
+ /** Chroma options (used when backend === "chroma"). */
349
+ chroma?: {
350
+ url?: string;
351
+ rawMessagesCollection?: string;
352
+ insightsCollection?: string;
353
+ };
354
+ }
355
+ type EmbedQueryFn = (input: {
356
+ userId: string;
357
+ query: string;
358
+ authToken?: string;
359
+ }) => Promise<number[]>;
360
+ interface UnifiedSearchKnowledgeResult {
361
+ chunkId: string;
362
+ documentId: string;
363
+ documentName: string;
364
+ content: string;
365
+ similarity: number;
366
+ chunkIndex: number;
367
+ }
368
+ interface UnifiedSearchInsightsResult {
369
+ id: string;
370
+ content: string;
371
+ similarity: number;
372
+ metadata: Record<string, unknown>;
373
+ }
374
+ interface UnifiedSearchReasoningDeps {
375
+ /** Optional query rewriter. When present, "rewrite" strategy is available. */
376
+ queryRewriter?: QueryRewriter;
377
+ /** Optional iterative recall planner. When present, "iterative" strategy is available. */
378
+ iterativePlanner?: IterativeRecallPlanner;
379
+ /** Default reasoning strategy when callers do not specify one. @default "none" */
380
+ defaultStrategy?: UnifiedMemoryReasoningStrategy;
381
+ }
382
+ interface UnifiedSearchDeps {
383
+ /** Embed a query string using the active user's provider. */
384
+ embedQuery?: EmbedQueryFn;
385
+ /**
386
+ * Postgres-side ANN search over `raw_messages`. Used when chroma is
387
+ * not enabled but the host has a postgres-backed manager. If both
388
+ * chroma and this are absent, the memory source will return empty
389
+ * (with a warning) in standalone mode.
390
+ */
391
+ searchRawMessagesAnn?: (input: {
392
+ userId: string;
393
+ queryEmbedding: number[];
394
+ limit: number;
395
+ threshold: number;
396
+ botId?: string;
397
+ }) => Promise<Array<{
398
+ id: string;
399
+ content: string;
400
+ similarity: number;
401
+ metadata: Record<string, unknown>;
402
+ }>>;
403
+ /** RAG over uploaded knowledge documents. Returns a list of chunk hits. */
404
+ searchKnowledge?: (input: {
405
+ userId: string;
406
+ query: string;
407
+ options: {
408
+ limit: number;
409
+ threshold: number;
410
+ documentIds?: string[];
411
+ };
412
+ authToken?: string;
413
+ }) => Promise<UnifiedSearchKnowledgeResult[]>;
414
+ /** Semantic search over extracted insights. */
415
+ searchInsights?: (input: {
416
+ userId: string;
417
+ query: string;
418
+ limit: number;
419
+ threshold: number;
420
+ botIds?: string[];
421
+ includeArchived?: boolean;
422
+ authToken?: string;
423
+ }) => Promise<UnifiedSearchInsightsResult[]>;
424
+ /**
425
+ * Optional BM25 (lexical) sub-query for the memory source. Mirrors the
426
+ * `searchRawMessagesAnn` shape but operates on FTS5 keywords instead of
427
+ * dense embeddings. When absent, the unified pipeline skips the lexical
428
+ * path and emits a `memory_lexical_search_not_configured` warning.
429
+ */
430
+ searchRawMessagesLexical?: (input: {
431
+ userId: string;
432
+ keywords: string[];
433
+ limit: number;
434
+ botId?: string;
435
+ }) => Promise<Array<{
436
+ id: string;
437
+ content: string;
438
+ similarity: number;
439
+ metadata: Record<string, unknown>;
440
+ }>>;
441
+ /** Optional reasoning providers. */
442
+ reasoning?: UnifiedSearchReasoningDeps;
443
+ }
444
+ interface MemoryStoreConfig {
445
+ /**
446
+ * Database handle factory. Required for the Postgres backend
447
+ * (`OPENCONTEXT_MEMORY_STORE_BACKEND=postgres`).
448
+ */
449
+ db?: MemoryStoreDb;
450
+ /**
451
+ * Custom SQLite database file path for the default SQLite backend.
452
+ *
453
+ * If provided, this takes precedence over the `MEMORY_STORE_DB_PATH`
454
+ * environment variable and the default `~/.opencontext/memory/store.db`.
455
+ *
456
+ * For backward compatibility with early tutorials, `db.path` is also
457
+ * honoured when `db` is supplied as `{ type, path }`.
458
+ */
459
+ dbPath?: string;
460
+ /** Environment helpers. Defaults to a process.env-only stub if omitted. */
461
+ env?: MemoryStoreEnv;
462
+ /** Vector backend config. Defaults to env-based detection. */
463
+ vector?: MemoryStoreVectorConfig;
464
+ /** Optional cross-source search providers. */
465
+ unified?: UnifiedSearchDeps;
466
+ /** Optional logger. Defaults to console. */
467
+ logger?: Pick<Console, "log" | "warn" | "error">;
468
+ }
469
+
470
+ export { isRawMemorySemanticResult as A, mergeUnifiedMemorySearchResults as B, normalizeUnifiedMemoryReasoningStrategy as C, normalizeUnifiedMemorySearchSources as D, type EmbedQueryFn as E, toKnowledgeResult as F, toMemoryResult as G, type UnifiedSearchDeps as H, type IterativeRecallCandidate as I, type UnifiedMemoryMergeStrategy as J, type UnifiedMemoryRankedList as K, mergeUnifiedMemorySearchResultsRrf as L, type MemoryStoreConfig as M, normalizeUnifiedMemoryMergeStrategy as N, type QueryRewriter as Q, type UnifiedMemoryReasoningInfo as U, type VectorBackend as V, type IterativeRecallExecutor as a, type IterativeRecallPlanner as b, type IterativeRecallPlannerOptions as c, type IterativeRecallResult as d, type IterativeRecallSearchRequest as e, type IterativeRecallSearchResult as f, type IterativeRecallStats as g, type MemoryStoreDb as h, type MemoryStoreEnv as i, type QueryRewriterInput as j, type QueryRewriterOptions as k, type UnifiedMemoryReasoningStrategy as l, type UnifiedMemorySearchInput as m, type UnifiedMemorySearchOutput as n, type UnifiedMemorySearchResult as o, type UnifiedMemorySearchSource as p, type UnifiedMemorySearchWarning as q, type UnifiedSearchInsightsResult as r, type UnifiedSearchKnowledgeResult as s, type UnifiedSearchReasoningDeps as t, clampUnifiedMemorySearchLimit as u, clampUnifiedMemorySearchThreshold as v, createIdentityIterativePlanner as w, createIdentityRewriter as x, createIterativeRecallPlanner as y, createUserVoiceRewriter as z };
package/dist/http.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { M as MemoryStoreConfig } from './config-Ll6ktm4D.js';
1
+ import { M as MemoryStoreConfig } from './config-2Q092MUz.js';
2
2
 
3
3
  /**
4
4
  * @melandlabs/memory-store/http — HTTP server entry.