@shadowforge0/aquifer-memory 0.3.1 → 0.4.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/consumers/cli.js CHANGED
@@ -23,7 +23,7 @@ const { loadConfig } = require('./shared/config');
23
23
  function parseArgs(argv) {
24
24
  const args = { _: [], flags: {} };
25
25
  // Flags that take a value (not boolean)
26
- const VALUE_FLAGS = new Set(['limit', 'agent-id', 'source', 'date-from', 'date-to', 'output', 'format', 'config', 'status', 'concurrency']);
26
+ const VALUE_FLAGS = new Set(['limit', 'agent-id', 'source', 'date-from', 'date-to', 'output', 'format', 'config', 'status', 'concurrency', 'entities', 'entity-mode', 'session-id', 'verdict', 'note']);
27
27
  for (let i = 0; i < argv.length; i++) {
28
28
  if (argv[i] === '--') { args._.push(...argv.slice(i + 1)); break; }
29
29
  if (argv[i].startsWith('--')) {
@@ -56,13 +56,18 @@ async function cmdRecall(aquifer, args) {
56
56
  process.exit(1);
57
57
  }
58
58
 
59
- const results = await aquifer.recall(query, {
59
+ const recallOpts = {
60
60
  limit: parseInt(args.flags.limit || '5', 10),
61
61
  agentId: args.flags['agent-id'] || undefined,
62
62
  source: args.flags.source || undefined,
63
63
  dateFrom: args.flags['date-from'] || undefined,
64
64
  dateTo: args.flags['date-to'] || undefined,
65
- });
65
+ };
66
+ if (args.flags.entities) {
67
+ recallOpts.entities = args.flags.entities.split(',').map(s => s.trim()).filter(Boolean);
68
+ recallOpts.entityMode = args.flags['entity-mode'] || 'any';
69
+ }
70
+ const results = await aquifer.recall(query, recallOpts);
66
71
 
67
72
  if (args.flags.json) {
68
73
  console.log(JSON.stringify(results, null, 2));
@@ -86,6 +91,28 @@ async function cmdRecall(aquifer, args) {
86
91
  }
87
92
  }
88
93
 
94
+ async function cmdFeedback(aquifer, args) {
95
+ const sessionId = args.flags['session-id'] || args._[1];
96
+ const verdict = args.flags.verdict;
97
+
98
+ if (!sessionId || !verdict) {
99
+ console.error('Usage: aquifer feedback --session-id ID --verdict helpful|unhelpful [--note TEXT] [--agent-id ID]');
100
+ process.exit(1);
101
+ }
102
+
103
+ const result = await aquifer.feedback(sessionId, {
104
+ verdict,
105
+ agentId: args.flags['agent-id'] || undefined,
106
+ note: args.flags.note || undefined,
107
+ });
108
+
109
+ if (args.flags.json) {
110
+ console.log(JSON.stringify(result, null, 2));
111
+ } else {
112
+ console.log(`Feedback: ${result.verdict} (trust ${result.trustBefore.toFixed(2)} → ${result.trustAfter.toFixed(2)})`);
113
+ }
114
+ }
115
+
89
116
  async function cmdBackfill(aquifer, args) {
90
117
  const limit = parseInt(args.flags.limit || '100', 10);
91
118
  const dryRun = !!args.flags['dry-run'];
@@ -243,6 +270,7 @@ async function main() {
243
270
  Commands:
244
271
  migrate Run database migrations
245
272
  recall <query> Search sessions (requires embed config)
273
+ feedback Record trust feedback on a session
246
274
  backfill Enrich pending sessions
247
275
  stats Show database statistics
248
276
  export Export sessions as JSONL
@@ -254,6 +282,11 @@ Options:
254
282
  --source NAME Filter by source
255
283
  --date-from YYYY-MM-DD Start date
256
284
  --date-to YYYY-MM-DD End date
285
+ --entities A,B,C Entity names (comma-separated, recall)
286
+ --entity-mode any|all Entity match mode (recall, default: any)
287
+ --session-id ID Session ID (feedback)
288
+ --verdict helpful|unhelpful Feedback verdict (feedback)
289
+ --note TEXT Feedback note (feedback)
257
290
  --json JSON output
258
291
  --dry-run Preview only (backfill)
259
292
  --output PATH Output file (export)
@@ -290,6 +323,9 @@ Options:
290
323
  case 'recall':
291
324
  await cmdRecall(aquifer, args);
292
325
  break;
326
+ case 'feedback':
327
+ await cmdFeedback(aquifer, args);
328
+ break;
293
329
  case 'backfill':
294
330
  await cmdBackfill(aquifer, args);
295
331
  break;
package/consumers/mcp.js CHANGED
@@ -69,12 +69,12 @@ async function main() {
69
69
 
70
70
  const server = new McpServer({
71
71
  name: 'aquifer-memory',
72
- version: '0.2.0',
72
+ version: '0.3.1',
73
73
  });
74
74
 
75
75
  server.tool(
76
76
  'session_recall',
77
- 'Search stored sessions by keyword, returning ranked summaries and matched conversation turns.',
77
+ 'Search stored sessions by keyword. Supports entity intersection for precise multi-entity queries.',
78
78
  {
79
79
  query: z.string().min(1).describe('Search query (keyword or natural language)'),
80
80
  limit: z.number().int().min(1).max(20).optional().describe('Max results (default 5)'),
@@ -82,20 +82,26 @@ async function main() {
82
82
  source: z.string().optional().describe('Filter by source (e.g., gateway, cc)'),
83
83
  dateFrom: z.string().optional().describe('Start date YYYY-MM-DD'),
84
84
  dateTo: z.string().optional().describe('End date YYYY-MM-DD'),
85
+ entities: z.array(z.string()).optional().describe('Entity names to match'),
86
+ entityMode: z.enum(['any', 'all']).optional().describe('"any" (default, boost) or "all" (only sessions with every entity)'),
85
87
  },
86
88
  async (params) => {
87
89
  try {
88
90
  const aquifer = getAquifer();
89
91
  const limit = params.limit || 5;
90
-
91
- const results = await aquifer.recall(params.query, {
92
+ const recallOpts = {
92
93
  limit,
93
94
  agentId: params.agentId || undefined,
94
95
  source: params.source || undefined,
95
96
  dateFrom: params.dateFrom || undefined,
96
97
  dateTo: params.dateTo || undefined,
97
- });
98
+ };
99
+ if (params.entities && params.entities.length > 0) {
100
+ recallOpts.entities = params.entities;
101
+ recallOpts.entityMode = params.entityMode || 'any';
102
+ }
98
103
 
104
+ const results = await aquifer.recall(params.query, recallOpts);
99
105
  const text = formatResults(results, params.query);
100
106
  return { content: [{ type: 'text', text }] };
101
107
  } catch (err) {
@@ -107,6 +113,33 @@ async function main() {
107
113
  }
108
114
  );
109
115
 
116
+ server.tool(
117
+ 'session_feedback',
118
+ 'Record trust feedback on a recalled session. Helpful sessions rank higher in future recalls.',
119
+ {
120
+ sessionId: z.string().min(1).describe('Session ID to give feedback on'),
121
+ verdict: z.enum(['helpful', 'unhelpful']).describe('Was the recalled session useful?'),
122
+ note: z.string().optional().describe('Optional reason'),
123
+ },
124
+ async (params) => {
125
+ try {
126
+ const aquifer = getAquifer();
127
+ const result = await aquifer.feedback(params.sessionId, {
128
+ verdict: params.verdict,
129
+ note: params.note || undefined,
130
+ });
131
+ return {
132
+ content: [{ type: 'text', text: `Feedback: ${result.verdict} (trust ${result.trustBefore.toFixed(2)} → ${result.trustAfter.toFixed(2)})` }],
133
+ };
134
+ } catch (err) {
135
+ return {
136
+ content: [{ type: 'text', text: `session_feedback error: ${err.message}` }],
137
+ isError: true,
138
+ };
139
+ }
140
+ }
141
+ );
142
+
110
143
  // Graceful shutdown
111
144
  const cleanup = async () => {
112
145
  if (_aquifer?._pool) await _aquifer._pool.end().catch(() => {});
@@ -189,12 +189,14 @@ module.exports = {
189
189
 
190
190
  // --- session_recall tool ---
191
191
 
192
+ // --- session_recall tool ---
193
+
192
194
  api.registerTool((ctx) => {
193
195
  if ((ctx?.sessionKey || '').includes('subagent')) return null;
194
196
 
195
197
  return {
196
198
  name: 'session_recall',
197
- description: 'Search stored sessions by keyword, returning ranked summaries and matched conversation turns.',
199
+ description: 'Search stored sessions by keyword. Supports entity intersection for precise multi-entity queries.',
198
200
  parameters: {
199
201
  type: 'object',
200
202
  properties: {
@@ -204,20 +206,27 @@ module.exports = {
204
206
  source: { type: 'string', description: 'Filter by source' },
205
207
  dateFrom: { type: 'string', description: 'Start date YYYY-MM-DD' },
206
208
  dateTo: { type: 'string', description: 'End date YYYY-MM-DD' },
209
+ entities: { type: 'array', items: { type: 'string' }, description: 'Entity names to match' },
210
+ entityMode: { type: 'string', enum: ['any', 'all'], description: '"any" (default, boost) or "all" (only sessions with every entity)' },
207
211
  },
208
212
  required: ['query'],
209
213
  },
210
214
  async execute(_toolCallId, params) {
211
215
  try {
212
216
  const limit = Math.max(1, Math.min(20, parseInt(params?.limit ?? 5, 10) || 5));
213
- const results = await aquifer.recall(params.query, {
217
+ const recallOpts = {
214
218
  limit,
215
219
  agentId: params.agentId || undefined,
216
220
  source: params.source || undefined,
217
221
  dateFrom: params.dateFrom || undefined,
218
222
  dateTo: params.dateTo || undefined,
219
- });
223
+ };
224
+ if (Array.isArray(params.entities) && params.entities.length > 0) {
225
+ recallOpts.entities = params.entities;
226
+ recallOpts.entityMode = params.entityMode || 'any';
227
+ }
220
228
 
229
+ const results = await aquifer.recall(params.query, recallOpts);
221
230
  const text = formatRecallResults(results);
222
231
  return { content: [{ type: 'text', text }] };
223
232
  } catch (err) {
@@ -230,6 +239,42 @@ module.exports = {
230
239
  };
231
240
  }, { name: 'session_recall' });
232
241
 
233
- api.logger.info('[aquifer-memory] registered (before_reset + session_recall)');
242
+ // --- session_feedback tool ---
243
+
244
+ api.registerTool((ctx) => {
245
+ if ((ctx?.sessionKey || '').includes('subagent')) return null;
246
+
247
+ return {
248
+ name: 'session_feedback',
249
+ description: 'Record trust feedback on a recalled session. Helpful sessions rank higher in future recalls.',
250
+ parameters: {
251
+ type: 'object',
252
+ properties: {
253
+ sessionId: { type: 'string', description: 'Session ID to give feedback on' },
254
+ verdict: { type: 'string', enum: ['helpful', 'unhelpful'], description: 'Was the recalled session useful?' },
255
+ note: { type: 'string', description: 'Optional reason' },
256
+ },
257
+ required: ['sessionId', 'verdict'],
258
+ },
259
+ async execute(_toolCallId, params) {
260
+ try {
261
+ const result = await aquifer.feedback(params.sessionId, {
262
+ verdict: params.verdict,
263
+ note: params.note || undefined,
264
+ });
265
+ return {
266
+ content: [{ type: 'text', text: `Feedback: ${result.verdict} (trust ${result.trustBefore.toFixed(2)} → ${result.trustAfter.toFixed(2)})` }],
267
+ };
268
+ } catch (err) {
269
+ return {
270
+ content: [{ type: 'text', text: `session_feedback error: ${err.message}` }],
271
+ isError: true,
272
+ };
273
+ }
274
+ },
275
+ };
276
+ }, { name: 'session_feedback' });
277
+
278
+ api.logger.info('[aquifer-memory] registered (before_reset + session_recall + session_feedback)');
234
279
  },
235
280
  };
package/core/aquifer.js CHANGED
@@ -361,7 +361,7 @@ function createAquifer(config) {
361
361
  schema, tenantId, agentId, sessionId,
362
362
  summaryText: summaryResult.summaryText,
363
363
  structuredSummary: summaryResult.structuredSummary,
364
- model: null, sourceHash: null,
364
+ model: session.model || null, sourceHash: null,
365
365
  msgCount: normalized.length,
366
366
  userCount: turns.length,
367
367
  assistantCount: normalized.filter(m => m.role === 'assistant').length,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shadowforge0/aquifer-memory",
3
- "version": "0.3.1",
3
+ "version": "0.4.1",
4
4
  "description": "PG-native long-term memory for AI agents. Turn-level embedding, hybrid RRF ranking, optional knowledge graph. Includes CLI, MCP server, and OpenClaw plugin.",
5
5
  "main": "index.js",
6
6
  "files": [