@shadowforge0/aquifer-memory 1.5.12 → 1.7.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 +84 -73
- package/README_CN.md +676 -0
- package/README_TW.md +684 -0
- package/aquifer.config.example.json +34 -0
- package/consumers/claude-code.js +11 -11
- package/consumers/cli.js +421 -53
- package/consumers/codex-handoff.js +258 -0
- package/consumers/codex.js +1676 -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 +380 -18
- package/core/finalization-review.js +319 -0
- package/core/mcp-manifest.js +52 -2
- package/core/memory-bootstrap.js +200 -0
- package/core/memory-consolidation.js +1590 -0
- package/core/memory-promotion.js +544 -0
- package/core/memory-recall.js +247 -0
- package/core/memory-records.js +797 -0
- package/core/memory-safety-gate.js +224 -0
- package/core/session-finalization.js +365 -0
- package/core/storage.js +385 -2
- package/docs/getting-started.md +105 -0
- package/docs/postprocess-contract.md +2 -2
- package/docs/setup.md +92 -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 +672 -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
package/consumers/cli.js
CHANGED
|
@@ -11,10 +11,13 @@
|
|
|
11
11
|
* aquifer backfill [options] Enrich pending sessions
|
|
12
12
|
* aquifer stats [options] Show database statistics
|
|
13
13
|
* aquifer export [options] Export sessions
|
|
14
|
+
* aquifer operator ... Run operator-safe consolidation jobs
|
|
14
15
|
* aquifer mcp Start MCP server
|
|
15
16
|
*/
|
|
16
17
|
|
|
18
|
+
const fs = require('fs');
|
|
17
19
|
const { createAquiferFromConfig } = require('./shared/factory');
|
|
20
|
+
const { formatRecallResults } = require('./shared/recall-format');
|
|
18
21
|
|
|
19
22
|
function formatDate(value, fallback) {
|
|
20
23
|
if (!value) return fallback;
|
|
@@ -36,6 +39,130 @@ function parsePositiveInt(value, fallback) {
|
|
|
36
39
|
return Math.max(1, parsed);
|
|
37
40
|
}
|
|
38
41
|
|
|
42
|
+
function parseScopePath(value) {
|
|
43
|
+
if (!value) return undefined;
|
|
44
|
+
const parts = String(value).split(',').map(s => s.trim()).filter(Boolean);
|
|
45
|
+
return parts.length > 0 ? parts : undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseCsvList(value) {
|
|
49
|
+
if (!value) return undefined;
|
|
50
|
+
const parts = String(value).split(',').map(s => s.trim()).filter(Boolean);
|
|
51
|
+
return parts.length > 0 ? parts : undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function readJsonFlagValue(value, label) {
|
|
55
|
+
if (!value || value === true) {
|
|
56
|
+
throw new Error(`${label} requires a JSON value`);
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
return JSON.parse(String(value));
|
|
60
|
+
} catch (err) {
|
|
61
|
+
throw new Error(`${label} must be valid JSON: ${err.message}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function readJsonFlagFile(filePath, label) {
|
|
66
|
+
if (!filePath || filePath === true) {
|
|
67
|
+
throw new Error(`${label} requires a file path`);
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
71
|
+
} catch (err) {
|
|
72
|
+
throw new Error(`${label} must point to valid JSON: ${err.message}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function readSynthesisSummaryFromFlags(flags = {}) {
|
|
77
|
+
if (flags['synthesis-summary-file']) {
|
|
78
|
+
return readJsonFlagFile(flags['synthesis-summary-file'], '--synthesis-summary-file');
|
|
79
|
+
}
|
|
80
|
+
if (flags['synthesis-summary']) {
|
|
81
|
+
return readJsonFlagValue(flags['synthesis-summary'], '--synthesis-summary');
|
|
82
|
+
}
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function hasQuickstartEmbedConfig(env) {
|
|
87
|
+
return !!(
|
|
88
|
+
env.EMBED_PROVIDER
|
|
89
|
+
|| (env.AQUIFER_EMBED_BASE_URL && env.AQUIFER_EMBED_MODEL)
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function printQuickstartFailure(title, detailLines = []) {
|
|
94
|
+
console.error(` FAIL — ${title}`);
|
|
95
|
+
for (const line of detailLines) {
|
|
96
|
+
if (line) console.error(` ${line}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function buildQuickstartSetupHints(env, detected, err) {
|
|
101
|
+
const hints = [];
|
|
102
|
+
const message = err && err.message ? err.message : String(err || 'Unknown error');
|
|
103
|
+
const hasDb = !!(env.DATABASE_URL || env.AQUIFER_DB_URL || detected.DATABASE_URL);
|
|
104
|
+
const hasEmbed = hasQuickstartEmbedConfig(env)
|
|
105
|
+
|| !!detected.EMBED_PROVIDER;
|
|
106
|
+
|
|
107
|
+
if (/Database URL is required/i.test(message)) {
|
|
108
|
+
hints.push('Quickstart could not find a PostgreSQL connection.');
|
|
109
|
+
if (!hasDb) {
|
|
110
|
+
hints.push('If you expect local defaults, make sure PostgreSQL is running on localhost:5432.');
|
|
111
|
+
hints.push('Otherwise set DATABASE_URL or AQUIFER_DB_URL explicitly and run quickstart again.');
|
|
112
|
+
}
|
|
113
|
+
return hints;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (/OPENAI_API_KEY/i.test(message)) {
|
|
117
|
+
hints.push('OpenAI embeddings were selected, but OPENAI_API_KEY is not set.');
|
|
118
|
+
hints.push('Export OPENAI_API_KEY or switch EMBED_PROVIDER back to ollama for local quickstart.');
|
|
119
|
+
return hints;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!hasDb || !hasEmbed) {
|
|
123
|
+
hints.push('Quickstart is missing part of the local setup.');
|
|
124
|
+
if (!hasDb) hints.push('PostgreSQL was not autodetected and no DATABASE_URL is set.');
|
|
125
|
+
if (!hasEmbed) hints.push('No embedding provider was autodetected and no embed env is set.');
|
|
126
|
+
hints.push('Try `docker compose up -d`, then run `npx aquifer quickstart` again.');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
hints.push(`Raw error: ${message}`);
|
|
130
|
+
return hints;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function buildQuickstartRecallHints(err) {
|
|
134
|
+
const message = err && err.message ? err.message : String(err || 'Unknown error');
|
|
135
|
+
|
|
136
|
+
if (/requires config\.embed\.fn|EMBED_PROVIDER/i.test(message)) {
|
|
137
|
+
return [
|
|
138
|
+
'Quickstart reached recall, but embeddings are not configured.',
|
|
139
|
+
'Set EMBED_PROVIDER=ollama for local Ollama, or EMBED_PROVIDER=openai with OPENAI_API_KEY.',
|
|
140
|
+
`Raw error: ${message}`,
|
|
141
|
+
];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (/OPENAI_API_KEY/i.test(message)) {
|
|
145
|
+
return [
|
|
146
|
+
'Recall is configured to use OpenAI embeddings, but OPENAI_API_KEY is missing.',
|
|
147
|
+
'Export OPENAI_API_KEY and rerun quickstart.',
|
|
148
|
+
`Raw error: ${message}`,
|
|
149
|
+
];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (/ECONNREFUSED|ENOTFOUND|fetch failed|connect/i.test(message)) {
|
|
153
|
+
return [
|
|
154
|
+
'Aquifer could not reach the embedding service during recall.',
|
|
155
|
+
'If you expect local Ollama, make sure it is running and the model is available.',
|
|
156
|
+
`Raw error: ${message}`,
|
|
157
|
+
];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return [
|
|
161
|
+
'Aquifer could not recall the quickstart test session.',
|
|
162
|
+
`Raw error: ${message}`,
|
|
163
|
+
];
|
|
164
|
+
}
|
|
165
|
+
|
|
39
166
|
// ---------------------------------------------------------------------------
|
|
40
167
|
// Argument parser (minimal, no deps)
|
|
41
168
|
// ---------------------------------------------------------------------------
|
|
@@ -43,7 +170,15 @@ function parsePositiveInt(value, fallback) {
|
|
|
43
170
|
function parseArgs(argv) {
|
|
44
171
|
const args = { _: [], flags: {} };
|
|
45
172
|
// Flags that take a value (not boolean)
|
|
46
|
-
const VALUE_FLAGS = new Set([
|
|
173
|
+
const VALUE_FLAGS = new Set([
|
|
174
|
+
'limit', 'agent-id', 'source', 'date-from', 'date-to', 'output', 'format', 'config', 'status',
|
|
175
|
+
'concurrency', 'entities', 'entity-mode', 'mode', 'session-id', 'memory-id', 'canonical-key',
|
|
176
|
+
'verdict', 'feedback-type', 'note', 'db', 'since', 'min-messages', 'lookback-days', 'max-chars',
|
|
177
|
+
'out', 'active-scope-key', 'active-scope-path', 'cadence', 'period-start', 'period-end',
|
|
178
|
+
'policy-version', 'worker-id', 'apply-token', 'claim-lease-seconds', 'snapshot-as-of',
|
|
179
|
+
'scope-key', 'scope-keys', 'scope-kind', 'context-key', 'topic-key', 'authority', 'input',
|
|
180
|
+
'synthesis-summary', 'synthesis-summary-file',
|
|
181
|
+
]);
|
|
47
182
|
for (let i = 0; i < argv.length; i++) {
|
|
48
183
|
if (argv[i] === '--') { args._.push(...argv.slice(i + 1)); break; }
|
|
49
184
|
if (argv[i].startsWith('--')) {
|
|
@@ -76,13 +211,13 @@ async function cmdRecall(aquifer, args) {
|
|
|
76
211
|
process.exit(1);
|
|
77
212
|
}
|
|
78
213
|
|
|
79
|
-
const recallOpts = {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
214
|
+
const recallOpts = { limit: parsePositiveInt(args.flags.limit, 5) };
|
|
215
|
+
if (args.flags['agent-id']) recallOpts.agentId = args.flags['agent-id'];
|
|
216
|
+
if (args.flags.source) recallOpts.source = args.flags.source;
|
|
217
|
+
if (args.flags['date-from']) recallOpts.dateFrom = args.flags['date-from'];
|
|
218
|
+
if (args.flags['date-to']) recallOpts.dateTo = args.flags['date-to'];
|
|
219
|
+
if (args.flags['active-scope-key']) recallOpts.activeScopeKey = args.flags['active-scope-key'];
|
|
220
|
+
if (args.flags['active-scope-path']) recallOpts.activeScopePath = parseScopePath(args.flags['active-scope-path']);
|
|
86
221
|
if (args.flags.entities) {
|
|
87
222
|
recallOpts.entities = args.flags.entities.split(',').map(s => s.trim()).filter(Boolean);
|
|
88
223
|
recallOpts.entityMode = args.flags['entity-mode'] || 'any';
|
|
@@ -94,39 +229,20 @@ async function cmdRecall(aquifer, args) {
|
|
|
94
229
|
return;
|
|
95
230
|
}
|
|
96
231
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const showExplain = !!args.flags.explain;
|
|
103
|
-
for (let i = 0; i < results.length; i++) {
|
|
104
|
-
const r = results[i];
|
|
105
|
-
const ss = r.structuredSummary || {};
|
|
106
|
-
const title = ss.title || r.summaryText?.slice(0, 60) || '(untitled)';
|
|
107
|
-
const date = formatDate(r.startedAt, '?');
|
|
108
|
-
console.log(`${i + 1}. [${r.score?.toFixed(3)}] ${title} (${date}, ${r.agentId})`);
|
|
109
|
-
if (ss.overview) console.log(` ${ss.overview.slice(0, 200)}`);
|
|
110
|
-
if (r.matchedTurnText) console.log(` > ${r.matchedTurnText.slice(0, 150)}`);
|
|
111
|
-
if (showExplain && r._debug) {
|
|
112
|
-
const d = r._debug;
|
|
113
|
-
const f = (v) => typeof v === 'number' ? v.toFixed(3) : '?';
|
|
114
|
-
const parts = [
|
|
115
|
-
`rrf=${f(d.rrf)}`, `td=${f(d.timeDecay)}`, `access=${f(d.access)}`,
|
|
116
|
-
`entity=${f(d.entityScore)}`, `trust=${f(d.trustScore)}(\u00d7${f(d.trustMultiplier)})`,
|
|
117
|
-
`ol=${f(d.openLoopBoost)}`, `\u2192 hybrid=${f(d.hybridScore)}`,
|
|
118
|
-
];
|
|
119
|
-
if (d.rerankApplied) parts.push(`rerank=${f(d.rerankScore)}(${d.rerankReason || '?'})`);
|
|
120
|
-
else parts.push(`[rerank: off (${d.rerankReason || '?'})]`);
|
|
121
|
-
console.log(` ${parts.join(' ')}`);
|
|
122
|
-
}
|
|
123
|
-
console.log();
|
|
124
|
-
}
|
|
232
|
+
console.log(formatRecallResults(results, {
|
|
233
|
+
query,
|
|
234
|
+
showScore: true,
|
|
235
|
+
showExplain: !!args.flags.explain,
|
|
236
|
+
}));
|
|
125
237
|
}
|
|
126
238
|
|
|
127
239
|
async function cmdFeedback(aquifer, args) {
|
|
128
240
|
const sessionId = args.flags['session-id'] || args._[1];
|
|
129
241
|
const verdict = args.flags.verdict;
|
|
242
|
+
if (args.flags['memory-id'] || args.flags['canonical-key'] || args.flags['feedback-type']) {
|
|
243
|
+
console.error('Use `aquifer memory-feedback` for curated memory feedback.');
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
130
246
|
|
|
131
247
|
if (!sessionId || !verdict) {
|
|
132
248
|
console.error('Usage: aquifer feedback --session-id ID --verdict helpful|unhelpful [--note TEXT] [--agent-id ID]');
|
|
@@ -146,6 +262,66 @@ async function cmdFeedback(aquifer, args) {
|
|
|
146
262
|
}
|
|
147
263
|
}
|
|
148
264
|
|
|
265
|
+
async function cmdMemoryFeedback(aquifer, args) {
|
|
266
|
+
const memoryId = args.flags['memory-id'];
|
|
267
|
+
const canonicalKey = args.flags['canonical-key'];
|
|
268
|
+
const feedbackType = args.flags['feedback-type'];
|
|
269
|
+
|
|
270
|
+
if ((!memoryId && !canonicalKey) || !feedbackType) {
|
|
271
|
+
console.error('Usage: aquifer memory-feedback (--memory-id ID | --canonical-key KEY) --feedback-type helpful|confirm|irrelevant|scope_mismatch|stale|incorrect [--note TEXT] [--agent-id ID]');
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const result = await aquifer.memoryFeedback({
|
|
276
|
+
memoryId: memoryId || undefined,
|
|
277
|
+
canonicalKey: canonicalKey || undefined,
|
|
278
|
+
}, {
|
|
279
|
+
feedbackType,
|
|
280
|
+
agentId: args.flags['agent-id'] || undefined,
|
|
281
|
+
note: args.flags.note || undefined,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
if (args.flags.json) {
|
|
285
|
+
console.log(JSON.stringify(result, null, 2));
|
|
286
|
+
} else {
|
|
287
|
+
const target = result.canonicalKey || result.memoryId;
|
|
288
|
+
console.log(`Memory feedback: ${result.feedbackType} (${target})`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async function cmdEvidenceRecall(aquifer, args) {
|
|
293
|
+
const query = args._.slice(1).join(' ');
|
|
294
|
+
if (!query) {
|
|
295
|
+
console.error('Usage: aquifer evidence-recall <query> [--limit N] [--agent-id ID] [--allow-unsafe-debug] [--json]');
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const recallOpts = { limit: parsePositiveInt(args.flags.limit, 5) };
|
|
300
|
+
if (args.flags['agent-id']) recallOpts.agentId = args.flags['agent-id'];
|
|
301
|
+
if (args.flags.source) recallOpts.source = args.flags.source;
|
|
302
|
+
if (args.flags['date-from']) recallOpts.dateFrom = args.flags['date-from'];
|
|
303
|
+
if (args.flags['date-to']) recallOpts.dateTo = args.flags['date-to'];
|
|
304
|
+
if (args.flags.entities) {
|
|
305
|
+
recallOpts.entities = args.flags.entities.split(',').map(s => s.trim()).filter(Boolean);
|
|
306
|
+
recallOpts.entityMode = args.flags['entity-mode'] || 'any';
|
|
307
|
+
}
|
|
308
|
+
if (args.flags.mode) recallOpts.mode = args.flags.mode;
|
|
309
|
+
if (args.flags['allow-unsafe-debug']) recallOpts.allowUnsafeDebug = true;
|
|
310
|
+
|
|
311
|
+
const results = await aquifer.evidenceRecall(query, recallOpts);
|
|
312
|
+
|
|
313
|
+
if (args.flags.json) {
|
|
314
|
+
console.log(JSON.stringify(results, null, 2));
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
console.log(formatRecallResults(results, {
|
|
319
|
+
query,
|
|
320
|
+
showScore: true,
|
|
321
|
+
showExplain: !!args.flags.explain,
|
|
322
|
+
}));
|
|
323
|
+
}
|
|
324
|
+
|
|
149
325
|
async function cmdFeedbackStats(aquifer, args) {
|
|
150
326
|
const stats = await aquifer.feedbackStats({
|
|
151
327
|
agentId: args.flags['agent-id'] || undefined,
|
|
@@ -238,13 +414,39 @@ async function cmdQuickstart(aquifer) {
|
|
|
238
414
|
skipSummary: true,
|
|
239
415
|
skipEntities: true,
|
|
240
416
|
});
|
|
417
|
+
if (Array.isArray(enrichResult.warnings) && enrichResult.warnings.length > 0) {
|
|
418
|
+
printQuickstartFailure('embedding step returned warnings.', [
|
|
419
|
+
'Quickstart expects turn embeddings to succeed cleanly.',
|
|
420
|
+
...enrichResult.warnings.map(w => `Warning: ${w}`),
|
|
421
|
+
]);
|
|
422
|
+
process.exitCode = 1;
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
if (!Number.isFinite(enrichResult.turnsEmbedded) || enrichResult.turnsEmbedded <= 0) {
|
|
426
|
+
printQuickstartFailure('0 turns were embedded.', [
|
|
427
|
+
'The quickstart test session contains user turns, so this usually means the embedding setup is not working.',
|
|
428
|
+
'Check EMBED_PROVIDER / AQUIFER_EMBED_* settings, or make sure Ollama/OpenAI is reachable.',
|
|
429
|
+
]);
|
|
430
|
+
process.exitCode = 1;
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
241
433
|
console.log(` OK — ${enrichResult.turnsEmbedded} turns embedded\n`);
|
|
242
434
|
|
|
243
435
|
// 4. Recall
|
|
244
436
|
console.log('4/5 Recalling "PostgreSQL memory store"...');
|
|
245
|
-
|
|
437
|
+
let results;
|
|
438
|
+
try {
|
|
439
|
+
results = await aquifer.recall('PostgreSQL memory store', { limit: 3 });
|
|
440
|
+
} catch (err) {
|
|
441
|
+
printQuickstartFailure('recall step failed.', buildQuickstartRecallHints(err));
|
|
442
|
+
process.exitCode = 1;
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
246
445
|
if (results.length === 0) {
|
|
247
|
-
|
|
446
|
+
printQuickstartFailure('quickstart could not recall its own test session.', [
|
|
447
|
+
'The write step succeeded, but the test query returned no matches.',
|
|
448
|
+
'This usually means the embedding path is misconfigured or the embed service is not reachable.',
|
|
449
|
+
]);
|
|
248
450
|
process.exitCode = 1;
|
|
249
451
|
return;
|
|
250
452
|
}
|
|
@@ -282,14 +484,17 @@ async function cmdQuickstart(aquifer) {
|
|
|
282
484
|
}
|
|
283
485
|
|
|
284
486
|
async function cmdBootstrap(aquifer, args) {
|
|
285
|
-
const
|
|
286
|
-
agentId: args.flags['agent-id'] || undefined,
|
|
287
|
-
source: args.flags.source || undefined,
|
|
487
|
+
const bootstrapOpts = {
|
|
288
488
|
limit: parsePositiveInt(args.flags.limit, 5),
|
|
289
|
-
lookbackDays: parsePositiveInt(args.flags['lookback-days'], 14),
|
|
290
489
|
maxChars: parsePositiveInt(args.flags['max-chars'], 4000),
|
|
291
490
|
format: args.flags.json ? 'structured' : 'text',
|
|
292
|
-
}
|
|
491
|
+
};
|
|
492
|
+
if (args.flags['agent-id']) bootstrapOpts.agentId = args.flags['agent-id'];
|
|
493
|
+
if (args.flags.source) bootstrapOpts.source = args.flags.source;
|
|
494
|
+
if (args.flags['lookback-days']) bootstrapOpts.lookbackDays = parsePositiveInt(args.flags['lookback-days'], 14);
|
|
495
|
+
if (args.flags['active-scope-key']) bootstrapOpts.activeScopeKey = args.flags['active-scope-key'];
|
|
496
|
+
if (args.flags['active-scope-path']) bootstrapOpts.activeScopePath = parseScopePath(args.flags['active-scope-path']);
|
|
497
|
+
const result = await aquifer.bootstrap(bootstrapOpts);
|
|
293
498
|
|
|
294
499
|
if (args.flags.json) {
|
|
295
500
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -305,6 +510,102 @@ async function cmdBootstrap(aquifer, args) {
|
|
|
305
510
|
}
|
|
306
511
|
}
|
|
307
512
|
|
|
513
|
+
async function cmdOperator(aquifer, args) {
|
|
514
|
+
const operatorVerb = args._[1] || 'compaction';
|
|
515
|
+
const cadenceVerbs = new Set(['manual', 'daily', 'weekly', 'monthly']);
|
|
516
|
+
|
|
517
|
+
if (operatorVerb === 'archive-distill') {
|
|
518
|
+
const inputPath = args.flags.input || args._[2];
|
|
519
|
+
if (!inputPath) {
|
|
520
|
+
console.error('Usage: aquifer operator archive-distill --input /path/to/archive.json [--authority verified_summary] [--json]');
|
|
521
|
+
process.exit(1);
|
|
522
|
+
}
|
|
523
|
+
const archiveSnapshot = JSON.parse(fs.readFileSync(inputPath, 'utf8'));
|
|
524
|
+
const result = await aquifer.memory.consolidation.runJob({
|
|
525
|
+
job: 'archive-distill',
|
|
526
|
+
archiveSnapshot,
|
|
527
|
+
authority: args.flags.authority || undefined,
|
|
528
|
+
scopeKind: args.flags['scope-kind'] || undefined,
|
|
529
|
+
scopeKey: args.flags['scope-key'] || undefined,
|
|
530
|
+
contextKey: args.flags['context-key'] || undefined,
|
|
531
|
+
topicKey: args.flags['topic-key'] || undefined,
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
if (args.flags.json) {
|
|
535
|
+
console.log(JSON.stringify(result, null, 2));
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const sessionCount = Array.isArray(archiveSnapshot.sessions) ? archiveSnapshot.sessions.length : 0;
|
|
540
|
+
console.log(`Archive distill planned from ${sessionCount} sessions.`);
|
|
541
|
+
console.log(`Candidates: ${result.candidates.length}`);
|
|
542
|
+
console.log('Visibility: recall/bootstrap hidden until a separate promotion step.');
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (operatorVerb !== 'compaction' && operatorVerb !== 'compact' && !cadenceVerbs.has(operatorVerb)) {
|
|
547
|
+
console.error('Usage: aquifer operator <compaction|archive-distill> [...]');
|
|
548
|
+
process.exit(1);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const cadence = args.flags.cadence
|
|
552
|
+
|| (cadenceVerbs.has(operatorVerb) ? operatorVerb : args._[2])
|
|
553
|
+
|| 'manual';
|
|
554
|
+
const synthesisSummary = readSynthesisSummaryFromFlags(args.flags);
|
|
555
|
+
const result = await aquifer.memory.consolidation.runJob({
|
|
556
|
+
job: 'compaction',
|
|
557
|
+
cadence,
|
|
558
|
+
periodStart: args.flags['period-start'] || undefined,
|
|
559
|
+
periodEnd: args.flags['period-end'] || undefined,
|
|
560
|
+
policyVersion: args.flags['policy-version'] || undefined,
|
|
561
|
+
workerId: args.flags['worker-id'] || undefined,
|
|
562
|
+
applyToken: args.flags['apply-token'] || undefined,
|
|
563
|
+
claimLeaseSeconds: args.flags['claim-lease-seconds']
|
|
564
|
+
? parsePositiveInt(args.flags['claim-lease-seconds'], undefined)
|
|
565
|
+
: undefined,
|
|
566
|
+
snapshotAsOf: args.flags['snapshot-as-of'] || undefined,
|
|
567
|
+
scopeKeys: parseCsvList(args.flags['scope-keys'] || args.flags['scope-key']),
|
|
568
|
+
scopeKind: args.flags['scope-kind'] || undefined,
|
|
569
|
+
scopeKey: args.flags['scope-key'] || undefined,
|
|
570
|
+
contextKey: args.flags['context-key'] || undefined,
|
|
571
|
+
topicKey: args.flags['topic-key'] || undefined,
|
|
572
|
+
activeScopeKey: args.flags['active-scope-key'] || undefined,
|
|
573
|
+
activeScopePath: parseScopePath(args.flags['active-scope-path']),
|
|
574
|
+
limit: parsePositiveInt(args.flags.limit, 1000),
|
|
575
|
+
apply: args.flags.apply === true,
|
|
576
|
+
promoteCandidates: args.flags['promote-candidates'] === true,
|
|
577
|
+
includeSynthesisPrompt: args.flags['include-synthesis-prompt'] === true,
|
|
578
|
+
synthesisSummary,
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
if (args.flags.json) {
|
|
582
|
+
console.log(JSON.stringify(result, null, 2));
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
console.log(`${result.dryRun ? 'Planned' : 'Executed'} ${result.cadence} compaction window ${result.periodStart} -> ${result.periodEnd}`);
|
|
587
|
+
console.log(`Snapshot: ${result.snapshotCount} active rows${result.snapshotTruncated ? ' (snapshot limit reached)' : ''}`);
|
|
588
|
+
console.log(`Plan: ${result.plan.statusUpdates.length} lifecycle updates, ${result.plan.candidates.length} candidates`);
|
|
589
|
+
if (result.synthesisPrompt) {
|
|
590
|
+
console.log('\nSynthesis prompt:\n');
|
|
591
|
+
console.log(result.synthesisPrompt);
|
|
592
|
+
}
|
|
593
|
+
if (result.promotionReview) {
|
|
594
|
+
console.log(`\n${result.promotionReview}`);
|
|
595
|
+
}
|
|
596
|
+
if (result.dryRun) {
|
|
597
|
+
console.log('Mode: dry-run only. Re-run with --apply to write compaction_runs and lifecycle changes.');
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
if (result.run) {
|
|
601
|
+
console.log(`Run: #${result.run.id} status=${result.run.status}`);
|
|
602
|
+
} else if (result.existingRun) {
|
|
603
|
+
console.log(`Existing run: #${result.existingRun.id} status=${result.existingRun.status} reason=${result.skipReason || 'claim_not_acquired'}`);
|
|
604
|
+
} else {
|
|
605
|
+
console.log(`No run claimed: ${result.skipReason || 'claim_not_acquired'}`);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
308
609
|
async function cmdExport(aquifer, args) {
|
|
309
610
|
const output = args.flags.output || null;
|
|
310
611
|
const limit = parsePositiveInt(args.flags.limit, 1000);
|
|
@@ -333,6 +634,10 @@ async function cmdExport(aquifer, args) {
|
|
|
333
634
|
}
|
|
334
635
|
}
|
|
335
636
|
|
|
637
|
+
async function cmdCompact(aquifer, args) {
|
|
638
|
+
return cmdOperator(aquifer, { ...args, _: ['operator', 'compaction', ...args._.slice(1)] });
|
|
639
|
+
}
|
|
640
|
+
|
|
336
641
|
// ---------------------------------------------------------------------------
|
|
337
642
|
// Main
|
|
338
643
|
// ---------------------------------------------------------------------------
|
|
@@ -343,17 +648,22 @@ async function main() {
|
|
|
343
648
|
console.log(`Usage: aquifer <command> [options]
|
|
344
649
|
|
|
345
650
|
Commands:
|
|
346
|
-
|
|
347
|
-
|
|
651
|
+
quickstart Verify end-to-end setup (migrate → commit → enrich → recall)
|
|
652
|
+
migrate Run database migrations
|
|
348
653
|
recall <query> Search sessions (requires embed config)
|
|
654
|
+
evidence-recall <query> Search legacy session/evidence plane explicitly
|
|
349
655
|
feedback Record trust feedback on a session
|
|
656
|
+
memory-feedback Record curated memory feedback
|
|
350
657
|
feedback-stats Show trust feedback statistics and coverage
|
|
351
658
|
backfill Enrich pending sessions
|
|
659
|
+
operator ... Run operator-safe consolidation jobs
|
|
660
|
+
compact Plan or apply curated memory compaction
|
|
352
661
|
stats Show database statistics
|
|
353
662
|
export Export sessions as JSONL
|
|
354
663
|
bootstrap Show recent session context (for new session start)
|
|
355
|
-
|
|
356
|
-
|
|
664
|
+
codex-recovery ... Inspect or run Codex SessionStart recovery flow
|
|
665
|
+
ingest-opencode Import sessions from OpenCode's local SQLite DB
|
|
666
|
+
mcp Start MCP server
|
|
357
667
|
|
|
358
668
|
Options:
|
|
359
669
|
--limit N Limit results
|
|
@@ -364,23 +674,55 @@ Options:
|
|
|
364
674
|
--entities A,B,C Entity names (comma-separated, recall)
|
|
365
675
|
--entity-mode any|all Entity match mode (recall, default: any)
|
|
366
676
|
--session-id ID Session ID (feedback)
|
|
677
|
+
--memory-id ID Curated memory record ID (memory-feedback)
|
|
678
|
+
--canonical-key KEY Active curated memory canonical key (memory-feedback)
|
|
367
679
|
--verdict helpful|unhelpful Feedback verdict (feedback)
|
|
680
|
+
--feedback-type TYPE Curated memory feedback type
|
|
368
681
|
--note TEXT Feedback note (feedback)
|
|
369
682
|
--explain Show score breakdown per result (recall)
|
|
683
|
+
--allow-unsafe-debug Allow broad evidence-recall without audit boundary
|
|
370
684
|
--json JSON output
|
|
371
685
|
--dry-run Preview only (backfill)
|
|
372
686
|
--output PATH Output file (export)
|
|
373
687
|
--config PATH Config file path
|
|
374
688
|
--lookback-days N How far back in days (bootstrap, default: 14)
|
|
375
689
|
--max-chars N Max output characters (bootstrap, default: 4000)
|
|
690
|
+
--active-scope-key KEY Active curated memory scope key
|
|
691
|
+
--active-scope-path A,B Ordered curated scope path
|
|
692
|
+
--cadence manual|daily|weekly|monthly
|
|
693
|
+
--period-start ISO Compaction window start
|
|
694
|
+
--period-end ISO Compaction window end
|
|
695
|
+
--apply Apply compaction; default is dry-run
|
|
696
|
+
--promote-candidates Promote compaction/synthesis candidates when applying
|
|
697
|
+
--include-synthesis-prompt Include timer synthesis prompt in operator output
|
|
698
|
+
--synthesis-summary JSON Timer synthesis summary JSON to attach to a compaction plan
|
|
699
|
+
--synthesis-summary-file P Read timer synthesis summary JSON from file
|
|
700
|
+
--scope-key A,B Limit compaction snapshot to specific scope keys
|
|
701
|
+
--scope-kind KIND Explicit synthesis target scope kind
|
|
702
|
+
--snapshot-as-of ISO Read active snapshot as of a specific instant
|
|
703
|
+
--claim-lease-seconds N Override compaction apply lease
|
|
704
|
+
--input PATH Archive distill input JSON path
|
|
376
705
|
--db PATH OpenCode SQLite path (ingest-opencode)
|
|
377
706
|
--since YYYY-MM-DD Only ingest sessions after date (ingest-opencode)
|
|
378
|
-
--min-messages N Min user messages to ingest (ingest-opencode, default: 3)
|
|
707
|
+
--min-messages N Min user messages to ingest (ingest-opencode, default: 3)
|
|
708
|
+
|
|
709
|
+
Operator examples:
|
|
710
|
+
aquifer operator compaction daily --json
|
|
711
|
+
aquifer operator compaction daily --include-synthesis-prompt --json
|
|
712
|
+
aquifer operator compaction manual --period-start 2026-04-27T00:00:00Z --period-end 2026-04-28T00:00:00Z --apply
|
|
713
|
+
aquifer operator compaction daily --synthesis-summary-file /tmp/timer-summary.json --apply --promote-candidates --json
|
|
714
|
+
aquifer operator archive-distill --input /tmp/archive-snapshot.json --json`);
|
|
379
715
|
process.exit(0);
|
|
380
716
|
}
|
|
381
717
|
|
|
382
718
|
const command = argv[0];
|
|
383
719
|
const args = parseArgs(argv);
|
|
720
|
+
let quickstartDetected = {};
|
|
721
|
+
|
|
722
|
+
if (command === 'codex-recovery') {
|
|
723
|
+
await require('../scripts/codex-recovery').main(argv.slice(1));
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
384
726
|
|
|
385
727
|
// MCP: delegate to mcp.js
|
|
386
728
|
if (command === 'mcp') {
|
|
@@ -414,10 +756,10 @@ Options:
|
|
|
414
756
|
// the operator to have set env explicitly.
|
|
415
757
|
if (command === 'quickstart') {
|
|
416
758
|
const { autodetectForQuickstart } = require('./shared/autodetect');
|
|
417
|
-
|
|
418
|
-
if (Object.keys(
|
|
759
|
+
quickstartDetected = await autodetectForQuickstart(process.env);
|
|
760
|
+
if (Object.keys(quickstartDetected).length > 0) {
|
|
419
761
|
console.log('Autodetected localhost services (env not set):');
|
|
420
|
-
for (const [k, v] of Object.entries(
|
|
762
|
+
for (const [k, v] of Object.entries(quickstartDetected)) {
|
|
421
763
|
console.log(` ${k}=${v}`);
|
|
422
764
|
process.env[k] = v;
|
|
423
765
|
}
|
|
@@ -425,7 +767,17 @@ Options:
|
|
|
425
767
|
}
|
|
426
768
|
}
|
|
427
769
|
|
|
428
|
-
|
|
770
|
+
let aquifer;
|
|
771
|
+
try {
|
|
772
|
+
aquifer = createAquiferFromConfig(configOverrides);
|
|
773
|
+
} catch (err) {
|
|
774
|
+
if (command === 'quickstart') {
|
|
775
|
+
printQuickstartFailure('setup check failed before quickstart could start.', buildQuickstartSetupHints(process.env, quickstartDetected, err));
|
|
776
|
+
process.exit(1);
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
throw err;
|
|
780
|
+
}
|
|
429
781
|
|
|
430
782
|
try {
|
|
431
783
|
switch (command) {
|
|
@@ -438,15 +790,27 @@ Options:
|
|
|
438
790
|
case 'recall':
|
|
439
791
|
await cmdRecall(aquifer, args);
|
|
440
792
|
break;
|
|
793
|
+
case 'evidence-recall':
|
|
794
|
+
await cmdEvidenceRecall(aquifer, args);
|
|
795
|
+
break;
|
|
441
796
|
case 'feedback':
|
|
442
797
|
await cmdFeedback(aquifer, args);
|
|
443
798
|
break;
|
|
799
|
+
case 'memory-feedback':
|
|
800
|
+
await cmdMemoryFeedback(aquifer, args);
|
|
801
|
+
break;
|
|
444
802
|
case 'feedback-stats':
|
|
445
803
|
await cmdFeedbackStats(aquifer, args);
|
|
446
804
|
break;
|
|
447
805
|
case 'backfill':
|
|
448
806
|
await cmdBackfill(aquifer, args);
|
|
449
807
|
break;
|
|
808
|
+
case 'operator':
|
|
809
|
+
await cmdOperator(aquifer, args);
|
|
810
|
+
break;
|
|
811
|
+
case 'compact':
|
|
812
|
+
await cmdCompact(aquifer, args);
|
|
813
|
+
break;
|
|
450
814
|
case 'stats':
|
|
451
815
|
await cmdStats(aquifer, args);
|
|
452
816
|
break;
|
|
@@ -471,7 +835,11 @@ Options:
|
|
|
471
835
|
}
|
|
472
836
|
|
|
473
837
|
// Export for testing; execute only when run directly
|
|
474
|
-
module.exports = {
|
|
838
|
+
module.exports = {
|
|
839
|
+
parseArgs,
|
|
840
|
+
cmdOperator,
|
|
841
|
+
readSynthesisSummaryFromFlags,
|
|
842
|
+
};
|
|
475
843
|
|
|
476
844
|
if (require.main === module) {
|
|
477
845
|
main().catch(err => {
|