@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 crypto = require('crypto');
4
+ const { resolveApplicableRecords } = require('./memory-bootstrap');
4
5
 
5
6
  function requireField(obj, field) {
6
7
  if (!obj || obj[field] === undefined || obj[field] === null || obj[field] === '') {
@@ -16,6 +17,14 @@ function toJsonOrNull(value) {
16
17
  return value === undefined || value === null ? null : JSON.stringify(value);
17
18
  }
18
19
 
20
+ function vecToStr(vec) {
21
+ if (!vec || !Array.isArray(vec) || vec.length === 0) return null;
22
+ for (let i = 0; i < vec.length; i++) {
23
+ if (!Number.isFinite(vec[i])) throw new Error(`Vector contains non-finite value at index ${i}`);
24
+ }
25
+ return `[${vec.join(',')}]`;
26
+ }
27
+
19
28
  function advisoryLockKeys(namespace, value) {
20
29
  const digest = crypto.createHash('sha256').update(`${namespace}:${value}`).digest();
21
30
  return [digest.readInt32BE(0), digest.readInt32BE(4)];
@@ -24,10 +33,10 @@ function advisoryLockKeys(namespace, value) {
24
33
  const BOOTSTRAP_ORDER_SQL = `
25
34
  CASE m.memory_type
26
35
  WHEN 'constraint' THEN 0
27
- WHEN 'preference' THEN 1
28
- WHEN 'state' THEN 2
29
- WHEN 'open_loop' THEN 3
30
- WHEN 'decision' THEN 4
36
+ WHEN 'state' THEN 1
37
+ WHEN 'open_loop' THEN 2
38
+ WHEN 'decision' THEN 3
39
+ WHEN 'preference' THEN 4
31
40
  WHEN 'fact' THEN 5
32
41
  WHEN 'conclusion' THEN 6
33
42
  WHEN 'entity_note' THEN 7
@@ -46,12 +55,132 @@ const BOOTSTRAP_ORDER_SQL = `
46
55
  m.accepted_at DESC NULLS LAST,
47
56
  m.id ASC`;
48
57
 
58
+ const CURRENT_TYPE_PRIORITY = {
59
+ constraint: 0,
60
+ state: 1,
61
+ open_loop: 2,
62
+ decision: 3,
63
+ preference: 4,
64
+ fact: 5,
65
+ conclusion: 6,
66
+ entity_note: 7,
67
+ };
68
+
69
+ const CURRENT_AUTHORITY_PRIORITY = {
70
+ user_explicit: 0,
71
+ executable_evidence: 1,
72
+ manual: 2,
73
+ system: 3,
74
+ verified_summary: 4,
75
+ llm_inference: 5,
76
+ raw_transcript: 6,
77
+ };
78
+
79
+ function parseTime(value) {
80
+ const parsed = Date.parse(value || '');
81
+ return Number.isFinite(parsed) ? parsed : null;
82
+ }
83
+
84
+ function normalizeScopePath(activeScopePath, activeScopeKey) {
85
+ const source = Array.isArray(activeScopePath)
86
+ ? activeScopePath
87
+ : (typeof activeScopePath === 'string' ? activeScopePath.split(',') : null);
88
+ if (source && source.length > 0) {
89
+ const seen = new Set();
90
+ const path = [];
91
+ for (const value of source) {
92
+ const key = String(value || '').trim();
93
+ if (!key || seen.has(key)) continue;
94
+ seen.add(key);
95
+ path.push(key);
96
+ }
97
+ if (path.length > 0) return path;
98
+ }
99
+ if (activeScopeKey) return [String(activeScopeKey).trim()].filter(Boolean);
100
+ return ['global'];
101
+ }
102
+
103
+ function compareRecordIdAsc(a, b) {
104
+ const left = a.memoryId ?? a.memory_id ?? a.id ?? null;
105
+ const right = b.memoryId ?? b.memory_id ?? b.id ?? null;
106
+ const leftNum = Number(left);
107
+ const rightNum = Number(right);
108
+ if (Number.isFinite(leftNum) && Number.isFinite(rightNum) && leftNum !== rightNum) {
109
+ return leftNum - rightNum;
110
+ }
111
+ return String(left ?? '').localeCompare(String(right ?? ''));
112
+ }
113
+
114
+ function normalizeCurrentMemoryRow(row = {}) {
115
+ const { embedding: _embedding, ...publicRow } = row;
116
+ void _embedding;
117
+ const memoryId = row.memoryId ?? row.memory_id ?? row.id ?? null;
118
+ const evidenceRefsValue = row.evidenceRefs ?? row.evidence_refs ?? [];
119
+ const evidenceRefs = Array.isArray(evidenceRefsValue) ? evidenceRefsValue : [];
120
+ return {
121
+ ...publicRow,
122
+ memoryId: memoryId === null ? null : String(memoryId),
123
+ canonicalKey: row.canonicalKey ?? row.canonical_key ?? null,
124
+ memoryType: row.memoryType ?? row.memory_type ?? null,
125
+ scopeKey: row.scopeKey ?? row.scope_key ?? null,
126
+ scopeKind: row.scopeKind ?? row.scope_kind ?? null,
127
+ inheritanceMode: row.inheritanceMode ?? row.inheritance_mode ?? row.scope_inheritance_mode ?? null,
128
+ visibleInBootstrap: row.visibleInBootstrap ?? row.visible_in_bootstrap ?? false,
129
+ visibleInRecall: row.visibleInRecall ?? row.visible_in_recall ?? false,
130
+ acceptedAt: row.acceptedAt ?? row.accepted_at ?? null,
131
+ validFrom: row.validFrom ?? row.valid_from ?? null,
132
+ validTo: row.validTo ?? row.valid_to ?? null,
133
+ staleAfter: row.staleAfter ?? row.stale_after ?? null,
134
+ evidenceRefs,
135
+ evidence_refs: evidenceRefs,
136
+ };
137
+ }
138
+
139
+ function currentScopePriority(record, positions) {
140
+ return positions.get(record.scopeKey ?? record.scope_key) ?? -1;
141
+ }
142
+
143
+ function sortCurrentMemoryRecords(a, b, positions) {
144
+ const leftScope = currentScopePriority(a, positions);
145
+ const rightScope = currentScopePriority(b, positions);
146
+ if (rightScope !== leftScope) return rightScope - leftScope;
147
+
148
+ const leftType = CURRENT_TYPE_PRIORITY[a.memoryType ?? a.memory_type] ?? 99;
149
+ const rightType = CURRENT_TYPE_PRIORITY[b.memoryType ?? b.memory_type] ?? 99;
150
+ if (leftType !== rightType) return leftType - rightType;
151
+
152
+ const leftAuthority = CURRENT_AUTHORITY_PRIORITY[a.authority] ?? 99;
153
+ const rightAuthority = CURRENT_AUTHORITY_PRIORITY[b.authority] ?? 99;
154
+ if (leftAuthority !== rightAuthority) return leftAuthority - rightAuthority;
155
+
156
+ const leftAccepted = parseTime(a.acceptedAt ?? a.accepted_at);
157
+ const rightAccepted = parseTime(b.acceptedAt ?? b.accepted_at);
158
+ if (leftAccepted !== rightAccepted) return (rightAccepted ?? 0) - (leftAccepted ?? 0);
159
+
160
+ return compareRecordIdAsc(a, b);
161
+ }
162
+
163
+ function isCurrentProjectionRow(row, asOf) {
164
+ if ((row.status || 'candidate') !== 'active') return false;
165
+ if (row.visibleInBootstrap !== true && row.visibleInRecall !== true) return false;
166
+ const at = parseTime(asOf);
167
+ if (at === null) return true;
168
+ const validFrom = parseTime(row.validFrom ?? row.valid_from);
169
+ const validTo = parseTime(row.validTo ?? row.valid_to);
170
+ const staleAfter = parseTime(row.staleAfter ?? row.stale_after);
171
+ if (validFrom !== null && validFrom > at) return false;
172
+ if (validTo !== null && validTo <= at) return false;
173
+ if (staleAfter !== null && staleAfter <= at) return false;
174
+ return true;
175
+ }
176
+
49
177
  function createMemoryRecords({ pool, schema, defaultTenantId, inTransaction = false }) {
50
178
  const scopes = `${schema}.scopes`;
51
179
  const versions = `${schema}.versions`;
52
180
  const memories = `${schema}.memory_records`;
53
181
  const factAssertions = `${schema}.fact_assertions_v1`;
54
182
  const evidenceRefs = `${schema}.evidence_refs`;
183
+ const evidenceItems = `${schema}.evidence_items`;
55
184
  const feedback = `${schema}.feedback`;
56
185
  const canTransact = typeof pool.connect === 'function';
57
186
 
@@ -133,12 +262,12 @@ function createMemoryRecords({ pool, schema, defaultTenantId, inTransaction = fa
133
262
  valid_to, stale_after, superseded_by, backing_fact_id, observed_at,
134
263
  revoked_at, superseded_at, version_id, visible_in_bootstrap,
135
264
  visible_in_recall, rank_features, created_by_finalization_id,
136
- created_by_compaction_run_id
265
+ created_by_compaction_run_id, embedding
137
266
  )
138
267
  VALUES (
139
268
  $1,$2,$3,$4,$5,$6,$7,COALESCE($8,''),COALESCE($9::jsonb,'{}'::jsonb),
140
269
  COALESCE($10,'candidate'),COALESCE($11,'llm_inference'),$12,$13,$14,$15,
141
- $16,$17,$18,$19,$20,$21,COALESCE($22,false),COALESCE($23,false),COALESCE($24::jsonb,'{}'::jsonb),$25,$26
270
+ $16,$17,$18,$19,$20,$21,COALESCE($22,false),COALESCE($23,false),COALESCE($24::jsonb,'{}'::jsonb),$25,$26,$27::vector
142
271
  )
143
272
  ON CONFLICT (tenant_id, canonical_key) WHERE status = 'active' DO UPDATE SET
144
273
  scope_id = EXCLUDED.scope_id,
@@ -161,7 +290,8 @@ function createMemoryRecords({ pool, schema, defaultTenantId, inTransaction = fa
161
290
  visible_in_recall = EXCLUDED.visible_in_recall,
162
291
  rank_features = COALESCE(NULLIF(EXCLUDED.rank_features, '{}'::jsonb), ${memories}.rank_features),
163
292
  created_by_finalization_id = COALESCE(${memories}.created_by_finalization_id, EXCLUDED.created_by_finalization_id),
164
- created_by_compaction_run_id = COALESCE(${memories}.created_by_compaction_run_id, EXCLUDED.created_by_compaction_run_id)
293
+ created_by_compaction_run_id = COALESCE(${memories}.created_by_compaction_run_id, EXCLUDED.created_by_compaction_run_id),
294
+ embedding = COALESCE(EXCLUDED.embedding, ${memories}.embedding)
165
295
  RETURNING *`,
166
296
  [
167
297
  tenantId,
@@ -190,6 +320,7 @@ function createMemoryRecords({ pool, schema, defaultTenantId, inTransaction = fa
190
320
  toJson(input.rankFeatures, {}),
191
321
  input.createdByFinalizationId || input.created_by_finalization_id || null,
192
322
  input.createdByCompactionRunId || input.created_by_compaction_run_id || null,
323
+ vecToStr(input.embedding),
193
324
  ]
194
325
  );
195
326
  return result.rows[0] || null;
@@ -276,18 +407,27 @@ function createMemoryRecords({ pool, schema, defaultTenantId, inTransaction = fa
276
407
  requireField(input, 'sourceKind');
277
408
  requireField(input, 'sourceRef');
278
409
  const tenantId = input.tenantId || defaultTenantId;
410
+ const evidenceItemId = input.evidenceItemId || input.evidence_item_id || null;
411
+ const conflictTarget = evidenceItemId
412
+ ? `(tenant_id, owner_kind, owner_id, evidence_item_id, relation_kind)
413
+ WHERE evidence_item_id IS NOT NULL`
414
+ : `(tenant_id, owner_kind, owner_id, source_kind, source_ref, relation_kind)
415
+ WHERE evidence_item_id IS NULL`;
279
416
  const result = await pool.query(
280
417
  `INSERT INTO ${evidenceRefs} (
281
418
  tenant_id, owner_kind, owner_id, source_kind, source_ref,
282
419
  relation_kind, weight, metadata, created_by_finalization_id,
283
- created_by_compaction_run_id
420
+ created_by_compaction_run_id, evidence_item_id
284
421
  )
285
- VALUES ($1,$2,$3,$4,$5,COALESCE($6,'supporting'),COALESCE($7,1.0),COALESCE($8::jsonb,'{}'::jsonb),$9,$10)
286
- ON CONFLICT (tenant_id, owner_kind, owner_id, source_kind, source_ref, relation_kind)
422
+ VALUES ($1,$2,$3,$4,$5,COALESCE($6,'supporting'),COALESCE($7,1.0),COALESCE($8::jsonb,'{}'::jsonb),$9,$10,$11)
423
+ ON CONFLICT ${conflictTarget}
287
424
  DO UPDATE SET weight = EXCLUDED.weight,
425
+ source_kind = EXCLUDED.source_kind,
426
+ source_ref = EXCLUDED.source_ref,
288
427
  metadata = COALESCE(NULLIF(EXCLUDED.metadata, '{}'::jsonb), ${evidenceRefs}.metadata),
289
428
  created_by_finalization_id = COALESCE(${evidenceRefs}.created_by_finalization_id, EXCLUDED.created_by_finalization_id),
290
- created_by_compaction_run_id = COALESCE(${evidenceRefs}.created_by_compaction_run_id, EXCLUDED.created_by_compaction_run_id)
429
+ created_by_compaction_run_id = COALESCE(${evidenceRefs}.created_by_compaction_run_id, EXCLUDED.created_by_compaction_run_id),
430
+ evidence_item_id = COALESCE(${evidenceRefs}.evidence_item_id, EXCLUDED.evidence_item_id)
291
431
  RETURNING *`,
292
432
  [
293
433
  tenantId,
@@ -300,11 +440,55 @@ function createMemoryRecords({ pool, schema, defaultTenantId, inTransaction = fa
300
440
  toJson(input.metadata, {}),
301
441
  input.createdByFinalizationId || input.created_by_finalization_id || null,
302
442
  input.createdByCompactionRunId || input.created_by_compaction_run_id || null,
443
+ evidenceItemId,
303
444
  ]
304
445
  );
305
446
  return result.rows[0] || null;
306
447
  }
307
448
 
449
+ async function upsertEvidenceItem(input = {}) {
450
+ requireField(input, 'sourceKind');
451
+ requireField(input, 'sourceRef');
452
+ requireField(input, 'excerptText');
453
+ const tenantId = input.tenantId || defaultTenantId;
454
+ const excerptText = String(input.excerptText || input.excerpt_text || '').trim();
455
+ const excerptHash = input.excerptHash || input.excerpt_hash || crypto
456
+ .createHash('sha256')
457
+ .update(excerptText)
458
+ .digest('hex');
459
+ const result = await pool.query(
460
+ `INSERT INTO ${evidenceItems} (
461
+ tenant_id, source_kind, source_ref, session_row_id, turn_embedding_id,
462
+ summary_row_id, created_by_finalization_id, excerpt_text, excerpt_hash,
463
+ embedding, metadata
464
+ )
465
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10::vector,COALESCE($11::jsonb,'{}'::jsonb))
466
+ ON CONFLICT (tenant_id, source_kind, source_ref, excerpt_hash)
467
+ DO UPDATE SET
468
+ session_row_id = COALESCE(${evidenceItems}.session_row_id, EXCLUDED.session_row_id),
469
+ turn_embedding_id = COALESCE(${evidenceItems}.turn_embedding_id, EXCLUDED.turn_embedding_id),
470
+ summary_row_id = COALESCE(${evidenceItems}.summary_row_id, EXCLUDED.summary_row_id),
471
+ created_by_finalization_id = COALESCE(${evidenceItems}.created_by_finalization_id, EXCLUDED.created_by_finalization_id),
472
+ embedding = COALESCE(${evidenceItems}.embedding, EXCLUDED.embedding),
473
+ metadata = COALESCE(NULLIF(EXCLUDED.metadata, '{}'::jsonb), ${evidenceItems}.metadata)
474
+ RETURNING *`,
475
+ [
476
+ tenantId,
477
+ input.sourceKind || input.source_kind,
478
+ input.sourceRef || input.source_ref,
479
+ input.sessionRowId || input.session_row_id || null,
480
+ input.turnEmbeddingId || input.turn_embedding_id || null,
481
+ input.summaryRowId || input.summary_row_id || null,
482
+ input.createdByFinalizationId || input.created_by_finalization_id || null,
483
+ excerptText,
484
+ excerptHash,
485
+ vecToStr(input.embedding),
486
+ toJson(input.metadata, {}),
487
+ ],
488
+ );
489
+ return result.rows[0] || null;
490
+ }
491
+
308
492
  async function recordFeedback(input = {}) {
309
493
  requireField(input, 'targetKind');
310
494
  requireField(input, 'targetId');
@@ -518,6 +702,9 @@ function createMemoryRecords({ pool, schema, defaultTenantId, inTransaction = fa
518
702
  params.push(input.visibleInRecall === true);
519
703
  where.push(`m.visible_in_recall = $${params.length}`);
520
704
  }
705
+ if (input.withoutEmbedding === true) {
706
+ where.push(`m.embedding IS NULL`);
707
+ }
521
708
  params.push(Math.max(1, Math.min(200, input.limit || 50)));
522
709
  const orderBy = input.visibleInBootstrap === true
523
710
  ? BOOTSTRAP_ORDER_SQL
@@ -534,6 +721,151 @@ function createMemoryRecords({ pool, schema, defaultTenantId, inTransaction = fa
534
721
  return result.rows;
535
722
  }
536
723
 
724
+ async function updateMemoryEmbedding(input = {}) {
725
+ requireField(input, 'memoryId');
726
+ const tenantId = input.tenantId || defaultTenantId;
727
+ const embedding = vecToStr(input.embedding);
728
+ if (!embedding) throw new Error('embedding is required');
729
+ const result = await pool.query(
730
+ `UPDATE ${memories}
731
+ SET embedding = $3::vector,
732
+ updated_at = now()
733
+ WHERE tenant_id = $1 AND id = $2
734
+ AND embedding IS NULL
735
+ RETURNING *`,
736
+ [
737
+ tenantId,
738
+ input.memoryId,
739
+ embedding,
740
+ ]
741
+ );
742
+ if (result.rows[0]) {
743
+ return {
744
+ status: 'updated',
745
+ updated: true,
746
+ skipped: false,
747
+ memory: result.rows[0],
748
+ };
749
+ }
750
+ const existing = await pool.query(
751
+ `SELECT * FROM ${memories}
752
+ WHERE tenant_id = $1 AND id = $2
753
+ LIMIT 1`,
754
+ [tenantId, input.memoryId]
755
+ );
756
+ if (existing.rows[0]) {
757
+ return {
758
+ status: 'skipped_existing_embedding',
759
+ updated: false,
760
+ skipped: true,
761
+ memory: existing.rows[0],
762
+ };
763
+ }
764
+ return {
765
+ status: 'missing',
766
+ updated: false,
767
+ skipped: true,
768
+ memory: null,
769
+ };
770
+ }
771
+
772
+ async function currentProjection(input = {}) {
773
+ const tenantId = input.tenantId || defaultTenantId;
774
+ let activeScopePath = normalizeScopePath(input.activeScopePath, input.activeScopeKey);
775
+ let activeScopeKey = input.activeScopeKey || activeScopePath[activeScopePath.length - 1] || null;
776
+ if (input.scopeId && !input.activeScopeKey && !input.activeScopePath) {
777
+ const scopeResult = await pool.query(
778
+ `SELECT scope_key FROM ${scopes} WHERE tenant_id = $1 AND id = $2 LIMIT 1`,
779
+ [tenantId, input.scopeId],
780
+ );
781
+ const scopedKey = scopeResult.rows[0]?.scope_key || null;
782
+ if (scopedKey) {
783
+ activeScopePath = [scopedKey];
784
+ activeScopeKey = scopedKey;
785
+ }
786
+ }
787
+ const limit = Math.max(1, Math.min(100, input.limit || 50));
788
+ const fetchLimit = Math.max(limit + 1, Math.min(200, Math.max(limit * 4, 40)));
789
+ const asOf = input.asOf || new Date().toISOString();
790
+ const params = [tenantId, activeScopePath, asOf];
791
+ const where = [
792
+ `m.tenant_id = $1`,
793
+ `m.status = 'active'`,
794
+ `s.scope_key = ANY($2::text[])`,
795
+ `(m.visible_in_bootstrap = true OR m.visible_in_recall = true)`,
796
+ `(m.valid_from IS NULL OR m.valid_from <= $3::timestamptz)`,
797
+ `(m.valid_to IS NULL OR m.valid_to > $3::timestamptz)`,
798
+ `(m.stale_after IS NULL OR m.stale_after > $3::timestamptz)`,
799
+ ];
800
+
801
+ if (input.scopeId) {
802
+ params.push(input.scopeId);
803
+ where.push(`m.scope_id = $${params.length}`);
804
+ }
805
+
806
+ params.push(fetchLimit);
807
+ const limitParam = `$${params.length}`;
808
+ const evidenceRefsSelect = input.includeEvidenceRefs === true
809
+ ? `COALESCE((
810
+ SELECT jsonb_agg(
811
+ jsonb_build_object(
812
+ 'id', e.id,
813
+ 'sourceKind', e.source_kind,
814
+ 'sourceRef', e.source_ref,
815
+ 'relationKind', e.relation_kind,
816
+ 'weight', e.weight,
817
+ 'metadata', e.metadata
818
+ )
819
+ ORDER BY e.id ASC
820
+ )
821
+ FROM ${evidenceRefs} e
822
+ WHERE e.tenant_id = m.tenant_id
823
+ AND e.owner_kind = 'memory_record'
824
+ AND e.owner_id = m.id
825
+ ), '[]'::jsonb)`
826
+ : `'[]'::jsonb`;
827
+
828
+ const result = await pool.query(
829
+ `SELECT
830
+ m.*,
831
+ s.scope_kind,
832
+ s.scope_key,
833
+ s.inheritance_mode AS scope_inheritance_mode,
834
+ ${evidenceRefsSelect} AS evidence_refs
835
+ FROM ${memories} m
836
+ JOIN ${scopes} s ON s.id = m.scope_id
837
+ WHERE ${where.join(' AND ')}
838
+ ORDER BY array_position($2::text[], s.scope_key) DESC NULLS LAST,
839
+ ${BOOTSTRAP_ORDER_SQL}
840
+ LIMIT ${limitParam}`,
841
+ params,
842
+ );
843
+
844
+ const positions = new Map(activeScopePath.map((key, index) => [key, index]));
845
+ const applicable = resolveApplicableRecords(
846
+ result.rows
847
+ .map(normalizeCurrentMemoryRow)
848
+ .filter(row => isCurrentProjectionRow(row, asOf)),
849
+ { activeScopeKey, activeScopePath },
850
+ ).sort((left, right) => sortCurrentMemoryRecords(left, right, positions));
851
+
852
+ const selected = applicable.slice(0, limit);
853
+ const truncated = applicable.length > limit;
854
+ return {
855
+ memories: selected,
856
+ meta: {
857
+ source: 'memory_records',
858
+ servingContract: 'current_memory_v1',
859
+ count: selected.length,
860
+ activeScopeKey,
861
+ activeScopePath,
862
+ asOf,
863
+ truncated,
864
+ degraded: truncated,
865
+ },
866
+ };
867
+ }
868
+
537
869
  async function withTransaction(fn) {
538
870
  if (inTransaction) {
539
871
  return fn(api, { transactional: true });
@@ -563,6 +895,7 @@ function createMemoryRecords({ pool, schema, defaultTenantId, inTransaction = fa
563
895
  createVersion,
564
896
  upsertMemory,
565
897
  upsertFactAssertion,
898
+ upsertEvidenceItem,
566
899
  linkEvidence,
567
900
  recordFeedback,
568
901
  findActiveByCanonicalKey,
@@ -570,8 +903,11 @@ function createMemoryRecords({ pool, schema, defaultTenantId, inTransaction = fa
570
903
  lockCanonicalKey,
571
904
  updateMemoryStatus,
572
905
  updateMemoryStatusIfCurrent,
906
+ updateMemoryEmbedding,
573
907
  updateFactAssertionStatus,
574
908
  listActive,
909
+ currentProjection,
910
+ normalizeCurrentMemoryRow,
575
911
  withTransaction,
576
912
  };
577
913
 
@@ -0,0 +1,132 @@
1
+ 'use strict';
2
+
3
+ function splitScopePath(value) {
4
+ if (Array.isArray(value)) return value.map(v => String(v).trim()).filter(Boolean);
5
+ if (typeof value !== 'string') return null;
6
+ const parts = value.split(',').map(v => v.trim()).filter(Boolean);
7
+ return parts.length > 0 ? parts : null;
8
+ }
9
+
10
+ function hasEvidenceBoundary(opts = {}) {
11
+ return Boolean(
12
+ opts.agentId
13
+ || (Array.isArray(opts.agentIds) && opts.agentIds.length > 0)
14
+ || opts.source
15
+ || opts.dateFrom
16
+ || opts.dateTo
17
+ || opts.host
18
+ || opts.sessionId
19
+ || opts.allowUnsafeDebug === true
20
+ || opts.unsafeDebug === true
21
+ );
22
+ }
23
+
24
+ function assertCuratedRecallOpts(opts = {}) {
25
+ const unsupported = [];
26
+ for (const key of ['agentId', 'agentIds', 'source', 'dateFrom', 'dateTo', 'entities', 'entityMode', 'weights', 'rerank', 'allowUnsafeDebug', 'unsafeDebug']) {
27
+ if (opts[key] !== undefined && opts[key] !== null) unsupported.push(key);
28
+ }
29
+ if (unsupported.length > 0) {
30
+ throw new Error(`curated memory_recall does not support legacy filters: ${unsupported.join(', ')}. Use activeScopeKey/activeScopePath or historical_recall.`);
31
+ }
32
+ }
33
+
34
+ function assertCuratedBootstrapOpts(opts = {}) {
35
+ const unsupported = [];
36
+ for (const key of ['agentId', 'source', 'lookbackDays', 'dateFrom', 'dateTo']) {
37
+ if (opts[key] !== undefined && opts[key] !== null) unsupported.push(key);
38
+ }
39
+ if (unsupported.length > 0) {
40
+ throw new Error(`curated session_bootstrap does not support legacy filters: ${unsupported.join(', ')}. Use activeScopeKey/activeScopePath.`);
41
+ }
42
+ }
43
+
44
+ function curatedRecallTitle(row = {}) {
45
+ const title = row.title || row.summary || row.canonical_key || row.canonicalKey || row.memory_type || row.memoryType || 'memory';
46
+ return String(title).trim();
47
+ }
48
+
49
+ function curatedRecallSummary(row = {}) {
50
+ const summary = row.summary || row.title || row.canonical_key || row.canonicalKey || '';
51
+ return String(summary).trim();
52
+ }
53
+
54
+ function normalizeCuratedRecallRow(row = {}) {
55
+ const { embedding: _embedding, ...publicRow } = row;
56
+ void _embedding;
57
+ const memoryId = row.memoryId || row.memory_id || row.id || null;
58
+ const canonicalKey = row.canonicalKey || row.canonical_key || null;
59
+ const memoryType = row.memoryType || row.memory_type || null;
60
+ const scopeKey = row.scopeKey || row.scope_key || null;
61
+ const scopeKind = row.scopeKind || row.scope_kind || null;
62
+ const summaryText = curatedRecallSummary(row) || null;
63
+ const title = curatedRecallTitle(row) || null;
64
+ const scoreValue = row.recall_score ?? row.score ?? row.lexical_rank ?? null;
65
+ const score = scoreValue === null ? null : Number(scoreValue);
66
+ return {
67
+ ...publicRow,
68
+ memoryId: memoryId === null ? null : String(memoryId),
69
+ canonicalKey,
70
+ memoryType,
71
+ scopeKey,
72
+ scopeKind,
73
+ title,
74
+ summaryText,
75
+ structuredSummary: {
76
+ title,
77
+ overview: summaryText,
78
+ },
79
+ startedAt: row.acceptedAt || row.accepted_at || row.observedAt || row.observed_at || null,
80
+ score: Number.isFinite(score) ? score : null,
81
+ feedbackTarget: {
82
+ kind: 'memory_feedback',
83
+ memoryId: memoryId === null ? null : String(memoryId),
84
+ canonicalKey,
85
+ },
86
+ };
87
+ }
88
+
89
+ function createMemoryServingRuntime(memoryCfg = {}, env = process.env) {
90
+ const servingMode = memoryCfg.servingMode || env.AQUIFER_MEMORY_SERVING_MODE || 'legacy';
91
+ const defaultActiveScopeKey = memoryCfg.activeScopeKey || null;
92
+ const defaultActiveScopePath = splitScopePath(memoryCfg.activeScopePath || null);
93
+
94
+ function resolveMode(opts = {}) {
95
+ const mode = opts.memoryMode || opts.servingMode || servingMode;
96
+ if (mode === 'legacy' || mode === 'evidence') return 'legacy';
97
+ if (mode === 'curated') return 'curated';
98
+ throw new Error(`Invalid memory serving mode: "${mode}". Must be one of: legacy, curated`);
99
+ }
100
+
101
+ function withDefaultScope(opts = {}) {
102
+ const next = { ...opts };
103
+ if (!next.activeScopePath && defaultActiveScopePath) next.activeScopePath = defaultActiveScopePath;
104
+ if (Array.isArray(next.activeScopePath) && next.activeScopePath.length > 0) {
105
+ if (!next.activeScopeKey) next.activeScopeKey = next.activeScopePath[next.activeScopePath.length - 1];
106
+ return next;
107
+ }
108
+ if (!next.activeScopeKey && defaultActiveScopeKey) next.activeScopeKey = defaultActiveScopeKey;
109
+ return next;
110
+ }
111
+
112
+ return {
113
+ assertCuratedBootstrapOpts,
114
+ assertCuratedRecallOpts,
115
+ defaultActiveScopeKey,
116
+ defaultActiveScopePath,
117
+ hasEvidenceBoundary,
118
+ normalizeCuratedRecallRow,
119
+ resolveMode,
120
+ servingMode,
121
+ withDefaultScope,
122
+ };
123
+ }
124
+
125
+ module.exports = {
126
+ assertCuratedBootstrapOpts,
127
+ assertCuratedRecallOpts,
128
+ createMemoryServingRuntime,
129
+ hasEvidenceBoundary,
130
+ normalizeCuratedRecallRow,
131
+ splitScopePath,
132
+ };