@shadowforge0/aquifer-memory 1.9.0 → 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/README.md +33 -4
- package/README_CN.md +9 -1
- package/README_TW.md +5 -2
- package/consumers/cli.js +55 -34
- package/consumers/codex-active-checkpoint.js +3 -1
- package/consumers/codex-current-memory.js +10 -6
- package/consumers/codex.js +5 -2
- 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 -49
- package/consumers/openclaw-ext/index.js +1 -1
- package/consumers/openclaw-ext/openclaw.plugin.json +1 -1
- package/consumers/openclaw-ext/package.json +1 -1
- package/consumers/openclaw-plugin.js +66 -23
- package/consumers/shared/compat-recall.js +101 -0
- package/consumers/shared/openclaw-product-tools.js +130 -0
- package/consumers/shared/recall-format.js +2 -2
- package/core/aquifer.js +385 -20
- package/core/backends/local.js +60 -1
- 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 +20 -51
- package/core/memory-promotion.js +392 -55
- package/core/memory-recall.js +26 -48
- package/core/memory-records.js +91 -103
- package/core/memory-type-policy.js +298 -0
- package/core/postgres-migrations.js +9 -0
- package/core/session-checkpoint-producer.js +3 -1
- package/core/session-checkpoints.js +1 -1
- package/core/session-finalization.js +2 -2
- package/docs/getting-started.md +16 -3
- package/docs/setup.md +61 -2
- package/package.json +2 -2
- package/schema/004-completion.sql +4 -4
- package/schema/010-v1-finalization-review.sql +72 -0
- package/schema/020-v1-assistant-shaping-memory.sql +30 -0
- package/scripts/backfill-canonical-key.js +1 -1
- package/scripts/diagnose-fts-zh.js +1 -1
- package/scripts/extract-insights-from-recent-sessions.js +4 -4
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const MEMORY_TYPE_POLICY = Object.freeze({
|
|
4
|
+
assistant_shaping: {
|
|
5
|
+
label: '回答塑形',
|
|
6
|
+
active: true,
|
|
7
|
+
defaultInheritance: 'defaultable',
|
|
8
|
+
defaultAspect: 'assistant_shaping',
|
|
9
|
+
bootstrapPriority: 0,
|
|
10
|
+
recallRank: 95,
|
|
11
|
+
runtimeContract: true,
|
|
12
|
+
},
|
|
13
|
+
constraint: {
|
|
14
|
+
label: '限制',
|
|
15
|
+
active: true,
|
|
16
|
+
defaultInheritance: 'additive',
|
|
17
|
+
defaultAspect: 'constraint',
|
|
18
|
+
bootstrapPriority: 1,
|
|
19
|
+
recallRank: 80,
|
|
20
|
+
},
|
|
21
|
+
state: {
|
|
22
|
+
label: '狀態',
|
|
23
|
+
active: true,
|
|
24
|
+
defaultInheritance: 'defaultable',
|
|
25
|
+
defaultAspect: 'state',
|
|
26
|
+
bootstrapPriority: 2,
|
|
27
|
+
recallRank: 60,
|
|
28
|
+
},
|
|
29
|
+
open_loop: {
|
|
30
|
+
label: '未完成',
|
|
31
|
+
active: true,
|
|
32
|
+
defaultInheritance: 'non_inheritable',
|
|
33
|
+
defaultAspect: 'open_loop',
|
|
34
|
+
bootstrapPriority: 3,
|
|
35
|
+
recallRank: 55,
|
|
36
|
+
},
|
|
37
|
+
decision: {
|
|
38
|
+
label: '決策',
|
|
39
|
+
active: true,
|
|
40
|
+
defaultInheritance: 'non_inheritable',
|
|
41
|
+
defaultAspect: 'decision',
|
|
42
|
+
bootstrapPriority: 4,
|
|
43
|
+
recallRank: 50,
|
|
44
|
+
},
|
|
45
|
+
preference: {
|
|
46
|
+
label: '偏好',
|
|
47
|
+
active: true,
|
|
48
|
+
defaultInheritance: 'defaultable',
|
|
49
|
+
defaultAspect: 'preference',
|
|
50
|
+
bootstrapPriority: 5,
|
|
51
|
+
recallRank: 70,
|
|
52
|
+
},
|
|
53
|
+
fact: {
|
|
54
|
+
label: '事實',
|
|
55
|
+
active: true,
|
|
56
|
+
defaultInheritance: 'defaultable',
|
|
57
|
+
defaultAspect: 'fact',
|
|
58
|
+
bootstrapPriority: 6,
|
|
59
|
+
recallRank: 40,
|
|
60
|
+
},
|
|
61
|
+
conclusion: {
|
|
62
|
+
label: '判斷',
|
|
63
|
+
active: true,
|
|
64
|
+
defaultInheritance: 'defaultable',
|
|
65
|
+
defaultAspect: 'conclusion',
|
|
66
|
+
bootstrapPriority: 7,
|
|
67
|
+
recallRank: 30,
|
|
68
|
+
},
|
|
69
|
+
entity_note: {
|
|
70
|
+
label: '註記',
|
|
71
|
+
active: true,
|
|
72
|
+
defaultInheritance: 'defaultable',
|
|
73
|
+
defaultAspect: 'entity_note',
|
|
74
|
+
bootstrapPriority: 8,
|
|
75
|
+
recallRank: 20,
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const AUTHORITY_POLICY = Object.freeze({
|
|
80
|
+
raw_transcript: { strength: 0, sortPriority: 6 },
|
|
81
|
+
llm_inference: { strength: 1, sortPriority: 5 },
|
|
82
|
+
verified_summary: { strength: 2, sortPriority: 4 },
|
|
83
|
+
manual: { strength: 3, sortPriority: 2 },
|
|
84
|
+
system: { strength: 4, sortPriority: 3 },
|
|
85
|
+
executable_evidence: { strength: 5, sortPriority: 1 },
|
|
86
|
+
user_explicit: { strength: 6, sortPriority: 0 },
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const FEEDBACK_WEIGHT = Object.freeze({
|
|
90
|
+
helpful: 0.15,
|
|
91
|
+
confirm: 0.10,
|
|
92
|
+
irrelevant: -0.20,
|
|
93
|
+
scope_mismatch: -0.25,
|
|
94
|
+
stale: -0.30,
|
|
95
|
+
incorrect: -0.50,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const ASSISTANT_SHAPING_KINDS = Object.freeze([
|
|
99
|
+
'response_style',
|
|
100
|
+
'interaction_boundary',
|
|
101
|
+
'retrieval_policy',
|
|
102
|
+
'tool_routing',
|
|
103
|
+
'memory_policy',
|
|
104
|
+
'user_preference',
|
|
105
|
+
'correction_pattern',
|
|
106
|
+
'decision_style',
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
const ASSISTANT_SHAPING_SERVING_IMPACTS = Object.freeze([
|
|
110
|
+
'changes_future_responses',
|
|
111
|
+
'changes_retrieval_selection',
|
|
112
|
+
'changes_bootstrap_selection',
|
|
113
|
+
'changes_tool_routing',
|
|
114
|
+
'changes_memory_promotion',
|
|
115
|
+
'sets_user_boundary',
|
|
116
|
+
'sets_assistant_behavior',
|
|
117
|
+
]);
|
|
118
|
+
|
|
119
|
+
const ASSISTANT_SHAPING_PROMPT_LINES = Object.freeze([
|
|
120
|
+
'Use assistant_shaping only when a memory changes durable cross-consumer runtime behavior for the user: future response style, interaction boundary, retrieval selection, tool routing, or memory promotion policy.',
|
|
121
|
+
'Each assistant_shaping item must include guidance or policy text plus shapingKind, servingImpact, and userRelevance. For non-explicit-user-instruction summaries, include temporalSupport showing repeated observations, multiple sessions, or a consolidation window.',
|
|
122
|
+
'Treat shapingKind and servingImpact as extensible hints, not as a closed product taxonomy. If the item only reports current task state, implementation facts, or a single answer, keep it in the ordinary memory layer instead.',
|
|
123
|
+
]);
|
|
124
|
+
|
|
125
|
+
function normalizeToken(value) {
|
|
126
|
+
return String(value || '').trim().toLowerCase();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function memoryTypePolicy(type) {
|
|
130
|
+
return MEMORY_TYPE_POLICY[normalizeToken(type)] || null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function activeMemoryTypes() {
|
|
134
|
+
return Object.keys(MEMORY_TYPE_POLICY).filter(type => MEMORY_TYPE_POLICY[type].active);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function isActiveMemoryType(type) {
|
|
138
|
+
const policy = memoryTypePolicy(type);
|
|
139
|
+
return Boolean(policy && policy.active);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function defaultInheritanceForMemoryType(type) {
|
|
143
|
+
return memoryTypePolicy(type)?.defaultInheritance || 'defaultable';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function defaultAspectForMemoryType(type) {
|
|
147
|
+
return memoryTypePolicy(type)?.defaultAspect || normalizeToken(type) || 'memory';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function memoryTypeLabel(type) {
|
|
151
|
+
return memoryTypePolicy(type)?.label || '記憶';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function memoryTypeBootstrapPriority(type) {
|
|
155
|
+
return memoryTypePolicy(type)?.bootstrapPriority ?? 99;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function memoryTypeRecallRank(type) {
|
|
159
|
+
return memoryTypePolicy(type)?.recallRank ?? 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function memoryTypeRuntimeContract(type) {
|
|
163
|
+
return memoryTypePolicy(type)?.runtimeContract === true;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function authorityPolicy(authority) {
|
|
167
|
+
return AUTHORITY_POLICY[normalizeToken(authority)] || null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function authorityStrength(authority) {
|
|
171
|
+
return authorityPolicy(authority)?.strength ?? 0;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function authoritySortPriority(authority) {
|
|
175
|
+
return authorityPolicy(authority)?.sortPriority ?? 99;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function feedbackWeight(type) {
|
|
179
|
+
return FEEDBACK_WEIGHT[normalizeToken(type)] || 0;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function sqlLiteral(value) {
|
|
183
|
+
return `'${String(value).replace(/'/g, "''")}'`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function caseSql(expr, entries, valueKey, fallback) {
|
|
187
|
+
const clauses = entries
|
|
188
|
+
.map(([key, policy]) => `WHEN ${sqlLiteral(key)} THEN ${policy[valueKey]}`)
|
|
189
|
+
.join('\n ');
|
|
190
|
+
return `CASE ${expr}\n ${clauses}\n ELSE ${fallback}\n END`;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function memoryTypeBootstrapPrioritySql(expr = 'm.memory_type') {
|
|
194
|
+
return caseSql(expr, Object.entries(MEMORY_TYPE_POLICY), 'bootstrapPriority', 99);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function memoryTypeRecallRankSql(expr = 'm.memory_type') {
|
|
198
|
+
const entries = Object.entries(MEMORY_TYPE_POLICY)
|
|
199
|
+
.map(([key, policy]) => [key, { recallWeight: (policy.recallRank || 0) / 100 }]);
|
|
200
|
+
return caseSql(expr, entries, 'recallWeight', 0);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function authoritySortPrioritySql(expr = 'm.authority') {
|
|
204
|
+
return caseSql(expr, Object.entries(AUTHORITY_POLICY), 'sortPriority', 99);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function feedbackWeightSql(expr = 'f.feedback_type') {
|
|
208
|
+
const entries = Object.entries(FEEDBACK_WEIGHT).map(([key, weight]) => [key, { weight }]);
|
|
209
|
+
return caseSql(expr, entries, 'weight', 0);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function memoryTypeOf(record = {}) {
|
|
213
|
+
return record.memoryType || record.memory_type || record.type || 'memory';
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function memorySummaryText(record = {}) {
|
|
217
|
+
return String(record.summary || record.summaryText || record.title || '').replace(/\s+/g, ' ').trim();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function assistantShapingMetadata(record = {}) {
|
|
221
|
+
const payload = record.payload && typeof record.payload === 'object' ? record.payload : {};
|
|
222
|
+
const entries = [
|
|
223
|
+
['policy', payload.policyKey || payload.policy_key || record.policyKey || record.policy_key],
|
|
224
|
+
['kind', payload.shapingKind || payload.shaping_kind || payload.kind || payload.category || record.shapingKind || record.shaping_kind],
|
|
225
|
+
['impact', payload.servingImpact || payload.serving_impact || payload.impact || record.servingImpact || record.serving_impact],
|
|
226
|
+
];
|
|
227
|
+
return entries
|
|
228
|
+
.map(([key, value]) => [key, String(value || '').trim()])
|
|
229
|
+
.filter(([, value]) => value);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function formatRuntimeMemoryLine(record = {}, opts = {}) {
|
|
233
|
+
const type = memoryTypeOf(record);
|
|
234
|
+
const label = opts.localizedLabel === true ? memoryTypeLabel(type) : type;
|
|
235
|
+
const attrs = [];
|
|
236
|
+
const scope = record.scopeKey || record.scope_key;
|
|
237
|
+
const authority = record.authority;
|
|
238
|
+
const confidence = record.confidence || record.payload?.confidence || record.payload?.currentMemoryConfidence;
|
|
239
|
+
if (opts.includeScope === true && scope) attrs.push(`scope=${scope}`);
|
|
240
|
+
if (opts.includeAuthority === true && authority) attrs.push(`authority=${authority}`);
|
|
241
|
+
if (opts.includeConfidence === true && confidence) attrs.push(`confidence=${confidence}`);
|
|
242
|
+
const suffix = attrs.length > 0 ? ` ${attrs.join(' ')}` : '';
|
|
243
|
+
const metadata = type === 'assistant_shaping'
|
|
244
|
+
? assistantShapingMetadata(record).map(([key, value]) => `${key}=${value}`).join(' ')
|
|
245
|
+
: '';
|
|
246
|
+
const metadataSuffix = metadata ? ` (${metadata})` : '';
|
|
247
|
+
return `- ${label}${suffix}: ${memorySummaryText(record)}${metadataSuffix}`;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function assistantShapingKindAllowed(value) {
|
|
251
|
+
return ASSISTANT_SHAPING_KINDS.includes(normalizeToken(value));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function assistantShapingServingImpactAllowed(value) {
|
|
255
|
+
const token = normalizeToken(value);
|
|
256
|
+
return assistantShapingServingImpactKnown(token)
|
|
257
|
+
|| /^(changes|sets|routes|selects|filters|prioritizes|suppresses)_[a-z0-9_]+$/.test(token);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function assistantShapingServingImpactKnown(value) {
|
|
261
|
+
return ASSISTANT_SHAPING_SERVING_IMPACTS.includes(normalizeToken(value));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function assistantShapingPromptLines() {
|
|
265
|
+
return ASSISTANT_SHAPING_PROMPT_LINES.slice();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
module.exports = {
|
|
269
|
+
ASSISTANT_SHAPING_KINDS,
|
|
270
|
+
ASSISTANT_SHAPING_SERVING_IMPACTS,
|
|
271
|
+
AUTHORITY_POLICY,
|
|
272
|
+
FEEDBACK_WEIGHT,
|
|
273
|
+
MEMORY_TYPE_POLICY,
|
|
274
|
+
activeMemoryTypes,
|
|
275
|
+
assistantShapingKindAllowed,
|
|
276
|
+
assistantShapingMetadata,
|
|
277
|
+
assistantShapingPromptLines,
|
|
278
|
+
assistantShapingServingImpactAllowed,
|
|
279
|
+
assistantShapingServingImpactKnown,
|
|
280
|
+
authoritySortPriority,
|
|
281
|
+
authoritySortPrioritySql,
|
|
282
|
+
authorityStrength,
|
|
283
|
+
defaultAspectForMemoryType,
|
|
284
|
+
defaultInheritanceForMemoryType,
|
|
285
|
+
feedbackWeight,
|
|
286
|
+
feedbackWeightSql,
|
|
287
|
+
formatRuntimeMemoryLine,
|
|
288
|
+
isActiveMemoryType,
|
|
289
|
+
memorySummaryText,
|
|
290
|
+
memoryTypeBootstrapPriority,
|
|
291
|
+
memoryTypeBootstrapPrioritySql,
|
|
292
|
+
memoryTypeLabel,
|
|
293
|
+
memoryTypeOf,
|
|
294
|
+
memoryTypePolicy,
|
|
295
|
+
memoryTypeRecallRank,
|
|
296
|
+
memoryTypeRecallRankSql,
|
|
297
|
+
memoryTypeRuntimeContract,
|
|
298
|
+
};
|
|
@@ -93,6 +93,14 @@ const MIGRATION_PLAN = [
|
|
|
93
93
|
{ index: 'idx_feedback_memory_review_latest' },
|
|
94
94
|
],
|
|
95
95
|
},
|
|
96
|
+
{
|
|
97
|
+
id: '020-v1-assistant-shaping-memory',
|
|
98
|
+
file: '020-v1-assistant-shaping-memory.sql',
|
|
99
|
+
always: true,
|
|
100
|
+
signature: [
|
|
101
|
+
{ index: 'idx_memory_records_assistant_shaping_bootstrap' },
|
|
102
|
+
],
|
|
103
|
+
},
|
|
96
104
|
];
|
|
97
105
|
|
|
98
106
|
function createPostgresMigrationRuntime(opts = {}) {
|
|
@@ -309,6 +317,7 @@ function createPostgresMigrationRuntime(opts = {}) {
|
|
|
309
317
|
['017-v1-memory-record-embeddings.sql', '017-v1-memory-record-embeddings'],
|
|
310
318
|
['018-v1-finalization-candidate-envelope.sql', '018-v1-finalization-candidate-envelope'],
|
|
311
319
|
['019-v1-memory-review-resolutions.sql', '019-v1-memory-review-resolutions'],
|
|
320
|
+
['020-v1-assistant-shaping-memory.sql', '020-v1-assistant-shaping-memory'],
|
|
312
321
|
]) {
|
|
313
322
|
await client.query(loadSql(migration[0], schema));
|
|
314
323
|
ddlExecuted.push(migration[1]);
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
const crypto = require('node:crypto');
|
|
4
4
|
const { sanitizeSummaryResult } = require('./memory-safety-gate');
|
|
5
|
+
const { assistantShapingPromptLines } = require('./memory-type-policy');
|
|
5
6
|
const { buildScopeEnvelope, getScopeByEnvelopeId } = require('./scope-attribution');
|
|
6
7
|
|
|
7
8
|
const DEFAULT_POLICY_VERSION = 'session_checkpoint_producer_v1';
|
|
8
9
|
const DEFAULT_COVERAGE_COORDINATE_SYSTEM = 'codex_sanitized_view_v1';
|
|
9
|
-
const STRUCTURED_SUMMARY_SHAPE = '{"summaryText":"...","structuredSummary":{"facts":[],"decisions":[],"open_loops":[],"preferences":[],"constraints":[],"conclusions":[],"entity_notes":[],"states":[]},"coverage":{"coordinateSystem":"codex_sanitized_view_v1","coveredUntilMessageIndex":0,"coveredUntilChar":0}}';
|
|
10
|
+
const STRUCTURED_SUMMARY_SHAPE = '{"summaryText":"...","structuredSummary":{"assistant_shaping":[],"facts":[],"decisions":[],"open_loops":[],"preferences":[],"constraints":[],"conclusions":[],"entity_notes":[],"states":[]},"coverage":{"coordinateSystem":"codex_sanitized_view_v1","coveredUntilMessageIndex":0,"coveredUntilChar":0}}';
|
|
10
11
|
|
|
11
12
|
function stableJson(value) {
|
|
12
13
|
if (Array.isArray(value)) return `[${value.map(stableJson).join(',')}]`;
|
|
@@ -318,6 +319,7 @@ function buildCheckpointSynthesisPrompt(synthesisInput = {}, opts = {}) {
|
|
|
318
319
|
'Return compact JSON with this shape:',
|
|
319
320
|
STRUCTURED_SUMMARY_SHAPE,
|
|
320
321
|
`Keep facts/decisions/open_loops concrete and scoped. Use at most ${maxFacts} facts.`,
|
|
322
|
+
...assistantShapingPromptLines(),
|
|
321
323
|
'Preserve the coverage object so handoff can skip only the already-covered transcript range.',
|
|
322
324
|
'',
|
|
323
325
|
'<checkpoint_synthesis_input>',
|
|
@@ -44,7 +44,7 @@ function parsePositiveInt(value, fallback = 10, max = 200) {
|
|
|
44
44
|
function compactStructuredSummary(value = {}) {
|
|
45
45
|
if (!value || typeof value !== 'object') return {};
|
|
46
46
|
const out = {};
|
|
47
|
-
for (const key of ['facts', 'decisions', 'open_loops', 'openLoops', 'preferences', 'constraints', 'conclusions', 'entity_notes', 'entityNotes', 'states']) {
|
|
47
|
+
for (const key of ['assistant_shaping', 'assistant_shaping_memories', 'facts', 'decisions', 'open_loops', 'openLoops', 'preferences', 'constraints', 'conclusions', 'entity_notes', 'entityNotes', 'states']) {
|
|
48
48
|
const rows = Array.isArray(value[key]) ? value[key] : [];
|
|
49
49
|
if (rows.length > 0) out[key] = rows.slice(0, 8);
|
|
50
50
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const crypto = require('crypto');
|
|
4
4
|
const storage = require('./storage');
|
|
5
5
|
const { createMemoryRecords } = require('./memory-records');
|
|
6
|
-
const { createMemoryPromotion } = require('./memory-promotion');
|
|
6
|
+
const { createMemoryPromotion, sanitizePromotionCandidate } = require('./memory-promotion');
|
|
7
7
|
const { sanitizeSummaryResult } = require('./memory-safety-gate');
|
|
8
8
|
const { buildFinalizationReview, buildSessionStartContext } = require('./finalization-review');
|
|
9
9
|
const { buildFinalizationInspection } = require('./finalization-inspector');
|
|
@@ -397,7 +397,7 @@ function createSessionFinalization({
|
|
|
397
397
|
authority: input.authority || 'verified_summary',
|
|
398
398
|
evidenceRefs,
|
|
399
399
|
});
|
|
400
|
-
const candidates = decorateCandidates(rawCandidates, input);
|
|
400
|
+
const candidates = decorateCandidates(rawCandidates.map(sanitizePromotionCandidate), input);
|
|
401
401
|
const candidateEnvelope = buildCandidateEnvelope(input, candidates, {
|
|
402
402
|
transcriptHash: base.transcriptHash,
|
|
403
403
|
});
|
package/docs/getting-started.md
CHANGED
|
@@ -104,13 +104,26 @@ Run with `--dry-run --json` to verify the active package version, extension path
|
|
|
104
104
|
| Plan curated compaction | `npx aquifer compact --cadence daily --period-start 2026-04-27T00:00:00Z --period-end 2026-04-28T00:00:00Z` |
|
|
105
105
|
| Generate a timer synthesis prompt | `npx aquifer operator compaction daily --include-synthesis-prompt --json` |
|
|
106
106
|
| Apply reviewed timer synthesis candidates | `npx aquifer operator compaction daily --synthesis-summary-file /tmp/timer-summary.json --apply --promote-candidates --json` |
|
|
107
|
-
|
|
|
108
|
-
|
|
|
107
|
+
| Check memory readiness | `npx aquifer stats` |
|
|
108
|
+
| Check saved-content preparation | `npx aquifer backlog --json` |
|
|
109
|
+
| Prepare saved content | `npx aquifer backfill` |
|
|
109
110
|
| Resolve a reviewed memory issue | `npx aquifer review resolve --memory-id 42 --resolution resolved --reason "verified current" --expected-latest-issue-feedback-id 9 --json` |
|
|
110
111
|
|
|
112
|
+
`stats`, `backlog`, MCP `memory_stats`, and MCP `memory_pending` default to the
|
|
113
|
+
same public status surface: `Aquifer status` or `Saved content status`, plus
|
|
114
|
+
`Available`, `Attention`, and `Action` where relevant. Use CLI `--diagnostics`
|
|
115
|
+
or MCP `diagnostics: true` when a host needs raw counters, buckets, guidance,
|
|
116
|
+
or samples.
|
|
117
|
+
|
|
111
118
|
Timer synthesis is an operator-reviewed candidate workflow. The prompt output
|
|
112
119
|
and summary JSON do not become active curated memory unless the apply step is
|
|
113
|
-
run with `--promote-candidates`.
|
|
120
|
+
run with `--promote-candidates`. Daily/weekly/monthly aggregate proposals are
|
|
121
|
+
source-rollup material for review and lineage only; normal promotion is blocked
|
|
122
|
+
until a reviewed synthesis summary is attached. Reviewed synthesis items must
|
|
123
|
+
also pass the temporal distillation standard: each item needs `mergeKey`,
|
|
124
|
+
`scopeClass`, `durability`, `promotionTarget`, and `sourceCanonicalKeys` from
|
|
125
|
+
the prompt's `sourceCurrentMemory`, and runtime state also needs `staleAfter`
|
|
126
|
+
or `validTo`.
|
|
114
127
|
|
|
115
128
|
The default public serving mode is `legacy`. To test scoped curated memory serving, set `AQUIFER_MEMORY_SERVING_MODE=curated` plus `AQUIFER_MEMORY_ACTIVE_SCOPE_KEY` or `AQUIFER_MEMORY_ACTIVE_SCOPE_PATH`. Rollback is config-only: set the serving mode back to `legacy` and restart the MCP/CLI process.
|
|
116
129
|
|
package/docs/setup.md
CHANGED
|
@@ -303,7 +303,14 @@ The summary file must match the normal structured summary shape, for example:
|
|
|
303
303
|
"summaryText": "Reviewed timer synthesis.",
|
|
304
304
|
"structuredSummary": {
|
|
305
305
|
"states": [
|
|
306
|
-
{
|
|
306
|
+
{
|
|
307
|
+
"state": "The reviewed state that should continue into current memory.",
|
|
308
|
+
"mergeKey": "product-state:example",
|
|
309
|
+
"scopeClass": "product_memory",
|
|
310
|
+
"durability": "durable",
|
|
311
|
+
"promotionTarget": "project_current_memory",
|
|
312
|
+
"sourceCanonicalKeys": ["memory:canonical:key-from-prompt"]
|
|
313
|
+
}
|
|
307
314
|
],
|
|
308
315
|
"decisions": [],
|
|
309
316
|
"open_loops": []
|
|
@@ -311,9 +318,61 @@ The summary file must match the normal structured summary shape, for example:
|
|
|
311
318
|
}
|
|
312
319
|
```
|
|
313
320
|
|
|
321
|
+
Assistant behavior memory uses the same reviewed synthesis path, but the
|
|
322
|
+
serving text must be behavior-level rather than project-specific wording:
|
|
323
|
+
|
|
324
|
+
```json
|
|
325
|
+
{
|
|
326
|
+
"summaryText": "Reviewed assistant behavior synthesis.",
|
|
327
|
+
"structuredSummary": {
|
|
328
|
+
"assistant_shaping": [
|
|
329
|
+
{
|
|
330
|
+
"guidance": "The user dislikes paying time and token cost for rediscovery. On continuation work, start from known state before broad exploration.",
|
|
331
|
+
"shapingKind": "tool_routing",
|
|
332
|
+
"servingImpact": "changes_tool_routing",
|
|
333
|
+
"userRelevance": "This reduces repeated context work and keeps collaboration moving.",
|
|
334
|
+
"temporalSupport": { "observationCount": 3 },
|
|
335
|
+
"abstraction": {
|
|
336
|
+
"languageLevel": "user_behavior",
|
|
337
|
+
"appliesBeyondSource": true,
|
|
338
|
+
"sourceBound": false,
|
|
339
|
+
"principle": "The user values avoiding repeated context work."
|
|
340
|
+
},
|
|
341
|
+
"mergeKey": "mk_hates_repeating_context_work",
|
|
342
|
+
"scopeClass": "assistant_behavior",
|
|
343
|
+
"durability": "durable",
|
|
344
|
+
"promotionTarget": "assistant_behavior_memory",
|
|
345
|
+
"sourceCanonicalKeys": ["memory:canonical:key-from-prompt"]
|
|
346
|
+
}
|
|
347
|
+
]
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
314
352
|
Without `--promote-candidates`, synthesis output is recorded as candidate
|
|
315
353
|
ledger material only. The prompt and summary file are producer material; active
|
|
316
|
-
curated memory still requires the explicit promotion gate.
|
|
354
|
+
curated memory still requires the explicit promotion gate. The deterministic
|
|
355
|
+
aggregate proposals from a daily/weekly/monthly dry-run are source-rollup
|
|
356
|
+
review material, not active temporal memory; the normal product path blocks
|
|
357
|
+
their promotion unless a reviewed synthesis summary is attached.
|
|
358
|
+
|
|
359
|
+
Reviewed synthesis items must pass the temporal distillation standard before
|
|
360
|
+
they become promotable candidates. Each item needs `mergeKey`, `scopeClass`,
|
|
361
|
+
`durability`, `promotionTarget`, and `sourceCanonicalKeys` that point to
|
|
362
|
+
canonical keys from the prompt's `sourceCurrentMemory`; runtime state also needs
|
|
363
|
+
`staleAfter` or `validTo`. Workspace/operator policy, transient material,
|
|
364
|
+
runtime state without expiry, duplicate merge keys, invalid or missing source
|
|
365
|
+
lineage, and unsupported promotion targets are rejected before promotion.
|
|
366
|
+
`assistant_behavior_memory` additionally requires generalized user-behavior
|
|
367
|
+
abstraction metadata and normalizes serving text to the abstraction principle.
|
|
368
|
+
Candidate payloads keep only compact lineage references; per-candidate source
|
|
369
|
+
lineage stays on the candidate trace and in the compaction ledger for audit.
|
|
370
|
+
|
|
371
|
+
Consumers that should inherit user-level assistant behavior together with a
|
|
372
|
+
project scope should include the user scope in the active path, for example
|
|
373
|
+
`AQUIFER_MEMORY_ACTIVE_SCOPE_PATH=global,user:user,project:aquifer` and matching
|
|
374
|
+
`AQUIFER_MEMORY_ALLOWED_SCOPE_KEYS`. Applicable `assistant_shaping` records are
|
|
375
|
+
pinned ahead of ordinary project current memory during bootstrap.
|
|
317
376
|
|
|
318
377
|
## Read-only governance diagnostics
|
|
319
378
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shadowforge0/aquifer-memory",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.1",
|
|
4
4
|
"description": "PG-native long-term memory for AI agents. Turn-level embedding, hybrid RRF ranking, optional knowledge graph. MCP server, CLI, and library API.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"scripts": {
|
|
73
73
|
"test": "node --test test/*.test.js",
|
|
74
74
|
"test:integration": "node --test test/integration.test.js",
|
|
75
|
-
"test:release:package": "node --test test/package-surface.test.js test/mcp-manifest.test.js test/local-backend.test.js test/scope-attribution.test.js test/doctor.test.js test/finalization-inspector.test.js test/memory-explain.test.js test/memory-review.test.js test/operator-observability.test.js test/storage-finalization-status.test.js test/cli-parseargs.test.js test/v1-checkpoint-ledger-schema.test.js test/v1-finalization-envelope-schema.test.js test/v1-memory-review-resolutions-schema.test.js test/v1-evidence-items.test.js test/v1-curated-semantic-recall.test.js test/session-checkpoints.test.js test/session-checkpoint-producer.test.js test/session-checkpoint-planner.test.js test/storage-checkpoint-ranges.test.js test/v1-serving-cutover.test.js test/v1-current-memory-contract.test.js test/v1-scope-inheritance.golden.test.js test/v1-bootstrap-determinism.test.js test/consumer-codex.test.js test/codex-recovery-script.test.js test/codex-handoff.test.js",
|
|
75
|
+
"test:release:package": "node --test test/package-surface.test.js test/mcp-manifest.test.js test/local-backend.test.js test/scope-attribution.test.js test/doctor.test.js test/finalization-inspector.test.js test/memory-explain.test.js test/memory-review.test.js test/operator-observability.test.js test/storage-finalization-status.test.js test/cli-parseargs.test.js test/v1-checkpoint-ledger-schema.test.js test/v1-finalization-envelope-schema.test.js test/v1-memory-review-resolutions-schema.test.js test/v1-evidence-items.test.js test/v1-curated-semantic-recall.test.js test/session-checkpoints.test.js test/session-checkpoint-producer.test.js test/session-checkpoint-planner.test.js test/storage-checkpoint-ranges.test.js test/v1-serving-cutover.test.js test/v1-current-memory-contract.test.js test/v1-assistant-shaping-memory.test.js test/v1-assistant-shaping-review-explain.test.js test/v1-scope-inheritance.golden.test.js test/v1-bootstrap-determinism.test.js test/consumer-codex.test.js test/codex-recovery-script.test.js test/codex-handoff.test.js",
|
|
76
76
|
"test:release:db": "node -e \"if (!process.env.AQUIFER_TEST_DB_URL) { console.error('AQUIFER_TEST_DB_URL is required for test:release:db'); process.exit(1); }\" && node --test --test-concurrency=1 test/v1-evidence-items.test.js test/memory-review.integration.test.js test/consumer-mcp.integration.test.js test/consumer-cli.integration.test.js test/codex-finalization-serving.integration.test.js",
|
|
77
77
|
"lint": "eslint index.js core/*.js core/backends/*.js consumers/cli.js consumers/mcp.js consumers/claude-code.js consumers/codex.js consumers/codex-active-checkpoint.js consumers/codex-current-memory.js consumers/codex-handoff.js consumers/openclaw-install.js consumers/openclaw-plugin.js consumers/opencode.js consumers/shared/*.js consumers/default/*.js consumers/default/prompts/*.js consumers/openclaw-ext/*.js pipeline/*.js pipeline/consolidation/*.js scripts/*.js test/*.js",
|
|
78
78
|
"hooks:install": "git config core.hooksPath .githooks"
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
-- * consumer_profiles table — consumer schema registry with composite primary key
|
|
10
10
|
-- (tenant_id, consumer_id, version) for future multi-tenant safety
|
|
11
11
|
--
|
|
12
|
-
-- All identifiers stay parameterised on ${schema} so
|
|
13
|
-
--
|
|
12
|
+
-- All identifiers stay parameterised on ${schema} so schema renames remain a
|
|
13
|
+
-- config change rather than a DDL rewrite.
|
|
14
14
|
|
|
15
15
|
-- Ensure pg_trgm available (used by existing migrations; re-declared for independent
|
|
16
16
|
-- run safety).
|
|
@@ -141,8 +141,8 @@ CREATE TRIGGER trg_consumer_profiles_updated_at
|
|
|
141
141
|
EXECUTE FUNCTION ${schema}.set_updated_at();
|
|
142
142
|
|
|
143
143
|
-- timeline_events: append-only event log keyed by (tenant, agent, occurred_at).
|
|
144
|
-
-- category vocabulary is consumer-owned
|
|
145
|
-
--
|
|
144
|
+
-- category vocabulary is consumer-owned, event shape is strict core.
|
|
145
|
+
-- idempotency_key UNIQUE
|
|
146
146
|
-- across the table to make caller-driven dedupe safe.
|
|
147
147
|
CREATE TABLE IF NOT EXISTS ${schema}.timeline_events (
|
|
148
148
|
id BIGSERIAL PRIMARY KEY,
|
|
@@ -43,6 +43,78 @@ ALTER TABLE ${schema}.fact_assertions_v1
|
|
|
43
43
|
'tombstoned','quarantined','archived','incorrect'
|
|
44
44
|
));
|
|
45
45
|
|
|
46
|
+
ALTER TABLE ${schema}.memory_records
|
|
47
|
+
DROP CONSTRAINT IF EXISTS memory_records_lifecycle_consistency_check;
|
|
48
|
+
|
|
49
|
+
ALTER TABLE ${schema}.memory_records
|
|
50
|
+
ADD CONSTRAINT memory_records_lifecycle_consistency_check
|
|
51
|
+
CHECK (
|
|
52
|
+
(valid_to IS NULL OR valid_from IS NULL OR valid_to > valid_from)
|
|
53
|
+
AND (revoked_at IS NULL OR accepted_at IS NULL OR revoked_at >= accepted_at)
|
|
54
|
+
AND (superseded_at IS NULL OR accepted_at IS NULL OR superseded_at >= accepted_at)
|
|
55
|
+
AND NOT (revoked_at IS NOT NULL AND superseded_at IS NOT NULL)
|
|
56
|
+
AND (status <> 'active' OR (
|
|
57
|
+
revoked_at IS NULL
|
|
58
|
+
AND superseded_at IS NULL
|
|
59
|
+
AND superseded_by IS NULL
|
|
60
|
+
))
|
|
61
|
+
AND (status <> 'superseded' OR superseded_at IS NOT NULL)
|
|
62
|
+
AND (status <> 'revoked' OR revoked_at IS NOT NULL)
|
|
63
|
+
) NOT VALID;
|
|
64
|
+
|
|
65
|
+
ALTER TABLE ${schema}.fact_assertions_v1
|
|
66
|
+
DROP CONSTRAINT IF EXISTS fact_assertions_v1_lifecycle_consistency_check;
|
|
67
|
+
|
|
68
|
+
ALTER TABLE ${schema}.fact_assertions_v1
|
|
69
|
+
ADD CONSTRAINT fact_assertions_v1_lifecycle_consistency_check
|
|
70
|
+
CHECK (
|
|
71
|
+
(valid_to IS NULL OR valid_from IS NULL OR valid_to > valid_from)
|
|
72
|
+
AND (revoked_at IS NULL OR accepted_at IS NULL OR revoked_at >= accepted_at)
|
|
73
|
+
AND (superseded_at IS NULL OR accepted_at IS NULL OR superseded_at >= accepted_at)
|
|
74
|
+
AND NOT (revoked_at IS NOT NULL AND superseded_at IS NOT NULL)
|
|
75
|
+
AND (status <> 'active' OR (
|
|
76
|
+
revoked_at IS NULL
|
|
77
|
+
AND superseded_at IS NULL
|
|
78
|
+
AND superseded_by IS NULL
|
|
79
|
+
))
|
|
80
|
+
AND (status <> 'superseded' OR superseded_at IS NOT NULL)
|
|
81
|
+
AND (status <> 'revoked' OR revoked_at IS NOT NULL)
|
|
82
|
+
) NOT VALID;
|
|
83
|
+
|
|
84
|
+
DO $$
|
|
85
|
+
BEGIN
|
|
86
|
+
IF NOT EXISTS (
|
|
87
|
+
SELECT 1
|
|
88
|
+
FROM pg_constraint
|
|
89
|
+
WHERE conrelid = '${schema}.memory_records'::regclass
|
|
90
|
+
AND conname = 'memory_records_superseded_by_tenant_fk'
|
|
91
|
+
) THEN
|
|
92
|
+
ALTER TABLE ${schema}.memory_records
|
|
93
|
+
ADD CONSTRAINT memory_records_superseded_by_tenant_fk
|
|
94
|
+
FOREIGN KEY (tenant_id, superseded_by)
|
|
95
|
+
REFERENCES ${schema}.memory_records (tenant_id, id)
|
|
96
|
+
NOT VALID;
|
|
97
|
+
END IF;
|
|
98
|
+
END;
|
|
99
|
+
$$;
|
|
100
|
+
|
|
101
|
+
DO $$
|
|
102
|
+
BEGIN
|
|
103
|
+
IF NOT EXISTS (
|
|
104
|
+
SELECT 1
|
|
105
|
+
FROM pg_constraint
|
|
106
|
+
WHERE conrelid = '${schema}.fact_assertions_v1'::regclass
|
|
107
|
+
AND conname = 'fact_assertions_v1_superseded_by_tenant_fk'
|
|
108
|
+
) THEN
|
|
109
|
+
ALTER TABLE ${schema}.fact_assertions_v1
|
|
110
|
+
ADD CONSTRAINT fact_assertions_v1_superseded_by_tenant_fk
|
|
111
|
+
FOREIGN KEY (tenant_id, superseded_by)
|
|
112
|
+
REFERENCES ${schema}.fact_assertions_v1 (tenant_id, id)
|
|
113
|
+
NOT VALID;
|
|
114
|
+
END IF;
|
|
115
|
+
END;
|
|
116
|
+
$$;
|
|
117
|
+
|
|
46
118
|
-- =========================================================================
|
|
47
119
|
-- Row-level finalization lineage
|
|
48
120
|
-- =========================================================================
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
-- Aquifer v1 assistant-shaping memory layer
|
|
2
|
+
-- Requires: 007-v1-foundation.sql
|
|
3
|
+
-- Usage: replace ${schema} with actual schema name
|
|
4
|
+
--
|
|
5
|
+
-- Adds a product-level memory type for user-facing assistant shaping:
|
|
6
|
+
-- response style, retrieval policy, interaction boundaries, and tool routing.
|
|
7
|
+
-- This remains curated memory, not a host/persona-specific schema concept.
|
|
8
|
+
|
|
9
|
+
ALTER TABLE ${schema}.memory_records
|
|
10
|
+
DROP CONSTRAINT IF EXISTS memory_records_memory_type_check;
|
|
11
|
+
|
|
12
|
+
ALTER TABLE ${schema}.memory_records
|
|
13
|
+
ADD CONSTRAINT memory_records_memory_type_check
|
|
14
|
+
CHECK (memory_type IN (
|
|
15
|
+
'assistant_shaping',
|
|
16
|
+
'fact','state','decision','preference','constraint',
|
|
17
|
+
'entity_note','open_loop','conclusion'
|
|
18
|
+
));
|
|
19
|
+
|
|
20
|
+
CREATE INDEX IF NOT EXISTS idx_memory_records_assistant_shaping_bootstrap
|
|
21
|
+
ON ${schema}.memory_records (tenant_id, scope_id, status, accepted_at DESC, id)
|
|
22
|
+
WHERE status = 'active'
|
|
23
|
+
AND memory_type = 'assistant_shaping'
|
|
24
|
+
AND visible_in_bootstrap;
|
|
25
|
+
|
|
26
|
+
COMMENT ON CONSTRAINT memory_records_memory_type_check ON ${schema}.memory_records IS
|
|
27
|
+
'Allowed curated memory types. assistant_shaping stores user-facing assistant behavior and retrieval policy, not host-specific persona code.';
|
|
28
|
+
|
|
29
|
+
COMMENT ON INDEX ${schema}.idx_memory_records_assistant_shaping_bootstrap IS
|
|
30
|
+
'Fast path for high-priority assistant-shaping memories during bootstrap materialization.';
|
|
@@ -32,7 +32,7 @@ function printUsageAndExit(code = 0) {
|
|
|
32
32
|
'Usage: node scripts/backfill-canonical-key.js --schema <name> [options]',
|
|
33
33
|
'',
|
|
34
34
|
'Required:',
|
|
35
|
-
' --schema <name> Target schema (e.g.
|
|
35
|
+
' --schema <name> Target schema (e.g. aquifer, app_memory)',
|
|
36
36
|
' --agent <id> Limit to one agent (or use --all-agents)',
|
|
37
37
|
'',
|
|
38
38
|
'Optional:',
|
|
@@ -27,7 +27,7 @@ const SCHEMA = process.env.AQUIFER_SCHEMA || 'public';
|
|
|
27
27
|
|
|
28
28
|
const DEFAULT_QUERIES = [
|
|
29
29
|
// latin
|
|
30
|
-
'afterburn', 'bootstrap', 'session', 'recall', 'entity', 'OpenCode', '
|
|
30
|
+
'afterburn', 'bootstrap', 'session', 'recall', 'entity', 'OpenCode', 'ExampleUser', 'Aquifer',
|
|
31
31
|
// CJK short tokens — 最容易暴露 tokenizer 問題
|
|
32
32
|
'記憶', '時區', '去重', '架構', '修復',
|
|
33
33
|
// CJK phrase
|