@shadowforge0/aquifer-memory 1.7.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +8 -0
- package/README.md +66 -0
- package/aquifer.config.example.json +19 -0
- package/consumers/cli.js +217 -14
- package/consumers/codex-active-checkpoint.js +186 -0
- package/consumers/codex-current-memory.js +106 -0
- package/consumers/codex-handoff.js +442 -3
- package/consumers/codex.js +164 -107
- package/consumers/mcp.js +144 -6
- package/consumers/shared/config.js +60 -1
- package/consumers/shared/factory.js +10 -3
- package/core/aquifer.js +351 -840
- package/core/backends/capabilities.js +89 -0
- package/core/backends/local.js +430 -0
- package/core/legacy-bootstrap.js +140 -0
- package/core/mcp-manifest.js +66 -2
- package/core/memory-promotion.js +157 -26
- package/core/memory-recall.js +341 -22
- package/core/memory-records.js +128 -8
- package/core/memory-serving.js +132 -0
- package/core/postgres-migrations.js +533 -0
- package/core/public-session-filter.js +40 -0
- package/core/recall-runtime.js +115 -0
- package/core/scope-attribution.js +279 -0
- package/core/session-checkpoint-producer.js +412 -0
- package/core/session-checkpoints.js +432 -0
- package/core/session-finalization.js +82 -1
- package/core/storage-checkpoints.js +546 -0
- package/core/storage.js +121 -8
- package/docs/setup.md +22 -0
- package/package.json +8 -4
- package/schema/014-v1-checkpoint-runs.sql +349 -0
- package/schema/015-v1-evidence-items.sql +92 -0
- package/schema/016-v1-evidence-ref-multi-item.sql +19 -0
- package/schema/017-v1-memory-record-embeddings.sql +25 -0
- package/schema/018-v1-finalization-candidate-envelope.sql +39 -0
- package/scripts/codex-checkpoint-commands.js +464 -0
- package/scripts/codex-checkpoint-runtime.js +520 -0
- package/scripts/codex-recovery.js +105 -0
package/.env.example
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
DATABASE_URL=postgresql://aquifer:aquifer@localhost:5432/aquifer
|
|
2
|
+
AQUIFER_BACKEND=postgres
|
|
3
|
+
# AQUIFER_BACKEND=local
|
|
4
|
+
# AQUIFER_LOCAL_PATH=.aquifer/aquifer.local.json
|
|
2
5
|
AQUIFER_SCHEMA=aquifer
|
|
3
6
|
AQUIFER_TENANT_ID=default
|
|
4
7
|
|
|
@@ -9,6 +12,11 @@ AQUIFER_MEMORY_SERVING_MODE=legacy
|
|
|
9
12
|
# AQUIFER_MEMORY_ACTIVE_SCOPE_KEY=project:example
|
|
10
13
|
# AQUIFER_MEMORY_ACTIVE_SCOPE_PATH=global,project:example
|
|
11
14
|
|
|
15
|
+
# Optional Codex active-session checkpoint heartbeat policy.
|
|
16
|
+
# AQUIFER_CODEX_CHECKPOINT_CHECK_INTERVAL_MINUTES=10
|
|
17
|
+
# AQUIFER_CODEX_CHECKPOINT_EVERY_MESSAGES=20
|
|
18
|
+
# AQUIFER_CODEX_CHECKPOINT_QUIET_MS=3000
|
|
19
|
+
|
|
12
20
|
AQUIFER_EMBED_BASE_URL=http://localhost:11434/v1
|
|
13
21
|
AQUIFER_EMBED_MODEL=bge-m3
|
|
14
22
|
# EMBED_PROVIDER=ollama
|
package/README.md
CHANGED
|
@@ -74,11 +74,15 @@ Keep `AQUIFER_MEMORY_SERVING_MODE=legacy` for first rollout. Switch to `curated`
|
|
|
74
74
|
| Goal | Command |
|
|
75
75
|
|---|---|
|
|
76
76
|
| Verify setup | `npx aquifer quickstart` |
|
|
77
|
+
| Inspect selected backend capabilities without DB connection | `AQUIFER_BACKEND=local npx aquifer backend-info --json` |
|
|
77
78
|
| Start the MCP server | `npx aquifer mcp` |
|
|
78
79
|
| Search memory manually | `npx aquifer recall "auth middleware"` |
|
|
79
80
|
| Plan curated memory compaction | `npx aquifer compact --cadence daily --period-start 2026-04-27T00:00:00Z --period-end 2026-04-28T00:00:00Z` |
|
|
80
81
|
| Generate a timer synthesis prompt | `npx aquifer operator compaction daily --include-synthesis-prompt --json` |
|
|
81
82
|
| Apply reviewed timer synthesis candidates | `npx aquifer operator compaction daily --synthesis-summary-file /tmp/timer-summary.json --apply --promote-candidates --json` |
|
|
83
|
+
| Generate a finalized-session checkpoint prompt | `npx aquifer operator checkpoint --scope-key project:aquifer --min-finalizations 10 --include-synthesis-prompt --json` |
|
|
84
|
+
| Heartbeat-check an active Codex session for checkpoint work | `npx aquifer codex-recovery checkpoint-heartbeat --hook-stdin --scope-key project:aquifer` |
|
|
85
|
+
| Preview a Codex UserPromptSubmit heartbeat hook install | `npx aquifer codex-recovery checkpoint-heartbeat-hook --scope-key project:aquifer --hooks-path "$CODEX_HOME/hooks.json" --json` |
|
|
82
86
|
| Inspect storage health | `npx aquifer stats` |
|
|
83
87
|
| Enrich pending sessions | `npx aquifer backfill` |
|
|
84
88
|
|
|
@@ -86,6 +90,15 @@ Timer synthesis output is candidate material until an operator applies it with
|
|
|
86
90
|
`--promote-candidates`; it does not become active curated memory from the
|
|
87
91
|
prompt or summary file alone.
|
|
88
92
|
|
|
93
|
+
Checkpoint output follows the same boundary. `operator checkpoint` plans from
|
|
94
|
+
finalized session summaries and only writes `checkpoint_runs` when you pass an
|
|
95
|
+
explicit reviewed synthesis summary with `--apply`. `codex-recovery
|
|
96
|
+
checkpoint-heartbeat` is the active-session hook heartbeat for Codex JSONL
|
|
97
|
+
files: it first checks a tiny local scheduler marker, reads the transcript only
|
|
98
|
+
when the time window is due, then writes local spool process material only when
|
|
99
|
+
the message threshold is also due. It does not print prompt text by default and
|
|
100
|
+
does not write DB memory.
|
|
101
|
+
|
|
89
102
|
Need LLM summarization, the knowledge graph, OpenAI embeddings, reranking, or operations details? See [docs/setup.md](docs/setup.md) and [Environment Variables](#environment-variables).
|
|
90
103
|
|
|
91
104
|
---
|
|
@@ -139,6 +152,8 @@ Sessions, summaries, turn-level embeddings, entity graph — all live in one dat
|
|
|
139
152
|
| Variable | Required? | Purpose | Example |
|
|
140
153
|
|----------|-----------|---------|---------|
|
|
141
154
|
| `DATABASE_URL` | Yes | PostgreSQL connection string | `postgresql://user:pass@localhost:5432/mydb` |
|
|
155
|
+
| `AQUIFER_BACKEND` | No | Backend profile selector: `postgres` full backend or explicit degraded `local` starter | `postgres` |
|
|
156
|
+
| `AQUIFER_LOCAL_PATH` | No | Local starter JSON store path | `.aquifer/aquifer.local.json` |
|
|
142
157
|
| `AQUIFER_SCHEMA` | No | PG schema name (default: `aquifer`) | `memory` |
|
|
143
158
|
| `AQUIFER_TENANT_ID` | No | Multi-tenant key (default: `default`) | `my-app` |
|
|
144
159
|
| `AQUIFER_EMBED_BASE_URL` | Yes (for recall) | Embedding API base URL | `http://localhost:11434/v1` |
|
|
@@ -157,6 +172,10 @@ Sessions, summaries, turn-level embeddings, entity graph — all live in one dat
|
|
|
157
172
|
| `AQUIFER_MEMORY_SERVING_MODE` | No | Public serving mode: `legacy` default, or opt-in `curated` | `curated` |
|
|
158
173
|
| `AQUIFER_MEMORY_ACTIVE_SCOPE_KEY` | No | Default active curated scope for recall/bootstrap | `project:aquifer` |
|
|
159
174
|
| `AQUIFER_MEMORY_ACTIVE_SCOPE_PATH` | No | Ordered curated scope path for inheritance | `global,project:aquifer` |
|
|
175
|
+
| `AQUIFER_CODEX_CHECKPOINT_CHECK_INTERVAL_MINUTES` | No | Active Codex checkpoint heartbeat time gate (default: `10`) | `10` |
|
|
176
|
+
| `AQUIFER_CODEX_CHECKPOINT_EVERY_MESSAGES` | No | Active Codex checkpoint message delta gate (default: `20`) | `20` |
|
|
177
|
+
| `AQUIFER_CODEX_CHECKPOINT_EVERY_USER_MESSAGES` | No | Optional user-message delta gate | `10` |
|
|
178
|
+
| `AQUIFER_CODEX_CHECKPOINT_QUIET_MS` | No | Quiet period before reading due transcripts (default: `3000`) | `3000` |
|
|
160
179
|
| `AQUIFER_MIGRATIONS_MODE` | No | Startup handshake mode: `apply` (default), `check`, `off` | `apply` |
|
|
161
180
|
| `AQUIFER_MIGRATION_LOCK_TIMEOUT_MS` | No | Advisory-lock wait before `AQ_MIGRATION_LOCK_TIMEOUT` (default 30000) | `30000` |
|
|
162
181
|
| `AQUIFER_INSIGHTS_DEDUP_MODE` | No | Insights semantic dedup mode: `off` (default), `shadow`, `enforce` — env wins over code for this field only, so operators can kill-switch without redeploy | `shadow` |
|
|
@@ -219,6 +238,53 @@ Add to your project's `.claude.json` or user-level MCP config:
|
|
|
219
238
|
|
|
220
239
|
Tools appear as `mcp__aquifer__session_recall`, `mcp__aquifer__evidence_recall`, `mcp__aquifer__session_bootstrap`, `mcp__aquifer__session_feedback`, `mcp__aquifer__memory_feedback`, `mcp__aquifer__feedback_stats`, `mcp__aquifer__memory_stats`, `mcp__aquifer__memory_pending`.
|
|
221
240
|
|
|
241
|
+
For Codex long sessions, Aquifer exposes a UserPromptSubmit-friendly heartbeat
|
|
242
|
+
command instead of installing a daemon:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
npx aquifer codex-recovery checkpoint-heartbeat \
|
|
246
|
+
--hook-stdin \
|
|
247
|
+
--scope-key project:aquifer
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Run that from a host hook with Codex hook JSON on stdin. The heartbeat uses a
|
|
251
|
+
time-first gate: if the local marker says the next check is not due, it exits
|
|
252
|
+
without validating or reading the transcript. When due, it validates the
|
|
253
|
+
`transcript_path` realpath under the Codex sessions directory, waits for the
|
|
254
|
+
quiet period, checks the configured message delta, and writes a local spool file
|
|
255
|
+
for later review. Scheduler, claim, and spool files live under the Codex state
|
|
256
|
+
directory by default; they are process-control files, not DB memory.
|
|
257
|
+
|
|
258
|
+
Heartbeat policy resolves as command flags first, then Aquifer env/config, then
|
|
259
|
+
defaults. The default policy is 10 minutes, 20 safe messages, no user-message
|
|
260
|
+
gate, 3000 ms quiet period, and 60000 ms claim TTL. In config files this lives at
|
|
261
|
+
`codex.checkpoint`:
|
|
262
|
+
|
|
263
|
+
```json
|
|
264
|
+
{
|
|
265
|
+
"codex": {
|
|
266
|
+
"checkpoint": {
|
|
267
|
+
"checkIntervalMinutes": 10,
|
|
268
|
+
"everyMessages": 20,
|
|
269
|
+
"quietMs": 3000
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
To prepare the Codex hook entry, generate or apply the merged `hooks.json`:
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
npx aquifer codex-recovery checkpoint-heartbeat-hook \
|
|
279
|
+
--scope-key project:aquifer \
|
|
280
|
+
--hooks-path "$CODEX_HOME/hooks.json" \
|
|
281
|
+
--json
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
The hook installer is dry-run by default. Add `--apply` only after reviewing the
|
|
285
|
+
merged `UserPromptSubmit` command. `codex-recovery doctor --json` reports whether
|
|
286
|
+
the heartbeat hook is present.
|
|
287
|
+
|
|
222
288
|
### OpenClaw
|
|
223
289
|
|
|
224
290
|
Add to `openclaw.json` under `mcp.servers`:
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
{
|
|
2
|
+
"storage": {
|
|
3
|
+
"backend": "postgres",
|
|
4
|
+
"postgres": {
|
|
5
|
+
"url": "postgresql://user:password@localhost:5432/mydb",
|
|
6
|
+
"max": 10
|
|
7
|
+
},
|
|
8
|
+
"local": {
|
|
9
|
+
"path": ".aquifer/aquifer.local.json"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
2
12
|
"db": {
|
|
3
13
|
"url": "postgresql://user:password@localhost:5432/mydb",
|
|
4
14
|
"max": 10
|
|
@@ -14,6 +24,15 @@
|
|
|
14
24
|
"activeScopeKey": null,
|
|
15
25
|
"activeScopePath": null
|
|
16
26
|
},
|
|
27
|
+
"codex": {
|
|
28
|
+
"checkpoint": {
|
|
29
|
+
"checkIntervalMinutes": 10,
|
|
30
|
+
"everyMessages": 20,
|
|
31
|
+
"everyUserMessages": null,
|
|
32
|
+
"quietMs": 3000,
|
|
33
|
+
"claimTtlMs": 60000
|
|
34
|
+
}
|
|
35
|
+
},
|
|
17
36
|
"embed": {
|
|
18
37
|
"baseUrl": "http://localhost:11434/v1",
|
|
19
38
|
"model": "bge-m3",
|
package/consumers/cli.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* aquifer recall <query> [options] Search sessions
|
|
11
11
|
* aquifer backfill [options] Enrich pending sessions
|
|
12
12
|
* aquifer stats [options] Show database statistics
|
|
13
|
+
* aquifer backend-info [--json] Show selected backend capabilities
|
|
13
14
|
* aquifer export [options] Export sessions
|
|
14
15
|
* aquifer operator ... Run operator-safe consolidation jobs
|
|
15
16
|
* aquifer mcp Start MCP server
|
|
@@ -17,7 +18,9 @@
|
|
|
17
18
|
|
|
18
19
|
const fs = require('fs');
|
|
19
20
|
const { createAquiferFromConfig } = require('./shared/factory');
|
|
21
|
+
const { loadConfig } = require('./shared/config');
|
|
20
22
|
const { formatRecallResults } = require('./shared/recall-format');
|
|
23
|
+
const { backendCapabilities } = require('../core/backends/capabilities');
|
|
21
24
|
|
|
22
25
|
function formatDate(value, fallback) {
|
|
23
26
|
if (!value) return fallback;
|
|
@@ -109,6 +112,7 @@ function buildQuickstartSetupHints(env, detected, err) {
|
|
|
109
112
|
if (!hasDb) {
|
|
110
113
|
hints.push('If you expect local defaults, make sure PostgreSQL is running on localhost:5432.');
|
|
111
114
|
hints.push('Otherwise set DATABASE_URL or AQUIFER_DB_URL explicitly and run quickstart again.');
|
|
115
|
+
hints.push('To inspect the degraded local starter profile without PostgreSQL, run `AQUIFER_BACKEND=local aquifer backend-info --json`.');
|
|
112
116
|
}
|
|
113
117
|
return hints;
|
|
114
118
|
}
|
|
@@ -124,6 +128,7 @@ function buildQuickstartSetupHints(env, detected, err) {
|
|
|
124
128
|
if (!hasDb) hints.push('PostgreSQL was not autodetected and no DATABASE_URL is set.');
|
|
125
129
|
if (!hasEmbed) hints.push('No embedding provider was autodetected and no embed env is set.');
|
|
126
130
|
hints.push('Try `docker compose up -d`, then run `npx aquifer quickstart` again.');
|
|
131
|
+
hints.push('To inspect the degraded local starter profile without PostgreSQL, run `AQUIFER_BACKEND=local aquifer backend-info --json`.');
|
|
127
132
|
}
|
|
128
133
|
|
|
129
134
|
hints.push(`Raw error: ${message}`);
|
|
@@ -176,8 +181,8 @@ function parseArgs(argv) {
|
|
|
176
181
|
'verdict', 'feedback-type', 'note', 'db', 'since', 'min-messages', 'lookback-days', 'max-chars',
|
|
177
182
|
'out', 'active-scope-key', 'active-scope-path', 'cadence', 'period-start', 'period-end',
|
|
178
183
|
'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',
|
|
184
|
+
'scope-id', 'scope-key', 'scope-keys', 'scope-kind', 'context-key', 'topic-key', 'authority', 'input',
|
|
185
|
+
'synthesis-summary', 'synthesis-summary-file', 'min-finalizations', 'checkpoint-key',
|
|
181
186
|
]);
|
|
182
187
|
for (let i = 0; i < argv.length; i++) {
|
|
183
188
|
if (argv[i] === '--') { args._.push(...argv.slice(i + 1)); break; }
|
|
@@ -199,7 +204,29 @@ function parseArgs(argv) {
|
|
|
199
204
|
// Commands
|
|
200
205
|
// ---------------------------------------------------------------------------
|
|
201
206
|
|
|
202
|
-
async function cmdMigrate(aquifer) {
|
|
207
|
+
async function cmdMigrate(aquifer, args = { flags: {} }) {
|
|
208
|
+
if (args.flags && args.flags.json) {
|
|
209
|
+
const notices = [];
|
|
210
|
+
const originalStderrWrite = process.stderr.write;
|
|
211
|
+
process.stderr.write = function writeCapturedStderr(chunk, encoding, callback) {
|
|
212
|
+
notices.push(Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk));
|
|
213
|
+
if (typeof encoding === 'function') encoding();
|
|
214
|
+
if (typeof callback === 'function') callback();
|
|
215
|
+
return true;
|
|
216
|
+
};
|
|
217
|
+
try {
|
|
218
|
+
await aquifer.migrate();
|
|
219
|
+
} finally {
|
|
220
|
+
process.stderr.write = originalStderrWrite;
|
|
221
|
+
}
|
|
222
|
+
console.log(JSON.stringify({
|
|
223
|
+
ok: true,
|
|
224
|
+
migrated: true,
|
|
225
|
+
notices: notices.join('').split(/\r?\n/).map(line => line.trim()).filter(Boolean),
|
|
226
|
+
}, null, 2));
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
203
230
|
await aquifer.migrate();
|
|
204
231
|
console.log('Migrations applied successfully.');
|
|
205
232
|
}
|
|
@@ -381,15 +408,122 @@ async function cmdStats(aquifer, args) {
|
|
|
381
408
|
if (args.flags.json) {
|
|
382
409
|
console.log(JSON.stringify(stats, null, 2));
|
|
383
410
|
} else {
|
|
411
|
+
console.log(`Backend: ${stats.backendKind || 'unknown'} (${stats.backendProfile || 'unknown'})`);
|
|
412
|
+
console.log(`Serving mode: ${stats.serving?.mode || 'legacy'}`);
|
|
413
|
+
console.log(`Active scope: ${stats.serving?.activeScopePath?.join(' > ') || stats.serving?.activeScopeKey || 'none'}`);
|
|
384
414
|
console.log(`Sessions: ${stats.sessionTotal} (${Object.entries(stats.sessions).map(([k, v]) => `${k}: ${v}`).join(', ')})`);
|
|
385
415
|
console.log(`Summaries: ${stats.summaries}`);
|
|
386
416
|
console.log(`Turn embeddings: ${stats.turnEmbeddings}`);
|
|
387
417
|
console.log(`Entities: ${stats.entities}`);
|
|
418
|
+
if (stats.memoryRecords) {
|
|
419
|
+
console.log(`Memory records: ${stats.memoryRecords.total} (${stats.memoryRecords.active} active, ${stats.memoryRecords.visibleInRecall} recall-visible, ${stats.memoryRecords.visibleInBootstrap} bootstrap-visible)`);
|
|
420
|
+
if (stats.memoryRecords.latest) console.log(`Memory record range: ${formatDate(stats.memoryRecords.earliest, '?')} — ${formatDate(stats.memoryRecords.latest, '?')}`);
|
|
421
|
+
}
|
|
422
|
+
if (stats.sessionFinalizations?.available) {
|
|
423
|
+
const statusText = Object.entries(stats.sessionFinalizations.statuses || {})
|
|
424
|
+
.map(([status, count]) => `${status}: ${count}`)
|
|
425
|
+
.join(', ') || 'none';
|
|
426
|
+
console.log(`Session finalizations: ${stats.sessionFinalizations.total} (${statusText})`);
|
|
427
|
+
if (stats.sessionFinalizations.latestFinalizedAt) console.log(`Latest finalization: ${formatDate(stats.sessionFinalizations.latestFinalizedAt, '?')}`);
|
|
428
|
+
}
|
|
388
429
|
if (stats.earliest) console.log(`Range: ${formatDate(stats.earliest, '?')} — ${formatDate(stats.latest, '?')}`);
|
|
430
|
+
if ((stats.serving?.mode || 'legacy') !== 'curated') {
|
|
431
|
+
console.log('Warning: legacy serving returns session/evidence material; configure curated serving with an active scope for current-memory answers.');
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function selectedBackendInfo(opts = {}) {
|
|
437
|
+
const config = loadConfig(opts);
|
|
438
|
+
const backendKind = config.storage.backend;
|
|
439
|
+
const capabilities = backendCapabilities(backendKind);
|
|
440
|
+
return {
|
|
441
|
+
backendKind,
|
|
442
|
+
backendProfile: capabilities.profile,
|
|
443
|
+
label: capabilities.label,
|
|
444
|
+
summary: capabilities.summary,
|
|
445
|
+
capabilities: capabilities.capabilities,
|
|
446
|
+
storage: {
|
|
447
|
+
localPath: config.storage.local.path,
|
|
448
|
+
postgresUrlConfigured: Boolean(config.storage.postgres.url || config.db.url),
|
|
449
|
+
},
|
|
450
|
+
upgradeHint: capabilities.upgradeHint,
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async function cmdBackendInfo(args) {
|
|
455
|
+
const info = selectedBackendInfo();
|
|
456
|
+
if (args.flags.json) {
|
|
457
|
+
console.log(JSON.stringify(info, null, 2));
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
console.log(`Backend: ${info.label} (${info.backendKind}/${info.backendProfile})`);
|
|
462
|
+
console.log(info.summary);
|
|
463
|
+
console.log(`PostgreSQL URL configured: ${info.storage.postgresUrlConfigured ? 'yes' : 'no'}`);
|
|
464
|
+
if (info.backendKind === 'local') {
|
|
465
|
+
console.log(`Local path: ${info.storage.localPath}`);
|
|
466
|
+
}
|
|
467
|
+
if (info.upgradeHint) {
|
|
468
|
+
console.log(`Upgrade: ${info.upgradeHint}`);
|
|
469
|
+
}
|
|
470
|
+
console.log('Capabilities:');
|
|
471
|
+
for (const [name, status] of Object.entries(info.capabilities)) {
|
|
472
|
+
console.log(` ${name}: ${status}`);
|
|
389
473
|
}
|
|
390
474
|
}
|
|
391
475
|
|
|
476
|
+
async function cmdLocalQuickstart(aquifer) {
|
|
477
|
+
const cfg = aquifer.getConfig();
|
|
478
|
+
console.log('Aquifer quickstart — verifying local starter backend.\n');
|
|
479
|
+
|
|
480
|
+
console.log('1/5 Preparing local store...');
|
|
481
|
+
await aquifer.init();
|
|
482
|
+
console.log(` OK — ${cfg.backendPath}\n`);
|
|
483
|
+
|
|
484
|
+
const sessionId = `quickstart-local-${Date.now()}`;
|
|
485
|
+
console.log('2/5 Committing test session...');
|
|
486
|
+
await aquifer.commit(sessionId, [
|
|
487
|
+
{ role: 'user', content: 'Aquifer local starter can store a session without PostgreSQL.' },
|
|
488
|
+
{ role: 'assistant', content: 'Local starter uses JSON persistence and degraded lexical recall.' },
|
|
489
|
+
], { agentId: 'quickstart', source: 'quickstart' });
|
|
490
|
+
console.log(' OK\n');
|
|
491
|
+
|
|
492
|
+
console.log('3/5 Checking degraded enrich path...');
|
|
493
|
+
const enrichResult = await aquifer.enrich(sessionId, {
|
|
494
|
+
agentId: 'quickstart',
|
|
495
|
+
skipSummary: true,
|
|
496
|
+
skipEntities: true,
|
|
497
|
+
});
|
|
498
|
+
console.log(` OK — ${enrichResult.turnsEmbedded} turns embedded (local starter is lexical only)\n`);
|
|
499
|
+
|
|
500
|
+
console.log('4/5 Recalling "JSON persistence"...');
|
|
501
|
+
const results = await aquifer.recall('JSON persistence', { agentId: 'quickstart', limit: 3 });
|
|
502
|
+
if (results.length === 0) {
|
|
503
|
+
printQuickstartFailure('local starter could not recall its own test session.', [
|
|
504
|
+
'The write step succeeded, but lexical recall returned no matches.',
|
|
505
|
+
]);
|
|
506
|
+
process.exitCode = 1;
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
console.log(` OK — ${results.length} result(s), top score: ${results[0].score?.toFixed(3)}\n`);
|
|
510
|
+
|
|
511
|
+
console.log('5/5 Cleaning up test data...');
|
|
512
|
+
if (typeof aquifer.deleteSession === 'function') {
|
|
513
|
+
await aquifer.deleteSession(sessionId, { agentId: 'quickstart' });
|
|
514
|
+
}
|
|
515
|
+
console.log(' OK\n');
|
|
516
|
+
|
|
517
|
+
console.log('✓ Aquifer local starter is working.');
|
|
518
|
+
console.log(' This backend is degraded: use PostgreSQL quickstart for semantic recall, migrations, curated memory, and operator workflows.');
|
|
519
|
+
}
|
|
520
|
+
|
|
392
521
|
async function cmdQuickstart(aquifer) {
|
|
522
|
+
if (aquifer.getConfig().backendKind === 'local') {
|
|
523
|
+
await cmdLocalQuickstart(aquifer);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
|
|
393
527
|
console.log('Aquifer quickstart — verifying end-to-end setup.\n');
|
|
394
528
|
|
|
395
529
|
// 1. Migrate
|
|
@@ -514,6 +648,51 @@ async function cmdOperator(aquifer, args) {
|
|
|
514
648
|
const operatorVerb = args._[1] || 'compaction';
|
|
515
649
|
const cadenceVerbs = new Set(['manual', 'daily', 'weekly', 'monthly']);
|
|
516
650
|
|
|
651
|
+
if (operatorVerb === 'checkpoint') {
|
|
652
|
+
const synthesisSummary = readSynthesisSummaryFromFlags(args.flags);
|
|
653
|
+
const result = await aquifer.checkpoints.runProducer({
|
|
654
|
+
scopeId: args.flags['scope-id'] || undefined,
|
|
655
|
+
scopeKind: args.flags['scope-kind'] || undefined,
|
|
656
|
+
scopeKey: args.flags['scope-key'] || undefined,
|
|
657
|
+
source: args.flags.source || undefined,
|
|
658
|
+
agentId: args.flags['agent-id'] || undefined,
|
|
659
|
+
minFinalizations: args.flags['min-finalizations']
|
|
660
|
+
? parsePositiveInt(args.flags['min-finalizations'], 10)
|
|
661
|
+
: undefined,
|
|
662
|
+
limit: parsePositiveInt(args.flags.limit, 50),
|
|
663
|
+
checkpointKey: args.flags['checkpoint-key'] || undefined,
|
|
664
|
+
policyVersion: args.flags['policy-version'] || undefined,
|
|
665
|
+
force: args.flags.force === true,
|
|
666
|
+
apply: args.flags.apply === true,
|
|
667
|
+
finalize: args.flags.finalize === true,
|
|
668
|
+
includeSynthesisPrompt: args.flags['include-synthesis-prompt'] === true,
|
|
669
|
+
synthesisSummary,
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
if (args.flags.json) {
|
|
673
|
+
console.log(JSON.stringify(result, null, 2));
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
console.log(result.due
|
|
678
|
+
? `Checkpoint due for ${result.scope.scopeKey}: ${result.sourceFinalizationCount} finalized source(s).`
|
|
679
|
+
: `Checkpoint not ready for ${result.scope.scopeKey}: ${result.sourceFinalizationCount}/${result.minFinalizations} finalized source(s).`);
|
|
680
|
+
if (result.range) {
|
|
681
|
+
console.log(`Range: finalization ${result.range.fromFinalizationIdExclusive} -> ${result.range.toFinalizationIdInclusive}`);
|
|
682
|
+
}
|
|
683
|
+
if (result.synthesisPrompt) {
|
|
684
|
+
console.log('\nCheckpoint synthesis prompt:\n');
|
|
685
|
+
console.log(result.synthesisPrompt);
|
|
686
|
+
}
|
|
687
|
+
if (result.run) {
|
|
688
|
+
console.log(`Run: #${result.run.id} status=${result.run.status}`);
|
|
689
|
+
} else if (result.runInput) {
|
|
690
|
+
console.log(`Run input prepared: status=${result.runInput.status}`);
|
|
691
|
+
console.log('Mode: dry-run only. Re-run with --apply and an explicit synthesis summary to write checkpoint_runs.');
|
|
692
|
+
}
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
|
|
517
696
|
if (operatorVerb === 'archive-distill') {
|
|
518
697
|
const inputPath = args.flags.input || args._[2];
|
|
519
698
|
if (!inputPath) {
|
|
@@ -544,7 +723,7 @@ async function cmdOperator(aquifer, args) {
|
|
|
544
723
|
}
|
|
545
724
|
|
|
546
725
|
if (operatorVerb !== 'compaction' && operatorVerb !== 'compact' && !cadenceVerbs.has(operatorVerb)) {
|
|
547
|
-
console.error('Usage: aquifer operator <compaction|archive-distill> [...]');
|
|
726
|
+
console.error('Usage: aquifer operator <compaction|checkpoint|archive-distill> [...]');
|
|
548
727
|
process.exit(1);
|
|
549
728
|
}
|
|
550
729
|
|
|
@@ -650,6 +829,7 @@ async function main() {
|
|
|
650
829
|
Commands:
|
|
651
830
|
quickstart Verify end-to-end setup (migrate → commit → enrich → recall)
|
|
652
831
|
migrate Run database migrations
|
|
832
|
+
backend-info Show selected backend capabilities without connecting to a database
|
|
653
833
|
recall <query> Search sessions (requires embed config)
|
|
654
834
|
evidence-recall <query> Search legacy session/evidence plane explicitly
|
|
655
835
|
feedback Record trust feedback on a session
|
|
@@ -661,7 +841,7 @@ Commands:
|
|
|
661
841
|
stats Show database statistics
|
|
662
842
|
export Export sessions as JSONL
|
|
663
843
|
bootstrap Show recent session context (for new session start)
|
|
664
|
-
codex-recovery ... Inspect or run Codex
|
|
844
|
+
codex-recovery ... Inspect or run Codex recovery/checkpoint flows
|
|
665
845
|
ingest-opencode Import sessions from OpenCode's local SQLite DB
|
|
666
846
|
mcp Start MCP server
|
|
667
847
|
|
|
@@ -697,7 +877,10 @@ Options:
|
|
|
697
877
|
--include-synthesis-prompt Include timer synthesis prompt in operator output
|
|
698
878
|
--synthesis-summary JSON Timer synthesis summary JSON to attach to a compaction plan
|
|
699
879
|
--synthesis-summary-file P Read timer synthesis summary JSON from file
|
|
880
|
+
--min-finalizations N Min finalized session summaries before checkpoint proposal
|
|
881
|
+
--checkpoint-key KEY Explicit checkpoint key when applying checkpoint producer output
|
|
700
882
|
--scope-key A,B Limit compaction snapshot to specific scope keys
|
|
883
|
+
--scope-id ID Target scope row id for checkpoint producer
|
|
701
884
|
--scope-kind KIND Explicit synthesis target scope kind
|
|
702
885
|
--snapshot-as-of ISO Read active snapshot as of a specific instant
|
|
703
886
|
--claim-lease-seconds N Override compaction apply lease
|
|
@@ -711,6 +894,11 @@ Operator examples:
|
|
|
711
894
|
aquifer operator compaction daily --include-synthesis-prompt --json
|
|
712
895
|
aquifer operator compaction manual --period-start 2026-04-27T00:00:00Z --period-end 2026-04-28T00:00:00Z --apply
|
|
713
896
|
aquifer operator compaction daily --synthesis-summary-file /tmp/timer-summary.json --apply --promote-candidates --json
|
|
897
|
+
aquifer operator checkpoint --scope-key project:aquifer --min-finalizations 10 --include-synthesis-prompt --json
|
|
898
|
+
aquifer operator checkpoint --scope-id 7 --synthesis-summary-file /tmp/checkpoint-summary.json --apply --finalize --json
|
|
899
|
+
AQUIFER_BACKEND=local aquifer backend-info --json
|
|
900
|
+
aquifer codex-recovery checkpoint-heartbeat --hook-stdin --scope-key project:aquifer
|
|
901
|
+
aquifer codex-recovery checkpoint-heartbeat-hook --scope-key project:aquifer --hooks-path "$CODEX_HOME/hooks.json" --json
|
|
714
902
|
aquifer operator archive-distill --input /tmp/archive-snapshot.json --json`);
|
|
715
903
|
process.exit(0);
|
|
716
904
|
}
|
|
@@ -743,6 +931,14 @@ Operator examples:
|
|
|
743
931
|
return;
|
|
744
932
|
}
|
|
745
933
|
|
|
934
|
+
if (command === 'backend-info') {
|
|
935
|
+
if (args.flags.config) {
|
|
936
|
+
process.env.AQUIFER_CONFIG = args.flags.config;
|
|
937
|
+
}
|
|
938
|
+
await cmdBackendInfo(args);
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
|
|
746
942
|
// All other commands need an Aquifer instance
|
|
747
943
|
const configOverrides = {};
|
|
748
944
|
if (args.flags.config) {
|
|
@@ -755,15 +951,18 @@ Operator examples:
|
|
|
755
951
|
// Production commands (migrate, mcp, recall, ...) stay strict — they expect
|
|
756
952
|
// the operator to have set env explicitly.
|
|
757
953
|
if (command === 'quickstart') {
|
|
758
|
-
const {
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
console.log(
|
|
764
|
-
|
|
954
|
+
const selected = loadConfig({ overrides: configOverrides });
|
|
955
|
+
if (selected.storage.backend !== 'local') {
|
|
956
|
+
const { autodetectForQuickstart } = require('./shared/autodetect');
|
|
957
|
+
quickstartDetected = await autodetectForQuickstart(process.env);
|
|
958
|
+
if (Object.keys(quickstartDetected).length > 0) {
|
|
959
|
+
console.log('Autodetected localhost services (env not set):');
|
|
960
|
+
for (const [k, v] of Object.entries(quickstartDetected)) {
|
|
961
|
+
console.log(` ${k}=${v}`);
|
|
962
|
+
process.env[k] = v;
|
|
963
|
+
}
|
|
964
|
+
console.log(' Export these in your shell (or MCP client env) to make them permanent.\n');
|
|
765
965
|
}
|
|
766
|
-
console.log(' Export these in your shell (or MCP client env) to make them permanent.\n');
|
|
767
966
|
}
|
|
768
967
|
}
|
|
769
968
|
|
|
@@ -785,7 +984,7 @@ Operator examples:
|
|
|
785
984
|
await cmdQuickstart(aquifer);
|
|
786
985
|
break;
|
|
787
986
|
case 'migrate':
|
|
788
|
-
await cmdMigrate(aquifer);
|
|
987
|
+
await cmdMigrate(aquifer, args);
|
|
789
988
|
break;
|
|
790
989
|
case 'recall':
|
|
791
990
|
await cmdRecall(aquifer, args);
|
|
@@ -837,6 +1036,10 @@ Operator examples:
|
|
|
837
1036
|
// Export for testing; execute only when run directly
|
|
838
1037
|
module.exports = {
|
|
839
1038
|
parseArgs,
|
|
1039
|
+
selectedBackendInfo,
|
|
1040
|
+
cmdBackendInfo,
|
|
1041
|
+
cmdMigrate,
|
|
1042
|
+
cmdLocalQuickstart,
|
|
840
1043
|
cmdOperator,
|
|
841
1044
|
readSynthesisSummaryFromFlags,
|
|
842
1045
|
};
|