@shadowforge0/aquifer-memory 1.0.0 → 1.0.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/README.md +24 -4
- package/core/aquifer.js +6 -16
- package/core/storage.js +2 -118
- package/index.js +1 -2
- package/package.json +1 -3
package/README.md
CHANGED
|
@@ -130,13 +130,14 @@ Full env-to-config mapping is in [consumers/shared/config.js](consumers/shared/c
|
|
|
130
130
|
|
|
131
131
|
## Host Integration
|
|
132
132
|
|
|
133
|
-
MCP is the primary integration surface. Agent hosts connect to the Aquifer MCP server, which exposes
|
|
133
|
+
MCP is the primary integration surface. Agent hosts connect to the Aquifer MCP server, which exposes five tools: `session_recall`, `session_feedback`, `session_bootstrap`, `memory_stats`, `memory_pending`.
|
|
134
134
|
|
|
135
135
|
| Integration | Route | Status | When to use |
|
|
136
136
|
|-------------|-------|--------|-------------|
|
|
137
137
|
| MCP server | `consumers/mcp.js` | Primary | Claude Code, OpenClaw, Codex, any MCP-capable host |
|
|
138
138
|
| Library API | `createAquifer()` | Primary | Backend apps, custom pipelines, direct Node.js usage |
|
|
139
|
-
| CLI | `consumers/cli.js` | Secondary | Operations, debugging, manual recall/backfill |
|
|
139
|
+
| CLI | `consumers/cli.js` | Secondary | Operations, debugging, manual recall/backfill (`aquifer bootstrap`, `aquifer ingest-opencode`, etc.) |
|
|
140
|
+
| OpenCode ingest | `consumers/opencode.js` | Secondary | Import sessions from OpenCode's SQLite DB |
|
|
140
141
|
| OpenClaw plugin | `consumers/openclaw-plugin.js` | Compatibility only | Session capture via `before_reset` — not for tool delivery |
|
|
141
142
|
|
|
142
143
|
### Claude Code
|
|
@@ -160,7 +161,7 @@ Add to your project's `.claude.json` or user-level MCP config:
|
|
|
160
161
|
}
|
|
161
162
|
```
|
|
162
163
|
|
|
163
|
-
Tools appear as `mcp__aquifer__session_recall`, `mcp__aquifer__session_feedback`, etc.
|
|
164
|
+
Tools appear as `mcp__aquifer__session_recall`, `mcp__aquifer__session_feedback`, `mcp__aquifer__session_bootstrap`, etc.
|
|
164
165
|
|
|
165
166
|
### OpenClaw
|
|
166
167
|
|
|
@@ -184,7 +185,7 @@ Add to `openclaw.json` under `mcp.servers`:
|
|
|
184
185
|
}
|
|
185
186
|
```
|
|
186
187
|
|
|
187
|
-
Tools materialize as `aquifer__session_recall`, `aquifer__session_feedback`, `aquifer__memory_stats`, `aquifer__memory_pending` (server name prefix added by the host).
|
|
188
|
+
Tools materialize as `aquifer__session_recall`, `aquifer__session_feedback`, `aquifer__session_bootstrap`, `aquifer__memory_stats`, `aquifer__memory_pending` (server name prefix added by the host).
|
|
188
189
|
|
|
189
190
|
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
|
|
|
@@ -245,6 +246,7 @@ Any host that supports MCP stdio can connect the same way — point it at `node
|
|
|
245
246
|
| `pipeline/extract-entities.js` | LLM-powered entity extraction (12 types) |
|
|
246
247
|
| `pipeline/rerank.js` | Cross-encoder reranking (TEI, Jina, OpenRouter) |
|
|
247
248
|
| `pipeline/normalize/` | Session normalization for Claude Code / gateway noise |
|
|
249
|
+
| `consumers/opencode.js` | OpenCode SQLite ingest — reads sessions from OpenCode's local DB |
|
|
248
250
|
| `schema/001-base.sql` | DDL: sessions, summaries, turn_embeddings, FTS indexes |
|
|
249
251
|
| `schema/002-entities.sql` | DDL: entities, mentions, relations, entity_sessions |
|
|
250
252
|
| `schema/003-trust-feedback.sql` | DDL: trust_score column, session_feedback audit trail |
|
|
@@ -435,6 +437,24 @@ await aquifer.feedback('session-id', {
|
|
|
435
437
|
});
|
|
436
438
|
```
|
|
437
439
|
|
|
440
|
+
#### `aquifer.bootstrap(opts)`
|
|
441
|
+
|
|
442
|
+
Loads recent session context for a new conversation — summaries, open loops, and decisions. Time-based (no embedding search), designed for session-start injection.
|
|
443
|
+
|
|
444
|
+
```javascript
|
|
445
|
+
const result = await aquifer.bootstrap({
|
|
446
|
+
agentId: 'main',
|
|
447
|
+
limit: 5, // max sessions (default: 5)
|
|
448
|
+
lookbackDays: 14, // how far back (default: 14)
|
|
449
|
+
maxChars: 4000, // max output chars (default: 4000)
|
|
450
|
+
format: 'text', // 'text', 'structured', or 'both'
|
|
451
|
+
});
|
|
452
|
+
// format='text': result.text contains XML block ready for injection
|
|
453
|
+
// format='structured': result.sessions, result.openLoops, result.recentDecisions
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
Cross-session dedup on open loops and decisions, sentinel filtering (removes 無/none/n/a), and maxChars truncation.
|
|
457
|
+
|
|
438
458
|
#### `aquifer.close()`
|
|
439
459
|
|
|
440
460
|
Closes the PostgreSQL connection pool (only if Aquifer created it).
|
package/core/aquifer.js
CHANGED
|
@@ -918,7 +918,6 @@ function createAquifer(config) {
|
|
|
918
918
|
},
|
|
919
919
|
|
|
920
920
|
async getSessionFull(sessionId) {
|
|
921
|
-
// Try to find the session across agents by querying directly
|
|
922
921
|
const result = await pool.query(
|
|
923
922
|
`SELECT * FROM ${qi(schema)}.sessions
|
|
924
923
|
WHERE session_id = $1 AND tenant_id = $2
|
|
@@ -928,24 +927,15 @@ function createAquifer(config) {
|
|
|
928
927
|
const session = result.rows[0];
|
|
929
928
|
if (!session) return null;
|
|
930
929
|
|
|
931
|
-
const
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
),
|
|
938
|
-
pool.query(
|
|
939
|
-
`SELECT * FROM ${qi(schema)}.session_summaries
|
|
940
|
-
WHERE session_row_id = $1
|
|
941
|
-
LIMIT 1`,
|
|
942
|
-
[session.id]
|
|
943
|
-
),
|
|
944
|
-
]);
|
|
930
|
+
const sumResult = await pool.query(
|
|
931
|
+
`SELECT * FROM ${qi(schema)}.session_summaries
|
|
932
|
+
WHERE session_row_id = $1
|
|
933
|
+
LIMIT 1`,
|
|
934
|
+
[session.id]
|
|
935
|
+
);
|
|
945
936
|
|
|
946
937
|
return {
|
|
947
938
|
session,
|
|
948
|
-
segments: segResult.rows,
|
|
949
939
|
summary: sumResult.rows[0] || null,
|
|
950
940
|
};
|
|
951
941
|
},
|
package/core/storage.js
CHANGED
|
@@ -96,44 +96,6 @@ async function upsertSession(pool, {
|
|
|
96
96
|
};
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
// ---------------------------------------------------------------------------
|
|
100
|
-
// upsertSegments
|
|
101
|
-
// ---------------------------------------------------------------------------
|
|
102
|
-
|
|
103
|
-
async function upsertSegments(pool, sessionRowId, segments, { schema } = {}) {
|
|
104
|
-
if (!segments || segments.length === 0) return;
|
|
105
|
-
for (const seg of segments) {
|
|
106
|
-
await pool.query(
|
|
107
|
-
`INSERT INTO ${qi(schema)}.session_segments
|
|
108
|
-
(session_row_id, segment_no, start_msg_idx, end_msg_idx,
|
|
109
|
-
started_at, ended_at, raw_msg_count, effective_msg_count,
|
|
110
|
-
boundary_type, boundary_meta)
|
|
111
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10)
|
|
112
|
-
ON CONFLICT (session_row_id, segment_no) DO UPDATE SET
|
|
113
|
-
start_msg_idx = EXCLUDED.start_msg_idx,
|
|
114
|
-
end_msg_idx = EXCLUDED.end_msg_idx,
|
|
115
|
-
started_at = EXCLUDED.started_at,
|
|
116
|
-
ended_at = EXCLUDED.ended_at,
|
|
117
|
-
raw_msg_count = EXCLUDED.raw_msg_count,
|
|
118
|
-
effective_msg_count = EXCLUDED.effective_msg_count,
|
|
119
|
-
boundary_type = EXCLUDED.boundary_type,
|
|
120
|
-
boundary_meta = EXCLUDED.boundary_meta`,
|
|
121
|
-
[
|
|
122
|
-
sessionRowId,
|
|
123
|
-
seg.segmentNo,
|
|
124
|
-
seg.startMsgIdx !== null && seg.startMsgIdx !== undefined ? seg.startMsgIdx : null,
|
|
125
|
-
seg.endMsgIdx !== null && seg.endMsgIdx !== undefined ? seg.endMsgIdx : null,
|
|
126
|
-
seg.startedAt || null,
|
|
127
|
-
seg.endedAt || null,
|
|
128
|
-
seg.rawMsgCount || 0,
|
|
129
|
-
seg.effectiveMsgCount || 0,
|
|
130
|
-
seg.boundaryType || null,
|
|
131
|
-
seg.boundaryMeta ? JSON.stringify(seg.boundaryMeta) : '{}',
|
|
132
|
-
]
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
99
|
// ---------------------------------------------------------------------------
|
|
138
100
|
// upsertSummary
|
|
139
101
|
// ---------------------------------------------------------------------------
|
|
@@ -159,9 +121,8 @@ async function upsertSummary(pool, sessionRowId, {
|
|
|
159
121
|
`INSERT INTO ${qi(schema)}.session_summaries
|
|
160
122
|
(session_row_id, tenant_id, agent_id, session_id, summary_version, model, source_hash,
|
|
161
123
|
message_count, user_message_count, assistant_message_count,
|
|
162
|
-
boundary_count, fresh_tail_count,
|
|
163
124
|
started_at, ended_at, structured_summary, summary_text, embedding, updated_at)
|
|
164
|
-
VALUES ($1,$2,$3,$4,1,$5,$6,$7,$8,$9
|
|
125
|
+
VALUES ($1,$2,$3,$4,1,$5,$6,$7,$8,$9,$10,$11,COALESCE($12::jsonb,'{}'::jsonb),COALESCE($13,''),$14::vector,now())
|
|
165
126
|
ON CONFLICT (session_row_id) DO UPDATE SET
|
|
166
127
|
tenant_id = EXCLUDED.tenant_id,
|
|
167
128
|
agent_id = EXCLUDED.agent_id,
|
|
@@ -211,50 +172,6 @@ async function markStatus(pool, sessionRowId, status, error, { schema } = {}) {
|
|
|
211
172
|
return result.rows[0] || null;
|
|
212
173
|
}
|
|
213
174
|
|
|
214
|
-
// ---------------------------------------------------------------------------
|
|
215
|
-
// persistProcessingResults (@internal — prefer aquifer.enrich() for full pipeline)
|
|
216
|
-
// ---------------------------------------------------------------------------
|
|
217
|
-
|
|
218
|
-
async function persistProcessingResults(pool, sessionRowId, {
|
|
219
|
-
schema,
|
|
220
|
-
segments,
|
|
221
|
-
summaryText,
|
|
222
|
-
structuredSummary,
|
|
223
|
-
agentId,
|
|
224
|
-
sessionId,
|
|
225
|
-
tenantId,
|
|
226
|
-
model,
|
|
227
|
-
sourceHash,
|
|
228
|
-
msgCount,
|
|
229
|
-
userCount,
|
|
230
|
-
assistantCount,
|
|
231
|
-
startedAt,
|
|
232
|
-
endedAt,
|
|
233
|
-
embedding,
|
|
234
|
-
}) {
|
|
235
|
-
const client = await pool.connect();
|
|
236
|
-
try {
|
|
237
|
-
await client.query('BEGIN');
|
|
238
|
-
if (segments) await upsertSegments(client, sessionRowId, segments, { schema });
|
|
239
|
-
await upsertSummary(client, sessionRowId, {
|
|
240
|
-
schema, tenantId, agentId, sessionId, summaryText,
|
|
241
|
-
structuredSummary, model, sourceHash,
|
|
242
|
-
msgCount, userCount, assistantCount,
|
|
243
|
-
startedAt, endedAt, embedding,
|
|
244
|
-
});
|
|
245
|
-
await markStatus(client, sessionRowId, 'succeeded', null, { schema });
|
|
246
|
-
await client.query('COMMIT');
|
|
247
|
-
} catch (err) {
|
|
248
|
-
await client.query('ROLLBACK').catch(() => {});
|
|
249
|
-
try {
|
|
250
|
-
await markStatus(pool, sessionRowId, 'failed', err.message, { schema });
|
|
251
|
-
} catch (_) { /* swallow */ }
|
|
252
|
-
throw err;
|
|
253
|
-
} finally {
|
|
254
|
-
client.release();
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
175
|
// ---------------------------------------------------------------------------
|
|
259
176
|
// getSession
|
|
260
177
|
// ---------------------------------------------------------------------------
|
|
@@ -282,36 +199,6 @@ async function getSession(pool, sessionId, agentId, options = {}, { schema, tena
|
|
|
282
199
|
return result.rows[0] || null;
|
|
283
200
|
}
|
|
284
201
|
|
|
285
|
-
// ---------------------------------------------------------------------------
|
|
286
|
-
// getSessionFull
|
|
287
|
-
// ---------------------------------------------------------------------------
|
|
288
|
-
|
|
289
|
-
async function getSessionFull(pool, sessionId, agentId, { schema, tenantId } = {}) {
|
|
290
|
-
const session = await getSession(pool, sessionId, agentId, { tenantId }, { schema, tenantId });
|
|
291
|
-
if (!session) return null;
|
|
292
|
-
|
|
293
|
-
const [segResult, sumResult] = await Promise.all([
|
|
294
|
-
pool.query(
|
|
295
|
-
`SELECT * FROM ${qi(schema)}.session_segments
|
|
296
|
-
WHERE session_row_id = $1
|
|
297
|
-
ORDER BY segment_no ASC`,
|
|
298
|
-
[session.id]
|
|
299
|
-
),
|
|
300
|
-
pool.query(
|
|
301
|
-
`SELECT * FROM ${qi(schema)}.session_summaries
|
|
302
|
-
WHERE session_row_id = $1
|
|
303
|
-
LIMIT 1`,
|
|
304
|
-
[session.id]
|
|
305
|
-
),
|
|
306
|
-
]);
|
|
307
|
-
|
|
308
|
-
return {
|
|
309
|
-
session,
|
|
310
|
-
segments: segResult.rows,
|
|
311
|
-
summary: sumResult.rows[0] || null,
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
|
|
315
202
|
// ---------------------------------------------------------------------------
|
|
316
203
|
// getMessages
|
|
317
204
|
// ---------------------------------------------------------------------------
|
|
@@ -414,7 +301,7 @@ async function recordAccess(pool, sessionRowIds, { schema } = {}) {
|
|
|
414
301
|
if (!sessionRowIds || sessionRowIds.length === 0) return;
|
|
415
302
|
await pool.query(
|
|
416
303
|
`UPDATE ${qi(schema)}.session_summaries
|
|
417
|
-
SET access_count = access_count + 1, last_accessed_at = now()
|
|
304
|
+
SET access_count = COALESCE(access_count, 0) + 1, last_accessed_at = now()
|
|
418
305
|
WHERE session_row_id = ANY($1)`,
|
|
419
306
|
[sessionRowIds]
|
|
420
307
|
);
|
|
@@ -643,12 +530,9 @@ async function recordFeedback(pool, {
|
|
|
643
530
|
|
|
644
531
|
module.exports = {
|
|
645
532
|
upsertSession,
|
|
646
|
-
upsertSegments,
|
|
647
533
|
upsertSummary,
|
|
648
534
|
markStatus,
|
|
649
|
-
persistProcessingResults,
|
|
650
535
|
getSession,
|
|
651
|
-
getSessionFull,
|
|
652
536
|
getMessages,
|
|
653
537
|
searchSessions,
|
|
654
538
|
recordAccess,
|
package/index.js
CHANGED
|
@@ -3,6 +3,5 @@
|
|
|
3
3
|
const { createAquifer } = require('./core/aquifer');
|
|
4
4
|
const { createEmbedder } = require('./pipeline/embed');
|
|
5
5
|
const { createReranker } = require('./pipeline/rerank');
|
|
6
|
-
const { normalizeSession, detectClient } = require('./pipeline/normalize');
|
|
7
6
|
|
|
8
|
-
module.exports = { createAquifer, createEmbedder, createReranker
|
|
7
|
+
module.exports = { createAquifer, createEmbedder, createReranker };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shadowforge0/aquifer-memory",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "PG-native long-term memory for AI agents. Turn-level embedding, hybrid RRF ranking, optional knowledge graph. MCP server, CLI, and library API.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -17,8 +17,6 @@
|
|
|
17
17
|
},
|
|
18
18
|
"exports": {
|
|
19
19
|
".": "./index.js",
|
|
20
|
-
"./core/*": "./core/*.js",
|
|
21
|
-
"./pipeline/*": "./pipeline/*.js",
|
|
22
20
|
"./consumers/mcp": "./consumers/mcp.js",
|
|
23
21
|
"./consumers/openclaw-plugin": "./consumers/openclaw-plugin.js",
|
|
24
22
|
"./consumers/opencode": "./consumers/opencode.js",
|