@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
|
@@ -0,0 +1,1827 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/client.ts
|
|
27
|
+
var import_node_crypto2 = require("crypto");
|
|
28
|
+
|
|
29
|
+
// src/core/config.ts
|
|
30
|
+
function createDefaultScorerConfig() {
|
|
31
|
+
return {
|
|
32
|
+
weightRecency: 0.4,
|
|
33
|
+
weightFrequency: 0.25,
|
|
34
|
+
weightLinks: 0.2,
|
|
35
|
+
weightBase: 0.15,
|
|
36
|
+
frequencyLogCap: 5,
|
|
37
|
+
linkSaturation: 5,
|
|
38
|
+
supersessionPenalty: 0.1,
|
|
39
|
+
contradictionPenalty: 0.5,
|
|
40
|
+
consolidationPenalty: 0.5,
|
|
41
|
+
tierBaseScores: {
|
|
42
|
+
identity: 1,
|
|
43
|
+
procedural: 0.9,
|
|
44
|
+
structural: 0.8,
|
|
45
|
+
episodic: 0.5,
|
|
46
|
+
transient: 0.2
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function createDefaultClassifierConfig() {
|
|
51
|
+
return {
|
|
52
|
+
extraPatterns: {},
|
|
53
|
+
episodicMinWords: 10,
|
|
54
|
+
customClassifier: null
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function createDefaultLinkerConfig() {
|
|
58
|
+
return {
|
|
59
|
+
similarityThreshold: 0.65,
|
|
60
|
+
maxLinks: 10,
|
|
61
|
+
keywordPenalty: 0.8,
|
|
62
|
+
contradictionThreshold: 0.7,
|
|
63
|
+
elaborationThreshold: 0.85,
|
|
64
|
+
supersessionThreshold: 0.8,
|
|
65
|
+
negationWords: /* @__PURE__ */ new Set([
|
|
66
|
+
"not",
|
|
67
|
+
"never",
|
|
68
|
+
"don't",
|
|
69
|
+
"doesn't",
|
|
70
|
+
"isn't",
|
|
71
|
+
"aren't",
|
|
72
|
+
"won't",
|
|
73
|
+
"no"
|
|
74
|
+
]),
|
|
75
|
+
customRelationFn: null
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function createDefaultAssemblerConfig() {
|
|
79
|
+
return {
|
|
80
|
+
defaultMaxTokens: 3e3,
|
|
81
|
+
diversityStrength: 0.3,
|
|
82
|
+
sortOrder: "chronological",
|
|
83
|
+
minRelevance: 0.25
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function createDefaultConsolidationConfig() {
|
|
87
|
+
return {
|
|
88
|
+
minClusterSize: 3,
|
|
89
|
+
similarityThreshold: 0.7,
|
|
90
|
+
maxContentLength: 2e3,
|
|
91
|
+
prefix: "[Consolidated]",
|
|
92
|
+
sourceName: "consolidation",
|
|
93
|
+
actorName: "system",
|
|
94
|
+
maxCandidates: 1e3,
|
|
95
|
+
maxNeighborsPerCandidate: 50
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function createDefaultPromotionConfig() {
|
|
99
|
+
return {
|
|
100
|
+
transientToEpisodicAccesses: 2,
|
|
101
|
+
episodicToStructuralAccesses: 5,
|
|
102
|
+
structuralToIdentityAccesses: 15,
|
|
103
|
+
proceduralToIdentityAccesses: 25,
|
|
104
|
+
demotionImportanceThreshold: 0.1
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
var SalienceSignal = class {
|
|
108
|
+
name;
|
|
109
|
+
patterns;
|
|
110
|
+
boost;
|
|
111
|
+
_compiled;
|
|
112
|
+
constructor(name, patterns, boost) {
|
|
113
|
+
this.name = name;
|
|
114
|
+
this.patterns = patterns;
|
|
115
|
+
this.boost = boost;
|
|
116
|
+
this._compiled = patterns.map((p) => new RegExp(p, "i"));
|
|
117
|
+
}
|
|
118
|
+
scan(text) {
|
|
119
|
+
return this._compiled.some((re) => re.test(text));
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
var BUILTIN_SALIENCE_SIGNALS = [
|
|
123
|
+
new SalienceSignal("error", [
|
|
124
|
+
"\\b(?:error|exception|traceback|stack\\s*trace|segfault|panic|crash)\\b",
|
|
125
|
+
"\\b(?:failed|failure|broken|bug|regression)\\b",
|
|
126
|
+
"\\b(?:404|500|502|503|ECONNREFUSED|OOM|timeout)\\b"
|
|
127
|
+
], 0.15),
|
|
128
|
+
new SalienceSignal("urgency", [
|
|
129
|
+
"\\b(?:urgent|critical|blocker|p0|p1|sev[- ]?[01]|outage|incident)\\b",
|
|
130
|
+
"\\b(?:ASAP|immediately|right now|emergency)\\b",
|
|
131
|
+
"\\b(?:production is down|prod is down|site is down)\\b"
|
|
132
|
+
], 0.25),
|
|
133
|
+
new SalienceSignal("correction", [
|
|
134
|
+
"\\b(?:actually|correction|i was wrong|that's wrong|that's not right)\\b",
|
|
135
|
+
"\\b(?:let me correct|i meant|i misspoke|sorry,? i meant)\\b",
|
|
136
|
+
"\\b(?:no,? (?:it's|it is|that's|that is)|wait,? (?:no|actually))\\b"
|
|
137
|
+
], 0.2),
|
|
138
|
+
new SalienceSignal("decision", [
|
|
139
|
+
"\\b(?:we've decided|final decision|going with|approved|sign[- ]?off)\\b",
|
|
140
|
+
"\\b(?:agreement|consensus|resolved to|committed to)\\b",
|
|
141
|
+
"\\b(?:from now on|henceforth|going forward)\\b"
|
|
142
|
+
], 0.15),
|
|
143
|
+
new SalienceSignal("frustration", [
|
|
144
|
+
"\\b(?:i(?:'ve| have) told you|already said|keep telling you)\\b",
|
|
145
|
+
"\\b(?:again\\?|how many times|stop (?:doing|suggesting))\\b",
|
|
146
|
+
"\\b(?:this is (?:wrong|bad|terrible|awful)|doesn't work)\\b"
|
|
147
|
+
], 0.2)
|
|
148
|
+
];
|
|
149
|
+
function createDefaultSalienceConfig() {
|
|
150
|
+
return {
|
|
151
|
+
enabled: true,
|
|
152
|
+
maxBoost: 0.4,
|
|
153
|
+
signals: [...BUILTIN_SALIENCE_SIGNALS]
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
var BUILTIN_TIERS = [
|
|
157
|
+
{ name: "identity", defaultHalfLifeDays: 3650, description: "Name, role, company, tech stack \u2014 near-permanent" },
|
|
158
|
+
{ name: "procedural", defaultHalfLifeDays: 365, description: "Learned workflows, multi-step procedures, how-to knowledge" },
|
|
159
|
+
{ name: "structural", defaultHalfLifeDays: 180, description: "Architecture decisions, preferences, constraints" },
|
|
160
|
+
{ name: "episodic", defaultHalfLifeDays: 30, description: "Specific conversations, tasks completed" },
|
|
161
|
+
{ name: "transient", defaultHalfLifeDays: 7, description: "One-off questions, small talk" }
|
|
162
|
+
];
|
|
163
|
+
var MnemonicConfig = class {
|
|
164
|
+
scorer;
|
|
165
|
+
classifier;
|
|
166
|
+
linker;
|
|
167
|
+
assembler;
|
|
168
|
+
consolidation;
|
|
169
|
+
promotion;
|
|
170
|
+
salience;
|
|
171
|
+
charsPerToken;
|
|
172
|
+
defaultTier;
|
|
173
|
+
embeddingSearchLimit;
|
|
174
|
+
_tiers;
|
|
175
|
+
constructor(overrides) {
|
|
176
|
+
this.scorer = { ...createDefaultScorerConfig(), ...overrides?.scorer };
|
|
177
|
+
this.classifier = { ...createDefaultClassifierConfig(), ...overrides?.classifier };
|
|
178
|
+
this.linker = { ...createDefaultLinkerConfig(), ...overrides?.linker };
|
|
179
|
+
this.assembler = { ...createDefaultAssemblerConfig(), ...overrides?.assembler };
|
|
180
|
+
this.consolidation = { ...createDefaultConsolidationConfig(), ...overrides?.consolidation };
|
|
181
|
+
this.promotion = { ...createDefaultPromotionConfig(), ...overrides?.promotion };
|
|
182
|
+
this.salience = { ...createDefaultSalienceConfig(), ...overrides?.salience };
|
|
183
|
+
this.charsPerToken = overrides?.charsPerToken ?? 4;
|
|
184
|
+
this.defaultTier = overrides?.defaultTier ?? "episodic";
|
|
185
|
+
this.embeddingSearchLimit = overrides?.embeddingSearchLimit ?? 100;
|
|
186
|
+
if (this.consolidation.maxCandidates < 1) {
|
|
187
|
+
throw new Error(`maxCandidates must be >= 1, got ${this.consolidation.maxCandidates}`);
|
|
188
|
+
}
|
|
189
|
+
if (this.consolidation.maxNeighborsPerCandidate < 1) {
|
|
190
|
+
throw new Error(
|
|
191
|
+
`maxNeighborsPerCandidate must be >= 1, got ${this.consolidation.maxNeighborsPerCandidate}`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
this._tiers = /* @__PURE__ */ new Map();
|
|
195
|
+
for (const td of BUILTIN_TIERS) {
|
|
196
|
+
this._tiers.set(td.name, td);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
registerTier(tier) {
|
|
200
|
+
this._tiers.set(tier.name, tier);
|
|
201
|
+
if (!(tier.name in this.scorer.tierBaseScores)) {
|
|
202
|
+
this.scorer.tierBaseScores[tier.name] = 0.5;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
getTier(name) {
|
|
206
|
+
const td = this._tiers.get(name);
|
|
207
|
+
if (!td) throw new Error(`Unknown tier: '${name}'`);
|
|
208
|
+
return td;
|
|
209
|
+
}
|
|
210
|
+
tierNames() {
|
|
211
|
+
return [...this._tiers.keys()];
|
|
212
|
+
}
|
|
213
|
+
halfLifeDays(tierName) {
|
|
214
|
+
return this.getTier(tierName).defaultHalfLifeDays;
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// src/core/classifier.ts
|
|
219
|
+
var IDENTITY_PATTERNS = [
|
|
220
|
+
/\bmy name is\b/i,
|
|
221
|
+
/\bi(?:'m| am) (?:a |an )?(?:software|data|ml|backend|frontend|devops|platform)\b/i,
|
|
222
|
+
/\bi work (?:at|for)\b/i,
|
|
223
|
+
/\bmy (?:role|title|position) is\b/i,
|
|
224
|
+
/\bour (?:tech |technology )?stack (?:is|includes)\b/i,
|
|
225
|
+
/\bi (?:use|prefer|always use)\b.+(?:language|framework|editor|ide|os)\b/i
|
|
226
|
+
];
|
|
227
|
+
var PROCEDURAL_PATTERNS = [
|
|
228
|
+
/\b(?:step\s+\d|first|then|next|finally|afterwards)\b.*\b(?:step|run|execute|do|click|open|type|enter)\b/i,
|
|
229
|
+
/\bto\s+(?:do|deploy|set up|configure|install|build|run|create|fix)\b.+\b(?:first|then|after|before|finally)\b/i,
|
|
230
|
+
/\bworkflow\b.*\b(?:is|goes|looks like|involves)\b/i,
|
|
231
|
+
/\bprocedure\b.*\b(?:is|for|to)\b/i,
|
|
232
|
+
/\bwhen(?:ever)?\b.+\b(?:always|you should|we|i) (?:run|do|execute|call|use)\b/i,
|
|
233
|
+
/\brecipe\b.*\bfor\b/i,
|
|
234
|
+
/\brunbook\b/i
|
|
235
|
+
];
|
|
236
|
+
var STRUCTURAL_PATTERNS = [
|
|
237
|
+
/\bwe (?:decided|chose|agreed) to\b/i,
|
|
238
|
+
/\barchitectur(?:e|al)\b/i,
|
|
239
|
+
/\bconstraint\b/i,
|
|
240
|
+
/\brequirement\b/i,
|
|
241
|
+
/\bprefer(?:s|ence)?\b.+(?:over|instead|rather)\b/i,
|
|
242
|
+
/\balways\b.+\b(?:use|deploy|run)\b/i,
|
|
243
|
+
/\bnever\b.+\b(?:use|deploy|run)\b/i,
|
|
244
|
+
/\bstandard(?:s|ise|ize)?\b/i,
|
|
245
|
+
/\bconvention\b/i
|
|
246
|
+
];
|
|
247
|
+
var TRANSIENT_PATTERNS = [
|
|
248
|
+
/\b(?:hey|hi|hello|thanks|thank you|cheers|bye|goodbye)\b/i,
|
|
249
|
+
/\bwhat(?:'s| is) the (?:time|date|weather)\b/i,
|
|
250
|
+
/\bjust (?:a )?quick\b/i,
|
|
251
|
+
/\bnever ?mind\b/i
|
|
252
|
+
];
|
|
253
|
+
var COMPILED_DEFAULTS = [
|
|
254
|
+
["identity", IDENTITY_PATTERNS],
|
|
255
|
+
["procedural", PROCEDURAL_PATTERNS],
|
|
256
|
+
["structural", STRUCTURAL_PATTERNS],
|
|
257
|
+
["transient", TRANSIENT_PATTERNS]
|
|
258
|
+
];
|
|
259
|
+
function buildTierPatterns(cfg) {
|
|
260
|
+
if (Object.keys(cfg.extraPatterns).length === 0) {
|
|
261
|
+
return COMPILED_DEFAULTS;
|
|
262
|
+
}
|
|
263
|
+
return COMPILED_DEFAULTS.map(([name, compiled]) => {
|
|
264
|
+
const extra = (cfg.extraPatterns[name] ?? []).map((p) => new RegExp(p, "i"));
|
|
265
|
+
return [name, [...compiled, ...extra]];
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
function classify(content, options) {
|
|
269
|
+
const config = options?.config ?? new MnemonicConfig();
|
|
270
|
+
const cfg = config.classifier;
|
|
271
|
+
const actor = options?.actor ?? "";
|
|
272
|
+
if (cfg.customClassifier) {
|
|
273
|
+
return cfg.customClassifier(content, options?.source ?? "", actor);
|
|
274
|
+
}
|
|
275
|
+
for (const [tierName, patterns] of buildTierPatterns(cfg)) {
|
|
276
|
+
for (const pat of patterns) {
|
|
277
|
+
if (pat.test(content)) {
|
|
278
|
+
return tierName;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (actor === "user" && content.split(/\s+/).length > cfg.episodicMinWords) {
|
|
283
|
+
return "episodic";
|
|
284
|
+
}
|
|
285
|
+
return config.defaultTier;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// src/core/scorer.ts
|
|
289
|
+
function recencyFactor(memory, halfLifeDays) {
|
|
290
|
+
const now = Date.now();
|
|
291
|
+
const elapsedDays = Math.max(0, (now - memory.lastAccessedAt.getTime()) / 864e5);
|
|
292
|
+
return Math.pow(0.5, elapsedDays / halfLifeDays);
|
|
293
|
+
}
|
|
294
|
+
function frequencyFactor(memory, logCap) {
|
|
295
|
+
if (logCap <= 0) return 1;
|
|
296
|
+
return Math.min(1, Math.log2(1 + memory.accessCount) / logCap);
|
|
297
|
+
}
|
|
298
|
+
function linkFactor(memory, saturation) {
|
|
299
|
+
return Math.min(1, memory.linkedIds.length / saturation);
|
|
300
|
+
}
|
|
301
|
+
function score(memory, config) {
|
|
302
|
+
const cfg = config ?? new MnemonicConfig();
|
|
303
|
+
const sc = cfg.scorer;
|
|
304
|
+
const tierName = memory.tierName;
|
|
305
|
+
let halfLife;
|
|
306
|
+
try {
|
|
307
|
+
halfLife = cfg.halfLifeDays(tierName);
|
|
308
|
+
} catch {
|
|
309
|
+
halfLife = 30;
|
|
310
|
+
}
|
|
311
|
+
const base = sc.tierBaseScores[tierName] ?? 0.5;
|
|
312
|
+
const composite = sc.weightRecency * recencyFactor(memory, halfLife) + sc.weightFrequency * frequencyFactor(memory, sc.frequencyLogCap) + sc.weightLinks * linkFactor(memory, sc.linkSaturation) + sc.weightBase * base;
|
|
313
|
+
const boost = memory.metadata["boost"] ?? 0;
|
|
314
|
+
let result = Math.max(0, composite + boost);
|
|
315
|
+
if (memory.supersededBy != null) {
|
|
316
|
+
result *= Math.min(1, Math.max(0, sc.supersessionPenalty));
|
|
317
|
+
}
|
|
318
|
+
if (memory.supersededBy == null && memory.contradictedBy != null) {
|
|
319
|
+
result *= Math.min(1, Math.max(0, sc.contradictionPenalty));
|
|
320
|
+
}
|
|
321
|
+
if (memory.metadata["consolidated_into"] != null) {
|
|
322
|
+
result *= Math.min(1, Math.max(0, sc.consolidationPenalty));
|
|
323
|
+
}
|
|
324
|
+
return result;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// src/core/memory.ts
|
|
328
|
+
var import_node_crypto = require("crypto");
|
|
329
|
+
|
|
330
|
+
// src/core/utils.ts
|
|
331
|
+
function cosineSimilarityVector(query, embeddings) {
|
|
332
|
+
const dim = query.length;
|
|
333
|
+
const n = embeddings.length;
|
|
334
|
+
const result = new Float64Array(n);
|
|
335
|
+
for (let ei = 0; ei < n; ei++) {
|
|
336
|
+
if (embeddings[ei].length !== dim) {
|
|
337
|
+
throw new Error(
|
|
338
|
+
`Embedding dimension mismatch: ${embeddings[ei].length} vs query ${dim}`
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
let normQ = 0;
|
|
343
|
+
for (let i = 0; i < dim; i++) normQ += query[i] * query[i];
|
|
344
|
+
normQ = Math.sqrt(normQ);
|
|
345
|
+
if (normQ === 0) return result;
|
|
346
|
+
const qNormed = new Float64Array(dim);
|
|
347
|
+
for (let i = 0; i < dim; i++) qNormed[i] = query[i] / normQ;
|
|
348
|
+
for (let ei = 0; ei < n; ei++) {
|
|
349
|
+
const emb = embeddings[ei];
|
|
350
|
+
let dot = 0;
|
|
351
|
+
let normE = 0;
|
|
352
|
+
for (let i = 0; i < dim; i++) {
|
|
353
|
+
dot += qNormed[i] * emb[i];
|
|
354
|
+
normE += emb[i] * emb[i];
|
|
355
|
+
}
|
|
356
|
+
normE = Math.sqrt(normE);
|
|
357
|
+
result[ei] = normE === 0 ? 0 : dot / normE;
|
|
358
|
+
}
|
|
359
|
+
return result;
|
|
360
|
+
}
|
|
361
|
+
function cosineSimilarityMatrix(embeddings) {
|
|
362
|
+
const n = embeddings.length;
|
|
363
|
+
if (n === 0) return new Float64Array(0);
|
|
364
|
+
const dim = embeddings[0].length;
|
|
365
|
+
for (let i = 1; i < n; i++) {
|
|
366
|
+
if (embeddings[i].length !== dim) {
|
|
367
|
+
throw new Error(
|
|
368
|
+
`Embedding dimension mismatch at index ${i}: ${embeddings[i].length} vs ${dim}`
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
const result = new Float64Array(n * n);
|
|
373
|
+
const normed = [];
|
|
374
|
+
const isZeroNorm = [];
|
|
375
|
+
for (let i = 0; i < n; i++) {
|
|
376
|
+
const emb = embeddings[i];
|
|
377
|
+
let norm = 0;
|
|
378
|
+
for (let d = 0; d < dim; d++) norm += emb[d] * emb[d];
|
|
379
|
+
norm = Math.sqrt(norm);
|
|
380
|
+
const row = new Float64Array(dim);
|
|
381
|
+
if (norm > 0) {
|
|
382
|
+
for (let d = 0; d < dim; d++) row[d] = emb[d] / norm;
|
|
383
|
+
isZeroNorm.push(false);
|
|
384
|
+
} else {
|
|
385
|
+
isZeroNorm.push(true);
|
|
386
|
+
}
|
|
387
|
+
normed.push(row);
|
|
388
|
+
}
|
|
389
|
+
for (let i = 0; i < n; i++) {
|
|
390
|
+
result[i * n + i] = isZeroNorm[i] ? 0 : 1;
|
|
391
|
+
for (let j = i + 1; j < n; j++) {
|
|
392
|
+
let dot = 0;
|
|
393
|
+
for (let d = 0; d < dim; d++) dot += normed[i][d] * normed[j][d];
|
|
394
|
+
result[i * n + j] = dot;
|
|
395
|
+
result[j * n + i] = dot;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return result;
|
|
399
|
+
}
|
|
400
|
+
var DURATION_RE = /^(\d+)\s*([wdhms])$/;
|
|
401
|
+
var MS_MULTIPLIERS = {
|
|
402
|
+
w: 7 * 24 * 60 * 60 * 1e3,
|
|
403
|
+
d: 24 * 60 * 60 * 1e3,
|
|
404
|
+
h: 60 * 60 * 1e3,
|
|
405
|
+
m: 60 * 1e3,
|
|
406
|
+
s: 1e3
|
|
407
|
+
};
|
|
408
|
+
function parseDuration(s) {
|
|
409
|
+
const match = s.trim().toLowerCase().match(DURATION_RE);
|
|
410
|
+
if (!match) {
|
|
411
|
+
throw new Error(
|
|
412
|
+
`Cannot parse duration: '${s}'. Use e.g. 2w, 30d, 24h, 60m, 120s.`
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
const n = parseInt(match[1], 10);
|
|
416
|
+
const unit = match[2];
|
|
417
|
+
return n * MS_MULTIPLIERS[unit];
|
|
418
|
+
}
|
|
419
|
+
function estimateTokens(text, charsPerToken = 4) {
|
|
420
|
+
return Math.max(1, Math.floor(text.length / charsPerToken));
|
|
421
|
+
}
|
|
422
|
+
function parseSqlitePath(uri) {
|
|
423
|
+
if (!uri.includes("///")) {
|
|
424
|
+
throw new Error(
|
|
425
|
+
`Invalid SQLite URI: '${uri}'. Expected format: sqlite:///path/to/db`
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
return uri.split("///")[1] || "mnemonic.db";
|
|
429
|
+
}
|
|
430
|
+
function bruteForceSimilarPairs(memories, threshold, maxCandidates) {
|
|
431
|
+
const cap = Math.max(0, maxCandidates);
|
|
432
|
+
const withEmb = memories.filter((m) => m.embedding != null).slice(0, cap);
|
|
433
|
+
if (withEmb.length < 2) return [];
|
|
434
|
+
const simMatrix = cosineSimilarityMatrix(withEmb.map((m) => m.embedding));
|
|
435
|
+
const n = withEmb.length;
|
|
436
|
+
const pairs = [];
|
|
437
|
+
for (let i = 0; i < n; i++) {
|
|
438
|
+
for (let j = i + 1; j < n; j++) {
|
|
439
|
+
const sim = simMatrix[i * n + j];
|
|
440
|
+
if (sim >= threshold) {
|
|
441
|
+
pairs.push({ idA: withEmb[i].id, idB: withEmb[j].id, similarity: sim });
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return pairs;
|
|
446
|
+
}
|
|
447
|
+
function bruteForceCosineSearch(memories, embedding, limit) {
|
|
448
|
+
if (limit <= 0) return [];
|
|
449
|
+
const withEmb = memories.filter((m) => m.embedding != null);
|
|
450
|
+
if (withEmb.length === 0) return [];
|
|
451
|
+
const embList = withEmb.map((m) => m.embedding);
|
|
452
|
+
const sims = cosineSimilarityVector(embedding, embList);
|
|
453
|
+
const indices = Array.from({ length: withEmb.length }, (_, i) => i);
|
|
454
|
+
indices.sort((a, b) => sims[b] - sims[a]);
|
|
455
|
+
return indices.slice(0, limit).map((i) => withEmb[i]);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// src/core/memory.ts
|
|
459
|
+
var MemoryTier = {
|
|
460
|
+
IDENTITY: "identity",
|
|
461
|
+
PROCEDURAL: "procedural",
|
|
462
|
+
STRUCTURAL: "structural",
|
|
463
|
+
EPISODIC: "episodic",
|
|
464
|
+
TRANSIENT: "transient"
|
|
465
|
+
};
|
|
466
|
+
var Memory = class {
|
|
467
|
+
content;
|
|
468
|
+
id;
|
|
469
|
+
tier;
|
|
470
|
+
source;
|
|
471
|
+
actor;
|
|
472
|
+
sessionId;
|
|
473
|
+
createdAt;
|
|
474
|
+
lastAccessedAt;
|
|
475
|
+
accessCount;
|
|
476
|
+
importance;
|
|
477
|
+
supersededBy;
|
|
478
|
+
contradictedBy;
|
|
479
|
+
embedding;
|
|
480
|
+
metadata;
|
|
481
|
+
linkedIds;
|
|
482
|
+
agentId;
|
|
483
|
+
shared;
|
|
484
|
+
_charsPerToken;
|
|
485
|
+
constructor(init) {
|
|
486
|
+
const now = /* @__PURE__ */ new Date();
|
|
487
|
+
this.content = init.content;
|
|
488
|
+
this.id = init.id ?? (0, import_node_crypto.randomUUID)().replace(/-/g, "");
|
|
489
|
+
this.tier = init.tier ?? MemoryTier.EPISODIC;
|
|
490
|
+
this.source = init.source ?? "unknown";
|
|
491
|
+
this.actor = init.actor ?? "system";
|
|
492
|
+
this.sessionId = init.sessionId ?? null;
|
|
493
|
+
this.createdAt = init.createdAt ?? now;
|
|
494
|
+
this.lastAccessedAt = init.lastAccessedAt ?? now;
|
|
495
|
+
this.accessCount = init.accessCount ?? 0;
|
|
496
|
+
this.importance = init.importance ?? 1;
|
|
497
|
+
this.supersededBy = init.supersededBy ?? null;
|
|
498
|
+
this.contradictedBy = init.contradictedBy ?? null;
|
|
499
|
+
this.embedding = init.embedding ?? null;
|
|
500
|
+
this.metadata = init.metadata ?? {};
|
|
501
|
+
this.linkedIds = init.linkedIds ?? [];
|
|
502
|
+
this.agentId = init.agentId ?? "default";
|
|
503
|
+
this.shared = init.shared ?? false;
|
|
504
|
+
this._charsPerToken = init.charsPerToken ?? 4;
|
|
505
|
+
}
|
|
506
|
+
/** The tier as a plain string, regardless of whether it was set as an enum. */
|
|
507
|
+
get tierName() {
|
|
508
|
+
return this.tier;
|
|
509
|
+
}
|
|
510
|
+
/** Rough token count using configurable chars-per-token ratio. */
|
|
511
|
+
get tokenEstimate() {
|
|
512
|
+
return estimateTokens(this.content, this._charsPerToken);
|
|
513
|
+
}
|
|
514
|
+
/** Record an access. */
|
|
515
|
+
touch() {
|
|
516
|
+
this.lastAccessedAt = /* @__PURE__ */ new Date();
|
|
517
|
+
this.accessCount += 1;
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
function createEdge(sourceId, targetId, relation, weight = 1) {
|
|
521
|
+
return { sourceId, targetId, relation, weight, createdAt: /* @__PURE__ */ new Date() };
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// src/core/linker.ts
|
|
525
|
+
var NUMBER_RE = /\b\d+(?:[.,]\d+)*/g;
|
|
526
|
+
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;
|
|
527
|
+
function keywordOverlap(a, b) {
|
|
528
|
+
const sa = new Set(a.toLowerCase().split(/\s+/));
|
|
529
|
+
const sb = new Set(b.toLowerCase().split(/\s+/));
|
|
530
|
+
if (sa.size === 0 || sb.size === 0) return 0;
|
|
531
|
+
let intersection = 0;
|
|
532
|
+
for (const word of sa) {
|
|
533
|
+
if (sb.has(word)) intersection++;
|
|
534
|
+
}
|
|
535
|
+
const union = (/* @__PURE__ */ new Set([...sa, ...sb])).size;
|
|
536
|
+
return intersection / union;
|
|
537
|
+
}
|
|
538
|
+
function extractNumbers(text) {
|
|
539
|
+
return new Set(text.match(NUMBER_RE) ?? []);
|
|
540
|
+
}
|
|
541
|
+
function hasNumberDisagreement(a, b) {
|
|
542
|
+
const aNums = extractNumbers(a);
|
|
543
|
+
const bNums = extractNumbers(b);
|
|
544
|
+
if (aNums.size === 0 || bNums.size === 0) return false;
|
|
545
|
+
if (aNums.size !== bNums.size) return true;
|
|
546
|
+
for (const n of aNums) {
|
|
547
|
+
if (!bNums.has(n)) return true;
|
|
548
|
+
}
|
|
549
|
+
return false;
|
|
550
|
+
}
|
|
551
|
+
function hasCorrectionSignal(text) {
|
|
552
|
+
return CORRECTION_RE.test(text);
|
|
553
|
+
}
|
|
554
|
+
function inferRelation(a, b, similarity, cfg) {
|
|
555
|
+
if (cfg.customRelationFn) {
|
|
556
|
+
return cfg.customRelationFn(a, b, similarity);
|
|
557
|
+
}
|
|
558
|
+
const aWords = new Set(a.content.toLowerCase().split(/\s+/));
|
|
559
|
+
const bWords = new Set(b.content.toLowerCase().split(/\s+/));
|
|
560
|
+
const hasNegation = [...aWords].some((w) => cfg.negationWords.has(w)) !== [...bWords].some((w) => cfg.negationWords.has(w));
|
|
561
|
+
const hasNumbers = hasNumberDisagreement(a.content, b.content);
|
|
562
|
+
const hasCorrection = hasCorrectionSignal(a.content) || hasCorrectionSignal(b.content);
|
|
563
|
+
const hasContradiction = (hasNegation || hasNumbers || hasCorrection) && similarity > cfg.contradictionThreshold;
|
|
564
|
+
if (hasContradiction && a.createdAt > b.createdAt) {
|
|
565
|
+
if (hasNumbers || hasCorrection || similarity > cfg.supersessionThreshold) {
|
|
566
|
+
return "supersedes";
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
if (hasContradiction) return "contradicts";
|
|
570
|
+
if (similarity > cfg.elaborationThreshold) return "elaborates";
|
|
571
|
+
return "related_to";
|
|
572
|
+
}
|
|
573
|
+
function findLinks(newMemory, existing, options) {
|
|
574
|
+
const cfg = (options?.config ?? new MnemonicConfig()).linker;
|
|
575
|
+
const threshold = options?.similarityThreshold ?? cfg.similarityThreshold;
|
|
576
|
+
const limit = options?.maxLinks ?? cfg.maxLinks;
|
|
577
|
+
const others = existing.filter((m) => m.id !== newMemory.id);
|
|
578
|
+
if (others.length === 0) return [];
|
|
579
|
+
const scored = [];
|
|
580
|
+
if (newMemory.embedding) {
|
|
581
|
+
const withEmb = others.filter((m) => m.embedding != null);
|
|
582
|
+
const withoutEmb = others.filter((m) => m.embedding == null);
|
|
583
|
+
if (withEmb.length > 0) {
|
|
584
|
+
const embList = withEmb.map((m) => m.embedding);
|
|
585
|
+
const sims = cosineSimilarityVector(newMemory.embedding, embList);
|
|
586
|
+
for (let i = 0; i < withEmb.length; i++) {
|
|
587
|
+
if (sims[i] >= threshold) {
|
|
588
|
+
scored.push([sims[i], withEmb[i]]);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
for (const mem of withoutEmb) {
|
|
593
|
+
const sim = keywordOverlap(newMemory.content, mem.content) * cfg.keywordPenalty;
|
|
594
|
+
if (sim >= threshold) scored.push([sim, mem]);
|
|
595
|
+
}
|
|
596
|
+
} else {
|
|
597
|
+
for (const mem of others) {
|
|
598
|
+
const sim = keywordOverlap(newMemory.content, mem.content) * cfg.keywordPenalty;
|
|
599
|
+
if (sim >= threshold) scored.push([sim, mem]);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
scored.sort((a, b) => b[0] - a[0]);
|
|
603
|
+
return scored.slice(0, limit).map(([sim, mem]) => {
|
|
604
|
+
const relation = inferRelation(newMemory, mem, sim, cfg);
|
|
605
|
+
return createEdge(newMemory.id, mem.id, relation, sim);
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// src/core/assembler.ts
|
|
610
|
+
function getSortFn(sortOrder) {
|
|
611
|
+
if (typeof sortOrder === "function") return sortOrder;
|
|
612
|
+
if (sortOrder === "importance") return (a, b) => b.importance - a.importance;
|
|
613
|
+
if (sortOrder === "relevance") return () => 0;
|
|
614
|
+
return (a, b) => a.createdAt.getTime() - b.createdAt.getTime();
|
|
615
|
+
}
|
|
616
|
+
function assemble(_query, candidates, options) {
|
|
617
|
+
const config = options?.config ?? new MnemonicConfig();
|
|
618
|
+
const cfg = config.assembler;
|
|
619
|
+
const budget = options?.maxTokens ?? cfg.defaultMaxTokens;
|
|
620
|
+
const divStrength = options?.diversityStrength ?? cfg.diversityStrength;
|
|
621
|
+
const relevanceFloor = options?.minRelevance ?? cfg.minRelevance;
|
|
622
|
+
const qEmbed = options?.queryEmbedding ?? null;
|
|
623
|
+
let n = candidates.length;
|
|
624
|
+
if (n === 0) return [];
|
|
625
|
+
let importances = new Float64Array(n);
|
|
626
|
+
for (let i = 0; i < n; i++) {
|
|
627
|
+
importances[i] = score(candidates[i], config);
|
|
628
|
+
}
|
|
629
|
+
let embIndices = [];
|
|
630
|
+
for (let i = 0; i < n; i++) {
|
|
631
|
+
if (candidates[i].embedding) embIndices.push(i);
|
|
632
|
+
}
|
|
633
|
+
let relevances = new Float64Array(n);
|
|
634
|
+
for (let i = 0; i < n; i++) {
|
|
635
|
+
relevances[i] = keywordOverlap(_query, candidates[i].content);
|
|
636
|
+
}
|
|
637
|
+
if (qEmbed && embIndices.length > 0) {
|
|
638
|
+
const embList = embIndices.map((i) => candidates[i].embedding);
|
|
639
|
+
const sims = cosineSimilarityVector(qEmbed, embList);
|
|
640
|
+
for (let pos = 0; pos < embIndices.length; pos++) {
|
|
641
|
+
const idx = embIndices[pos];
|
|
642
|
+
relevances[idx] = Math.max(Math.max(0, sims[pos]), relevances[idx]);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
if (relevanceFloor > 0) {
|
|
646
|
+
const keep = [];
|
|
647
|
+
for (let i = 0; i < n; i++) {
|
|
648
|
+
if (relevances[i] >= relevanceFloor) keep.push(i);
|
|
649
|
+
}
|
|
650
|
+
if (keep.length < n) {
|
|
651
|
+
const oldToNew = /* @__PURE__ */ new Map();
|
|
652
|
+
keep.forEach((old, idx) => oldToNew.set(old, idx));
|
|
653
|
+
candidates = keep.map((i) => candidates[i]);
|
|
654
|
+
importances = Float64Array.from(keep.map((i) => importances[i]));
|
|
655
|
+
relevances = Float64Array.from(keep.map((i) => relevances[i]));
|
|
656
|
+
embIndices = embIndices.filter((i) => oldToNew.has(i)).map((i) => oldToNew.get(i));
|
|
657
|
+
n = candidates.length;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
let simMatrix = null;
|
|
661
|
+
const idxToPos = /* @__PURE__ */ new Map();
|
|
662
|
+
if (embIndices.length > 0 && divStrength > 0) {
|
|
663
|
+
const embList = embIndices.map((i) => candidates[i].embedding);
|
|
664
|
+
simMatrix = cosineSimilarityMatrix(embList);
|
|
665
|
+
for (let pos = 0; pos < embIndices.length; pos++) {
|
|
666
|
+
idxToPos.set(embIndices[pos], pos);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
const nEmb = embIndices.length;
|
|
670
|
+
const remaining = Array.from({ length: n }, (_, i) => i);
|
|
671
|
+
const selectedEmbPositions = [];
|
|
672
|
+
const selectedIndices = [];
|
|
673
|
+
let budgetLeft = budget;
|
|
674
|
+
while (remaining.length > 0 && budgetLeft > 0) {
|
|
675
|
+
let bestRi = -1;
|
|
676
|
+
let bestValue = -1;
|
|
677
|
+
for (let ri = 0; ri < remaining.length; ri++) {
|
|
678
|
+
const ci = remaining[ri];
|
|
679
|
+
if (candidates[ci].tokenEstimate > budgetLeft) continue;
|
|
680
|
+
let value = relevances[ci] * importances[ci];
|
|
681
|
+
if (selectedEmbPositions.length > 0 && simMatrix && idxToPos.has(ci)) {
|
|
682
|
+
const pos = idxToPos.get(ci);
|
|
683
|
+
let maxSim = 0;
|
|
684
|
+
for (const sp of selectedEmbPositions) {
|
|
685
|
+
const s = simMatrix[pos * nEmb + sp];
|
|
686
|
+
if (s > maxSim) maxSim = s;
|
|
687
|
+
}
|
|
688
|
+
value *= Math.min(1, Math.max(0, 1 - divStrength * maxSim));
|
|
689
|
+
}
|
|
690
|
+
if (value > bestValue) {
|
|
691
|
+
bestValue = value;
|
|
692
|
+
bestRi = ri;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
if (bestRi < 0) break;
|
|
696
|
+
const chosenIdx = remaining.splice(bestRi, 1)[0];
|
|
697
|
+
candidates[chosenIdx].touch();
|
|
698
|
+
selectedIndices.push(chosenIdx);
|
|
699
|
+
if (idxToPos.has(chosenIdx)) {
|
|
700
|
+
selectedEmbPositions.push(idxToPos.get(chosenIdx));
|
|
701
|
+
}
|
|
702
|
+
budgetLeft -= candidates[chosenIdx].tokenEstimate;
|
|
703
|
+
}
|
|
704
|
+
const selected = selectedIndices.map((i) => candidates[i]);
|
|
705
|
+
selected.sort(getSortFn(cfg.sortOrder));
|
|
706
|
+
return selected;
|
|
707
|
+
}
|
|
708
|
+
function detectConflicts(memories, config) {
|
|
709
|
+
const cfg = config ?? new MnemonicConfig();
|
|
710
|
+
const negWords = cfg.linker.negationWords;
|
|
711
|
+
const conflicts = [];
|
|
712
|
+
const seenPairs = /* @__PURE__ */ new Set();
|
|
713
|
+
const n = memories.length;
|
|
714
|
+
const pairKey = (a, b) => `${Math.min(a, b)},${Math.max(a, b)}`;
|
|
715
|
+
const idToIdx = /* @__PURE__ */ new Map();
|
|
716
|
+
for (let i = 0; i < n; i++) idToIdx.set(memories[i].id, i);
|
|
717
|
+
for (let i = 0; i < n; i++) {
|
|
718
|
+
const mem = memories[i];
|
|
719
|
+
if (mem.contradictedBy && idToIdx.has(mem.contradictedBy)) {
|
|
720
|
+
const j = idToIdx.get(mem.contradictedBy);
|
|
721
|
+
const key = pairKey(i, j);
|
|
722
|
+
if (!seenPairs.has(key)) {
|
|
723
|
+
seenPairs.add(key);
|
|
724
|
+
conflicts.push([Math.min(i, j), Math.max(i, j), "contradicted by newer memory"]);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
for (let i = 0; i < n; i++) {
|
|
729
|
+
for (let j = i + 1; j < n; j++) {
|
|
730
|
+
const key = pairKey(i, j);
|
|
731
|
+
if (seenPairs.has(key)) continue;
|
|
732
|
+
const a = memories[i], b = memories[j];
|
|
733
|
+
const kwSim = keywordOverlap(a.content, b.content);
|
|
734
|
+
let embSim = 0;
|
|
735
|
+
if (a.embedding && b.embedding) {
|
|
736
|
+
const sims = cosineSimilarityVector(a.embedding, [b.embedding]);
|
|
737
|
+
embSim = sims[0];
|
|
738
|
+
}
|
|
739
|
+
const maxSim = Math.max(kwSim, embSim);
|
|
740
|
+
if (maxSim < 0.6) continue;
|
|
741
|
+
if (hasNumberDisagreement(a.content, b.content)) {
|
|
742
|
+
conflicts.push([i, j, "numbers differ on similar topic"]);
|
|
743
|
+
seenPairs.add(key);
|
|
744
|
+
} else if (hasCorrectionSignal(a.content) || hasCorrectionSignal(b.content)) {
|
|
745
|
+
conflicts.push([i, j, "correction language detected"]);
|
|
746
|
+
seenPairs.add(key);
|
|
747
|
+
} else if (maxSim > 0.8) {
|
|
748
|
+
const aWords = new Set(a.content.toLowerCase().split(/\s+/));
|
|
749
|
+
const bWords = new Set(b.content.toLowerCase().split(/\s+/));
|
|
750
|
+
const aNeg = [...aWords].some((w) => negWords.has(w));
|
|
751
|
+
const bNeg = [...bWords].some((w) => negWords.has(w));
|
|
752
|
+
if (aNeg !== bNeg) {
|
|
753
|
+
conflicts.push([i, j, "contradictory statements"]);
|
|
754
|
+
seenPairs.add(key);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
return conflicts;
|
|
760
|
+
}
|
|
761
|
+
function formatContext(memories, header = "Relevant context:", conflicts) {
|
|
762
|
+
if (memories.length === 0) return "";
|
|
763
|
+
const lines = [header, ""];
|
|
764
|
+
for (const mem of memories) {
|
|
765
|
+
lines.push(`- [${mem.tierName}] ${mem.content}`);
|
|
766
|
+
}
|
|
767
|
+
if (conflicts && conflicts.length > 0) {
|
|
768
|
+
lines.push("");
|
|
769
|
+
for (const [idxA, idxB, reason] of conflicts) {
|
|
770
|
+
lines.push(`[!] Potential conflict between items ${idxA + 1} and ${idxB + 1}: ${reason}`);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return lines.join("\n");
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// src/core/promotion.ts
|
|
777
|
+
var TIER_TRANSITIONS = {
|
|
778
|
+
transient: { promoteTo: "episodic", demoteTo: null, promoteThresholdKey: "transientToEpisodicAccesses" },
|
|
779
|
+
episodic: { promoteTo: "structural", demoteTo: "transient", promoteThresholdKey: "episodicToStructuralAccesses" },
|
|
780
|
+
structural: { promoteTo: "identity", demoteTo: "episodic", promoteThresholdKey: "structuralToIdentityAccesses" },
|
|
781
|
+
procedural: { promoteTo: "identity", demoteTo: "structural", promoteThresholdKey: "proceduralToIdentityAccesses" },
|
|
782
|
+
identity: { promoteTo: null, demoteTo: "structural", promoteThresholdKey: null }
|
|
783
|
+
};
|
|
784
|
+
function checkPromotions(memories, config) {
|
|
785
|
+
const cfg = (config ?? new MnemonicConfig()).promotion;
|
|
786
|
+
const results = [];
|
|
787
|
+
for (const mem of memories) {
|
|
788
|
+
const transition = TIER_TRANSITIONS[mem.tierName];
|
|
789
|
+
if (!transition?.promoteTo || !transition.promoteThresholdKey) continue;
|
|
790
|
+
const threshold = cfg[transition.promoteThresholdKey];
|
|
791
|
+
if (mem.accessCount >= threshold) {
|
|
792
|
+
results.push({
|
|
793
|
+
memoryId: mem.id,
|
|
794
|
+
oldTier: mem.tierName,
|
|
795
|
+
newTier: transition.promoteTo,
|
|
796
|
+
reason: `access_count (${mem.accessCount}) >= ${threshold}`
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
return results;
|
|
801
|
+
}
|
|
802
|
+
function checkDemotions(memories, config) {
|
|
803
|
+
const fullCfg = config ?? new MnemonicConfig();
|
|
804
|
+
const cfg = fullCfg.promotion;
|
|
805
|
+
const results = [];
|
|
806
|
+
for (const mem of memories) {
|
|
807
|
+
if (mem.supersededBy != null) continue;
|
|
808
|
+
const transition = TIER_TRANSITIONS[mem.tierName];
|
|
809
|
+
if (!transition?.demoteTo) continue;
|
|
810
|
+
const importance = score(mem, fullCfg);
|
|
811
|
+
if (importance < cfg.demotionImportanceThreshold) {
|
|
812
|
+
results.push({
|
|
813
|
+
memoryId: mem.id,
|
|
814
|
+
oldTier: mem.tierName,
|
|
815
|
+
newTier: transition.demoteTo,
|
|
816
|
+
reason: `importance (${importance.toFixed(4)}) < ${cfg.demotionImportanceThreshold}`
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
return results;
|
|
821
|
+
}
|
|
822
|
+
function applyTierChange(memory, newTier) {
|
|
823
|
+
const oldTier = memory.tierName;
|
|
824
|
+
const history = memory.metadata["previous_tiers"] ?? [];
|
|
825
|
+
history.push(oldTier);
|
|
826
|
+
memory.metadata["previous_tiers"] = history;
|
|
827
|
+
memory.tier = newTier;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// src/core/salience.ts
|
|
831
|
+
function detectSalience(content, config) {
|
|
832
|
+
const cfg = config ?? createDefaultSalienceConfig();
|
|
833
|
+
if (!cfg.enabled) {
|
|
834
|
+
return { boost: 0, signals: [] };
|
|
835
|
+
}
|
|
836
|
+
let totalBoost = 0;
|
|
837
|
+
const fired = [];
|
|
838
|
+
for (const signal of cfg.signals) {
|
|
839
|
+
if (signal.scan(content)) {
|
|
840
|
+
totalBoost += signal.boost;
|
|
841
|
+
fired.push(signal.name);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
return {
|
|
845
|
+
boost: Math.min(totalBoost, cfg.maxBoost),
|
|
846
|
+
signals: fired
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// src/consolidation/engine.ts
|
|
851
|
+
var TRANSIENT_ERRORS = /* @__PURE__ */ new Set([
|
|
852
|
+
"RateLimitError",
|
|
853
|
+
"APITimeoutError",
|
|
854
|
+
"APIConnectionTimeoutError",
|
|
855
|
+
// OpenAI Node SDK timeout subclass
|
|
856
|
+
"APIConnectionError",
|
|
857
|
+
"InternalServerError"
|
|
858
|
+
]);
|
|
859
|
+
function findConsolidationCandidates(memories, config, pairs) {
|
|
860
|
+
const cfg = (config ?? new MnemonicConfig()).consolidation;
|
|
861
|
+
const episodic = memories.filter(
|
|
862
|
+
(m) => m.tierName === "episodic" && m.embedding != null
|
|
863
|
+
);
|
|
864
|
+
if (episodic.length < cfg.minClusterSize) return [];
|
|
865
|
+
if (pairs != null) {
|
|
866
|
+
return clusterFromPairs(episodic, pairs, cfg.minClusterSize);
|
|
867
|
+
}
|
|
868
|
+
return clusterFromMatrix(episodic, cfg.similarityThreshold, cfg.minClusterSize);
|
|
869
|
+
}
|
|
870
|
+
function unionFindClusters(items, edges, minClusterSize) {
|
|
871
|
+
const n = items.length;
|
|
872
|
+
const parent = Array.from({ length: n }, (_, i) => i);
|
|
873
|
+
function find(x) {
|
|
874
|
+
while (parent[x] !== x) {
|
|
875
|
+
parent[x] = parent[parent[x]];
|
|
876
|
+
x = parent[x];
|
|
877
|
+
}
|
|
878
|
+
return x;
|
|
879
|
+
}
|
|
880
|
+
for (const [a, b] of edges) {
|
|
881
|
+
const ra = find(a);
|
|
882
|
+
const rb = find(b);
|
|
883
|
+
if (ra !== rb) parent[ra] = rb;
|
|
884
|
+
}
|
|
885
|
+
const clusters = /* @__PURE__ */ new Map();
|
|
886
|
+
for (let i = 0; i < n; i++) {
|
|
887
|
+
const root = find(i);
|
|
888
|
+
if (!clusters.has(root)) clusters.set(root, []);
|
|
889
|
+
clusters.get(root).push(items[i]);
|
|
890
|
+
}
|
|
891
|
+
return [...clusters.values()].filter((c) => c.length >= minClusterSize);
|
|
892
|
+
}
|
|
893
|
+
function clusterFromPairs(episodic, pairs, minClusterSize) {
|
|
894
|
+
const idToIdx = /* @__PURE__ */ new Map();
|
|
895
|
+
for (let i = 0; i < episodic.length; i++) {
|
|
896
|
+
idToIdx.set(episodic[i].id, i);
|
|
897
|
+
}
|
|
898
|
+
const edges = [];
|
|
899
|
+
for (const p of pairs) {
|
|
900
|
+
const ia = idToIdx.get(p.idA);
|
|
901
|
+
const ib = idToIdx.get(p.idB);
|
|
902
|
+
if (ia != null && ib != null) {
|
|
903
|
+
edges.push([ia, ib]);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
return unionFindClusters(episodic, edges, minClusterSize);
|
|
907
|
+
}
|
|
908
|
+
function clusterFromMatrix(episodic, similarityThreshold, minClusterSize) {
|
|
909
|
+
const n = episodic.length;
|
|
910
|
+
const simMatrix = cosineSimilarityMatrix(episodic.map((m) => m.embedding));
|
|
911
|
+
const edges = [];
|
|
912
|
+
for (let i = 0; i < n; i++) {
|
|
913
|
+
for (let j = i + 1; j < n; j++) {
|
|
914
|
+
if (simMatrix[i * n + j] >= similarityThreshold) {
|
|
915
|
+
edges.push([i, j]);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
return unionFindClusters(episodic, edges, minClusterSize);
|
|
920
|
+
}
|
|
921
|
+
function defaultJoin(cluster, maxLength) {
|
|
922
|
+
const sorted = [...cluster].sort(
|
|
923
|
+
(a, b) => b.createdAt.getTime() - a.createdAt.getTime()
|
|
924
|
+
);
|
|
925
|
+
const unique = [];
|
|
926
|
+
for (const mem of sorted) {
|
|
927
|
+
if (unique.some((prev) => keywordOverlap(mem.content, prev) > 0.8)) continue;
|
|
928
|
+
unique.push(mem.content);
|
|
929
|
+
}
|
|
930
|
+
const header = `(${cluster.length} memories, ${unique.length} unique)`;
|
|
931
|
+
let combined = header + "\n" + unique.join("\n");
|
|
932
|
+
if (combined.length > maxLength) {
|
|
933
|
+
combined = combined.slice(0, maxLength - 3) + "...";
|
|
934
|
+
}
|
|
935
|
+
return combined;
|
|
936
|
+
}
|
|
937
|
+
async function mergeCluster(cluster, config) {
|
|
938
|
+
const cfg = (config ?? new MnemonicConfig()).consolidation;
|
|
939
|
+
let combined;
|
|
940
|
+
if (cfg.summarizer != null) {
|
|
941
|
+
try {
|
|
942
|
+
combined = (await cfg.summarizer(cluster.map((m) => m.content))).trim();
|
|
943
|
+
if (!combined) {
|
|
944
|
+
throw new Error("Summarizer returned empty content");
|
|
945
|
+
}
|
|
946
|
+
} catch (err) {
|
|
947
|
+
const errName = err instanceof Error ? err.constructor.name : "";
|
|
948
|
+
if (TRANSIENT_ERRORS.has(errName)) {
|
|
949
|
+
throw err;
|
|
950
|
+
}
|
|
951
|
+
console.warn(`Summarizer failed permanently with ${errName}; falling back to default join`);
|
|
952
|
+
combined = defaultJoin(cluster, cfg.maxContentLength);
|
|
953
|
+
}
|
|
954
|
+
if (combined.length > cfg.maxContentLength) {
|
|
955
|
+
combined = combined.slice(0, cfg.maxContentLength - 3) + "...";
|
|
956
|
+
}
|
|
957
|
+
} else {
|
|
958
|
+
combined = defaultJoin(cluster, cfg.maxContentLength);
|
|
959
|
+
}
|
|
960
|
+
const summary = new Memory({
|
|
961
|
+
content: `${cfg.prefix} ${combined}`,
|
|
962
|
+
tier: "structural",
|
|
963
|
+
source: cfg.sourceName,
|
|
964
|
+
actor: cfg.actorName,
|
|
965
|
+
metadata: {
|
|
966
|
+
consolidated_from: cluster.map((m) => m.id),
|
|
967
|
+
original_count: cluster.length
|
|
968
|
+
}
|
|
969
|
+
});
|
|
970
|
+
summary.importance = Math.max(...cluster.map((m) => m.importance));
|
|
971
|
+
const linkedSet = new Set(cluster.flatMap((m) => m.linkedIds));
|
|
972
|
+
summary.linkedIds = [...linkedSet];
|
|
973
|
+
return summary;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// src/stores/sync-adapter.ts
|
|
977
|
+
function syncToAsync(store) {
|
|
978
|
+
return {
|
|
979
|
+
async initialize() {
|
|
980
|
+
store.initialize();
|
|
981
|
+
},
|
|
982
|
+
async close() {
|
|
983
|
+
store.close();
|
|
984
|
+
},
|
|
985
|
+
async save(memory) {
|
|
986
|
+
store.save(memory);
|
|
987
|
+
},
|
|
988
|
+
async get(memoryId) {
|
|
989
|
+
return store.get(memoryId);
|
|
990
|
+
},
|
|
991
|
+
async listAll(options) {
|
|
992
|
+
return store.listAll(options);
|
|
993
|
+
},
|
|
994
|
+
async delete(memoryId) {
|
|
995
|
+
store.delete(memoryId);
|
|
996
|
+
},
|
|
997
|
+
async deleteOlderThan(ageMs, tier, options) {
|
|
998
|
+
return store.deleteOlderThan(ageMs, tier, options);
|
|
999
|
+
},
|
|
1000
|
+
async saveEdge(edge) {
|
|
1001
|
+
store.saveEdge(edge);
|
|
1002
|
+
},
|
|
1003
|
+
async getEdges(memoryId) {
|
|
1004
|
+
return store.getEdges(memoryId);
|
|
1005
|
+
},
|
|
1006
|
+
async searchByEmbedding(embedding, limit, options) {
|
|
1007
|
+
return store.searchByEmbedding(embedding, limit, options);
|
|
1008
|
+
},
|
|
1009
|
+
async findSimilarPairs(options) {
|
|
1010
|
+
return store.findSimilarPairs(options);
|
|
1011
|
+
},
|
|
1012
|
+
async getMeta(key) {
|
|
1013
|
+
return store.getMeta(key);
|
|
1014
|
+
},
|
|
1015
|
+
async setMeta(key, value) {
|
|
1016
|
+
store.setMeta(key, value);
|
|
1017
|
+
}
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// src/stores/sqlite.ts
|
|
1022
|
+
var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
|
|
1023
|
+
var SCHEMA = `
|
|
1024
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
1025
|
+
id TEXT PRIMARY KEY,
|
|
1026
|
+
content TEXT NOT NULL,
|
|
1027
|
+
tier TEXT NOT NULL,
|
|
1028
|
+
source TEXT NOT NULL DEFAULT 'unknown',
|
|
1029
|
+
actor TEXT NOT NULL DEFAULT 'system',
|
|
1030
|
+
session_id TEXT,
|
|
1031
|
+
created_at TEXT NOT NULL,
|
|
1032
|
+
last_accessed TEXT NOT NULL,
|
|
1033
|
+
access_count INTEGER NOT NULL DEFAULT 0,
|
|
1034
|
+
importance REAL NOT NULL DEFAULT 1.0,
|
|
1035
|
+
superseded_by TEXT,
|
|
1036
|
+
contradicted_by TEXT,
|
|
1037
|
+
embedding TEXT,
|
|
1038
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
1039
|
+
linked_ids TEXT NOT NULL DEFAULT '[]',
|
|
1040
|
+
agent_id TEXT NOT NULL DEFAULT 'default',
|
|
1041
|
+
shared INTEGER NOT NULL DEFAULT 0
|
|
1042
|
+
);
|
|
1043
|
+
|
|
1044
|
+
CREATE TABLE IF NOT EXISTS edges (
|
|
1045
|
+
source_id TEXT NOT NULL,
|
|
1046
|
+
target_id TEXT NOT NULL,
|
|
1047
|
+
relation TEXT NOT NULL,
|
|
1048
|
+
weight REAL NOT NULL DEFAULT 1.0,
|
|
1049
|
+
created_at TEXT NOT NULL,
|
|
1050
|
+
PRIMARY KEY (source_id, target_id, relation)
|
|
1051
|
+
);
|
|
1052
|
+
|
|
1053
|
+
CREATE TABLE IF NOT EXISTS meta (
|
|
1054
|
+
key TEXT PRIMARY KEY,
|
|
1055
|
+
value TEXT NOT NULL
|
|
1056
|
+
);
|
|
1057
|
+
|
|
1058
|
+
CREATE INDEX IF NOT EXISTS idx_memories_agent_id ON memories (agent_id);
|
|
1059
|
+
`;
|
|
1060
|
+
var SQLiteStore = class {
|
|
1061
|
+
_db;
|
|
1062
|
+
constructor(path = "mnemonic.db") {
|
|
1063
|
+
this._db = new import_better_sqlite3.default(path);
|
|
1064
|
+
this._db.pragma("journal_mode = WAL");
|
|
1065
|
+
}
|
|
1066
|
+
initialize() {
|
|
1067
|
+
this._db.exec(SCHEMA);
|
|
1068
|
+
this._migrate();
|
|
1069
|
+
}
|
|
1070
|
+
close() {
|
|
1071
|
+
this._db.close();
|
|
1072
|
+
}
|
|
1073
|
+
/** Add missing columns/tables for stores created before multi-agent support. */
|
|
1074
|
+
_migrate() {
|
|
1075
|
+
const cols = this._db.prepare("PRAGMA table_info(memories)").all();
|
|
1076
|
+
const colNames = new Set(cols.map((c) => c.name));
|
|
1077
|
+
if (!colNames.has("agent_id")) {
|
|
1078
|
+
this._db.exec(
|
|
1079
|
+
"ALTER TABLE memories ADD COLUMN agent_id TEXT NOT NULL DEFAULT 'default'"
|
|
1080
|
+
);
|
|
1081
|
+
this._db.exec(
|
|
1082
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_agent_id ON memories (agent_id)"
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
if (!colNames.has("shared")) {
|
|
1086
|
+
this._db.exec(
|
|
1087
|
+
"ALTER TABLE memories ADD COLUMN shared INTEGER NOT NULL DEFAULT 0"
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
1090
|
+
if (!colNames.has("contradicted_by")) {
|
|
1091
|
+
this._db.exec(
|
|
1092
|
+
"ALTER TABLE memories ADD COLUMN contradicted_by TEXT"
|
|
1093
|
+
);
|
|
1094
|
+
this._db.exec(
|
|
1095
|
+
"UPDATE memories SET contradicted_by = json_extract(metadata, '$.contradicted_by') WHERE contradicted_by IS NULL AND json_extract(metadata, '$.contradicted_by') IS NOT NULL"
|
|
1096
|
+
);
|
|
1097
|
+
}
|
|
1098
|
+
this._db.exec(
|
|
1099
|
+
"CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)"
|
|
1100
|
+
);
|
|
1101
|
+
}
|
|
1102
|
+
save(memory) {
|
|
1103
|
+
this._db.prepare(
|
|
1104
|
+
`INSERT OR REPLACE INTO memories
|
|
1105
|
+
(id, content, tier, source, actor, session_id, created_at,
|
|
1106
|
+
last_accessed, access_count, importance, superseded_by,
|
|
1107
|
+
contradicted_by, embedding, metadata, linked_ids, agent_id, shared)
|
|
1108
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
1109
|
+
).run(
|
|
1110
|
+
memory.id,
|
|
1111
|
+
memory.content,
|
|
1112
|
+
memory.tierName,
|
|
1113
|
+
memory.source,
|
|
1114
|
+
memory.actor,
|
|
1115
|
+
memory.sessionId,
|
|
1116
|
+
memory.createdAt.toISOString(),
|
|
1117
|
+
memory.lastAccessedAt.toISOString(),
|
|
1118
|
+
memory.accessCount,
|
|
1119
|
+
memory.importance,
|
|
1120
|
+
memory.supersededBy,
|
|
1121
|
+
memory.contradictedBy ?? null,
|
|
1122
|
+
memory.embedding ? JSON.stringify(memory.embedding) : null,
|
|
1123
|
+
JSON.stringify(memory.metadata),
|
|
1124
|
+
JSON.stringify(memory.linkedIds),
|
|
1125
|
+
memory.agentId,
|
|
1126
|
+
memory.shared ? 1 : 0
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1129
|
+
get(memoryId) {
|
|
1130
|
+
const row = this._db.prepare("SELECT * FROM memories WHERE id = ?").get(memoryId);
|
|
1131
|
+
if (!row) return null;
|
|
1132
|
+
return rowToMemory(row);
|
|
1133
|
+
}
|
|
1134
|
+
listAll(options) {
|
|
1135
|
+
const clauses = [];
|
|
1136
|
+
const params = [];
|
|
1137
|
+
if (options?.agentId) {
|
|
1138
|
+
if (options.includeShared === false) {
|
|
1139
|
+
clauses.push("agent_id = ?");
|
|
1140
|
+
params.push(options.agentId);
|
|
1141
|
+
} else {
|
|
1142
|
+
clauses.push("(agent_id = ? OR shared = 1)");
|
|
1143
|
+
params.push(options.agentId);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
if (options?.tier != null) {
|
|
1147
|
+
clauses.push("tier = ?");
|
|
1148
|
+
params.push(options.tier);
|
|
1149
|
+
}
|
|
1150
|
+
const where = clauses.length > 0 ? ` WHERE ${clauses.join(" AND ")}` : "";
|
|
1151
|
+
let limitClause = "";
|
|
1152
|
+
if (options?.limit != null) {
|
|
1153
|
+
limitClause = " LIMIT ?";
|
|
1154
|
+
params.push(options.limit);
|
|
1155
|
+
}
|
|
1156
|
+
const rows = this._db.prepare(`SELECT * FROM memories${where}${limitClause}`).all(...params);
|
|
1157
|
+
return rows.map(rowToMemory);
|
|
1158
|
+
}
|
|
1159
|
+
delete(memoryId) {
|
|
1160
|
+
this._db.transaction(() => {
|
|
1161
|
+
this._db.prepare("DELETE FROM memories WHERE id = ?").run(memoryId);
|
|
1162
|
+
this._db.prepare("DELETE FROM edges WHERE source_id = ? OR target_id = ?").run(memoryId, memoryId);
|
|
1163
|
+
})();
|
|
1164
|
+
}
|
|
1165
|
+
deleteOlderThan(ageMs, tier, options) {
|
|
1166
|
+
const cutoff = new Date(Date.now() - ageMs).toISOString();
|
|
1167
|
+
const conditions = ["created_at < ?"];
|
|
1168
|
+
const params = [cutoff];
|
|
1169
|
+
if (tier != null) {
|
|
1170
|
+
conditions.push("tier = ?");
|
|
1171
|
+
params.push(tier);
|
|
1172
|
+
}
|
|
1173
|
+
if (options?.agentId) {
|
|
1174
|
+
conditions.push("agent_id = ?");
|
|
1175
|
+
params.push(options.agentId);
|
|
1176
|
+
}
|
|
1177
|
+
const where = conditions.join(" AND ");
|
|
1178
|
+
const ids = this._db.prepare(`SELECT id FROM memories WHERE ${where}`).all(...params);
|
|
1179
|
+
if (ids.length === 0) return 0;
|
|
1180
|
+
const idList = ids.map((r) => r.id);
|
|
1181
|
+
const placeholders = idList.map(() => "?").join(",");
|
|
1182
|
+
const txn = this._db.transaction(() => {
|
|
1183
|
+
this._db.prepare(
|
|
1184
|
+
`DELETE FROM edges WHERE source_id IN (${placeholders}) OR target_id IN (${placeholders})`
|
|
1185
|
+
).run(...idList, ...idList);
|
|
1186
|
+
const result = this._db.prepare(`DELETE FROM memories WHERE ${where}`).run(...params);
|
|
1187
|
+
return result.changes;
|
|
1188
|
+
});
|
|
1189
|
+
return txn();
|
|
1190
|
+
}
|
|
1191
|
+
saveEdge(edge) {
|
|
1192
|
+
this._db.prepare(
|
|
1193
|
+
`INSERT OR REPLACE INTO edges (source_id, target_id, relation, weight, created_at)
|
|
1194
|
+
VALUES (?, ?, ?, ?, ?)`
|
|
1195
|
+
).run(
|
|
1196
|
+
edge.sourceId,
|
|
1197
|
+
edge.targetId,
|
|
1198
|
+
edge.relation,
|
|
1199
|
+
edge.weight,
|
|
1200
|
+
edge.createdAt.toISOString()
|
|
1201
|
+
);
|
|
1202
|
+
}
|
|
1203
|
+
getEdges(memoryId) {
|
|
1204
|
+
const rows = this._db.prepare("SELECT * FROM edges WHERE source_id = ? OR target_id = ?").all(memoryId, memoryId);
|
|
1205
|
+
return rows.map((r) => ({
|
|
1206
|
+
sourceId: r["source_id"],
|
|
1207
|
+
targetId: r["target_id"],
|
|
1208
|
+
relation: r["relation"],
|
|
1209
|
+
weight: r["weight"],
|
|
1210
|
+
createdAt: new Date(r["created_at"])
|
|
1211
|
+
}));
|
|
1212
|
+
}
|
|
1213
|
+
searchByEmbedding(embedding, limit = 50, options) {
|
|
1214
|
+
const all = this.listAll(options);
|
|
1215
|
+
return bruteForceCosineSearch(all, embedding, limit);
|
|
1216
|
+
}
|
|
1217
|
+
findSimilarPairs(options) {
|
|
1218
|
+
const maxCandidates = options?.maxCandidates ?? 1e3;
|
|
1219
|
+
const memories = this.listAll({
|
|
1220
|
+
agentId: options?.agentId,
|
|
1221
|
+
includeShared: false,
|
|
1222
|
+
tier: options?.tier
|
|
1223
|
+
});
|
|
1224
|
+
return bruteForceSimilarPairs(
|
|
1225
|
+
memories,
|
|
1226
|
+
options?.threshold ?? 0.7,
|
|
1227
|
+
maxCandidates
|
|
1228
|
+
);
|
|
1229
|
+
}
|
|
1230
|
+
getMeta(key) {
|
|
1231
|
+
const row = this._db.prepare("SELECT value FROM meta WHERE key = ?").get(key);
|
|
1232
|
+
return row?.value ?? null;
|
|
1233
|
+
}
|
|
1234
|
+
setMeta(key, value) {
|
|
1235
|
+
this._db.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)").run(key, value);
|
|
1236
|
+
}
|
|
1237
|
+
};
|
|
1238
|
+
function rowToMemory(row) {
|
|
1239
|
+
const embRaw = row["embedding"];
|
|
1240
|
+
const m = new Memory({
|
|
1241
|
+
id: row["id"],
|
|
1242
|
+
content: row["content"],
|
|
1243
|
+
tier: row["tier"],
|
|
1244
|
+
source: row["source"],
|
|
1245
|
+
actor: row["actor"],
|
|
1246
|
+
sessionId: row["session_id"],
|
|
1247
|
+
createdAt: new Date(row["created_at"]),
|
|
1248
|
+
lastAccessedAt: new Date(row["last_accessed"]),
|
|
1249
|
+
accessCount: row["access_count"],
|
|
1250
|
+
importance: row["importance"],
|
|
1251
|
+
supersededBy: row["superseded_by"],
|
|
1252
|
+
embedding: embRaw ? JSON.parse(embRaw) : null,
|
|
1253
|
+
metadata: JSON.parse(row["metadata"]),
|
|
1254
|
+
linkedIds: JSON.parse(row["linked_ids"]),
|
|
1255
|
+
agentId: row["agent_id"] ?? "default",
|
|
1256
|
+
shared: row["shared"] === 1 || row["shared"] === true
|
|
1257
|
+
});
|
|
1258
|
+
m.contradictedBy = row["contradicted_by"];
|
|
1259
|
+
return m;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
// src/client.ts
|
|
1263
|
+
var _storeRegistry = /* @__PURE__ */ new Map();
|
|
1264
|
+
function sanitizeUri(uri) {
|
|
1265
|
+
try {
|
|
1266
|
+
const url = new URL(uri);
|
|
1267
|
+
if (url.username || url.password) {
|
|
1268
|
+
url.username = "***";
|
|
1269
|
+
url.password = "";
|
|
1270
|
+
}
|
|
1271
|
+
return url.toString();
|
|
1272
|
+
} catch {
|
|
1273
|
+
return uri.includes("://") ? uri.split("://")[0] + "://***" : uri;
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
function parseStoreUri(uri, embeddingDim) {
|
|
1277
|
+
if (uri.startsWith("sqlite:")) {
|
|
1278
|
+
return new SQLiteStore(parseSqlitePath(uri));
|
|
1279
|
+
}
|
|
1280
|
+
if (uri.startsWith("postgres://") || uri.startsWith("postgresql://")) {
|
|
1281
|
+
throw new Error(
|
|
1282
|
+
"PostgresStore cannot be resolved from URI synchronously. Import PostgresStore directly and pass it as the store option."
|
|
1283
|
+
);
|
|
1284
|
+
}
|
|
1285
|
+
for (const [scheme, factory] of _storeRegistry) {
|
|
1286
|
+
if (uri.startsWith(`${scheme}://`) || uri.startsWith(`${scheme}+`)) {
|
|
1287
|
+
const result = factory(uri, embeddingDim);
|
|
1288
|
+
if (result instanceof Promise) {
|
|
1289
|
+
throw new Error(
|
|
1290
|
+
`Store factory for '${scheme}' returned a Promise. Async store factories must be resolved before passing to Mnemonic.`
|
|
1291
|
+
);
|
|
1292
|
+
}
|
|
1293
|
+
return result;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
throw new Error(`Unsupported store URI: '${sanitizeUri(uri)}'`);
|
|
1297
|
+
}
|
|
1298
|
+
var Mnemonic = class _Mnemonic {
|
|
1299
|
+
_config;
|
|
1300
|
+
_store;
|
|
1301
|
+
_embedder;
|
|
1302
|
+
_autoClassify;
|
|
1303
|
+
_agentId;
|
|
1304
|
+
_resolvedAgentId = null;
|
|
1305
|
+
_initPromise = null;
|
|
1306
|
+
constructor(options) {
|
|
1307
|
+
this._config = options?.config ?? new MnemonicConfig();
|
|
1308
|
+
this._embedder = options?.embedder ?? null;
|
|
1309
|
+
this._autoClassify = options?.autoClassify ?? true;
|
|
1310
|
+
this._agentId = options?.agentId ?? null;
|
|
1311
|
+
const storeOpt = options?.store ?? "sqlite:///mnemonic.db";
|
|
1312
|
+
if (typeof storeOpt === "string") {
|
|
1313
|
+
const raw = parseStoreUri(storeOpt, this._embedder?.dimension);
|
|
1314
|
+
this._store = _wrapIfSync(raw);
|
|
1315
|
+
} else {
|
|
1316
|
+
this._store = _wrapIfSync(storeOpt);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
get config() {
|
|
1320
|
+
return this._config;
|
|
1321
|
+
}
|
|
1322
|
+
/** The underlying async store. */
|
|
1323
|
+
get store() {
|
|
1324
|
+
return this._store;
|
|
1325
|
+
}
|
|
1326
|
+
/** The embedder (if configured). */
|
|
1327
|
+
get embedder() {
|
|
1328
|
+
return this._embedder;
|
|
1329
|
+
}
|
|
1330
|
+
/** The resolved agent ID (available after init()). */
|
|
1331
|
+
get agentId() {
|
|
1332
|
+
if (!this._resolvedAgentId) {
|
|
1333
|
+
throw new Error("Client not initialized. Call init() first.");
|
|
1334
|
+
}
|
|
1335
|
+
return this._resolvedAgentId;
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* Initialize the store and resolve agent identity. Must be called before use.
|
|
1339
|
+
* Pass `{ skipStoreInit: true }` when reusing an already-initialized store
|
|
1340
|
+
* (e.g. creating per-agent clients that share a single backend).
|
|
1341
|
+
*/
|
|
1342
|
+
async init(options) {
|
|
1343
|
+
if (this._resolvedAgentId) return;
|
|
1344
|
+
if (this._initPromise) {
|
|
1345
|
+
await this._initPromise;
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
this._initPromise = (async () => {
|
|
1349
|
+
if (!options?.skipStoreInit) {
|
|
1350
|
+
await this._store.initialize();
|
|
1351
|
+
}
|
|
1352
|
+
await this._resolveAgentId();
|
|
1353
|
+
})();
|
|
1354
|
+
try {
|
|
1355
|
+
await this._initPromise;
|
|
1356
|
+
} finally {
|
|
1357
|
+
this._initPromise = null;
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
async _resolveAgentId() {
|
|
1361
|
+
if (this._agentId) {
|
|
1362
|
+
this._resolvedAgentId = this._agentId;
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
const persisted = await this._store.getMeta("agent_id");
|
|
1366
|
+
if (persisted) {
|
|
1367
|
+
this._resolvedAgentId = persisted;
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
const generated = (0, import_node_crypto2.randomUUID)().replace(/-/g, "");
|
|
1371
|
+
await this._store.setMeta("agent_id", generated);
|
|
1372
|
+
this._resolvedAgentId = generated;
|
|
1373
|
+
}
|
|
1374
|
+
_ensureInitialized() {
|
|
1375
|
+
if (!this._resolvedAgentId) {
|
|
1376
|
+
throw new Error("Client not initialized. Call init() first.");
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
/** Return only memories owned by this agent (excludes shared from others). */
|
|
1380
|
+
async _ownMemories() {
|
|
1381
|
+
return this._store.listAll({
|
|
1382
|
+
agentId: this._resolvedAgentId,
|
|
1383
|
+
includeShared: false
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1386
|
+
static _contentHash(content) {
|
|
1387
|
+
return (0, import_node_crypto2.createHash)("sha256").update(content).digest("hex");
|
|
1388
|
+
}
|
|
1389
|
+
/**
|
|
1390
|
+
* Store a memory with automatic classification and linking.
|
|
1391
|
+
* If identical content already exists for this agent, returns the existing memory.
|
|
1392
|
+
*/
|
|
1393
|
+
async add(content, options) {
|
|
1394
|
+
this._ensureInitialized();
|
|
1395
|
+
const contentHash = _Mnemonic._contentHash(content);
|
|
1396
|
+
const knownMemories = await this._store.listAll({ agentId: this._resolvedAgentId });
|
|
1397
|
+
for (const mem of knownMemories) {
|
|
1398
|
+
if (mem.agentId === this._resolvedAgentId && _Mnemonic._contentHash(mem.content) === contentHash) {
|
|
1399
|
+
return mem;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
const source = options?.source ?? "unknown";
|
|
1403
|
+
const actor = options?.actor ?? "system";
|
|
1404
|
+
let resolvedTier;
|
|
1405
|
+
if (options?.tier != null) {
|
|
1406
|
+
resolvedTier = options.tier;
|
|
1407
|
+
} else if (this._autoClassify) {
|
|
1408
|
+
resolvedTier = classify(content, {
|
|
1409
|
+
source,
|
|
1410
|
+
actor,
|
|
1411
|
+
config: this._config
|
|
1412
|
+
});
|
|
1413
|
+
} else {
|
|
1414
|
+
resolvedTier = this._config.defaultTier;
|
|
1415
|
+
}
|
|
1416
|
+
const memory = new Memory({
|
|
1417
|
+
content,
|
|
1418
|
+
tier: resolvedTier,
|
|
1419
|
+
source,
|
|
1420
|
+
actor,
|
|
1421
|
+
sessionId: options?.sessionId ?? null,
|
|
1422
|
+
metadata: options?.metadata ?? {},
|
|
1423
|
+
agentId: this._resolvedAgentId,
|
|
1424
|
+
shared: options?.shared ?? false,
|
|
1425
|
+
charsPerToken: this._config.charsPerToken
|
|
1426
|
+
});
|
|
1427
|
+
if (this._embedder) {
|
|
1428
|
+
memory.embedding = await this._embedder.embed(content);
|
|
1429
|
+
}
|
|
1430
|
+
const salience = detectSalience(content, this._config.salience);
|
|
1431
|
+
if (salience.boost > 0) {
|
|
1432
|
+
memory.metadata["salience_signals"] = salience.signals;
|
|
1433
|
+
memory.metadata["boost"] = (memory.metadata["boost"] ?? 0) + salience.boost;
|
|
1434
|
+
}
|
|
1435
|
+
memory.importance = score(memory, this._config);
|
|
1436
|
+
const existing = await this._store.listAll({
|
|
1437
|
+
agentId: this._resolvedAgentId
|
|
1438
|
+
});
|
|
1439
|
+
const edges = findLinks(memory, existing, { config: this._config });
|
|
1440
|
+
await this._store.save(memory);
|
|
1441
|
+
for (const edge of edges) {
|
|
1442
|
+
memory.linkedIds.push(edge.targetId);
|
|
1443
|
+
await this._store.saveEdge(edge);
|
|
1444
|
+
const linked = await this._store.get(edge.targetId);
|
|
1445
|
+
if (linked) {
|
|
1446
|
+
if (linked.agentId === this._resolvedAgentId) {
|
|
1447
|
+
if (!linked.linkedIds.includes(memory.id)) {
|
|
1448
|
+
linked.linkedIds.push(memory.id);
|
|
1449
|
+
}
|
|
1450
|
+
if (edge.relation === "supersedes" && linked.supersededBy == null) {
|
|
1451
|
+
linked.supersededBy = memory.id;
|
|
1452
|
+
} else if (edge.relation === "contradicts" && linked.contradictedBy == null) {
|
|
1453
|
+
linked.contradictedBy = memory.id;
|
|
1454
|
+
}
|
|
1455
|
+
await this._store.save(linked);
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
await this._store.save(memory);
|
|
1460
|
+
return memory;
|
|
1461
|
+
}
|
|
1462
|
+
async _assemble(query, options) {
|
|
1463
|
+
let queryEmbedding = null;
|
|
1464
|
+
const limit = this._config.embeddingSearchLimit;
|
|
1465
|
+
let candidates;
|
|
1466
|
+
if (this._embedder) {
|
|
1467
|
+
queryEmbedding = await this._embedder.embed(query);
|
|
1468
|
+
candidates = await this._store.searchByEmbedding(
|
|
1469
|
+
queryEmbedding,
|
|
1470
|
+
limit,
|
|
1471
|
+
{ agentId: this._resolvedAgentId }
|
|
1472
|
+
);
|
|
1473
|
+
} else {
|
|
1474
|
+
candidates = await this._store.listAll({
|
|
1475
|
+
agentId: this._resolvedAgentId
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
if (options?.excludeSuperseded) {
|
|
1479
|
+
candidates = candidates.filter((m) => m.supersededBy == null);
|
|
1480
|
+
}
|
|
1481
|
+
const selected = assemble(query, candidates, {
|
|
1482
|
+
queryEmbedding,
|
|
1483
|
+
maxTokens: options?.maxTokens,
|
|
1484
|
+
minRelevance: options?.minRelevance,
|
|
1485
|
+
config: this._config
|
|
1486
|
+
});
|
|
1487
|
+
await Promise.all(selected.map((mem) => this._store.save(mem)));
|
|
1488
|
+
return selected;
|
|
1489
|
+
}
|
|
1490
|
+
/** Retrieve optimised context for a given query within a token budget. */
|
|
1491
|
+
async recall(query, options) {
|
|
1492
|
+
this._ensureInitialized();
|
|
1493
|
+
const selected = await this._assemble(query, {
|
|
1494
|
+
...options,
|
|
1495
|
+
excludeSuperseded: true
|
|
1496
|
+
});
|
|
1497
|
+
const conflicts = detectConflicts(selected, this._config);
|
|
1498
|
+
return formatContext(selected, options?.header, conflicts);
|
|
1499
|
+
}
|
|
1500
|
+
/** Like recall but returns the raw Memory objects. */
|
|
1501
|
+
async recallMemories(query, options) {
|
|
1502
|
+
this._ensureInitialized();
|
|
1503
|
+
return this._assemble(query, options);
|
|
1504
|
+
}
|
|
1505
|
+
/** Delete memories matching the given criteria. Returns count deleted. */
|
|
1506
|
+
async forget(options) {
|
|
1507
|
+
this._ensureInitialized();
|
|
1508
|
+
if (!options?.olderThan && !options?.tier && options?.belowImportance == null) {
|
|
1509
|
+
throw new Error(
|
|
1510
|
+
"forget() requires at least one filter (olderThan, tier, or belowImportance)"
|
|
1511
|
+
);
|
|
1512
|
+
}
|
|
1513
|
+
const tier = options?.tier;
|
|
1514
|
+
if (options?.belowImportance == null) {
|
|
1515
|
+
const olderThan = options?.olderThan?.trim();
|
|
1516
|
+
if (olderThan === "") {
|
|
1517
|
+
throw new Error("olderThan must be a valid duration string (e.g. '30d'), not empty");
|
|
1518
|
+
}
|
|
1519
|
+
const ageMs = olderThan ? parseDuration(olderThan) : 0;
|
|
1520
|
+
return this._store.deleteOlderThan(ageMs, tier, {
|
|
1521
|
+
agentId: this._resolvedAgentId
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
let candidates = await this._store.listAll({
|
|
1525
|
+
agentId: this._resolvedAgentId,
|
|
1526
|
+
includeShared: false,
|
|
1527
|
+
tier: tier ?? void 0
|
|
1528
|
+
});
|
|
1529
|
+
if (options?.olderThan != null) {
|
|
1530
|
+
const ageMs = parseDuration(options.olderThan);
|
|
1531
|
+
const cutoff = Date.now() - ageMs;
|
|
1532
|
+
candidates = candidates.filter((m) => m.createdAt.getTime() < cutoff);
|
|
1533
|
+
}
|
|
1534
|
+
let deleted = 0;
|
|
1535
|
+
for (const mem of candidates) {
|
|
1536
|
+
const currentImportance = score(mem, this._config);
|
|
1537
|
+
if (currentImportance < options.belowImportance) {
|
|
1538
|
+
await this._store.delete(mem.id);
|
|
1539
|
+
deleted++;
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
return deleted;
|
|
1543
|
+
}
|
|
1544
|
+
/**
|
|
1545
|
+
* Merge similar episodic memories into higher-tier summaries.
|
|
1546
|
+
*
|
|
1547
|
+
* Originals are kept alive but deprioritised via a scoring penalty
|
|
1548
|
+
* and `consolidated_into` metadata. A `consolidates` edge links
|
|
1549
|
+
* the summary to each original for graph traversal.
|
|
1550
|
+
*/
|
|
1551
|
+
async consolidate() {
|
|
1552
|
+
this._ensureInitialized();
|
|
1553
|
+
const ownEpisodic = await this._store.listAll({
|
|
1554
|
+
agentId: this._resolvedAgentId,
|
|
1555
|
+
includeShared: false,
|
|
1556
|
+
tier: MemoryTier.EPISODIC
|
|
1557
|
+
});
|
|
1558
|
+
const eligible = [];
|
|
1559
|
+
for (const m of ownEpisodic) {
|
|
1560
|
+
const summaryId = m.metadata["consolidated_into"];
|
|
1561
|
+
if (summaryId == null || await this._store.get(summaryId) == null) {
|
|
1562
|
+
eligible.push(m);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
const privateMemories = eligible.filter((m) => !m.shared);
|
|
1566
|
+
const sharedMemories = eligible.filter((m) => m.shared);
|
|
1567
|
+
const cfg = this._config.consolidation;
|
|
1568
|
+
const eligibleIds = new Set(eligible.map((m) => m.id));
|
|
1569
|
+
const allPairs = await this._store.findSimilarPairs({
|
|
1570
|
+
threshold: cfg.similarityThreshold,
|
|
1571
|
+
agentId: this._resolvedAgentId,
|
|
1572
|
+
tier: MemoryTier.EPISODIC,
|
|
1573
|
+
maxCandidates: cfg.maxCandidates,
|
|
1574
|
+
maxNeighborsPerCandidate: cfg.maxNeighborsPerCandidate
|
|
1575
|
+
});
|
|
1576
|
+
const eligiblePairs = allPairs.filter(
|
|
1577
|
+
(p) => eligibleIds.has(p.idA) && eligibleIds.has(p.idB)
|
|
1578
|
+
);
|
|
1579
|
+
const privateIds = new Set(privateMemories.map((m) => m.id));
|
|
1580
|
+
const sharedIds = new Set(sharedMemories.map((m) => m.id));
|
|
1581
|
+
const privatePairs = eligiblePairs.filter(
|
|
1582
|
+
(p) => privateIds.has(p.idA) && privateIds.has(p.idB)
|
|
1583
|
+
);
|
|
1584
|
+
const sharedPairs = eligiblePairs.filter(
|
|
1585
|
+
(p) => sharedIds.has(p.idA) && sharedIds.has(p.idB)
|
|
1586
|
+
);
|
|
1587
|
+
let totalClusters = 0;
|
|
1588
|
+
for (const [memories, pairs, isShared] of [
|
|
1589
|
+
[privateMemories, privatePairs, false],
|
|
1590
|
+
[sharedMemories, sharedPairs, true]
|
|
1591
|
+
]) {
|
|
1592
|
+
const clusters = findConsolidationCandidates(memories, this._config, pairs);
|
|
1593
|
+
const CONCURRENCY = 8;
|
|
1594
|
+
const prepared = [];
|
|
1595
|
+
const processBatch = async (batch) => {
|
|
1596
|
+
const results = await Promise.all(
|
|
1597
|
+
batch.map(async (cluster) => {
|
|
1598
|
+
const summary = await mergeCluster(cluster, this._config);
|
|
1599
|
+
summary.agentId = this._resolvedAgentId;
|
|
1600
|
+
summary.shared = isShared;
|
|
1601
|
+
for (const mem of cluster) {
|
|
1602
|
+
if (!summary.linkedIds.includes(mem.id)) {
|
|
1603
|
+
summary.linkedIds.push(mem.id);
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
if (this._embedder) {
|
|
1607
|
+
summary.embedding = await this._embedder.embed(summary.content);
|
|
1608
|
+
}
|
|
1609
|
+
summary.importance = score(summary, this._config);
|
|
1610
|
+
return { cluster, summary };
|
|
1611
|
+
})
|
|
1612
|
+
);
|
|
1613
|
+
prepared.push(...results);
|
|
1614
|
+
};
|
|
1615
|
+
for (let i = 0; i < clusters.length; i += CONCURRENCY) {
|
|
1616
|
+
await processBatch(clusters.slice(i, i + CONCURRENCY));
|
|
1617
|
+
}
|
|
1618
|
+
for (const { cluster, summary } of prepared) {
|
|
1619
|
+
await this._store.save(summary);
|
|
1620
|
+
for (const mem of cluster) {
|
|
1621
|
+
const edge = createEdge(summary.id, mem.id, "consolidates");
|
|
1622
|
+
await this._store.saveEdge(edge);
|
|
1623
|
+
mem.metadata["consolidated_into"] = summary.id;
|
|
1624
|
+
mem.linkedIds.push(summary.id);
|
|
1625
|
+
mem.importance = score(mem, this._config);
|
|
1626
|
+
await this._store.save(mem);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
totalClusters += clusters.length;
|
|
1630
|
+
}
|
|
1631
|
+
return totalClusters;
|
|
1632
|
+
}
|
|
1633
|
+
/** Run the tier promotion/demotion pipeline on own memories. */
|
|
1634
|
+
async promote() {
|
|
1635
|
+
this._ensureInitialized();
|
|
1636
|
+
const ownMemories = await this._ownMemories();
|
|
1637
|
+
let changes = await this._applyTierResults(
|
|
1638
|
+
checkPromotions(ownMemories, this._config)
|
|
1639
|
+
);
|
|
1640
|
+
const refreshed = await this._ownMemories();
|
|
1641
|
+
changes += await this._applyTierResults(
|
|
1642
|
+
checkDemotions(refreshed, this._config)
|
|
1643
|
+
);
|
|
1644
|
+
return changes;
|
|
1645
|
+
}
|
|
1646
|
+
/** Re-run the linker across all memories to retroactively set edges. */
|
|
1647
|
+
async relink() {
|
|
1648
|
+
this._ensureInitialized();
|
|
1649
|
+
const ownMemories = await this._ownMemories();
|
|
1650
|
+
const visible = await this._store.listAll({ agentId: this._resolvedAgentId });
|
|
1651
|
+
const ownById = new Map(ownMemories.map((m) => [m.id, m]));
|
|
1652
|
+
const existingRelations = /* @__PURE__ */ new Map();
|
|
1653
|
+
for (const mem of ownMemories) {
|
|
1654
|
+
const edges = await this._store.getEdges(mem.id);
|
|
1655
|
+
for (const e of edges) existingRelations.set(`${e.sourceId}:${e.targetId}`, e.relation);
|
|
1656
|
+
}
|
|
1657
|
+
let newEdges = 0;
|
|
1658
|
+
for (const mem of ownMemories) {
|
|
1659
|
+
const edges = findLinks(mem, visible, { config: this._config });
|
|
1660
|
+
for (const edge of edges) {
|
|
1661
|
+
const key = `${edge.sourceId}:${edge.targetId}`;
|
|
1662
|
+
const existingRel = existingRelations.get(key);
|
|
1663
|
+
if (existingRel == null) {
|
|
1664
|
+
await this._store.saveEdge(edge);
|
|
1665
|
+
existingRelations.set(key, edge.relation);
|
|
1666
|
+
newEdges++;
|
|
1667
|
+
} else if (existingRel !== edge.relation) {
|
|
1668
|
+
await this._store.saveEdge(edge);
|
|
1669
|
+
existingRelations.set(key, edge.relation);
|
|
1670
|
+
}
|
|
1671
|
+
if (edge.targetId !== mem.id && !mem.linkedIds.includes(edge.targetId)) {
|
|
1672
|
+
mem.linkedIds.push(edge.targetId);
|
|
1673
|
+
}
|
|
1674
|
+
const linked = ownById.get(edge.targetId);
|
|
1675
|
+
if (linked) {
|
|
1676
|
+
if (!linked.linkedIds.includes(mem.id)) linked.linkedIds.push(mem.id);
|
|
1677
|
+
if (edge.relation === "supersedes" && linked.supersededBy == null) {
|
|
1678
|
+
linked.supersededBy = mem.id;
|
|
1679
|
+
} else if (edge.relation === "contradicts" && linked.contradictedBy == null) {
|
|
1680
|
+
linked.contradictedBy = mem.id;
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
await Promise.all(ownMemories.map((m) => this._store.save(m)));
|
|
1686
|
+
return newEdges;
|
|
1687
|
+
}
|
|
1688
|
+
/** Return aggregate statistics about own memories. */
|
|
1689
|
+
async stats() {
|
|
1690
|
+
this._ensureInitialized();
|
|
1691
|
+
const memories = await this._ownMemories();
|
|
1692
|
+
const byTier = {};
|
|
1693
|
+
let totalTokens = 0;
|
|
1694
|
+
for (const m of memories) {
|
|
1695
|
+
byTier[m.tierName] = (byTier[m.tierName] ?? 0) + 1;
|
|
1696
|
+
totalTokens += m.tokenEstimate;
|
|
1697
|
+
}
|
|
1698
|
+
return {
|
|
1699
|
+
totalMemories: memories.length,
|
|
1700
|
+
totalTokens,
|
|
1701
|
+
byTier,
|
|
1702
|
+
agentId: this._resolvedAgentId
|
|
1703
|
+
};
|
|
1704
|
+
}
|
|
1705
|
+
/** Make a memory visible to all agents. */
|
|
1706
|
+
async share(memoryId) {
|
|
1707
|
+
this._ensureInitialized();
|
|
1708
|
+
const memory = await this._store.get(memoryId);
|
|
1709
|
+
if (!memory) {
|
|
1710
|
+
throw new Error(`Memory not found: ${memoryId}`);
|
|
1711
|
+
}
|
|
1712
|
+
if (memory.agentId !== this._resolvedAgentId) {
|
|
1713
|
+
throw new Error("Cannot share another agent's memory");
|
|
1714
|
+
}
|
|
1715
|
+
memory.shared = true;
|
|
1716
|
+
await this._store.save(memory);
|
|
1717
|
+
return memory;
|
|
1718
|
+
}
|
|
1719
|
+
/** Make a memory private (only visible to owner). */
|
|
1720
|
+
async unshare(memoryId) {
|
|
1721
|
+
this._ensureInitialized();
|
|
1722
|
+
const memory = await this._store.get(memoryId);
|
|
1723
|
+
if (!memory) {
|
|
1724
|
+
throw new Error(`Memory not found: ${memoryId}`);
|
|
1725
|
+
}
|
|
1726
|
+
if (memory.agentId !== this._resolvedAgentId) {
|
|
1727
|
+
throw new Error("Cannot unshare another agent's memory");
|
|
1728
|
+
}
|
|
1729
|
+
memory.shared = false;
|
|
1730
|
+
await this._store.save(memory);
|
|
1731
|
+
return memory;
|
|
1732
|
+
}
|
|
1733
|
+
async _applyTierResults(results) {
|
|
1734
|
+
let applied = 0;
|
|
1735
|
+
for (const result of results) {
|
|
1736
|
+
const mem = await this._store.get(result.memoryId);
|
|
1737
|
+
if (!mem) continue;
|
|
1738
|
+
applyTierChange(mem, result.newTier);
|
|
1739
|
+
mem.importance = score(mem, this._config);
|
|
1740
|
+
await this._store.save(mem);
|
|
1741
|
+
applied++;
|
|
1742
|
+
}
|
|
1743
|
+
return applied;
|
|
1744
|
+
}
|
|
1745
|
+
};
|
|
1746
|
+
function _wrapIfSync(store) {
|
|
1747
|
+
if (store instanceof SQLiteStore) {
|
|
1748
|
+
return syncToAsync(store);
|
|
1749
|
+
}
|
|
1750
|
+
return store;
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
// src/cli/index.ts
|
|
1754
|
+
function createClient(storeUri) {
|
|
1755
|
+
if (storeUri.startsWith("sqlite")) {
|
|
1756
|
+
return new Mnemonic({ store: new SQLiteStore(parseSqlitePath(storeUri)) });
|
|
1757
|
+
}
|
|
1758
|
+
throw new Error(
|
|
1759
|
+
`CLI currently supports sqlite:// URIs. Got: ${storeUri}`
|
|
1760
|
+
);
|
|
1761
|
+
}
|
|
1762
|
+
async function buildProgram() {
|
|
1763
|
+
const cmdPath = "commander";
|
|
1764
|
+
const commander = await import(cmdPath);
|
|
1765
|
+
const { Command } = commander;
|
|
1766
|
+
const program = new Command();
|
|
1767
|
+
program.name("mnemonic").description("Mnemonic memory layer CLI").version("0.1.0");
|
|
1768
|
+
program.command("stats").description("Show memory count by tier and token totals").option("--store <uri>", "Store URI", "sqlite:///mnemonic.db").action(async (opts) => {
|
|
1769
|
+
const client = createClient(opts.store);
|
|
1770
|
+
try {
|
|
1771
|
+
await client.init();
|
|
1772
|
+
const stats = await client.stats();
|
|
1773
|
+
process.stdout.write(`Total memories: ${stats.totalMemories}
|
|
1774
|
+
`);
|
|
1775
|
+
process.stdout.write(`Total tokens: ${stats.totalTokens}
|
|
1776
|
+
`);
|
|
1777
|
+
process.stdout.write(`Agent ID: ${stats.agentId}
|
|
1778
|
+
`);
|
|
1779
|
+
process.stdout.write("By tier:\n");
|
|
1780
|
+
for (const [tier, count] of Object.entries(stats.byTier)) {
|
|
1781
|
+
process.stdout.write(` ${tier}: ${count}
|
|
1782
|
+
`);
|
|
1783
|
+
}
|
|
1784
|
+
} finally {
|
|
1785
|
+
await client.store.close();
|
|
1786
|
+
}
|
|
1787
|
+
});
|
|
1788
|
+
program.command("prune").description("Delete old or low-value memories").requiredOption("--older-than <duration>", "Duration (e.g. 30d, 24h)").option("--tier <tier>", "Only delete this tier").option("--store <uri>", "Store URI", "sqlite:///mnemonic.db").action(async (opts) => {
|
|
1789
|
+
const client = createClient(opts.store);
|
|
1790
|
+
try {
|
|
1791
|
+
await client.init();
|
|
1792
|
+
const deleted = await client.forget({
|
|
1793
|
+
olderThan: opts.olderThan,
|
|
1794
|
+
tier: opts.tier
|
|
1795
|
+
});
|
|
1796
|
+
process.stdout.write(`Deleted ${deleted} memories
|
|
1797
|
+
`);
|
|
1798
|
+
} finally {
|
|
1799
|
+
await client.store.close();
|
|
1800
|
+
}
|
|
1801
|
+
});
|
|
1802
|
+
program.command("consolidate").description("Merge similar episodic memories into structural summaries").option("--store <uri>", "Store URI", "sqlite:///mnemonic.db").action(async (opts) => {
|
|
1803
|
+
const client = createClient(opts.store);
|
|
1804
|
+
try {
|
|
1805
|
+
await client.init();
|
|
1806
|
+
const count = await client.consolidate();
|
|
1807
|
+
process.stdout.write(`Consolidated ${count} groups
|
|
1808
|
+
`);
|
|
1809
|
+
} finally {
|
|
1810
|
+
await client.store.close();
|
|
1811
|
+
}
|
|
1812
|
+
});
|
|
1813
|
+
return program;
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
// src/cli/main.ts
|
|
1817
|
+
async function main() {
|
|
1818
|
+
const program = await buildProgram();
|
|
1819
|
+
await program.parseAsync();
|
|
1820
|
+
}
|
|
1821
|
+
main().catch((err) => {
|
|
1822
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1823
|
+
process.stderr.write(`Error: ${msg}
|
|
1824
|
+
`);
|
|
1825
|
+
process.exit(1);
|
|
1826
|
+
});
|
|
1827
|
+
//# sourceMappingURL=main.cjs.map
|