@shadowforge0/aquifer-memory 0.7.0 → 0.9.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/README.md +167 -83
- package/consumers/cli.js +74 -78
- package/consumers/mcp.js +69 -5
- package/consumers/openclaw-plugin.js +18 -5
- package/consumers/shared/config.js +3 -3
- package/consumers/shared/factory.js +6 -4
- package/core/aquifer.js +157 -17
- package/core/storage.js +12 -3
- package/docs/setup.md +194 -0
- package/index.js +2 -1
- package/package.json +8 -8
- package/pipeline/normalize/adapters/claude-code.js +90 -0
- package/pipeline/normalize/adapters/gateway.js +67 -0
- package/pipeline/normalize/constants.js +12 -0
- package/pipeline/normalize/detect.js +52 -0
- package/pipeline/normalize/extract.js +49 -0
- package/pipeline/normalize/index.js +129 -0
- package/pipeline/normalize/timestamp.js +33 -0
- package/scripts/smoke.mjs +115 -0
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ Sessions, summaries, turn-level embeddings, entity graph — all live in one dat
|
|
|
32
32
|
| **Ranking** | 3-way RRF: FTS + session embedding + turn embedding | Single vector similarity |
|
|
33
33
|
| **Knowledge graph** | Built-in entity extraction & co-occurrence | Usually separate system |
|
|
34
34
|
| **Multi-tenant** | `tenant_id` on every table, day-1 | Often an afterthought |
|
|
35
|
-
| **Dependencies** |
|
|
35
|
+
| **Dependencies** | `pg` + MCP SDK | Multiple SDKs |
|
|
36
36
|
|
|
37
37
|
### Before and after
|
|
38
38
|
|
|
@@ -48,97 +48,177 @@ Sessions, summaries, turn-level embeddings, entity graph — all live in one dat
|
|
|
48
48
|
|
|
49
49
|
---
|
|
50
50
|
|
|
51
|
-
##
|
|
51
|
+
## Requirements
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
| Component | Required? | Purpose | Example |
|
|
54
|
+
|-----------|-----------|---------|---------|
|
|
55
|
+
| Node.js >= 18 | Yes | Runtime | — |
|
|
56
|
+
| PostgreSQL 15+ | Yes | Storage for sessions, summaries, entities | Local, Docker, or managed |
|
|
57
|
+
| pgvector extension | Yes | Vector similarity search | `CREATE EXTENSION vector;` (included in `pgvector/pgvector` Docker image) |
|
|
58
|
+
| Embedding endpoint | Yes (for recall) | Turn + session embedding | Ollama `bge-m3`, OpenAI `text-embedding-3-small`, any OpenAI-compatible API |
|
|
59
|
+
| LLM endpoint | Optional | Built-in summarization during `enrich` | Ollama, OpenRouter, OpenAI — or provide your own `summaryFn` |
|
|
60
|
+
| `@modelcontextprotocol/sdk` + `zod` | Yes (for MCP server) | MCP protocol runtime | Included in dependencies — installed automatically |
|
|
54
61
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Quick Start (MCP Server)
|
|
65
|
+
|
|
66
|
+
This gets you from zero to a working MCP memory server. For library API usage, see [API Reference](#api-reference) below.
|
|
58
67
|
|
|
59
|
-
###
|
|
68
|
+
### 1. Start the stack
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
docker compose up -d
|
|
72
|
+
# Starts PostgreSQL 16 + pgvector and Ollama with bge-m3 (auto-pulled).
|
|
73
|
+
# First run takes a few minutes while Ollama downloads the model.
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Already have PostgreSQL + pgvector and an embedding endpoint? Skip this step.
|
|
77
|
+
|
|
78
|
+
### 2. Install
|
|
60
79
|
|
|
61
80
|
```bash
|
|
62
81
|
npm install @shadowforge0/aquifer-memory
|
|
63
82
|
```
|
|
64
83
|
|
|
65
|
-
###
|
|
84
|
+
### 3. Configure + verify
|
|
66
85
|
|
|
67
|
-
```
|
|
68
|
-
|
|
86
|
+
```bash
|
|
87
|
+
export DATABASE_URL="postgresql://aquifer:aquifer@localhost:5432/aquifer"
|
|
88
|
+
export AQUIFER_EMBED_BASE_URL="http://localhost:11434/v1"
|
|
89
|
+
export AQUIFER_EMBED_MODEL="bge-m3"
|
|
69
90
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
embed: {
|
|
75
|
-
fn: async (texts) => embeddings, // your embedding function
|
|
76
|
-
dim: 1024, // optional dimension hint
|
|
77
|
-
},
|
|
78
|
-
llm: {
|
|
79
|
-
fn: async (prompt) => text, // your LLM function (for built-in summarize)
|
|
80
|
-
},
|
|
81
|
-
entities: {
|
|
82
|
-
enabled: true,
|
|
83
|
-
scope: 'my-app', // entity namespace (default: 'default')
|
|
84
|
-
},
|
|
85
|
-
});
|
|
91
|
+
npx aquifer quickstart
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`quickstart` runs migrations, commits a test session, embeds it, recalls it, and cleans up. If it prints `✓ Aquifer is working`, you're done.
|
|
86
95
|
|
|
87
|
-
|
|
88
|
-
|
|
96
|
+
### 4. Start the MCP server
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npx aquifer mcp
|
|
89
100
|
```
|
|
90
101
|
|
|
91
|
-
|
|
102
|
+
See [.env.example](.env.example) for all env vars, or [docs/setup.md](docs/setup.md) for the full setup guide.
|
|
92
103
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Environment Variables
|
|
107
|
+
|
|
108
|
+
| Variable | Required? | Purpose | Example |
|
|
109
|
+
|----------|-----------|---------|---------|
|
|
110
|
+
| `DATABASE_URL` | Yes | PostgreSQL connection string | `postgresql://user:pass@localhost:5432/mydb` |
|
|
111
|
+
| `AQUIFER_SCHEMA` | No | PG schema name (default: `aquifer`) | `memory` |
|
|
112
|
+
| `AQUIFER_TENANT_ID` | No | Multi-tenant key (default: `default`) | `my-app` |
|
|
113
|
+
| `AQUIFER_EMBED_BASE_URL` | Yes (for recall) | Embedding API base URL | `http://localhost:11434/v1` |
|
|
114
|
+
| `AQUIFER_EMBED_MODEL` | Yes (for recall) | Embedding model name | `bge-m3` |
|
|
115
|
+
| `AQUIFER_EMBED_API_KEY` | Provider-dependent | API key for hosted embedding providers | `sk-...` |
|
|
116
|
+
| `AQUIFER_EMBED_DIM` | No | Embedding dimension override (auto-detected) | `1024` |
|
|
117
|
+
| `AQUIFER_LLM_BASE_URL` | No | LLM API base URL (for built-in summarization) | `http://localhost:11434/v1` |
|
|
118
|
+
| `AQUIFER_LLM_MODEL` | No | LLM model name | `llama3.1` |
|
|
119
|
+
| `AQUIFER_LLM_API_KEY` | Provider-dependent | API key for hosted LLM providers | `sk-...` |
|
|
120
|
+
| `AQUIFER_ENTITIES_ENABLED` | No | Enable knowledge graph (default: `false`) | `true` |
|
|
121
|
+
| `AQUIFER_ENTITY_SCOPE` | No | Entity namespace (default: `default`) | `my-app` |
|
|
122
|
+
| `AQUIFER_RERANK_ENABLED` | No | Enable cross-encoder reranking | `true` |
|
|
123
|
+
| `AQUIFER_RERANK_PROVIDER` | No | Reranker provider: `tei`, `jina`, `openrouter` | `tei` |
|
|
124
|
+
| `AQUIFER_RERANK_BASE_URL` | No | Reranker endpoint | `http://localhost:8080` |
|
|
125
|
+
| `AQUIFER_AGENT_ID` | No | Default agent ID | `main` |
|
|
126
|
+
|
|
127
|
+
Full env-to-config mapping is in [consumers/shared/config.js](consumers/shared/config.js).
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Host Integration
|
|
132
|
+
|
|
133
|
+
MCP is the primary integration surface. Agent hosts connect to the Aquifer MCP server, which exposes four tools: `session_recall`, `session_feedback`, `memory_stats`, `memory_pending`.
|
|
134
|
+
|
|
135
|
+
| Integration | Route | Status | When to use |
|
|
136
|
+
|-------------|-------|--------|-------------|
|
|
137
|
+
| MCP server | `consumers/mcp.js` | Primary | Claude Code, OpenClaw, Codex, any MCP-capable host |
|
|
138
|
+
| Library API | `createAquifer()` | Primary | Backend apps, custom pipelines, direct Node.js usage |
|
|
139
|
+
| CLI | `consumers/cli.js` | Secondary | Operations, debugging, manual recall/backfill |
|
|
140
|
+
| OpenClaw plugin | `consumers/openclaw-plugin.js` | Compatibility only | Session capture via `before_reset` — not for tool delivery |
|
|
141
|
+
|
|
142
|
+
### Claude Code
|
|
143
|
+
|
|
144
|
+
Add to your project's `.claude.json` or user-level MCP config:
|
|
145
|
+
|
|
146
|
+
```json
|
|
147
|
+
{
|
|
148
|
+
"mcpServers": {
|
|
149
|
+
"aquifer": {
|
|
150
|
+
"type": "stdio",
|
|
151
|
+
"command": "node",
|
|
152
|
+
"args": ["/path/to/aquifer/consumers/mcp.js"],
|
|
153
|
+
"env": {
|
|
154
|
+
"DATABASE_URL": "postgresql://...",
|
|
155
|
+
"AQUIFER_EMBED_BASE_URL": "http://localhost:11434/v1",
|
|
156
|
+
"AQUIFER_EMBED_MODEL": "bge-m3"
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
111
161
|
```
|
|
112
162
|
|
|
113
|
-
|
|
163
|
+
Tools appear as `mcp__aquifer__session_recall`, `mcp__aquifer__session_feedback`, etc.
|
|
114
164
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
165
|
+
### OpenClaw
|
|
166
|
+
|
|
167
|
+
Add to `openclaw.json` under `mcp.servers`:
|
|
168
|
+
|
|
169
|
+
```json
|
|
170
|
+
{
|
|
171
|
+
"mcp": {
|
|
172
|
+
"servers": {
|
|
173
|
+
"aquifer": {
|
|
174
|
+
"command": "node",
|
|
175
|
+
"args": ["/path/to/aquifer/consumers/mcp.js"],
|
|
176
|
+
"env": {
|
|
177
|
+
"DATABASE_URL": "postgresql://...",
|
|
178
|
+
"AQUIFER_EMBED_BASE_URL": "http://localhost:11434/v1",
|
|
179
|
+
"AQUIFER_EMBED_MODEL": "bge-m3"
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
123
185
|
```
|
|
124
186
|
|
|
187
|
+
Tools materialize as `aquifer__session_recall`, `aquifer__session_feedback`, `aquifer__memory_stats`, `aquifer__memory_pending` (server name prefix added by the host).
|
|
188
|
+
|
|
189
|
+
The OpenClaw plugin (`consumers/openclaw-plugin.js`) is retained for session capture via `before_reset` but is **not** the recommended tool delivery path. Use MCP.
|
|
190
|
+
|
|
191
|
+
### Other MCP-capable hosts
|
|
192
|
+
|
|
193
|
+
Any host that supports MCP stdio can connect the same way — point it at `node consumers/mcp.js` with the required env vars. The MCP server is the canonical external contract.
|
|
194
|
+
|
|
125
195
|
---
|
|
126
196
|
|
|
127
197
|
## Architecture
|
|
128
198
|
|
|
129
199
|
```
|
|
130
|
-
|
|
131
|
-
│
|
|
132
|
-
│
|
|
133
|
-
|
|
200
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
201
|
+
│ Agent Hosts │
|
|
202
|
+
│ Claude Code · OpenClaw · Codex · OpenCode · ... │
|
|
203
|
+
└──────────────────────┬───────────────────────────────────────┘
|
|
204
|
+
│ MCP (stdio or HTTP)
|
|
205
|
+
┌──────────────────────▼───────────────────────────────────────┐
|
|
206
|
+
│ Aquifer MCP Server (canonical API) │
|
|
207
|
+
│ session_recall · session_feedback · memory_stats · ... │
|
|
208
|
+
└──────────────────────┬───────────────────────────────────────┘
|
|
209
|
+
│
|
|
210
|
+
┌──────────────────────▼───────────────────────────────────────┐
|
|
211
|
+
│ createAquifer (engine) │
|
|
212
|
+
│ Config · Migration · Ingest · Recall · Enrich │
|
|
213
|
+
└────────┬──────────┬──────────┬──────────┬────────────────────┘
|
|
134
214
|
│ │ │ │
|
|
135
215
|
┌────▼───┐ ┌────▼────┐ ┌──▼───┐ ┌───▼──────────┐
|
|
136
216
|
│storage │ │hybrid- │ │entity│ │ pipeline/ │
|
|
137
217
|
│ .js │ │rank.js │ │ .js │ │summarize.js │
|
|
138
218
|
└────────┘ └─────────┘ └──────┘ │embed.js │
|
|
139
219
|
│ │ │extract-ent.js │
|
|
140
|
-
┌────▼───────────┐ ┌───▼──┐
|
|
141
|
-
│ PostgreSQL │ │ LLM │
|
|
220
|
+
┌────▼───────────┐ ┌───▼──┐ │rerank.js │
|
|
221
|
+
│ PostgreSQL │ │ LLM │ └───────────────┘
|
|
142
222
|
│ + pgvector │ │ API │
|
|
143
223
|
└────────────────┘ └──────┘
|
|
144
224
|
|
|
@@ -155,7 +235,7 @@ const results = await aquifer.recall('auth middleware decision', {
|
|
|
155
235
|
|
|
156
236
|
| File | Purpose |
|
|
157
237
|
|------|---------|
|
|
158
|
-
| `index.js` | Entry point — exports `createAquifer`, `createEmbedder` |
|
|
238
|
+
| `index.js` | Entry point — exports `createAquifer`, `createEmbedder`, `createReranker`, `normalizeSession` |
|
|
159
239
|
| `core/aquifer.js` | Main facade: `migrate()`, `ingest()`, `recall()`, `enrich()` |
|
|
160
240
|
| `core/storage.js` | Session/summary/turn CRUD, FTS search, embedding search |
|
|
161
241
|
| `core/entity.js` | Entity upsert, mention tracking, relation graph, normalization |
|
|
@@ -163,6 +243,8 @@ const results = await aquifer.recall('auth middleware decision', {
|
|
|
163
243
|
| `pipeline/summarize.js` | LLM-powered session summarization with structured output |
|
|
164
244
|
| `pipeline/embed.js` | Embedding client (any OpenAI-compatible API) |
|
|
165
245
|
| `pipeline/extract-entities.js` | LLM-powered entity extraction (12 types) |
|
|
246
|
+
| `pipeline/rerank.js` | Cross-encoder reranking (TEI, Jina, OpenRouter) |
|
|
247
|
+
| `pipeline/normalize/` | Session normalization for Claude Code / gateway noise |
|
|
166
248
|
| `schema/001-base.sql` | DDL: sessions, summaries, turn_embeddings, FTS indexes |
|
|
167
249
|
| `schema/002-entities.sql` | DDL: entities, mentions, relations, entity_sessions |
|
|
168
250
|
| `schema/003-trust-feedback.sql` | DDL: trust_score column, session_feedback audit trail |
|
|
@@ -361,7 +443,9 @@ Closes the PostgreSQL connection pool (only if Aquifer created it).
|
|
|
361
443
|
|
|
362
444
|
## Configuration
|
|
363
445
|
|
|
364
|
-
Aquifer
|
|
446
|
+
Aquifer resolves config from three sources in priority order: config file → environment variables → programmatic overrides. See [consumers/shared/config.js](consumers/shared/config.js) for the full env-to-config mapping.
|
|
447
|
+
|
|
448
|
+
Config file is auto-discovered at `aquifer.config.json` in the working directory, or set `AQUIFER_CONFIG=/path/to/config.json`.
|
|
365
449
|
|
|
366
450
|
```javascript
|
|
367
451
|
createAquifer({
|
|
@@ -395,22 +479,6 @@ createAquifer({
|
|
|
395
479
|
|
|
396
480
|
Fallback chain: `config.entities.scope` → `'default'`.
|
|
397
481
|
|
|
398
|
-
### Consumers (CLI, MCP, OpenClaw plugin)
|
|
399
|
-
|
|
400
|
-
For consumer-based setup using environment variables instead of code:
|
|
401
|
-
|
|
402
|
-
```bash
|
|
403
|
-
export DATABASE_URL="postgresql://..."
|
|
404
|
-
export AQUIFER_EMBED_BASE_URL="http://localhost:11434/v1"
|
|
405
|
-
export AQUIFER_EMBED_MODEL="bge-m3"
|
|
406
|
-
export AQUIFER_ENTITIES_ENABLED=true
|
|
407
|
-
|
|
408
|
-
aquifer migrate
|
|
409
|
-
aquifer recall "search query" --limit 5
|
|
410
|
-
aquifer backfill --concurrency 3
|
|
411
|
-
aquifer stats --json
|
|
412
|
-
```
|
|
413
|
-
|
|
414
482
|
---
|
|
415
483
|
|
|
416
484
|
## Database Schema
|
|
@@ -425,6 +493,8 @@ aquifer stats --json
|
|
|
425
493
|
|
|
426
494
|
Key indexes: GIN on messages, GiST on `tsvector`, ivfflat on embeddings, B-tree on tenant/agent/timestamps.
|
|
427
495
|
|
|
496
|
+
Note: the schema uses basic ivfflat indexes suitable for development and moderate-scale use. For large deployments (100k+ embeddings), consider adding HNSW indexes — this is a future optimization area, not included out of the box.
|
|
497
|
+
|
|
428
498
|
### 002-entities.sql
|
|
429
499
|
|
|
430
500
|
| Table | Purpose |
|
|
@@ -446,15 +516,29 @@ Also adds `trust_score` column to `session_summaries` (default 0.5, range 0–1)
|
|
|
446
516
|
|
|
447
517
|
---
|
|
448
518
|
|
|
519
|
+
## Troubleshooting
|
|
520
|
+
|
|
521
|
+
**`error: type "vector" does not exist`** — pgvector extension is not installed. Run `CREATE EXTENSION IF NOT EXISTS vector;` as a superuser, or use the `pgvector/pgvector` Docker image which includes it.
|
|
522
|
+
|
|
523
|
+
**`aquifer mcp requires @modelcontextprotocol/sdk and zod`** — These are now regular dependencies and should be installed automatically. If you see this error, run `npm install` again to ensure all deps are present.
|
|
524
|
+
|
|
525
|
+
**Recall returns no results** — Make sure you've run `enrich` after `commit`. Raw sessions are not searchable until enriched (summarized + embedded). Check `aquifer stats` to see if summaries and turn embeddings exist.
|
|
526
|
+
|
|
527
|
+
**OpenClaw tools not visible** — Use `mcp.servers.aquifer` in `openclaw.json`, not the plugin. Tools appear as `aquifer__session_recall` etc. The plugin (`consumers/openclaw-plugin.js`) is for session capture only.
|
|
528
|
+
|
|
529
|
+
**Embedding provider connection refused** — Verify your `AQUIFER_EMBED_BASE_URL` is reachable. For local Ollama, make sure the server is running and the model is pulled (`ollama pull bge-m3`).
|
|
530
|
+
|
|
531
|
+
---
|
|
532
|
+
|
|
449
533
|
## Dependencies
|
|
450
534
|
|
|
451
535
|
| Package | Purpose |
|
|
452
536
|
|---------|---------|
|
|
453
537
|
| `pg` ≥ 8.13 | PostgreSQL client |
|
|
538
|
+
| `@modelcontextprotocol/sdk` ≥ 1.29 | MCP server protocol |
|
|
539
|
+
| `zod` ≥ 3.25 | Schema validation (MCP tools) |
|
|
454
540
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
LLM and embedding calls use raw HTTP — no SDK required.
|
|
541
|
+
LLM and embedding calls use raw HTTP — no additional SDK required.
|
|
458
542
|
|
|
459
543
|
---
|
|
460
544
|
|
package/consumers/cli.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Aquifer CLI
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
8
|
+
* aquifer quickstart Verify end-to-end setup
|
|
8
9
|
* aquifer migrate Run database migrations
|
|
9
10
|
* aquifer recall <query> [options] Search sessions
|
|
10
11
|
* aquifer backfill [options] Enrich pending sessions
|
|
@@ -14,7 +15,6 @@
|
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
17
|
const { createAquiferFromConfig } = require('./shared/factory');
|
|
17
|
-
const { loadConfig } = require('./shared/config');
|
|
18
18
|
|
|
19
19
|
// ---------------------------------------------------------------------------
|
|
20
20
|
// Argument parser (minimal, no deps)
|
|
@@ -120,30 +120,12 @@ async function cmdBackfill(aquifer, args) {
|
|
|
120
120
|
const skipTurnEmbed = !!args.flags['skip-turn-embed'];
|
|
121
121
|
const skipEntities = !!args.flags['skip-entities'];
|
|
122
122
|
|
|
123
|
-
const
|
|
124
|
-
const schema = config.schema || 'aquifer';
|
|
125
|
-
const tenantId = config.tenantId || 'default';
|
|
126
|
-
const pool = aquifer._pool;
|
|
127
|
-
|
|
128
|
-
if (!pool) {
|
|
129
|
-
console.error('Backfill requires direct pool access.');
|
|
130
|
-
process.exit(1);
|
|
131
|
-
}
|
|
123
|
+
const pending = await aquifer.getPendingSessions({ limit });
|
|
132
124
|
|
|
133
|
-
|
|
134
|
-
const { rows } = await pool.query(`
|
|
135
|
-
SELECT session_id, agent_id, processing_status
|
|
136
|
-
FROM ${qi(schema)}.sessions
|
|
137
|
-
WHERE tenant_id = $1
|
|
138
|
-
AND processing_status IN ('pending', 'failed')
|
|
139
|
-
ORDER BY started_at DESC
|
|
140
|
-
LIMIT $2
|
|
141
|
-
`, [tenantId, limit]);
|
|
142
|
-
|
|
143
|
-
console.log(`Found ${rows.length} sessions to backfill${dryRun ? ' (dry-run)' : ''}`);
|
|
125
|
+
console.log(`Found ${pending.length} sessions to backfill${dryRun ? ' (dry-run)' : ''}`);
|
|
144
126
|
|
|
145
127
|
let enriched = 0, failed = 0;
|
|
146
|
-
for (const row of
|
|
128
|
+
for (const row of pending) {
|
|
147
129
|
if (dryRun) {
|
|
148
130
|
console.log(` [dry-run] ${row.session_id} (${row.agent_id}) status=${row.processing_status}`);
|
|
149
131
|
continue;
|
|
@@ -164,40 +146,12 @@ async function cmdBackfill(aquifer, args) {
|
|
|
164
146
|
}
|
|
165
147
|
}
|
|
166
148
|
|
|
167
|
-
console.log(`\nDone. enriched=${enriched} failed=${failed} total=${
|
|
149
|
+
console.log(`\nDone. enriched=${enriched} failed=${failed} total=${pending.length}`);
|
|
168
150
|
if (failed > 0) process.exitCode = 2;
|
|
169
151
|
}
|
|
170
152
|
|
|
171
153
|
async function cmdStats(aquifer, args) {
|
|
172
|
-
const
|
|
173
|
-
const schema = config.schema || 'aquifer';
|
|
174
|
-
const tenantId = config.tenantId || 'default';
|
|
175
|
-
const pool = aquifer._pool;
|
|
176
|
-
|
|
177
|
-
if (!pool) {
|
|
178
|
-
console.error('Stats requires direct pool access.');
|
|
179
|
-
process.exit(1);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const qi = (id) => `"${id}"`;
|
|
183
|
-
const [sessions, summaries, turns, entities] = await Promise.all([
|
|
184
|
-
pool.query(`SELECT processing_status, COUNT(*)::int as count FROM ${qi(schema)}.sessions WHERE tenant_id = $1 GROUP BY processing_status`, [tenantId]),
|
|
185
|
-
pool.query(`SELECT COUNT(*)::int as count FROM ${qi(schema)}.session_summaries WHERE tenant_id = $1`, [tenantId]),
|
|
186
|
-
pool.query(`SELECT COUNT(*)::int as count FROM ${qi(schema)}.turn_embeddings WHERE tenant_id = $1`, [tenantId]),
|
|
187
|
-
pool.query(`SELECT COUNT(*)::int as count FROM ${qi(schema)}.entities WHERE tenant_id = $1`, [tenantId]).catch(() => ({ rows: [{ count: 0 }] })),
|
|
188
|
-
]);
|
|
189
|
-
|
|
190
|
-
const timeRange = await pool.query(`SELECT MIN(started_at) as earliest, MAX(started_at) as latest FROM ${qi(schema)}.sessions WHERE tenant_id = $1`, [tenantId]);
|
|
191
|
-
|
|
192
|
-
const stats = {
|
|
193
|
-
sessions: Object.fromEntries(sessions.rows.map(r => [r.processing_status, r.count])),
|
|
194
|
-
sessionTotal: sessions.rows.reduce((s, r) => s + r.count, 0),
|
|
195
|
-
summaries: summaries.rows[0]?.count || 0,
|
|
196
|
-
turnEmbeddings: turns.rows[0]?.count || 0,
|
|
197
|
-
entities: entities.rows[0]?.count || 0,
|
|
198
|
-
earliest: timeRange.rows[0]?.earliest || null,
|
|
199
|
-
latest: timeRange.rows[0]?.latest || null,
|
|
200
|
-
};
|
|
154
|
+
const stats = await aquifer.getStats();
|
|
201
155
|
|
|
202
156
|
if (args.flags.json) {
|
|
203
157
|
console.log(JSON.stringify(stats, null, 2));
|
|
@@ -210,35 +164,73 @@ async function cmdStats(aquifer, args) {
|
|
|
210
164
|
}
|
|
211
165
|
}
|
|
212
166
|
|
|
213
|
-
async function
|
|
214
|
-
|
|
215
|
-
const schema = config.schema || 'aquifer';
|
|
216
|
-
const tenantId = config.tenantId || 'default';
|
|
217
|
-
const pool = aquifer._pool;
|
|
218
|
-
const output = args.flags.output || null;
|
|
219
|
-
const limit = parseInt(args.flags.limit || '1000', 10);
|
|
167
|
+
async function cmdQuickstart(aquifer) {
|
|
168
|
+
console.log('Aquifer quickstart — verifying end-to-end setup.\n');
|
|
220
169
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
170
|
+
// 1. Migrate
|
|
171
|
+
console.log('1/5 Running migrations...');
|
|
172
|
+
await aquifer.migrate();
|
|
173
|
+
console.log(' OK\n');
|
|
174
|
+
|
|
175
|
+
// 2. Commit
|
|
176
|
+
const sessionId = `quickstart-${Date.now()}`;
|
|
177
|
+
console.log('2/5 Committing test session...');
|
|
178
|
+
await aquifer.commit(sessionId, [
|
|
179
|
+
{ role: 'user', content: 'We decided to use PostgreSQL with pgvector for the AI memory store instead of a separate vector database.' },
|
|
180
|
+
{ role: 'assistant', content: 'Good choice. PG gives us ACID transactions, full-text search, and vector similarity all in one place.' },
|
|
181
|
+
{ role: 'user', content: 'The main advantage is turn-level embedding — we can find the exact moment a decision was made.' },
|
|
182
|
+
], { agentId: 'quickstart', source: 'quickstart' });
|
|
183
|
+
console.log(' OK\n');
|
|
184
|
+
|
|
185
|
+
// 3. Enrich (skip summary — LLM may not be configured)
|
|
186
|
+
console.log('3/5 Enriching (turn embeddings)...');
|
|
187
|
+
const enrichResult = await aquifer.enrich(sessionId, {
|
|
188
|
+
agentId: 'quickstart',
|
|
189
|
+
skipSummary: true,
|
|
190
|
+
skipEntities: true,
|
|
191
|
+
});
|
|
192
|
+
console.log(` OK — ${enrichResult.turnsEmbedded} turns embedded\n`);
|
|
225
193
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const
|
|
194
|
+
// 4. Recall
|
|
195
|
+
console.log('4/5 Recalling "PostgreSQL memory store"...');
|
|
196
|
+
const results = await aquifer.recall('PostgreSQL memory store', { limit: 3 });
|
|
197
|
+
if (results.length === 0) {
|
|
198
|
+
console.error(' FAIL — no results returned. Check your embedding config.');
|
|
199
|
+
process.exitCode = 1;
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
console.log(` OK — ${results.length} result(s), top score: ${results[0].score?.toFixed(3)}`);
|
|
203
|
+
if (results[0].matchedTurnText) {
|
|
204
|
+
console.log(` Matched: "${results[0].matchedTurnText.slice(0, 100)}..."`);
|
|
205
|
+
}
|
|
206
|
+
console.log();
|
|
207
|
+
|
|
208
|
+
// 5. Cleanup
|
|
209
|
+
console.log('5/5 Cleaning up test data...');
|
|
210
|
+
const { Pool } = require('pg');
|
|
211
|
+
const { loadConfig } = require('./shared/config');
|
|
212
|
+
const config = loadConfig();
|
|
213
|
+
const pool = new Pool({ connectionString: config.db.url });
|
|
214
|
+
const schema = config.schema || 'aquifer';
|
|
215
|
+
await pool.query(`DELETE FROM ${schema}.turn_embeddings WHERE session_id IN (SELECT id FROM ${schema}.sessions WHERE session_id = $1)`, [sessionId]);
|
|
216
|
+
await pool.query(`DELETE FROM ${schema}.session_summaries WHERE session_id IN (SELECT id FROM ${schema}.sessions WHERE session_id = $1)`, [sessionId]);
|
|
217
|
+
await pool.query(`DELETE FROM ${schema}.sessions WHERE session_id = $1`, [sessionId]);
|
|
218
|
+
await pool.end();
|
|
219
|
+
console.log(' OK\n');
|
|
220
|
+
|
|
221
|
+
console.log('✓ Aquifer is working. You can now start the MCP server:');
|
|
222
|
+
console.log(' npx aquifer mcp');
|
|
223
|
+
}
|
|
229
224
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
225
|
+
async function cmdExport(aquifer, args) {
|
|
226
|
+
const output = args.flags.output || null;
|
|
227
|
+
const limit = parseInt(args.flags.limit || '1000', 10);
|
|
233
228
|
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
ORDER BY s.started_at DESC
|
|
240
|
-
LIMIT $${params.length}
|
|
241
|
-
`, params);
|
|
229
|
+
const rows = await aquifer.exportSessions({
|
|
230
|
+
agentId: args.flags['agent-id'],
|
|
231
|
+
source: args.flags.source,
|
|
232
|
+
limit,
|
|
233
|
+
});
|
|
242
234
|
|
|
243
235
|
const stream = output ? require('fs').createWriteStream(output) : process.stdout;
|
|
244
236
|
for (const row of rows) {
|
|
@@ -268,6 +260,7 @@ async function main() {
|
|
|
268
260
|
console.log(`Usage: aquifer <command> [options]
|
|
269
261
|
|
|
270
262
|
Commands:
|
|
263
|
+
quickstart Verify end-to-end setup (migrate → commit → enrich → recall)
|
|
271
264
|
migrate Run database migrations
|
|
272
265
|
recall <query> Search sessions (requires embed config)
|
|
273
266
|
feedback Record trust feedback on a session
|
|
@@ -317,6 +310,9 @@ Options:
|
|
|
317
310
|
|
|
318
311
|
try {
|
|
319
312
|
switch (command) {
|
|
313
|
+
case 'quickstart':
|
|
314
|
+
await cmdQuickstart(aquifer);
|
|
315
|
+
break;
|
|
320
316
|
case 'migrate':
|
|
321
317
|
await cmdMigrate(aquifer);
|
|
322
318
|
break;
|
|
@@ -340,7 +336,7 @@ Options:
|
|
|
340
336
|
process.exit(1);
|
|
341
337
|
}
|
|
342
338
|
} finally {
|
|
343
|
-
|
|
339
|
+
await aquifer.close();
|
|
344
340
|
}
|
|
345
341
|
}
|
|
346
342
|
|