@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.
- package/.env.example +23 -0
- package/README.md +78 -73
- package/README_CN.md +659 -0
- package/README_TW.md +680 -0
- package/aquifer.config.example.json +34 -0
- package/consumers/claude-code.js +11 -11
- package/consumers/cli.js +353 -52
- package/consumers/codex-handoff.js +152 -0
- package/consumers/codex.js +1549 -0
- package/consumers/default/daily-entries.js +23 -4
- package/consumers/default/index.js +2 -2
- package/consumers/default/prompts/summary.js +6 -6
- package/consumers/mcp.js +96 -5
- package/consumers/openclaw-ext/index.js +0 -1
- package/consumers/openclaw-plugin.js +1 -1
- package/consumers/shared/config.js +8 -0
- package/consumers/shared/factory.js +1 -0
- package/consumers/shared/ingest.js +1 -1
- package/consumers/shared/normalize.js +14 -3
- package/consumers/shared/recall-format.js +27 -0
- package/consumers/shared/summary-parser.js +151 -0
- package/core/aquifer.js +372 -18
- package/core/finalization-review.js +319 -0
- package/core/mcp-manifest.js +52 -2
- package/core/memory-bootstrap.js +188 -0
- package/core/memory-consolidation.js +1236 -0
- package/core/memory-promotion.js +544 -0
- package/core/memory-recall.js +247 -0
- package/core/memory-records.js +581 -0
- package/core/memory-safety-gate.js +224 -0
- package/core/session-finalization.js +350 -0
- package/core/storage.js +385 -2
- package/docs/getting-started.md +99 -0
- package/docs/postprocess-contract.md +2 -2
- package/docs/setup.md +51 -2
- package/package.json +25 -11
- package/pipeline/normalize/adapters/codex.js +106 -0
- package/pipeline/normalize/detect.js +3 -2
- package/schema/001-base.sql +3 -0
- package/schema/007-v1-foundation.sql +273 -0
- package/schema/008-session-finalizations.sql +50 -0
- package/schema/009-v1-assertion-plane.sql +193 -0
- package/schema/010-v1-finalization-review.sql +160 -0
- package/schema/011-v1-compaction-claim.sql +46 -0
- package/schema/012-v1-compaction-lease.sql +39 -0
- package/schema/013-v1-compaction-lineage.sql +193 -0
- package/scripts/codex-recovery.js +532 -0
- package/consumers/miranda/context-inject.js +0 -120
- package/consumers/miranda/daily-entries.js +0 -224
- package/consumers/miranda/index.js +0 -364
- package/consumers/miranda/instance.js +0 -55
- package/consumers/miranda/llm.js +0 -99
- package/consumers/miranda/profile.json +0 -145
- package/consumers/miranda/prompts/summary.js +0 -303
- package/consumers/miranda/recall-format.js +0 -76
- package/consumers/miranda/render-daily-md.js +0 -186
- package/consumers/miranda/workspace-files.js +0 -91
- package/scripts/drop-entity-state-history.sql +0 -17
- package/scripts/drop-insights.sql +0 -12
- 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
|
+
};
|