@shadowforge0/aquifer-memory 1.5.12 → 1.6.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 (60) hide show
  1. package/.env.example +23 -0
  2. package/README.md +78 -73
  3. package/README_CN.md +659 -0
  4. package/README_TW.md +680 -0
  5. package/aquifer.config.example.json +34 -0
  6. package/consumers/claude-code.js +11 -11
  7. package/consumers/cli.js +353 -52
  8. package/consumers/codex-handoff.js +152 -0
  9. package/consumers/codex.js +1549 -0
  10. package/consumers/default/daily-entries.js +23 -4
  11. package/consumers/default/index.js +2 -2
  12. package/consumers/default/prompts/summary.js +6 -6
  13. package/consumers/mcp.js +96 -5
  14. package/consumers/openclaw-ext/index.js +0 -1
  15. package/consumers/openclaw-plugin.js +1 -1
  16. package/consumers/shared/config.js +8 -0
  17. package/consumers/shared/factory.js +1 -0
  18. package/consumers/shared/ingest.js +1 -1
  19. package/consumers/shared/normalize.js +14 -3
  20. package/consumers/shared/recall-format.js +27 -0
  21. package/consumers/shared/summary-parser.js +151 -0
  22. package/core/aquifer.js +372 -18
  23. package/core/finalization-review.js +319 -0
  24. package/core/mcp-manifest.js +52 -2
  25. package/core/memory-bootstrap.js +188 -0
  26. package/core/memory-consolidation.js +1236 -0
  27. package/core/memory-promotion.js +544 -0
  28. package/core/memory-recall.js +247 -0
  29. package/core/memory-records.js +581 -0
  30. package/core/memory-safety-gate.js +224 -0
  31. package/core/session-finalization.js +350 -0
  32. package/core/storage.js +385 -2
  33. package/docs/getting-started.md +99 -0
  34. package/docs/postprocess-contract.md +2 -2
  35. package/docs/setup.md +51 -2
  36. package/package.json +25 -11
  37. package/pipeline/normalize/adapters/codex.js +106 -0
  38. package/pipeline/normalize/detect.js +3 -2
  39. package/schema/001-base.sql +3 -0
  40. package/schema/007-v1-foundation.sql +273 -0
  41. package/schema/008-session-finalizations.sql +50 -0
  42. package/schema/009-v1-assertion-plane.sql +193 -0
  43. package/schema/010-v1-finalization-review.sql +160 -0
  44. package/schema/011-v1-compaction-claim.sql +46 -0
  45. package/schema/012-v1-compaction-lease.sql +39 -0
  46. package/schema/013-v1-compaction-lineage.sql +193 -0
  47. package/scripts/codex-recovery.js +532 -0
  48. package/consumers/miranda/context-inject.js +0 -120
  49. package/consumers/miranda/daily-entries.js +0 -224
  50. package/consumers/miranda/index.js +0 -364
  51. package/consumers/miranda/instance.js +0 -55
  52. package/consumers/miranda/llm.js +0 -99
  53. package/consumers/miranda/profile.json +0 -145
  54. package/consumers/miranda/prompts/summary.js +0 -303
  55. package/consumers/miranda/recall-format.js +0 -76
  56. package/consumers/miranda/render-daily-md.js +0 -186
  57. package/consumers/miranda/workspace-files.js +0 -91
  58. package/scripts/drop-entity-state-history.sql +0 -17
  59. package/scripts/drop-insights.sql +0 -12
  60. package/scripts/install-openclaw.sh +0 -59
