@199-bio/engram 0.7.4 → 0.8.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/dist/consolidation/consolidator.d.ts.map +1 -1
- package/dist/index.js +119 -3
- package/dist/retrieval/hybrid.d.ts.map +1 -1
- package/dist/storage/database.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/consolidation/consolidator.ts +12 -0
- package/src/index.ts +134 -3
- package/src/retrieval/hybrid.ts +152 -15
- package/src/storage/database.ts +312 -0
- package/src/web/chat-handler.ts +3 -3
- package/src/web/server.ts +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consolidator.d.ts","sourceRoot":"","sources":["../../src/consolidation/consolidator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,cAAc,EAAU,MAAM,EAAW,MAAM,wBAAwB,CAAC;AAEjF,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AA0FtD,UAAU,kBAAkB;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2BAA2B,CAAC,EAAE,MAAM,CAAC;CACtC;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,EAAE,CAAiB;IAC3B,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,MAAM,CAA6B;gBAGzC,EAAE,EAAE,cAAc,EAClB,KAAK,CAAC,EAAE,cAAc,EACtB,MAAM,CAAC,EAAE,YAAY;IAYvB,YAAY,IAAI,OAAO;IAIvB;;;OAGG;IACG,WAAW,CAAC,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC;QAC3D,cAAc,EAAE,MAAM,CAAC;QACvB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,iBAAiB,EAAE,MAAM,CAAC;KAC3B,CAAC;IAwEF;;OAEG;YACW,gBAAgB;IAoE9B;;OAEG;IACG,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAuHjE;;OAEG;IACH,SAAS,IAAI;QACX,UAAU,EAAE,OAAO,CAAC;QACpB,sBAAsB,EAAE,MAAM,CAAC;QAC/B,sBAAsB,EAAE,MAAM,CAAC;QAC/B,YAAY,EAAE,MAAM,CAAC;QACrB,wBAAwB,EAAE,MAAM,CAAC;KAClC;IAeD;;;OAGG;IACG,mBAAmB,CAAC,OAAO,GAAE;QACjC,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;KACf,GAAG,OAAO,CAAC;QACf,iBAAiB,EAAE,MAAM,CAAC;QAC1B,eAAe,EAAE,MAAM,CAAC;QACxB,eAAe,EAAE,MAAM,CAAC;KACzB,CAAC;IAwFF;;OAEG;YACW,2BAA2B;IAqDzC;;;OAGG;IACG,aAAa,IAAI,OAAO,CAAC;QAC7B,iBAAiB,EAAE,MAAM,CAAC;QAC1B,eAAe,EAAE,MAAM,CAAC;QACxB,cAAc,EAAE,MAAM,CAAC;QACvB,mBAAmB,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"consolidator.d.ts","sourceRoot":"","sources":["../../src/consolidation/consolidator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,cAAc,EAAU,MAAM,EAAW,MAAM,wBAAwB,CAAC;AAEjF,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AA0FtD,UAAU,kBAAkB;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2BAA2B,CAAC,EAAE,MAAM,CAAC;CACtC;AAED,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,EAAE,CAAiB;IAC3B,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,MAAM,CAA6B;gBAGzC,EAAE,EAAE,cAAc,EAClB,KAAK,CAAC,EAAE,cAAc,EACtB,MAAM,CAAC,EAAE,YAAY;IAYvB,YAAY,IAAI,OAAO;IAIvB;;;OAGG;IACG,WAAW,CAAC,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC;QAC3D,cAAc,EAAE,MAAM,CAAC;QACvB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,iBAAiB,EAAE,MAAM,CAAC;KAC3B,CAAC;IAwEF;;OAEG;YACW,gBAAgB;IAoE9B;;OAEG;IACG,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAuHjE;;OAEG;IACH,SAAS,IAAI;QACX,UAAU,EAAE,OAAO,CAAC;QACpB,sBAAsB,EAAE,MAAM,CAAC;QAC/B,sBAAsB,EAAE,MAAM,CAAC;QAC/B,YAAY,EAAE,MAAM,CAAC;QACrB,wBAAwB,EAAE,MAAM,CAAC;KAClC;IAeD;;;OAGG;IACG,mBAAmB,CAAC,OAAO,GAAE;QACjC,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;KACf,GAAG,OAAO,CAAC;QACf,iBAAiB,EAAE,MAAM,CAAC;QAC1B,eAAe,EAAE,MAAM,CAAC;QACxB,eAAe,EAAE,MAAM,CAAC;KACzB,CAAC;IAwFF;;OAEG;YACW,2BAA2B;IAqDzC;;;OAGG;IACG,aAAa,IAAI,OAAO,CAAC;QAC7B,iBAAiB,EAAE,MAAM,CAAC;QAC1B,eAAe,EAAE,MAAM,CAAC;QACxB,cAAc,EAAE,MAAM,CAAC;QACvB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,kBAAkB,EAAE,MAAM,CAAC;QAC3B,aAAa,EAAE,MAAM,CAAC;KACvB,CAAC;CA4BH"}
|
package/dist/index.js
CHANGED
|
@@ -58,7 +58,7 @@ async function initialize() {
|
|
|
58
58
|
// ============ MCP Server ============
|
|
59
59
|
const server = new Server({
|
|
60
60
|
name: "engram",
|
|
61
|
-
version: "0.
|
|
61
|
+
version: "0.8.0",
|
|
62
62
|
}, {
|
|
63
63
|
capabilities: {
|
|
64
64
|
tools: {},
|
|
@@ -257,6 +257,37 @@ const TOOLS = [
|
|
|
257
257
|
openWorldHint: true, // Calls Anthropic API for consolidation
|
|
258
258
|
},
|
|
259
259
|
},
|
|
260
|
+
{
|
|
261
|
+
name: "memory_feedback",
|
|
262
|
+
description: "Signal which memories from a recall were actually useful. Call AFTER using memories to answer user's question. This enables the memory system to learn which memories help together (Hebbian learning). Optional but improves memory quality over time.",
|
|
263
|
+
inputSchema: {
|
|
264
|
+
type: "object",
|
|
265
|
+
properties: {
|
|
266
|
+
recall_id: {
|
|
267
|
+
type: "string",
|
|
268
|
+
description: "The recall_id from the recall response",
|
|
269
|
+
},
|
|
270
|
+
useful_memory_ids: {
|
|
271
|
+
type: "array",
|
|
272
|
+
items: { type: "string" },
|
|
273
|
+
description: "IDs of memories that actually helped answer the question. Empty array if none were useful.",
|
|
274
|
+
},
|
|
275
|
+
need_more: {
|
|
276
|
+
type: "boolean",
|
|
277
|
+
description: "Set true if the memories were insufficient and you need a deeper search with more results",
|
|
278
|
+
default: false,
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
required: ["recall_id", "useful_memory_ids"],
|
|
282
|
+
},
|
|
283
|
+
annotations: {
|
|
284
|
+
title: "Memory Feedback",
|
|
285
|
+
readOnlyHint: false,
|
|
286
|
+
destructiveHint: false,
|
|
287
|
+
idempotentHint: true,
|
|
288
|
+
openWorldHint: false,
|
|
289
|
+
},
|
|
290
|
+
},
|
|
260
291
|
];
|
|
261
292
|
// List available tools
|
|
262
293
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
@@ -314,11 +345,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
314
345
|
}
|
|
315
346
|
case "recall": {
|
|
316
347
|
const { query, limit = 5, include_graph = true } = args;
|
|
317
|
-
const
|
|
348
|
+
const response = await search.search(query, {
|
|
318
349
|
limit,
|
|
319
350
|
includeGraph: include_graph,
|
|
320
351
|
});
|
|
321
|
-
const formatted = results.map((r) => ({
|
|
352
|
+
const formatted = response.results.map((r) => ({
|
|
322
353
|
id: r.memory.id,
|
|
323
354
|
content: r.memory.content,
|
|
324
355
|
source: r.memory.source,
|
|
@@ -330,14 +361,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
330
361
|
.map(([k]) => k)
|
|
331
362
|
.join(", "),
|
|
332
363
|
}));
|
|
364
|
+
// Format connected memories (Hebbian associations)
|
|
365
|
+
const connectedFormatted = response.connected_memories.map((c) => ({
|
|
366
|
+
id: c.memory.id,
|
|
367
|
+
content: c.memory.content,
|
|
368
|
+
connected_to: c.connected_to,
|
|
369
|
+
connection_strength: c.strength.toFixed(2),
|
|
370
|
+
}));
|
|
333
371
|
return {
|
|
334
372
|
content: [
|
|
335
373
|
{
|
|
336
374
|
type: "text",
|
|
337
375
|
text: JSON.stringify({
|
|
376
|
+
recall_id: response.recall_id, // For memory_feedback
|
|
338
377
|
query,
|
|
339
378
|
results: formatted,
|
|
340
379
|
count: formatted.length,
|
|
380
|
+
connected_memories: connectedFormatted,
|
|
381
|
+
hint: formatted.length > 0 ? "Call memory_feedback with useful_memory_ids after answering" : undefined,
|
|
341
382
|
}, null, 2),
|
|
342
383
|
},
|
|
343
384
|
],
|
|
@@ -497,6 +538,81 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
497
538
|
],
|
|
498
539
|
};
|
|
499
540
|
}
|
|
541
|
+
case "memory_feedback": {
|
|
542
|
+
const { recall_id, useful_memory_ids, need_more = false } = args;
|
|
543
|
+
// Update the retrieval log with feedback
|
|
544
|
+
const updated = db.updateRetrievalFeedback(recall_id, useful_memory_ids, need_more);
|
|
545
|
+
if (!updated) {
|
|
546
|
+
return {
|
|
547
|
+
content: [
|
|
548
|
+
{
|
|
549
|
+
type: "text",
|
|
550
|
+
text: JSON.stringify({
|
|
551
|
+
success: false,
|
|
552
|
+
error: `Recall ID not found: ${recall_id}`,
|
|
553
|
+
}),
|
|
554
|
+
},
|
|
555
|
+
],
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
// If need_more is set, do an expanded search
|
|
559
|
+
if (need_more) {
|
|
560
|
+
const expandedResponse = await search.expandSearch(recall_id);
|
|
561
|
+
const formatted = expandedResponse.results.map((r) => ({
|
|
562
|
+
id: r.memory.id,
|
|
563
|
+
content: r.memory.content,
|
|
564
|
+
source: r.memory.source,
|
|
565
|
+
timestamp: r.memory.timestamp.toISOString(),
|
|
566
|
+
relevance_score: r.score.toFixed(4),
|
|
567
|
+
matched_via: Object.entries(r.sources)
|
|
568
|
+
.filter(([, v]) => v !== undefined)
|
|
569
|
+
.map(([k]) => k)
|
|
570
|
+
.join(", "),
|
|
571
|
+
}));
|
|
572
|
+
return {
|
|
573
|
+
content: [
|
|
574
|
+
{
|
|
575
|
+
type: "text",
|
|
576
|
+
text: JSON.stringify({
|
|
577
|
+
success: true,
|
|
578
|
+
feedback_recorded: true,
|
|
579
|
+
useful_count: useful_memory_ids.length,
|
|
580
|
+
expanded_search: true,
|
|
581
|
+
additional_results: formatted,
|
|
582
|
+
additional_count: formatted.length,
|
|
583
|
+
}, null, 2),
|
|
584
|
+
},
|
|
585
|
+
],
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
// Process deferred learning if we have enough feedback
|
|
589
|
+
const unprocessed = db.getUnprocessedRetrievalLogs(50);
|
|
590
|
+
let learningApplied = 0;
|
|
591
|
+
for (const log of unprocessed) {
|
|
592
|
+
if (log.useful_ids && log.useful_ids.length >= 2) {
|
|
593
|
+
// Strengthen connections between useful memories
|
|
594
|
+
db.recordCoUseful(log.useful_ids);
|
|
595
|
+
learningApplied++;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
if (unprocessed.length > 0) {
|
|
599
|
+
db.markRetrievalLogsProcessed(unprocessed.map(l => l.id));
|
|
600
|
+
}
|
|
601
|
+
return {
|
|
602
|
+
content: [
|
|
603
|
+
{
|
|
604
|
+
type: "text",
|
|
605
|
+
text: JSON.stringify({
|
|
606
|
+
success: true,
|
|
607
|
+
feedback_recorded: true,
|
|
608
|
+
useful_count: useful_memory_ids.length,
|
|
609
|
+
learning_applied: learningApplied > 0,
|
|
610
|
+
connections_strengthened: learningApplied,
|
|
611
|
+
}, null, 2),
|
|
612
|
+
},
|
|
613
|
+
],
|
|
614
|
+
};
|
|
615
|
+
}
|
|
500
616
|
default:
|
|
501
617
|
throw new Error(`Unknown tool: ${name}`);
|
|
502
618
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hybrid.d.ts","sourceRoot":"","sources":["../../src/retrieval/hybrid.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAA0B,MAAM,cAAc,CAAC;AAEzF,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE;QACP,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"hybrid.d.ts","sourceRoot":"","sources":["../../src/retrieval/hybrid.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAA0B,MAAM,cAAc,CAAC;AAEzF,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE;QACP,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB,EAAE,KAAK,CAAC;QACxB,MAAM,EAAE,MAAM,CAAC;QACf,YAAY,EAAE,MAAM,CAAC;QACrB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;CACJ;AAwDD,qBAAa,YAAY;IAIrB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,SAAS;IALnB,OAAO,CAAC,SAAS,CAAS;gBAGhB,EAAE,EAAE,cAAc,EAClB,KAAK,EAAE,cAAc,EACrB,SAAS,EAAE,gBAAgB,GAAG,eAAe;IAMvD;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAIxB;;;;;OAKG;IACG,MAAM,CACV,KAAK,EAAE,MAAM,EACb,OAAO,GAAE;QACP,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,kBAAkB,CAAC,EAAE,OAAO,CAAC;QAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,qBAAqB,CAAC,EAAE,MAAM,CAAC;QAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,YAAY,CAAC,EAAE,OAAO,CAAC;KACnB,GACL,OAAO,CAAC,oBAAoB,CAAC;IAuNhC;;;OAGG;IACG,YAAY,CAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;QAAE,eAAe,CAAC,EAAE,MAAM,CAAA;KAAO,GACzC,OAAO,CAAC,oBAAoB,CAAC;IAyBhC;;OAEG;YACW,UAAU;IASxB;;OAEG;YACW,cAAc;IAS5B;;;OAGG;IACH,OAAO,CAAC,aAAa;IAQrB;;;OAGG;YACW,WAAW;IA8BzB;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOhD;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAYhD;;OAEG;IACG,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGvD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/storage/database.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;IAChB,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,IAAI,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,cAAc,CAAC;IAChE,UAAU,EAAE,IAAI,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC1C;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,IAAI,CAAC;IACjB,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC3C,UAAU,EAAE,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,IAAI,CAAC;IACjB,YAAY,EAAE,IAAI,CAAC;IACnB,UAAU,EAAE,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,IAAI,CAAC;IACjB,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC;CAC1B;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,EAAE,CAAoB;IAC9B,OAAO,CAAC,SAAS,CAA8C;gBAEnD,MAAM,EAAE,MAAM;IAoB1B,OAAO,CAAC,UAAU;
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/storage/database.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -596,6 +596,8 @@ Respond with JSON only.`;
|
|
|
596
596
|
memoriesCreated: number;
|
|
597
597
|
digestsCreated: number;
|
|
598
598
|
contradictionsFound: number;
|
|
599
|
+
connectionsDecayed: number;
|
|
600
|
+
logsCleanedUp: number;
|
|
599
601
|
}> {
|
|
600
602
|
console.error("[Consolidator] Starting sleep cycle...");
|
|
601
603
|
|
|
@@ -607,11 +609,21 @@ Respond with JSON only.`;
|
|
|
607
609
|
const memoryResult = await this.consolidate();
|
|
608
610
|
console.error(`[Consolidator] Memories: ${memoryResult.memoriesProcessed} → ${memoryResult.digestsCreated} digests`);
|
|
609
611
|
|
|
612
|
+
// Step 3: Decay unused Hebbian connections (memories that haven't fired together recently)
|
|
613
|
+
const connectionsDecayed = this.db.decayConnections(30, 0.9);
|
|
614
|
+
console.error(`[Consolidator] Connections decayed: ${connectionsDecayed}`);
|
|
615
|
+
|
|
616
|
+
// Step 4: Clean up old retrieval logs
|
|
617
|
+
const logsCleanedUp = this.db.cleanupRetrievalLogs(7);
|
|
618
|
+
console.error(`[Consolidator] Retrieval logs cleaned: ${logsCleanedUp}`);
|
|
619
|
+
|
|
610
620
|
return {
|
|
611
621
|
episodesProcessed: episodeResult.episodesProcessed,
|
|
612
622
|
memoriesCreated: episodeResult.memoriesCreated,
|
|
613
623
|
digestsCreated: memoryResult.digestsCreated,
|
|
614
624
|
contradictionsFound: memoryResult.contradictionsFound,
|
|
625
|
+
connectionsDecayed,
|
|
626
|
+
logsCleanedUp,
|
|
615
627
|
};
|
|
616
628
|
}
|
|
617
629
|
}
|
package/src/index.ts
CHANGED
|
@@ -76,7 +76,7 @@ async function initialize(): Promise<void> {
|
|
|
76
76
|
const server = new Server(
|
|
77
77
|
{
|
|
78
78
|
name: "engram",
|
|
79
|
-
version: "0.
|
|
79
|
+
version: "0.8.0",
|
|
80
80
|
},
|
|
81
81
|
{
|
|
82
82
|
capabilities: {
|
|
@@ -280,6 +280,37 @@ const TOOLS = [
|
|
|
280
280
|
openWorldHint: true, // Calls Anthropic API for consolidation
|
|
281
281
|
},
|
|
282
282
|
},
|
|
283
|
+
{
|
|
284
|
+
name: "memory_feedback",
|
|
285
|
+
description: "Signal which memories from a recall were actually useful. Call AFTER using memories to answer user's question. This enables the memory system to learn which memories help together (Hebbian learning). Optional but improves memory quality over time.",
|
|
286
|
+
inputSchema: {
|
|
287
|
+
type: "object" as const,
|
|
288
|
+
properties: {
|
|
289
|
+
recall_id: {
|
|
290
|
+
type: "string",
|
|
291
|
+
description: "The recall_id from the recall response",
|
|
292
|
+
},
|
|
293
|
+
useful_memory_ids: {
|
|
294
|
+
type: "array",
|
|
295
|
+
items: { type: "string" },
|
|
296
|
+
description: "IDs of memories that actually helped answer the question. Empty array if none were useful.",
|
|
297
|
+
},
|
|
298
|
+
need_more: {
|
|
299
|
+
type: "boolean",
|
|
300
|
+
description: "Set true if the memories were insufficient and you need a deeper search with more results",
|
|
301
|
+
default: false,
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
required: ["recall_id", "useful_memory_ids"],
|
|
305
|
+
},
|
|
306
|
+
annotations: {
|
|
307
|
+
title: "Memory Feedback",
|
|
308
|
+
readOnlyHint: false,
|
|
309
|
+
destructiveHint: false,
|
|
310
|
+
idempotentHint: true,
|
|
311
|
+
openWorldHint: false,
|
|
312
|
+
},
|
|
313
|
+
},
|
|
283
314
|
];
|
|
284
315
|
|
|
285
316
|
// List available tools
|
|
@@ -366,12 +397,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
366
397
|
include_graph?: boolean;
|
|
367
398
|
};
|
|
368
399
|
|
|
369
|
-
const
|
|
400
|
+
const response = await search.search(query, {
|
|
370
401
|
limit,
|
|
371
402
|
includeGraph: include_graph,
|
|
372
403
|
});
|
|
373
404
|
|
|
374
|
-
const formatted = results.map((r) => ({
|
|
405
|
+
const formatted = response.results.map((r) => ({
|
|
375
406
|
id: r.memory.id,
|
|
376
407
|
content: r.memory.content,
|
|
377
408
|
source: r.memory.source,
|
|
@@ -384,14 +415,25 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
384
415
|
.join(", "),
|
|
385
416
|
}));
|
|
386
417
|
|
|
418
|
+
// Format connected memories (Hebbian associations)
|
|
419
|
+
const connectedFormatted = response.connected_memories.map((c) => ({
|
|
420
|
+
id: c.memory.id,
|
|
421
|
+
content: c.memory.content,
|
|
422
|
+
connected_to: c.connected_to,
|
|
423
|
+
connection_strength: c.strength.toFixed(2),
|
|
424
|
+
}));
|
|
425
|
+
|
|
387
426
|
return {
|
|
388
427
|
content: [
|
|
389
428
|
{
|
|
390
429
|
type: "text" as const,
|
|
391
430
|
text: JSON.stringify({
|
|
431
|
+
recall_id: response.recall_id, // For memory_feedback
|
|
392
432
|
query,
|
|
393
433
|
results: formatted,
|
|
394
434
|
count: formatted.length,
|
|
435
|
+
connected_memories: connectedFormatted,
|
|
436
|
+
hint: formatted.length > 0 ? "Call memory_feedback with useful_memory_ids after answering" : undefined,
|
|
395
437
|
}, null, 2),
|
|
396
438
|
},
|
|
397
439
|
],
|
|
@@ -575,6 +617,95 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
575
617
|
};
|
|
576
618
|
}
|
|
577
619
|
|
|
620
|
+
case "memory_feedback": {
|
|
621
|
+
const { recall_id, useful_memory_ids, need_more = false } = args as {
|
|
622
|
+
recall_id: string;
|
|
623
|
+
useful_memory_ids: string[];
|
|
624
|
+
need_more?: boolean;
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
// Update the retrieval log with feedback
|
|
628
|
+
const updated = db.updateRetrievalFeedback(recall_id, useful_memory_ids, need_more);
|
|
629
|
+
|
|
630
|
+
if (!updated) {
|
|
631
|
+
return {
|
|
632
|
+
content: [
|
|
633
|
+
{
|
|
634
|
+
type: "text" as const,
|
|
635
|
+
text: JSON.stringify({
|
|
636
|
+
success: false,
|
|
637
|
+
error: `Recall ID not found: ${recall_id}`,
|
|
638
|
+
}),
|
|
639
|
+
},
|
|
640
|
+
],
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// If need_more is set, do an expanded search
|
|
645
|
+
if (need_more) {
|
|
646
|
+
const expandedResponse = await search.expandSearch(recall_id);
|
|
647
|
+
|
|
648
|
+
const formatted = expandedResponse.results.map((r) => ({
|
|
649
|
+
id: r.memory.id,
|
|
650
|
+
content: r.memory.content,
|
|
651
|
+
source: r.memory.source,
|
|
652
|
+
timestamp: r.memory.timestamp.toISOString(),
|
|
653
|
+
relevance_score: r.score.toFixed(4),
|
|
654
|
+
matched_via: Object.entries(r.sources)
|
|
655
|
+
.filter(([, v]) => v !== undefined)
|
|
656
|
+
.map(([k]) => k)
|
|
657
|
+
.join(", "),
|
|
658
|
+
}));
|
|
659
|
+
|
|
660
|
+
return {
|
|
661
|
+
content: [
|
|
662
|
+
{
|
|
663
|
+
type: "text" as const,
|
|
664
|
+
text: JSON.stringify({
|
|
665
|
+
success: true,
|
|
666
|
+
feedback_recorded: true,
|
|
667
|
+
useful_count: useful_memory_ids.length,
|
|
668
|
+
expanded_search: true,
|
|
669
|
+
additional_results: formatted,
|
|
670
|
+
additional_count: formatted.length,
|
|
671
|
+
}, null, 2),
|
|
672
|
+
},
|
|
673
|
+
],
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Process deferred learning if we have enough feedback
|
|
678
|
+
const unprocessed = db.getUnprocessedRetrievalLogs(50);
|
|
679
|
+
let learningApplied = 0;
|
|
680
|
+
|
|
681
|
+
for (const log of unprocessed) {
|
|
682
|
+
if (log.useful_ids && log.useful_ids.length >= 2) {
|
|
683
|
+
// Strengthen connections between useful memories
|
|
684
|
+
db.recordCoUseful(log.useful_ids);
|
|
685
|
+
learningApplied++;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
if (unprocessed.length > 0) {
|
|
690
|
+
db.markRetrievalLogsProcessed(unprocessed.map(l => l.id));
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
return {
|
|
694
|
+
content: [
|
|
695
|
+
{
|
|
696
|
+
type: "text" as const,
|
|
697
|
+
text: JSON.stringify({
|
|
698
|
+
success: true,
|
|
699
|
+
feedback_recorded: true,
|
|
700
|
+
useful_count: useful_memory_ids.length,
|
|
701
|
+
learning_applied: learningApplied > 0,
|
|
702
|
+
connections_strengthened: learningApplied,
|
|
703
|
+
}, null, 2),
|
|
704
|
+
},
|
|
705
|
+
],
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
|
|
578
709
|
default:
|
|
579
710
|
throw new Error(`Unknown tool: ${name}`);
|
|
580
711
|
}
|
package/src/retrieval/hybrid.ts
CHANGED
|
@@ -16,9 +16,20 @@ export interface HybridSearchResult {
|
|
|
16
16
|
bm25?: number;
|
|
17
17
|
semantic?: number;
|
|
18
18
|
graph?: number;
|
|
19
|
+
connected?: number; // Rank from Hebbian connections
|
|
19
20
|
};
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
export interface HybridSearchResponse {
|
|
24
|
+
results: HybridSearchResult[];
|
|
25
|
+
recall_id: string; // For LLM feedback
|
|
26
|
+
connected_memories: Array<{
|
|
27
|
+
memory: Memory;
|
|
28
|
+
connected_to: string; // ID of the memory it's connected to
|
|
29
|
+
strength: number;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
|
|
22
33
|
/**
|
|
23
34
|
* Calculate Ebbinghaus forgetting curve retention
|
|
24
35
|
* R = e^(-t/S) where t=time since last access, S=stability
|
|
@@ -74,11 +85,23 @@ function adjustScore(memory: Memory, baseScore: number, now: Date): { adjusted:
|
|
|
74
85
|
}
|
|
75
86
|
|
|
76
87
|
export class HybridSearch {
|
|
88
|
+
private sessionId: string;
|
|
89
|
+
|
|
77
90
|
constructor(
|
|
78
91
|
private db: EngramDatabase,
|
|
79
92
|
private graph: KnowledgeGraph,
|
|
80
93
|
private retriever: ColBERTRetriever | SimpleRetriever
|
|
81
|
-
) {
|
|
94
|
+
) {
|
|
95
|
+
// Generate a session ID for this search instance
|
|
96
|
+
this.sessionId = `session_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Generate a unique recall ID for tracking
|
|
101
|
+
*/
|
|
102
|
+
private generateRecallId(): string {
|
|
103
|
+
return `recall_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
104
|
+
}
|
|
82
105
|
|
|
83
106
|
/**
|
|
84
107
|
* Search using all available methods and fuse results
|
|
@@ -91,21 +114,36 @@ export class HybridSearch {
|
|
|
91
114
|
options: {
|
|
92
115
|
limit?: number;
|
|
93
116
|
includeGraph?: boolean;
|
|
117
|
+
includeConnections?: boolean; // Include Hebbian-connected memories
|
|
118
|
+
connectionBudget?: number; // How many results to allocate to connections (default: 30%)
|
|
119
|
+
minConnectionStrength?: number; // Minimum strength to include connections
|
|
94
120
|
bm25Weight?: number;
|
|
95
121
|
semanticWeight?: number;
|
|
96
122
|
graphWeight?: number;
|
|
97
|
-
|
|
123
|
+
connectionWeight?: number; // Weight for connected memories in RRF
|
|
124
|
+
useReranking?: boolean; // Use ColBERT to rerank BM25 results
|
|
98
125
|
} = {}
|
|
99
|
-
): Promise<
|
|
126
|
+
): Promise<HybridSearchResponse> {
|
|
100
127
|
const {
|
|
101
128
|
limit = 10,
|
|
102
129
|
includeGraph = true,
|
|
130
|
+
includeConnections = true,
|
|
131
|
+
connectionBudget = 0.3, // 30% of results can be connected memories
|
|
132
|
+
minConnectionStrength = 0.3,
|
|
103
133
|
bm25Weight = 1.0,
|
|
104
134
|
semanticWeight = 1.0,
|
|
105
135
|
graphWeight = 0.3,
|
|
106
|
-
|
|
136
|
+
connectionWeight = 0.5,
|
|
137
|
+
useReranking = true,
|
|
107
138
|
} = options;
|
|
108
139
|
|
|
140
|
+
// Generate recall_id for tracking
|
|
141
|
+
const recallId = this.generateRecallId();
|
|
142
|
+
|
|
143
|
+
// Calculate budgets: 70% direct results, 30% connected
|
|
144
|
+
const directBudget = Math.ceil(limit * (1 - connectionBudget));
|
|
145
|
+
const connectedBudget = limit - directBudget;
|
|
146
|
+
|
|
109
147
|
// Fetch more candidates than needed for fusion
|
|
110
148
|
const candidateLimit = Math.max(limit * 3, 30);
|
|
111
149
|
|
|
@@ -139,11 +177,15 @@ export class HybridSearch {
|
|
|
139
177
|
graphMemories.forEach(m => allCandidateIds.add(m.id));
|
|
140
178
|
|
|
141
179
|
if (allCandidateIds.size === 0) {
|
|
142
|
-
return
|
|
180
|
+
return {
|
|
181
|
+
results: [],
|
|
182
|
+
recall_id: recallId,
|
|
183
|
+
connected_memories: [],
|
|
184
|
+
};
|
|
143
185
|
}
|
|
144
186
|
|
|
145
187
|
// Create rankings for RRF
|
|
146
|
-
const rankings: Map<string, { bm25?: number; semantic?: number; graph?: number }> = new Map();
|
|
188
|
+
const rankings: Map<string, { bm25?: number; semantic?: number; graph?: number; connected?: number }> = new Map();
|
|
147
189
|
|
|
148
190
|
// BM25 ranking
|
|
149
191
|
bm25Results.forEach((result, rank) => {
|
|
@@ -168,7 +210,7 @@ export class HybridSearch {
|
|
|
168
210
|
|
|
169
211
|
// Calculate RRF scores
|
|
170
212
|
const k = 60; // RRF constant
|
|
171
|
-
const rrfScores: Array<{ id: string; score: number; sources:
|
|
213
|
+
const rrfScores: Array<{ id: string; score: number; sources: { bm25?: number; semantic?: number; graph?: number; connected?: number } }> = [];
|
|
172
214
|
|
|
173
215
|
for (const [id, ranks] of rankings) {
|
|
174
216
|
let score = 0;
|
|
@@ -204,11 +246,7 @@ export class HybridSearch {
|
|
|
204
246
|
score: adjusted,
|
|
205
247
|
retention,
|
|
206
248
|
originalScore: score,
|
|
207
|
-
sources
|
|
208
|
-
bm25: sources.bm25,
|
|
209
|
-
semantic: sources.semantic,
|
|
210
|
-
graph: sources.graph,
|
|
211
|
-
},
|
|
249
|
+
sources,
|
|
212
250
|
});
|
|
213
251
|
}
|
|
214
252
|
}
|
|
@@ -216,9 +254,44 @@ export class HybridSearch {
|
|
|
216
254
|
// Re-sort by adjusted score (accounts for recency/stability)
|
|
217
255
|
adjustedResults.sort((a, b) => b.score - a.score);
|
|
218
256
|
|
|
219
|
-
// Take top results
|
|
257
|
+
// Take top direct results
|
|
258
|
+
const directResults = adjustedResults.slice(0, directBudget);
|
|
259
|
+
const directIds = directResults.map(r => r.memory.id);
|
|
260
|
+
|
|
261
|
+
// Find Hebbian-connected memories (not already in direct results)
|
|
262
|
+
let connectedMemories: Array<{ memory: Memory; connected_to: string; strength: number }> = [];
|
|
263
|
+
if (includeConnections && directIds.length > 0) {
|
|
264
|
+
const connectedIds = this.db.getConnectedMemoryIds(directIds, minConnectionStrength, connectedBudget * 2);
|
|
265
|
+
|
|
266
|
+
for (const connId of connectedIds) {
|
|
267
|
+
if (directIds.includes(connId)) continue;
|
|
268
|
+
|
|
269
|
+
const memory = this.db.getMemory(connId);
|
|
270
|
+
if (!memory) continue;
|
|
271
|
+
|
|
272
|
+
// Find which direct result this is connected to
|
|
273
|
+
const connections = this.db.getMemoryConnections(connId, minConnectionStrength);
|
|
274
|
+
const connectedTo = connections.find(c =>
|
|
275
|
+
directIds.includes(c.memory_a === connId ? c.memory_b : c.memory_a)
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
if (connectedTo) {
|
|
279
|
+
connectedMemories.push({
|
|
280
|
+
memory,
|
|
281
|
+
connected_to: connectedTo.memory_a === connId ? connectedTo.memory_b : connectedTo.memory_a,
|
|
282
|
+
strength: connectedTo.strength,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Sort by strength and limit
|
|
288
|
+
connectedMemories.sort((a, b) => b.strength - a.strength);
|
|
289
|
+
connectedMemories = connectedMemories.slice(0, connectedBudget);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Build final results (direct + space for connected)
|
|
220
293
|
const results: HybridSearchResult[] = [];
|
|
221
|
-
for (const result of
|
|
294
|
+
for (const result of directResults) {
|
|
222
295
|
// Update access count (which also increases stability for future searches)
|
|
223
296
|
this.db.touchMemory(result.memory.id);
|
|
224
297
|
|
|
@@ -230,7 +303,71 @@ export class HybridSearch {
|
|
|
230
303
|
});
|
|
231
304
|
}
|
|
232
305
|
|
|
233
|
-
|
|
306
|
+
// Add connected memories to results with their own scores
|
|
307
|
+
for (const { memory, strength } of connectedMemories) {
|
|
308
|
+
const baseScore = strength * connectionWeight;
|
|
309
|
+
const { adjusted, retention } = adjustScore(memory, baseScore, now);
|
|
310
|
+
|
|
311
|
+
this.db.touchMemory(memory.id);
|
|
312
|
+
|
|
313
|
+
results.push({
|
|
314
|
+
memory,
|
|
315
|
+
score: adjusted,
|
|
316
|
+
retention,
|
|
317
|
+
sources: { connected: 1 },
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Track co-retrieval for Hebbian learning (all result IDs)
|
|
322
|
+
const allResultIds = results.map(r => r.memory.id);
|
|
323
|
+
if (allResultIds.length >= 2) {
|
|
324
|
+
this.db.recordCoRetrieval(allResultIds);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Log this retrieval for deferred learning
|
|
328
|
+
try {
|
|
329
|
+
this.db.createRetrievalLog(this.sessionId, recallId, query, allResultIds);
|
|
330
|
+
} catch {
|
|
331
|
+
// Ignore duplicate recall_id errors (can happen with rapid queries)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return {
|
|
335
|
+
results,
|
|
336
|
+
recall_id: recallId,
|
|
337
|
+
connected_memories: connectedMemories,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Expanded search when LLM needs more memories
|
|
343
|
+
* Relaxes constraints and follows weaker connections
|
|
344
|
+
*/
|
|
345
|
+
async expandSearch(
|
|
346
|
+
recallId: string,
|
|
347
|
+
options: { additionalLimit?: number } = {}
|
|
348
|
+
): Promise<HybridSearchResponse> {
|
|
349
|
+
const { additionalLimit = 10 } = options;
|
|
350
|
+
|
|
351
|
+
// Get the original retrieval log
|
|
352
|
+
const log = this.db.getRetrievalLog(recallId);
|
|
353
|
+
if (!log) {
|
|
354
|
+
return { results: [], recall_id: recallId, connected_memories: [] };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Search again with relaxed parameters
|
|
358
|
+
const response = await this.search(log.query, {
|
|
359
|
+
limit: additionalLimit + log.memory_ids.length,
|
|
360
|
+
includeConnections: true,
|
|
361
|
+
minConnectionStrength: 0.1, // Lower threshold
|
|
362
|
+
connectionBudget: 0.5, // More connected memories
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// Filter out memories already returned
|
|
366
|
+
const existingIds = new Set(log.memory_ids);
|
|
367
|
+
response.results = response.results.filter(r => !existingIds.has(r.memory.id));
|
|
368
|
+
response.connected_memories = response.connected_memories.filter(c => !existingIds.has(c.memory.id));
|
|
369
|
+
|
|
370
|
+
return response;
|
|
234
371
|
}
|
|
235
372
|
|
|
236
373
|
/**
|
package/src/storage/database.ts
CHANGED
|
@@ -87,6 +87,38 @@ export interface Contradiction {
|
|
|
87
87
|
resolved_at: Date | null;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Hebbian connection between two memories
|
|
92
|
+
* "Neurons that fire together, wire together"
|
|
93
|
+
* Strength increases when memories are useful TOGETHER, not just co-retrieved
|
|
94
|
+
*/
|
|
95
|
+
export interface MemoryConnection {
|
|
96
|
+
id: string;
|
|
97
|
+
memory_a: string;
|
|
98
|
+
memory_b: string;
|
|
99
|
+
strength: number; // 0-1, how strongly connected
|
|
100
|
+
co_retrievals: number; // Times retrieved together
|
|
101
|
+
co_useful: number; // Times BOTH were useful together (from LLM feedback)
|
|
102
|
+
last_fired: Date | null; // Last time this connection was activated
|
|
103
|
+
created_at: Date;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Log of retrieval sessions for deferred learning
|
|
108
|
+
* We don't update connections immediately - we batch process after N turns
|
|
109
|
+
*/
|
|
110
|
+
export interface RetrievalLog {
|
|
111
|
+
id: string;
|
|
112
|
+
session_id: string;
|
|
113
|
+
recall_id: string; // Unique ID for this recall operation
|
|
114
|
+
query: string;
|
|
115
|
+
memory_ids: string[]; // All memories retrieved
|
|
116
|
+
useful_ids: string[] | null; // From LLM feedback (null = no feedback yet)
|
|
117
|
+
need_more: boolean; // LLM requested more memories
|
|
118
|
+
timestamp: Date;
|
|
119
|
+
processed: boolean; // Has this been used for learning?
|
|
120
|
+
}
|
|
121
|
+
|
|
90
122
|
export class EngramDatabase {
|
|
91
123
|
private db: Database.Database;
|
|
92
124
|
private stmtCache: Map<string, Database.Statement> = new Map();
|
|
@@ -269,6 +301,45 @@ export class EngramDatabase {
|
|
|
269
301
|
CREATE INDEX IF NOT EXISTS idx_contradictions_entity ON contradictions(entity_id);
|
|
270
302
|
CREATE INDEX IF NOT EXISTS idx_contradictions_resolved ON contradictions(resolved);
|
|
271
303
|
`);
|
|
304
|
+
|
|
305
|
+
// Memory connections table (Hebbian learning)
|
|
306
|
+
// "Neurons that fire together, wire together"
|
|
307
|
+
this.db.exec(`
|
|
308
|
+
CREATE TABLE IF NOT EXISTS memory_connections (
|
|
309
|
+
id TEXT PRIMARY KEY,
|
|
310
|
+
memory_a TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
|
|
311
|
+
memory_b TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
|
|
312
|
+
strength REAL DEFAULT 0.1,
|
|
313
|
+
co_retrievals INTEGER DEFAULT 0,
|
|
314
|
+
co_useful INTEGER DEFAULT 0,
|
|
315
|
+
last_fired DATETIME,
|
|
316
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
317
|
+
UNIQUE(memory_a, memory_b)
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
CREATE INDEX IF NOT EXISTS idx_connections_memory_a ON memory_connections(memory_a);
|
|
321
|
+
CREATE INDEX IF NOT EXISTS idx_connections_memory_b ON memory_connections(memory_b);
|
|
322
|
+
CREATE INDEX IF NOT EXISTS idx_connections_strength ON memory_connections(strength);
|
|
323
|
+
`);
|
|
324
|
+
|
|
325
|
+
// Retrieval logs for deferred learning
|
|
326
|
+
this.db.exec(`
|
|
327
|
+
CREATE TABLE IF NOT EXISTS retrieval_logs (
|
|
328
|
+
id TEXT PRIMARY KEY,
|
|
329
|
+
session_id TEXT NOT NULL,
|
|
330
|
+
recall_id TEXT NOT NULL UNIQUE,
|
|
331
|
+
query TEXT NOT NULL,
|
|
332
|
+
memory_ids TEXT NOT NULL,
|
|
333
|
+
useful_ids TEXT,
|
|
334
|
+
need_more INTEGER DEFAULT 0,
|
|
335
|
+
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
336
|
+
processed INTEGER DEFAULT 0
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
CREATE INDEX IF NOT EXISTS idx_retrieval_logs_session ON retrieval_logs(session_id);
|
|
340
|
+
CREATE INDEX IF NOT EXISTS idx_retrieval_logs_recall ON retrieval_logs(recall_id);
|
|
341
|
+
CREATE INDEX IF NOT EXISTS idx_retrieval_logs_processed ON retrieval_logs(processed);
|
|
342
|
+
`);
|
|
272
343
|
}
|
|
273
344
|
|
|
274
345
|
/**
|
|
@@ -1043,6 +1114,220 @@ export class EngramDatabase {
|
|
|
1043
1114
|
return result.changes > 0;
|
|
1044
1115
|
}
|
|
1045
1116
|
|
|
1117
|
+
// ============ Memory Connections (Hebbian Learning) ============
|
|
1118
|
+
|
|
1119
|
+
/**
|
|
1120
|
+
* Get or create a connection between two memories
|
|
1121
|
+
* Always stores with smaller ID first for consistency
|
|
1122
|
+
*/
|
|
1123
|
+
getOrCreateConnection(memoryA: string, memoryB: string): MemoryConnection {
|
|
1124
|
+
// Normalize order: smaller ID always first
|
|
1125
|
+
const [a, b] = memoryA < memoryB ? [memoryA, memoryB] : [memoryB, memoryA];
|
|
1126
|
+
|
|
1127
|
+
const existing = this.stmt(
|
|
1128
|
+
"SELECT * FROM memory_connections WHERE memory_a = ? AND memory_b = ?"
|
|
1129
|
+
).get(a, b) as Record<string, unknown> | undefined;
|
|
1130
|
+
|
|
1131
|
+
if (existing) {
|
|
1132
|
+
return this.rowToConnection(existing);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
const id = randomUUID();
|
|
1136
|
+
this.db.prepare(`
|
|
1137
|
+
INSERT INTO memory_connections (id, memory_a, memory_b)
|
|
1138
|
+
VALUES (?, ?, ?)
|
|
1139
|
+
`).run(id, a, b);
|
|
1140
|
+
|
|
1141
|
+
return this.getConnection(id)!;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
getConnection(id: string): MemoryConnection | null {
|
|
1145
|
+
const row = this.stmt("SELECT * FROM memory_connections WHERE id = ?").get(id) as Record<string, unknown> | undefined;
|
|
1146
|
+
return row ? this.rowToConnection(row) : null;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
/**
|
|
1150
|
+
* Get all connections for a memory
|
|
1151
|
+
*/
|
|
1152
|
+
getMemoryConnections(memoryId: string, minStrength: number = 0): MemoryConnection[] {
|
|
1153
|
+
const rows = this.stmt(`
|
|
1154
|
+
SELECT * FROM memory_connections
|
|
1155
|
+
WHERE (memory_a = ? OR memory_b = ?) AND strength >= ?
|
|
1156
|
+
ORDER BY strength DESC
|
|
1157
|
+
`).all(memoryId, memoryId, minStrength) as Record<string, unknown>[];
|
|
1158
|
+
return rows.map(r => this.rowToConnection(r));
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
/**
|
|
1162
|
+
* Get connected memory IDs for a set of memories
|
|
1163
|
+
*/
|
|
1164
|
+
getConnectedMemoryIds(memoryIds: string[], minStrength: number = 0.3, limit: number = 10): string[] {
|
|
1165
|
+
if (memoryIds.length === 0) return [];
|
|
1166
|
+
|
|
1167
|
+
const placeholders = memoryIds.map(() => "?").join(",");
|
|
1168
|
+
const rows = this.db.prepare(`
|
|
1169
|
+
SELECT DISTINCT
|
|
1170
|
+
CASE WHEN memory_a IN (${placeholders}) THEN memory_b ELSE memory_a END as connected_id,
|
|
1171
|
+
strength
|
|
1172
|
+
FROM memory_connections
|
|
1173
|
+
WHERE (memory_a IN (${placeholders}) OR memory_b IN (${placeholders}))
|
|
1174
|
+
AND strength >= ?
|
|
1175
|
+
ORDER BY strength DESC
|
|
1176
|
+
LIMIT ?
|
|
1177
|
+
`).all(...memoryIds, ...memoryIds, ...memoryIds, minStrength, limit) as Array<{ connected_id: string; strength: number }>;
|
|
1178
|
+
|
|
1179
|
+
// Filter out memories that are already in the input set
|
|
1180
|
+
const inputSet = new Set(memoryIds);
|
|
1181
|
+
return rows.map(r => r.connected_id).filter(id => !inputSet.has(id));
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
/**
|
|
1185
|
+
* Record that memories were retrieved together (co-retrieval)
|
|
1186
|
+
*/
|
|
1187
|
+
recordCoRetrieval(memoryIds: string[]): void {
|
|
1188
|
+
if (memoryIds.length < 2) return;
|
|
1189
|
+
|
|
1190
|
+
// Update all pairs
|
|
1191
|
+
for (let i = 0; i < memoryIds.length; i++) {
|
|
1192
|
+
for (let j = i + 1; j < memoryIds.length; j++) {
|
|
1193
|
+
const [a, b] = memoryIds[i] < memoryIds[j]
|
|
1194
|
+
? [memoryIds[i], memoryIds[j]]
|
|
1195
|
+
: [memoryIds[j], memoryIds[i]];
|
|
1196
|
+
|
|
1197
|
+
this.db.prepare(`
|
|
1198
|
+
INSERT INTO memory_connections (id, memory_a, memory_b, co_retrievals, last_fired)
|
|
1199
|
+
VALUES (?, ?, ?, 1, CURRENT_TIMESTAMP)
|
|
1200
|
+
ON CONFLICT(memory_a, memory_b) DO UPDATE SET
|
|
1201
|
+
co_retrievals = co_retrievals + 1,
|
|
1202
|
+
last_fired = CURRENT_TIMESTAMP
|
|
1203
|
+
`).run(randomUUID(), a, b);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
/**
|
|
1209
|
+
* Record that memories were useful together (from LLM feedback)
|
|
1210
|
+
* This is what actually strengthens connections
|
|
1211
|
+
*/
|
|
1212
|
+
recordCoUseful(memoryIds: string[]): void {
|
|
1213
|
+
if (memoryIds.length < 2) return;
|
|
1214
|
+
|
|
1215
|
+
for (let i = 0; i < memoryIds.length; i++) {
|
|
1216
|
+
for (let j = i + 1; j < memoryIds.length; j++) {
|
|
1217
|
+
const [a, b] = memoryIds[i] < memoryIds[j]
|
|
1218
|
+
? [memoryIds[i], memoryIds[j]]
|
|
1219
|
+
: [memoryIds[j], memoryIds[i]];
|
|
1220
|
+
|
|
1221
|
+
// Get current stats to calculate new strength
|
|
1222
|
+
const conn = this.getOrCreateConnection(a, b);
|
|
1223
|
+
const newCoUseful = conn.co_useful + 1;
|
|
1224
|
+
const newCoRetrievals = Math.max(conn.co_retrievals, newCoUseful);
|
|
1225
|
+
|
|
1226
|
+
// Calculate strength with diminishing returns
|
|
1227
|
+
const usefulRatio = newCoUseful / Math.max(1, newCoRetrievals);
|
|
1228
|
+
const diminished = Math.sqrt(usefulRatio);
|
|
1229
|
+
const confidence = Math.min(1, newCoUseful / 3); // Require 3+ co-useful for strong connection
|
|
1230
|
+
const newStrength = diminished * confidence;
|
|
1231
|
+
|
|
1232
|
+
this.db.prepare(`
|
|
1233
|
+
UPDATE memory_connections
|
|
1234
|
+
SET co_useful = ?, strength = ?, last_fired = CURRENT_TIMESTAMP
|
|
1235
|
+
WHERE memory_a = ? AND memory_b = ?
|
|
1236
|
+
`).run(newCoUseful, newStrength, a, b);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/**
|
|
1242
|
+
* Decay unused connections (called during consolidation)
|
|
1243
|
+
*/
|
|
1244
|
+
decayConnections(daysThreshold: number = 30, decayFactor: number = 0.9): number {
|
|
1245
|
+
const result = this.db.prepare(`
|
|
1246
|
+
UPDATE memory_connections
|
|
1247
|
+
SET strength = strength * ?
|
|
1248
|
+
WHERE last_fired < datetime('now', '-' || ? || ' days')
|
|
1249
|
+
AND strength > 0.05
|
|
1250
|
+
`).run(decayFactor, daysThreshold);
|
|
1251
|
+
|
|
1252
|
+
// Clean up very weak connections
|
|
1253
|
+
this.db.prepare(`
|
|
1254
|
+
DELETE FROM memory_connections WHERE strength < 0.05
|
|
1255
|
+
`).run();
|
|
1256
|
+
|
|
1257
|
+
return result.changes;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// ============ Retrieval Logs (Deferred Learning) ============
|
|
1261
|
+
|
|
1262
|
+
/**
|
|
1263
|
+
* Log a retrieval for later learning
|
|
1264
|
+
*/
|
|
1265
|
+
createRetrievalLog(
|
|
1266
|
+
sessionId: string,
|
|
1267
|
+
recallId: string,
|
|
1268
|
+
query: string,
|
|
1269
|
+
memoryIds: string[]
|
|
1270
|
+
): RetrievalLog {
|
|
1271
|
+
const id = randomUUID();
|
|
1272
|
+
this.db.prepare(`
|
|
1273
|
+
INSERT INTO retrieval_logs (id, session_id, recall_id, query, memory_ids)
|
|
1274
|
+
VALUES (?, ?, ?, ?, ?)
|
|
1275
|
+
`).run(id, sessionId, recallId, query, JSON.stringify(memoryIds));
|
|
1276
|
+
return this.getRetrievalLog(recallId)!;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
getRetrievalLog(recallId: string): RetrievalLog | null {
|
|
1280
|
+
const row = this.stmt("SELECT * FROM retrieval_logs WHERE recall_id = ?").get(recallId) as Record<string, unknown> | undefined;
|
|
1281
|
+
return row ? this.rowToRetrievalLog(row) : null;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
/**
|
|
1285
|
+
* Update retrieval log with LLM feedback
|
|
1286
|
+
*/
|
|
1287
|
+
updateRetrievalFeedback(recallId: string, usefulIds: string[], needMore: boolean): boolean {
|
|
1288
|
+
const result = this.db.prepare(`
|
|
1289
|
+
UPDATE retrieval_logs
|
|
1290
|
+
SET useful_ids = ?, need_more = ?
|
|
1291
|
+
WHERE recall_id = ?
|
|
1292
|
+
`).run(JSON.stringify(usefulIds), needMore ? 1 : 0, recallId);
|
|
1293
|
+
return result.changes > 0;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
/**
|
|
1297
|
+
* Get unprocessed retrieval logs for learning
|
|
1298
|
+
*/
|
|
1299
|
+
getUnprocessedRetrievalLogs(limit: number = 100): RetrievalLog[] {
|
|
1300
|
+
const rows = this.stmt(`
|
|
1301
|
+
SELECT * FROM retrieval_logs
|
|
1302
|
+
WHERE processed = 0 AND useful_ids IS NOT NULL
|
|
1303
|
+
ORDER BY timestamp ASC
|
|
1304
|
+
LIMIT ?
|
|
1305
|
+
`).all(limit) as Record<string, unknown>[];
|
|
1306
|
+
return rows.map(r => this.rowToRetrievalLog(r));
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
/**
|
|
1310
|
+
* Mark retrieval logs as processed
|
|
1311
|
+
*/
|
|
1312
|
+
markRetrievalLogsProcessed(ids: string[]): void {
|
|
1313
|
+
if (ids.length === 0) return;
|
|
1314
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
1315
|
+
this.db.prepare(`
|
|
1316
|
+
UPDATE retrieval_logs SET processed = 1 WHERE id IN (${placeholders})
|
|
1317
|
+
`).run(...ids);
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
/**
|
|
1321
|
+
* Clean up old retrieval logs
|
|
1322
|
+
*/
|
|
1323
|
+
cleanupRetrievalLogs(daysOld: number = 7): number {
|
|
1324
|
+
const result = this.db.prepare(`
|
|
1325
|
+
DELETE FROM retrieval_logs
|
|
1326
|
+
WHERE processed = 1 AND timestamp < datetime('now', '-' || ? || ' days')
|
|
1327
|
+
`).run(daysOld);
|
|
1328
|
+
return result.changes;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1046
1331
|
// ============ Statistics ============
|
|
1047
1332
|
|
|
1048
1333
|
getStats(): {
|
|
@@ -1174,4 +1459,31 @@ export class EngramDatabase {
|
|
|
1174
1459
|
resolved_at: row.resolved_at ? new Date(row.resolved_at as string) : null,
|
|
1175
1460
|
};
|
|
1176
1461
|
}
|
|
1462
|
+
|
|
1463
|
+
private rowToConnection(row: Record<string, unknown>): MemoryConnection {
|
|
1464
|
+
return {
|
|
1465
|
+
id: row.id as string,
|
|
1466
|
+
memory_a: row.memory_a as string,
|
|
1467
|
+
memory_b: row.memory_b as string,
|
|
1468
|
+
strength: row.strength as number,
|
|
1469
|
+
co_retrievals: row.co_retrievals as number,
|
|
1470
|
+
co_useful: row.co_useful as number,
|
|
1471
|
+
last_fired: row.last_fired ? new Date(row.last_fired as string) : null,
|
|
1472
|
+
created_at: new Date(row.created_at as string),
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
private rowToRetrievalLog(row: Record<string, unknown>): RetrievalLog {
|
|
1477
|
+
return {
|
|
1478
|
+
id: row.id as string,
|
|
1479
|
+
session_id: row.session_id as string,
|
|
1480
|
+
recall_id: row.recall_id as string,
|
|
1481
|
+
query: row.query as string,
|
|
1482
|
+
memory_ids: JSON.parse(row.memory_ids as string),
|
|
1483
|
+
useful_ids: row.useful_ids ? JSON.parse(row.useful_ids as string) : null,
|
|
1484
|
+
need_more: Boolean(row.need_more),
|
|
1485
|
+
timestamp: new Date(row.timestamp as string),
|
|
1486
|
+
processed: Boolean(row.processed),
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1177
1489
|
}
|
package/src/web/chat-handler.ts
CHANGED
|
@@ -669,15 +669,15 @@ export class ChatHandler {
|
|
|
669
669
|
const query = input.query as string;
|
|
670
670
|
const limit = (input.limit as number) || 10;
|
|
671
671
|
|
|
672
|
-
const
|
|
672
|
+
const response = await this.search.search(query, { limit });
|
|
673
673
|
return {
|
|
674
|
-
results: results.map((r) => ({
|
|
674
|
+
results: response.results.map((r) => ({
|
|
675
675
|
id: r.memory.id,
|
|
676
676
|
content: r.memory.content.substring(0, 300) + (r.memory.content.length > 300 ? "..." : ""),
|
|
677
677
|
timestamp: r.memory.timestamp.toISOString(),
|
|
678
678
|
score: r.score.toFixed(3),
|
|
679
679
|
})),
|
|
680
|
-
count: results.length,
|
|
680
|
+
count: response.results.length,
|
|
681
681
|
};
|
|
682
682
|
}
|
|
683
683
|
|
package/src/web/server.ts
CHANGED
|
@@ -241,9 +241,9 @@ export class EngramWebServer {
|
|
|
241
241
|
const offset = parseInt(url.searchParams.get("offset") || "0");
|
|
242
242
|
|
|
243
243
|
if (query) {
|
|
244
|
-
const
|
|
244
|
+
const response = await this.search.search(query, { limit });
|
|
245
245
|
res.end(JSON.stringify({
|
|
246
|
-
memories: results.map(r => ({
|
|
246
|
+
memories: response.results.map(r => ({
|
|
247
247
|
...r.memory,
|
|
248
248
|
score: r.score,
|
|
249
249
|
sources: r.sources,
|