@shadowforge0/aquifer-memory 1.6.0 → 1.8.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.
Files changed (44) hide show
  1. package/.env.example +8 -0
  2. package/README.md +72 -0
  3. package/README_CN.md +17 -0
  4. package/README_TW.md +4 -0
  5. package/aquifer.config.example.json +19 -0
  6. package/consumers/cli.js +259 -12
  7. package/consumers/codex-active-checkpoint.js +186 -0
  8. package/consumers/codex-current-memory.js +106 -0
  9. package/consumers/codex-handoff.js +551 -6
  10. package/consumers/codex.js +209 -25
  11. package/consumers/mcp.js +144 -6
  12. package/consumers/shared/config.js +60 -1
  13. package/consumers/shared/factory.js +10 -3
  14. package/core/aquifer.js +357 -838
  15. package/core/backends/capabilities.js +89 -0
  16. package/core/backends/local.js +430 -0
  17. package/core/legacy-bootstrap.js +140 -0
  18. package/core/mcp-manifest.js +66 -2
  19. package/core/memory-bootstrap.js +20 -8
  20. package/core/memory-consolidation.js +365 -11
  21. package/core/memory-promotion.js +157 -26
  22. package/core/memory-recall.js +341 -22
  23. package/core/memory-records.js +347 -11
  24. package/core/memory-serving.js +132 -0
  25. package/core/postgres-migrations.js +533 -0
  26. package/core/public-session-filter.js +40 -0
  27. package/core/recall-runtime.js +115 -0
  28. package/core/scope-attribution.js +279 -0
  29. package/core/session-checkpoint-producer.js +412 -0
  30. package/core/session-checkpoints.js +432 -0
  31. package/core/session-finalization.js +98 -2
  32. package/core/storage-checkpoints.js +546 -0
  33. package/core/storage.js +121 -8
  34. package/docs/getting-started.md +6 -0
  35. package/docs/setup.md +66 -3
  36. package/package.json +8 -4
  37. package/schema/014-v1-checkpoint-runs.sql +349 -0
  38. package/schema/015-v1-evidence-items.sql +92 -0
  39. package/schema/016-v1-evidence-ref-multi-item.sql +19 -0
  40. package/schema/017-v1-memory-record-embeddings.sql +25 -0
  41. package/schema/018-v1-finalization-candidate-envelope.sql +39 -0
  42. package/scripts/codex-checkpoint-commands.js +464 -0
  43. package/scripts/codex-checkpoint-runtime.js +520 -0
  44. package/scripts/codex-recovery.js +246 -1
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { resolveApplicableRecords } = require('./memory-bootstrap');
4
+ const { hybridRank } = require('./hybrid-rank');
4
5
 
