@melandlabs/memory-store 1.2.5 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { U as UnifiedSearchDeps } from '../config-Dw4aXj2a.js';
2
+ import { U as UnifiedSearchDeps } from '../config-CbE2t4SW.js';
3
3
  import '@melandlabs/memory-consolidation';
4
4
  import '@melandlabs/contracts/peer';
5
5
  import '@melandlabs/contracts';
6
6
  import '@melandlabs/contracts/derived-fact';
7
7
  import '@melandlabs/contracts/entity-edge';
8
+ import '@melandlabs/indexeddb';
8
9
 
9
10
  /**
10
11
  * Shared argv parsing + `unified.*` wiring for the memory-store CLI bins.
@@ -15,18 +16,30 @@ import '@melandlabs/contracts/entity-edge';
15
16
  * `/v1/search` route does, so they must speak the same config. This
16
17
  * module owns that surface so the two bins stay in lock-step.
17
18
  *
18
- * `--embedding-provider local` and every `--*-backend=chroma` value
19
- * require `@melandlabs/ai-rag` (a peer install — pulling it in adds
20
- * `@huggingface/transformers`, `chromadb`, and ~30 MB of ONNX weights
21
- * on first run). The bin errors with a clear remediation message if the
22
- * package is missing.
19
+ * `--embedding-provider local` and the insight/knowledge Chroma backends
20
+ * require `@melandlabs/ai-rag`. Raw-message Chroma, LanceDB, and Milvus use
21
+ * the existing `@melandlabs/rag` stores. Missing optional dependencies fail
22
+ * at startup with an actionable error.
23
23
  */
24
24
 
25
25
  interface UnifiedArgs {
26
26
  embeddingProvider: "local" | "openrouter" | "none";
27
27
  embeddingModel?: string;
28
+ embeddingCacheDir?: string;
29
+ rerankerProvider: "local" | "none";
30
+ rerankerModel?: string;
31
+ rerankerCacheDir?: string;
32
+ rerankerBatchSize?: number;
33
+ rerankerMaxTokens?: number;
28
34
  chromaUrl?: string;
29
- memoryBackend: "sqlite-vec" | "chroma" | "none";
35
+ memoryBackend: "sqlite-vec" | "chroma" | "lancedb" | "milvus" | "none";
36
+ lancedbUri?: string;
37
+ lancedbTable?: string;
38
+ milvusAddress?: string;
39
+ milvusToken?: string;
40
+ milvusDatabase?: string;
41
+ milvusCollection?: string;
42
+ milvusDimension?: number;
30
43
  insightsBackend: "sqlite-vec" | "chroma" | "none";
31
44
  insightsCollection: string;
32
45
  knowledgeBackend: "chroma" | "none";
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { ChromaVectorStore } from '@melandlabs/rag/chroma-vector-store';
2
3
  import { mkdirSync } from 'fs';
3
4
  import { dirname } from 'path';
4
5
  import { getOpenContextPath } from '@melandlabs/env-config';
@@ -346,6 +347,121 @@ function createUserVoiceRewriter(options) {
346
347
  };
347
348
  }
348
349
 
