@shadowforge0/aquifer-memory 1.8.1 → 1.9.1
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 +1 -0
- package/README.md +82 -26
- package/README_CN.md +33 -23
- package/README_TW.md +25 -24
- package/aquifer.config.example.json +2 -1
- package/consumers/cli.js +587 -33
- package/consumers/codex-active-checkpoint.js +3 -1
- package/consumers/codex-current-memory.js +10 -6
- package/consumers/codex.js +6 -3
- package/consumers/default/daily-entries.js +2 -2
- package/consumers/default/index.js +40 -30
- package/consumers/default/prompts/summary.js +2 -2
- package/consumers/mcp.js +56 -46
- package/consumers/openclaw-ext/index.js +65 -7
- package/consumers/openclaw-ext/openclaw.plugin.json +1 -1
- package/consumers/openclaw-ext/package.json +1 -1
- package/consumers/openclaw-install.js +326 -0
- package/consumers/openclaw-plugin.js +105 -24
- package/consumers/shared/compat-recall.js +101 -0
- package/consumers/shared/config.js +2 -0
- package/consumers/shared/openclaw-product-tools.js +130 -0
- package/consumers/shared/recall-format.js +2 -2
- package/core/aquifer.js +553 -41
- package/core/backends/local.js +169 -1
- package/core/doctor.js +924 -0
- package/core/finalization-inspector.js +164 -0
- package/core/finalization-review.js +88 -42
- package/core/interface.js +629 -0
- package/core/mcp-manifest.js +11 -3
- package/core/memory-bootstrap.js +25 -27
- package/core/memory-consolidation.js +564 -42
- package/core/memory-explain.js +593 -0
- package/core/memory-promotion.js +392 -55
- package/core/memory-recall.js +75 -71
- package/core/memory-records.js +107 -108
- package/core/memory-review.js +891 -0
- package/core/memory-serving.js +61 -4
- package/core/memory-type-policy.js +298 -0
- package/core/operator-observability.js +249 -0
- package/core/postgres-migrations.js +22 -0
- package/core/session-checkpoint-producer.js +3 -1
- package/core/session-checkpoints.js +1 -1
- package/core/session-finalization.js +78 -3
- package/core/storage.js +124 -8
- package/docs/getting-started.md +50 -4
- package/docs/setup.md +163 -24
- package/package.json +5 -4
- package/schema/004-completion.sql +4 -4
- package/schema/010-v1-finalization-review.sql +72 -0
- package/schema/019-v1-memory-review-resolutions.sql +53 -0
- package/schema/020-v1-assistant-shaping-memory.sql +30 -0
- package/scripts/backfill-canonical-key.js +1 -1
- package/scripts/codex-checkpoint-commands.js +28 -0
- package/scripts/codex-checkpoint-runtime.js +109 -0
- package/scripts/codex-recovery.js +16 -4
- package/scripts/diagnose-fts-zh.js +1 -1
- package/scripts/extract-insights-from-recent-sessions.js +4 -4
package/core/memory-recall.js
CHANGED
|
@@ -1,27 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { resolveApplicableRecords } = require('./memory-bootstrap');
|
|
4
|
+
const { assertAllowedScopeRequest } = require('./memory-serving');
|
|
4
5
|
const { hybridRank } = require('./hybrid-rank');
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
decision: 50,
|
|
12
|
-
fact: 40,
|
|
13
|
-
conclusion: 30,
|
|
14
|
-
entity_note: 20,
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const FEEDBACK_WEIGHT = {
|
|
18
|
-
helpful: 0.15,
|
|
19
|
-
confirm: 0.10,
|
|
20
|
-
irrelevant: -0.20,
|
|
21
|
-
scope_mismatch: -0.25,
|
|
22
|
-
stale: -0.30,
|
|
23
|
-
incorrect: -0.50,
|
|
24
|
-
};
|
|
6
|
+
const {
|
|
7
|
+
feedbackWeight,
|
|
8
|
+
feedbackWeightSql,
|
|
9
|
+
memoryTypeRecallRank,
|
|
10
|
+
memoryTypeRecallRankSql,
|
|
11
|
+
} = require('./memory-type-policy');
|
|
25
12
|
|
|
26
13
|
const RETRIEVAL_TYPE_BOOST = 0.05;
|
|
27
14
|
const SIGNAL_PRIORITY = {
|
|
@@ -30,34 +17,14 @@ const SIGNAL_PRIORITY = {
|
|
|
30
17
|
memory_row: 3,
|
|
31
18
|
};
|
|
32
19
|
|
|
33
|
-
const TYPE_RANK_SQL =
|
|
34
|
-
CASE m.memory_type
|
|
35
|
-
WHEN 'constraint' THEN 0.80
|
|
36
|
-
WHEN 'preference' THEN 0.70
|
|
37
|
-
WHEN 'state' THEN 0.60
|
|
38
|
-
WHEN 'open_loop' THEN 0.55
|
|
39
|
-
WHEN 'decision' THEN 0.50
|
|
40
|
-
WHEN 'fact' THEN 0.40
|
|
41
|
-
WHEN 'conclusion' THEN 0.30
|
|
42
|
-
WHEN 'entity_note' THEN 0.20
|
|
43
|
-
ELSE 0
|
|
44
|
-
END`;
|
|
45
|
-
|
|
20
|
+
const TYPE_RANK_SQL = memoryTypeRecallRankSql('m.memory_type');
|
|
46
21
|
const TYPE_BOOST_SQL = `(${TYPE_RANK_SQL}) * ${RETRIEVAL_TYPE_BOOST}`;
|
|
47
22
|
|
|
48
23
|
function feedbackScoreSql(schema) {
|
|
49
24
|
return `
|
|
50
25
|
COALESCE((
|
|
51
26
|
SELECT SUM(
|
|
52
|
-
|
|
53
|
-
WHEN 'helpful' THEN 0.15
|
|
54
|
-
WHEN 'confirm' THEN 0.10
|
|
55
|
-
WHEN 'irrelevant' THEN -0.20
|
|
56
|
-
WHEN 'scope_mismatch' THEN -0.25
|
|
57
|
-
WHEN 'stale' THEN -0.30
|
|
58
|
-
WHEN 'incorrect' THEN -0.50
|
|
59
|
-
ELSE 0
|
|
60
|
-
END
|
|
27
|
+
${feedbackWeightSql('f.feedback_type')}
|
|
61
28
|
)
|
|
62
29
|
FROM ${schema}.feedback f
|
|
63
30
|
WHERE f.tenant_id = $1
|
|
@@ -104,6 +71,7 @@ function isActiveVisible(record, opts = {}) {
|
|
|
104
71
|
}
|
|
105
72
|
|
|
106
73
|
function activeScopeKeys(opts = {}) {
|
|
74
|
+
assertAllowedScopeRequest(opts);
|
|
107
75
|
if (Array.isArray(opts.activeScopePath) && opts.activeScopePath.length > 0) {
|
|
108
76
|
return opts.activeScopePath.map(value => String(value)).filter(Boolean);
|
|
109
77
|
}
|
|
@@ -221,7 +189,7 @@ function feedbackScore(record, feedbackEvents = []) {
|
|
|
221
189
|
const targetId = String(event.targetId || event.target_id || '');
|
|
222
190
|
if (targetId !== id) continue;
|
|
223
191
|
const type = event.feedbackType || event.feedback_type || event.verdict;
|
|
224
|
-
score +=
|
|
192
|
+
score += feedbackWeight(type);
|
|
225
193
|
}
|
|
226
194
|
return score;
|
|
227
195
|
}
|
|
@@ -258,7 +226,7 @@ function recallMemoryRecords(records = [], query, opts = {}) {
|
|
|
258
226
|
.map(record => {
|
|
259
227
|
const haystack = textOf(record).toLowerCase();
|
|
260
228
|
const lexical = lexicalScore(haystack, q);
|
|
261
|
-
const typeRank = ((
|
|
229
|
+
const typeRank = (memoryTypeRecallRank(record.memoryType || record.memory_type) / 100) * RETRIEVAL_TYPE_BOOST;
|
|
262
230
|
const feedback = feedbackScore(record, feedbackEvents);
|
|
263
231
|
return {
|
|
264
232
|
...record,
|
|
@@ -274,6 +242,26 @@ function recallMemoryRecords(records = [], query, opts = {}) {
|
|
|
274
242
|
}
|
|
275
243
|
|
|
276
244
|
function createMemoryRecall({ pool, schema, defaultTenantId }) {
|
|
245
|
+
async function withResolvedScopeKey(opts = {}) {
|
|
246
|
+
const next = { ...opts };
|
|
247
|
+
if (!next.scopeId) return next;
|
|
248
|
+
const tenantId = next.tenantId || defaultTenantId;
|
|
249
|
+
const result = await pool.query(
|
|
250
|
+
`SELECT scope_key FROM ${schema}.scopes WHERE tenant_id = $1 AND id = $2 LIMIT 1`,
|
|
251
|
+
[tenantId, next.scopeId],
|
|
252
|
+
);
|
|
253
|
+
const resolvedScopeKey = result.rows?.[0]?.scope_key || null;
|
|
254
|
+
if (resolvedScopeKey) {
|
|
255
|
+
next.resolvedScopeKey = resolvedScopeKey;
|
|
256
|
+
if (!next.activeScopeKey && !next.activeScopePath) {
|
|
257
|
+
next.activeScopePath = [resolvedScopeKey];
|
|
258
|
+
next.activeScopeKey = resolvedScopeKey;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
assertAllowedScopeRequest(next);
|
|
262
|
+
return next;
|
|
263
|
+
}
|
|
264
|
+
|
|
277
265
|
function applyCurrentMemoryFilters(where, params, opts = {}) {
|
|
278
266
|
const scopeKeys = activeScopeKeys(opts);
|
|
279
267
|
if (opts.scopeId) {
|
|
@@ -295,18 +283,22 @@ function createMemoryRecall({ pool, schema, defaultTenantId }) {
|
|
|
295
283
|
}
|
|
296
284
|
|
|
297
285
|
async function recall(query, opts = {}) {
|
|
286
|
+
const scopedOpts = await withResolvedScopeKey(opts);
|
|
298
287
|
const q = String(query || '').trim();
|
|
299
288
|
if (!q) throw new Error('memory.recall(query): query must be a non-empty string');
|
|
300
|
-
const tenantId =
|
|
301
|
-
const limit = Math.max(1, Math.min(50,
|
|
302
|
-
const cfg = (
|
|
303
|
-
const scopeKeys = activeScopeKeys(
|
|
289
|
+
const tenantId = scopedOpts.tenantId || defaultTenantId;
|
|
290
|
+
const limit = Math.max(1, Math.min(50, scopedOpts.limit || 10));
|
|
291
|
+
const cfg = (scopedOpts.ftsConfig === 'zhcfg' || scopedOpts.ftsConfig === 'simple') ? scopedOpts.ftsConfig : 'simple';
|
|
292
|
+
const scopeKeys = activeScopeKeys(scopedOpts);
|
|
304
293
|
const fetchLimit = Math.max(limit, Math.min(200, scopeKeys ? limit * 4 : limit));
|
|
305
294
|
const feedbackScoreExpr = feedbackScoreSql(schema);
|
|
306
295
|
const params = [tenantId, q];
|
|
307
296
|
const where = [
|
|
308
297
|
`m.tenant_id = $1`,
|
|
309
298
|
`m.status = 'active'`,
|
|
299
|
+
`m.revoked_at IS NULL`,
|
|
300
|
+
`m.superseded_at IS NULL`,
|
|
301
|
+
`m.superseded_by IS NULL`,
|
|
310
302
|
`m.visible_in_recall = true`,
|
|
311
303
|
`(m.search_tsv @@ plainto_tsquery('${cfg}', $2)
|
|
312
304
|
OR m.title ILIKE '%' || $2 || '%'
|
|
@@ -314,7 +306,7 @@ function createMemoryRecall({ pool, schema, defaultTenantId }) {
|
|
|
314
306
|
OR m.context_key ILIKE '%' || $2 || '%'
|
|
315
307
|
OR m.topic_key ILIKE '%' || $2 || '%')`,
|
|
316
308
|
];
|
|
317
|
-
applyCurrentMemoryFilters(where, params,
|
|
309
|
+
applyCurrentMemoryFilters(where, params, scopedOpts);
|
|
318
310
|
params.push(fetchLimit);
|
|
319
311
|
const result = await pool.query(
|
|
320
312
|
`SELECT
|
|
@@ -329,7 +321,7 @@ function createMemoryRecall({ pool, schema, defaultTenantId }) {
|
|
|
329
321
|
+ ${TYPE_BOOST_SQL}
|
|
330
322
|
+ ${feedbackScoreExpr} AS recall_score
|
|
331
323
|
FROM ${schema}.memory_records m
|
|
332
|
-
JOIN ${schema}.scopes s ON s.id = m.scope_id
|
|
324
|
+
JOIN ${schema}.scopes s ON s.tenant_id = m.tenant_id AND s.id = m.scope_id
|
|
333
325
|
WHERE ${where.join(' AND ')}
|
|
334
326
|
ORDER BY
|
|
335
327
|
title_match DESC,
|
|
@@ -340,7 +332,7 @@ function createMemoryRecall({ pool, schema, defaultTenantId }) {
|
|
|
340
332
|
params
|
|
341
333
|
);
|
|
342
334
|
const applicableRows = scopeKeys
|
|
343
|
-
? resolveApplicableRecords(result.rows,
|
|
335
|
+
? resolveApplicableRecords(result.rows, scopedOpts)
|
|
344
336
|
: result.rows;
|
|
345
337
|
return applicableRows
|
|
346
338
|
.sort(sortRecallRows)
|
|
@@ -348,21 +340,25 @@ function createMemoryRecall({ pool, schema, defaultTenantId }) {
|
|
|
348
340
|
}
|
|
349
341
|
|
|
350
342
|
async function recallViaEvidenceItems(query, opts = {}) {
|
|
343
|
+
const scopedOpts = await withResolvedScopeKey(opts);
|
|
351
344
|
const q = String(query || '').trim();
|
|
352
345
|
if (!q) throw new Error('memory.recall(query): query must be a non-empty string');
|
|
353
|
-
const tenantId =
|
|
354
|
-
const limit = Math.max(1, Math.min(50,
|
|
355
|
-
const scopeKeys = activeScopeKeys(
|
|
346
|
+
const tenantId = scopedOpts.tenantId || defaultTenantId;
|
|
347
|
+
const limit = Math.max(1, Math.min(50, scopedOpts.limit || 10));
|
|
348
|
+
const scopeKeys = activeScopeKeys(scopedOpts);
|
|
356
349
|
const fetchLimit = Math.max(limit, Math.min(200, scopeKeys ? limit * 4 : limit));
|
|
357
350
|
const feedbackScoreExpr = feedbackScoreSql(schema);
|
|
358
351
|
const params = [tenantId, q];
|
|
359
352
|
const where = [
|
|
360
353
|
`m.tenant_id = $1`,
|
|
361
354
|
`m.status = 'active'`,
|
|
355
|
+
`m.revoked_at IS NULL`,
|
|
356
|
+
`m.superseded_at IS NULL`,
|
|
357
|
+
`m.superseded_by IS NULL`,
|
|
362
358
|
`m.visible_in_recall = true`,
|
|
363
359
|
];
|
|
364
|
-
applyCurrentMemoryFilters(where, params,
|
|
365
|
-
const queryVec = vecToStr(
|
|
360
|
+
applyCurrentMemoryFilters(where, params, scopedOpts);
|
|
361
|
+
const queryVec = vecToStr(scopedOpts.queryVec);
|
|
366
362
|
let vectorScoreExpr = '0';
|
|
367
363
|
let evidencePredicate = `(ei.excerpt_text ILIKE '%' || $2 || '%'
|
|
368
364
|
OR ei.search_tsv @@ plainto_tsquery('simple', $2))`;
|
|
@@ -370,7 +366,7 @@ function createMemoryRecall({ pool, schema, defaultTenantId }) {
|
|
|
370
366
|
params.push(queryVec);
|
|
371
367
|
const vecPos = params.length;
|
|
372
368
|
vectorScoreExpr = `COALESCE(1.0 - (ei.embedding <=> $${vecPos}::vector), 0)`;
|
|
373
|
-
evidencePredicate =
|
|
369
|
+
evidencePredicate = scopedOpts.vectorOnly === true
|
|
374
370
|
? `ei.embedding IS NOT NULL`
|
|
375
371
|
: `(${evidencePredicate} OR ei.embedding IS NOT NULL)`;
|
|
376
372
|
}
|
|
@@ -379,7 +375,7 @@ function createMemoryRecall({ pool, schema, defaultTenantId }) {
|
|
|
379
375
|
`WITH eligible_memories AS (
|
|
380
376
|
SELECT m.*, s.scope_kind, s.scope_key, s.inheritance_mode AS scope_inheritance_mode
|
|
381
377
|
FROM ${schema}.memory_records m
|
|
382
|
-
JOIN ${schema}.scopes s ON s.id = m.scope_id
|
|
378
|
+
JOIN ${schema}.scopes s ON s.tenant_id = m.tenant_id AND s.id = m.scope_id
|
|
383
379
|
WHERE ${where.join(' AND ')}
|
|
384
380
|
),
|
|
385
381
|
evidence_hits AS (
|
|
@@ -425,7 +421,7 @@ function createMemoryRecall({ pool, schema, defaultTenantId }) {
|
|
|
425
421
|
params,
|
|
426
422
|
);
|
|
427
423
|
const applicableRows = scopeKeys
|
|
428
|
-
? resolveApplicableRecords(result.rows,
|
|
424
|
+
? resolveApplicableRecords(result.rows, scopedOpts)
|
|
429
425
|
: result.rows;
|
|
430
426
|
return applicableRows
|
|
431
427
|
.sort(sortRecallRows)
|
|
@@ -433,21 +429,25 @@ function createMemoryRecall({ pool, schema, defaultTenantId }) {
|
|
|
433
429
|
}
|
|
434
430
|
|
|
435
431
|
async function recallViaMemoryEmbeddings(queryVec, opts = {}) {
|
|
432
|
+
const scopedOpts = await withResolvedScopeKey(opts);
|
|
436
433
|
const vector = vecToStr(queryVec);
|
|
437
434
|
if (!vector) return [];
|
|
438
|
-
const tenantId =
|
|
439
|
-
const limit = Math.max(1, Math.min(50,
|
|
440
|
-
const scopeKeys = activeScopeKeys(
|
|
435
|
+
const tenantId = scopedOpts.tenantId || defaultTenantId;
|
|
436
|
+
const limit = Math.max(1, Math.min(50, scopedOpts.limit || 10));
|
|
437
|
+
const scopeKeys = activeScopeKeys(scopedOpts);
|
|
441
438
|
const fetchLimit = Math.max(limit, Math.min(200, scopeKeys ? limit * 4 : limit));
|
|
442
439
|
const feedbackScoreExpr = feedbackScoreSql(schema);
|
|
443
440
|
const params = [tenantId, vector];
|
|
444
441
|
const where = [
|
|
445
442
|
`m.tenant_id = $1`,
|
|
446
443
|
`m.status = 'active'`,
|
|
444
|
+
`m.revoked_at IS NULL`,
|
|
445
|
+
`m.superseded_at IS NULL`,
|
|
446
|
+
`m.superseded_by IS NULL`,
|
|
447
447
|
`m.visible_in_recall = true`,
|
|
448
448
|
`m.embedding IS NOT NULL`,
|
|
449
449
|
];
|
|
450
|
-
applyCurrentMemoryFilters(where, params,
|
|
450
|
+
applyCurrentMemoryFilters(where, params, scopedOpts);
|
|
451
451
|
params.push(fetchLimit);
|
|
452
452
|
const result = await pool.query(
|
|
453
453
|
`SELECT
|
|
@@ -463,7 +463,7 @@ function createMemoryRecall({ pool, schema, defaultTenantId }) {
|
|
|
463
463
|
+ ${TYPE_BOOST_SQL}
|
|
464
464
|
+ ${feedbackScoreExpr} AS recall_score
|
|
465
465
|
FROM ${schema}.memory_records m
|
|
466
|
-
JOIN ${schema}.scopes s ON s.id = m.scope_id
|
|
466
|
+
JOIN ${schema}.scopes s ON s.tenant_id = m.tenant_id AND s.id = m.scope_id
|
|
467
467
|
WHERE ${where.join(' AND ')}
|
|
468
468
|
ORDER BY
|
|
469
469
|
m.embedding <=> $2::vector ASC,
|
|
@@ -473,7 +473,7 @@ function createMemoryRecall({ pool, schema, defaultTenantId }) {
|
|
|
473
473
|
params,
|
|
474
474
|
);
|
|
475
475
|
const applicableRows = scopeKeys
|
|
476
|
-
? resolveApplicableRecords(result.rows,
|
|
476
|
+
? resolveApplicableRecords(result.rows, scopedOpts)
|
|
477
477
|
: result.rows;
|
|
478
478
|
return applicableRows
|
|
479
479
|
.sort(sortRecallRows)
|
|
@@ -481,26 +481,30 @@ function createMemoryRecall({ pool, schema, defaultTenantId }) {
|
|
|
481
481
|
}
|
|
482
482
|
|
|
483
483
|
async function recallViaLinkedSummaryEmbeddings(queryVec, opts = {}) {
|
|
484
|
+
const scopedOpts = await withResolvedScopeKey(opts);
|
|
484
485
|
const vector = vecToStr(queryVec);
|
|
485
486
|
if (!vector) return [];
|
|
486
|
-
const tenantId =
|
|
487
|
-
const limit = Math.max(1, Math.min(50,
|
|
488
|
-
const scopeKeys = activeScopeKeys(
|
|
487
|
+
const tenantId = scopedOpts.tenantId || defaultTenantId;
|
|
488
|
+
const limit = Math.max(1, Math.min(50, scopedOpts.limit || 10));
|
|
489
|
+
const scopeKeys = activeScopeKeys(scopedOpts);
|
|
489
490
|
const fetchLimit = Math.max(limit, Math.min(200, scopeKeys ? limit * 4 : limit));
|
|
490
491
|
const feedbackScoreExpr = feedbackScoreSql(schema);
|
|
491
492
|
const params = [tenantId, vector];
|
|
492
493
|
const where = [
|
|
493
494
|
`m.tenant_id = $1`,
|
|
494
495
|
`m.status = 'active'`,
|
|
496
|
+
`m.revoked_at IS NULL`,
|
|
497
|
+
`m.superseded_at IS NULL`,
|
|
498
|
+
`m.superseded_by IS NULL`,
|
|
495
499
|
`m.visible_in_recall = true`,
|
|
496
500
|
];
|
|
497
|
-
applyCurrentMemoryFilters(where, params,
|
|
501
|
+
applyCurrentMemoryFilters(where, params, scopedOpts);
|
|
498
502
|
params.push(fetchLimit);
|
|
499
503
|
const result = await pool.query(
|
|
500
504
|
`WITH eligible_memories AS (
|
|
501
505
|
SELECT m.*, s.scope_kind, s.scope_key, s.inheritance_mode AS scope_inheritance_mode
|
|
502
506
|
FROM ${schema}.memory_records m
|
|
503
|
-
JOIN ${schema}.scopes s ON s.id = m.scope_id
|
|
507
|
+
JOIN ${schema}.scopes s ON s.tenant_id = m.tenant_id AND s.id = m.scope_id
|
|
504
508
|
WHERE ${where.join(' AND ')}
|
|
505
509
|
),
|
|
506
510
|
linked_summary_hits AS (
|
|
@@ -543,7 +547,7 @@ function createMemoryRecall({ pool, schema, defaultTenantId }) {
|
|
|
543
547
|
params,
|
|
544
548
|
);
|
|
545
549
|
const applicableRows = scopeKeys
|
|
546
|
-
? resolveApplicableRecords(result.rows,
|
|
550
|
+
? resolveApplicableRecords(result.rows, scopedOpts)
|
|
547
551
|
: result.rows;
|
|
548
552
|
return applicableRows
|
|
549
553
|
.sort(sortRecallRows)
|