@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.
Files changed (57) hide show
  1. package/.env.example +1 -0
  2. package/README.md +82 -26
  3. package/README_CN.md +33 -23
  4. package/README_TW.md +25 -24
  5. package/aquifer.config.example.json +2 -1
  6. package/consumers/cli.js +587 -33
  7. package/consumers/codex-active-checkpoint.js +3 -1
  8. package/consumers/codex-current-memory.js +10 -6
  9. package/consumers/codex.js +6 -3
  10. package/consumers/default/daily-entries.js +2 -2
  11. package/consumers/default/index.js +40 -30
  12. package/consumers/default/prompts/summary.js +2 -2
  13. package/consumers/mcp.js +56 -46
  14. package/consumers/openclaw-ext/index.js +65 -7
  15. package/consumers/openclaw-ext/openclaw.plugin.json +1 -1
  16. package/consumers/openclaw-ext/package.json +1 -1
  17. package/consumers/openclaw-install.js +326 -0
  18. package/consumers/openclaw-plugin.js +105 -24
  19. package/consumers/shared/compat-recall.js +101 -0
  20. package/consumers/shared/config.js +2 -0
  21. package/consumers/shared/openclaw-product-tools.js +130 -0
  22. package/consumers/shared/recall-format.js +2 -2
  23. package/core/aquifer.js +553 -41
  24. package/core/backends/local.js +169 -1
  25. package/core/doctor.js +924 -0
  26. package/core/finalization-inspector.js +164 -0
  27. package/core/finalization-review.js +88 -42
  28. package/core/interface.js +629 -0
  29. package/core/mcp-manifest.js +11 -3
  30. package/core/memory-bootstrap.js +25 -27
  31. package/core/memory-consolidation.js +564 -42
  32. package/core/memory-explain.js +593 -0
  33. package/core/memory-promotion.js +392 -55
  34. package/core/memory-recall.js +75 -71
  35. package/core/memory-records.js +107 -108
  36. package/core/memory-review.js +891 -0
  37. package/core/memory-serving.js +61 -4
  38. package/core/memory-type-policy.js +298 -0
  39. package/core/operator-observability.js +249 -0
  40. package/core/postgres-migrations.js +22 -0
  41. package/core/session-checkpoint-producer.js +3 -1
  42. package/core/session-checkpoints.js +1 -1
  43. package/core/session-finalization.js +78 -3
  44. package/core/storage.js +124 -8
  45. package/docs/getting-started.md +50 -4
  46. package/docs/setup.md +163 -24
  47. package/package.json +5 -4
  48. package/schema/004-completion.sql +4 -4
  49. package/schema/010-v1-finalization-review.sql +72 -0
  50. package/schema/019-v1-memory-review-resolutions.sql +53 -0
  51. package/schema/020-v1-assistant-shaping-memory.sql +30 -0
  52. package/scripts/backfill-canonical-key.js +1 -1
  53. package/scripts/codex-checkpoint-commands.js +28 -0
  54. package/scripts/codex-checkpoint-runtime.js +109 -0
  55. package/scripts/codex-recovery.js +16 -4
  56. package/scripts/diagnose-fts-zh.js +1 -1
  57. package/scripts/extract-insights-from-recent-sessions.js +4 -4
@@ -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
- const TYPE_RANK = {
7
- constraint: 80,
8
- preference: 70,
9
- state: 60,
10
- open_loop: 55,
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
- CASE f.feedback_type
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 += FEEDBACK_WEIGHT[type] || 0;
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 = ((TYPE_RANK[record.memoryType || record.memory_type] || 0) / 100) * RETRIEVAL_TYPE_BOOST;
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 = opts.tenantId || defaultTenantId;
301
- const limit = Math.max(1, Math.min(50, opts.limit || 10));
302
- const cfg = (opts.ftsConfig === 'zhcfg' || opts.ftsConfig === 'simple') ? opts.ftsConfig : 'simple';
303
- const scopeKeys = activeScopeKeys(opts);
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, opts);
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, opts)
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 = opts.tenantId || defaultTenantId;
354
- const limit = Math.max(1, Math.min(50, opts.limit || 10));
355
- const scopeKeys = activeScopeKeys(opts);
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, opts);
365
- const queryVec = vecToStr(opts.queryVec);
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 = opts.vectorOnly === true
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, opts)
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 = opts.tenantId || defaultTenantId;
439
- const limit = Math.max(1, Math.min(50, opts.limit || 10));
440
- const scopeKeys = activeScopeKeys(opts);
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, opts);
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, opts)
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 = opts.tenantId || defaultTenantId;
487
- const limit = Math.max(1, Math.min(50, opts.limit || 10));
488
- const scopeKeys = activeScopeKeys(opts);
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, opts);
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, opts)
550
+ ? resolveApplicableRecords(result.rows, scopedOpts)
547
551
  : result.rows;
548
552
  return applicableRows
549
553
  .sort(sortRecallRows)