5
6
  const TYPE_RANK = {
6
7
  constraint: 80,
@@ -22,6 +23,13 @@ const FEEDBACK_WEIGHT = {
22
23
  incorrect: -0.50,
23
24
  };
24
25
 
26
+ const RETRIEVAL_TYPE_BOOST = 0.05;
27
+ const SIGNAL_PRIORITY = {
28
+ linked_summary: 1,
29
+ evidence_item: 2,
30
+ memory_row: 3,
31
+ };
32
+
25
33
  const TYPE_RANK_SQL = `
26
34
  CASE m.memory_type
27
35
  WHEN 'constraint' THEN 0.80
@@ -35,6 +43,8 @@ const TYPE_RANK_SQL = `
35
43
  ELSE 0
36
44
  END`;
37
45
 
46
+ const TYPE_BOOST_SQL = `(${TYPE_RANK_SQL}) * ${RETRIEVAL_TYPE_BOOST}`;
47
+
38
48
  function feedbackScoreSql(schema) {
39
49
  return `
40
50
  COALESCE((
@@ -109,6 +119,10 @@ function rankValue(record, key) {
109
119
  }
110
120
 
111
121
  function sortRecallRows(a, b) {
122
+ const aSignalPriority = rankValue(a, 'signal_priority');
123
+ const bSignalPriority = rankValue(b, 'signal_priority');
124
+ if (bSignalPriority !== aSignalPriority) return bSignalPriority - aSignalPriority;
125
+
112
126
  const aTitleMatch = a.title_match === true ? 1 : 0;
113
127
  const bTitleMatch = b.title_match === true ? 1 : 0;
114
128
  if (bTitleMatch !== aTitleMatch) return bTitleMatch - aTitleMatch;
@@ -124,6 +138,82 @@ function sortRecallRows(a, b) {
124
138
  return getId(a).localeCompare(getId(b));
125
139
  }
126
140
 
141
+ function memoryRecallKey(row) {
142
+ return String(row && (row.id || row.memory_id || row.memoryId || row.canonical_key || row.canonicalKey || ''));
143
+ }
144
+
145
+ function rankHybridMemoryRows(lexicalRows = [], embeddingRows = [], opts = {}) {
146
+ const limit = Math.max(1, Math.min(50, opts.limit || 10));
147
+ const rowsById = new Map();
148
+ function remember(row, signal) {
149
+ const id = memoryRecallKey(row);
150
+ if (!id) return;
151
+ const existing = rowsById.get(id);
152
+ const next = existing ? { ...existing, ...row } : { ...row };
153
+ const signals = new Set(existing && Array.isArray(existing._matchSignals) ? existing._matchSignals : []);
154
+ signals.add(signal);
155
+ next._matchSignals = [...signals];
156
+ next.match_signal = signals.size > 1 ? 'memory_row_hybrid' : 'memory_row';
157
+ delete next.signal_priority;
158
+ rowsById.set(id, next);
159
+ }
160
+ for (const row of lexicalRows || []) remember(row, 'lexical');
161
+ for (const row of embeddingRows || []) remember(row, 'semantic');
162
+
163
+ function adapt(row) {
164
+ const id = memoryRecallKey(row);
165
+ return {
166
+ ...row,
167
+ session_id: id,
168
+ started_at: row.accepted_at || row.observed_at || row.updated_at || row.created_at || row.started_at,
169
+ trust_score: row.trust_score ?? 0.5,
170
+ };
171
+ }
172
+
173
+ const fused = hybridRank(
174
+ (lexicalRows || []).map(adapt),
175
+ (embeddingRows || []).map(adapt),
176
+ [],
177
+ {
178
+ limit: Math.max(limit, rowsById.size || limit),
179
+ weights: { rrf: 0.82, timeDecay: 0.12, access: 0.06, entityBoost: 0, openLoop: 0 },
180
+ },
181
+ );
182
+
183
+ const scored = fused.map(fusedRow => {
184
+ const id = memoryRecallKey(fusedRow);
185
+ const row = rowsById.get(id) || fusedRow;
186
+ const rowScore = rankValue(row, 'recall_score') || rankValue(row, 'score') || rankValue(row, 'semantic_score') || rankValue(row, 'lexical_rank');
187
+ const typeScore = rankValue(row, 'type_rank');
188
+ const feedback = rankValue(row, 'feedback_score');
189
+ const score = (0.82 * rankValue(fusedRow, '_score')) + (0.14 * Math.min(1, Math.max(0, rowScore))) + (0.02 * typeScore) + (0.02 * feedback);
190
+ const ranked = {
191
+ ...row,
192
+ recall_score: score,
193
+ score,
194
+ _score: score,
195
+ _rrf: fusedRow._rrf,
196
+ _timeDecay: fusedRow._timeDecay,
197
+ _access: fusedRow._access,
198
+ };
199
+ delete ranked.session_id;
200
+ delete ranked.signal_priority;
201
+ return ranked;
202
+ });
203
+
204
+ scored.sort((a, b) => {
205
+ const aScore = rankValue(a, '_score');
206
+ const bScore = rankValue(b, '_score');
207
+ if (bScore !== aScore) return bScore - aScore;
208
+ const aAccepted = Date.parse(a.accepted_at || a.acceptedAt || '') || 0;
209
+ const bAccepted = Date.parse(b.accepted_at || b.acceptedAt || '') || 0;
210
+ if (bAccepted !== aAccepted) return bAccepted - aAccepted;
211
+ return memoryRecallKey(a).localeCompare(memoryRecallKey(b));
212
+ });
213
+
214
+ return scored.slice(0, limit);
215
+ }
216
+
127
217
  function feedbackScore(record, feedbackEvents = []) {
128
218
  const id = getId(record);
129
219
  let score = 0;
@@ -144,6 +234,14 @@ function lexicalScore(haystack, query) {
144
234
  return hits / tokens.length;
145
235
  }
146
236
 
237
+ function vecToStr(vec) {
238
+ if (!vec || !Array.isArray(vec) || vec.length === 0) return null;
239
+ for (let i = 0; i < vec.length; i++) {
240
+ if (!Number.isFinite(vec[i])) throw new Error(`Vector contains non-finite value at index ${i}`);
241
+ }
242
+ return `[${vec.join(',')}]`;
243
+ }
244
+
147
245
  function recallMemoryRecords(records = [], query, opts = {}) {
148
246
  const q = String(query || '').trim().toLowerCase();
149
247
  if (!q) throw new Error('memory.recall(query): query must be a non-empty string');
@@ -160,11 +258,13 @@ function recallMemoryRecords(records = [], query, opts = {}) {
160
258
  .map(record => {
161
259
  const haystack = textOf(record).toLowerCase();
162
260
  const lexical = lexicalScore(haystack, q);
163
- const typeRank = (TYPE_RANK[record.memoryType || record.memory_type] || 0) / 100;
261
+ const typeRank = ((TYPE_RANK[record.memoryType || record.memory_type] || 0) / 100) * RETRIEVAL_TYPE_BOOST;
164
262
  const feedback = feedbackScore(record, feedbackEvents);
165
263
  return {
166
264
  ...record,
167
265
  score: lexical + typeRank + feedback,
266
+ signal_priority: SIGNAL_PRIORITY.memory_row,
267
+ match_signal: 'memory_row',
168
268
  _debug: { lexical, typeRank, feedback },
169
269
  };
170
270
  })
@@ -174,11 +274,32 @@ function recallMemoryRecords(records = [], query, opts = {}) {
174
274
  }
175
275
 
176
276
  function createMemoryRecall({ pool, schema, defaultTenantId }) {
277
+ function applyCurrentMemoryFilters(where, params, opts = {}) {
278
+ const scopeKeys = activeScopeKeys(opts);
279
+ if (opts.scopeId) {
280
+ params.push(opts.scopeId);
281
+ where.push(`m.scope_id = $${params.length}`);
282
+ }
283
+ if (scopeKeys) {
284
+ params.push(scopeKeys);
285
+ where.push(`s.scope_key = ANY($${params.length}::text[])`);
286
+ }
287
+ if (opts.asOf) {
288
+ params.push(opts.asOf);
289
+ const at = `$${params.length}::timestamptz`;
290
+ where.push(`(m.valid_from IS NULL OR m.valid_from <= ${at})`);
291
+ where.push(`(m.valid_to IS NULL OR m.valid_to > ${at})`);
292
+ where.push(`(m.stale_after IS NULL OR m.stale_after > ${at})`);
293
+ }
294
+ return scopeKeys;
295
+ }
296
+
177
297
  async function recall(query, opts = {}) {
178
298
  const q = String(query || '').trim();
179
299
  if (!q) throw new Error('memory.recall(query): query must be a non-empty string');
180
300
  const tenantId = opts.tenantId || defaultTenantId;
181
301
  const limit = Math.max(1, Math.min(50, opts.limit || 10));
302
+ const cfg = (opts.ftsConfig === 'zhcfg' || opts.ftsConfig === 'simple') ? opts.ftsConfig : 'simple';
182
303
  const scopeKeys = activeScopeKeys(opts);
183
304
  const fetchLimit = Math.max(limit, Math.min(200, scopeKeys ? limit * 4 : limit));
184
305
  const feedbackScoreExpr = feedbackScoreSql(schema);
@@ -187,37 +308,25 @@ function createMemoryRecall({ pool, schema, defaultTenantId }) {
187
308
  `m.tenant_id = $1`,
188
309
  `m.status = 'active'`,
189
310
  `m.visible_in_recall = true`,
190
- `(m.search_tsv @@ plainto_tsquery('simple', $2)
311
+ `(m.search_tsv @@ plainto_tsquery('${cfg}', $2)
191
312
  OR m.title ILIKE '%' || $2 || '%'
192
313
  OR m.summary ILIKE '%' || $2 || '%'
193
314
  OR m.context_key ILIKE '%' || $2 || '%'
194
315
  OR m.topic_key ILIKE '%' || $2 || '%')`,
195
316
  ];
196
- if (opts.scopeId) {
197
- params.push(opts.scopeId);
198
- where.push(`m.scope_id = $${params.length}`);
199
- }
200
- if (scopeKeys) {
201
- params.push(scopeKeys);
202
- where.push(`s.scope_key = ANY($${params.length}::text[])`);
203
- }
204
- if (opts.asOf) {
205
- params.push(opts.asOf);
206
- const at = `$${params.length}::timestamptz`;
207
- where.push(`(m.valid_from IS NULL OR m.valid_from <= ${at})`);
208
- where.push(`(m.valid_to IS NULL OR m.valid_to > ${at})`);
209
- where.push(`(m.stale_after IS NULL OR m.stale_after > ${at})`);
210
- }
317
+ applyCurrentMemoryFilters(where, params, opts);
211
318
  params.push(fetchLimit);
212
319
  const result = await pool.query(
213
320
  `SELECT
214
321
  m.*, s.scope_kind, s.scope_key, s.inheritance_mode AS scope_inheritance_mode,
322
+ 'memory_row'::text AS match_signal,
323
+ ${SIGNAL_PRIORITY.memory_row}::int AS signal_priority,
215
324
  (m.title ILIKE '%' || $2 || '%') AS title_match,
216
- ts_rank(m.search_tsv, plainto_tsquery('simple', $2)) AS lexical_rank,
217
- ${TYPE_RANK_SQL} AS type_rank,
325
+ ts_rank(m.search_tsv, plainto_tsquery('${cfg}', $2)) AS lexical_rank,
326
+ ${TYPE_BOOST_SQL} AS type_rank,
218
327
  ${feedbackScoreExpr} AS feedback_score,
219
- ts_rank(m.search_tsv, plainto_tsquery('simple', $2))
220
- + ${TYPE_RANK_SQL}
328
+ ts_rank(m.search_tsv, plainto_tsquery('${cfg}', $2))
329
+ + ${TYPE_BOOST_SQL}
221
330
  + ${feedbackScoreExpr} AS recall_score
222
331
  FROM ${schema}.memory_records m
223
332
  JOIN ${schema}.scopes s ON s.id = m.scope_id
@@ -238,10 +347,220 @@ function createMemoryRecall({ pool, schema, defaultTenantId }) {
238
347
  .slice(0, limit);
239
348
  }
240
349
 
241
- return { recall };
350
+ async function recallViaEvidenceItems(query, opts = {}) {
351
+ const q = String(query || '').trim();
352
+ if (!q) throw new Error('memory.recall(query): query must be a non-empty string');
353
+ const tenantId = opts.tenantId || defaultTenantId;
354
+ const limit = Math.max(1, Math.min(50, opts.limit || 10));
355
+ const scopeKeys = activeScopeKeys(opts);
356
+ const fetchLimit = Math.max(limit, Math.min(200, scopeKeys ? limit * 4 : limit));
357
+ const feedbackScoreExpr = feedbackScoreSql(schema);
358
+ const params = [tenantId, q];
359
+ const where = [
360
+ `m.tenant_id = $1`,
361
+ `m.status = 'active'`,
362
+ `m.visible_in_recall = true`,
363
+ ];
364
+ applyCurrentMemoryFilters(where, params, opts);
365
+ const queryVec = vecToStr(opts.queryVec);
366
+ let vectorScoreExpr = '0';
367
+ let evidencePredicate = `(ei.excerpt_text ILIKE '%' || $2 || '%'
368
+ OR ei.search_tsv @@ plainto_tsquery('simple', $2))`;
369
+ if (queryVec) {
370
+ params.push(queryVec);
371
+ const vecPos = params.length;
372
+ vectorScoreExpr = `COALESCE(1.0 - (ei.embedding <=> $${vecPos}::vector), 0)`;
373
+ evidencePredicate = opts.vectorOnly === true
374
+ ? `ei.embedding IS NOT NULL`
375
+ : `(${evidencePredicate} OR ei.embedding IS NOT NULL)`;
376
+ }
377
+ params.push(fetchLimit);
378
+ const result = await pool.query(
379
+ `WITH eligible_memories AS (
380
+ SELECT m.*, s.scope_kind, s.scope_key, s.inheritance_mode AS scope_inheritance_mode
381
+ FROM ${schema}.memory_records m
382
+ JOIN ${schema}.scopes s ON s.id = m.scope_id
383
+ WHERE ${where.join(' AND ')}
384
+ ),
385
+ evidence_hits AS (
386
+ SELECT
387
+ e.owner_id AS memory_id,
388
+ MAX(
389
+ CASE WHEN ei.excerpt_text ILIKE '%' || $2 || '%' THEN 1 ELSE 0 END
390
+ + ts_rank(ei.search_tsv, plainto_tsquery('simple', $2))
391
+ + similarity(ei.excerpt_text, $2)
392
+ + ${vectorScoreExpr}
393
+ ) AS evidence_score,
394
+ MAX(ei.created_at) AS latest_evidence_at
395
+ FROM ${schema}.evidence_items ei
396
+ JOIN ${schema}.evidence_refs e
397
+ ON e.tenant_id = ei.tenant_id
398
+ AND e.evidence_item_id = ei.id
399
+ AND e.owner_kind = 'memory_record'
400
+ JOIN eligible_memories em ON em.id = e.owner_id
401
+ WHERE ei.tenant_id = $1
402
+ AND ${evidencePredicate}
403
+ GROUP BY e.owner_id
404
+ )
405
+ SELECT
406
+ m.*,
407
+ 'evidence_item'::text AS match_signal,
408
+ ${SIGNAL_PRIORITY.evidence_item}::int AS signal_priority,
409
+ FALSE AS title_match,
410
+ 0::real AS lexical_rank,
411
+ eh.evidence_score,
412
+ ${TYPE_BOOST_SQL} AS type_rank,
413
+ ${feedbackScoreExpr} AS feedback_score,
414
+ eh.evidence_score
415
+ + ${TYPE_BOOST_SQL}
416
+ + ${feedbackScoreExpr} AS recall_score
417
+ FROM evidence_hits eh
418
+ JOIN eligible_memories m ON m.id = eh.memory_id
419
+ ORDER BY
420
+ recall_score DESC,
421
+ eh.latest_evidence_at DESC NULLS LAST,
422
+ m.accepted_at DESC NULLS LAST,
423
+ m.id ASC
424
+ LIMIT $${params.length}`,
425
+ params,
426
+ );
427
+ const applicableRows = scopeKeys
428
+ ? resolveApplicableRecords(result.rows, opts)
429
+ : result.rows;
430
+ return applicableRows
431
+ .sort(sortRecallRows)
432
+ .slice(0, limit);
433
+ }
434
+
435
+ async function recallViaMemoryEmbeddings(queryVec, opts = {}) {
436
+ const vector = vecToStr(queryVec);
437
+ if (!vector) return [];
438
+ const tenantId = opts.tenantId || defaultTenantId;
439
+ const limit = Math.max(1, Math.min(50, opts.limit || 10));
440
+ const scopeKeys = activeScopeKeys(opts);
441
+ const fetchLimit = Math.max(limit, Math.min(200, scopeKeys ? limit * 4 : limit));
442
+ const feedbackScoreExpr = feedbackScoreSql(schema);
443
+ const params = [tenantId, vector];
444
+ const where = [
445
+ `m.tenant_id = $1`,
446
+ `m.status = 'active'`,
447
+ `m.visible_in_recall = true`,
448
+ `m.embedding IS NOT NULL`,
449
+ ];
450
+ applyCurrentMemoryFilters(where, params, opts);
451
+ params.push(fetchLimit);
452
+ const result = await pool.query(
453
+ `SELECT
454
+ m.*, s.scope_kind, s.scope_key, s.inheritance_mode AS scope_inheritance_mode,
455
+ 'memory_row'::text AS match_signal,
456
+ ${SIGNAL_PRIORITY.memory_row}::int AS signal_priority,
457
+ FALSE AS title_match,
458
+ 0::real AS lexical_rank,
459
+ 1.0 - (m.embedding <=> $2::vector) AS semantic_score,
460
+ ${TYPE_BOOST_SQL} AS type_rank,
461
+ ${feedbackScoreExpr} AS feedback_score,
462
+ 1.0 - (m.embedding <=> $2::vector)
463
+ + ${TYPE_BOOST_SQL}
464
+ + ${feedbackScoreExpr} AS recall_score
465
+ FROM ${schema}.memory_records m
466
+ JOIN ${schema}.scopes s ON s.id = m.scope_id
467
+ WHERE ${where.join(' AND ')}
468
+ ORDER BY
469
+ m.embedding <=> $2::vector ASC,
470
+ m.accepted_at DESC NULLS LAST,
471
+ m.id ASC
472
+ LIMIT $${params.length}`,
473
+ params,
474
+ );
475
+ const applicableRows = scopeKeys
476
+ ? resolveApplicableRecords(result.rows, opts)
477
+ : result.rows;
478
+ return applicableRows
479
+ .sort(sortRecallRows)
480
+ .slice(0, limit);
481
+ }
482
+
483
+ async function recallViaLinkedSummaryEmbeddings(queryVec, opts = {}) {
484
+ const vector = vecToStr(queryVec);
485
+ if (!vector) return [];
486
+ const tenantId = opts.tenantId || defaultTenantId;
487
+ const limit = Math.max(1, Math.min(50, opts.limit || 10));
488
+ const scopeKeys = activeScopeKeys(opts);
489
+ const fetchLimit = Math.max(limit, Math.min(200, scopeKeys ? limit * 4 : limit));
490
+ const feedbackScoreExpr = feedbackScoreSql(schema);
491
+ const params = [tenantId, vector];
492
+ const where = [
493
+ `m.tenant_id = $1`,
494
+ `m.status = 'active'`,
495
+ `m.visible_in_recall = true`,
496
+ ];
497
+ applyCurrentMemoryFilters(where, params, opts);
498
+ params.push(fetchLimit);
499
+ const result = await pool.query(
500
+ `WITH eligible_memories AS (
501
+ SELECT m.*, s.scope_kind, s.scope_key, s.inheritance_mode AS scope_inheritance_mode
502
+ FROM ${schema}.memory_records m
503
+ JOIN ${schema}.scopes s ON s.id = m.scope_id
504
+ WHERE ${where.join(' AND ')}
505
+ ),
506
+ linked_summary_hits AS (
507
+ SELECT
508
+ e.owner_id AS memory_id,
509
+ MAX(1.0 - (ss.embedding <=> $2::vector)) AS linked_summary_score,
510
+ MAX(ss.updated_at) AS latest_summary_at
511
+ FROM ${schema}.evidence_refs e
512
+ JOIN ${schema}.sessions src
513
+ ON src.tenant_id = e.tenant_id
514
+ AND src.session_id = e.source_ref
515
+ JOIN ${schema}.session_summaries ss
516
+ ON ss.session_row_id = src.id
517
+ WHERE e.tenant_id = $1
518
+ AND e.owner_kind = 'memory_record'
519
+ AND e.source_kind = 'session_summary'
520
+ AND ss.embedding IS NOT NULL
521
+ AND EXISTS (SELECT 1 FROM eligible_memories em WHERE em.id = e.owner_id)
522
+ GROUP BY e.owner_id
523
+ )
524
+ SELECT
525
+ m.*,
526
+ 'linked_summary'::text AS match_signal,
527
+ ${SIGNAL_PRIORITY.linked_summary}::int AS signal_priority,
528
+ FALSE AS title_match,
529
+ 0::real AS lexical_rank,
530
+ lsh.linked_summary_score,
531
+ 0::real AS type_rank,
532
+ ${feedbackScoreExpr} AS feedback_score,
533
+ (lsh.linked_summary_score * 0.35)
534
+ + ${feedbackScoreExpr} AS recall_score
535
+ FROM linked_summary_hits lsh
536
+ JOIN eligible_memories m ON m.id = lsh.memory_id
537
+ ORDER BY
538
+ recall_score DESC,
539
+ lsh.latest_summary_at DESC NULLS LAST,
540
+ m.accepted_at DESC NULLS LAST,
541
+ m.id ASC
542
+ LIMIT $${params.length}`,
543
+ params,
544
+ );
545
+ const applicableRows = scopeKeys
546
+ ? resolveApplicableRecords(result.rows, opts)
547
+ : result.rows;
548
+ return applicableRows
549
+ .sort(sortRecallRows)
550
+ .slice(0, limit);
551
+ }
552
+
553
+ return {
554
+ recall,
555
+ recallViaEvidenceItems,
556
+ recallViaMemoryEmbeddings,
557
+ recallViaLinkedSummaryEmbeddings,
558
+ rankHybridMemoryRows,
559
+ };
242
560
  }
243
561
 
244
562
  module.exports = {
245
563
  recallMemoryRecords,
246
564
  createMemoryRecall,
565
+ rankHybridMemoryRows,
247
566
  };