@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.
- package/.env.example +8 -0
- package/README.md +72 -0
- package/README_CN.md +17 -0
- package/README_TW.md +4 -0
- package/aquifer.config.example.json +19 -0
- package/consumers/cli.js +259 -12
- package/consumers/codex-active-checkpoint.js +186 -0
- package/consumers/codex-current-memory.js +106 -0
- package/consumers/codex-handoff.js +551 -6
- package/consumers/codex.js +209 -25
- package/consumers/mcp.js +144 -6
- package/consumers/shared/config.js +60 -1
- package/consumers/shared/factory.js +10 -3
- package/core/aquifer.js +357 -838
- package/core/backends/capabilities.js +89 -0
- package/core/backends/local.js +430 -0
- package/core/legacy-bootstrap.js +140 -0
- package/core/mcp-manifest.js +66 -2
- package/core/memory-bootstrap.js +20 -8
- package/core/memory-consolidation.js +365 -11
- package/core/memory-promotion.js +157 -26
- package/core/memory-recall.js +341 -22
- package/core/memory-records.js +347 -11
- package/core/memory-serving.js +132 -0
- package/core/postgres-migrations.js +533 -0
- package/core/public-session-filter.js +40 -0
- package/core/recall-runtime.js +115 -0
- package/core/scope-attribution.js +279 -0
- package/core/session-checkpoint-producer.js +412 -0
- package/core/session-checkpoints.js +432 -0
- package/core/session-finalization.js +98 -2
- package/core/storage-checkpoints.js +546 -0
- package/core/storage.js +121 -8
- package/docs/getting-started.md +6 -0
- package/docs/setup.md +66 -3
- package/package.json +8 -4
- package/schema/014-v1-checkpoint-runs.sql +349 -0
- package/schema/015-v1-evidence-items.sql +92 -0
- package/schema/016-v1-evidence-ref-multi-item.sql +19 -0
- package/schema/017-v1-memory-record-embeddings.sql +25 -0
- package/schema/018-v1-finalization-candidate-envelope.sql +39 -0
- package/scripts/codex-checkpoint-commands.js +464 -0
- package/scripts/codex-checkpoint-runtime.js +520 -0
- package/scripts/codex-recovery.js +246 -1
package/core/memory-records.js
CHANGED
|
@@ -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 '
|
|
28
|
-
WHEN '
|
|
29
|
-
WHEN '
|
|
30
|
-
WHEN '
|
|
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
|
|
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
|
+
};
|