@melandlabs/memory-store 1.3.6 → 1.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -73,6 +73,11 @@ Both bins accept the same `--embedding-provider` / `--*-backend` flag surface
73
73
  with `--reasoning`, which reads `OPENCONTEXT_LLM_API_KEY` /
74
74
  `OPENCONTEXT_LLM_BASE_URL` / `OPENCONTEXT_LLM_MODEL` from the environment:
75
75
 
76
+ For reproducible OpenRouter runs, set `OPENCONTEXT_LLM_PROVIDER` to one
77
+ provider name. The CLI disables provider fallback and rejects a response
78
+ reported from a different provider; leaving it unset preserves automatic
79
+ routing.
80
+
76
81
  ```bash
77
82
  # HTTP daemon with local embeddings + sqlite-vec memory + LLM reasoning
78
83
  opencontext-memory-http \
@@ -0,0 +1,550 @@
1
+ import * as _melandlabs_memory_consolidation from '@melandlabs/memory-consolidation';
2
+ import { MemoryApplicabilityContext } from '@melandlabs/memory-consolidation';
3
+ import * as _melandlabs_contracts_peer from '@melandlabs/contracts/peer';
4
+ import { Peer } from '@melandlabs/contracts/peer';
5
+ import { m as UnifiedMemoryRrfWeights, i as SessionNeighborExpansionOptions, E as EvidenceSelector, j as UnifiedMemoryMergeStrategy, I as IterativeRecallPlanner, l as UnifiedMemoryReasoningStrategy } from './evidence-selector-uOHsxwch.js';
6
+ import { FactType } from '@melandlabs/contracts';
7
+ import { DerivedFact } from '@melandlabs/contracts/derived-fact';
8
+ import { EntityEdge } from '@melandlabs/contracts/entity-edge';
9
+ import { RawMessage, RawMessageSearchChunk } from '@melandlabs/indexeddb';
10
+
11
+ /**
12
+ * Reranker adapter contract.
13
+ *
14
+ * A `Reranker` is an optional plug-in applied to the unified-search output
15
+ * after per-source merge but before the final `slice(0, limit)`. It lets a
16
+ * host swap in a cross-encoder, a learned ranker, or any other deterministic
17
+ * ordering without changing the retrieval pipeline.
18
+ *
19
+ * Implementations MUST be deterministic for the same `(query, candidates)`
20
+ * pair — non-deterministic orderings would defeat the existing
21
+ * `rrfScore`/`similarity` tie-break tests.
22
+ */
23
+ interface RerankerCandidate {
24
+ id: string;
25
+ content: string;
26
+ metadata?: Record<string, unknown>;
27
+ }
28
+ interface RerankerInput {
29
+ query: string;
30
+ candidates: RerankerCandidate[];
31
+ topK?: number;
32
+ }
33
+ interface RerankerScore {
34
+ id: string;
35
+ score: number;
36
+ evidenceScores?: Array<{
37
+ sourceChunkId?: string;
38
+ startPosition?: number;
39
+ endPosition?: number;
40
+ contentSha256: string;
41
+ score: number;
42
+ inputTokens: number;
43
+ queryTruncated: boolean;
44
+ }>;
45
+ }
46
+ interface Reranker {
47
+ rerank(input: RerankerInput): Promise<RerankerScore[]>;
48
+ }
49
+
50
+ /**
51
+ * Query rewriting for memory retrieval.
52
+ *
53
+ * The memory corpus is usually a first-person chat log ("I told you ...",
54
+ * "I prefer ..."). A query asked in the assistant's register ("What does the
55
+ * user prefer?") often misses the best evidence. A rewriter rephrases the
56
+ * question into the register the memory was written in, improving dense
57
+ * retrieval without changing the underlying index.
58
+ *
59
+ * The module is provider-agnostic: callers inject a `complete` callback that
60
+ * talks to whatever LLM they already use.
61
+ */
62
+ interface QueryRewriterInput {
63
+ query: string;
64
+ userId: string;
65
+ authToken?: string;
66
+ }
67
+ interface QueryRewriter {
68
+ /**
69
+ * Return one or more query variants. The first element is always the
70
+ * original query; subsequent elements are rewritten variants.
71
+ */
72
+ rewrite(input: QueryRewriterInput): Promise<string[]>;
73
+ /**
74
+ * Optional hook that reports whether the most recent `rewrite` call
75
+ * silently degraded to the original query (for example because the LLM
76
+ * call failed or produced no usable variants).
77
+ */
78
+ lastDegraded?(): boolean;
79
+ }
80
+ interface QueryRewriterOptions {
81
+ /** Optional evidence-oriented retrieval expressions; legacy user voice remains default. */
82
+ style?: "user-voice" | "evidence";
83
+ /**
84
+ * LLM completion callback. Receives the full prompt and returns the raw
85
+ * model output. The rewriter parses the output and falls back to the
86
+ * original query on any failure.
87
+ */
88
+ complete: (prompt: string) => Promise<string>;
89
+ /**
90
+ * Maximum number of rewritten variants to return in addition to the
91
+ * original query. Use `0` to return only the original. Higher values ask
92
+ * the LLM for more alternatives and multiply embedding/search cost
93
+ * proportionally, so callers should bound it (typical: 1–3).
94
+ * @default 1
95
+ */
96
+ maxVariants?: number;
97
+ /**
98
+ * When true, the rewriter returns only the original query and skips the
99
+ * LLM call. Useful as a kill-switch.
100
+ * @default false
101
+ */
102
+ disabled?: boolean;
103
+ }
104
+ declare function createUserVoiceRewriter(options: QueryRewriterOptions): QueryRewriter;
105
+ /**
106
+ * A no-op rewriter that always returns the original query.
107
+ */
108
+ declare function createIdentityRewriter(): QueryRewriter;
109
+
110
+ interface MemoryStoreDb {
111
+ /** Resolve the active Drizzle DB handle. Must be server-side. */
112
+ getDb(): unknown;
113
+ /** Whether the DB has finished initializing (optional, for startup gating). */
114
+ isDbInitialized?(): boolean;
115
+ /** Initialize the DB (optional, called by `createMemoryStore` if present). */
116
+ initDb?(): unknown;
117
+ /**
118
+ * Drizzle table references for the postgres backend. Required when
119
+ * running outside Tauri mode without a registered factory. The host
120
+ * application owns the actual schema.
121
+ */
122
+ tables?: {
123
+ rawMessages?: unknown;
124
+ memorySummaries?: unknown;
125
+ };
126
+ }
127
+ /**
128
+ * Placeholder for the historical host-injected environment. The fields
129
+ * it used to expose (`isTauriMode`, `getTauriDbPath`, `getTauriDataDir`)
130
+ * were Tauri-specific and have been removed. Backend selection is now
131
+ * driven entirely by the `OPENCONTEXT_MEMORY_STORE_BACKEND` env var.
132
+ *
133
+ * The interface is kept (empty) so existing `env?: MemoryStoreEnv`
134
+ * parameters on the public surface still type-check, but new code
135
+ * should not need to construct a value of this type.
136
+ */
137
+ interface MemoryStoreEnv {
138
+ }
139
+ type VectorBackend = "sqlite-vec" | "chroma" | "lancedb" | "milvus";
140
+ interface MemoryStoreVectorConfig {
141
+ /** Active backend selector. Defaults to env-based detection. */
142
+ backend?: VectorBackend;
143
+ /** SQLite-vec options (used when backend === "sqlite-vec"). */
144
+ sqliteVec?: {
145
+ dbPath?: string;
146
+ insightsCollection?: string;
147
+ };
148
+ /** Chroma options (used when backend === "chroma"). */
149
+ chroma?: {
150
+ url?: string;
151
+ rawMessagesCollection?: string;
152
+ insightsCollection?: string;
153
+ };
154
+ /** LanceDB options (used when backend === "lancedb"). */
155
+ lancedb?: {
156
+ uri: string;
157
+ table?: string;
158
+ };
159
+ /** Milvus options (used when backend === "milvus"). */
160
+ milvus?: {
161
+ address: string;
162
+ token?: string;
163
+ database?: string;
164
+ collection?: string;
165
+ dimension?: number;
166
+ };
167
+ }
168
+ type EmbedQueryFn = (input: {
169
+ userId: string;
170
+ query: string;
171
+ authToken?: string;
172
+ }) => Promise<number[]>;
173
+ type EmbedDocumentsFn = (input: {
174
+ userId: string;
175
+ texts: string[];
176
+ authToken?: string;
177
+ }) => Promise<number[][]>;
178
+ interface UnifiedSearchKnowledgeResult {
179
+ chunkId: string;
180
+ documentId: string;
181
+ documentName: string;
182
+ content: string;
183
+ similarity: number;
184
+ chunkIndex: number;
185
+ }
186
+ interface UnifiedSearchInsightsResult {
187
+ id: string;
188
+ content: string;
189
+ similarity: number;
190
+ metadata: Record<string, unknown>;
191
+ }
192
+ interface UnifiedSearchReasoningDeps {
193
+ /** Optional extractive context selection after ranking. Disabled unless supplied. */
194
+ evidenceSelector?: EvidenceSelector;
195
+ /**
196
+ * LLM single-turn synthesis callback. Wired into
197
+ * `search({ synthesize: true })` so a host that wants synthesis
198
+ * responses only has to drop in one callable. When omitted, the
199
+ * synthesis path returns the gathered evidence with a
200
+ * `synthesize_llm_not_configured` warning instead of throwing.
201
+ */
202
+ complete?: (prompt: string) => Promise<string>;
203
+ /** Default merge strategy used by `searchUnifiedMemory` when the caller does not specify one. @default "rrf" */
204
+ defaultMergeStrategy?: UnifiedMemoryMergeStrategy;
205
+ /** Optional query rewriter. When present, "rewrite" strategy is available. */
206
+ queryRewriter?: QueryRewriter;
207
+ /** Also search rewritten expressions with BM25 under RRF. At most four distinct queries, including the original. @default false */
208
+ rewriteLexical?: boolean;
209
+ /** Optional rank fusion across semantic query variants under RRF. @default "max-score" */
210
+ rewriteSemanticMerge?: "max-score" | "rrf";
211
+ /** Optional iterative recall planner. When present, "iterative" strategy is available. */
212
+ iterativePlanner?: IterativeRecallPlanner;
213
+ /** Default reasoning strategy when callers do not specify one. @default "none" */
214
+ defaultStrategy?: UnifiedMemoryReasoningStrategy;
215
+ }
216
+ interface MemorySummaryHit {
217
+ summaryId: string;
218
+ summaryText: string;
219
+ summaryTier?: "L1" | "L2" | "L3";
220
+ keywords?: string[];
221
+ startTimestamp?: number;
222
+ endTimestamp?: number;
223
+ }
224
+ /**
225
+ * Trusted applicability fields forwarded together to retrieval providers.
226
+ * They are absent for legacy unscoped searches and always present together
227
+ * for scoped searches. Providers must enforce the canonical applicability
228
+ * contract at `applicabilityAt`; these values must not be derived from public
229
+ * request payloads.
230
+ */
231
+ interface SearchProviderApplicabilityInput {
232
+ applicabilityContexts?: readonly MemoryApplicabilityContext[];
233
+ /** Epoch milliseconds resolved once at the `search()` boundary. */
234
+ applicabilityAt?: number;
235
+ }
236
+ interface UnifiedSearchDeps {
237
+ /** Default rank-fusion weights; omitted channels use weight 1. */
238
+ defaultRrfWeights?: UnifiedMemoryRrfWeights;
239
+ /** Default reciprocal-rank damping constant. */
240
+ defaultRrfK?: number;
241
+ /** Default opt-in session-neighbor expansion configuration. */
242
+ defaultSessionNeighborExpansion?: SessionNeighborExpansionOptions;
243
+ /** Embed a query string using the active user's provider. */
244
+ embedQuery?: EmbedQueryFn;
245
+ /** Batch document embedding used by child indexing when the provider supports it. */
246
+ embedDocuments?: EmbedDocumentsFn;
247
+ /** Model-tokenizer chunking for new writes; omitted hosts retain legacy splitting. */
248
+ getDocumentChunking?: () => Promise<{
249
+ maxTokens: number;
250
+ overlapTokens: number;
251
+ countTokens: (text: string) => number;
252
+ }>;
253
+ /** Safe, non-secret identity used by health checks and child-index metadata. */
254
+ embeddingInfo?: {
255
+ provider?: string;
256
+ model?: string;
257
+ dimensions?: number;
258
+ maxTokens?: number;
259
+ };
260
+ /** Runtime-safe retrieval readiness used by health and diagnostics. */
261
+ getRawMessageRetrievalStatus?: () => Promise<{
262
+ backend: string;
263
+ embeddingProvider?: string;
264
+ embeddingModel?: string;
265
+ embeddingDimensions?: number;
266
+ childCount: number;
267
+ embeddedChildCount: number;
268
+ indexedDimensions: number[];
269
+ semanticReady: boolean;
270
+ lexicalReady: boolean;
271
+ semanticDegradedReason?: string;
272
+ rerankerProvider?: string;
273
+ rerankerModel?: string;
274
+ rerankerReady?: boolean;
275
+ }>;
276
+ /** Optional external child-vector index used by the shared write path. */
277
+ rawMessageChildIndex?: {
278
+ replaceMessages(messages: RawMessage[], chunks: RawMessageSearchChunk[]): Promise<void>;
279
+ };
280
+ /**
281
+ * Dense raw-message search supplied by the selected child-vector backend.
282
+ * External Postgres hosts may also provide their legacy parent-level
283
+ * implementation here; Postgres schema changes are outside this package.
284
+ */
285
+ searchRawMessagesAnn?: (input: SearchProviderApplicabilityInput & {
286
+ userId: string;
287
+ queryEmbedding: number[];
288
+ limit: number;
289
+ threshold: number;
290
+ botId?: string;
291
+ /**
292
+ * Forward `includeDeprecated` from `UnifiedMemorySearchInput` so
293
+ * supersession-aware callers can opt into deprecated rows for
294
+ * audits. Default `false` preserves the current-truth behaviour.
295
+ */
296
+ includeDeprecated?: boolean;
297
+ /**
298
+ * Forward `asOf` from `UnifiedMemorySearchInput` so time-travel
299
+ * callers can ask "what was true at this instant?". Implementations
300
+ * that don't yet honour the field should ignore it — the rest of
301
+ * the unified pipeline stays correct.
302
+ */
303
+ asOf?: string;
304
+ /** Optional peer scope resolved from `UnifiedMemorySearchInput.peerFilter`. */
305
+ peers?: ReadonlyArray<Peer>;
306
+ /** Optional `FactType` filter resolved from `UnifiedMemorySearchInput.factTypes`. */
307
+ factTypes?: FactType[];
308
+ }) => Promise<Array<{
309
+ id: string;
310
+ content: string;
311
+ similarity: number;
312
+ metadata: Record<string, unknown>;
313
+ }>>;
314
+ /** Native dense+BM25 path for backends such as LanceDB and Milvus. */
315
+ searchRawMessagesHybrid?: (input: SearchProviderApplicabilityInput & {
316
+ userId: string;
317
+ query: string;
318
+ queryEmbedding: number[];
319
+ limit: number;
320
+ botId?: string;
321
+ }) => Promise<Array<{
322
+ id: string;
323
+ content: string;
324
+ similarity: number;
325
+ metadata: Record<string, unknown>;
326
+ }>>;
327
+ /** RAG over uploaded knowledge documents. Returns a list of chunk hits. */
328
+ searchKnowledge?: (input: SearchProviderApplicabilityInput & {
329
+ userId: string;
330
+ query: string;
331
+ options: {
332
+ limit: number;
333
+ threshold: number;
334
+ documentIds?: string[];
335
+ };
336
+ authToken?: string;
337
+ /** Optional peer scope resolved from `UnifiedMemorySearchInput.peerFilter`. */
338
+ peers?: ReadonlyArray<Peer>;
339
+ }) => Promise<UnifiedSearchKnowledgeResult[]>;
340
+ /** Semantic search over extracted insights. */
341
+ searchInsights?: (input: SearchProviderApplicabilityInput & {
342
+ userId: string;
343
+ query: string;
344
+ limit: number;
345
+ threshold: number;
346
+ botIds?: string[];
347
+ includeArchived?: boolean;
348
+ authToken?: string;
349
+ /** Optional peer scope resolved from `UnifiedMemorySearchInput.peerFilter`. */
350
+ peers?: ReadonlyArray<Peer>;
351
+ }) => Promise<UnifiedSearchInsightsResult[]>;
352
+ /**
353
+ * Optional BM25 (lexical) sub-query for the memory source. Mirrors the
354
+ * `searchRawMessagesAnn` shape but operates on FTS5 keywords instead of
355
+ * dense embeddings. When absent, the unified pipeline skips the lexical
356
+ * path and emits a `memory_lexical_search_not_configured` warning.
357
+ */
358
+ searchRawMessagesLexical?: (input: SearchProviderApplicabilityInput & {
359
+ userId: string;
360
+ keywords: string[];
361
+ limit: number;
362
+ botId?: string;
363
+ /**
364
+ * Forward `includeDeprecated` from `UnifiedMemorySearchInput` so
365
+ * supersession-aware callers can opt into deprecated rows for
366
+ * audits. Default `false` preserves the current-truth behaviour.
367
+ */
368
+ includeDeprecated?: boolean;
369
+ /**
370
+ * Forward `asOf` from `UnifiedMemorySearchInput` so time-travel
371
+ * callers can ask "what was true at this instant?". Implementations
372
+ * that don't yet honour the field should ignore it — the rest of
373
+ * the unified pipeline stays correct.
374
+ */
375
+ asOf?: string;
376
+ /** Optional peer scope resolved from `UnifiedMemorySearchInput.peerFilter`. */
377
+ peers?: ReadonlyArray<Peer>;
378
+ /** Optional `FactType` filter resolved from `UnifiedMemorySearchInput.factTypes`. */
379
+ factTypes?: FactType[];
380
+ }) => Promise<Array<{
381
+ id: string;
382
+ content: string;
383
+ similarity: number;
384
+ metadata: Record<string, unknown>;
385
+ }>>;
386
+ /** Optional bounded lookup for messages adjacent to fused seed messages. */
387
+ searchRawMessageNeighbors?: (input: {
388
+ userId: string;
389
+ sessionId: string;
390
+ messageSequences: number[];
391
+ window: number;
392
+ includeDeprecated?: boolean;
393
+ }) => Promise<Array<{
394
+ id: string;
395
+ content: string;
396
+ metadata: Record<string, unknown>;
397
+ similarity: number;
398
+ }>>;
399
+ /**
400
+ * Optional summary-tier search. When wired in, `reflect()` consults the
401
+ * summary tier alongside raw messages and emits a
402
+ * `reflect_summaries_unavailable` warning if the call fails.
403
+ */
404
+ searchSummaries?: (input: SearchProviderApplicabilityInput & {
405
+ userId: string;
406
+ query: string;
407
+ keywords: string[];
408
+ limit: number;
409
+ threshold: number;
410
+ dateFrom?: string;
411
+ dateTo?: string;
412
+ authToken?: string;
413
+ /** Optional peer scope resolved from `UnifiedMemorySearchInput.peerFilter`. */
414
+ peers?: ReadonlyArray<Peer>;
415
+ }) => Promise<MemorySummaryHit[]>;
416
+ /**
417
+ * Optional entity sub-query provider. Hosts wire this in to opt
418
+ * into the `entity` channel of unified search: matches are
419
+ * projected back as `UnifiedMemorySearchResult`s and fused with
420
+ * semantic + lexical via RRF. Without it, the entity channel is
421
+ * skipped silently (with a `memory_entity_search_not_configured`
422
+ * warning when RRF was requested).
423
+ *
424
+ * The provider is intentionally narrow: it returns `(messageId,
425
+ * label, score)` tuples. The host is responsible for keeping its
426
+ * own entity store (graph nodes, separate SQLite table, etc.) —
427
+ * the SDK never persists entity edges itself.
428
+ */
429
+ entitySearch?: (input: SearchProviderApplicabilityInput & {
430
+ userId: string;
431
+ keywords: string[];
432
+ limit: number;
433
+ botId?: string;
434
+ /** Optional peer scope resolved from `UnifiedMemorySearchInput.peerFilter`. */
435
+ peers?: ReadonlyArray<Peer>;
436
+ }) => Promise<Array<{
437
+ messageId: string;
438
+ label: string;
439
+ score: number;
440
+ }>>;
441
+ /**
442
+ * Optional entity extractor wired into the `distill` primitive. The
443
+ * extractor receives the raw message text and returns a batch of
444
+ * `EntityEdge`s the host may persist via the `persist` callback
445
+ * supplied on the `distill` input.
446
+ *
447
+ * When omitted, `distill` short-circuits with a
448
+ * `distill_extractor_not_configured` warning — matching the
449
+ * `memory_iterative_planner_not_configured` precedent.
450
+ */
451
+ entityExtractor?: (input: {
452
+ userId: string;
453
+ messageId: string;
454
+ content: string;
455
+ }) => Promise<EntityEdge[]>;
456
+ /**
457
+ * Optional deriver wired into the `derive` primitive. The deriver
458
+ * receives a window of candidate fact texts and returns a batch of
459
+ * `DerivedFact`s the host may persist via the `persist` callback
460
+ * supplied on the `derive` input.
461
+ *
462
+ * When omitted, `derive` short-circuits with a
463
+ * `derive_deriver_not_configured` warning — same precedent.
464
+ */
465
+ deriver?: (input: {
466
+ userId: string;
467
+ userScope: {
468
+ userId: string;
469
+ botIds?: string[];
470
+ dateFrom?: string;
471
+ dateTo?: string;
472
+ };
473
+ recentFactTexts: string[];
474
+ window?: {
475
+ from: number;
476
+ to: number;
477
+ };
478
+ }) => Promise<DerivedFact[]>;
479
+ /**
480
+ * Optional host-side check that validates whether a `peerFilter` peer is
481
+ * actually reachable from this `userId`. Used by
482
+ * `resolveScopePeer` to drop out-of-scope peers with a warning
483
+ * rather than broadening the search. May be omitted for hosts that
484
+ * have no peer-permission model.
485
+ */
486
+ peerScopeCheck?: (input: {
487
+ userId: string;
488
+ peers: ReadonlyArray<_melandlabs_contracts_peer.Peer>;
489
+ }) => Promise<boolean> | boolean;
490
+ /** Optional reasoning providers. */
491
+ reasoning?: UnifiedSearchReasoningDeps;
492
+ /**
493
+ * Optional reranker applied after per-source merge and before the
494
+ * final limit. Defaults to an identity pass-through so the merge order
495
+ * is preserved.
496
+ */
497
+ reranker?: Reranker;
498
+ /** Safe, non-secret reranker identity surfaced by health and diagnostics. */
499
+ rerankerInfo?: {
500
+ provider: string;
501
+ model?: string;
502
+ maxTokens?: number;
503
+ ready: boolean;
504
+ };
505
+ /**
506
+ * Optional logger. When set (e.g. via `MemoryStoreConfig.logger`),
507
+ * `reflect` / `consolidate` log through it instead of `console`.
508
+ * Falls back to `console` when omitted.
509
+ */
510
+ logger?: Pick<Console, "log" | "warn" | "error">;
511
+ }
512
+ interface MemoryStoreConfig {
513
+ /**
514
+ * Database handle factory. Required for the Postgres backend
515
+ * (`OPENCONTEXT_MEMORY_STORE_BACKEND=postgres`).
516
+ */
517
+ db?: MemoryStoreDb;
518
+ /**
519
+ * Custom SQLite database file path for the default SQLite backend.
520
+ *
521
+ * If provided, this takes precedence over the `MEMORY_STORE_DB_PATH`
522
+ * environment variable and the default `~/.opencontext/memory/store.db`.
523
+ *
524
+ * For backward compatibility with early tutorials, `db.path` is also
525
+ * honoured when `db` is supplied as `{ type, path }`.
526
+ */
527
+ dbPath?: string;
528
+ /** Environment helpers. Defaults to a process.env-only stub if omitted. */
529
+ env?: MemoryStoreEnv;
530
+ /** Vector backend config. Defaults to env-based detection. */
531
+ vector?: MemoryStoreVectorConfig;
532
+ /** Optional cross-source search providers. */
533
+ unified?: UnifiedSearchDeps;
534
+ /**
535
+ * Optional write-back graph store. When set, `createMemoryStore` wires
536
+ * it into `store.graphStore` and forwards it to `consolidate`. Hosts
537
+ * that prefer post-construction wiring can use `attachMemoryGraphStore`.
538
+ */
539
+ graphStore?: _melandlabs_memory_consolidation.MemoryGraphStoreWithOperationHistory;
540
+ /**
541
+ * Optional storage adapter carrying `deprecateRecords`. Forwarded into
542
+ * `consolidate` for callers that want deprecation writes without
543
+ * attaching a full graph store.
544
+ */
545
+ storage?: _melandlabs_memory_consolidation.MemoryStorageAdapterLike;
546
+ /** Optional logger. Defaults to console. */
547
+ logger?: Pick<Console, "log" | "warn" | "error">;
548
+ }
549
+
550
+ export { type EmbedQueryFn as E, type MemoryStoreConfig as M, type QueryRewriter as Q, type SearchProviderApplicabilityInput as S, type UnifiedSearchDeps as U, type VectorBackend as V, type MemoryStoreEnv as a, type MemoryStoreDb as b, type QueryRewriterInput as c, type QueryRewriterOptions as d, type UnifiedSearchInsightsResult as e, type UnifiedSearchKnowledgeResult as f, type UnifiedSearchReasoningDeps as g, createIdentityRewriter as h, createUserVoiceRewriter as i };
@@ -30,7 +30,9 @@ interface RawMessage {
30
30
  userId: string;
31
31
  channel?: string;
32
32
  person?: string;
33
- timestamp: number;
33
+ timestamp?: number;
34
+ /** Storage-assigned, per-user insertion order; not an event timestamp. */
35
+ messageSequence?: number;
34
36
  content: string;
35
37
  attachments?: Array<{
36
38
  name: string;