350
+ // src/storage/raw-message-child-vector-index.ts
351
+ var RawMessageChildVectorIndex = class {
352
+ backend;
353
+ store;
354
+ catalog;
355
+ constructor(options) {
356
+ this.backend = options.backend;
357
+ this.store = options.store;
358
+ this.catalog = options.catalog;
359
+ }
360
+ async replaceMessages(messages, chunks) {
361
+ const chunksByMessage = /* @__PURE__ */ new Map();
362
+ for (const chunk of chunks) {
363
+ const group = chunksByMessage.get(chunk.messageId) ?? [];
364
+ group.push(chunk);
365
+ chunksByMessage.set(chunk.messageId, group);
366
+ }
367
+ for (const message of messages) {
368
+ await this.store.deleteDocument(message.messageId);
369
+ const vectorChunks = (chunksByMessage.get(message.messageId) ?? []).filter(
370
+ (chunk) => Boolean(chunk.embedding?.length) && !chunk.embedding?.every((value) => value === 0)
371
+ ).map((chunk) => ({
372
+ id: chunk.chunkId,
373
+ documentId: message.messageId,
374
+ content: chunk.content,
375
+ embedding: chunk.embedding,
376
+ metadata: {
377
+ userId: message.userId,
378
+ botId: message.botId,
379
+ platform: message.platform,
380
+ timestamp: message.timestamp,
381
+ chunkIndex: chunk.chunkIndex,
382
+ chunkCount: chunk.chunkCount,
383
+ startPosition: chunk.startPosition,
384
+ endPosition: chunk.endPosition,
385
+ contentHash: chunk.contentHash,
386
+ embeddingModel: chunk.embeddingModel
387
+ }
388
+ }));
389
+ if (vectorChunks.length > 0) await this.store.addChunks(vectorChunks);
390
+ }
391
+ }
392
+ async search(input) {
393
+ const results = input.hybrid && this.store.hybridSearch ? await this.store.hybridSearch({
394
+ text: input.query ?? "",
395
+ vector: input.queryEmbedding,
396
+ limit: input.limit,
397
+ candidateLimit: input.limit,
398
+ fusion: "rrf",
399
+ filter: { userId: input.userId }
400
+ }) : await this.store.similaritySearch(input.queryEmbedding, input.limit, input.userId);
401
+ return this.hydrate(results, input);
402
+ }
403
+ async getChunkCount() {
404
+ return this.store.getChunkCount();
405
+ }
406
+ async hydrate(results, input) {
407
+ const matchedChunks = await this.catalog.getRawMessageSearchChunks({
408
+ chunkIds: results.map((result) => result.id),
409
+ userId: input.userId
410
+ });
411
+ const matchedById = new Map(matchedChunks.map((chunk) => [chunk.chunkId, chunk]));
412
+ const messageIds = [...new Set(matchedChunks.map((chunk) => chunk.messageId))];
413
+ const [allChunks, parents] = await Promise.all([
414
+ this.catalog.getRawMessageSearchChunks({ messageIds, userId: input.userId }),
415
+ Promise.all(messageIds.map((messageId) => this.catalog.getMessageById(messageId)))
416
+ ]);
417
+ const parentById = new Map(
418
+ parents.filter((parent) => Boolean(parent)).map((parent) => [parent.messageId, parent])
419
+ );
420
+ const chunksByMessage = /* @__PURE__ */ new Map();
421
+ for (const chunk of allChunks) {
422
+ const group = chunksByMessage.get(chunk.messageId) ?? [];
423
+ group.push(chunk);
424
+ chunksByMessage.set(chunk.messageId, group);
425
+ }
426
+ const strongest = /* @__PURE__ */ new Map();
427
+ for (const result of results) {
428
+ const chunk = matchedById.get(result.id);
429
+ if (!chunk) continue;
430
+ const parent = parentById.get(chunk.messageId);
431
+ if (!parent || input.botId && parent.botId !== input.botId) continue;
432
+ if (input.threshold !== void 0 && result.score < input.threshold) continue;
433
+ const siblings = (chunksByMessage.get(parent.messageId) ?? []).sort(
434
+ (a, b) => a.chunkIndex - b.chunkIndex
435
+ );
436
+ const content = chunk.chunkCount <= 1 ? parent.content : parent.content.slice(
437
+ siblings[Math.max(0, chunk.chunkIndex - 1)]?.startPosition ?? chunk.startPosition,
438
+ siblings[Math.min(siblings.length - 1, chunk.chunkIndex + 1)]?.endPosition ?? chunk.endPosition
439
+ );
440
+ const hit = {
441
+ id: parent.messageId,
442
+ content,
443
+ similarity: result.score,
444
+ metadata: {
445
+ ...parent.metadata ?? {},
446
+ userId: parent.userId,
447
+ botId: parent.botId,
448
+ platform: parent.platform,
449
+ timestamp: parent.timestamp,
450
+ sourceMessageId: parent.messageId,
451
+ sourceChunkId: chunk.chunkId,
452
+ sourceChunkIndex: chunk.chunkIndex,
453
+ sourceChunkCount: chunk.chunkCount,
454
+ backend: this.backend,
455
+ scoring: input.hybrid ? "native-hybrid" : "dense"
456
+ }
457
+ };
458
+ const existing = strongest.get(parent.messageId);
459
+ if (!existing || hit.similarity > existing.similarity) strongest.set(parent.messageId, hit);
460
+ }
461
+ return [...strongest.values()].sort((a, b) => b.similarity - a.similarity).slice(0, input.limit);
462
+ }
463
+ };
464
+
349
465
  // src/storage/postgres-raw-message-factory.ts
350
466
  var factory = null;
