@shadowforge0/aquifer-memory 1.5.9 → 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 (65) hide show
  1. package/.env.example +23 -0
  2. package/README.md +96 -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 +374 -39
  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 +131 -7
  14. package/consumers/openclaw-ext/index.js +0 -1
  15. package/consumers/openclaw-plugin.js +44 -4
  16. package/consumers/shared/config.js +28 -0
  17. package/consumers/shared/factory.js +2 -0
  18. package/consumers/shared/ingest.js +1 -1
  19. package/consumers/shared/normalize.js +14 -3
  20. package/consumers/shared/recall-format.js +53 -0
  21. package/consumers/shared/summary-parser.js +151 -0
  22. package/core/aquifer.js +384 -18
  23. package/core/finalization-review.js +319 -0
  24. package/core/insights.js +210 -58
  25. package/core/mcp-manifest.js +69 -2
  26. package/core/memory-bootstrap.js +188 -0
  27. package/core/memory-consolidation.js +1236 -0
  28. package/core/memory-promotion.js +544 -0
  29. package/core/memory-recall.js +247 -0
  30. package/core/memory-records.js +581 -0
  31. package/core/memory-safety-gate.js +224 -0
  32. package/core/session-finalization.js +350 -0
  33. package/core/storage.js +456 -2
  34. package/docs/getting-started.md +99 -0
  35. package/docs/postprocess-contract.md +2 -2
  36. package/docs/setup.md +51 -2
  37. package/package.json +31 -9
  38. package/pipeline/normalize/adapters/codex.js +106 -0
  39. package/pipeline/normalize/detect.js +3 -2
  40. package/schema/001-base.sql +3 -0
  41. package/schema/007-v1-foundation.sql +273 -0
  42. package/schema/008-session-finalizations.sql +50 -0
  43. package/schema/009-v1-assertion-plane.sql +193 -0
  44. package/schema/010-v1-finalization-review.sql +160 -0
  45. package/schema/011-v1-compaction-claim.sql +46 -0
  46. package/schema/012-v1-compaction-lease.sql +39 -0
  47. package/schema/013-v1-compaction-lineage.sql +193 -0
  48. package/scripts/backfill-canonical-key.js +250 -0
  49. package/scripts/codex-recovery.js +532 -0
  50. package/consumers/miranda/context-inject.js +0 -119
  51. package/consumers/miranda/daily-entries.js +0 -224
  52. package/consumers/miranda/index.js +0 -364
  53. package/consumers/miranda/instance.js +0 -55
  54. package/consumers/miranda/llm.js +0 -99
  55. package/consumers/miranda/profile.json +0 -145
  56. package/consumers/miranda/prompts/summary.js +0 -303
  57. package/consumers/miranda/recall-format.js +0 -76
  58. package/consumers/miranda/render-daily-md.js +0 -186
  59. package/consumers/miranda/workspace-files.js +0 -91
  60. package/scripts/drop-entity-state-history.sql +0 -17
  61. package/scripts/drop-insights.sql +0 -12
  62. package/scripts/install-openclaw.sh +0 -59
  63. package/scripts/queries.json +0 -45
  64. package/scripts/retro-recall-bench.js +0 -409
  65. package/scripts/sample-bench-queries.sql +0 -75