@@ -0,0 +1,247 @@
1
+ 'use strict';
2
+
3
+ const { resolveApplicableRecords } = require('./memory-bootstrap');
4
+
5
+ const TYPE_RANK = {
6
+ constraint: 80,
7
+ preference: 70,
8
+ state: 60,
9
+ open_loop: 55,
10
+ decision: 50,
11
+ fact: 40,
12
+ conclusion: 30,
13
+ entity_note: 20,
14
+ };
15
+
16
+ const FEEDBACK_WEIGHT = {
17
+ helpful: 0.15,
18
+ confirm: 0.10,
19
+ irrelevant: -0.20,
20
+ scope_mismatch: -0.25,
21
+ stale: -0.30,
22
+ incorrect: -0.50,
23
+ };
24
+
25
+ const TYPE_RANK_SQL = `
26
+ CASE m.memory_type
27
+ WHEN 'constraint' THEN 0.80
28
+ WHEN 'preference' THEN 0.70
29
+ WHEN 'state' THEN 0.60
30
+ WHEN 'open_loop' THEN 0.55
31
+ WHEN 'decision' THEN 0.50
32
+ WHEN 'fact' THEN 0.40
33
+ WHEN 'conclusion' THEN 0.30
34
+ WHEN 'entity_note' THEN 0.20
35
+ ELSE 0
36
+ END`;
37
+
38
+ function feedbackScoreSql(schema) {
39
+ return `
40
+ COALESCE((
41
+ SELECT SUM(
42
+ CASE f.feedback_type
43
+ WHEN 'helpful' THEN 0.15
44
+ WHEN 'confirm' THEN 0.10
45
+ WHEN 'irrelevant' THEN -0.20
46
+ WHEN 'scope_mismatch' THEN -0.25
47
+ WHEN 'stale' THEN -0.30
48
+ WHEN 'incorrect' THEN -0.50
49
+ ELSE 0
50
+ END
51
+ )
52
+ FROM ${schema}.feedback f
53
+ WHERE f.tenant_id = $1
54
+ AND f.target_kind = 'memory_record'
55
+ AND f.target_id = m.id::text
56
+ ), 0)`;
57
+ }
58
+
59
+ function textOf(record) {
60
+ return [
61
+ record.title,
62
+ record.summary,
63
+ record.contextKey || record.context_key,
64
+ record.topicKey || record.topic_key,
65
+ ].filter(Boolean).join(' ');
66
+ }
67
+
68
+ function getId(record) {
69
+ return String(record.memoryId || record.memory_id || record.id || record.canonicalKey || record.canonical_key);
70
+ }
71
+
72
+ function parseTime(value) {
73
+ const t = Date.parse(value || '');
74
+ return Number.isFinite(t) ? t : null;
75
+ }
76
+
77
+ function isWithinTime(record, asOf) {
78
+ if (!asOf) return true;
79
+ const at = Date.parse(asOf);
80
+ if (!Number.isFinite(at)) return true;
81
+ const validFrom = parseTime(record.validFrom || record.valid_from);
82
+ const validTo = parseTime(record.validTo || record.valid_to);
83
+ const staleAfter = parseTime(record.staleAfter || record.stale_after);
84
+ if (validFrom !== null && validFrom > at) return false;
85
+ if (validTo !== null && validTo <= at) return false;
86
+ if (staleAfter !== null && staleAfter <= at) return false;
87
+ return true;
88
+ }
89
+
90
+ function isActiveVisible(record, opts = {}) {
91
+ const status = record.status || 'candidate';
92
+ const visible = record.visibleInRecall ?? record.visible_in_recall;
93
+ return status === 'active' && visible === true && isWithinTime(record, opts.asOf);
94
+ }
95
+
96
+ function activeScopeKeys(opts = {}) {
97
+ if (Array.isArray(opts.activeScopePath) && opts.activeScopePath.length > 0) {
98
+ return opts.activeScopePath.map(value => String(value)).filter(Boolean);
99
+ }
100
+ if (opts.activeScopeKey) return [String(opts.activeScopeKey)];
101
+ return null;
102
+ }
103
+
104
+ function rankValue(record, key) {
105
+ const value = record[key];
106
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
107
+ const parsed = Number(value);
108
+ return Number.isFinite(parsed) ? parsed : 0;
109
+ }
110
+
111
+ function sortRecallRows(a, b) {
112
+ const aTitleMatch = a.title_match === true ? 1 : 0;
113
+ const bTitleMatch = b.title_match === true ? 1 : 0;
114
+ if (bTitleMatch !== aTitleMatch) return bTitleMatch - aTitleMatch;
115
+
116
+ const aScore = rankValue(a, 'recall_score') || rankValue(a, 'score');
117
+ const bScore = rankValue(b, 'recall_score') || rankValue(b, 'score');
118
+ if (bScore !== aScore) return bScore - aScore;
119
+
120
+ const aAccepted = Date.parse(a.acceptedAt || a.accepted_at || '') || 0;
121
+ const bAccepted = Date.parse(b.acceptedAt || b.accepted_at || '') || 0;
122
+ if (bAccepted !== aAccepted) return bAccepted - aAccepted;
123
+
124
+ return getId(a).localeCompare(getId(b));
125
+ }
126
+
127
+ function feedbackScore(record, feedbackEvents = []) {
128
+ const id = getId(record);
129
+ let score = 0;
130
+ for (const event of feedbackEvents) {
131
+ const targetId = String(event.targetId || event.target_id || '');
132
+ if (targetId !== id) continue;
133
+ const type = event.feedbackType || event.feedback_type || event.verdict;
134
+ score += FEEDBACK_WEIGHT[type] || 0;
135
+ }
136
+ return score;
137
+ }
138
+
139
+ function lexicalScore(haystack, query) {
140
+ if (haystack.includes(query)) return 1;
141
+ const tokens = query.split(/\s+/).filter(Boolean);
142
+ if (tokens.length === 0) return 0;
143
+ const hits = tokens.filter(t => haystack.includes(t)).length;
144
+ return hits / tokens.length;
145
+ }
146
+
147
+ function recallMemoryRecords(records = [], query, opts = {}) {
148
+ const q = String(query || '').trim().toLowerCase();
149
+ if (!q) throw new Error('memory.recall(query): query must be a non-empty string');
150
+ const limit = Math.max(1, Math.min(50, opts.limit || 10));
151
+ const feedbackEvents = opts.feedbackEvents || [];
152
+ const scopeFiltered = activeScopeKeys(opts)
153
+ ? resolveApplicableRecords(
154
+ records.filter(record => isActiveVisible(record, opts)),
155
+ opts,
156
+ )
157
+ : records.filter(record => isActiveVisible(record, opts));
158
+
159
+ return scopeFiltered
160
+ .map(record => {
161
+ const haystack = textOf(record).toLowerCase();
162
+ const lexical = lexicalScore(haystack, q);
163
+ const typeRank = (TYPE_RANK[record.memoryType || record.memory_type] || 0) / 100;
164
+ const feedback = feedbackScore(record, feedbackEvents);
165
+ return {
166
+ ...record,
167
+ score: lexical + typeRank + feedback,
168
+ _debug: { lexical, typeRank, feedback },
169
+ };
170
+ })
171
+ .filter(record => record._debug.lexical > 0 || opts.includeAll === true)
172
+ .sort(sortRecallRows)
173
+ .slice(0, limit);
174
+ }
175
+
176
+ function createMemoryRecall({ pool, schema, defaultTenantId }) {
177
+ async function recall(query, opts = {}) {
178
+ const q = String(query || '').trim();
179
+ if (!q) throw new Error('memory.recall(query): query must be a non-empty string');
180
+ const tenantId = opts.tenantId || defaultTenantId;
181
+ const limit = Math.max(1, Math.min(50, opts.limit || 10));
182
+ const scopeKeys = activeScopeKeys(opts);
183
+ const fetchLimit = Math.max(limit, Math.min(200, scopeKeys ? limit * 4 : limit));
184
+ const feedbackScoreExpr = feedbackScoreSql(schema);
185
+ const params = [tenantId, q];
186
+ const where = [
187
+ `m.tenant_id = $1`,
188
+ `m.status = 'active'`,
189
+ `m.visible_in_recall = true`,
190
+ `(m.search_tsv @@ plainto_tsquery('simple', $2)
191
+ OR m.title ILIKE '%' || $2 || '%'
192
+ OR m.summary ILIKE '%' || $2 || '%'
193
+ OR m.context_key ILIKE '%' || $2 || '%'
194
+ OR m.topic_key ILIKE '%' || $2 || '%')`,
195
+ ];
196
+ if (opts.scopeId) {
197
+ params.push(opts.scopeId);
198
+ where.push(`m.scope_id = $${params.length}`);
199
+ }
200
+ if (scopeKeys) {
201
+ params.push(scopeKeys);
202
+ where.push(`s.scope_key = ANY($${params.length}::text[])`);
203
+ }
204
+ if (opts.asOf) {
205
+ params.push(opts.asOf);
206
+ const at = `$${params.length}::timestamptz`;
207
+ where.push(`(m.valid_from IS NULL OR m.valid_from <= ${at})`);
208
+ where.push(`(m.valid_to IS NULL OR m.valid_to > ${at})`);
209
+ where.push(`(m.stale_after IS NULL OR m.stale_after > ${at})`);
210
+ }
211
+ params.push(fetchLimit);
212
+ const result = await pool.query(
213
+ `SELECT
214
+ m.*, s.scope_kind, s.scope_key, s.inheritance_mode AS scope_inheritance_mode,
215
+ (m.title ILIKE '%' || $2 || '%') AS title_match,
216
+ ts_rank(m.search_tsv, plainto_tsquery('simple', $2)) AS lexical_rank,
217
+ ${TYPE_RANK_SQL} AS type_rank,
218
+ ${feedbackScoreExpr} AS feedback_score,
219
+ ts_rank(m.search_tsv, plainto_tsquery('simple', $2))
220
+ + ${TYPE_RANK_SQL}
221
+ + ${feedbackScoreExpr} AS recall_score
222
+ FROM ${schema}.memory_records m
223
+ JOIN ${schema}.scopes s ON s.id = m.scope_id
224
+ WHERE ${where.join(' AND ')}
225
+ ORDER BY
226
+ title_match DESC,
227
+ recall_score DESC,
228
+ m.accepted_at DESC NULLS LAST,
229
+ m.id ASC
230
+ LIMIT $${params.length}`,
231
+ params
232
+ );
233
+ const applicableRows = scopeKeys
234
+ ? resolveApplicableRecords(result.rows, opts)
235
+ : result.rows;
236
+ return applicableRows
237
+ .sort(sortRecallRows)
238
+ .slice(0, limit);
239
+ }
240
+
241
+ return { recall };
242
+ }
243
+
244
+ module.exports = {
245
+ recallMemoryRecords,
246
+ createMemoryRecall,
247
+ };