351
467
  async function resolvePostgresFactory(env) {
@@ -449,7 +565,7 @@ function isInsightSQLiteVecEnabled(_env) {
449
565
  }
450
566
  async function getInsightSQLiteVecStore(_env) {
451
567
  const dbPath = resolveSQLiteRawMessageDbPath();
452
- const { getSQLiteVecStore } = await import('@melandlabs/rag/sqlite-vec-store');
568
+ const { getSQLiteVecStore } = await import('@melandlabs/ai-rag/sqlite-vec-store');
453
569
  return await getSQLiteVecStore(dbPath, void 0, {
454
570
  collectionName: process.env.SQLITE_VEC_INSIGHTS_COLLECTION || "opencontext_insights"
455
571
  });
@@ -473,17 +589,19 @@ async function searchInsightsWithSQLiteVec(input, env) {
473
589
  // src/server/cli-shared.ts
474
590
  async function loadAiRag() {
475
591
  try {
476
- const [local, chroma] = await Promise.all([
592
+ const [local, reranker, chroma] = await Promise.all([
477
593
  import('@melandlabs/ai-rag/local-transformers-embedding-provider'),
594
+ import('@melandlabs/ai-rag/local-transformers-reranker'),
478
595
  import('@melandlabs/ai-rag/chroma-store')
479
596
  ]);
480
597
  return {
481
598
  LocalTransformersEmbeddingProvider: local.LocalTransformersEmbeddingProvider,
599
+ LocalTransformersReranker: reranker.LocalTransformersReranker,
482
600
  ChromaVectorStore: chroma.ChromaVectorStore
483
601
  };
484
602
  } catch (error) {
485
603
  throw new Error(
486
- `--embedding-provider local / --*-backend=chroma require @melandlabs/ai-rag to be installed. Run \`pnpm add @melandlabs/ai-rag\` and try again. (${error.message})`
604
+ `Local embedding/reranking and insight/knowledge Chroma require @melandlabs/ai-rag to be installed. Run \`pnpm add @melandlabs/ai-rag\` and try again. (${error.message})`
487
605
  );
488
606
  }
489
607
  }
@@ -492,8 +610,21 @@ function parseUnifiedArgs(argv) {
492
610
  const args = {
493
611
  embeddingProvider: env.EMBEDDING_PROVIDER ?? "none",
494
612
  embeddingModel: env.EMBEDDING_MODEL,
613
+ embeddingCacheDir: env.LOCAL_EMBEDDING_CACHE_DIR,
614
+ rerankerProvider: env.RERANKER_PROVIDER ?? "none",
615
+ rerankerModel: env.LOCAL_RERANKER_MODEL,
616
+ rerankerCacheDir: env.LOCAL_RERANKER_CACHE_DIR,
617
+ rerankerBatchSize: env.LOCAL_RERANKER_BATCH_SIZE ? Number.parseInt(env.LOCAL_RERANKER_BATCH_SIZE, 10) : void 0,
618
+ rerankerMaxTokens: env.LOCAL_RERANKER_MAX_TOKENS ? Number.parseInt(env.LOCAL_RERANKER_MAX_TOKENS, 10) : void 0,
495
619
  chromaUrl: env.CHROMA_URL,
496
620
  memoryBackend: env.MEMORY_BACKEND ?? "none",
621
+ lancedbUri: env.LANCEDB_URI,
622
+ lancedbTable: env.LANCEDB_TABLE,
623
+ milvusAddress: env.MILVUS_ADDRESS,
624
+ milvusToken: env.MILVUS_TOKEN,
625
+ milvusDatabase: env.MILVUS_DATABASE,
626
+ milvusCollection: env.MILVUS_COLLECTION,
627
+ milvusDimension: env.MILVUS_DIMENSION ? Number.parseInt(env.MILVUS_DIMENSION, 10) : void 0,
497
628
  insightsBackend: env.INSIGHTS_BACKEND ?? "none",
498
629
  insightsCollection: env.INSIGHTS_COLLECTION ?? "opencontext_insights",
499
630
  knowledgeBackend: env.KNOWLEDGE_BACKEND ?? "none",
@@ -518,12 +649,51 @@ function parseUnifiedArgs(argv) {
518
649
  case "--embedding-model":
519
650
  args.embeddingModel = takeValue();
520
651
  break;
652
+ case "--embedding-cache-dir":
653
+ args.embeddingCacheDir = takeValue();
654
+ break;
655
+ case "--reranker-provider":
656
+ args.rerankerProvider = takeValue();
657
+ break;
658
+ case "--reranker-model":
659
+ args.rerankerModel = takeValue();
660
+ break;
661
+ case "--reranker-cache-dir":
662
+ args.rerankerCacheDir = takeValue();
663
+ break;
664
+ case "--reranker-batch-size":
665
+ args.rerankerBatchSize = Number.parseInt(takeValue(), 10);
666
+ break;
667
+ case "--reranker-max-tokens":
668
+ args.rerankerMaxTokens = Number.parseInt(takeValue(), 10);
669
+ break;
521
670
  case "--chroma-url":
522
671
  args.chromaUrl = takeValue();
523
672
  break;
524
673
  case "--memory-backend":
525
674
  args.memoryBackend = takeValue();
526
675
  break;
676
+ case "--lancedb-uri":
677
+ args.lancedbUri = takeValue();
678
+ break;
679
+ case "--lancedb-table":
680
+ args.lancedbTable = takeValue();
681
+ break;
682
+ case "--milvus-address":
683
+ args.milvusAddress = takeValue();
684
+ break;
685
+ case "--milvus-token":
686
+ args.milvusToken = takeValue();
687
+ break;
688
+ case "--milvus-database":
689
+ args.milvusDatabase = takeValue();
690
+ break;
691
+ case "--milvus-collection":
692
+ args.milvusCollection = takeValue();
693
+ break;
694
+ case "--milvus-dimension":
695
+ args.milvusDimension = Number.parseInt(takeValue(), 10);
696
+ break;
527
697
  case "--insights-backend":
528
698
  args.insightsBackend = takeValue();
529
699
  break;
@@ -553,31 +723,60 @@ function parseUnifiedArgs(argv) {
553
723
  break;
554
724
  }
555
725
  }
556
- for (const [name, value] of [
557
- ["--embedding-provider", args.embeddingProvider],
558
- ["--memory-backend", args.memoryBackend],
559
- ["--insights-backend", args.insightsBackend],
560
- ["--knowledge-backend", args.knowledgeBackend]
561
- ]) {
562
- const allowed = ["local", "openrouter", "none", "chroma", "sqlite-vec"];
726
+ const validate = (name, value, allowed) => {
563
727
  if (!allowed.includes(value)) {
564
728
  throw new Error(`[memory-store/cli] ${name} must be one of: ${allowed.join(", ")} (got "${value}")`);
565
729
  }
730
+ };
731
+ validate("--embedding-provider", args.embeddingProvider, ["local", "openrouter", "none"]);
732
+ validate("--reranker-provider", args.rerankerProvider, ["local", "none"]);
733
+ validate("--memory-backend", args.memoryBackend, ["sqlite-vec", "chroma", "lancedb", "milvus", "none"]);
734
+ validate("--insights-backend", args.insightsBackend, ["sqlite-vec", "chroma", "none"]);
735
+ validate("--knowledge-backend", args.knowledgeBackend, ["chroma", "none"]);
736
+ if (args.milvusDimension !== void 0 && (!Number.isInteger(args.milvusDimension) || args.milvusDimension <= 0)) {
737
+ throw new Error("[memory-store/cli] --milvus-dimension must be a positive integer");
738
+ }
739
+ for (const [name, value] of [
740
+ ["--reranker-batch-size", args.rerankerBatchSize],
741
+ ["--reranker-max-tokens", args.rerankerMaxTokens]
742
+ ]) {
743
+ if (value !== void 0 && (!Number.isInteger(value) || value <= 0)) {
744
+ throw new Error(`[memory-store/cli] ${name} must be a positive integer`);
745
+ }
566
746
  }
567
747
  return args;
568
748
  }
569
749
  async function buildUnified(args) {
570
750
  const unified = {};
571
751
  const log = (msg) => console.warn(`[memory-store/cli] ${msg}`);
572
- const wantsAiRag = args.embeddingProvider === "local" || args.memoryBackend === "chroma" || args.insightsBackend === "chroma" || args.knowledgeBackend === "chroma";
752
+ const wantsAiRag = args.embeddingProvider === "local" || args.rerankerProvider === "local" || args.insightsBackend === "chroma" || args.knowledgeBackend === "chroma";
573
753
  const aiRag = wantsAiRag ? await loadAiRag() : null;
574
754
  if (args.embeddingProvider === "local") {
575
755
  try {
576
756
  if (!aiRag) {
577
757
  throw new Error("ai-rag modules not loaded");
578
758
  }
579
- const provider = new aiRag.LocalTransformersEmbeddingProvider({ modelName: args.embeddingModel });
580
- unified.embedQuery = async ({ query }) => provider.embedQuery(query);
759
+ const provider = new aiRag.LocalTransformersEmbeddingProvider({
760
+ modelName: args.embeddingModel,
761
+ cacheDir: args.embeddingCacheDir
762
+ });
763
+ unified.embeddingInfo = {
764
+ provider: "local",
765
+ model: args.embeddingModel ?? "Xenova/all-MiniLM-L6-v2",
766
+ maxTokens: 512
767
+ };
768
+ unified.embedQuery = async ({ query }) => {
769
+ const embedding = await provider.embedQuery(query);
770
+ if (unified.embeddingInfo) unified.embeddingInfo.dimensions = embedding.length;
771
+ return embedding;
772
+ };
773
+ unified.embedDocuments = async ({ texts }) => {
774
+ const embeddings = await provider.embedDocuments(texts);
775
+ if (unified.embeddingInfo && embeddings[0]) {
776
+ unified.embeddingInfo.dimensions = embeddings[0].length;
777
+ }
778
+ return embeddings;
779
+ };
581
780
  log(
582
781
  `embedQuery wired via LocalTransformersEmbeddingProvider (model=${args.embeddingModel ?? "Xenova/all-MiniLM-L6-v2"})`
583
782
  );
@@ -593,6 +792,7 @@ async function buildUnified(args) {
593
792
  }
594
793
  const model = args.embeddingModel ?? "text-embedding-3-small";
595
794
  const baseURL = "https://openrouter.ai/api/v1";
795
+ unified.embeddingInfo = { provider: "openrouter", model };
596
796
  unified.embedQuery = async ({ query }) => {
597
797
  const res = await fetch(`${baseURL}/embeddings`, {
598
798
  method: "POST",
@@ -610,49 +810,150 @@ async function buildUnified(args) {
610
810
  const body = await res.json();
611
811
  const embedding = body.data?.[0]?.embedding;
612
812
  if (!embedding) throw new Error("openrouter embeddings response missing data[0].embedding");
813
+ if (unified.embeddingInfo) unified.embeddingInfo.dimensions = embedding.length;
613
814
  return embedding;
614
815
  };
615
816
  log(`embedQuery wired via openrouter (model=${model})`);
616
817
  }
617
- if (args.memoryBackend === "sqlite-vec") {
618
- const store = createRawMessageStore({ env: void 0 });
619
- const manager2 = await store.getManager();
620
- if (typeof manager2.searchMessagesSemantically !== "function") {
818
+ if (args.rerankerProvider === "local") {
819
+ if (!aiRag) throw new Error("Local reranker modules were not loaded");
820
+ const reranker = new aiRag.LocalTransformersReranker({
821
+ modelName: args.rerankerModel,
822
+ cacheDir: args.rerankerCacheDir,
823
+ batchSize: args.rerankerBatchSize,
824
+ maxTokens: args.rerankerMaxTokens
825
+ });
826
+ await reranker.warmup();
827
+ unified.reranker = reranker;
828
+ unified.rerankerInfo = {
829
+ provider: "local",
830
+ model: reranker.getModelName(),
831
+ maxTokens: reranker.getMaxTokens(),
832
+ ready: true
833
+ };
834
+ log(`reranker wired via local Transformers.js (model=${reranker.getModelName()})`);
835
+ } else {
836
+ unified.rerankerInfo = { provider: "none", ready: false };
837
+ }
838
+ if (args.memoryBackend !== "none") {
839
+ if (args.memoryBackend === "chroma" && !args.chromaUrl) {
840
+ throw new Error("--memory-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
841
+ }
842
+ if (args.memoryBackend === "lancedb" && !args.lancedbUri) {
843
+ throw new Error("--memory-backend=lancedb requires --lancedb-uri <uri> (or LANCEDB_URI env)");
844
+ }
845
+ if (args.memoryBackend === "milvus" && !args.milvusAddress) {
846
+ throw new Error("--memory-backend=milvus requires --milvus-address <address> (or MILVUS_ADDRESS env)");
847
+ }
848
+ if (args.memoryBackend === "lancedb") {
849
+ try {
850
+ await import('@lancedb/lancedb');
851
+ } catch (error) {
852
+ throw new Error(
853
+ `LanceDB requires the optional dependency @lancedb/lancedb (${error.message})`
854
+ );
855
+ }
856
+ }
857
+ if (args.memoryBackend === "milvus") {
858
+ try {
859
+ await import('@zilliz/milvus2-sdk-node');
860
+ } catch (error) {
861
+ throw new Error(
862
+ `Milvus requires the optional dependency @zilliz/milvus2-sdk-node (${error.message})`
863
+ );
864
+ }
865
+ }
866
+ const rawStore = createRawMessageStore({ env: void 0 });
867
+ const manager2 = await rawStore.getManager();
868
+ if (typeof manager2.getRawMessageSearchChunks !== "function" || typeof manager2.getRawMessageSearchIndexStats !== "function") {
621
869
  throw new Error(
622
- "the active raw-message manager does not implement searchMessagesSemantically; sqlite-vec ANN for the memory source is unavailable in this build"
870
+ `${args.memoryBackend} child retrieval requires the SQLite raw-message catalog; the Postgres manager remains on the legacy parent fallback in this release`
623
871
  );
624
872
  }
625
- unified.searchRawMessagesAnn = async ({ userId, queryEmbedding, limit, threshold, botId }) => {
626
- const rows = await manager2.searchMessagesSemantically({
627
- userId,
628
- queryEmbedding,
629
- limit,
630
- threshold,
631
- botId
873
+ if (typeof manager2.lexicalSearchMessages === "function") {
874
+ unified.searchRawMessagesLexical = async (input) => {
875
+ const rows = await manager2.lexicalSearchMessages(input);
876
+ return rows.map((row) => ({ ...row, metadata: row.metadata ?? {} }));
877
+ };
878
+ }
879
+ let externalStore;
880
+ if (args.memoryBackend === "chroma") {
881
+ externalStore = new ChromaVectorStore({
882
+ url: args.chromaUrl,
883
+ collectionName: "opencontext_raw_message_chunks"
884
+ });
885
+ } else if (args.memoryBackend === "lancedb") {
886
+ const { LanceDBStore } = await import('@melandlabs/rag/lancedb-store');
887
+ externalStore = new LanceDBStore({
888
+ uri: args.lancedbUri,
889
+ tableName: args.lancedbTable ?? "opencontext_raw_message_chunks",
890
+ defaultFusion: "rrf",
891
+ candidateMultiplier: 4
892
+ });
893
+ } else if (args.memoryBackend === "milvus") {
894
+ const { MilvusStore } = await import('@melandlabs/rag/milvus-store');
895
+ externalStore = new MilvusStore({
896
+ address: args.milvusAddress,
897
+ token: args.milvusToken,
898
+ database: args.milvusDatabase,
899
+ collectionName: args.milvusCollection ?? "opencontext_raw_message_chunks",
900
+ dimension: args.milvusDimension,
901
+ defaultFusion: "rrf",
902
+ candidateMultiplier: 4
632
903
  });
633
- return rows.map((r) => ({
634
- id: r.id,
635
- content: r.content,
636
- similarity: r.similarity,
637
- metadata: r.metadata ?? {}
638
- }));
639
- };
640
- log("memory backend wired via sqlite-vec (uses the manager's searchMessagesSemantically)");
641
- } else if (args.memoryBackend === "chroma") {
642
- if (!args.chromaUrl)
643
- throw new Error("--memory-backend=chroma requires --chroma-url <url> (or CHROMA_URL env)");
644
- if (!aiRag) {
645
- throw new Error("ai-rag modules not loaded");
646
904
  }
647
- const store = new aiRag.ChromaVectorStore({
648
- url: args.chromaUrl,
649
- collectionName: "opencontext_raw_messages"
650
- });
651
- unified.searchRawMessagesAnn = async ({ queryEmbedding, limit, threshold }) => {
652
- const results = await store.similaritySearchWithOptions(queryEmbedding, { limit: limit + 1 });
653
- return results.filter((r) => r.score >= threshold).map((r) => ({ id: r.id, content: r.content, similarity: r.score, metadata: r.metadata ?? {} }));
905
+ if (args.memoryBackend === "sqlite-vec") {
906
+ if (typeof manager2.searchMessagesSemantically !== "function") {
907
+ throw new Error("sqlite-vec child search is unavailable in the active raw-message manager");
908
+ }
909
+ unified.searchRawMessagesAnn = async ({ userId, queryEmbedding, limit, threshold, botId }) => {
910
+ const rows = await manager2.searchMessagesSemantically({
911
+ userId,
912
+ queryEmbedding,
913
+ limit,
914
+ threshold,
915
+ botId
916
+ });
917
+ return rows.map((row) => ({ ...row, metadata: row.metadata ?? {} }));
918
+ };
919
+ } else if (externalStore) {
920
+ const childIndex = new RawMessageChildVectorIndex({
921
+ backend: args.memoryBackend,
922
+ store: externalStore,
923
+ catalog: {
924
+ getMessageById: (messageId) => manager2.getMessageById(messageId),
925
+ // biome-ignore lint/style/noNonNullAssertion: guarded above
926
+ getRawMessageSearchChunks: (input) => manager2.getRawMessageSearchChunks(input)
927
+ }
928
+ });
929
+ unified.rawMessageChildIndex = childIndex;
930
+ unified.searchRawMessagesAnn = (input) => childIndex.search(input);
931
+ if (args.memoryBackend !== "chroma") {
932
+ unified.searchRawMessagesHybrid = (input) => childIndex.search({ ...input, hybrid: true });
933
+ }
934
+ }
935
+ unified.getRawMessageRetrievalStatus = async () => {
936
+ const stats = await manager2.getRawMessageSearchIndexStats();
937
+ const embeddingDimensions = unified.embeddingInfo?.dimensions;
938
+ const dimensionMismatch = embeddingDimensions !== void 0 && stats.embeddingDimensions.length > 0 && !stats.embeddingDimensions.includes(embeddingDimensions);
939
+ const semanticReady = Boolean(unified.embedQuery) && (args.memoryBackend === "sqlite-vec" ? stats.semanticReady : stats.embeddedChunkCount > 0) && !dimensionMismatch;
940
+ return {
941
+ backend: args.memoryBackend,
942
+ embeddingProvider: unified.embeddingInfo?.provider,
943
+ embeddingModel: unified.embeddingInfo?.model,
944
+ embeddingDimensions,
945
+ childCount: stats.chunkCount,
946
+ embeddedChildCount: stats.embeddedChunkCount,
947
+ indexedDimensions: stats.embeddingDimensions,
948
+ semanticReady,
949
+ lexicalReady: stats.lexicalReady,
950
+ semanticDegradedReason: dimensionMismatch ? "semantic_dimension_mismatch" : !unified.embedQuery ? "semantic_unavailable" : stats.embeddedChunkCount === 0 ? "semantic_child_vectors_empty" : void 0,
951
+ rerankerProvider: unified.rerankerInfo?.provider,
952
+ rerankerModel: unified.rerankerInfo?.model,
953
+ rerankerReady: unified.rerankerInfo?.ready ?? false
954
+ };
654
955
  };
655
- log("memory backend wired via chroma (collection=opencontext_raw_messages)");
956
+ log(`memory backend wired via ${args.memoryBackend} with SQLite child catalog and lexical search`);
656
957
  }
657
958
  if (args.insightsBackend === "sqlite-vec" && isInsightSQLiteVecEnabled()) {
658
959
  if (!unified.embedQuery) {
@@ -779,20 +1080,41 @@ function printUnifiedHelp() {
779
1080
  --embedding-model <name> Model name
780
1081
  (env: EMBEDDING_MODEL; local \u2192 Xenova/all-MiniLM-L6-v2,
781
1082
  openrouter \u2192 text-embedding-3-small)
1083
+ --embedding-cache-dir <path> Local embedding cache (env: LOCAL_EMBEDDING_CACHE_DIR)
782
1084
  Note: "local" dynamically imports @melandlabs/ai-rag (a peer install).
783
1085
 
1086
+ Reranking (after RRF/source fusion, before final Top-K):
1087
+ --reranker-provider <name> local | none
1088
+ (env: RERANKER_PROVIDER, default: none)
1089
+ --reranker-model <name> Local sequence-classification model
1090
+ (env: LOCAL_RERANKER_MODEL,
1091
+ default: Xenova/ms-marco-MiniLM-L-6-v2)
1092
+ --reranker-cache-dir <path> Persistent model cache (env: LOCAL_RERANKER_CACHE_DIR)
1093
+ --reranker-batch-size <int> Pair scoring batch size (env: LOCAL_RERANKER_BATCH_SIZE,
1094
+ default: 8)
1095
+ --reranker-max-tokens <int> Query/document pair token limit
1096
+ (env: LOCAL_RERANKER_MAX_TOKENS, default: 512)
1097
+
784
1098
  Cross-source search (wires unified.searchKnowledge / searchInsights / searchRawMessagesAnn):
785
1099
  --chroma-url <url> Chroma server URL
786
1100
  (env: CHROMA_URL; required when any *-backend=chroma)
787
- --memory-backend <name> sqlite-vec | chroma | none
1101
+ --memory-backend <name> sqlite-vec | chroma | lancedb | milvus | none
788
1102
  (env: MEMORY_BACKEND, default: none)
1103
+ --lancedb-uri <uri> LanceDB directory or URI (env: LANCEDB_URI)
1104
+ --lancedb-table <name> Optional table name (env: LANCEDB_TABLE)
1105
+ --milvus-address <address> Milvus endpoint (env: MILVUS_ADDRESS)
1106
+ --milvus-token <token> Optional Milvus token (env: MILVUS_TOKEN)
1107
+ --milvus-database <name> Optional database (env: MILVUS_DATABASE)
1108
+ --milvus-collection <name> Optional collection (env: MILVUS_COLLECTION)
1109
+ --milvus-dimension <int> Optional vector dimension (env: MILVUS_DIMENSION)
789
1110
  --insights-backend <name> sqlite-vec | chroma | none
790
1111
  (env: INSIGHTS_BACKEND, default: none)
791
1112
  --insights-collection <name> Chroma collection (default: opencontext_insights)
792
1113
  --knowledge-backend <name> chroma | none
793
1114
  (env: KNOWLEDGE_BACKEND, default: none)
794
1115
  --knowledge-collection <name> Chroma collection (default: opencontext_knowledge)
795
- Note: the chroma backends dynamically import @melandlabs/ai-rag.
1116
+ Notes: memory Chroma uses @melandlabs/rag; insight/knowledge Chroma use
1117
+ @melandlabs/ai-rag. LanceDB and Milvus require their optional peer dependencies.
796
1118
 
797
1119
  Reasoning (wires unified.reasoning.{queryRewriter, iterativePlanner}):
798
1120
  --reasoning Enable the LLM reasoning layer so /v1/search and
@@ -1,9 +1,10 @@
1
- import { a as MemoryStoreEnv } from '../config-Dw4aXj2a.js';
1
+ import { a as MemoryStoreEnv } from '../config-CbE2t4SW.js';
2
2
  import '@melandlabs/memory-consolidation';
3
3
  import '@melandlabs/contracts/peer';
4
4
  import '@melandlabs/contracts';
5
5
  import '@melandlabs/contracts/derived-fact';
6
6
  import '@melandlabs/contracts/entity-edge';
7
+ import '@melandlabs/indexeddb';
7
8
 
8
9
  /**
9
10
  * Lazy registration slot for the host's Postgres-backed raw message
@@ -1,5 +1,5 @@
1
- import { RawMessageStorageManager } from '@melandlabs/indexeddb';
2
- import { a as MemoryStoreEnv, M as MemoryStoreConfig } from '../config-Dw4aXj2a.js';
1
+ import { RawMessageStorageManager, RawMessageSearchChunk, RawMessageSearchIndexStats } from '@melandlabs/indexeddb';
2
+ import { a as MemoryStoreEnv, M as MemoryStoreConfig } from '../config-CbE2t4SW.js';
3
3
  import '@melandlabs/memory-consolidation';
4
4
  import '@melandlabs/contracts/peer';
5
5
  import '@melandlabs/contracts';
@@ -24,6 +24,12 @@ import '@melandlabs/contracts/entity-edge';
24
24
 
25
25
  type RawMessageStorageBackend = "sqlite" | "postgres";
26
26
  type RawMessageStorageManagerWithSearch = RawMessageStorageManager & {
27
+ getRawMessageSearchChunks?: (input: {
28
+ chunkIds?: string[];
29
+ messageIds?: string[];
30
+ userId?: string;
31
+ }) => Promise<RawMessageSearchChunk[]>;
32
+ getRawMessageSearchIndexStats?: () => Promise<RawMessageSearchIndexStats>;
27
33
  searchMessagesSemantically?: (input: {
28
34
  userId: string;
29
35
  queryEmbedding: number[];
@@ -32,6 +38,12 @@ type RawMessageStorageManagerWithSearch = RawMessageStorageManager & {
32
38
  scanLimit?: number;
33
39
  threshold?: number;
34
40
  includeArchived?: boolean;
41
+ /**
42
+ * Forward `includeDeprecated` from the unified search input so
43
+ * supersession-aware callers can opt into deprecated rows for
44
+ * audits. Default `false` preserves the current-truth behaviour.
45
+ */
46
+ includeDeprecated?: boolean;
35
47
  platform?: string;
36
48
  botId?: string;
37
49
  channel?: string;
@@ -44,6 +56,12 @@ type RawMessageStorageManagerWithSearch = RawMessageStorageManager & {
44
56
  keywords: string[];
45
57
  limit?: number;
46
58
  includeArchived?: boolean;
59
+ /**
60
+ * Forward `includeDeprecated` from the unified search input so
61
+ * supersession-aware callers can opt into deprecated rows for
62
+ * audits. Default `false` preserves the current-truth behaviour.
63
+ */
64
+ includeDeprecated?: boolean;
47
65
  platform?: string;
48
66
  botId?: string;
49
67
  }) => Promise<unknown[]>;
@@ -1,10 +1,11 @@
1
1
  import { SQLiteRawMessageManager } from '@melandlabs/sqlite';
2
- import { a as MemoryStoreEnv } from '../config-Dw4aXj2a.js';
2
+ import { a as MemoryStoreEnv } from '../config-CbE2t4SW.js';
3
3
  import '@melandlabs/memory-consolidation';
4
4
  import '@melandlabs/contracts/peer';
5
5
  import '@melandlabs/contracts';
6
6
  import '@melandlabs/contracts/derived-fact';
7
7
  import '@melandlabs/contracts/entity-edge';
8
+ import '@melandlabs/indexeddb';
8
9
 
9
10
  /**
10
11
  * SQLite-backed raw message store.
@@ -48,6 +49,12 @@ declare function lexicalSearchRawMessages(input: {
48
49
  keywords: string[];
49
50
  limit?: number;
50
51
  includeArchived?: boolean;
52
+ /**
53
+ * Forward `includeDeprecated` from `UnifiedMemorySearchInput` so
54
+ * supersession-aware callers can opt into deprecated rows for
55
+ * audits. Default `false` preserves the current-truth behaviour.
56
+ */
57
+ includeDeprecated?: boolean;
51
58
  platform?: string;
52
59
  botId?: string;
53
60
  factTypes?: Array<"world" | "experience" | "mental_model">;