@@ -20,7 +20,7 @@ const MCP_SERVER_NAME = 'aquifer-memory';
20
20
  const MCP_TOOL_MANIFEST = Object.freeze([
21
21
  {
22
22
  name: 'session_recall',
23
- description: 'Search stored sessions by keyword or natural language. Use entities when the user names specific people, projects, files, tools, or concepts; entityMode="all" hard-filters to sessions containing every entity (default "any" boosts). Use mode to force fts/vector/hybrid (default hybrid). Use dateFrom/dateTo for time-bounded recall.',
23
+ description: 'Search Aquifer memory. When memory serving mode is curated, this searches active curated memory only; use evidence_recall for legacy session/evidence lookup. Use entities/date filters where supported by the active serving mode.',
24
24
  inputSchema: {
25
25
  type: 'object',
26
26
  additionalProperties: false,
@@ -46,13 +46,45 @@ const MCP_TOOL_MANIFEST = Object.freeze([
46
46
  enum: ['fts', 'hybrid', 'vector'],
47
47
  description: 'Recall mode: "fts" (keyword only, no embed needed), "hybrid" (default, FTS + vector), "vector" (vector only)',
48
48
  },
49
+ explain: {
50
+ type: 'boolean',
51
+ description: 'Include per-result score breakdown (rrf, timeDecay, entity, trust, rerank). Diagnostic use only.',
52
+ },
53
+ activeScopeKey: { type: 'string', description: 'Active curated memory scope key, e.g. project:aquifer' },
54
+ activeScopePath: {
55
+ type: 'array',
56
+ items: { type: 'string' },
57
+ description: 'Ordered curated scope path from global to active scope',
58
+ },
59
+ },
60
+ required: ['query'],
61
+ },
62
+ },
63
+ {
64
+ name: 'evidence_recall',
65
+ description: 'Explicit legacy/evidence search over stored sessions and summaries. This is for audit/debug/distillation and must not implicitly feed bootstrap.',
66
+ inputSchema: {
67
+ type: 'object',
68
+ additionalProperties: false,
69
+ properties: {
70
+ query: { type: 'string', minLength: 1, description: 'Evidence search query (keyword or natural language)' },
71
+ limit: { type: 'integer', minimum: 1, maximum: 20, description: 'Max results (default 5)' },
72
+ agentId: { type: 'string', description: 'Filter by agent ID' },
73
+ source: { type: 'string', description: 'Filter by source (e.g., gateway, cc)' },
74
+ dateFrom: { type: 'string', description: 'Start date YYYY-MM-DD' },
75
+ dateTo: { type: 'string', description: 'End date YYYY-MM-DD' },
76
+ entities: { type: 'array', items: { type: 'string' }, description: 'Entity names to match' },
77
+ entityMode: { type: 'string', enum: ['any', 'all'], description: '"any" (default, boost) or "all" hard filter' },
78
+ mode: { type: 'string', enum: ['fts', 'hybrid', 'vector'], description: 'Legacy evidence recall mode' },
79
+ explain: { type: 'boolean', description: 'Include diagnostic score breakdown.' },
80
+ allowUnsafeDebug: { type: 'boolean', description: 'Allow broad evidence/debug search without an audit boundary.' },
49
81
  },
50
82
  required: ['query'],
51
83
  },
52
84
  },
53
85
  {
54
86
  name: 'session_feedback',
55
- description: 'Record trust feedback on a recalled session. Helpful sessions rank higher in future recalls.',
87
+ description: 'After using legacy session_recall or bootstrap, mark the recalled session helpful or unhelpful. This only targets legacy session trust, not curated memory rows.',
56
88
  inputSchema: {
57
89
  type: 'object',
58
90
  additionalProperties: false,
@@ -65,6 +97,22 @@ const MCP_TOOL_MANIFEST = Object.freeze([
65
97
  required: ['sessionId', 'verdict'],
66
98
  },
67
99
  },
100
+ {
101
+ name: 'memory_feedback',
102
+ description: 'Record append-only feedback on a curated memory row. This affects curated ranking/review priority only and does not mutate memory truth.',
103
+ inputSchema: {
104
+ type: 'object',
105
+ additionalProperties: false,
106
+ properties: {
107
+ memoryId: { type: 'string', minLength: 1, description: 'Curated memory record ID to give feedback on' },
108
+ canonicalKey: { type: 'string', minLength: 1, description: 'Canonical key of the active curated memory record' },
109
+ feedbackType: { type: 'string', enum: ['helpful', 'confirm', 'irrelevant', 'scope_mismatch', 'stale', 'incorrect'], description: 'Curated memory feedback event type' },
110
+ note: { type: 'string', description: 'Optional reason' },
111
+ agentId: { type: 'string', description: 'Optional actor/agent label for audit metadata' },
112
+ },
113
+ required: ['feedbackType'],
114
+ },
115
+ },
68
116
  {
69
117
  name: 'memory_stats',
70
118
  description: 'Return storage statistics for the Aquifer memory store (session counts by status, summaries, turn embeddings, entities, date range).',
@@ -85,6 +133,19 @@ const MCP_TOOL_MANIFEST = Object.freeze([
85
133
  },
86
134
  },
87
135
  },
136
+ {
137
+ name: 'feedback_stats',
138
+ description: 'Return trust feedback statistics: total feedback count, helpful/unhelpful breakdown, trust score distribution, and coverage (how many sessions have been rated).',
139
+ inputSchema: {
140
+ type: 'object',
141
+ additionalProperties: false,
142
+ properties: {
143
+ agentId: { type: 'string', description: 'Filter by agent ID' },
144
+ dateFrom: { type: 'string', description: 'Start date YYYY-MM-DD for feedback window' },
145
+ dateTo: { type: 'string', description: 'End date YYYY-MM-DD for feedback window' },
146
+ },
147
+ },
148
+ },
88
149
  {
89
150
  name: 'session_bootstrap',
90
151
  description: 'Load recent session context for a new conversation. Returns summaries, open items, and decisions from recent sessions. Call this at the start of a conversation for continuity; use session_recall for keyword search.',
@@ -96,6 +157,12 @@ const MCP_TOOL_MANIFEST = Object.freeze([
96
157
  limit: { type: 'integer', minimum: 1, maximum: 20, description: 'Max sessions (default 5)' },
97
158
  lookbackDays: { type: 'integer', minimum: 1, maximum: 90, description: 'How far back in days (default 14)' },
98
159
  maxChars: { type: 'integer', minimum: 500, maximum: 12000, description: 'Max output characters (default 4000)' },
160
+ activeScopeKey: { type: 'string', description: 'Active curated memory scope key, e.g. project:aquifer' },
161
+ activeScopePath: {
162
+ type: 'array',
163
+ items: { type: 'string' },
164
+ description: 'Ordered curated scope path from global to active scope',
165
+ },
99
166
  },
100
167
  },
101
168
  },
@@ -0,0 +1,188 @@
1
+ 'use strict';
2
+
3
+ const TYPE_PRIORITY = {
4
+ constraint: 0,
5
+ preference: 1,
6
+ state: 2,
7
+ open_loop: 3,
8
+ decision: 4,
9
+ fact: 5,
10
+ conclusion: 6,
11
+ entity_note: 7,
12
+ };
13
+
14
+ const AUTHORITY_PRIORITY = {
15
+ user_explicit: 0,
16
+ executable_evidence: 1,
17
+ manual: 2,
18
+ system: 3,
19
+ verified_summary: 4,
20
+ llm_inference: 5,
21
+ raw_transcript: 6,
22
+ };
23
+
24
+ function recordId(record) {
25
+ return String(record.memoryId || record.memory_id || record.id || record.canonicalKey || record.canonical_key);
26
+ }
27
+
28
+ function scopeKey(record) {
29
+ return record.scopeKey || record.scope_key || record.scope || '';
30
+ }
31
+
32
+ function inheritanceMode(record) {
33
+ return record.inheritanceMode || record.inheritance_mode || record.scope_inheritance_mode || 'defaultable';
34
+ }
35
+
36
+ function canonicalKey(record) {
37
+ return record.canonicalKey || record.canonical_key || recordId(record);
38
+ }
39
+
40
+ function parseTime(value) {
41
+ const t = Date.parse(value || '');
42
+ return Number.isFinite(t) ? t : null;
43
+ }
44
+
45
+ function isWithinTime(record, asOf) {
46
+ if (!asOf) return true;
47
+ const at = Date.parse(asOf);
48
+ if (!Number.isFinite(at)) return true;
49
+ const validFrom = parseTime(record.validFrom || record.valid_from);
50
+ const validTo = parseTime(record.validTo || record.valid_to);
51
+ const staleAfter = parseTime(record.staleAfter || record.stale_after);
52
+ if (validFrom !== null && validFrom > at) return false;
53
+ if (validTo !== null && validTo <= at) return false;
54
+ if (staleAfter !== null && staleAfter <= at) return false;
55
+ return true;
56
+ }
57
+
58
+ function isActiveBootstrap(record, opts = {}) {
59
+ return (record.status || 'candidate') === 'active'
60
+ && (record.visibleInBootstrap ?? record.visible_in_bootstrap) === true
61
+ && isWithinTime(record, opts.asOf);
62
+ }
63
+
64
+ function resolveApplicableRecords(records = [], opts = {}) {
65
+ const activeScopePath = opts.activeScopePath || (opts.activeScopeKey ? [opts.activeScopeKey] : ['global']);
66
+ const activeScope = opts.activeScopeKey || activeScopePath[activeScopePath.length - 1] || null;
67
+ const position = new Map(activeScopePath.map((key, idx) => [key, idx]));
68
+ const additive = [];
69
+ const winners = new Map();
70
+
71
+ for (const record of records) {
72
+ const recScope = scopeKey(record);
73
+ const mode = inheritanceMode(record);
74
+ const isExact = activeScope && recScope === activeScope;
75
+ const isInPath = position.has(recScope);
76
+ if (mode === 'non_inheritable' && !isExact) continue;
77
+ if (mode !== 'non_inheritable' && activeScopePath.length > 0 && !isInPath) continue;
78
+
79
+ if (mode === 'additive') {
80
+ additive.push(record);
81
+ continue;
82
+ }
83
+
84
+ const key = canonicalKey(record);
85
+ const existing = winners.get(key);
86
+ if (!existing) {
87
+ winners.set(key, record);
88
+ continue;
89
+ }
90
+
91
+ const currentPos = position.get(recScope) ?? -1;
92
+ const existingPos = position.get(scopeKey(existing)) ?? -1;
93
+ if (currentPos > existingPos) winners.set(key, record);
94
+ }
95
+
96
+ return [...winners.values(), ...additive];
97
+ }
98
+
99
+ function sortForBootstrap(a, b) {
100
+ const aType = TYPE_PRIORITY[a.memoryType || a.memory_type] ?? 99;
101
+ const bType = TYPE_PRIORITY[b.memoryType || b.memory_type] ?? 99;
102
+ if (aType !== bType) return aType - bType;
103
+
104
+ const aAuth = AUTHORITY_PRIORITY[a.authority] ?? 99;
105
+ const bAuth = AUTHORITY_PRIORITY[b.authority] ?? 99;
106
+ if (aAuth !== bAuth) return aAuth - bAuth;
107
+
108
+ const aAccepted = Date.parse(a.acceptedAt || a.accepted_at || '') || 0;
109
+ const bAccepted = Date.parse(b.acceptedAt || b.accepted_at || '') || 0;
110
+ if (aAccepted !== bAccepted) return bAccepted - aAccepted;
111
+
112
+ return canonicalKey(a).localeCompare(canonicalKey(b));
113
+ }
114
+
115
+ function lineFor(record) {
116
+ const type = record.memoryType || record.memory_type || 'memory';
117
+ const text = record.summary || record.title || '';
118
+ return `- ${type}: ${String(text).trim()}`;
119
+ }
120
+
121
+ function buildText(records, meta) {
122
+ const lines = records.map(lineFor);
123
+ return [
124
+ `<memory-bootstrap memories="${records.length}" overflow="${meta.overflow}" degraded="${meta.degraded}">`,
125
+ ...lines,
126
+ '</memory-bootstrap>',
127
+ ].join('\n');
128
+ }
129
+
130
+ function buildMemoryBootstrap(records = [], opts = {}) {
131
+ const maxChars = Math.max(120, opts.maxChars || 4000);
132
+ const active = resolveApplicableRecords(
133
+ records.filter(record => isActiveBootstrap(record, opts)),
134
+ opts,
135
+ ).sort(sortForBootstrap);
136
+
137
+ const meta = {
138
+ overflow: false,
139
+ degraded: false,
140
+ maxChars,
141
+ count: active.length,
142
+ };
143
+
144
+ let selected = active.slice();
145
+ let text = buildText(selected, meta);
146
+ while (text.length > maxChars && selected.length > 1) {
147
+ selected = selected.slice(0, -1);
148
+ meta.overflow = true;
149
+ meta.degraded = true;
150
+ text = buildText(selected, meta);
151
+ }
152
+
153
+ if (text.length > maxChars) {
154
+ meta.overflow = true;
155
+ meta.degraded = true;
156
+ }
157
+
158
+ const structured = {
159
+ memories: selected,
160
+ meta: { ...meta, count: selected.length },
161
+ };
162
+
163
+ if (opts.format === 'text') return { ...structured, text };
164
+ if (opts.format === 'both' || opts.format === undefined) return { ...structured, text };
165
+ return structured;
166
+ }
167
+
168
+ function createMemoryBootstrap({ records }) {
169
+ async function bootstrap(opts = {}) {
170
+ const rows = await records.listActive({
171
+ tenantId: opts.tenantId,
172
+ scopeId: opts.scopeId,
173
+ scopeKeys: opts.activeScopePath || (opts.activeScopeKey ? [opts.activeScopeKey] : undefined),
174
+ visibleInBootstrap: true,
175
+ asOf: opts.asOf,
176
+ limit: opts.limit || 50,
177
+ });
178
+ return buildMemoryBootstrap(rows, opts);
179
+ }
180
+
181
+ return { bootstrap };
182
+ }
183
+
184
+ module.exports = {
185
+ buildMemoryBootstrap,
186
+ resolveApplicableRecords,
187
+ createMemoryBootstrap,
188
+ };