@shadowforge0/aquifer-memory 1.5.12 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +23 -0
- package/README.md +78 -73
- package/README_CN.md +659 -0
- package/README_TW.md +680 -0
- package/aquifer.config.example.json +34 -0
- package/consumers/claude-code.js +11 -11
- package/consumers/cli.js +353 -52
- package/consumers/codex-handoff.js +152 -0
- package/consumers/codex.js +1549 -0
- package/consumers/default/daily-entries.js +23 -4
- package/consumers/default/index.js +2 -2
- package/consumers/default/prompts/summary.js +6 -6
- package/consumers/mcp.js +96 -5
- package/consumers/openclaw-ext/index.js +0 -1
- package/consumers/openclaw-plugin.js +1 -1
- package/consumers/shared/config.js +8 -0
- package/consumers/shared/factory.js +1 -0
- package/consumers/shared/ingest.js +1 -1
- package/consumers/shared/normalize.js +14 -3
- package/consumers/shared/recall-format.js +27 -0
- package/consumers/shared/summary-parser.js +151 -0
- package/core/aquifer.js +372 -18
- package/core/finalization-review.js +319 -0
- package/core/mcp-manifest.js +52 -2
- package/core/memory-bootstrap.js +188 -0
- package/core/memory-consolidation.js +1236 -0
- package/core/memory-promotion.js +544 -0
- package/core/memory-recall.js +247 -0
- package/core/memory-records.js +581 -0
- package/core/memory-safety-gate.js +224 -0
- package/core/session-finalization.js +350 -0
- package/core/storage.js +385 -2
- package/docs/getting-started.md +99 -0
- package/docs/postprocess-contract.md +2 -2
- package/docs/setup.md +51 -2
- package/package.json +25 -11
- package/pipeline/normalize/adapters/codex.js +106 -0
- package/pipeline/normalize/detect.js +3 -2
- package/schema/001-base.sql +3 -0
- package/schema/007-v1-foundation.sql +273 -0
- package/schema/008-session-finalizations.sql +50 -0
- package/schema/009-v1-assertion-plane.sql +193 -0
- package/schema/010-v1-finalization-review.sql +160 -0
- package/schema/011-v1-compaction-claim.sql +46 -0
- package/schema/012-v1-compaction-lease.sql +39 -0
- package/schema/013-v1-compaction-lineage.sql +193 -0
- package/scripts/codex-recovery.js +532 -0
- package/consumers/miranda/context-inject.js +0 -120
- package/consumers/miranda/daily-entries.js +0 -224
- package/consumers/miranda/index.js +0 -364
- package/consumers/miranda/instance.js +0 -55
- package/consumers/miranda/llm.js +0 -99
- package/consumers/miranda/profile.json +0 -145
- package/consumers/miranda/prompts/summary.js +0 -303
- package/consumers/miranda/recall-format.js +0 -76
- package/consumers/miranda/render-daily-md.js +0 -186
- package/consumers/miranda/workspace-files.js +0 -91
- package/scripts/drop-entity-state-history.sql +0 -17
- package/scripts/drop-insights.sql +0 -12
- package/scripts/install-openclaw.sh +0 -59
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"db": {
|
|
3
|
+
"url": "postgresql://user:password@localhost:5432/mydb",
|
|
4
|
+
"max": 10
|
|
5
|
+
},
|
|
6
|
+
"schema": "aquifer",
|
|
7
|
+
"tenantId": "default",
|
|
8
|
+
"defaults": {
|
|
9
|
+
"agentId": "main",
|
|
10
|
+
"source": "api"
|
|
11
|
+
},
|
|
12
|
+
"memory": {
|
|
13
|
+
"servingMode": "legacy",
|
|
14
|
+
"activeScopeKey": null,
|
|
15
|
+
"activeScopePath": null
|
|
16
|
+
},
|
|
17
|
+
"embed": {
|
|
18
|
+
"baseUrl": "http://localhost:11434/v1",
|
|
19
|
+
"model": "bge-m3",
|
|
20
|
+
"dim": null
|
|
21
|
+
},
|
|
22
|
+
"llm": {
|
|
23
|
+
"baseUrl": null,
|
|
24
|
+
"model": null
|
|
25
|
+
},
|
|
26
|
+
"entities": {
|
|
27
|
+
"enabled": false,
|
|
28
|
+
"scope": "default"
|
|
29
|
+
},
|
|
30
|
+
"rerank": {
|
|
31
|
+
"enabled": false,
|
|
32
|
+
"provider": null
|
|
33
|
+
}
|
|
34
|
+
}
|
package/consumers/claude-code.js
CHANGED
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
4
|
// Claude Code host adapter.
|
|
5
5
|
//
|
|
6
|
-
// Generic entry points for CC-side afterburn hooks. No persona logic —
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// `entityParseFn`.
|
|
6
|
+
// Generic entry points for CC-side afterburn hooks. No persona logic — callers
|
|
7
|
+
// construct any persona-specific hooks outside this package and inject them via
|
|
8
|
+
// `postProcess`, `summaryFn`, `entityParseFn`, or `contextInjector`.
|
|
10
9
|
//
|
|
11
10
|
// API:
|
|
12
11
|
// runEnrich({ aquifer, sessionId, agentId, ... })
|
|
@@ -16,9 +15,9 @@
|
|
|
16
15
|
// runBackfill({ aquifer, sessionIds, ... })
|
|
17
16
|
// Iterate enrich() over pending sessions (for catch-up after a gap).
|
|
18
17
|
//
|
|
19
|
-
// runContextInject({
|
|
20
|
-
// Return
|
|
21
|
-
//
|
|
18
|
+
// runContextInject({ contextInjector, ... })
|
|
19
|
+
// Return a host-specific system context string for a CC session start
|
|
20
|
+
// hook. The injector is supplied by the deployment, not by Aquifer core.
|
|
22
21
|
// ---------------------------------------------------------------------------
|
|
23
22
|
|
|
24
23
|
/**
|
|
@@ -106,12 +105,13 @@ async function runBackfill({
|
|
|
106
105
|
}
|
|
107
106
|
|
|
108
107
|
/**
|
|
109
|
-
* Build
|
|
110
|
-
*
|
|
108
|
+
* Build a host-specific system context for a CC SessionStart hook.
|
|
109
|
+
* The caller supplies the context injector so this public adapter stays generic.
|
|
111
110
|
*/
|
|
112
111
|
async function runContextInject(opts = {}) {
|
|
113
|
-
const
|
|
114
|
-
|
|
112
|
+
const injector = opts.contextInjector || opts.computeInjection;
|
|
113
|
+
if (typeof injector !== 'function') throw new Error('runContextInject: contextInjector is required');
|
|
114
|
+
return injector(opts);
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
module.exports = { runEnrich, runBackfill, runContextInject };
|
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,98 @@ 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 hasQuickstartEmbedConfig(env) {
|
|
55
|
+
return !!(
|
|
56
|
+
env.EMBED_PROVIDER
|
|
57
|
+
|| (env.AQUIFER_EMBED_BASE_URL && env.AQUIFER_EMBED_MODEL)
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function printQuickstartFailure(title, detailLines = []) {
|
|
62
|
+
console.error(` FAIL — ${title}`);
|
|
63
|
+
for (const line of detailLines) {
|
|
64
|
+
if (line) console.error(` ${line}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function buildQuickstartSetupHints(env, detected, err) {
|
|
69
|
+
const hints = [];
|
|
70
|
+
const message = err && err.message ? err.message : String(err || 'Unknown error');
|
|
71
|
+
const hasDb = !!(env.DATABASE_URL || env.AQUIFER_DB_URL || detected.DATABASE_URL);
|
|
72
|
+
const hasEmbed = hasQuickstartEmbedConfig(env)
|
|
73
|
+
|| !!detected.EMBED_PROVIDER;
|
|
74
|
+
|
|
75
|
+
if (/Database URL is required/i.test(message)) {
|
|
76
|
+
hints.push('Quickstart could not find a PostgreSQL connection.');
|
|
77
|
+
if (!hasDb) {
|
|
78
|
+
hints.push('If you expect local defaults, make sure PostgreSQL is running on localhost:5432.');
|
|
79
|
+
hints.push('Otherwise set DATABASE_URL or AQUIFER_DB_URL explicitly and run quickstart again.');
|
|
80
|
+
}
|
|
81
|
+
return hints;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (/OPENAI_API_KEY/i.test(message)) {
|
|
85
|
+
hints.push('OpenAI embeddings were selected, but OPENAI_API_KEY is not set.');
|
|
86
|
+
hints.push('Export OPENAI_API_KEY or switch EMBED_PROVIDER back to ollama for local quickstart.');
|
|
87
|
+
return hints;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!hasDb || !hasEmbed) {
|
|
91
|
+
hints.push('Quickstart is missing part of the local setup.');
|
|
92
|
+
if (!hasDb) hints.push('PostgreSQL was not autodetected and no DATABASE_URL is set.');
|
|
93
|
+
if (!hasEmbed) hints.push('No embedding provider was autodetected and no embed env is set.');
|
|
94
|
+
hints.push('Try `docker compose up -d`, then run `npx aquifer quickstart` again.');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
hints.push(`Raw error: ${message}`);
|
|
98
|
+
return hints;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function buildQuickstartRecallHints(err) {
|
|
102
|
+
const message = err && err.message ? err.message : String(err || 'Unknown error');
|
|
103
|
+
|
|
104
|
+
if (/requires config\.embed\.fn|EMBED_PROVIDER/i.test(message)) {
|
|
105
|
+
return [
|
|
106
|
+
'Quickstart reached recall, but embeddings are not configured.',
|
|
107
|
+
'Set EMBED_PROVIDER=ollama for local Ollama, or EMBED_PROVIDER=openai with OPENAI_API_KEY.',
|
|
108
|
+
`Raw error: ${message}`,
|
|
109
|
+
];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (/OPENAI_API_KEY/i.test(message)) {
|
|
113
|
+
return [
|
|
114
|
+
'Recall is configured to use OpenAI embeddings, but OPENAI_API_KEY is missing.',
|
|
115
|
+
'Export OPENAI_API_KEY and rerun quickstart.',
|
|
116
|
+
`Raw error: ${message}`,
|
|
117
|
+
];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (/ECONNREFUSED|ENOTFOUND|fetch failed|connect/i.test(message)) {
|
|
121
|
+
return [
|
|
122
|
+
'Aquifer could not reach the embedding service during recall.',
|
|
123
|
+
'If you expect local Ollama, make sure it is running and the model is available.',
|
|
124
|
+
`Raw error: ${message}`,
|
|
125
|
+
];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return [
|
|
129
|
+
'Aquifer could not recall the quickstart test session.',
|
|
130
|
+
`Raw error: ${message}`,
|
|
131
|
+
];
|
|
132
|
+
}
|
|
133
|
+
|
|
39
134
|
// ---------------------------------------------------------------------------
|
|
40
135
|
// Argument parser (minimal, no deps)
|
|
41
136
|
// ---------------------------------------------------------------------------
|
|
@@ -43,7 +138,14 @@ function parsePositiveInt(value, fallback) {
|
|
|
43
138
|
function parseArgs(argv) {
|
|
44
139
|
const args = { _: [], flags: {} };
|
|
45
140
|
// Flags that take a value (not boolean)
|
|
46
|
-
const VALUE_FLAGS = new Set([
|
|
141
|
+
const VALUE_FLAGS = new Set([
|
|
142
|
+
'limit', 'agent-id', 'source', 'date-from', 'date-to', 'output', 'format', 'config', 'status',
|
|
143
|
+
'concurrency', 'entities', 'entity-mode', 'mode', 'session-id', 'memory-id', 'canonical-key',
|
|
144
|
+
'verdict', 'feedback-type', 'note', 'db', 'since', 'min-messages', 'lookback-days', 'max-chars',
|
|
145
|
+
'out', 'active-scope-key', 'active-scope-path', 'cadence', 'period-start', 'period-end',
|
|
146
|
+
'policy-version', 'worker-id', 'apply-token', 'claim-lease-seconds', 'snapshot-as-of',
|
|
147
|
+
'scope-key', 'scope-keys', 'scope-kind', 'context-key', 'topic-key', 'authority', 'input',
|
|
148
|
+
]);
|
|
47
149
|
for (let i = 0; i < argv.length; i++) {
|
|
48
150
|
if (argv[i] === '--') { args._.push(...argv.slice(i + 1)); break; }
|
|
49
151
|
if (argv[i].startsWith('--')) {
|
|
@@ -76,13 +178,13 @@ async function cmdRecall(aquifer, args) {
|
|
|
76
178
|
process.exit(1);
|
|
77
179
|
}
|
|
78
180
|
|
|
79
|
-
const recallOpts = {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
181
|
+
const recallOpts = { limit: parsePositiveInt(args.flags.limit, 5) };
|
|
182
|
+
if (args.flags['agent-id']) recallOpts.agentId = args.flags['agent-id'];
|
|
183
|
+
if (args.flags.source) recallOpts.source = args.flags.source;
|
|
184
|
+
if (args.flags['date-from']) recallOpts.dateFrom = args.flags['date-from'];
|
|
185
|
+
if (args.flags['date-to']) recallOpts.dateTo = args.flags['date-to'];
|
|
186
|
+
if (args.flags['active-scope-key']) recallOpts.activeScopeKey = args.flags['active-scope-key'];
|
|
187
|
+
if (args.flags['active-scope-path']) recallOpts.activeScopePath = parseScopePath(args.flags['active-scope-path']);
|
|
86
188
|
if (args.flags.entities) {
|
|
87
189
|
recallOpts.entities = args.flags.entities.split(',').map(s => s.trim()).filter(Boolean);
|
|
88
190
|
recallOpts.entityMode = args.flags['entity-mode'] || 'any';
|
|
@@ -94,39 +196,20 @@ async function cmdRecall(aquifer, args) {
|
|
|
94
196
|
return;
|
|
95
197
|
}
|
|
96
198
|
|
|
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
|
-
}
|
|
199
|
+
console.log(formatRecallResults(results, {
|
|
200
|
+
query,
|
|
201
|
+
showScore: true,
|
|
202
|
+
showExplain: !!args.flags.explain,
|
|
203
|
+
}));
|
|
125
204
|
}
|
|
126
205
|
|
|
127
206
|
async function cmdFeedback(aquifer, args) {
|
|
128
207
|
const sessionId = args.flags['session-id'] || args._[1];
|
|
129
208
|
const verdict = args.flags.verdict;
|
|
209
|
+
if (args.flags['memory-id'] || args.flags['canonical-key'] || args.flags['feedback-type']) {
|
|
210
|
+
console.error('Use `aquifer memory-feedback` for curated memory feedback.');
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
130
213
|
|
|
131
214
|
if (!sessionId || !verdict) {
|
|
132
215
|
console.error('Usage: aquifer feedback --session-id ID --verdict helpful|unhelpful [--note TEXT] [--agent-id ID]');
|
|
@@ -146,6 +229,66 @@ async function cmdFeedback(aquifer, args) {
|
|
|
146
229
|
}
|
|
147
230
|
}
|
|
148
231
|
|
|
232
|
+
async function cmdMemoryFeedback(aquifer, args) {
|
|
233
|
+
const memoryId = args.flags['memory-id'];
|
|
234
|
+
const canonicalKey = args.flags['canonical-key'];
|
|
235
|
+
const feedbackType = args.flags['feedback-type'];
|
|
236
|
+
|
|
237
|
+
if ((!memoryId && !canonicalKey) || !feedbackType) {
|
|
238
|
+
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]');
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const result = await aquifer.memoryFeedback({
|
|
243
|
+
memoryId: memoryId || undefined,
|
|
244
|
+
canonicalKey: canonicalKey || undefined,
|
|
245
|
+
}, {
|
|
246
|
+
feedbackType,
|
|
247
|
+
agentId: args.flags['agent-id'] || undefined,
|
|
248
|
+
note: args.flags.note || undefined,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
if (args.flags.json) {
|
|
252
|
+
console.log(JSON.stringify(result, null, 2));
|
|
253
|
+
} else {
|
|
254
|
+
const target = result.canonicalKey || result.memoryId;
|
|
255
|
+
console.log(`Memory feedback: ${result.feedbackType} (${target})`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function cmdEvidenceRecall(aquifer, args) {
|
|
260
|
+
const query = args._.slice(1).join(' ');
|
|
261
|
+
if (!query) {
|
|
262
|
+
console.error('Usage: aquifer evidence-recall <query> [--limit N] [--agent-id ID] [--allow-unsafe-debug] [--json]');
|
|
263
|
+
process.exit(1);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const recallOpts = { limit: parsePositiveInt(args.flags.limit, 5) };
|
|
267
|
+
if (args.flags['agent-id']) recallOpts.agentId = args.flags['agent-id'];
|
|
268
|
+
if (args.flags.source) recallOpts.source = args.flags.source;
|
|
269
|
+
if (args.flags['date-from']) recallOpts.dateFrom = args.flags['date-from'];
|
|
270
|
+
if (args.flags['date-to']) recallOpts.dateTo = args.flags['date-to'];
|
|
271
|
+
if (args.flags.entities) {
|
|
272
|
+
recallOpts.entities = args.flags.entities.split(',').map(s => s.trim()).filter(Boolean);
|
|
273
|
+
recallOpts.entityMode = args.flags['entity-mode'] || 'any';
|
|
274
|
+
}
|
|
275
|
+
if (args.flags.mode) recallOpts.mode = args.flags.mode;
|
|
276
|
+
if (args.flags['allow-unsafe-debug']) recallOpts.allowUnsafeDebug = true;
|
|
277
|
+
|
|
278
|
+
const results = await aquifer.evidenceRecall(query, recallOpts);
|
|
279
|
+
|
|
280
|
+
if (args.flags.json) {
|
|
281
|
+
console.log(JSON.stringify(results, null, 2));
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
console.log(formatRecallResults(results, {
|
|
286
|
+
query,
|
|
287
|
+
showScore: true,
|
|
288
|
+
showExplain: !!args.flags.explain,
|
|
289
|
+
}));
|
|
290
|
+
}
|
|
291
|
+
|
|
149
292
|
async function cmdFeedbackStats(aquifer, args) {
|
|
150
293
|
const stats = await aquifer.feedbackStats({
|
|
151
294
|
agentId: args.flags['agent-id'] || undefined,
|
|
@@ -238,13 +381,39 @@ async function cmdQuickstart(aquifer) {
|
|
|
238
381
|
skipSummary: true,
|
|
239
382
|
skipEntities: true,
|
|
240
383
|
});
|
|
384
|
+
if (Array.isArray(enrichResult.warnings) && enrichResult.warnings.length > 0) {
|
|
385
|
+
printQuickstartFailure('embedding step returned warnings.', [
|
|
386
|
+
'Quickstart expects turn embeddings to succeed cleanly.',
|
|
387
|
+
...enrichResult.warnings.map(w => `Warning: ${w}`),
|
|
388
|
+
]);
|
|
389
|
+
process.exitCode = 1;
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (!Number.isFinite(enrichResult.turnsEmbedded) || enrichResult.turnsEmbedded <= 0) {
|
|
393
|
+
printQuickstartFailure('0 turns were embedded.', [
|
|
394
|
+
'The quickstart test session contains user turns, so this usually means the embedding setup is not working.',
|
|
395
|
+
'Check EMBED_PROVIDER / AQUIFER_EMBED_* settings, or make sure Ollama/OpenAI is reachable.',
|
|
396
|
+
]);
|
|
397
|
+
process.exitCode = 1;
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
241
400
|
console.log(` OK — ${enrichResult.turnsEmbedded} turns embedded\n`);
|
|
242
401
|
|
|
243
402
|
// 4. Recall
|
|
244
403
|
console.log('4/5 Recalling "PostgreSQL memory store"...');
|
|
245
|
-
|
|
404
|
+
let results;
|
|
405
|
+
try {
|
|
406
|
+
results = await aquifer.recall('PostgreSQL memory store', { limit: 3 });
|
|
407
|
+
} catch (err) {
|
|
408
|
+
printQuickstartFailure('recall step failed.', buildQuickstartRecallHints(err));
|
|
409
|
+
process.exitCode = 1;
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
246
412
|
if (results.length === 0) {
|
|
247
|
-
|
|
413
|
+
printQuickstartFailure('quickstart could not recall its own test session.', [
|
|
414
|
+
'The write step succeeded, but the test query returned no matches.',
|
|
415
|
+
'This usually means the embedding path is misconfigured or the embed service is not reachable.',
|
|
416
|
+
]);
|
|
248
417
|
process.exitCode = 1;
|
|
249
418
|
return;
|
|
250
419
|
}
|
|
@@ -282,14 +451,17 @@ async function cmdQuickstart(aquifer) {
|
|
|
282
451
|
}
|
|
283
452
|
|
|
284
453
|
async function cmdBootstrap(aquifer, args) {
|
|
285
|
-
const
|
|
286
|
-
agentId: args.flags['agent-id'] || undefined,
|
|
287
|
-
source: args.flags.source || undefined,
|
|
454
|
+
const bootstrapOpts = {
|
|
288
455
|
limit: parsePositiveInt(args.flags.limit, 5),
|
|
289
|
-
lookbackDays: parsePositiveInt(args.flags['lookback-days'], 14),
|
|
290
456
|
maxChars: parsePositiveInt(args.flags['max-chars'], 4000),
|
|
291
457
|
format: args.flags.json ? 'structured' : 'text',
|
|
292
|
-
}
|
|
458
|
+
};
|
|
459
|
+
if (args.flags['agent-id']) bootstrapOpts.agentId = args.flags['agent-id'];
|
|
460
|
+
if (args.flags.source) bootstrapOpts.source = args.flags.source;
|
|
461
|
+
if (args.flags['lookback-days']) bootstrapOpts.lookbackDays = parsePositiveInt(args.flags['lookback-days'], 14);
|
|
462
|
+
if (args.flags['active-scope-key']) bootstrapOpts.activeScopeKey = args.flags['active-scope-key'];
|
|
463
|
+
if (args.flags['active-scope-path']) bootstrapOpts.activeScopePath = parseScopePath(args.flags['active-scope-path']);
|
|
464
|
+
const result = await aquifer.bootstrap(bootstrapOpts);
|
|
293
465
|
|
|
294
466
|
if (args.flags.json) {
|
|
295
467
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -305,6 +477,85 @@ async function cmdBootstrap(aquifer, args) {
|
|
|
305
477
|
}
|
|
306
478
|
}
|
|
307
479
|
|
|
480
|
+
async function cmdOperator(aquifer, args) {
|
|
481
|
+
const operatorVerb = args._[1] || 'compaction';
|
|
482
|
+
const cadenceVerbs = new Set(['manual', 'daily', 'weekly', 'monthly']);
|
|
483
|
+
|
|
484
|
+
if (operatorVerb === 'archive-distill') {
|
|
485
|
+
const inputPath = args.flags.input || args._[2];
|
|
486
|
+
if (!inputPath) {
|
|
487
|
+
console.error('Usage: aquifer operator archive-distill --input /path/to/archive.json [--authority verified_summary] [--json]');
|
|
488
|
+
process.exit(1);
|
|
489
|
+
}
|
|
490
|
+
const archiveSnapshot = JSON.parse(fs.readFileSync(inputPath, 'utf8'));
|
|
491
|
+
const result = await aquifer.memory.consolidation.runJob({
|
|
492
|
+
job: 'archive-distill',
|
|
493
|
+
archiveSnapshot,
|
|
494
|
+
authority: args.flags.authority || undefined,
|
|
495
|
+
scopeKind: args.flags['scope-kind'] || undefined,
|
|
496
|
+
scopeKey: args.flags['scope-key'] || undefined,
|
|
497
|
+
contextKey: args.flags['context-key'] || undefined,
|
|
498
|
+
topicKey: args.flags['topic-key'] || undefined,
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
if (args.flags.json) {
|
|
502
|
+
console.log(JSON.stringify(result, null, 2));
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const sessionCount = Array.isArray(archiveSnapshot.sessions) ? archiveSnapshot.sessions.length : 0;
|
|
507
|
+
console.log(`Archive distill planned from ${sessionCount} sessions.`);
|
|
508
|
+
console.log(`Candidates: ${result.candidates.length}`);
|
|
509
|
+
console.log('Visibility: recall/bootstrap hidden until a separate promotion step.');
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (operatorVerb !== 'compaction' && operatorVerb !== 'compact' && !cadenceVerbs.has(operatorVerb)) {
|
|
514
|
+
console.error('Usage: aquifer operator <compaction|archive-distill> [...]');
|
|
515
|
+
process.exit(1);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const cadence = args.flags.cadence
|
|
519
|
+
|| (cadenceVerbs.has(operatorVerb) ? operatorVerb : args._[2])
|
|
520
|
+
|| 'manual';
|
|
521
|
+
const result = await aquifer.memory.consolidation.runJob({
|
|
522
|
+
job: 'compaction',
|
|
523
|
+
cadence,
|
|
524
|
+
periodStart: args.flags['period-start'] || undefined,
|
|
525
|
+
periodEnd: args.flags['period-end'] || undefined,
|
|
526
|
+
policyVersion: args.flags['policy-version'] || undefined,
|
|
527
|
+
workerId: args.flags['worker-id'] || undefined,
|
|
528
|
+
applyToken: args.flags['apply-token'] || undefined,
|
|
529
|
+
claimLeaseSeconds: args.flags['claim-lease-seconds']
|
|
530
|
+
? parsePositiveInt(args.flags['claim-lease-seconds'], undefined)
|
|
531
|
+
: undefined,
|
|
532
|
+
snapshotAsOf: args.flags['snapshot-as-of'] || undefined,
|
|
533
|
+
scopeKeys: parseCsvList(args.flags['scope-keys'] || args.flags['scope-key']),
|
|
534
|
+
limit: parsePositiveInt(args.flags.limit, 1000),
|
|
535
|
+
apply: args.flags.apply === true,
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
if (args.flags.json) {
|
|
539
|
+
console.log(JSON.stringify(result, null, 2));
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
console.log(`${result.dryRun ? 'Planned' : 'Executed'} ${result.cadence} compaction window ${result.periodStart} -> ${result.periodEnd}`);
|
|
544
|
+
console.log(`Snapshot: ${result.snapshotCount} active rows${result.snapshotTruncated ? ' (snapshot limit reached)' : ''}`);
|
|
545
|
+
console.log(`Plan: ${result.plan.statusUpdates.length} lifecycle updates, ${result.plan.candidates.length} aggregate candidates`);
|
|
546
|
+
if (result.dryRun) {
|
|
547
|
+
console.log('Mode: dry-run only. Re-run with --apply to write compaction_runs and lifecycle changes.');
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
if (result.run) {
|
|
551
|
+
console.log(`Run: #${result.run.id} status=${result.run.status}`);
|
|
552
|
+
} else if (result.existingRun) {
|
|
553
|
+
console.log(`Existing run: #${result.existingRun.id} status=${result.existingRun.status} reason=${result.skipReason || 'claim_not_acquired'}`);
|
|
554
|
+
} else {
|
|
555
|
+
console.log(`No run claimed: ${result.skipReason || 'claim_not_acquired'}`);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
308
559
|
async function cmdExport(aquifer, args) {
|
|
309
560
|
const output = args.flags.output || null;
|
|
310
561
|
const limit = parsePositiveInt(args.flags.limit, 1000);
|
|
@@ -333,6 +584,10 @@ async function cmdExport(aquifer, args) {
|
|
|
333
584
|
}
|
|
334
585
|
}
|
|
335
586
|
|
|
587
|
+
async function cmdCompact(aquifer, args) {
|
|
588
|
+
return cmdOperator(aquifer, { ...args, _: ['operator', 'compaction', ...args._.slice(1)] });
|
|
589
|
+
}
|
|
590
|
+
|
|
336
591
|
// ---------------------------------------------------------------------------
|
|
337
592
|
// Main
|
|
338
593
|
// ---------------------------------------------------------------------------
|
|
@@ -343,17 +598,21 @@ async function main() {
|
|
|
343
598
|
console.log(`Usage: aquifer <command> [options]
|
|
344
599
|
|
|
345
600
|
Commands:
|
|
346
|
-
|
|
347
|
-
|
|
601
|
+
quickstart Verify end-to-end setup (migrate → commit → enrich → recall)
|
|
602
|
+
migrate Run database migrations
|
|
348
603
|
recall <query> Search sessions (requires embed config)
|
|
604
|
+
evidence-recall <query> Search legacy session/evidence plane explicitly
|
|
349
605
|
feedback Record trust feedback on a session
|
|
606
|
+
memory-feedback Record curated memory feedback
|
|
350
607
|
feedback-stats Show trust feedback statistics and coverage
|
|
351
608
|
backfill Enrich pending sessions
|
|
609
|
+
operator ... Run operator-safe consolidation jobs
|
|
610
|
+
compact Plan or apply curated memory compaction
|
|
352
611
|
stats Show database statistics
|
|
353
612
|
export Export sessions as JSONL
|
|
354
613
|
bootstrap Show recent session context (for new session start)
|
|
355
|
-
|
|
356
|
-
|
|
614
|
+
ingest-opencode Import sessions from OpenCode's local SQLite DB
|
|
615
|
+
mcp Start MCP server
|
|
357
616
|
|
|
358
617
|
Options:
|
|
359
618
|
--limit N Limit results
|
|
@@ -364,23 +623,43 @@ Options:
|
|
|
364
623
|
--entities A,B,C Entity names (comma-separated, recall)
|
|
365
624
|
--entity-mode any|all Entity match mode (recall, default: any)
|
|
366
625
|
--session-id ID Session ID (feedback)
|
|
626
|
+
--memory-id ID Curated memory record ID (memory-feedback)
|
|
627
|
+
--canonical-key KEY Active curated memory canonical key (memory-feedback)
|
|
367
628
|
--verdict helpful|unhelpful Feedback verdict (feedback)
|
|
629
|
+
--feedback-type TYPE Curated memory feedback type
|
|
368
630
|
--note TEXT Feedback note (feedback)
|
|
369
631
|
--explain Show score breakdown per result (recall)
|
|
632
|
+
--allow-unsafe-debug Allow broad evidence-recall without audit boundary
|
|
370
633
|
--json JSON output
|
|
371
634
|
--dry-run Preview only (backfill)
|
|
372
635
|
--output PATH Output file (export)
|
|
373
636
|
--config PATH Config file path
|
|
374
637
|
--lookback-days N How far back in days (bootstrap, default: 14)
|
|
375
638
|
--max-chars N Max output characters (bootstrap, default: 4000)
|
|
639
|
+
--active-scope-key KEY Active curated memory scope key
|
|
640
|
+
--active-scope-path A,B Ordered curated scope path
|
|
641
|
+
--cadence manual|daily|weekly|monthly
|
|
642
|
+
--period-start ISO Compaction window start
|
|
643
|
+
--period-end ISO Compaction window end
|
|
644
|
+
--apply Apply compaction; default is dry-run
|
|
645
|
+
--scope-key A,B Limit compaction snapshot to specific scope keys
|
|
646
|
+
--snapshot-as-of ISO Read active snapshot as of a specific instant
|
|
647
|
+
--claim-lease-seconds N Override compaction apply lease
|
|
648
|
+
--input PATH Archive distill input JSON path
|
|
376
649
|
--db PATH OpenCode SQLite path (ingest-opencode)
|
|
377
650
|
--since YYYY-MM-DD Only ingest sessions after date (ingest-opencode)
|
|
378
|
-
--min-messages N Min user messages to ingest (ingest-opencode, default: 3)
|
|
651
|
+
--min-messages N Min user messages to ingest (ingest-opencode, default: 3)
|
|
652
|
+
|
|
653
|
+
Operator examples:
|
|
654
|
+
aquifer operator compaction daily --json
|
|
655
|
+
aquifer operator compaction manual --period-start 2026-04-27T00:00:00Z --period-end 2026-04-28T00:00:00Z --apply
|
|
656
|
+
aquifer operator archive-distill --input /tmp/archive-snapshot.json --json`);
|
|
379
657
|
process.exit(0);
|
|
380
658
|
}
|
|
381
659
|
|
|
382
660
|
const command = argv[0];
|
|
383
661
|
const args = parseArgs(argv);
|
|
662
|
+
let quickstartDetected = {};
|
|
384
663
|
|
|
385
664
|
// MCP: delegate to mcp.js
|
|
386
665
|
if (command === 'mcp') {
|
|
@@ -414,10 +693,10 @@ Options:
|
|
|
414
693
|
// the operator to have set env explicitly.
|
|
415
694
|
if (command === 'quickstart') {
|
|
416
695
|
const { autodetectForQuickstart } = require('./shared/autodetect');
|
|
417
|
-
|
|
418
|
-
if (Object.keys(
|
|
696
|
+
quickstartDetected = await autodetectForQuickstart(process.env);
|
|
697
|
+
if (Object.keys(quickstartDetected).length > 0) {
|
|
419
698
|
console.log('Autodetected localhost services (env not set):');
|
|
420
|
-
for (const [k, v] of Object.entries(
|
|
699
|
+
for (const [k, v] of Object.entries(quickstartDetected)) {
|
|
421
700
|
console.log(` ${k}=${v}`);
|
|
422
701
|
process.env[k] = v;
|
|
423
702
|
}
|
|
@@ -425,7 +704,17 @@ Options:
|
|
|
425
704
|
}
|
|
426
705
|
}
|
|
427
706
|
|
|
428
|
-
|
|
707
|
+
let aquifer;
|
|
708
|
+
try {
|
|
709
|
+
aquifer = createAquiferFromConfig(configOverrides);
|
|
710
|
+
} catch (err) {
|
|
711
|
+
if (command === 'quickstart') {
|
|
712
|
+
printQuickstartFailure('setup check failed before quickstart could start.', buildQuickstartSetupHints(process.env, quickstartDetected, err));
|
|
713
|
+
process.exit(1);
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
throw err;
|
|
717
|
+
}
|
|
429
718
|
|
|
430
719
|
try {
|
|
431
720
|
switch (command) {
|
|
@@ -438,15 +727,27 @@ Options:
|
|
|
438
727
|
case 'recall':
|
|
439
728
|
await cmdRecall(aquifer, args);
|
|
440
729
|
break;
|
|
730
|
+
case 'evidence-recall':
|
|
731
|
+
await cmdEvidenceRecall(aquifer, args);
|
|
732
|
+
break;
|
|
441
733
|
case 'feedback':
|
|
442
734
|
await cmdFeedback(aquifer, args);
|
|
443
735
|
break;
|
|
736
|
+
case 'memory-feedback':
|
|
737
|
+
await cmdMemoryFeedback(aquifer, args);
|
|
738
|
+
break;
|
|
444
739
|
case 'feedback-stats':
|
|
445
740
|
await cmdFeedbackStats(aquifer, args);
|
|
446
741
|
break;
|
|
447
742
|
case 'backfill':
|
|
448
743
|
await cmdBackfill(aquifer, args);
|
|
449
744
|
break;
|
|
745
|
+
case 'operator':
|
|
746
|
+
await cmdOperator(aquifer, args);
|
|
747
|
+
break;
|
|
748
|
+
case 'compact':
|
|
749
|
+
await cmdCompact(aquifer, args);
|
|
750
|
+
break;
|
|
450
751
|
case 'stats':
|
|
451
752
|
await cmdStats(aquifer, args);
|
|
452
753
|
break;
|