@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.
Files changed (43) hide show
  1. package/README.md +33 -4
  2. package/README_CN.md +9 -1
  3. package/README_TW.md +5 -2
  4. package/consumers/cli.js +55 -34
  5. package/consumers/codex-active-checkpoint.js +3 -1
  6. package/consumers/codex-current-memory.js +10 -6
  7. package/consumers/codex.js +5 -2
  8. package/consumers/default/daily-entries.js +2 -2
  9. package/consumers/default/index.js +40 -30
  10. package/consumers/default/prompts/summary.js +2 -2
  11. package/consumers/mcp.js +56 -49
  12. package/consumers/openclaw-ext/index.js +1 -1
  13. package/consumers/openclaw-ext/openclaw.plugin.json +1 -1
  14. package/consumers/openclaw-ext/package.json +1 -1
  15. package/consumers/openclaw-plugin.js +66 -23
  16. package/consumers/shared/compat-recall.js +101 -0
  17. package/consumers/shared/openclaw-product-tools.js +130 -0
  18. package/consumers/shared/recall-format.js +2 -2
  19. package/core/aquifer.js +385 -20
  20. package/core/backends/local.js +60 -1
  21. package/core/finalization-review.js +88 -42
  22. package/core/interface.js +629 -0
  23. package/core/mcp-manifest.js +11 -3
  24. package/core/memory-bootstrap.js +25 -27
  25. package/core/memory-consolidation.js +564 -42
  26. package/core/memory-explain.js +20 -51
  27. package/core/memory-promotion.js +392 -55
  28. package/core/memory-recall.js +26 -48
  29. package/core/memory-records.js +91 -103
  30. package/core/memory-type-policy.js +298 -0
  31. package/core/postgres-migrations.js +9 -0
  32. package/core/session-checkpoint-producer.js +3 -1
  33. package/core/session-checkpoints.js +1 -1
  34. package/core/session-finalization.js +2 -2
  35. package/docs/getting-started.md +16 -3
  36. package/docs/setup.md +61 -2
  37. package/package.json +2 -2
  38. package/schema/004-completion.sql +4 -4
  39. package/schema/010-v1-finalization-review.sql +72 -0
  40. package/schema/020-v1-assistant-shaping-memory.sql +30 -0
  41. package/scripts/backfill-canonical-key.js +1 -1
  42. package/scripts/diagnose-fts-zh.js +1 -1
  43. 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
  });
@@ -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
- | Show stats | `npx aquifer stats` |
108
- | Enrich pending sessions | `npx aquifer backfill` |
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
- { "state": "The reviewed state that should continue into current memory." }
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.0",
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 P4 schema rename
13
- -- (miranda → aquifer) is a one-line config change rather than a DDL rewrite.
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 (focus/todo/mood/handoff/narrative/cli
145
- -- for Miranda default), event shape is strict core. idempotency_key UNIQUE
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. miranda, jenny)',
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', 'Jenny', 'Aquifer',
30
+ 'afterburn', 'bootstrap', 'session', 'recall', 'entity', 'OpenCode', 'ExampleUser', 'Aquifer',
31
31
  // CJK short tokens — 最容易暴露 tokenizer 問題
32
32
  '記憶', '時區', '去重', '架構', '修復',
33
33
  // CJK phrase