@smyslenny/agent-memory 2.0.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/index.js ADDED
@@ -0,0 +1,849 @@
1
+ // AgentMemory v2 — Sleep-cycle memory for AI agents
2
+
3
+ // src/core/db.ts
4
+ import Database from "better-sqlite3";
5
+ import { randomUUID } from "crypto";
6
+ var SCHEMA_VERSION = 1;
7
+ var SCHEMA_SQL = `
8
+ -- Memory entries
9
+ CREATE TABLE IF NOT EXISTS memories (
10
+ id TEXT PRIMARY KEY,
11
+ content TEXT NOT NULL,
12
+ type TEXT NOT NULL CHECK(type IN ('identity','emotion','knowledge','event')),
13
+ priority INTEGER NOT NULL DEFAULT 2 CHECK(priority BETWEEN 0 AND 3),
14
+ emotion_val REAL NOT NULL DEFAULT 0.0,
15
+ vitality REAL NOT NULL DEFAULT 1.0,
16
+ stability REAL NOT NULL DEFAULT 1.0,
17
+ access_count INTEGER NOT NULL DEFAULT 0,
18
+ last_accessed TEXT,
19
+ created_at TEXT NOT NULL,
20
+ updated_at TEXT NOT NULL,
21
+ source TEXT,
22
+ agent_id TEXT NOT NULL DEFAULT 'default',
23
+ hash TEXT,
24
+ UNIQUE(hash, agent_id)
25
+ );
26
+
27
+ -- URI paths (Content-Path separation, from nocturne)
28
+ CREATE TABLE IF NOT EXISTS paths (
29
+ id TEXT PRIMARY KEY,
30
+ memory_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
31
+ uri TEXT NOT NULL UNIQUE,
32
+ alias TEXT,
33
+ domain TEXT NOT NULL,
34
+ created_at TEXT NOT NULL
35
+ );
36
+
37
+ -- Association network (knowledge graph)
38
+ CREATE TABLE IF NOT EXISTS links (
39
+ source_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
40
+ target_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
41
+ relation TEXT NOT NULL,
42
+ weight REAL NOT NULL DEFAULT 1.0,
43
+ created_at TEXT NOT NULL,
44
+ PRIMARY KEY (source_id, target_id)
45
+ );
46
+
47
+ -- Snapshots (version control, from nocturne + Memory Palace)
48
+ CREATE TABLE IF NOT EXISTS snapshots (
49
+ id TEXT PRIMARY KEY,
50
+ memory_id TEXT NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
51
+ content TEXT NOT NULL,
52
+ changed_by TEXT,
53
+ action TEXT NOT NULL CHECK(action IN ('create','update','delete','merge')),
54
+ created_at TEXT NOT NULL
55
+ );
56
+
57
+ -- Full-text search index (BM25)
58
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
59
+ id UNINDEXED,
60
+ content,
61
+ tokenize='unicode61'
62
+ );
63
+
64
+ -- Schema version tracking
65
+ CREATE TABLE IF NOT EXISTS schema_meta (
66
+ key TEXT PRIMARY KEY,
67
+ value TEXT NOT NULL
68
+ );
69
+
70
+ -- Indexes for common queries
71
+ CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);
72
+ CREATE INDEX IF NOT EXISTS idx_memories_priority ON memories(priority);
73
+ CREATE INDEX IF NOT EXISTS idx_memories_agent ON memories(agent_id);
74
+ CREATE INDEX IF NOT EXISTS idx_memories_vitality ON memories(vitality);
75
+ CREATE INDEX IF NOT EXISTS idx_memories_hash ON memories(hash);
76
+ CREATE INDEX IF NOT EXISTS idx_paths_memory ON paths(memory_id);
77
+ CREATE INDEX IF NOT EXISTS idx_paths_domain ON paths(domain);
78
+ CREATE INDEX IF NOT EXISTS idx_links_source ON links(source_id);
79
+ CREATE INDEX IF NOT EXISTS idx_links_target ON links(target_id);
80
+ `;
81
+ function openDatabase(opts) {
82
+ const db = new Database(opts.path);
83
+ if (opts.walMode !== false) {
84
+ db.pragma("journal_mode = WAL");
85
+ }
86
+ db.pragma("foreign_keys = ON");
87
+ db.pragma("busy_timeout = 5000");
88
+ db.exec(SCHEMA_SQL);
89
+ const getVersion = db.prepare("SELECT value FROM schema_meta WHERE key = 'version'");
90
+ const row = getVersion.get();
91
+ if (!row) {
92
+ db.prepare("INSERT INTO schema_meta (key, value) VALUES ('version', ?)").run(
93
+ String(SCHEMA_VERSION)
94
+ );
95
+ }
96
+ return db;
97
+ }
98
+ function now() {
99
+ return (/* @__PURE__ */ new Date()).toISOString();
100
+ }
101
+ function newId() {
102
+ return randomUUID();
103
+ }
104
+
105
+ // src/core/memory.ts
106
+ import { createHash } from "crypto";
107
+ function contentHash(content) {
108
+ return createHash("sha256").update(content.trim()).digest("hex").slice(0, 16);
109
+ }
110
+ var TYPE_PRIORITY = {
111
+ identity: 0,
112
+ emotion: 1,
113
+ knowledge: 2,
114
+ event: 3
115
+ };
116
+ var PRIORITY_STABILITY = {
117
+ 0: Infinity,
118
+ // P0: never decays
119
+ 1: 365,
120
+ // P1: 365-day half-life
121
+ 2: 90,
122
+ // P2: 90-day half-life
123
+ 3: 14
124
+ // P3: 14-day half-life
125
+ };
126
+ function createMemory(db, input) {
127
+ const hash = contentHash(input.content);
128
+ const agentId = input.agent_id ?? "default";
129
+ const priority = input.priority ?? TYPE_PRIORITY[input.type];
130
+ const stability = PRIORITY_STABILITY[priority];
131
+ const existing = db.prepare("SELECT id FROM memories WHERE hash = ? AND agent_id = ?").get(hash, agentId);
132
+ if (existing) {
133
+ return null;
134
+ }
135
+ const id = newId();
136
+ const timestamp = now();
137
+ db.prepare(
138
+ `INSERT INTO memories (id, content, type, priority, emotion_val, vitality, stability,
139
+ access_count, created_at, updated_at, source, agent_id, hash)
140
+ VALUES (?, ?, ?, ?, ?, 1.0, ?, 0, ?, ?, ?, ?, ?)`
141
+ ).run(
142
+ id,
143
+ input.content,
144
+ input.type,
145
+ priority,
146
+ input.emotion_val ?? 0,
147
+ stability === Infinity ? 999999 : stability,
148
+ timestamp,
149
+ timestamp,
150
+ input.source ?? null,
151
+ agentId,
152
+ hash
153
+ );
154
+ db.prepare("INSERT INTO memories_fts (id, content) VALUES (?, ?)").run(id, input.content);
155
+ return getMemory(db, id);
156
+ }
157
+ function getMemory(db, id) {
158
+ return db.prepare("SELECT * FROM memories WHERE id = ?").get(id) ?? null;
159
+ }
160
+ function updateMemory(db, id, input) {
161
+ const existing = getMemory(db, id);
162
+ if (!existing) return null;
163
+ const fields = [];
164
+ const values = [];
165
+ if (input.content !== void 0) {
166
+ fields.push("content = ?", "hash = ?");
167
+ values.push(input.content, contentHash(input.content));
168
+ }
169
+ if (input.type !== void 0) {
170
+ fields.push("type = ?");
171
+ values.push(input.type);
172
+ }
173
+ if (input.priority !== void 0) {
174
+ fields.push("priority = ?");
175
+ values.push(input.priority);
176
+ }
177
+ if (input.emotion_val !== void 0) {
178
+ fields.push("emotion_val = ?");
179
+ values.push(input.emotion_val);
180
+ }
181
+ if (input.vitality !== void 0) {
182
+ fields.push("vitality = ?");
183
+ values.push(input.vitality);
184
+ }
185
+ if (input.stability !== void 0) {
186
+ fields.push("stability = ?");
187
+ values.push(input.stability);
188
+ }
189
+ if (input.source !== void 0) {
190
+ fields.push("source = ?");
191
+ values.push(input.source);
192
+ }
193
+ fields.push("updated_at = ?");
194
+ values.push(now());
195
+ values.push(id);
196
+ db.prepare(`UPDATE memories SET ${fields.join(", ")} WHERE id = ?`).run(...values);
197
+ if (input.content !== void 0) {
198
+ db.prepare("DELETE FROM memories_fts WHERE id = ?").run(id);
199
+ db.prepare("INSERT INTO memories_fts (id, content) VALUES (?, ?)").run(id, input.content);
200
+ }
201
+ return getMemory(db, id);
202
+ }
203
+ function deleteMemory(db, id) {
204
+ db.prepare("DELETE FROM memories_fts WHERE id = ?").run(id);
205
+ const result = db.prepare("DELETE FROM memories WHERE id = ?").run(id);
206
+ return result.changes > 0;
207
+ }
208
+ function listMemories(db, opts) {
209
+ const conditions = [];
210
+ const params = [];
211
+ if (opts?.agent_id) {
212
+ conditions.push("agent_id = ?");
213
+ params.push(opts.agent_id);
214
+ }
215
+ if (opts?.type) {
216
+ conditions.push("type = ?");
217
+ params.push(opts.type);
218
+ }
219
+ if (opts?.priority !== void 0) {
220
+ conditions.push("priority = ?");
221
+ params.push(opts.priority);
222
+ }
223
+ if (opts?.min_vitality !== void 0) {
224
+ conditions.push("vitality >= ?");
225
+ params.push(opts.min_vitality);
226
+ }
227
+ const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
228
+ const limit = opts?.limit ?? 100;
229
+ const offset = opts?.offset ?? 0;
230
+ return db.prepare(`SELECT * FROM memories ${where} ORDER BY priority ASC, updated_at DESC LIMIT ? OFFSET ?`).all(...params, limit, offset);
231
+ }
232
+ function recordAccess(db, id, growthFactor = 1.5) {
233
+ const mem = getMemory(db, id);
234
+ if (!mem) return;
235
+ const newStability = Math.min(999999, mem.stability * growthFactor);
236
+ db.prepare(
237
+ `UPDATE memories SET access_count = access_count + 1, last_accessed = ?, stability = ?,
238
+ vitality = MIN(1.0, vitality * 1.2) WHERE id = ?`
239
+ ).run(now(), newStability, id);
240
+ }
241
+ function countMemories(db, agent_id = "default") {
242
+ const total = db.prepare("SELECT COUNT(*) as c FROM memories WHERE agent_id = ?").get(agent_id).c;
243
+ const byType = db.prepare("SELECT type, COUNT(*) as c FROM memories WHERE agent_id = ? GROUP BY type").all(agent_id);
244
+ const byPriority = db.prepare("SELECT priority, COUNT(*) as c FROM memories WHERE agent_id = ? GROUP BY priority").all(agent_id);
245
+ return {
246
+ total,
247
+ by_type: Object.fromEntries(byType.map((r) => [r.type, r.c])),
248
+ by_priority: Object.fromEntries(byPriority.map((r) => [`P${r.priority}`, r.c]))
249
+ };
250
+ }
251
+
252
+ // src/core/path.ts
253
+ var DEFAULT_DOMAINS = /* @__PURE__ */ new Set(["core", "emotion", "knowledge", "event", "system"]);
254
+ function parseUri(uri) {
255
+ const match = uri.match(/^([a-z]+):\/\/(.+)$/);
256
+ if (!match) throw new Error(`Invalid URI: ${uri}. Expected format: domain://path`);
257
+ return { domain: match[1], path: match[2] };
258
+ }
259
+ function createPath(db, memoryId, uri, alias, validDomains) {
260
+ const { domain } = parseUri(uri);
261
+ const domains = validDomains ?? DEFAULT_DOMAINS;
262
+ if (!domains.has(domain)) {
263
+ throw new Error(`Invalid domain "${domain}". Valid: ${[...domains].join(", ")}`);
264
+ }
265
+ const existing = db.prepare("SELECT id FROM paths WHERE uri = ?").get(uri);
266
+ if (existing) {
267
+ throw new Error(`URI already exists: ${uri}`);
268
+ }
269
+ const id = newId();
270
+ db.prepare(
271
+ "INSERT INTO paths (id, memory_id, uri, alias, domain, created_at) VALUES (?, ?, ?, ?, ?, ?)"
272
+ ).run(id, memoryId, uri, alias ?? null, domain, now());
273
+ return getPath(db, id);
274
+ }
275
+ function getPath(db, id) {
276
+ return db.prepare("SELECT * FROM paths WHERE id = ?").get(id) ?? null;
277
+ }
278
+ function getPathByUri(db, uri) {
279
+ return db.prepare("SELECT * FROM paths WHERE uri = ?").get(uri) ?? null;
280
+ }
281
+ function getPathsByMemory(db, memoryId) {
282
+ return db.prepare("SELECT * FROM paths WHERE memory_id = ?").all(memoryId);
283
+ }
284
+ function getPathsByDomain(db, domain) {
285
+ return db.prepare("SELECT * FROM paths WHERE domain = ? ORDER BY uri").all(domain);
286
+ }
287
+ function getPathsByPrefix(db, prefix) {
288
+ return db.prepare("SELECT * FROM paths WHERE uri LIKE ? ORDER BY uri").all(`${prefix}%`);
289
+ }
290
+ function deletePath(db, id) {
291
+ const result = db.prepare("DELETE FROM paths WHERE id = ?").run(id);
292
+ return result.changes > 0;
293
+ }
294
+
295
+ // src/core/link.ts
296
+ function createLink(db, sourceId, targetId, relation, weight = 1) {
297
+ db.prepare(
298
+ `INSERT OR REPLACE INTO links (source_id, target_id, relation, weight, created_at)
299
+ VALUES (?, ?, ?, ?, ?)`
300
+ ).run(sourceId, targetId, relation, weight, now());
301
+ return { source_id: sourceId, target_id: targetId, relation, weight, created_at: now() };
302
+ }
303
+ function getLinks(db, memoryId) {
304
+ return db.prepare("SELECT * FROM links WHERE source_id = ? OR target_id = ?").all(memoryId, memoryId);
305
+ }
306
+ function getOutgoingLinks(db, sourceId) {
307
+ return db.prepare("SELECT * FROM links WHERE source_id = ?").all(sourceId);
308
+ }
309
+ function traverse(db, startId, maxHops = 2) {
310
+ const visited = /* @__PURE__ */ new Set();
311
+ const results = [];
312
+ const queue = [
313
+ { id: startId, hop: 0, relation: "self" }
314
+ ];
315
+ while (queue.length > 0) {
316
+ const current = queue.shift();
317
+ if (visited.has(current.id)) continue;
318
+ visited.add(current.id);
319
+ if (current.hop > 0) {
320
+ results.push(current);
321
+ }
322
+ if (current.hop < maxHops) {
323
+ const links = db.prepare("SELECT target_id, relation FROM links WHERE source_id = ?").all(current.id);
324
+ for (const link of links) {
325
+ if (!visited.has(link.target_id)) {
326
+ queue.push({
327
+ id: link.target_id,
328
+ hop: current.hop + 1,
329
+ relation: link.relation
330
+ });
331
+ }
332
+ }
333
+ const reverseLinks = db.prepare("SELECT source_id, relation FROM links WHERE target_id = ?").all(current.id);
334
+ for (const link of reverseLinks) {
335
+ if (!visited.has(link.source_id)) {
336
+ queue.push({
337
+ id: link.source_id,
338
+ hop: current.hop + 1,
339
+ relation: link.relation
340
+ });
341
+ }
342
+ }
343
+ }
344
+ }
345
+ return results;
346
+ }
347
+ function deleteLink(db, sourceId, targetId) {
348
+ const result = db.prepare("DELETE FROM links WHERE source_id = ? AND target_id = ?").run(sourceId, targetId);
349
+ return result.changes > 0;
350
+ }
351
+
352
+ // src/core/snapshot.ts
353
+ function createSnapshot(db, memoryId, action, changedBy) {
354
+ const memory = db.prepare("SELECT content FROM memories WHERE id = ?").get(memoryId);
355
+ if (!memory) throw new Error(`Memory not found: ${memoryId}`);
356
+ const id = newId();
357
+ db.prepare(
358
+ `INSERT INTO snapshots (id, memory_id, content, changed_by, action, created_at)
359
+ VALUES (?, ?, ?, ?, ?, ?)`
360
+ ).run(id, memoryId, memory.content, changedBy ?? null, action, now());
361
+ return { id, memory_id: memoryId, content: memory.content, changed_by: changedBy ?? null, action, created_at: now() };
362
+ }
363
+ function getSnapshots(db, memoryId) {
364
+ return db.prepare("SELECT * FROM snapshots WHERE memory_id = ? ORDER BY created_at DESC").all(memoryId);
365
+ }
366
+ function getSnapshot(db, id) {
367
+ return db.prepare("SELECT * FROM snapshots WHERE id = ?").get(id) ?? null;
368
+ }
369
+ function rollback(db, snapshotId) {
370
+ const snapshot = getSnapshot(db, snapshotId);
371
+ if (!snapshot) return false;
372
+ createSnapshot(db, snapshot.memory_id, "update", "rollback");
373
+ db.prepare("UPDATE memories SET content = ?, updated_at = ? WHERE id = ?").run(
374
+ snapshot.content,
375
+ now(),
376
+ snapshot.memory_id
377
+ );
378
+ db.prepare("DELETE FROM memories_fts WHERE id = ?").run(snapshot.memory_id);
379
+ db.prepare("INSERT INTO memories_fts (id, content) VALUES (?, ?)").run(
380
+ snapshot.memory_id,
381
+ snapshot.content
382
+ );
383
+ return true;
384
+ }
385
+
386
+ // src/core/guard.ts
387
+ function guard(db, input) {
388
+ const hash = contentHash(input.content);
389
+ const agentId = input.agent_id ?? "default";
390
+ const exactMatch = db.prepare("SELECT id FROM memories WHERE hash = ? AND agent_id = ?").get(hash, agentId);
391
+ if (exactMatch) {
392
+ return { action: "skip", reason: "Exact duplicate (hash match)", existingId: exactMatch.id };
393
+ }
394
+ if (input.uri) {
395
+ const existingPath = getPathByUri(db, input.uri);
396
+ if (existingPath) {
397
+ return {
398
+ action: "update",
399
+ reason: `URI ${input.uri} already exists, updating`,
400
+ existingId: existingPath.memory_id
401
+ };
402
+ }
403
+ }
404
+ const similar = db.prepare(
405
+ `SELECT m.id, m.content, m.type, rank
406
+ FROM memories_fts f
407
+ JOIN memories m ON m.id = f.id
408
+ WHERE memories_fts MATCH ? AND m.agent_id = ?
409
+ ORDER BY rank
410
+ LIMIT 3`
411
+ ).all(escapeFts(input.content), agentId);
412
+ if (similar.length > 0 && similar[0].rank < -10) {
413
+ const existing = similar[0];
414
+ if (existing.type === input.type) {
415
+ const merged = `${existing.content}
416
+
417
+ [Updated] ${input.content}`;
418
+ return {
419
+ action: "merge",
420
+ reason: "Similar content found, merging",
421
+ existingId: existing.id,
422
+ mergedContent: merged
423
+ };
424
+ }
425
+ }
426
+ const priority = input.priority ?? (input.type === "identity" ? 0 : input.type === "emotion" ? 1 : 2);
427
+ if (priority <= 1) {
428
+ if (!input.content.trim()) {
429
+ return { action: "skip", reason: "Empty content rejected by gate" };
430
+ }
431
+ }
432
+ return { action: "add", reason: "Passed all guard checks" };
433
+ }
434
+ function escapeFts(text) {
435
+ const words = text.slice(0, 100).replace(/[^\w\u4e00-\u9fff\s]/g, " ").split(/\s+/).filter((w) => w.length > 1).slice(0, 5);
436
+ if (words.length === 0) return '""';
437
+ return words.map((w) => `"${w}"`).join(" OR ");
438
+ }
439
+
440
+ // src/search/bm25.ts
441
+ function searchBM25(db, query, opts) {
442
+ const limit = opts?.limit ?? 20;
443
+ const agentId = opts?.agent_id ?? "default";
444
+ const minVitality = opts?.min_vitality ?? 0;
445
+ const ftsQuery = buildFtsQuery(query);
446
+ if (!ftsQuery) return [];
447
+ try {
448
+ const rows = db.prepare(
449
+ `SELECT m.*, rank AS score
450
+ FROM memories_fts f
451
+ JOIN memories m ON m.id = f.id
452
+ WHERE memories_fts MATCH ?
453
+ AND m.agent_id = ?
454
+ AND m.vitality >= ?
455
+ ORDER BY rank
456
+ LIMIT ?`
457
+ ).all(ftsQuery, agentId, minVitality, limit);
458
+ return rows.map((row) => ({
459
+ memory: { ...row, score: void 0 },
460
+ score: Math.abs(row.score),
461
+ // FTS5 rank is negative (lower = better)
462
+ matchReason: "bm25"
463
+ }));
464
+ } catch {
465
+ return searchSimple(db, query, agentId, minVitality, limit);
466
+ }
467
+ }
468
+ function searchSimple(db, query, agentId, minVitality, limit) {
469
+ const rows = db.prepare(
470
+ `SELECT * FROM memories
471
+ WHERE agent_id = ? AND vitality >= ? AND content LIKE ?
472
+ ORDER BY priority ASC, updated_at DESC
473
+ LIMIT ?`
474
+ ).all(agentId, minVitality, `%${query}%`, limit);
475
+ return rows.map((m, i) => ({
476
+ memory: m,
477
+ score: 1 / (i + 1),
478
+ // Simple rank by position
479
+ matchReason: "like"
480
+ }));
481
+ }
482
+ function buildFtsQuery(text) {
483
+ const words = text.replace(/[^\w\u4e00-\u9fff\u3040-\u30ff\s]/g, " ").split(/\s+/).filter((w) => w.length > 1).slice(0, 10);
484
+ if (words.length === 0) return null;
485
+ return words.map((w) => `"${w}"`).join(" OR ");
486
+ }
487
+
488
+ // src/search/intent.ts
489
+ var INTENT_PATTERNS = {
490
+ factual: [
491
+ /^(what|who|where|which|how much|how many)/i,
492
+ /是(什么|谁|哪)/,
493
+ /叫什么/,
494
+ /名字/,
495
+ /地址/,
496
+ /号码/,
497
+ /密码/,
498
+ /配置/,
499
+ /设置/
500
+ ],
501
+ temporal: [
502
+ /^(when|what time|how long)/i,
503
+ /(yesterday|today|last week|recently|ago|before|after)/i,
504
+ /什么时候/,
505
+ /(昨天|今天|上周|最近|以前|之前|之后)/,
506
+ /\d{4}[-/]\d{1,2}/,
507
+ /(几月|几号|几点)/
508
+ ],
509
+ causal: [
510
+ /^(why|how come|what caused)/i,
511
+ /^(because|due to|reason)/i,
512
+ /为什么/,
513
+ /原因/,
514
+ /导致/,
515
+ /怎么回事/,
516
+ /为啥/
517
+ ],
518
+ exploratory: [
519
+ /^(how|tell me about|explain|describe)/i,
520
+ /^(what do you think|what about)/i,
521
+ /怎么样/,
522
+ /介绍/,
523
+ /说说/,
524
+ /讲讲/,
525
+ /有哪些/,
526
+ /关于/
527
+ ]
528
+ };
529
+ function classifyIntent(query) {
530
+ const scores = {
531
+ factual: 0,
532
+ exploratory: 0,
533
+ temporal: 0,
534
+ causal: 0
535
+ };
536
+ for (const [intent, patterns] of Object.entries(INTENT_PATTERNS)) {
537
+ for (const pattern of patterns) {
538
+ if (pattern.test(query)) {
539
+ scores[intent] += 1;
540
+ }
541
+ }
542
+ }
543
+ let maxIntent = "factual";
544
+ let maxScore = 0;
545
+ for (const [intent, score] of Object.entries(scores)) {
546
+ if (score > maxScore) {
547
+ maxScore = score;
548
+ maxIntent = intent;
549
+ }
550
+ }
551
+ const totalScore = Object.values(scores).reduce((a, b) => a + b, 0);
552
+ const confidence = totalScore > 0 ? maxScore / totalScore : 0.5;
553
+ return { intent: maxIntent, confidence };
554
+ }
555
+ function getStrategy(intent) {
556
+ switch (intent) {
557
+ case "factual":
558
+ return { boostRecent: false, boostPriority: true, limit: 5 };
559
+ case "temporal":
560
+ return { boostRecent: true, boostPriority: false, limit: 10 };
561
+ case "causal":
562
+ return { boostRecent: false, boostPriority: false, limit: 10 };
563
+ case "exploratory":
564
+ return { boostRecent: false, boostPriority: false, limit: 15 };
565
+ }
566
+ }
567
+
568
+ // src/search/rerank.ts
569
+ function rerank(results, opts) {
570
+ const now2 = Date.now();
571
+ const scored = results.map((r) => {
572
+ let finalScore = r.score;
573
+ if (opts.boostPriority) {
574
+ const priorityMultiplier = [4, 3, 2, 1][r.memory.priority] ?? 1;
575
+ finalScore *= priorityMultiplier;
576
+ }
577
+ if (opts.boostRecent && r.memory.updated_at) {
578
+ const age = now2 - new Date(r.memory.updated_at).getTime();
579
+ const daysSinceUpdate = age / (1e3 * 60 * 60 * 24);
580
+ const recencyBoost = Math.max(0.1, 1 / (1 + daysSinceUpdate * 0.1));
581
+ finalScore *= recencyBoost;
582
+ }
583
+ finalScore *= Math.max(0.1, r.memory.vitality);
584
+ return { ...r, score: finalScore };
585
+ });
586
+ scored.sort((a, b) => b.score - a.score);
587
+ return scored.slice(0, opts.limit);
588
+ }
589
+
590
+ // src/sleep/decay.ts
591
+ var MIN_VITALITY = {
592
+ 0: 1,
593
+ // P0: identity — never decays
594
+ 1: 0.3,
595
+ // P1: emotion — slow decay
596
+ 2: 0.1,
597
+ // P2: knowledge — normal decay
598
+ 3: 0
599
+ // P3: event — full decay
600
+ };
601
+ function calculateVitality(stability, daysSinceCreation, priority) {
602
+ if (priority === 0) return 1;
603
+ const S = Math.max(0.01, stability);
604
+ const retention = Math.exp(-daysSinceCreation / S);
605
+ const minVit = MIN_VITALITY[priority] ?? 0;
606
+ return Math.max(minVit, retention);
607
+ }
608
+ function runDecay(db) {
609
+ const currentTime = now();
610
+ const currentMs = new Date(currentTime).getTime();
611
+ const memories = db.prepare("SELECT id, priority, stability, created_at, vitality FROM memories WHERE priority > 0").all();
612
+ let updated = 0;
613
+ let decayed = 0;
614
+ let belowThreshold = 0;
615
+ const updateStmt = db.prepare("UPDATE memories SET vitality = ?, updated_at = ? WHERE id = ?");
616
+ const transaction = db.transaction(() => {
617
+ for (const mem of memories) {
618
+ const createdMs = new Date(mem.created_at).getTime();
619
+ const daysSince = (currentMs - createdMs) / (1e3 * 60 * 60 * 24);
620
+ const newVitality = calculateVitality(mem.stability, daysSince, mem.priority);
621
+ if (Math.abs(newVitality - mem.vitality) > 1e-3) {
622
+ updateStmt.run(newVitality, currentTime, mem.id);
623
+ updated++;
624
+ if (newVitality < mem.vitality) {
625
+ decayed++;
626
+ }
627
+ if (newVitality < 0.05) {
628
+ belowThreshold++;
629
+ }
630
+ }
631
+ }
632
+ });
633
+ transaction();
634
+ return { updated, decayed, belowThreshold };
635
+ }
636
+ function getDecayedMemories(db, threshold = 0.05) {
637
+ return db.prepare(
638
+ `SELECT id, content, vitality, priority FROM memories
639
+ WHERE vitality < ? AND priority >= 3
640
+ ORDER BY vitality ASC`
641
+ ).all(threshold);
642
+ }
643
+
644
+ // src/sleep/sync.ts
645
+ function syncOne(db, input) {
646
+ const memInput = {
647
+ content: input.content,
648
+ type: input.type ?? "event",
649
+ priority: input.priority,
650
+ emotion_val: input.emotion_val,
651
+ source: input.source,
652
+ agent_id: input.agent_id,
653
+ uri: input.uri
654
+ };
655
+ const guardResult = guard(db, memInput);
656
+ switch (guardResult.action) {
657
+ case "skip":
658
+ return { action: "skipped", reason: guardResult.reason, memoryId: guardResult.existingId };
659
+ case "add": {
660
+ const mem = createMemory(db, memInput);
661
+ if (!mem) return { action: "skipped", reason: "createMemory returned null" };
662
+ if (input.uri) {
663
+ try {
664
+ createPath(db, mem.id, input.uri);
665
+ } catch {
666
+ }
667
+ }
668
+ return { action: "added", memoryId: mem.id, reason: guardResult.reason };
669
+ }
670
+ case "update": {
671
+ if (!guardResult.existingId) return { action: "skipped", reason: "No existing ID for update" };
672
+ createSnapshot(db, guardResult.existingId, "update", "sync");
673
+ updateMemory(db, guardResult.existingId, { content: input.content });
674
+ return { action: "updated", memoryId: guardResult.existingId, reason: guardResult.reason };
675
+ }
676
+ case "merge": {
677
+ if (!guardResult.existingId || !guardResult.mergedContent) {
678
+ return { action: "skipped", reason: "Missing merge data" };
679
+ }
680
+ createSnapshot(db, guardResult.existingId, "merge", "sync");
681
+ updateMemory(db, guardResult.existingId, { content: guardResult.mergedContent });
682
+ return { action: "merged", memoryId: guardResult.existingId, reason: guardResult.reason };
683
+ }
684
+ }
685
+ }
686
+ function syncBatch(db, inputs) {
687
+ const results = [];
688
+ const transaction = db.transaction(() => {
689
+ for (const input of inputs) {
690
+ results.push(syncOne(db, input));
691
+ }
692
+ });
693
+ transaction();
694
+ return results;
695
+ }
696
+
697
+ // src/sleep/tidy.ts
698
+ function runTidy(db, opts) {
699
+ const threshold = opts?.vitalityThreshold ?? 0.05;
700
+ const maxSnapshots = opts?.maxSnapshotsPerMemory ?? 10;
701
+ let archived = 0;
702
+ let orphansCleaned = 0;
703
+ let snapshotsPruned = 0;
704
+ const transaction = db.transaction(() => {
705
+ const decayed = getDecayedMemories(db, threshold);
706
+ for (const mem of decayed) {
707
+ try {
708
+ createSnapshot(db, mem.id, "delete", "tidy");
709
+ } catch {
710
+ }
711
+ deleteMemory(db, mem.id);
712
+ archived++;
713
+ }
714
+ const orphans = db.prepare(
715
+ `DELETE FROM paths WHERE memory_id NOT IN (SELECT id FROM memories)`
716
+ ).run();
717
+ orphansCleaned = orphans.changes;
718
+ const memoriesWithSnapshots = db.prepare(
719
+ `SELECT memory_id, COUNT(*) as cnt FROM snapshots
720
+ GROUP BY memory_id HAVING cnt > ?`
721
+ ).all(maxSnapshots);
722
+ for (const { memory_id } of memoriesWithSnapshots) {
723
+ const pruned = db.prepare(
724
+ `DELETE FROM snapshots WHERE id NOT IN (
725
+ SELECT id FROM snapshots WHERE memory_id = ?
726
+ ORDER BY created_at DESC LIMIT ?
727
+ ) AND memory_id = ?`
728
+ ).run(memory_id, maxSnapshots, memory_id);
729
+ snapshotsPruned += pruned.changes;
730
+ }
731
+ });
732
+ transaction();
733
+ return { archived, orphansCleaned, snapshotsPruned };
734
+ }
735
+
736
+ // src/sleep/govern.ts
737
+ function runGovern(db) {
738
+ let orphanPaths = 0;
739
+ let orphanLinks = 0;
740
+ let emptyMemories = 0;
741
+ const transaction = db.transaction(() => {
742
+ const pathResult = db.prepare("DELETE FROM paths WHERE memory_id NOT IN (SELECT id FROM memories)").run();
743
+ orphanPaths = pathResult.changes;
744
+ const linkResult = db.prepare(
745
+ `DELETE FROM links WHERE
746
+ source_id NOT IN (SELECT id FROM memories) OR
747
+ target_id NOT IN (SELECT id FROM memories)`
748
+ ).run();
749
+ orphanLinks = linkResult.changes;
750
+ const emptyResult = db.prepare("DELETE FROM memories WHERE TRIM(content) = ''").run();
751
+ emptyMemories = emptyResult.changes;
752
+ });
753
+ transaction();
754
+ return { orphanPaths, orphanLinks, emptyMemories };
755
+ }
756
+
757
+ // src/sleep/boot.ts
758
+ function boot(db, opts) {
759
+ const agentId = opts?.agent_id ?? "default";
760
+ const corePaths = opts?.corePaths ?? [
761
+ "core://agent",
762
+ "core://user",
763
+ "core://agent/identity",
764
+ "core://user/identity"
765
+ ];
766
+ const memories = /* @__PURE__ */ new Map();
767
+ const identities = listMemories(db, { agent_id: agentId, priority: 0 });
768
+ for (const mem of identities) {
769
+ memories.set(mem.id, mem);
770
+ recordAccess(db, mem.id, 1.1);
771
+ }
772
+ const bootPaths = [];
773
+ for (const uri of corePaths) {
774
+ const path = getPathByUri(db, uri);
775
+ if (path) {
776
+ bootPaths.push(uri);
777
+ if (!memories.has(path.memory_id)) {
778
+ const mem = getMemory(db, path.memory_id);
779
+ if (mem) {
780
+ memories.set(mem.id, mem);
781
+ recordAccess(db, mem.id, 1.1);
782
+ }
783
+ }
784
+ }
785
+ }
786
+ const bootEntry = getPathByUri(db, "system://boot");
787
+ if (bootEntry) {
788
+ const bootMem = getMemory(db, bootEntry.memory_id);
789
+ if (bootMem) {
790
+ const additionalUris = bootMem.content.split("\n").map((l) => l.trim()).filter((l) => l.match(/^[a-z]+:\/\//));
791
+ for (const uri of additionalUris) {
792
+ const path = getPathByUri(db, uri);
793
+ if (path && !memories.has(path.memory_id)) {
794
+ const mem = getMemory(db, path.memory_id);
795
+ if (mem) {
796
+ memories.set(mem.id, mem);
797
+ bootPaths.push(uri);
798
+ }
799
+ }
800
+ }
801
+ }
802
+ }
803
+ return {
804
+ identityMemories: [...memories.values()],
805
+ bootPaths
806
+ };
807
+ }
808
+ export {
809
+ boot,
810
+ calculateVitality,
811
+ classifyIntent,
812
+ contentHash,
813
+ countMemories,
814
+ createLink,
815
+ createMemory,
816
+ createPath,
817
+ createSnapshot,
818
+ deleteLink,
819
+ deleteMemory,
820
+ deletePath,
821
+ getDecayedMemories,
822
+ getLinks,
823
+ getMemory,
824
+ getOutgoingLinks,
825
+ getPath,
826
+ getPathByUri,
827
+ getPathsByDomain,
828
+ getPathsByMemory,
829
+ getPathsByPrefix,
830
+ getSnapshot,
831
+ getSnapshots,
832
+ getStrategy,
833
+ guard,
834
+ listMemories,
835
+ openDatabase,
836
+ parseUri,
837
+ recordAccess,
838
+ rerank,
839
+ rollback,
840
+ runDecay,
841
+ runGovern,
842
+ runTidy,
843
+ searchBM25,
844
+ syncBatch,
845
+ syncOne,
846
+ traverse,
847
+ updateMemory
848
+ };
849
+ //# sourceMappingURL=index.js.map