@mnemonic-ai/core 0.1.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/chunk-5Z46NSNR.js +228 -0
- package/dist/chunk-5Z46NSNR.js.map +1 -0
- package/dist/chunk-CZDK53NR.js +24 -0
- package/dist/chunk-CZDK53NR.js.map +1 -0
- package/dist/chunk-L7SCUMC3.js +53 -0
- package/dist/chunk-L7SCUMC3.js.map +1 -0
- package/dist/chunk-M3IZJTMM.js +474 -0
- package/dist/chunk-M3IZJTMM.js.map +1 -0
- package/dist/chunk-NA7L5FQN.js +1581 -0
- package/dist/chunk-NA7L5FQN.js.map +1 -0
- package/dist/chunk-OEEEWS2M.js +375 -0
- package/dist/chunk-OEEEWS2M.js.map +1 -0
- package/dist/chunk-YZW6DYUY.js +46 -0
- package/dist/chunk-YZW6DYUY.js.map +1 -0
- package/dist/cli/main.cjs +1827 -0
- package/dist/cli/main.cjs.map +1 -0
- package/dist/cli/main.d.cts +1 -0
- package/dist/cli/main.d.ts +1 -0
- package/dist/cli/main.js +84 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/client-b2Xhkqdl.d.cts +409 -0
- package/dist/client-b2Xhkqdl.d.ts +409 -0
- package/dist/index.cjs +2547 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +309 -0
- package/dist/index.d.ts +309 -0
- package/dist/index.js +160 -0
- package/dist/index.js.map +1 -0
- package/dist/local-MYLINANE.js +7 -0
- package/dist/local-MYLINANE.js.map +1 -0
- package/dist/mcp/main.cjs +2775 -0
- package/dist/mcp/main.cjs.map +1 -0
- package/dist/mcp/main.d.cts +1 -0
- package/dist/mcp/main.d.ts +1 -0
- package/dist/mcp/main.js +31 -0
- package/dist/mcp/main.js.map +1 -0
- package/dist/mcp/server.cjs +2765 -0
- package/dist/mcp/server.cjs.map +1 -0
- package/dist/mcp/server.d.cts +23 -0
- package/dist/mcp/server.d.ts +23 -0
- package/dist/mcp/server.js +12 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/openai-GDIC3YVT.js +7 -0
- package/dist/openai-GDIC3YVT.js.map +1 -0
- package/dist/postgres-GQ6DZDBW.js +8 -0
- package/dist/postgres-GQ6DZDBW.js.map +1 -0
- package/package.json +117 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2547 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from))
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
|
+
|
|
33
|
+
// src/embeddings/openai.ts
|
|
34
|
+
var openai_exports = {};
|
|
35
|
+
__export(openai_exports, {
|
|
36
|
+
OpenAIEmbedder: () => OpenAIEmbedder
|
|
37
|
+
});
|
|
38
|
+
var MODEL_DIMENSIONS, OpenAIEmbedder;
|
|
39
|
+
var init_openai = __esm({
|
|
40
|
+
"src/embeddings/openai.ts"() {
|
|
41
|
+
"use strict";
|
|
42
|
+
MODEL_DIMENSIONS = {
|
|
43
|
+
"text-embedding-3-small": 1536,
|
|
44
|
+
"text-embedding-3-large": 3072,
|
|
45
|
+
"text-embedding-ada-002": 1536
|
|
46
|
+
};
|
|
47
|
+
OpenAIEmbedder = class {
|
|
48
|
+
_model;
|
|
49
|
+
_apiKey;
|
|
50
|
+
_client = null;
|
|
51
|
+
constructor(options) {
|
|
52
|
+
this._model = options?.model ?? "text-embedding-3-small";
|
|
53
|
+
this._apiKey = options?.apiKey;
|
|
54
|
+
}
|
|
55
|
+
get dimension() {
|
|
56
|
+
return MODEL_DIMENSIONS[this._model] ?? 1536;
|
|
57
|
+
}
|
|
58
|
+
async getClient() {
|
|
59
|
+
if (!this._client) {
|
|
60
|
+
const mod = await Function('return import("openai")')();
|
|
61
|
+
this._client = new mod.default({ apiKey: this._apiKey });
|
|
62
|
+
}
|
|
63
|
+
return this._client;
|
|
64
|
+
}
|
|
65
|
+
async embed(text) {
|
|
66
|
+
const client = await this.getClient();
|
|
67
|
+
const response = await client.embeddings.create({
|
|
68
|
+
model: this._model,
|
|
69
|
+
input: text
|
|
70
|
+
});
|
|
71
|
+
return response.data[0].embedding;
|
|
72
|
+
}
|
|
73
|
+
async embedBatch(texts) {
|
|
74
|
+
const client = await this.getClient();
|
|
75
|
+
const response = await client.embeddings.create({
|
|
76
|
+
model: this._model,
|
|
77
|
+
input: texts
|
|
78
|
+
});
|
|
79
|
+
return response.data.sort((a, b) => a.index - b.index).map((d) => d.embedding);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// src/embeddings/local.ts
|
|
86
|
+
var local_exports = {};
|
|
87
|
+
__export(local_exports, {
|
|
88
|
+
LocalEmbedder: () => LocalEmbedder
|
|
89
|
+
});
|
|
90
|
+
var MODEL_DIMENSIONS2, DEFAULT_MODEL, DEFAULT_DIMENSION, LocalEmbedder;
|
|
91
|
+
var init_local = __esm({
|
|
92
|
+
"src/embeddings/local.ts"() {
|
|
93
|
+
"use strict";
|
|
94
|
+
MODEL_DIMENSIONS2 = {
|
|
95
|
+
"Xenova/all-MiniLM-L6-v2": 384,
|
|
96
|
+
"Xenova/all-MiniLM-L12-v2": 384,
|
|
97
|
+
"Xenova/all-mpnet-base-v2": 768
|
|
98
|
+
};
|
|
99
|
+
DEFAULT_MODEL = "Xenova/all-MiniLM-L6-v2";
|
|
100
|
+
DEFAULT_DIMENSION = 384;
|
|
101
|
+
LocalEmbedder = class {
|
|
102
|
+
_modelName;
|
|
103
|
+
_pipeline = null;
|
|
104
|
+
_dimension;
|
|
105
|
+
constructor(options) {
|
|
106
|
+
this._modelName = options?.model ?? DEFAULT_MODEL;
|
|
107
|
+
this._dimension = MODEL_DIMENSIONS2[this._modelName] ?? DEFAULT_DIMENSION;
|
|
108
|
+
}
|
|
109
|
+
get dimension() {
|
|
110
|
+
return this._dimension;
|
|
111
|
+
}
|
|
112
|
+
async _getPipeline() {
|
|
113
|
+
if (this._pipeline) return this._pipeline;
|
|
114
|
+
let pipeline;
|
|
115
|
+
try {
|
|
116
|
+
const mod = await import("@huggingface/transformers");
|
|
117
|
+
pipeline = mod.pipeline ?? mod.default?.pipeline;
|
|
118
|
+
} catch {
|
|
119
|
+
throw new Error(
|
|
120
|
+
"LocalEmbedder requires '@huggingface/transformers'. Install it with: npm install @huggingface/transformers"
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
this._pipeline = await pipeline("feature-extraction", this._modelName, {
|
|
124
|
+
quantized: true
|
|
125
|
+
});
|
|
126
|
+
return this._pipeline;
|
|
127
|
+
}
|
|
128
|
+
async embed(text) {
|
|
129
|
+
const pipe = await this._getPipeline();
|
|
130
|
+
const output = await pipe(text, { pooling: "mean", normalize: true });
|
|
131
|
+
return Array.from(output.data).slice(0, this._dimension);
|
|
132
|
+
}
|
|
133
|
+
async embedBatch(texts) {
|
|
134
|
+
const results = [];
|
|
135
|
+
for (const text of texts) {
|
|
136
|
+
results.push(await this.embed(text));
|
|
137
|
+
}
|
|
138
|
+
return results;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// src/index.ts
|
|
145
|
+
var index_exports = {};
|
|
146
|
+
__export(index_exports, {
|
|
147
|
+
BUILTIN_SALIENCE_SIGNALS: () => BUILTIN_SALIENCE_SIGNALS,
|
|
148
|
+
BUILTIN_TIERS: () => BUILTIN_TIERS,
|
|
149
|
+
LocalEmbedder: () => LocalEmbedder,
|
|
150
|
+
Memory: () => Memory,
|
|
151
|
+
MemoryTier: () => MemoryTier,
|
|
152
|
+
Mnemonic: () => Mnemonic,
|
|
153
|
+
MnemonicConfig: () => MnemonicConfig,
|
|
154
|
+
OpenAIEmbedder: () => OpenAIEmbedder,
|
|
155
|
+
PostgresStore: () => PostgresStore,
|
|
156
|
+
SQLiteStore: () => SQLiteStore,
|
|
157
|
+
SalienceSignal: () => SalienceSignal,
|
|
158
|
+
TRANSIENT_ERRORS: () => TRANSIENT_ERRORS,
|
|
159
|
+
applyTierChange: () => applyTierChange,
|
|
160
|
+
assemble: () => assemble,
|
|
161
|
+
bruteForceCosineSearch: () => bruteForceCosineSearch,
|
|
162
|
+
buildEmbedder: () => buildEmbedder,
|
|
163
|
+
checkDemotions: () => checkDemotions,
|
|
164
|
+
checkPromotions: () => checkPromotions,
|
|
165
|
+
classify: () => classify,
|
|
166
|
+
cosineSimilarity: () => cosineSimilarity,
|
|
167
|
+
cosineSimilarityMatrix: () => cosineSimilarityMatrix,
|
|
168
|
+
cosineSimilarityVector: () => cosineSimilarityVector,
|
|
169
|
+
createEdge: () => createEdge,
|
|
170
|
+
createLangChainMemory: () => createLangChainMemory,
|
|
171
|
+
detectSalience: () => detectSalience,
|
|
172
|
+
estimateTokens: () => estimateTokens,
|
|
173
|
+
findConsolidationCandidates: () => findConsolidationCandidates,
|
|
174
|
+
findLinks: () => findLinks,
|
|
175
|
+
formatContext: () => formatContext,
|
|
176
|
+
mergeCluster: () => mergeCluster,
|
|
177
|
+
parseDuration: () => parseDuration,
|
|
178
|
+
recallAsPrompt: () => recallAsPrompt,
|
|
179
|
+
registerStore: () => registerStore,
|
|
180
|
+
score: () => score,
|
|
181
|
+
syncToAsync: () => syncToAsync
|
|
182
|
+
});
|
|
183
|
+
module.exports = __toCommonJS(index_exports);
|
|
184
|
+
|
|
185
|
+
// src/client.ts
|
|
186
|
+
var import_node_crypto2 = require("crypto");
|
|
187
|
+
|
|
188
|
+
// src/core/config.ts
|
|
189
|
+
function createDefaultScorerConfig() {
|
|
190
|
+
return {
|
|
191
|
+
weightRecency: 0.4,
|
|
192
|
+
weightFrequency: 0.25,
|
|
193
|
+
weightLinks: 0.2,
|
|
194
|
+
weightBase: 0.15,
|
|
195
|
+
frequencyLogCap: 5,
|
|
196
|
+
linkSaturation: 5,
|
|
197
|
+
supersessionPenalty: 0.1,
|
|
198
|
+
contradictionPenalty: 0.5,
|
|
199
|
+
consolidationPenalty: 0.5,
|
|
200
|
+
tierBaseScores: {
|
|
201
|
+
identity: 1,
|
|
202
|
+
procedural: 0.9,
|
|
203
|
+
structural: 0.8,
|
|
204
|
+
episodic: 0.5,
|
|
205
|
+
transient: 0.2
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function createDefaultClassifierConfig() {
|
|
210
|
+
return {
|
|
211
|
+
extraPatterns: {},
|
|
212
|
+
episodicMinWords: 10,
|
|
213
|
+
customClassifier: null
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function createDefaultLinkerConfig() {
|
|
217
|
+
return {
|
|
218
|
+
similarityThreshold: 0.65,
|
|
219
|
+
maxLinks: 10,
|
|
220
|
+
keywordPenalty: 0.8,
|
|
221
|
+
contradictionThreshold: 0.7,
|
|
222
|
+
elaborationThreshold: 0.85,
|
|
223
|
+
supersessionThreshold: 0.8,
|
|
224
|
+
negationWords: /* @__PURE__ */ new Set([
|
|
225
|
+
"not",
|
|
226
|
+
"never",
|
|
227
|
+
"don't",
|
|
228
|
+
"doesn't",
|
|
229
|
+
"isn't",
|
|
230
|
+
"aren't",
|
|
231
|
+
"won't",
|
|
232
|
+
"no"
|
|
233
|
+
]),
|
|
234
|
+
customRelationFn: null
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function createDefaultAssemblerConfig() {
|
|
238
|
+
return {
|
|
239
|
+
defaultMaxTokens: 3e3,
|
|
240
|
+
diversityStrength: 0.3,
|
|
241
|
+
sortOrder: "chronological",
|
|
242
|
+
minRelevance: 0.25
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function createDefaultConsolidationConfig() {
|
|
246
|
+
return {
|
|
247
|
+
minClusterSize: 3,
|
|
248
|
+
similarityThreshold: 0.7,
|
|
249
|
+
maxContentLength: 2e3,
|
|
250
|
+
prefix: "[Consolidated]",
|
|
251
|
+
sourceName: "consolidation",
|
|
252
|
+
actorName: "system",
|
|
253
|
+
maxCandidates: 1e3,
|
|
254
|
+
maxNeighborsPerCandidate: 50
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function createDefaultPromotionConfig() {
|
|
258
|
+
return {
|
|
259
|
+
transientToEpisodicAccesses: 2,
|
|
260
|
+
episodicToStructuralAccesses: 5,
|
|
261
|
+
structuralToIdentityAccesses: 15,
|
|
262
|
+
proceduralToIdentityAccesses: 25,
|
|
263
|
+
demotionImportanceThreshold: 0.1
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
var SalienceSignal = class {
|
|
267
|
+
name;
|
|
268
|
+
patterns;
|
|
269
|
+
boost;
|
|
270
|
+
_compiled;
|
|
271
|
+
constructor(name, patterns, boost) {
|
|
272
|
+
this.name = name;
|
|
273
|
+
this.patterns = patterns;
|
|
274
|
+
this.boost = boost;
|
|
275
|
+
this._compiled = patterns.map((p) => new RegExp(p, "i"));
|
|
276
|
+
}
|
|
277
|
+
scan(text) {
|
|
278
|
+
return this._compiled.some((re) => re.test(text));
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
var BUILTIN_SALIENCE_SIGNALS = [
|
|
282
|
+
new SalienceSignal("error", [
|
|
283
|
+
"\\b(?:error|exception|traceback|stack\\s*trace|segfault|panic|crash)\\b",
|
|
284
|
+
"\\b(?:failed|failure|broken|bug|regression)\\b",
|
|
285
|
+
"\\b(?:404|500|502|503|ECONNREFUSED|OOM|timeout)\\b"
|
|
286
|
+
], 0.15),
|
|
287
|
+
new SalienceSignal("urgency", [
|
|
288
|
+
"\\b(?:urgent|critical|blocker|p0|p1|sev[- ]?[01]|outage|incident)\\b",
|
|
289
|
+
"\\b(?:ASAP|immediately|right now|emergency)\\b",
|
|
290
|
+
"\\b(?:production is down|prod is down|site is down)\\b"
|
|
291
|
+
], 0.25),
|
|
292
|
+
new SalienceSignal("correction", [
|
|
293
|
+
"\\b(?:actually|correction|i was wrong|that's wrong|that's not right)\\b",
|
|
294
|
+
"\\b(?:let me correct|i meant|i misspoke|sorry,? i meant)\\b",
|
|
295
|
+
"\\b(?:no,? (?:it's|it is|that's|that is)|wait,? (?:no|actually))\\b"
|
|
296
|
+
], 0.2),
|
|
297
|
+
new SalienceSignal("decision", [
|
|
298
|
+
"\\b(?:we've decided|final decision|going with|approved|sign[- ]?off)\\b",
|
|
299
|
+
"\\b(?:agreement|consensus|resolved to|committed to)\\b",
|
|
300
|
+
"\\b(?:from now on|henceforth|going forward)\\b"
|
|
301
|
+
], 0.15),
|
|
302
|
+
new SalienceSignal("frustration", [
|
|
303
|
+
"\\b(?:i(?:'ve| have) told you|already said|keep telling you)\\b",
|
|
304
|
+
"\\b(?:again\\?|how many times|stop (?:doing|suggesting))\\b",
|
|
305
|
+
"\\b(?:this is (?:wrong|bad|terrible|awful)|doesn't work)\\b"
|
|
306
|
+
], 0.2)
|
|
307
|
+
];
|
|
308
|
+
function createDefaultSalienceConfig() {
|
|
309
|
+
return {
|
|
310
|
+
enabled: true,
|
|
311
|
+
maxBoost: 0.4,
|
|
312
|
+
signals: [...BUILTIN_SALIENCE_SIGNALS]
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
var BUILTIN_TIERS = [
|
|
316
|
+
{ name: "identity", defaultHalfLifeDays: 3650, description: "Name, role, company, tech stack \u2014 near-permanent" },
|
|
317
|
+
{ name: "procedural", defaultHalfLifeDays: 365, description: "Learned workflows, multi-step procedures, how-to knowledge" },
|
|
318
|
+
{ name: "structural", defaultHalfLifeDays: 180, description: "Architecture decisions, preferences, constraints" },
|
|
319
|
+
{ name: "episodic", defaultHalfLifeDays: 30, description: "Specific conversations, tasks completed" },
|
|
320
|
+
{ name: "transient", defaultHalfLifeDays: 7, description: "One-off questions, small talk" }
|
|
321
|
+
];
|
|
322
|
+
var MnemonicConfig = class {
|
|
323
|
+
scorer;
|
|
324
|
+
classifier;
|
|
325
|
+
linker;
|
|
326
|
+
assembler;
|
|
327
|
+
consolidation;
|
|
328
|
+
promotion;
|
|
329
|
+
salience;
|
|
330
|
+
charsPerToken;
|
|
331
|
+
defaultTier;
|
|
332
|
+
embeddingSearchLimit;
|
|
333
|
+
_tiers;
|
|
334
|
+
constructor(overrides) {
|
|
335
|
+
this.scorer = { ...createDefaultScorerConfig(), ...overrides?.scorer };
|
|
336
|
+
this.classifier = { ...createDefaultClassifierConfig(), ...overrides?.classifier };
|
|
337
|
+
this.linker = { ...createDefaultLinkerConfig(), ...overrides?.linker };
|
|
338
|
+
this.assembler = { ...createDefaultAssemblerConfig(), ...overrides?.assembler };
|
|
339
|
+
this.consolidation = { ...createDefaultConsolidationConfig(), ...overrides?.consolidation };
|
|
340
|
+
this.promotion = { ...createDefaultPromotionConfig(), ...overrides?.promotion };
|
|
341
|
+
this.salience = { ...createDefaultSalienceConfig(), ...overrides?.salience };
|
|
342
|
+
this.charsPerToken = overrides?.charsPerToken ?? 4;
|
|
343
|
+
this.defaultTier = overrides?.defaultTier ?? "episodic";
|
|
344
|
+
this.embeddingSearchLimit = overrides?.embeddingSearchLimit ?? 100;
|
|
345
|
+
if (this.consolidation.maxCandidates < 1) {
|
|
346
|
+
throw new Error(`maxCandidates must be >= 1, got ${this.consolidation.maxCandidates}`);
|
|
347
|
+
}
|
|
348
|
+
if (this.consolidation.maxNeighborsPerCandidate < 1) {
|
|
349
|
+
throw new Error(
|
|
350
|
+
`maxNeighborsPerCandidate must be >= 1, got ${this.consolidation.maxNeighborsPerCandidate}`
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
this._tiers = /* @__PURE__ */ new Map();
|
|
354
|
+
for (const td of BUILTIN_TIERS) {
|
|
355
|
+
this._tiers.set(td.name, td);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
registerTier(tier) {
|
|
359
|
+
this._tiers.set(tier.name, tier);
|
|
360
|
+
if (!(tier.name in this.scorer.tierBaseScores)) {
|
|
361
|
+
this.scorer.tierBaseScores[tier.name] = 0.5;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
getTier(name) {
|
|
365
|
+
const td = this._tiers.get(name);
|
|
366
|
+
if (!td) throw new Error(`Unknown tier: '${name}'`);
|
|
367
|
+
return td;
|
|
368
|
+
}
|
|
369
|
+
tierNames() {
|
|
370
|
+
return [...this._tiers.keys()];
|
|
371
|
+
}
|
|
372
|
+
halfLifeDays(tierName) {
|
|
373
|
+
return this.getTier(tierName).defaultHalfLifeDays;
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
// src/core/classifier.ts
|
|
378
|
+
var IDENTITY_PATTERNS = [
|
|
379
|
+
/\bmy name is\b/i,
|
|
380
|
+
/\bi(?:'m| am) (?:a |an )?(?:software|data|ml|backend|frontend|devops|platform)\b/i,
|
|
381
|
+
/\bi work (?:at|for)\b/i,
|
|
382
|
+
/\bmy (?:role|title|position) is\b/i,
|
|
383
|
+
/\bour (?:tech |technology )?stack (?:is|includes)\b/i,
|
|
384
|
+
/\bi (?:use|prefer|always use)\b.+(?:language|framework|editor|ide|os)\b/i
|
|
385
|
+
];
|
|
386
|
+
var PROCEDURAL_PATTERNS = [
|
|
387
|
+
/\b(?:step\s+\d|first|then|next|finally|afterwards)\b.*\b(?:step|run|execute|do|click|open|type|enter)\b/i,
|
|
388
|
+
/\bto\s+(?:do|deploy|set up|configure|install|build|run|create|fix)\b.+\b(?:first|then|after|before|finally)\b/i,
|
|
389
|
+
/\bworkflow\b.*\b(?:is|goes|looks like|involves)\b/i,
|
|
390
|
+
/\bprocedure\b.*\b(?:is|for|to)\b/i,
|
|
391
|
+
/\bwhen(?:ever)?\b.+\b(?:always|you should|we|i) (?:run|do|execute|call|use)\b/i,
|
|
392
|
+
/\brecipe\b.*\bfor\b/i,
|
|
393
|
+
/\brunbook\b/i
|
|
394
|
+
];
|
|
395
|
+
var STRUCTURAL_PATTERNS = [
|
|
396
|
+
/\bwe (?:decided|chose|agreed) to\b/i,
|
|
397
|
+
/\barchitectur(?:e|al)\b/i,
|
|
398
|
+
/\bconstraint\b/i,
|
|
399
|
+
/\brequirement\b/i,
|
|
400
|
+
/\bprefer(?:s|ence)?\b.+(?:over|instead|rather)\b/i,
|
|
401
|
+
/\balways\b.+\b(?:use|deploy|run)\b/i,
|
|
402
|
+
/\bnever\b.+\b(?:use|deploy|run)\b/i,
|
|
403
|
+
/\bstandard(?:s|ise|ize)?\b/i,
|
|
404
|
+
/\bconvention\b/i
|
|
405
|
+
];
|
|
406
|
+
var TRANSIENT_PATTERNS = [
|
|
407
|
+
/\b(?:hey|hi|hello|thanks|thank you|cheers|bye|goodbye)\b/i,
|
|
408
|
+
/\bwhat(?:'s| is) the (?:time|date|weather)\b/i,
|
|
409
|
+
/\bjust (?:a )?quick\b/i,
|
|
410
|
+
/\bnever ?mind\b/i
|
|
411
|
+
];
|
|
412
|
+
var COMPILED_DEFAULTS = [
|
|
413
|
+
["identity", IDENTITY_PATTERNS],
|
|
414
|
+
["procedural", PROCEDURAL_PATTERNS],
|
|
415
|
+
["structural", STRUCTURAL_PATTERNS],
|
|
416
|
+
["transient", TRANSIENT_PATTERNS]
|
|
417
|
+
];
|
|
418
|
+
function buildTierPatterns(cfg) {
|
|
419
|
+
if (Object.keys(cfg.extraPatterns).length === 0) {
|
|
420
|
+
return COMPILED_DEFAULTS;
|
|
421
|
+
}
|
|
422
|
+
return COMPILED_DEFAULTS.map(([name, compiled]) => {
|
|
423
|
+
const extra = (cfg.extraPatterns[name] ?? []).map((p) => new RegExp(p, "i"));
|
|
424
|
+
return [name, [...compiled, ...extra]];
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
function classify(content, options) {
|
|
428
|
+
const config = options?.config ?? new MnemonicConfig();
|
|
429
|
+
const cfg = config.classifier;
|
|
430
|
+
const actor = options?.actor ?? "";
|
|
431
|
+
if (cfg.customClassifier) {
|
|
432
|
+
return cfg.customClassifier(content, options?.source ?? "", actor);
|
|
433
|
+
}
|
|
434
|
+
for (const [tierName, patterns] of buildTierPatterns(cfg)) {
|
|
435
|
+
for (const pat of patterns) {
|
|
436
|
+
if (pat.test(content)) {
|
|
437
|
+
return tierName;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if (actor === "user" && content.split(/\s+/).length > cfg.episodicMinWords) {
|
|
442
|
+
return "episodic";
|
|
443
|
+
}
|
|
444
|
+
return config.defaultTier;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// src/core/scorer.ts
|
|
448
|
+
function recencyFactor(memory, halfLifeDays) {
|
|
449
|
+
const now = Date.now();
|
|
450
|
+
const elapsedDays = Math.max(0, (now - memory.lastAccessedAt.getTime()) / 864e5);
|
|
451
|
+
return Math.pow(0.5, elapsedDays / halfLifeDays);
|
|
452
|
+
}
|
|
453
|
+
function frequencyFactor(memory, logCap) {
|
|
454
|
+
if (logCap <= 0) return 1;
|
|
455
|
+
return Math.min(1, Math.log2(1 + memory.accessCount) / logCap);
|
|
456
|
+
}
|
|
457
|
+
function linkFactor(memory, saturation) {
|
|
458
|
+
return Math.min(1, memory.linkedIds.length / saturation);
|
|
459
|
+
}
|
|
460
|
+
function score(memory, config) {
|
|
461
|
+
const cfg = config ?? new MnemonicConfig();
|
|
462
|
+
const sc = cfg.scorer;
|
|
463
|
+
const tierName = memory.tierName;
|
|
464
|
+
let halfLife;
|
|
465
|
+
try {
|
|
466
|
+
halfLife = cfg.halfLifeDays(tierName);
|
|
467
|
+
} catch {
|
|
468
|
+
halfLife = 30;
|
|
469
|
+
}
|
|
470
|
+
const base = sc.tierBaseScores[tierName] ?? 0.5;
|
|
471
|
+
const composite = sc.weightRecency * recencyFactor(memory, halfLife) + sc.weightFrequency * frequencyFactor(memory, sc.frequencyLogCap) + sc.weightLinks * linkFactor(memory, sc.linkSaturation) + sc.weightBase * base;
|
|
472
|
+
const boost = memory.metadata["boost"] ?? 0;
|
|
473
|
+
let result = Math.max(0, composite + boost);
|
|
474
|
+
if (memory.supersededBy != null) {
|
|
475
|
+
result *= Math.min(1, Math.max(0, sc.supersessionPenalty));
|
|
476
|
+
}
|
|
477
|
+
if (memory.supersededBy == null && memory.contradictedBy != null) {
|
|
478
|
+
result *= Math.min(1, Math.max(0, sc.contradictionPenalty));
|
|
479
|
+
}
|
|
480
|
+
if (memory.metadata["consolidated_into"] != null) {
|
|
481
|
+
result *= Math.min(1, Math.max(0, sc.consolidationPenalty));
|
|
482
|
+
}
|
|
483
|
+
return result;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// src/core/memory.ts
|
|
487
|
+
var import_node_crypto = require("crypto");
|
|
488
|
+
|
|
489
|
+
// src/core/utils.ts
|
|
490
|
+
function cosineSimilarity(a, b) {
|
|
491
|
+
if (a.length !== b.length) {
|
|
492
|
+
throw new Error(
|
|
493
|
+
`Embedding dimension mismatch: ${a.length} vs ${b.length}`
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
let dot = 0;
|
|
497
|
+
let normA = 0;
|
|
498
|
+
let normB = 0;
|
|
499
|
+
for (let i = 0; i < a.length; i++) {
|
|
500
|
+
dot += a[i] * b[i];
|
|
501
|
+
normA += a[i] * a[i];
|
|
502
|
+
normB += b[i] * b[i];
|
|
503
|
+
}
|
|
504
|
+
normA = Math.sqrt(normA);
|
|
505
|
+
normB = Math.sqrt(normB);
|
|
506
|
+
if (normA === 0 || normB === 0) return 0;
|
|
507
|
+
return dot / (normA * normB);
|
|
508
|
+
}
|
|
509
|
+
function cosineSimilarityVector(query, embeddings) {
|
|
510
|
+
const dim = query.length;
|
|
511
|
+
const n = embeddings.length;
|
|
512
|
+
const result = new Float64Array(n);
|
|
513
|
+
for (let ei = 0; ei < n; ei++) {
|
|
514
|
+
if (embeddings[ei].length !== dim) {
|
|
515
|
+
throw new Error(
|
|
516
|
+
`Embedding dimension mismatch: ${embeddings[ei].length} vs query ${dim}`
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
let normQ = 0;
|
|
521
|
+
for (let i = 0; i < dim; i++) normQ += query[i] * query[i];
|
|
522
|
+
normQ = Math.sqrt(normQ);
|
|
523
|
+
if (normQ === 0) return result;
|
|
524
|
+
const qNormed = new Float64Array(dim);
|
|
525
|
+
for (let i = 0; i < dim; i++) qNormed[i] = query[i] / normQ;
|
|
526
|
+
for (let ei = 0; ei < n; ei++) {
|
|
527
|
+
const emb = embeddings[ei];
|
|
528
|
+
let dot = 0;
|
|
529
|
+
let normE = 0;
|
|
530
|
+
for (let i = 0; i < dim; i++) {
|
|
531
|
+
dot += qNormed[i] * emb[i];
|
|
532
|
+
normE += emb[i] * emb[i];
|
|
533
|
+
}
|
|
534
|
+
normE = Math.sqrt(normE);
|
|
535
|
+
result[ei] = normE === 0 ? 0 : dot / normE;
|
|
536
|
+
}
|
|
537
|
+
return result;
|
|
538
|
+
}
|
|
539
|
+
function cosineSimilarityMatrix(embeddings) {
|
|
540
|
+
const n = embeddings.length;
|
|
541
|
+
if (n === 0) return new Float64Array(0);
|
|
542
|
+
const dim = embeddings[0].length;
|
|
543
|
+
for (let i = 1; i < n; i++) {
|
|
544
|
+
if (embeddings[i].length !== dim) {
|
|
545
|
+
throw new Error(
|
|
546
|
+
`Embedding dimension mismatch at index ${i}: ${embeddings[i].length} vs ${dim}`
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
const result = new Float64Array(n * n);
|
|
551
|
+
const normed = [];
|
|
552
|
+
const isZeroNorm = [];
|
|
553
|
+
for (let i = 0; i < n; i++) {
|
|
554
|
+
const emb = embeddings[i];
|
|
555
|
+
let norm = 0;
|
|
556
|
+
for (let d = 0; d < dim; d++) norm += emb[d] * emb[d];
|
|
557
|
+
norm = Math.sqrt(norm);
|
|
558
|
+
const row = new Float64Array(dim);
|
|
559
|
+
if (norm > 0) {
|
|
560
|
+
for (let d = 0; d < dim; d++) row[d] = emb[d] / norm;
|
|
561
|
+
isZeroNorm.push(false);
|
|
562
|
+
} else {
|
|
563
|
+
isZeroNorm.push(true);
|
|
564
|
+
}
|
|
565
|
+
normed.push(row);
|
|
566
|
+
}
|
|
567
|
+
for (let i = 0; i < n; i++) {
|
|
568
|
+
result[i * n + i] = isZeroNorm[i] ? 0 : 1;
|
|
569
|
+
for (let j = i + 1; j < n; j++) {
|
|
570
|
+
let dot = 0;
|
|
571
|
+
for (let d = 0; d < dim; d++) dot += normed[i][d] * normed[j][d];
|
|
572
|
+
result[i * n + j] = dot;
|
|
573
|
+
result[j * n + i] = dot;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return result;
|
|
577
|
+
}
|
|
578
|
+
var DURATION_RE = /^(\d+)\s*([wdhms])$/;
|
|
579
|
+
var MS_MULTIPLIERS = {
|
|
580
|
+
w: 7 * 24 * 60 * 60 * 1e3,
|
|
581
|
+
d: 24 * 60 * 60 * 1e3,
|
|
582
|
+
h: 60 * 60 * 1e3,
|
|
583
|
+
m: 60 * 1e3,
|
|
584
|
+
s: 1e3
|
|
585
|
+
};
|
|
586
|
+
function parseDuration(s) {
|
|
587
|
+
const match = s.trim().toLowerCase().match(DURATION_RE);
|
|
588
|
+
if (!match) {
|
|
589
|
+
throw new Error(
|
|
590
|
+
`Cannot parse duration: '${s}'. Use e.g. 2w, 30d, 24h, 60m, 120s.`
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
const n = parseInt(match[1], 10);
|
|
594
|
+
const unit = match[2];
|
|
595
|
+
return n * MS_MULTIPLIERS[unit];
|
|
596
|
+
}
|
|
597
|
+
function estimateTokens(text, charsPerToken = 4) {
|
|
598
|
+
return Math.max(1, Math.floor(text.length / charsPerToken));
|
|
599
|
+
}
|
|
600
|
+
function parseSqlitePath(uri) {
|
|
601
|
+
if (!uri.includes("///")) {
|
|
602
|
+
throw new Error(
|
|
603
|
+
`Invalid SQLite URI: '${uri}'. Expected format: sqlite:///path/to/db`
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
return uri.split("///")[1] || "mnemonic.db";
|
|
607
|
+
}
|
|
608
|
+
function bruteForceSimilarPairs(memories, threshold, maxCandidates) {
|
|
609
|
+
const cap = Math.max(0, maxCandidates);
|
|
610
|
+
const withEmb = memories.filter((m) => m.embedding != null).slice(0, cap);
|
|
611
|
+
if (withEmb.length < 2) return [];
|
|
612
|
+
const simMatrix = cosineSimilarityMatrix(withEmb.map((m) => m.embedding));
|
|
613
|
+
const n = withEmb.length;
|
|
614
|
+
const pairs = [];
|
|
615
|
+
for (let i = 0; i < n; i++) {
|
|
616
|
+
for (let j = i + 1; j < n; j++) {
|
|
617
|
+
const sim = simMatrix[i * n + j];
|
|
618
|
+
if (sim >= threshold) {
|
|
619
|
+
pairs.push({ idA: withEmb[i].id, idB: withEmb[j].id, similarity: sim });
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
return pairs;
|
|
624
|
+
}
|
|
625
|
+
function bruteForceCosineSearch(memories, embedding, limit) {
|
|
626
|
+
if (limit <= 0) return [];
|
|
627
|
+
const withEmb = memories.filter((m) => m.embedding != null);
|
|
628
|
+
if (withEmb.length === 0) return [];
|
|
629
|
+
const embList = withEmb.map((m) => m.embedding);
|
|
630
|
+
const sims = cosineSimilarityVector(embedding, embList);
|
|
631
|
+
const indices = Array.from({ length: withEmb.length }, (_, i) => i);
|
|
632
|
+
indices.sort((a, b) => sims[b] - sims[a]);
|
|
633
|
+
return indices.slice(0, limit).map((i) => withEmb[i]);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// src/core/memory.ts
|
|
637
|
+
var MemoryTier = {
|
|
638
|
+
IDENTITY: "identity",
|
|
639
|
+
PROCEDURAL: "procedural",
|
|
640
|
+
STRUCTURAL: "structural",
|
|
641
|
+
EPISODIC: "episodic",
|
|
642
|
+
TRANSIENT: "transient"
|
|
643
|
+
};
|
|
644
|
+
var Memory = class {
|
|
645
|
+
content;
|
|
646
|
+
id;
|
|
647
|
+
tier;
|
|
648
|
+
source;
|
|
649
|
+
actor;
|
|
650
|
+
sessionId;
|
|
651
|
+
createdAt;
|
|
652
|
+
lastAccessedAt;
|
|
653
|
+
accessCount;
|
|
654
|
+
importance;
|
|
655
|
+
supersededBy;
|
|
656
|
+
contradictedBy;
|
|
657
|
+
embedding;
|
|
658
|
+
metadata;
|
|
659
|
+
linkedIds;
|
|
660
|
+
agentId;
|
|
661
|
+
shared;
|
|
662
|
+
_charsPerToken;
|
|
663
|
+
constructor(init) {
|
|
664
|
+
const now = /* @__PURE__ */ new Date();
|
|
665
|
+
this.content = init.content;
|
|
666
|
+
this.id = init.id ?? (0, import_node_crypto.randomUUID)().replace(/-/g, "");
|
|
667
|
+
this.tier = init.tier ?? MemoryTier.EPISODIC;
|
|
668
|
+
this.source = init.source ?? "unknown";
|
|
669
|
+
this.actor = init.actor ?? "system";
|
|
670
|
+
this.sessionId = init.sessionId ?? null;
|
|
671
|
+
this.createdAt = init.createdAt ?? now;
|
|
672
|
+
this.lastAccessedAt = init.lastAccessedAt ?? now;
|
|
673
|
+
this.accessCount = init.accessCount ?? 0;
|
|
674
|
+
this.importance = init.importance ?? 1;
|
|
675
|
+
this.supersededBy = init.supersededBy ?? null;
|
|
676
|
+
this.contradictedBy = init.contradictedBy ?? null;
|
|
677
|
+
this.embedding = init.embedding ?? null;
|
|
678
|
+
this.metadata = init.metadata ?? {};
|
|
679
|
+
this.linkedIds = init.linkedIds ?? [];
|
|
680
|
+
this.agentId = init.agentId ?? "default";
|
|
681
|
+
this.shared = init.shared ?? false;
|
|
682
|
+
this._charsPerToken = init.charsPerToken ?? 4;
|
|
683
|
+
}
|
|
684
|
+
/** The tier as a plain string, regardless of whether it was set as an enum. */
|
|
685
|
+
get tierName() {
|
|
686
|
+
return this.tier;
|
|
687
|
+
}
|
|
688
|
+
/** Rough token count using configurable chars-per-token ratio. */
|
|
689
|
+
get tokenEstimate() {
|
|
690
|
+
return estimateTokens(this.content, this._charsPerToken);
|
|
691
|
+
}
|
|
692
|
+
/** Record an access. */
|
|
693
|
+
touch() {
|
|
694
|
+
this.lastAccessedAt = /* @__PURE__ */ new Date();
|
|
695
|
+
this.accessCount += 1;
|
|
696
|
+
}
|
|
697
|
+
};
|
|
698
|
+
function createEdge(sourceId, targetId, relation, weight = 1) {
|
|
699
|
+
return { sourceId, targetId, relation, weight, createdAt: /* @__PURE__ */ new Date() };
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// src/core/linker.ts
|
|
703
|
+
var NUMBER_RE = /\b\d+(?:[.,]\d+)*/g;
|
|
704
|
+
var CORRECTION_RE = /\b(?:correction|actually|i was wrong|that was wrong|that's wrong|that's not right)\b|\b(?:that isn't right|that isn't correct)\b|\b(?:let me correct|i meant|i misspoke|sorry,? i meant)\b|\b(?:no,? (?:it's|it is|that's|that is)|wait,? (?:no|actually))\b|\b(?:disregard|ignore what i said|scratch that|on second thought)\b/i;
|
|
705
|
+
function keywordOverlap(a, b) {
|
|
706
|
+
const sa = new Set(a.toLowerCase().split(/\s+/));
|
|
707
|
+
const sb = new Set(b.toLowerCase().split(/\s+/));
|
|
708
|
+
if (sa.size === 0 || sb.size === 0) return 0;
|
|
709
|
+
let intersection = 0;
|
|
710
|
+
for (const word of sa) {
|
|
711
|
+
if (sb.has(word)) intersection++;
|
|
712
|
+
}
|
|
713
|
+
const union = (/* @__PURE__ */ new Set([...sa, ...sb])).size;
|
|
714
|
+
return intersection / union;
|
|
715
|
+
}
|
|
716
|
+
function extractNumbers(text) {
|
|
717
|
+
return new Set(text.match(NUMBER_RE) ?? []);
|
|
718
|
+
}
|
|
719
|
+
function hasNumberDisagreement(a, b) {
|
|
720
|
+
const aNums = extractNumbers(a);
|
|
721
|
+
const bNums = extractNumbers(b);
|
|
722
|
+
if (aNums.size === 0 || bNums.size === 0) return false;
|
|
723
|
+
if (aNums.size !== bNums.size) return true;
|
|
724
|
+
for (const n of aNums) {
|
|
725
|
+
if (!bNums.has(n)) return true;
|
|
726
|
+
}
|
|
727
|
+
return false;
|
|
728
|
+
}
|
|
729
|
+
function hasCorrectionSignal(text) {
|
|
730
|
+
return CORRECTION_RE.test(text);
|
|
731
|
+
}
|
|
732
|
+
function inferRelation(a, b, similarity, cfg) {
|
|
733
|
+
if (cfg.customRelationFn) {
|
|
734
|
+
return cfg.customRelationFn(a, b, similarity);
|
|
735
|
+
}
|
|
736
|
+
const aWords = new Set(a.content.toLowerCase().split(/\s+/));
|
|
737
|
+
const bWords = new Set(b.content.toLowerCase().split(/\s+/));
|
|
738
|
+
const hasNegation = [...aWords].some((w) => cfg.negationWords.has(w)) !== [...bWords].some((w) => cfg.negationWords.has(w));
|
|
739
|
+
const hasNumbers = hasNumberDisagreement(a.content, b.content);
|
|
740
|
+
const hasCorrection = hasCorrectionSignal(a.content) || hasCorrectionSignal(b.content);
|
|
741
|
+
const hasContradiction = (hasNegation || hasNumbers || hasCorrection) && similarity > cfg.contradictionThreshold;
|
|
742
|
+
if (hasContradiction && a.createdAt > b.createdAt) {
|
|
743
|
+
if (hasNumbers || hasCorrection || similarity > cfg.supersessionThreshold) {
|
|
744
|
+
return "supersedes";
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
if (hasContradiction) return "contradicts";
|
|
748
|
+
if (similarity > cfg.elaborationThreshold) return "elaborates";
|
|
749
|
+
return "related_to";
|
|
750
|
+
}
|
|
751
|
+
function findLinks(newMemory, existing, options) {
|
|
752
|
+
const cfg = (options?.config ?? new MnemonicConfig()).linker;
|
|
753
|
+
const threshold = options?.similarityThreshold ?? cfg.similarityThreshold;
|
|
754
|
+
const limit = options?.maxLinks ?? cfg.maxLinks;
|
|
755
|
+
const others = existing.filter((m) => m.id !== newMemory.id);
|
|
756
|
+
if (others.length === 0) return [];
|
|
757
|
+
const scored = [];
|
|
758
|
+
if (newMemory.embedding) {
|
|
759
|
+
const withEmb = others.filter((m) => m.embedding != null);
|
|
760
|
+
const withoutEmb = others.filter((m) => m.embedding == null);
|
|
761
|
+
if (withEmb.length > 0) {
|
|
762
|
+
const embList = withEmb.map((m) => m.embedding);
|
|
763
|
+
const sims = cosineSimilarityVector(newMemory.embedding, embList);
|
|
764
|
+
for (let i = 0; i < withEmb.length; i++) {
|
|
765
|
+
if (sims[i] >= threshold) {
|
|
766
|
+
scored.push([sims[i], withEmb[i]]);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
for (const mem of withoutEmb) {
|
|
771
|
+
const sim = keywordOverlap(newMemory.content, mem.content) * cfg.keywordPenalty;
|
|
772
|
+
if (sim >= threshold) scored.push([sim, mem]);
|
|
773
|
+
}
|
|
774
|
+
} else {
|
|
775
|
+
for (const mem of others) {
|
|
776
|
+
const sim = keywordOverlap(newMemory.content, mem.content) * cfg.keywordPenalty;
|
|
777
|
+
if (sim >= threshold) scored.push([sim, mem]);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
scored.sort((a, b) => b[0] - a[0]);
|
|
781
|
+
return scored.slice(0, limit).map(([sim, mem]) => {
|
|
782
|
+
const relation = inferRelation(newMemory, mem, sim, cfg);
|
|
783
|
+
return createEdge(newMemory.id, mem.id, relation, sim);
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// src/core/assembler.ts
|
|
788
|
+
function getSortFn(sortOrder) {
|
|
789
|
+
if (typeof sortOrder === "function") return sortOrder;
|
|
790
|
+
if (sortOrder === "importance") return (a, b) => b.importance - a.importance;
|
|
791
|
+
if (sortOrder === "relevance") return () => 0;
|
|
792
|
+
return (a, b) => a.createdAt.getTime() - b.createdAt.getTime();
|
|
793
|
+
}
|
|
794
|
+
function assemble(_query, candidates, options) {
|
|
795
|
+
const config = options?.config ?? new MnemonicConfig();
|
|
796
|
+
const cfg = config.assembler;
|
|
797
|
+
const budget = options?.maxTokens ?? cfg.defaultMaxTokens;
|
|
798
|
+
const divStrength = options?.diversityStrength ?? cfg.diversityStrength;
|
|
799
|
+
const relevanceFloor = options?.minRelevance ?? cfg.minRelevance;
|
|
800
|
+
const qEmbed = options?.queryEmbedding ?? null;
|
|
801
|
+
let n = candidates.length;
|
|
802
|
+
if (n === 0) return [];
|
|
803
|
+
let importances = new Float64Array(n);
|
|
804
|
+
for (let i = 0; i < n; i++) {
|
|
805
|
+
importances[i] = score(candidates[i], config);
|
|
806
|
+
}
|
|
807
|
+
let embIndices = [];
|
|
808
|
+
for (let i = 0; i < n; i++) {
|
|
809
|
+
if (candidates[i].embedding) embIndices.push(i);
|
|
810
|
+
}
|
|
811
|
+
let relevances = new Float64Array(n);
|
|
812
|
+
for (let i = 0; i < n; i++) {
|
|
813
|
+
relevances[i] = keywordOverlap(_query, candidates[i].content);
|
|
814
|
+
}
|
|
815
|
+
if (qEmbed && embIndices.length > 0) {
|
|
816
|
+
const embList = embIndices.map((i) => candidates[i].embedding);
|
|
817
|
+
const sims = cosineSimilarityVector(qEmbed, embList);
|
|
818
|
+
for (let pos = 0; pos < embIndices.length; pos++) {
|
|
819
|
+
const idx = embIndices[pos];
|
|
820
|
+
relevances[idx] = Math.max(Math.max(0, sims[pos]), relevances[idx]);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
if (relevanceFloor > 0) {
|
|
824
|
+
const keep = [];
|
|
825
|
+
for (let i = 0; i < n; i++) {
|
|
826
|
+
if (relevances[i] >= relevanceFloor) keep.push(i);
|
|
827
|
+
}
|
|
828
|
+
if (keep.length < n) {
|
|
829
|
+
const oldToNew = /* @__PURE__ */ new Map();
|
|
830
|
+
keep.forEach((old, idx) => oldToNew.set(old, idx));
|
|
831
|
+
candidates = keep.map((i) => candidates[i]);
|
|
832
|
+
importances = Float64Array.from(keep.map((i) => importances[i]));
|
|
833
|
+
relevances = Float64Array.from(keep.map((i) => relevances[i]));
|
|
834
|
+
embIndices = embIndices.filter((i) => oldToNew.has(i)).map((i) => oldToNew.get(i));
|
|
835
|
+
n = candidates.length;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
let simMatrix = null;
|
|
839
|
+
const idxToPos = /* @__PURE__ */ new Map();
|
|
840
|
+
if (embIndices.length > 0 && divStrength > 0) {
|
|
841
|
+
const embList = embIndices.map((i) => candidates[i].embedding);
|
|
842
|
+
simMatrix = cosineSimilarityMatrix(embList);
|
|
843
|
+
for (let pos = 0; pos < embIndices.length; pos++) {
|
|
844
|
+
idxToPos.set(embIndices[pos], pos);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
const nEmb = embIndices.length;
|
|
848
|
+
const remaining = Array.from({ length: n }, (_, i) => i);
|
|
849
|
+
const selectedEmbPositions = [];
|
|
850
|
+
const selectedIndices = [];
|
|
851
|
+
let budgetLeft = budget;
|
|
852
|
+
while (remaining.length > 0 && budgetLeft > 0) {
|
|
853
|
+
let bestRi = -1;
|
|
854
|
+
let bestValue = -1;
|
|
855
|
+
for (let ri = 0; ri < remaining.length; ri++) {
|
|
856
|
+
const ci = remaining[ri];
|
|
857
|
+
if (candidates[ci].tokenEstimate > budgetLeft) continue;
|
|
858
|
+
let value = relevances[ci] * importances[ci];
|
|
859
|
+
if (selectedEmbPositions.length > 0 && simMatrix && idxToPos.has(ci)) {
|
|
860
|
+
const pos = idxToPos.get(ci);
|
|
861
|
+
let maxSim = 0;
|
|
862
|
+
for (const sp of selectedEmbPositions) {
|
|
863
|
+
const s = simMatrix[pos * nEmb + sp];
|
|
864
|
+
if (s > maxSim) maxSim = s;
|
|
865
|
+
}
|
|
866
|
+
value *= Math.min(1, Math.max(0, 1 - divStrength * maxSim));
|
|
867
|
+
}
|
|
868
|
+
if (value > bestValue) {
|
|
869
|
+
bestValue = value;
|
|
870
|
+
bestRi = ri;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
if (bestRi < 0) break;
|
|
874
|
+
const chosenIdx = remaining.splice(bestRi, 1)[0];
|
|
875
|
+
candidates[chosenIdx].touch();
|
|
876
|
+
selectedIndices.push(chosenIdx);
|
|
877
|
+
if (idxToPos.has(chosenIdx)) {
|
|
878
|
+
selectedEmbPositions.push(idxToPos.get(chosenIdx));
|
|
879
|
+
}
|
|
880
|
+
budgetLeft -= candidates[chosenIdx].tokenEstimate;
|
|
881
|
+
}
|
|
882
|
+
const selected = selectedIndices.map((i) => candidates[i]);
|
|
883
|
+
selected.sort(getSortFn(cfg.sortOrder));
|
|
884
|
+
return selected;
|
|
885
|
+
}
|
|
886
|
+
function detectConflicts(memories, config) {
|
|
887
|
+
const cfg = config ?? new MnemonicConfig();
|
|
888
|
+
const negWords = cfg.linker.negationWords;
|
|
889
|
+
const conflicts = [];
|
|
890
|
+
const seenPairs = /* @__PURE__ */ new Set();
|
|
891
|
+
const n = memories.length;
|
|
892
|
+
const pairKey = (a, b) => `${Math.min(a, b)},${Math.max(a, b)}`;
|
|
893
|
+
const idToIdx = /* @__PURE__ */ new Map();
|
|
894
|
+
for (let i = 0; i < n; i++) idToIdx.set(memories[i].id, i);
|
|
895
|
+
for (let i = 0; i < n; i++) {
|
|
896
|
+
const mem = memories[i];
|
|
897
|
+
if (mem.contradictedBy && idToIdx.has(mem.contradictedBy)) {
|
|
898
|
+
const j = idToIdx.get(mem.contradictedBy);
|
|
899
|
+
const key = pairKey(i, j);
|
|
900
|
+
if (!seenPairs.has(key)) {
|
|
901
|
+
seenPairs.add(key);
|
|
902
|
+
conflicts.push([Math.min(i, j), Math.max(i, j), "contradicted by newer memory"]);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
for (let i = 0; i < n; i++) {
|
|
907
|
+
for (let j = i + 1; j < n; j++) {
|
|
908
|
+
const key = pairKey(i, j);
|
|
909
|
+
if (seenPairs.has(key)) continue;
|
|
910
|
+
const a = memories[i], b = memories[j];
|
|
911
|
+
const kwSim = keywordOverlap(a.content, b.content);
|
|
912
|
+
let embSim = 0;
|
|
913
|
+
if (a.embedding && b.embedding) {
|
|
914
|
+
const sims = cosineSimilarityVector(a.embedding, [b.embedding]);
|
|
915
|
+
embSim = sims[0];
|
|
916
|
+
}
|
|
917
|
+
const maxSim = Math.max(kwSim, embSim);
|
|
918
|
+
if (maxSim < 0.6) continue;
|
|
919
|
+
if (hasNumberDisagreement(a.content, b.content)) {
|
|
920
|
+
conflicts.push([i, j, "numbers differ on similar topic"]);
|
|
921
|
+
seenPairs.add(key);
|
|
922
|
+
} else if (hasCorrectionSignal(a.content) || hasCorrectionSignal(b.content)) {
|
|
923
|
+
conflicts.push([i, j, "correction language detected"]);
|
|
924
|
+
seenPairs.add(key);
|
|
925
|
+
} else if (maxSim > 0.8) {
|
|
926
|
+
const aWords = new Set(a.content.toLowerCase().split(/\s+/));
|
|
927
|
+
const bWords = new Set(b.content.toLowerCase().split(/\s+/));
|
|
928
|
+
const aNeg = [...aWords].some((w) => negWords.has(w));
|
|
929
|
+
const bNeg = [...bWords].some((w) => negWords.has(w));
|
|
930
|
+
if (aNeg !== bNeg) {
|
|
931
|
+
conflicts.push([i, j, "contradictory statements"]);
|
|
932
|
+
seenPairs.add(key);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
return conflicts;
|
|
938
|
+
}
|
|
939
|
+
function formatContext(memories, header = "Relevant context:", conflicts) {
|
|
940
|
+
if (memories.length === 0) return "";
|
|
941
|
+
const lines = [header, ""];
|
|
942
|
+
for (const mem of memories) {
|
|
943
|
+
lines.push(`- [${mem.tierName}] ${mem.content}`);
|
|
944
|
+
}
|
|
945
|
+
if (conflicts && conflicts.length > 0) {
|
|
946
|
+
lines.push("");
|
|
947
|
+
for (const [idxA, idxB, reason] of conflicts) {
|
|
948
|
+
lines.push(`[!] Potential conflict between items ${idxA + 1} and ${idxB + 1}: ${reason}`);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
return lines.join("\n");
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// src/core/promotion.ts
|
|
955
|
+
var TIER_TRANSITIONS = {
|
|
956
|
+
transient: { promoteTo: "episodic", demoteTo: null, promoteThresholdKey: "transientToEpisodicAccesses" },
|
|
957
|
+
episodic: { promoteTo: "structural", demoteTo: "transient", promoteThresholdKey: "episodicToStructuralAccesses" },
|
|
958
|
+
structural: { promoteTo: "identity", demoteTo: "episodic", promoteThresholdKey: "structuralToIdentityAccesses" },
|
|
959
|
+
procedural: { promoteTo: "identity", demoteTo: "structural", promoteThresholdKey: "proceduralToIdentityAccesses" },
|
|
960
|
+
identity: { promoteTo: null, demoteTo: "structural", promoteThresholdKey: null }
|
|
961
|
+
};
|
|
962
|
+
function checkPromotions(memories, config) {
|
|
963
|
+
const cfg = (config ?? new MnemonicConfig()).promotion;
|
|
964
|
+
const results = [];
|
|
965
|
+
for (const mem of memories) {
|
|
966
|
+
const transition = TIER_TRANSITIONS[mem.tierName];
|
|
967
|
+
if (!transition?.promoteTo || !transition.promoteThresholdKey) continue;
|
|
968
|
+
const threshold = cfg[transition.promoteThresholdKey];
|
|
969
|
+
if (mem.accessCount >= threshold) {
|
|
970
|
+
results.push({
|
|
971
|
+
memoryId: mem.id,
|
|
972
|
+
oldTier: mem.tierName,
|
|
973
|
+
newTier: transition.promoteTo,
|
|
974
|
+
reason: `access_count (${mem.accessCount}) >= ${threshold}`
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
return results;
|
|
979
|
+
}
|
|
980
|
+
function checkDemotions(memories, config) {
|
|
981
|
+
const fullCfg = config ?? new MnemonicConfig();
|
|
982
|
+
const cfg = fullCfg.promotion;
|
|
983
|
+
const results = [];
|
|
984
|
+
for (const mem of memories) {
|
|
985
|
+
if (mem.supersededBy != null) continue;
|
|
986
|
+
const transition = TIER_TRANSITIONS[mem.tierName];
|
|
987
|
+
if (!transition?.demoteTo) continue;
|
|
988
|
+
const importance = score(mem, fullCfg);
|
|
989
|
+
if (importance < cfg.demotionImportanceThreshold) {
|
|
990
|
+
results.push({
|
|
991
|
+
memoryId: mem.id,
|
|
992
|
+
oldTier: mem.tierName,
|
|
993
|
+
newTier: transition.demoteTo,
|
|
994
|
+
reason: `importance (${importance.toFixed(4)}) < ${cfg.demotionImportanceThreshold}`
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
return results;
|
|
999
|
+
}
|
|
1000
|
+
function applyTierChange(memory, newTier) {
|
|
1001
|
+
const oldTier = memory.tierName;
|
|
1002
|
+
const history = memory.metadata["previous_tiers"] ?? [];
|
|
1003
|
+
history.push(oldTier);
|
|
1004
|
+
memory.metadata["previous_tiers"] = history;
|
|
1005
|
+
memory.tier = newTier;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// src/core/salience.ts
|
|
1009
|
+
function detectSalience(content, config) {
|
|
1010
|
+
const cfg = config ?? createDefaultSalienceConfig();
|
|
1011
|
+
if (!cfg.enabled) {
|
|
1012
|
+
return { boost: 0, signals: [] };
|
|
1013
|
+
}
|
|
1014
|
+
let totalBoost = 0;
|
|
1015
|
+
const fired = [];
|
|
1016
|
+
for (const signal of cfg.signals) {
|
|
1017
|
+
if (signal.scan(content)) {
|
|
1018
|
+
totalBoost += signal.boost;
|
|
1019
|
+
fired.push(signal.name);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
return {
|
|
1023
|
+
boost: Math.min(totalBoost, cfg.maxBoost),
|
|
1024
|
+
signals: fired
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// src/consolidation/engine.ts
|
|
1029
|
+
var TRANSIENT_ERRORS = /* @__PURE__ */ new Set([
|
|
1030
|
+
"RateLimitError",
|
|
1031
|
+
"APITimeoutError",
|
|
1032
|
+
"APIConnectionTimeoutError",
|
|
1033
|
+
// OpenAI Node SDK timeout subclass
|
|
1034
|
+
"APIConnectionError",
|
|
1035
|
+
"InternalServerError"
|
|
1036
|
+
]);
|
|
1037
|
+
function findConsolidationCandidates(memories, config, pairs) {
|
|
1038
|
+
const cfg = (config ?? new MnemonicConfig()).consolidation;
|
|
1039
|
+
const episodic = memories.filter(
|
|
1040
|
+
(m) => m.tierName === "episodic" && m.embedding != null
|
|
1041
|
+
);
|
|
1042
|
+
if (episodic.length < cfg.minClusterSize) return [];
|
|
1043
|
+
if (pairs != null) {
|
|
1044
|
+
return clusterFromPairs(episodic, pairs, cfg.minClusterSize);
|
|
1045
|
+
}
|
|
1046
|
+
return clusterFromMatrix(episodic, cfg.similarityThreshold, cfg.minClusterSize);
|
|
1047
|
+
}
|
|
1048
|
+
function unionFindClusters(items, edges, minClusterSize) {
|
|
1049
|
+
const n = items.length;
|
|
1050
|
+
const parent = Array.from({ length: n }, (_, i) => i);
|
|
1051
|
+
function find(x) {
|
|
1052
|
+
while (parent[x] !== x) {
|
|
1053
|
+
parent[x] = parent[parent[x]];
|
|
1054
|
+
x = parent[x];
|
|
1055
|
+
}
|
|
1056
|
+
return x;
|
|
1057
|
+
}
|
|
1058
|
+
for (const [a, b] of edges) {
|
|
1059
|
+
const ra = find(a);
|
|
1060
|
+
const rb = find(b);
|
|
1061
|
+
if (ra !== rb) parent[ra] = rb;
|
|
1062
|
+
}
|
|
1063
|
+
const clusters = /* @__PURE__ */ new Map();
|
|
1064
|
+
for (let i = 0; i < n; i++) {
|
|
1065
|
+
const root = find(i);
|
|
1066
|
+
if (!clusters.has(root)) clusters.set(root, []);
|
|
1067
|
+
clusters.get(root).push(items[i]);
|
|
1068
|
+
}
|
|
1069
|
+
return [...clusters.values()].filter((c) => c.length >= minClusterSize);
|
|
1070
|
+
}
|
|
1071
|
+
function clusterFromPairs(episodic, pairs, minClusterSize) {
|
|
1072
|
+
const idToIdx = /* @__PURE__ */ new Map();
|
|
1073
|
+
for (let i = 0; i < episodic.length; i++) {
|
|
1074
|
+
idToIdx.set(episodic[i].id, i);
|
|
1075
|
+
}
|
|
1076
|
+
const edges = [];
|
|
1077
|
+
for (const p of pairs) {
|
|
1078
|
+
const ia = idToIdx.get(p.idA);
|
|
1079
|
+
const ib = idToIdx.get(p.idB);
|
|
1080
|
+
if (ia != null && ib != null) {
|
|
1081
|
+
edges.push([ia, ib]);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
return unionFindClusters(episodic, edges, minClusterSize);
|
|
1085
|
+
}
|
|
1086
|
+
function clusterFromMatrix(episodic, similarityThreshold, minClusterSize) {
|
|
1087
|
+
const n = episodic.length;
|
|
1088
|
+
const simMatrix = cosineSimilarityMatrix(episodic.map((m) => m.embedding));
|
|
1089
|
+
const edges = [];
|
|
1090
|
+
for (let i = 0; i < n; i++) {
|
|
1091
|
+
for (let j = i + 1; j < n; j++) {
|
|
1092
|
+
if (simMatrix[i * n + j] >= similarityThreshold) {
|
|
1093
|
+
edges.push([i, j]);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
return unionFindClusters(episodic, edges, minClusterSize);
|
|
1098
|
+
}
|
|
1099
|
+
function defaultJoin(cluster, maxLength) {
|
|
1100
|
+
const sorted = [...cluster].sort(
|
|
1101
|
+
(a, b) => b.createdAt.getTime() - a.createdAt.getTime()
|
|
1102
|
+
);
|
|
1103
|
+
const unique = [];
|
|
1104
|
+
for (const mem of sorted) {
|
|
1105
|
+
if (unique.some((prev) => keywordOverlap(mem.content, prev) > 0.8)) continue;
|
|
1106
|
+
unique.push(mem.content);
|
|
1107
|
+
}
|
|
1108
|
+
const header = `(${cluster.length} memories, ${unique.length} unique)`;
|
|
1109
|
+
let combined = header + "\n" + unique.join("\n");
|
|
1110
|
+
if (combined.length > maxLength) {
|
|
1111
|
+
combined = combined.slice(0, maxLength - 3) + "...";
|
|
1112
|
+
}
|
|
1113
|
+
return combined;
|
|
1114
|
+
}
|
|
1115
|
+
async function mergeCluster(cluster, config) {
|
|
1116
|
+
const cfg = (config ?? new MnemonicConfig()).consolidation;
|
|
1117
|
+
let combined;
|
|
1118
|
+
if (cfg.summarizer != null) {
|
|
1119
|
+
try {
|
|
1120
|
+
combined = (await cfg.summarizer(cluster.map((m) => m.content))).trim();
|
|
1121
|
+
if (!combined) {
|
|
1122
|
+
throw new Error("Summarizer returned empty content");
|
|
1123
|
+
}
|
|
1124
|
+
} catch (err) {
|
|
1125
|
+
const errName = err instanceof Error ? err.constructor.name : "";
|
|
1126
|
+
if (TRANSIENT_ERRORS.has(errName)) {
|
|
1127
|
+
throw err;
|
|
1128
|
+
}
|
|
1129
|
+
console.warn(`Summarizer failed permanently with ${errName}; falling back to default join`);
|
|
1130
|
+
combined = defaultJoin(cluster, cfg.maxContentLength);
|
|
1131
|
+
}
|
|
1132
|
+
if (combined.length > cfg.maxContentLength) {
|
|
1133
|
+
combined = combined.slice(0, cfg.maxContentLength - 3) + "...";
|
|
1134
|
+
}
|
|
1135
|
+
} else {
|
|
1136
|
+
combined = defaultJoin(cluster, cfg.maxContentLength);
|
|
1137
|
+
}
|
|
1138
|
+
const summary = new Memory({
|
|
1139
|
+
content: `${cfg.prefix} ${combined}`,
|
|
1140
|
+
tier: "structural",
|
|
1141
|
+
source: cfg.sourceName,
|
|
1142
|
+
actor: cfg.actorName,
|
|
1143
|
+
metadata: {
|
|
1144
|
+
consolidated_from: cluster.map((m) => m.id),
|
|
1145
|
+
original_count: cluster.length
|
|
1146
|
+
}
|
|
1147
|
+
});
|
|
1148
|
+
summary.importance = Math.max(...cluster.map((m) => m.importance));
|
|
1149
|
+
const linkedSet = new Set(cluster.flatMap((m) => m.linkedIds));
|
|
1150
|
+
summary.linkedIds = [...linkedSet];
|
|
1151
|
+
return summary;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// src/stores/sync-adapter.ts
|
|
1155
|
+
function syncToAsync(store) {
|
|
1156
|
+
return {
|
|
1157
|
+
async initialize() {
|
|
1158
|
+
store.initialize();
|
|
1159
|
+
},
|
|
1160
|
+
async close() {
|
|
1161
|
+
store.close();
|
|
1162
|
+
},
|
|
1163
|
+
async save(memory) {
|
|
1164
|
+
store.save(memory);
|
|
1165
|
+
},
|
|
1166
|
+
async get(memoryId) {
|
|
1167
|
+
return store.get(memoryId);
|
|
1168
|
+
},
|
|
1169
|
+
async listAll(options) {
|
|
1170
|
+
return store.listAll(options);
|
|
1171
|
+
},
|
|
1172
|
+
async delete(memoryId) {
|
|
1173
|
+
store.delete(memoryId);
|
|
1174
|
+
},
|
|
1175
|
+
async deleteOlderThan(ageMs, tier, options) {
|
|
1176
|
+
return store.deleteOlderThan(ageMs, tier, options);
|
|
1177
|
+
},
|
|
1178
|
+
async saveEdge(edge) {
|
|
1179
|
+
store.saveEdge(edge);
|
|
1180
|
+
},
|
|
1181
|
+
async getEdges(memoryId) {
|
|
1182
|
+
return store.getEdges(memoryId);
|
|
1183
|
+
},
|
|
1184
|
+
async searchByEmbedding(embedding, limit, options) {
|
|
1185
|
+
return store.searchByEmbedding(embedding, limit, options);
|
|
1186
|
+
},
|
|
1187
|
+
async findSimilarPairs(options) {
|
|
1188
|
+
return store.findSimilarPairs(options);
|
|
1189
|
+
},
|
|
1190
|
+
async getMeta(key) {
|
|
1191
|
+
return store.getMeta(key);
|
|
1192
|
+
},
|
|
1193
|
+
async setMeta(key, value) {
|
|
1194
|
+
store.setMeta(key, value);
|
|
1195
|
+
}
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
// src/stores/sqlite.ts
|
|
1200
|
+
var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
|
|
1201
|
+
var SCHEMA = `
|
|
1202
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
1203
|
+
id TEXT PRIMARY KEY,
|
|
1204
|
+
content TEXT NOT NULL,
|
|
1205
|
+
tier TEXT NOT NULL,
|
|
1206
|
+
source TEXT NOT NULL DEFAULT 'unknown',
|
|
1207
|
+
actor TEXT NOT NULL DEFAULT 'system',
|
|
1208
|
+
session_id TEXT,
|
|
1209
|
+
created_at TEXT NOT NULL,
|
|
1210
|
+
last_accessed TEXT NOT NULL,
|
|
1211
|
+
access_count INTEGER NOT NULL DEFAULT 0,
|
|
1212
|
+
importance REAL NOT NULL DEFAULT 1.0,
|
|
1213
|
+
superseded_by TEXT,
|
|
1214
|
+
contradicted_by TEXT,
|
|
1215
|
+
embedding TEXT,
|
|
1216
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
1217
|
+
linked_ids TEXT NOT NULL DEFAULT '[]',
|
|
1218
|
+
agent_id TEXT NOT NULL DEFAULT 'default',
|
|
1219
|
+
shared INTEGER NOT NULL DEFAULT 0
|
|
1220
|
+
);
|
|
1221
|
+
|
|
1222
|
+
CREATE TABLE IF NOT EXISTS edges (
|
|
1223
|
+
source_id TEXT NOT NULL,
|
|
1224
|
+
target_id TEXT NOT NULL,
|
|
1225
|
+
relation TEXT NOT NULL,
|
|
1226
|
+
weight REAL NOT NULL DEFAULT 1.0,
|
|
1227
|
+
created_at TEXT NOT NULL,
|
|
1228
|
+
PRIMARY KEY (source_id, target_id, relation)
|
|
1229
|
+
);
|
|
1230
|
+
|
|
1231
|
+
CREATE TABLE IF NOT EXISTS meta (
|
|
1232
|
+
key TEXT PRIMARY KEY,
|
|
1233
|
+
value TEXT NOT NULL
|
|
1234
|
+
);
|
|
1235
|
+
|
|
1236
|
+
CREATE INDEX IF NOT EXISTS idx_memories_agent_id ON memories (agent_id);
|
|
1237
|
+
`;
|
|
1238
|
+
var SQLiteStore = class {
|
|
1239
|
+
_db;
|
|
1240
|
+
constructor(path = "mnemonic.db") {
|
|
1241
|
+
this._db = new import_better_sqlite3.default(path);
|
|
1242
|
+
this._db.pragma("journal_mode = WAL");
|
|
1243
|
+
}
|
|
1244
|
+
initialize() {
|
|
1245
|
+
this._db.exec(SCHEMA);
|
|
1246
|
+
this._migrate();
|
|
1247
|
+
}
|
|
1248
|
+
close() {
|
|
1249
|
+
this._db.close();
|
|
1250
|
+
}
|
|
1251
|
+
/** Add missing columns/tables for stores created before multi-agent support. */
|
|
1252
|
+
_migrate() {
|
|
1253
|
+
const cols = this._db.prepare("PRAGMA table_info(memories)").all();
|
|
1254
|
+
const colNames = new Set(cols.map((c) => c.name));
|
|
1255
|
+
if (!colNames.has("agent_id")) {
|
|
1256
|
+
this._db.exec(
|
|
1257
|
+
"ALTER TABLE memories ADD COLUMN agent_id TEXT NOT NULL DEFAULT 'default'"
|
|
1258
|
+
);
|
|
1259
|
+
this._db.exec(
|
|
1260
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_agent_id ON memories (agent_id)"
|
|
1261
|
+
);
|
|
1262
|
+
}
|
|
1263
|
+
if (!colNames.has("shared")) {
|
|
1264
|
+
this._db.exec(
|
|
1265
|
+
"ALTER TABLE memories ADD COLUMN shared INTEGER NOT NULL DEFAULT 0"
|
|
1266
|
+
);
|
|
1267
|
+
}
|
|
1268
|
+
if (!colNames.has("contradicted_by")) {
|
|
1269
|
+
this._db.exec(
|
|
1270
|
+
"ALTER TABLE memories ADD COLUMN contradicted_by TEXT"
|
|
1271
|
+
);
|
|
1272
|
+
this._db.exec(
|
|
1273
|
+
"UPDATE memories SET contradicted_by = json_extract(metadata, '$.contradicted_by') WHERE contradicted_by IS NULL AND json_extract(metadata, '$.contradicted_by') IS NOT NULL"
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
this._db.exec(
|
|
1277
|
+
"CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)"
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
save(memory) {
|
|
1281
|
+
this._db.prepare(
|
|
1282
|
+
`INSERT OR REPLACE INTO memories
|
|
1283
|
+
(id, content, tier, source, actor, session_id, created_at,
|
|
1284
|
+
last_accessed, access_count, importance, superseded_by,
|
|
1285
|
+
contradicted_by, embedding, metadata, linked_ids, agent_id, shared)
|
|
1286
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
1287
|
+
).run(
|
|
1288
|
+
memory.id,
|
|
1289
|
+
memory.content,
|
|
1290
|
+
memory.tierName,
|
|
1291
|
+
memory.source,
|
|
1292
|
+
memory.actor,
|
|
1293
|
+
memory.sessionId,
|
|
1294
|
+
memory.createdAt.toISOString(),
|
|
1295
|
+
memory.lastAccessedAt.toISOString(),
|
|
1296
|
+
memory.accessCount,
|
|
1297
|
+
memory.importance,
|
|
1298
|
+
memory.supersededBy,
|
|
1299
|
+
memory.contradictedBy ?? null,
|
|
1300
|
+
memory.embedding ? JSON.stringify(memory.embedding) : null,
|
|
1301
|
+
JSON.stringify(memory.metadata),
|
|
1302
|
+
JSON.stringify(memory.linkedIds),
|
|
1303
|
+
memory.agentId,
|
|
1304
|
+
memory.shared ? 1 : 0
|
|
1305
|
+
);
|
|
1306
|
+
}
|
|
1307
|
+
get(memoryId) {
|
|
1308
|
+
const row = this._db.prepare("SELECT * FROM memories WHERE id = ?").get(memoryId);
|
|
1309
|
+
if (!row) return null;
|
|
1310
|
+
return rowToMemory(row);
|
|
1311
|
+
}
|
|
1312
|
+
listAll(options) {
|
|
1313
|
+
const clauses = [];
|
|
1314
|
+
const params = [];
|
|
1315
|
+
if (options?.agentId) {
|
|
1316
|
+
if (options.includeShared === false) {
|
|
1317
|
+
clauses.push("agent_id = ?");
|
|
1318
|
+
params.push(options.agentId);
|
|
1319
|
+
} else {
|
|
1320
|
+
clauses.push("(agent_id = ? OR shared = 1)");
|
|
1321
|
+
params.push(options.agentId);
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
if (options?.tier != null) {
|
|
1325
|
+
clauses.push("tier = ?");
|
|
1326
|
+
params.push(options.tier);
|
|
1327
|
+
}
|
|
1328
|
+
const where = clauses.length > 0 ? ` WHERE ${clauses.join(" AND ")}` : "";
|
|
1329
|
+
let limitClause = "";
|
|
1330
|
+
if (options?.limit != null) {
|
|
1331
|
+
limitClause = " LIMIT ?";
|
|
1332
|
+
params.push(options.limit);
|
|
1333
|
+
}
|
|
1334
|
+
const rows = this._db.prepare(`SELECT * FROM memories${where}${limitClause}`).all(...params);
|
|
1335
|
+
return rows.map(rowToMemory);
|
|
1336
|
+
}
|
|
1337
|
+
delete(memoryId) {
|
|
1338
|
+
this._db.transaction(() => {
|
|
1339
|
+
this._db.prepare("DELETE FROM memories WHERE id = ?").run(memoryId);
|
|
1340
|
+
this._db.prepare("DELETE FROM edges WHERE source_id = ? OR target_id = ?").run(memoryId, memoryId);
|
|
1341
|
+
})();
|
|
1342
|
+
}
|
|
1343
|
+
deleteOlderThan(ageMs, tier, options) {
|
|
1344
|
+
const cutoff = new Date(Date.now() - ageMs).toISOString();
|
|
1345
|
+
const conditions = ["created_at < ?"];
|
|
1346
|
+
const params = [cutoff];
|
|
1347
|
+
if (tier != null) {
|
|
1348
|
+
conditions.push("tier = ?");
|
|
1349
|
+
params.push(tier);
|
|
1350
|
+
}
|
|
1351
|
+
if (options?.agentId) {
|
|
1352
|
+
conditions.push("agent_id = ?");
|
|
1353
|
+
params.push(options.agentId);
|
|
1354
|
+
}
|
|
1355
|
+
const where = conditions.join(" AND ");
|
|
1356
|
+
const ids = this._db.prepare(`SELECT id FROM memories WHERE ${where}`).all(...params);
|
|
1357
|
+
if (ids.length === 0) return 0;
|
|
1358
|
+
const idList = ids.map((r) => r.id);
|
|
1359
|
+
const placeholders = idList.map(() => "?").join(",");
|
|
1360
|
+
const txn = this._db.transaction(() => {
|
|
1361
|
+
this._db.prepare(
|
|
1362
|
+
`DELETE FROM edges WHERE source_id IN (${placeholders}) OR target_id IN (${placeholders})`
|
|
1363
|
+
).run(...idList, ...idList);
|
|
1364
|
+
const result = this._db.prepare(`DELETE FROM memories WHERE ${where}`).run(...params);
|
|
1365
|
+
return result.changes;
|
|
1366
|
+
});
|
|
1367
|
+
return txn();
|
|
1368
|
+
}
|
|
1369
|
+
saveEdge(edge) {
|
|
1370
|
+
this._db.prepare(
|
|
1371
|
+
`INSERT OR REPLACE INTO edges (source_id, target_id, relation, weight, created_at)
|
|
1372
|
+
VALUES (?, ?, ?, ?, ?)`
|
|
1373
|
+
).run(
|
|
1374
|
+
edge.sourceId,
|
|
1375
|
+
edge.targetId,
|
|
1376
|
+
edge.relation,
|
|
1377
|
+
edge.weight,
|
|
1378
|
+
edge.createdAt.toISOString()
|
|
1379
|
+
);
|
|
1380
|
+
}
|
|
1381
|
+
getEdges(memoryId) {
|
|
1382
|
+
const rows = this._db.prepare("SELECT * FROM edges WHERE source_id = ? OR target_id = ?").all(memoryId, memoryId);
|
|
1383
|
+
return rows.map((r) => ({
|
|
1384
|
+
sourceId: r["source_id"],
|
|
1385
|
+
targetId: r["target_id"],
|
|
1386
|
+
relation: r["relation"],
|
|
1387
|
+
weight: r["weight"],
|
|
1388
|
+
createdAt: new Date(r["created_at"])
|
|
1389
|
+
}));
|
|
1390
|
+
}
|
|
1391
|
+
searchByEmbedding(embedding, limit = 50, options) {
|
|
1392
|
+
const all = this.listAll(options);
|
|
1393
|
+
return bruteForceCosineSearch(all, embedding, limit);
|
|
1394
|
+
}
|
|
1395
|
+
findSimilarPairs(options) {
|
|
1396
|
+
const maxCandidates = options?.maxCandidates ?? 1e3;
|
|
1397
|
+
const memories = this.listAll({
|
|
1398
|
+
agentId: options?.agentId,
|
|
1399
|
+
includeShared: false,
|
|
1400
|
+
tier: options?.tier
|
|
1401
|
+
});
|
|
1402
|
+
return bruteForceSimilarPairs(
|
|
1403
|
+
memories,
|
|
1404
|
+
options?.threshold ?? 0.7,
|
|
1405
|
+
maxCandidates
|
|
1406
|
+
);
|
|
1407
|
+
}
|
|
1408
|
+
getMeta(key) {
|
|
1409
|
+
const row = this._db.prepare("SELECT value FROM meta WHERE key = ?").get(key);
|
|
1410
|
+
return row?.value ?? null;
|
|
1411
|
+
}
|
|
1412
|
+
setMeta(key, value) {
|
|
1413
|
+
this._db.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)").run(key, value);
|
|
1414
|
+
}
|
|
1415
|
+
};
|
|
1416
|
+
function rowToMemory(row) {
|
|
1417
|
+
const embRaw = row["embedding"];
|
|
1418
|
+
const m = new Memory({
|
|
1419
|
+
id: row["id"],
|
|
1420
|
+
content: row["content"],
|
|
1421
|
+
tier: row["tier"],
|
|
1422
|
+
source: row["source"],
|
|
1423
|
+
actor: row["actor"],
|
|
1424
|
+
sessionId: row["session_id"],
|
|
1425
|
+
createdAt: new Date(row["created_at"]),
|
|
1426
|
+
lastAccessedAt: new Date(row["last_accessed"]),
|
|
1427
|
+
accessCount: row["access_count"],
|
|
1428
|
+
importance: row["importance"],
|
|
1429
|
+
supersededBy: row["superseded_by"],
|
|
1430
|
+
embedding: embRaw ? JSON.parse(embRaw) : null,
|
|
1431
|
+
metadata: JSON.parse(row["metadata"]),
|
|
1432
|
+
linkedIds: JSON.parse(row["linked_ids"]),
|
|
1433
|
+
agentId: row["agent_id"] ?? "default",
|
|
1434
|
+
shared: row["shared"] === 1 || row["shared"] === true
|
|
1435
|
+
});
|
|
1436
|
+
m.contradictedBy = row["contradicted_by"];
|
|
1437
|
+
return m;
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
// src/client.ts
|
|
1441
|
+
var _storeRegistry = /* @__PURE__ */ new Map();
|
|
1442
|
+
function sanitizeUri(uri) {
|
|
1443
|
+
try {
|
|
1444
|
+
const url = new URL(uri);
|
|
1445
|
+
if (url.username || url.password) {
|
|
1446
|
+
url.username = "***";
|
|
1447
|
+
url.password = "";
|
|
1448
|
+
}
|
|
1449
|
+
return url.toString();
|
|
1450
|
+
} catch {
|
|
1451
|
+
return uri.includes("://") ? uri.split("://")[0] + "://***" : uri;
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
var SCHEME_RE = /^[a-z][a-z0-9+.-]{0,31}$/;
|
|
1455
|
+
function registerStore(scheme, factory, options) {
|
|
1456
|
+
if (!SCHEME_RE.test(scheme)) {
|
|
1457
|
+
throw new Error(
|
|
1458
|
+
`Invalid store scheme '${scheme}': must match [a-z][a-z0-9+.-]{0,31}`
|
|
1459
|
+
);
|
|
1460
|
+
}
|
|
1461
|
+
if (!options?.overwrite && _storeRegistry.has(scheme)) {
|
|
1462
|
+
throw new Error(
|
|
1463
|
+
`Store scheme '${scheme}' is already registered. Pass { overwrite: true } to replace.`
|
|
1464
|
+
);
|
|
1465
|
+
}
|
|
1466
|
+
_storeRegistry.set(scheme, factory);
|
|
1467
|
+
}
|
|
1468
|
+
function parseStoreUri(uri, embeddingDim) {
|
|
1469
|
+
if (uri.startsWith("sqlite:")) {
|
|
1470
|
+
return new SQLiteStore(parseSqlitePath(uri));
|
|
1471
|
+
}
|
|
1472
|
+
if (uri.startsWith("postgres://") || uri.startsWith("postgresql://")) {
|
|
1473
|
+
throw new Error(
|
|
1474
|
+
"PostgresStore cannot be resolved from URI synchronously. Import PostgresStore directly and pass it as the store option."
|
|
1475
|
+
);
|
|
1476
|
+
}
|
|
1477
|
+
for (const [scheme, factory] of _storeRegistry) {
|
|
1478
|
+
if (uri.startsWith(`${scheme}://`) || uri.startsWith(`${scheme}+`)) {
|
|
1479
|
+
const result = factory(uri, embeddingDim);
|
|
1480
|
+
if (result instanceof Promise) {
|
|
1481
|
+
throw new Error(
|
|
1482
|
+
`Store factory for '${scheme}' returned a Promise. Async store factories must be resolved before passing to Mnemonic.`
|
|
1483
|
+
);
|
|
1484
|
+
}
|
|
1485
|
+
return result;
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
throw new Error(`Unsupported store URI: '${sanitizeUri(uri)}'`);
|
|
1489
|
+
}
|
|
1490
|
+
var Mnemonic = class _Mnemonic {
|
|
1491
|
+
_config;
|
|
1492
|
+
_store;
|
|
1493
|
+
_embedder;
|
|
1494
|
+
_autoClassify;
|
|
1495
|
+
_agentId;
|
|
1496
|
+
_resolvedAgentId = null;
|
|
1497
|
+
_initPromise = null;
|
|
1498
|
+
constructor(options) {
|
|
1499
|
+
this._config = options?.config ?? new MnemonicConfig();
|
|
1500
|
+
this._embedder = options?.embedder ?? null;
|
|
1501
|
+
this._autoClassify = options?.autoClassify ?? true;
|
|
1502
|
+
this._agentId = options?.agentId ?? null;
|
|
1503
|
+
const storeOpt = options?.store ?? "sqlite:///mnemonic.db";
|
|
1504
|
+
if (typeof storeOpt === "string") {
|
|
1505
|
+
const raw = parseStoreUri(storeOpt, this._embedder?.dimension);
|
|
1506
|
+
this._store = _wrapIfSync(raw);
|
|
1507
|
+
} else {
|
|
1508
|
+
this._store = _wrapIfSync(storeOpt);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
get config() {
|
|
1512
|
+
return this._config;
|
|
1513
|
+
}
|
|
1514
|
+
/** The underlying async store. */
|
|
1515
|
+
get store() {
|
|
1516
|
+
return this._store;
|
|
1517
|
+
}
|
|
1518
|
+
/** The embedder (if configured). */
|
|
1519
|
+
get embedder() {
|
|
1520
|
+
return this._embedder;
|
|
1521
|
+
}
|
|
1522
|
+
/** The resolved agent ID (available after init()). */
|
|
1523
|
+
get agentId() {
|
|
1524
|
+
if (!this._resolvedAgentId) {
|
|
1525
|
+
throw new Error("Client not initialized. Call init() first.");
|
|
1526
|
+
}
|
|
1527
|
+
return this._resolvedAgentId;
|
|
1528
|
+
}
|
|
1529
|
+
/**
|
|
1530
|
+
* Initialize the store and resolve agent identity. Must be called before use.
|
|
1531
|
+
* Pass `{ skipStoreInit: true }` when reusing an already-initialized store
|
|
1532
|
+
* (e.g. creating per-agent clients that share a single backend).
|
|
1533
|
+
*/
|
|
1534
|
+
async init(options) {
|
|
1535
|
+
if (this._resolvedAgentId) return;
|
|
1536
|
+
if (this._initPromise) {
|
|
1537
|
+
await this._initPromise;
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
this._initPromise = (async () => {
|
|
1541
|
+
if (!options?.skipStoreInit) {
|
|
1542
|
+
await this._store.initialize();
|
|
1543
|
+
}
|
|
1544
|
+
await this._resolveAgentId();
|
|
1545
|
+
})();
|
|
1546
|
+
try {
|
|
1547
|
+
await this._initPromise;
|
|
1548
|
+
} finally {
|
|
1549
|
+
this._initPromise = null;
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
async _resolveAgentId() {
|
|
1553
|
+
if (this._agentId) {
|
|
1554
|
+
this._resolvedAgentId = this._agentId;
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1557
|
+
const persisted = await this._store.getMeta("agent_id");
|
|
1558
|
+
if (persisted) {
|
|
1559
|
+
this._resolvedAgentId = persisted;
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1562
|
+
const generated = (0, import_node_crypto2.randomUUID)().replace(/-/g, "");
|
|
1563
|
+
await this._store.setMeta("agent_id", generated);
|
|
1564
|
+
this._resolvedAgentId = generated;
|
|
1565
|
+
}
|
|
1566
|
+
_ensureInitialized() {
|
|
1567
|
+
if (!this._resolvedAgentId) {
|
|
1568
|
+
throw new Error("Client not initialized. Call init() first.");
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
/** Return only memories owned by this agent (excludes shared from others). */
|
|
1572
|
+
async _ownMemories() {
|
|
1573
|
+
return this._store.listAll({
|
|
1574
|
+
agentId: this._resolvedAgentId,
|
|
1575
|
+
includeShared: false
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
static _contentHash(content) {
|
|
1579
|
+
return (0, import_node_crypto2.createHash)("sha256").update(content).digest("hex");
|
|
1580
|
+
}
|
|
1581
|
+
/**
|
|
1582
|
+
* Store a memory with automatic classification and linking.
|
|
1583
|
+
* If identical content already exists for this agent, returns the existing memory.
|
|
1584
|
+
*/
|
|
1585
|
+
async add(content, options) {
|
|
1586
|
+
this._ensureInitialized();
|
|
1587
|
+
const contentHash = _Mnemonic._contentHash(content);
|
|
1588
|
+
const knownMemories = await this._store.listAll({ agentId: this._resolvedAgentId });
|
|
1589
|
+
for (const mem of knownMemories) {
|
|
1590
|
+
if (mem.agentId === this._resolvedAgentId && _Mnemonic._contentHash(mem.content) === contentHash) {
|
|
1591
|
+
return mem;
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
const source = options?.source ?? "unknown";
|
|
1595
|
+
const actor = options?.actor ?? "system";
|
|
1596
|
+
let resolvedTier;
|
|
1597
|
+
if (options?.tier != null) {
|
|
1598
|
+
resolvedTier = options.tier;
|
|
1599
|
+
} else if (this._autoClassify) {
|
|
1600
|
+
resolvedTier = classify(content, {
|
|
1601
|
+
source,
|
|
1602
|
+
actor,
|
|
1603
|
+
config: this._config
|
|
1604
|
+
});
|
|
1605
|
+
} else {
|
|
1606
|
+
resolvedTier = this._config.defaultTier;
|
|
1607
|
+
}
|
|
1608
|
+
const memory = new Memory({
|
|
1609
|
+
content,
|
|
1610
|
+
tier: resolvedTier,
|
|
1611
|
+
source,
|
|
1612
|
+
actor,
|
|
1613
|
+
sessionId: options?.sessionId ?? null,
|
|
1614
|
+
metadata: options?.metadata ?? {},
|
|
1615
|
+
agentId: this._resolvedAgentId,
|
|
1616
|
+
shared: options?.shared ?? false,
|
|
1617
|
+
charsPerToken: this._config.charsPerToken
|
|
1618
|
+
});
|
|
1619
|
+
if (this._embedder) {
|
|
1620
|
+
memory.embedding = await this._embedder.embed(content);
|
|
1621
|
+
}
|
|
1622
|
+
const salience = detectSalience(content, this._config.salience);
|
|
1623
|
+
if (salience.boost > 0) {
|
|
1624
|
+
memory.metadata["salience_signals"] = salience.signals;
|
|
1625
|
+
memory.metadata["boost"] = (memory.metadata["boost"] ?? 0) + salience.boost;
|
|
1626
|
+
}
|
|
1627
|
+
memory.importance = score(memory, this._config);
|
|
1628
|
+
const existing = await this._store.listAll({
|
|
1629
|
+
agentId: this._resolvedAgentId
|
|
1630
|
+
});
|
|
1631
|
+
const edges = findLinks(memory, existing, { config: this._config });
|
|
1632
|
+
await this._store.save(memory);
|
|
1633
|
+
for (const edge of edges) {
|
|
1634
|
+
memory.linkedIds.push(edge.targetId);
|
|
1635
|
+
await this._store.saveEdge(edge);
|
|
1636
|
+
const linked = await this._store.get(edge.targetId);
|
|
1637
|
+
if (linked) {
|
|
1638
|
+
if (linked.agentId === this._resolvedAgentId) {
|
|
1639
|
+
if (!linked.linkedIds.includes(memory.id)) {
|
|
1640
|
+
linked.linkedIds.push(memory.id);
|
|
1641
|
+
}
|
|
1642
|
+
if (edge.relation === "supersedes" && linked.supersededBy == null) {
|
|
1643
|
+
linked.supersededBy = memory.id;
|
|
1644
|
+
} else if (edge.relation === "contradicts" && linked.contradictedBy == null) {
|
|
1645
|
+
linked.contradictedBy = memory.id;
|
|
1646
|
+
}
|
|
1647
|
+
await this._store.save(linked);
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
await this._store.save(memory);
|
|
1652
|
+
return memory;
|
|
1653
|
+
}
|
|
1654
|
+
async _assemble(query, options) {
|
|
1655
|
+
let queryEmbedding = null;
|
|
1656
|
+
const limit = this._config.embeddingSearchLimit;
|
|
1657
|
+
let candidates;
|
|
1658
|
+
if (this._embedder) {
|
|
1659
|
+
queryEmbedding = await this._embedder.embed(query);
|
|
1660
|
+
candidates = await this._store.searchByEmbedding(
|
|
1661
|
+
queryEmbedding,
|
|
1662
|
+
limit,
|
|
1663
|
+
{ agentId: this._resolvedAgentId }
|
|
1664
|
+
);
|
|
1665
|
+
} else {
|
|
1666
|
+
candidates = await this._store.listAll({
|
|
1667
|
+
agentId: this._resolvedAgentId
|
|
1668
|
+
});
|
|
1669
|
+
}
|
|
1670
|
+
if (options?.excludeSuperseded) {
|
|
1671
|
+
candidates = candidates.filter((m) => m.supersededBy == null);
|
|
1672
|
+
}
|
|
1673
|
+
const selected = assemble(query, candidates, {
|
|
1674
|
+
queryEmbedding,
|
|
1675
|
+
maxTokens: options?.maxTokens,
|
|
1676
|
+
minRelevance: options?.minRelevance,
|
|
1677
|
+
config: this._config
|
|
1678
|
+
});
|
|
1679
|
+
await Promise.all(selected.map((mem) => this._store.save(mem)));
|
|
1680
|
+
return selected;
|
|
1681
|
+
}
|
|
1682
|
+
/** Retrieve optimised context for a given query within a token budget. */
|
|
1683
|
+
async recall(query, options) {
|
|
1684
|
+
this._ensureInitialized();
|
|
1685
|
+
const selected = await this._assemble(query, {
|
|
1686
|
+
...options,
|
|
1687
|
+
excludeSuperseded: true
|
|
1688
|
+
});
|
|
1689
|
+
const conflicts = detectConflicts(selected, this._config);
|
|
1690
|
+
return formatContext(selected, options?.header, conflicts);
|
|
1691
|
+
}
|
|
1692
|
+
/** Like recall but returns the raw Memory objects. */
|
|
1693
|
+
async recallMemories(query, options) {
|
|
1694
|
+
this._ensureInitialized();
|
|
1695
|
+
return this._assemble(query, options);
|
|
1696
|
+
}
|
|
1697
|
+
/** Delete memories matching the given criteria. Returns count deleted. */
|
|
1698
|
+
async forget(options) {
|
|
1699
|
+
this._ensureInitialized();
|
|
1700
|
+
if (!options?.olderThan && !options?.tier && options?.belowImportance == null) {
|
|
1701
|
+
throw new Error(
|
|
1702
|
+
"forget() requires at least one filter (olderThan, tier, or belowImportance)"
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1705
|
+
const tier = options?.tier;
|
|
1706
|
+
if (options?.belowImportance == null) {
|
|
1707
|
+
const olderThan = options?.olderThan?.trim();
|
|
1708
|
+
if (olderThan === "") {
|
|
1709
|
+
throw new Error("olderThan must be a valid duration string (e.g. '30d'), not empty");
|
|
1710
|
+
}
|
|
1711
|
+
const ageMs = olderThan ? parseDuration(olderThan) : 0;
|
|
1712
|
+
return this._store.deleteOlderThan(ageMs, tier, {
|
|
1713
|
+
agentId: this._resolvedAgentId
|
|
1714
|
+
});
|
|
1715
|
+
}
|
|
1716
|
+
let candidates = await this._store.listAll({
|
|
1717
|
+
agentId: this._resolvedAgentId,
|
|
1718
|
+
includeShared: false,
|
|
1719
|
+
tier: tier ?? void 0
|
|
1720
|
+
});
|
|
1721
|
+
if (options?.olderThan != null) {
|
|
1722
|
+
const ageMs = parseDuration(options.olderThan);
|
|
1723
|
+
const cutoff = Date.now() - ageMs;
|
|
1724
|
+
candidates = candidates.filter((m) => m.createdAt.getTime() < cutoff);
|
|
1725
|
+
}
|
|
1726
|
+
let deleted = 0;
|
|
1727
|
+
for (const mem of candidates) {
|
|
1728
|
+
const currentImportance = score(mem, this._config);
|
|
1729
|
+
if (currentImportance < options.belowImportance) {
|
|
1730
|
+
await this._store.delete(mem.id);
|
|
1731
|
+
deleted++;
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
return deleted;
|
|
1735
|
+
}
|
|
1736
|
+
/**
|
|
1737
|
+
* Merge similar episodic memories into higher-tier summaries.
|
|
1738
|
+
*
|
|
1739
|
+
* Originals are kept alive but deprioritised via a scoring penalty
|
|
1740
|
+
* and `consolidated_into` metadata. A `consolidates` edge links
|
|
1741
|
+
* the summary to each original for graph traversal.
|
|
1742
|
+
*/
|
|
1743
|
+
async consolidate() {
|
|
1744
|
+
this._ensureInitialized();
|
|
1745
|
+
const ownEpisodic = await this._store.listAll({
|
|
1746
|
+
agentId: this._resolvedAgentId,
|
|
1747
|
+
includeShared: false,
|
|
1748
|
+
tier: MemoryTier.EPISODIC
|
|
1749
|
+
});
|
|
1750
|
+
const eligible = [];
|
|
1751
|
+
for (const m of ownEpisodic) {
|
|
1752
|
+
const summaryId = m.metadata["consolidated_into"];
|
|
1753
|
+
if (summaryId == null || await this._store.get(summaryId) == null) {
|
|
1754
|
+
eligible.push(m);
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
const privateMemories = eligible.filter((m) => !m.shared);
|
|
1758
|
+
const sharedMemories = eligible.filter((m) => m.shared);
|
|
1759
|
+
const cfg = this._config.consolidation;
|
|
1760
|
+
const eligibleIds = new Set(eligible.map((m) => m.id));
|
|
1761
|
+
const allPairs = await this._store.findSimilarPairs({
|
|
1762
|
+
threshold: cfg.similarityThreshold,
|
|
1763
|
+
agentId: this._resolvedAgentId,
|
|
1764
|
+
tier: MemoryTier.EPISODIC,
|
|
1765
|
+
maxCandidates: cfg.maxCandidates,
|
|
1766
|
+
maxNeighborsPerCandidate: cfg.maxNeighborsPerCandidate
|
|
1767
|
+
});
|
|
1768
|
+
const eligiblePairs = allPairs.filter(
|
|
1769
|
+
(p) => eligibleIds.has(p.idA) && eligibleIds.has(p.idB)
|
|
1770
|
+
);
|
|
1771
|
+
const privateIds = new Set(privateMemories.map((m) => m.id));
|
|
1772
|
+
const sharedIds = new Set(sharedMemories.map((m) => m.id));
|
|
1773
|
+
const privatePairs = eligiblePairs.filter(
|
|
1774
|
+
(p) => privateIds.has(p.idA) && privateIds.has(p.idB)
|
|
1775
|
+
);
|
|
1776
|
+
const sharedPairs = eligiblePairs.filter(
|
|
1777
|
+
(p) => sharedIds.has(p.idA) && sharedIds.has(p.idB)
|
|
1778
|
+
);
|
|
1779
|
+
let totalClusters = 0;
|
|
1780
|
+
for (const [memories, pairs, isShared] of [
|
|
1781
|
+
[privateMemories, privatePairs, false],
|
|
1782
|
+
[sharedMemories, sharedPairs, true]
|
|
1783
|
+
]) {
|
|
1784
|
+
const clusters = findConsolidationCandidates(memories, this._config, pairs);
|
|
1785
|
+
const CONCURRENCY = 8;
|
|
1786
|
+
const prepared = [];
|
|
1787
|
+
const processBatch = async (batch) => {
|
|
1788
|
+
const results = await Promise.all(
|
|
1789
|
+
batch.map(async (cluster) => {
|
|
1790
|
+
const summary = await mergeCluster(cluster, this._config);
|
|
1791
|
+
summary.agentId = this._resolvedAgentId;
|
|
1792
|
+
summary.shared = isShared;
|
|
1793
|
+
for (const mem of cluster) {
|
|
1794
|
+
if (!summary.linkedIds.includes(mem.id)) {
|
|
1795
|
+
summary.linkedIds.push(mem.id);
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
if (this._embedder) {
|
|
1799
|
+
summary.embedding = await this._embedder.embed(summary.content);
|
|
1800
|
+
}
|
|
1801
|
+
summary.importance = score(summary, this._config);
|
|
1802
|
+
return { cluster, summary };
|
|
1803
|
+
})
|
|
1804
|
+
);
|
|
1805
|
+
prepared.push(...results);
|
|
1806
|
+
};
|
|
1807
|
+
for (let i = 0; i < clusters.length; i += CONCURRENCY) {
|
|
1808
|
+
await processBatch(clusters.slice(i, i + CONCURRENCY));
|
|
1809
|
+
}
|
|
1810
|
+
for (const { cluster, summary } of prepared) {
|
|
1811
|
+
await this._store.save(summary);
|
|
1812
|
+
for (const mem of cluster) {
|
|
1813
|
+
const edge = createEdge(summary.id, mem.id, "consolidates");
|
|
1814
|
+
await this._store.saveEdge(edge);
|
|
1815
|
+
mem.metadata["consolidated_into"] = summary.id;
|
|
1816
|
+
mem.linkedIds.push(summary.id);
|
|
1817
|
+
mem.importance = score(mem, this._config);
|
|
1818
|
+
await this._store.save(mem);
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
totalClusters += clusters.length;
|
|
1822
|
+
}
|
|
1823
|
+
return totalClusters;
|
|
1824
|
+
}
|
|
1825
|
+
/** Run the tier promotion/demotion pipeline on own memories. */
|
|
1826
|
+
async promote() {
|
|
1827
|
+
this._ensureInitialized();
|
|
1828
|
+
const ownMemories = await this._ownMemories();
|
|
1829
|
+
let changes = await this._applyTierResults(
|
|
1830
|
+
checkPromotions(ownMemories, this._config)
|
|
1831
|
+
);
|
|
1832
|
+
const refreshed = await this._ownMemories();
|
|
1833
|
+
changes += await this._applyTierResults(
|
|
1834
|
+
checkDemotions(refreshed, this._config)
|
|
1835
|
+
);
|
|
1836
|
+
return changes;
|
|
1837
|
+
}
|
|
1838
|
+
/** Re-run the linker across all memories to retroactively set edges. */
|
|
1839
|
+
async relink() {
|
|
1840
|
+
this._ensureInitialized();
|
|
1841
|
+
const ownMemories = await this._ownMemories();
|
|
1842
|
+
const visible = await this._store.listAll({ agentId: this._resolvedAgentId });
|
|
1843
|
+
const ownById = new Map(ownMemories.map((m) => [m.id, m]));
|
|
1844
|
+
const existingRelations = /* @__PURE__ */ new Map();
|
|
1845
|
+
for (const mem of ownMemories) {
|
|
1846
|
+
const edges = await this._store.getEdges(mem.id);
|
|
1847
|
+
for (const e of edges) existingRelations.set(`${e.sourceId}:${e.targetId}`, e.relation);
|
|
1848
|
+
}
|
|
1849
|
+
let newEdges = 0;
|
|
1850
|
+
for (const mem of ownMemories) {
|
|
1851
|
+
const edges = findLinks(mem, visible, { config: this._config });
|
|
1852
|
+
for (const edge of edges) {
|
|
1853
|
+
const key = `${edge.sourceId}:${edge.targetId}`;
|
|
1854
|
+
const existingRel = existingRelations.get(key);
|
|
1855
|
+
if (existingRel == null) {
|
|
1856
|
+
await this._store.saveEdge(edge);
|
|
1857
|
+
existingRelations.set(key, edge.relation);
|
|
1858
|
+
newEdges++;
|
|
1859
|
+
} else if (existingRel !== edge.relation) {
|
|
1860
|
+
await this._store.saveEdge(edge);
|
|
1861
|
+
existingRelations.set(key, edge.relation);
|
|
1862
|
+
}
|
|
1863
|
+
if (edge.targetId !== mem.id && !mem.linkedIds.includes(edge.targetId)) {
|
|
1864
|
+
mem.linkedIds.push(edge.targetId);
|
|
1865
|
+
}
|
|
1866
|
+
const linked = ownById.get(edge.targetId);
|
|
1867
|
+
if (linked) {
|
|
1868
|
+
if (!linked.linkedIds.includes(mem.id)) linked.linkedIds.push(mem.id);
|
|
1869
|
+
if (edge.relation === "supersedes" && linked.supersededBy == null) {
|
|
1870
|
+
linked.supersededBy = mem.id;
|
|
1871
|
+
} else if (edge.relation === "contradicts" && linked.contradictedBy == null) {
|
|
1872
|
+
linked.contradictedBy = mem.id;
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
await Promise.all(ownMemories.map((m) => this._store.save(m)));
|
|
1878
|
+
return newEdges;
|
|
1879
|
+
}
|
|
1880
|
+
/** Return aggregate statistics about own memories. */
|
|
1881
|
+
async stats() {
|
|
1882
|
+
this._ensureInitialized();
|
|
1883
|
+
const memories = await this._ownMemories();
|
|
1884
|
+
const byTier = {};
|
|
1885
|
+
let totalTokens = 0;
|
|
1886
|
+
for (const m of memories) {
|
|
1887
|
+
byTier[m.tierName] = (byTier[m.tierName] ?? 0) + 1;
|
|
1888
|
+
totalTokens += m.tokenEstimate;
|
|
1889
|
+
}
|
|
1890
|
+
return {
|
|
1891
|
+
totalMemories: memories.length,
|
|
1892
|
+
totalTokens,
|
|
1893
|
+
byTier,
|
|
1894
|
+
agentId: this._resolvedAgentId
|
|
1895
|
+
};
|
|
1896
|
+
}
|
|
1897
|
+
/** Make a memory visible to all agents. */
|
|
1898
|
+
async share(memoryId) {
|
|
1899
|
+
this._ensureInitialized();
|
|
1900
|
+
const memory = await this._store.get(memoryId);
|
|
1901
|
+
if (!memory) {
|
|
1902
|
+
throw new Error(`Memory not found: ${memoryId}`);
|
|
1903
|
+
}
|
|
1904
|
+
if (memory.agentId !== this._resolvedAgentId) {
|
|
1905
|
+
throw new Error("Cannot share another agent's memory");
|
|
1906
|
+
}
|
|
1907
|
+
memory.shared = true;
|
|
1908
|
+
await this._store.save(memory);
|
|
1909
|
+
return memory;
|
|
1910
|
+
}
|
|
1911
|
+
/** Make a memory private (only visible to owner). */
|
|
1912
|
+
async unshare(memoryId) {
|
|
1913
|
+
this._ensureInitialized();
|
|
1914
|
+
const memory = await this._store.get(memoryId);
|
|
1915
|
+
if (!memory) {
|
|
1916
|
+
throw new Error(`Memory not found: ${memoryId}`);
|
|
1917
|
+
}
|
|
1918
|
+
if (memory.agentId !== this._resolvedAgentId) {
|
|
1919
|
+
throw new Error("Cannot unshare another agent's memory");
|
|
1920
|
+
}
|
|
1921
|
+
memory.shared = false;
|
|
1922
|
+
await this._store.save(memory);
|
|
1923
|
+
return memory;
|
|
1924
|
+
}
|
|
1925
|
+
async _applyTierResults(results) {
|
|
1926
|
+
let applied = 0;
|
|
1927
|
+
for (const result of results) {
|
|
1928
|
+
const mem = await this._store.get(result.memoryId);
|
|
1929
|
+
if (!mem) continue;
|
|
1930
|
+
applyTierChange(mem, result.newTier);
|
|
1931
|
+
mem.importance = score(mem, this._config);
|
|
1932
|
+
await this._store.save(mem);
|
|
1933
|
+
applied++;
|
|
1934
|
+
}
|
|
1935
|
+
return applied;
|
|
1936
|
+
}
|
|
1937
|
+
};
|
|
1938
|
+
function _wrapIfSync(store) {
|
|
1939
|
+
if (store instanceof SQLiteStore) {
|
|
1940
|
+
return syncToAsync(store);
|
|
1941
|
+
}
|
|
1942
|
+
return store;
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
// src/stores/postgres.ts
|
|
1946
|
+
var _pgModule = null;
|
|
1947
|
+
var _registerPgvector = null;
|
|
1948
|
+
async function loadPg() {
|
|
1949
|
+
if (_pgModule) return _pgModule;
|
|
1950
|
+
try {
|
|
1951
|
+
_pgModule = await import("pg");
|
|
1952
|
+
return _pgModule;
|
|
1953
|
+
} catch {
|
|
1954
|
+
throw new Error(
|
|
1955
|
+
"PostgresStore requires the 'pg' package. Install it with: npm install pg pgvector"
|
|
1956
|
+
);
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
async function loadPgvectorRegister() {
|
|
1960
|
+
if (_registerPgvector) return _registerPgvector;
|
|
1961
|
+
try {
|
|
1962
|
+
const mod = await import("pgvector/pg");
|
|
1963
|
+
_registerPgvector = mod.registerTypes ?? mod.default?.registerTypes ?? mod.registerType ?? mod.default?.registerType;
|
|
1964
|
+
return _registerPgvector;
|
|
1965
|
+
} catch {
|
|
1966
|
+
throw new Error(
|
|
1967
|
+
"PostgresStore requires the 'pgvector' package. Install it with: npm install pgvector"
|
|
1968
|
+
);
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
var PostgresStore = class {
|
|
1972
|
+
_dsn;
|
|
1973
|
+
_embeddingDim;
|
|
1974
|
+
_poolMin;
|
|
1975
|
+
_poolMax;
|
|
1976
|
+
_pool = null;
|
|
1977
|
+
_pgvectorRegistered = false;
|
|
1978
|
+
constructor(options) {
|
|
1979
|
+
this._dsn = options.dsn;
|
|
1980
|
+
this._embeddingDim = options.embeddingDim ?? 1536;
|
|
1981
|
+
this._poolMin = options.poolMin ?? 2;
|
|
1982
|
+
this._poolMax = options.poolMax ?? 10;
|
|
1983
|
+
}
|
|
1984
|
+
async _getPool() {
|
|
1985
|
+
if (this._pool) return this._pool;
|
|
1986
|
+
const pg = await loadPg();
|
|
1987
|
+
this._pool = new pg.Pool({
|
|
1988
|
+
connectionString: this._dsn,
|
|
1989
|
+
min: this._poolMin,
|
|
1990
|
+
max: this._poolMax
|
|
1991
|
+
});
|
|
1992
|
+
return this._pool;
|
|
1993
|
+
}
|
|
1994
|
+
async _query(sql, params = []) {
|
|
1995
|
+
const pool = await this._getPool();
|
|
1996
|
+
const client = await pool.connect();
|
|
1997
|
+
try {
|
|
1998
|
+
return await client.query(sql, params);
|
|
1999
|
+
} finally {
|
|
2000
|
+
client.release();
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
/** Register pgvector on the pool's connect event so every connection gets it. */
|
|
2004
|
+
async _setupPgvectorOnPool() {
|
|
2005
|
+
if (this._pgvectorRegistered) return;
|
|
2006
|
+
const pool = await this._getPool();
|
|
2007
|
+
const register = await loadPgvectorRegister();
|
|
2008
|
+
pool.on("connect", (client2) => {
|
|
2009
|
+
register(client2).catch((err) => {
|
|
2010
|
+
process.stderr.write(`pgvector registration failed: ${err.message}
|
|
2011
|
+
`);
|
|
2012
|
+
});
|
|
2013
|
+
});
|
|
2014
|
+
const client = await pool.connect();
|
|
2015
|
+
try {
|
|
2016
|
+
await register(client);
|
|
2017
|
+
} finally {
|
|
2018
|
+
client.release();
|
|
2019
|
+
}
|
|
2020
|
+
this._pgvectorRegistered = true;
|
|
2021
|
+
}
|
|
2022
|
+
async initialize() {
|
|
2023
|
+
await this._query("CREATE EXTENSION IF NOT EXISTS vector");
|
|
2024
|
+
await this._setupPgvectorOnPool();
|
|
2025
|
+
await this._query(`
|
|
2026
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
2027
|
+
id TEXT PRIMARY KEY,
|
|
2028
|
+
content TEXT NOT NULL,
|
|
2029
|
+
tier TEXT NOT NULL,
|
|
2030
|
+
source TEXT NOT NULL DEFAULT 'unknown',
|
|
2031
|
+
actor TEXT NOT NULL DEFAULT 'system',
|
|
2032
|
+
session_id TEXT,
|
|
2033
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
2034
|
+
last_accessed TIMESTAMPTZ NOT NULL,
|
|
2035
|
+
access_count INTEGER NOT NULL DEFAULT 0,
|
|
2036
|
+
importance DOUBLE PRECISION NOT NULL DEFAULT 1.0,
|
|
2037
|
+
superseded_by TEXT,
|
|
2038
|
+
contradicted_by TEXT,
|
|
2039
|
+
embedding vector(${this._embeddingDim}),
|
|
2040
|
+
metadata JSONB NOT NULL DEFAULT '{}',
|
|
2041
|
+
linked_ids JSONB NOT NULL DEFAULT '[]',
|
|
2042
|
+
agent_id TEXT NOT NULL DEFAULT 'default',
|
|
2043
|
+
shared BOOLEAN NOT NULL DEFAULT FALSE
|
|
2044
|
+
)
|
|
2045
|
+
`);
|
|
2046
|
+
await this._query(`
|
|
2047
|
+
CREATE TABLE IF NOT EXISTS edges (
|
|
2048
|
+
source_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
|
|
2049
|
+
target_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
|
|
2050
|
+
relation TEXT NOT NULL,
|
|
2051
|
+
weight DOUBLE PRECISION NOT NULL DEFAULT 1.0,
|
|
2052
|
+
created_at TIMESTAMPTZ NOT NULL,
|
|
2053
|
+
PRIMARY KEY (source_id, target_id, relation)
|
|
2054
|
+
)
|
|
2055
|
+
`);
|
|
2056
|
+
await this._query(`
|
|
2057
|
+
CREATE TABLE IF NOT EXISTS meta (
|
|
2058
|
+
key TEXT PRIMARY KEY,
|
|
2059
|
+
value TEXT NOT NULL
|
|
2060
|
+
)
|
|
2061
|
+
`);
|
|
2062
|
+
await this._migrateColumns();
|
|
2063
|
+
await this._query(
|
|
2064
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_agent_id ON memories (agent_id)"
|
|
2065
|
+
);
|
|
2066
|
+
await this._query(
|
|
2067
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories (tier)"
|
|
2068
|
+
);
|
|
2069
|
+
await this._query(
|
|
2070
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_created_at ON memories (created_at)"
|
|
2071
|
+
);
|
|
2072
|
+
await this._query(
|
|
2073
|
+
"CREATE INDEX IF NOT EXISTS idx_edges_source ON edges (source_id)"
|
|
2074
|
+
);
|
|
2075
|
+
await this._query(
|
|
2076
|
+
"CREATE INDEX IF NOT EXISTS idx_edges_target ON edges (target_id)"
|
|
2077
|
+
);
|
|
2078
|
+
await this._query(
|
|
2079
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_shared ON memories (shared) WHERE shared = TRUE"
|
|
2080
|
+
);
|
|
2081
|
+
try {
|
|
2082
|
+
await this._query(`
|
|
2083
|
+
CREATE INDEX IF NOT EXISTS idx_memories_embedding_hnsw
|
|
2084
|
+
ON memories USING hnsw (embedding vector_cosine_ops)
|
|
2085
|
+
WITH (m = 16, ef_construction = 64)
|
|
2086
|
+
`);
|
|
2087
|
+
} catch {
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
async _migrateColumns() {
|
|
2091
|
+
await this._query(
|
|
2092
|
+
"ALTER TABLE memories ADD COLUMN IF NOT EXISTS agent_id TEXT NOT NULL DEFAULT 'default'"
|
|
2093
|
+
);
|
|
2094
|
+
await this._query(
|
|
2095
|
+
"ALTER TABLE memories ADD COLUMN IF NOT EXISTS shared BOOLEAN NOT NULL DEFAULT FALSE"
|
|
2096
|
+
);
|
|
2097
|
+
await this._query(
|
|
2098
|
+
"ALTER TABLE memories ADD COLUMN IF NOT EXISTS contradicted_by TEXT"
|
|
2099
|
+
);
|
|
2100
|
+
await this._query(
|
|
2101
|
+
"UPDATE memories SET contradicted_by = metadata->>'contradicted_by' WHERE contradicted_by IS NULL AND metadata->>'contradicted_by' IS NOT NULL"
|
|
2102
|
+
);
|
|
2103
|
+
for (const col of ["source_id", "target_id"]) {
|
|
2104
|
+
const name = `edges_${col}_fkey`;
|
|
2105
|
+
try {
|
|
2106
|
+
await this._query(
|
|
2107
|
+
`ALTER TABLE edges ADD CONSTRAINT ${name} FOREIGN KEY (${col}) REFERENCES memories(id) ON DELETE CASCADE`
|
|
2108
|
+
);
|
|
2109
|
+
} catch (err) {
|
|
2110
|
+
const code = err.code;
|
|
2111
|
+
if (code !== "42710") throw err;
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
async close() {
|
|
2116
|
+
if (this._pool) {
|
|
2117
|
+
await this._pool.end();
|
|
2118
|
+
this._pool = null;
|
|
2119
|
+
this._pgvectorRegistered = false;
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
async save(memory) {
|
|
2123
|
+
await this._query(
|
|
2124
|
+
`INSERT INTO memories
|
|
2125
|
+
(id, content, tier, source, actor, session_id, created_at,
|
|
2126
|
+
last_accessed, access_count, importance, superseded_by,
|
|
2127
|
+
contradicted_by, embedding, metadata, linked_ids, agent_id, shared)
|
|
2128
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13::vector, $14, $15, $16, $17)
|
|
2129
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
2130
|
+
content = EXCLUDED.content,
|
|
2131
|
+
tier = EXCLUDED.tier,
|
|
2132
|
+
source = EXCLUDED.source,
|
|
2133
|
+
actor = EXCLUDED.actor,
|
|
2134
|
+
session_id = EXCLUDED.session_id,
|
|
2135
|
+
last_accessed = EXCLUDED.last_accessed,
|
|
2136
|
+
access_count = EXCLUDED.access_count,
|
|
2137
|
+
importance = EXCLUDED.importance,
|
|
2138
|
+
superseded_by = EXCLUDED.superseded_by,
|
|
2139
|
+
contradicted_by = EXCLUDED.contradicted_by,
|
|
2140
|
+
embedding = EXCLUDED.embedding,
|
|
2141
|
+
metadata = EXCLUDED.metadata,
|
|
2142
|
+
linked_ids = EXCLUDED.linked_ids,
|
|
2143
|
+
agent_id = EXCLUDED.agent_id,
|
|
2144
|
+
shared = EXCLUDED.shared`,
|
|
2145
|
+
[
|
|
2146
|
+
memory.id,
|
|
2147
|
+
memory.content,
|
|
2148
|
+
memory.tierName,
|
|
2149
|
+
memory.source,
|
|
2150
|
+
memory.actor,
|
|
2151
|
+
memory.sessionId,
|
|
2152
|
+
memory.createdAt.toISOString(),
|
|
2153
|
+
memory.lastAccessedAt.toISOString(),
|
|
2154
|
+
memory.accessCount,
|
|
2155
|
+
memory.importance,
|
|
2156
|
+
memory.supersededBy,
|
|
2157
|
+
memory.contradictedBy ?? null,
|
|
2158
|
+
memory.embedding ? `[${memory.embedding.join(",")}]` : null,
|
|
2159
|
+
JSON.stringify(memory.metadata),
|
|
2160
|
+
JSON.stringify(memory.linkedIds),
|
|
2161
|
+
memory.agentId,
|
|
2162
|
+
memory.shared
|
|
2163
|
+
]
|
|
2164
|
+
);
|
|
2165
|
+
}
|
|
2166
|
+
async get(memoryId) {
|
|
2167
|
+
const result = await this._query(
|
|
2168
|
+
"SELECT * FROM memories WHERE id = $1",
|
|
2169
|
+
[memoryId]
|
|
2170
|
+
);
|
|
2171
|
+
if (result.rows.length === 0) return null;
|
|
2172
|
+
return rowToMemory2(result.rows[0]);
|
|
2173
|
+
}
|
|
2174
|
+
async listAll(options) {
|
|
2175
|
+
const clauses = [];
|
|
2176
|
+
const params = [];
|
|
2177
|
+
let paramIdx = 1;
|
|
2178
|
+
if (options?.agentId) {
|
|
2179
|
+
if (options.includeShared === false) {
|
|
2180
|
+
clauses.push(`agent_id = $${paramIdx}`);
|
|
2181
|
+
params.push(options.agentId);
|
|
2182
|
+
paramIdx++;
|
|
2183
|
+
} else {
|
|
2184
|
+
clauses.push(`(agent_id = $${paramIdx} OR shared = TRUE)`);
|
|
2185
|
+
params.push(options.agentId);
|
|
2186
|
+
paramIdx++;
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
if (options?.tier != null) {
|
|
2190
|
+
clauses.push(`tier = $${paramIdx}`);
|
|
2191
|
+
params.push(options.tier);
|
|
2192
|
+
paramIdx++;
|
|
2193
|
+
}
|
|
2194
|
+
const where = clauses.length > 0 ? ` WHERE ${clauses.join(" AND ")}` : "";
|
|
2195
|
+
let limitClause = "";
|
|
2196
|
+
if (options?.limit != null) {
|
|
2197
|
+
limitClause = ` LIMIT $${paramIdx}`;
|
|
2198
|
+
params.push(options.limit);
|
|
2199
|
+
paramIdx++;
|
|
2200
|
+
}
|
|
2201
|
+
const result = await this._query(
|
|
2202
|
+
`SELECT * FROM memories${where}${limitClause}`,
|
|
2203
|
+
params
|
|
2204
|
+
);
|
|
2205
|
+
return result.rows.map(rowToMemory2);
|
|
2206
|
+
}
|
|
2207
|
+
async delete(memoryId) {
|
|
2208
|
+
await this._query("DELETE FROM memories WHERE id = $1", [memoryId]);
|
|
2209
|
+
}
|
|
2210
|
+
async deleteOlderThan(ageMs, tier, options) {
|
|
2211
|
+
const cutoff = new Date(Date.now() - ageMs).toISOString();
|
|
2212
|
+
const conditions = ["created_at < $1"];
|
|
2213
|
+
const params = [cutoff];
|
|
2214
|
+
let paramIdx = 2;
|
|
2215
|
+
if (tier != null) {
|
|
2216
|
+
conditions.push(`tier = $${paramIdx}`);
|
|
2217
|
+
params.push(tier);
|
|
2218
|
+
paramIdx++;
|
|
2219
|
+
}
|
|
2220
|
+
if (options?.agentId) {
|
|
2221
|
+
conditions.push(`agent_id = $${paramIdx}`);
|
|
2222
|
+
params.push(options.agentId);
|
|
2223
|
+
paramIdx++;
|
|
2224
|
+
}
|
|
2225
|
+
const where = conditions.join(" AND ");
|
|
2226
|
+
const result = await this._query(
|
|
2227
|
+
`DELETE FROM memories WHERE ${where}`,
|
|
2228
|
+
params
|
|
2229
|
+
);
|
|
2230
|
+
return result.rowCount ?? 0;
|
|
2231
|
+
}
|
|
2232
|
+
async saveEdge(edge) {
|
|
2233
|
+
await this._query(
|
|
2234
|
+
`INSERT INTO edges (source_id, target_id, relation, weight, created_at)
|
|
2235
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
2236
|
+
ON CONFLICT (source_id, target_id, relation) DO UPDATE SET
|
|
2237
|
+
weight = EXCLUDED.weight,
|
|
2238
|
+
created_at = EXCLUDED.created_at`,
|
|
2239
|
+
[
|
|
2240
|
+
edge.sourceId,
|
|
2241
|
+
edge.targetId,
|
|
2242
|
+
edge.relation,
|
|
2243
|
+
edge.weight,
|
|
2244
|
+
edge.createdAt.toISOString()
|
|
2245
|
+
]
|
|
2246
|
+
);
|
|
2247
|
+
}
|
|
2248
|
+
async getEdges(memoryId) {
|
|
2249
|
+
const result = await this._query(
|
|
2250
|
+
"SELECT * FROM edges WHERE source_id = $1 OR target_id = $1",
|
|
2251
|
+
[memoryId]
|
|
2252
|
+
);
|
|
2253
|
+
return result.rows.map(
|
|
2254
|
+
(r) => ({
|
|
2255
|
+
sourceId: r["source_id"],
|
|
2256
|
+
targetId: r["target_id"],
|
|
2257
|
+
relation: r["relation"],
|
|
2258
|
+
weight: r["weight"],
|
|
2259
|
+
createdAt: new Date(r["created_at"])
|
|
2260
|
+
})
|
|
2261
|
+
);
|
|
2262
|
+
}
|
|
2263
|
+
async searchByEmbedding(embedding, limit = 50, options) {
|
|
2264
|
+
try {
|
|
2265
|
+
const embeddingStr = `[${embedding.join(",")}]`;
|
|
2266
|
+
if (options?.agentId) {
|
|
2267
|
+
const result2 = await this._query(
|
|
2268
|
+
`SELECT * FROM memories
|
|
2269
|
+
WHERE (agent_id = $1 OR shared = TRUE) AND embedding IS NOT NULL
|
|
2270
|
+
ORDER BY embedding <=> $2::vector
|
|
2271
|
+
LIMIT $3`,
|
|
2272
|
+
[options.agentId, embeddingStr, limit]
|
|
2273
|
+
);
|
|
2274
|
+
return result2.rows.map(rowToMemory2);
|
|
2275
|
+
}
|
|
2276
|
+
const result = await this._query(
|
|
2277
|
+
`SELECT * FROM memories
|
|
2278
|
+
WHERE embedding IS NOT NULL
|
|
2279
|
+
ORDER BY embedding <=> $1::vector
|
|
2280
|
+
LIMIT $2`,
|
|
2281
|
+
[embeddingStr, limit]
|
|
2282
|
+
);
|
|
2283
|
+
return result.rows.map(rowToMemory2);
|
|
2284
|
+
} catch (err) {
|
|
2285
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2286
|
+
if (msg.includes("operator") || msg.includes("vector") || msg.includes("does not exist")) {
|
|
2287
|
+
const all = await this.listAll(options);
|
|
2288
|
+
return bruteForceCosineSearch(all, embedding, limit);
|
|
2289
|
+
}
|
|
2290
|
+
throw err;
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
async findSimilarPairs(options) {
|
|
2294
|
+
const threshold = options?.threshold ?? 0.7;
|
|
2295
|
+
const maxCandidates = options?.maxCandidates ?? 1e3;
|
|
2296
|
+
const maxNeighbors = options?.maxNeighborsPerCandidate ?? 50;
|
|
2297
|
+
const distanceThreshold = 1 - threshold;
|
|
2298
|
+
const conditions = ["embedding IS NOT NULL"];
|
|
2299
|
+
const params = [];
|
|
2300
|
+
let paramIdx = 1;
|
|
2301
|
+
if (options?.agentId) {
|
|
2302
|
+
conditions.push(`agent_id = $${paramIdx}`);
|
|
2303
|
+
params.push(options.agentId);
|
|
2304
|
+
paramIdx++;
|
|
2305
|
+
}
|
|
2306
|
+
if (options?.tier != null) {
|
|
2307
|
+
conditions.push(`tier = $${paramIdx}`);
|
|
2308
|
+
params.push(options.tier);
|
|
2309
|
+
paramIdx++;
|
|
2310
|
+
}
|
|
2311
|
+
params.push(maxCandidates);
|
|
2312
|
+
const limitIdx = paramIdx++;
|
|
2313
|
+
params.push(distanceThreshold);
|
|
2314
|
+
const distIdx = paramIdx++;
|
|
2315
|
+
params.push(threshold);
|
|
2316
|
+
const simIdx = paramIdx++;
|
|
2317
|
+
params.push(maxNeighbors);
|
|
2318
|
+
const neighborLimitIdx = paramIdx++;
|
|
2319
|
+
const where = conditions.join(" AND ");
|
|
2320
|
+
const sql = `
|
|
2321
|
+
WITH candidates AS (
|
|
2322
|
+
SELECT id, embedding
|
|
2323
|
+
FROM memories
|
|
2324
|
+
WHERE ${where}
|
|
2325
|
+
LIMIT $${limitIdx}
|
|
2326
|
+
)
|
|
2327
|
+
SELECT c1.id AS id_a, c2_sub.id AS id_b, c2_sub.sim AS similarity
|
|
2328
|
+
FROM candidates c1
|
|
2329
|
+
CROSS JOIN LATERAL (
|
|
2330
|
+
SELECT c2.id, 1.0 - (c1.embedding <=> c2.embedding) AS sim
|
|
2331
|
+
FROM candidates c2
|
|
2332
|
+
WHERE c2.id > c1.id
|
|
2333
|
+
AND c1.embedding <=> c2.embedding <= $${distIdx}
|
|
2334
|
+
ORDER BY c1.embedding <=> c2.embedding
|
|
2335
|
+
LIMIT $${neighborLimitIdx}
|
|
2336
|
+
) c2_sub
|
|
2337
|
+
WHERE c2_sub.sim >= $${simIdx}
|
|
2338
|
+
`;
|
|
2339
|
+
try {
|
|
2340
|
+
const result = await this._query(sql, params);
|
|
2341
|
+
return result.rows.map(
|
|
2342
|
+
(r) => ({
|
|
2343
|
+
idA: r["id_a"],
|
|
2344
|
+
idB: r["id_b"],
|
|
2345
|
+
similarity: r["similarity"]
|
|
2346
|
+
})
|
|
2347
|
+
);
|
|
2348
|
+
} catch (err) {
|
|
2349
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2350
|
+
if (msg.includes("operator") || msg.includes("vector") || msg.includes("does not exist")) {
|
|
2351
|
+
const all = await this.listAll({
|
|
2352
|
+
agentId: options?.agentId,
|
|
2353
|
+
includeShared: false,
|
|
2354
|
+
tier: options?.tier,
|
|
2355
|
+
limit: maxCandidates
|
|
2356
|
+
});
|
|
2357
|
+
return bruteForceSimilarPairs(all, threshold, maxCandidates);
|
|
2358
|
+
}
|
|
2359
|
+
throw err;
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
async getMeta(key) {
|
|
2363
|
+
const result = await this._query(
|
|
2364
|
+
"SELECT value FROM meta WHERE key = $1",
|
|
2365
|
+
[key]
|
|
2366
|
+
);
|
|
2367
|
+
return result.rows.length > 0 ? result.rows[0].value : null;
|
|
2368
|
+
}
|
|
2369
|
+
async setMeta(key, value) {
|
|
2370
|
+
await this._query(
|
|
2371
|
+
`INSERT INTO meta (key, value) VALUES ($1, $2)
|
|
2372
|
+
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value`,
|
|
2373
|
+
[key, value]
|
|
2374
|
+
);
|
|
2375
|
+
}
|
|
2376
|
+
};
|
|
2377
|
+
function rowToMemory2(row) {
|
|
2378
|
+
const embedding = row["embedding"];
|
|
2379
|
+
let parsedEmbedding = null;
|
|
2380
|
+
if (embedding != null) {
|
|
2381
|
+
if (Array.isArray(embedding)) {
|
|
2382
|
+
parsedEmbedding = embedding;
|
|
2383
|
+
} else if (typeof embedding === "string") {
|
|
2384
|
+
parsedEmbedding = JSON.parse(embedding);
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
const m = new Memory({
|
|
2388
|
+
id: row["id"],
|
|
2389
|
+
content: row["content"],
|
|
2390
|
+
tier: row["tier"],
|
|
2391
|
+
source: row["source"],
|
|
2392
|
+
actor: row["actor"],
|
|
2393
|
+
sessionId: row["session_id"],
|
|
2394
|
+
createdAt: new Date(row["created_at"]),
|
|
2395
|
+
lastAccessedAt: new Date(row["last_accessed"]),
|
|
2396
|
+
accessCount: row["access_count"],
|
|
2397
|
+
importance: row["importance"],
|
|
2398
|
+
supersededBy: row["superseded_by"],
|
|
2399
|
+
embedding: parsedEmbedding,
|
|
2400
|
+
metadata: typeof row["metadata"] === "string" ? JSON.parse(row["metadata"]) : row["metadata"] ?? {},
|
|
2401
|
+
linkedIds: typeof row["linked_ids"] === "string" ? JSON.parse(row["linked_ids"]) : row["linked_ids"] ?? [],
|
|
2402
|
+
agentId: row["agent_id"] ?? "default",
|
|
2403
|
+
shared: row["shared"] === true || row["shared"] === "t"
|
|
2404
|
+
});
|
|
2405
|
+
m.contradictedBy = row["contradicted_by"];
|
|
2406
|
+
return m;
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
// src/index.ts
|
|
2410
|
+
init_openai();
|
|
2411
|
+
init_local();
|
|
2412
|
+
|
|
2413
|
+
// src/embeddings/factory.ts
|
|
2414
|
+
async function buildEmbedder(provider) {
|
|
2415
|
+
const resolved = (provider ?? process.env["MNEMONIC_EMBEDDER"] ?? "none").trim().toLowerCase();
|
|
2416
|
+
if (resolved === "none" || resolved === "") return null;
|
|
2417
|
+
if (resolved === "openai") {
|
|
2418
|
+
const { OpenAIEmbedder: OpenAIEmbedder2 } = await Promise.resolve().then(() => (init_openai(), openai_exports));
|
|
2419
|
+
return new OpenAIEmbedder2({
|
|
2420
|
+
model: process.env["MNEMONIC_OPENAI_MODEL"],
|
|
2421
|
+
apiKey: process.env["MNEMONIC_OPENAI_API_KEY"]
|
|
2422
|
+
});
|
|
2423
|
+
}
|
|
2424
|
+
if (resolved === "local") {
|
|
2425
|
+
const { LocalEmbedder: LocalEmbedder2 } = await Promise.resolve().then(() => (init_local(), local_exports));
|
|
2426
|
+
return new LocalEmbedder2();
|
|
2427
|
+
}
|
|
2428
|
+
throw new Error(
|
|
2429
|
+
`Unknown embedder provider: '${resolved}'. Valid options: none, openai, local`
|
|
2430
|
+
);
|
|
2431
|
+
}
|
|
2432
|
+
|
|
2433
|
+
// src/adapters/raw.ts
|
|
2434
|
+
async function recallAsPrompt(mnemonic, query, options) {
|
|
2435
|
+
return mnemonic.recall(query, {
|
|
2436
|
+
maxTokens: options?.maxTokens ?? 3e3,
|
|
2437
|
+
header: options?.header ?? "Relevant context from memory:"
|
|
2438
|
+
});
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
// src/adapters/langchain.ts
|
|
2442
|
+
async function createLangChainMemory(options) {
|
|
2443
|
+
let BaseMemory;
|
|
2444
|
+
let getInputValues;
|
|
2445
|
+
let getOutputValues;
|
|
2446
|
+
try {
|
|
2447
|
+
const lcPath = "@langchain/core/memory";
|
|
2448
|
+
const mod = await import(lcPath);
|
|
2449
|
+
BaseMemory = mod.BaseMemory ?? mod.default?.BaseMemory;
|
|
2450
|
+
getInputValues = mod.getInputValues ?? mod.default?.getInputValues;
|
|
2451
|
+
getOutputValues = mod.getOutputValues ?? mod.default?.getOutputValues;
|
|
2452
|
+
} catch (err) {
|
|
2453
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2454
|
+
if (msg.includes("Cannot find") || msg.includes("MODULE_NOT_FOUND")) {
|
|
2455
|
+
throw new Error(
|
|
2456
|
+
"LangChain adapter requires '@langchain/core'. Install it with: npm install @langchain/core"
|
|
2457
|
+
);
|
|
2458
|
+
}
|
|
2459
|
+
throw err;
|
|
2460
|
+
}
|
|
2461
|
+
const {
|
|
2462
|
+
mnemonic,
|
|
2463
|
+
memoryKey = "memory",
|
|
2464
|
+
maxTokens = 3e3,
|
|
2465
|
+
inputKey,
|
|
2466
|
+
outputKey
|
|
2467
|
+
} = options;
|
|
2468
|
+
function extractString(record, helper, key) {
|
|
2469
|
+
if (helper && key) {
|
|
2470
|
+
try {
|
|
2471
|
+
const vals = helper(record, key);
|
|
2472
|
+
const v = vals[key];
|
|
2473
|
+
return typeof v === "string" ? v : void 0;
|
|
2474
|
+
} catch {
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
if (key && typeof record[key] === "string") return record[key];
|
|
2478
|
+
return Object.values(record).find((v) => typeof v === "string");
|
|
2479
|
+
}
|
|
2480
|
+
class MnemonicLangChainMemory extends BaseMemory {
|
|
2481
|
+
// BaseMemory v1.x requires `memoryKeys` (not `memoryVariables`)
|
|
2482
|
+
get memoryKeys() {
|
|
2483
|
+
return [memoryKey];
|
|
2484
|
+
}
|
|
2485
|
+
async loadMemoryVariables(inputs) {
|
|
2486
|
+
const query = extractString(inputs, getInputValues, inputKey) ?? "";
|
|
2487
|
+
const context = await mnemonic.recall(String(query), { maxTokens });
|
|
2488
|
+
return { [memoryKey]: context };
|
|
2489
|
+
}
|
|
2490
|
+
async saveContext(inputs, outputs) {
|
|
2491
|
+
const humanInput = extractString(inputs, getInputValues, inputKey);
|
|
2492
|
+
if (humanInput) {
|
|
2493
|
+
await mnemonic.add(String(humanInput), {
|
|
2494
|
+
source: "langchain",
|
|
2495
|
+
actor: "user"
|
|
2496
|
+
});
|
|
2497
|
+
}
|
|
2498
|
+
const aiOutput = extractString(outputs, getOutputValues, outputKey);
|
|
2499
|
+
if (aiOutput) {
|
|
2500
|
+
await mnemonic.add(String(aiOutput), {
|
|
2501
|
+
source: "langchain",
|
|
2502
|
+
actor: "assistant"
|
|
2503
|
+
});
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
return new MnemonicLangChainMemory();
|
|
2508
|
+
}
|
|
2509
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2510
|
+
0 && (module.exports = {
|
|
2511
|
+
BUILTIN_SALIENCE_SIGNALS,
|
|
2512
|
+
BUILTIN_TIERS,
|
|
2513
|
+
LocalEmbedder,
|
|
2514
|
+
Memory,
|
|
2515
|
+
MemoryTier,
|
|
2516
|
+
Mnemonic,
|
|
2517
|
+
MnemonicConfig,
|
|
2518
|
+
OpenAIEmbedder,
|
|
2519
|
+
PostgresStore,
|
|
2520
|
+
SQLiteStore,
|
|
2521
|
+
SalienceSignal,
|
|
2522
|
+
TRANSIENT_ERRORS,
|
|
2523
|
+
applyTierChange,
|
|
2524
|
+
assemble,
|
|
2525
|
+
bruteForceCosineSearch,
|
|
2526
|
+
buildEmbedder,
|
|
2527
|
+
checkDemotions,
|
|
2528
|
+
checkPromotions,
|
|
2529
|
+
classify,
|
|
2530
|
+
cosineSimilarity,
|
|
2531
|
+
cosineSimilarityMatrix,
|
|
2532
|
+
cosineSimilarityVector,
|
|
2533
|
+
createEdge,
|
|
2534
|
+
createLangChainMemory,
|
|
2535
|
+
detectSalience,
|
|
2536
|
+
estimateTokens,
|
|
2537
|
+
findConsolidationCandidates,
|
|
2538
|
+
findLinks,
|
|
2539
|
+
formatContext,
|
|
2540
|
+
mergeCluster,
|
|
2541
|
+
parseDuration,
|
|
2542
|
+
recallAsPrompt,
|
|
2543
|
+
registerStore,
|
|
2544
|
+
score,
|
|
2545
|
+
syncToAsync
|
|
2546
|
+
});
|
|
2547
|
+
//# sourceMappingURL=index.cjs.map
|