@smyslenny/agent-memory 5.0.1 → 5.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/.github/workflows/test.yml +1 -1
- package/.pnpm-approve-builds.json +1 -0
- package/CHANGELOG.md +153 -0
- package/README.md +52 -15
- package/dist/bin/agent-memory.js +285 -27
- package/dist/bin/agent-memory.js.map +1 -1
- package/dist/index.d.ts +77 -3
- package/dist/index.js +354 -26
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +604 -13997
- package/dist/mcp/server.js.map +1 -1
- package/docs/README-zh.md +132 -15
- package/docs/architecture.md +1 -1
- package/docs/integrations/generic.md +43 -3
- package/docs/integrations/openclaw.md +48 -8
- package/docs/migration-v3-v4.md +15 -0
- package/package.json +4 -3
package/dist/bin/agent-memory.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// AgentMemory
|
|
2
|
+
// AgentMemory — Sleep-cycle memory for AI agents
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __esm = (fn, res) => function __init() {
|
|
@@ -100,6 +100,11 @@ function migrateDatabase(db, from, to) {
|
|
|
100
100
|
v = 7;
|
|
101
101
|
continue;
|
|
102
102
|
}
|
|
103
|
+
if (v === 7) {
|
|
104
|
+
migrateV7ToV8(db);
|
|
105
|
+
v = 8;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
103
108
|
throw new Error(`Unsupported schema migration path: v${from} \u2192 v${to} (stuck at v${v})`);
|
|
104
109
|
}
|
|
105
110
|
}
|
|
@@ -187,6 +192,8 @@ function inferSchemaVersion(db) {
|
|
|
187
192
|
const hasFeedbackEvents = tableExists(db, "feedback_events");
|
|
188
193
|
const hasEmotionTag = tableHasColumn(db, "memories", "emotion_tag");
|
|
189
194
|
const hasProvenance = tableHasColumn(db, "memories", "source_session") && tableHasColumn(db, "memories", "source_context") && tableHasColumn(db, "memories", "observed_at");
|
|
195
|
+
const hasMemoryArchive = tableExists(db, "memory_archive");
|
|
196
|
+
if (hasAgentScopedPaths && hasAgentScopedLinks && hasV4Embeddings && hasMaintenanceJobs && hasFeedbackEvents && hasEmotionTag && hasProvenance && hasMemoryArchive) return 8;
|
|
190
197
|
if (hasAgentScopedPaths && hasAgentScopedLinks && hasV4Embeddings && hasMaintenanceJobs && hasFeedbackEvents && hasEmotionTag && hasProvenance) return 7;
|
|
191
198
|
if (hasAgentScopedPaths && hasAgentScopedLinks && hasV4Embeddings && hasMaintenanceJobs && hasFeedbackEvents && hasEmotionTag) return 6;
|
|
192
199
|
if (hasAgentScopedPaths && hasAgentScopedLinks && hasV4Embeddings && hasMaintenanceJobs && hasFeedbackEvents) return 5;
|
|
@@ -228,6 +235,11 @@ function ensureIndexes(db) {
|
|
|
228
235
|
db.exec("CREATE INDEX IF NOT EXISTS idx_feedback_events_agent_source ON feedback_events(agent_id, source, created_at DESC);");
|
|
229
236
|
}
|
|
230
237
|
}
|
|
238
|
+
if (tableExists(db, "memory_archive")) {
|
|
239
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_archive_agent ON memory_archive(agent_id);");
|
|
240
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_archive_type ON memory_archive(type);");
|
|
241
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_archive_archived_at ON memory_archive(archived_at);");
|
|
242
|
+
}
|
|
231
243
|
}
|
|
232
244
|
function ensureFeedbackEventSchema(db) {
|
|
233
245
|
if (!tableExists(db, "feedback_events")) return;
|
|
@@ -403,11 +415,55 @@ function migrateV6ToV7(db) {
|
|
|
403
415
|
throw e;
|
|
404
416
|
}
|
|
405
417
|
}
|
|
418
|
+
function migrateV7ToV8(db) {
|
|
419
|
+
if (tableExists(db, "memory_archive")) {
|
|
420
|
+
db.prepare("INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('version', ?)").run(String(8));
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
try {
|
|
424
|
+
db.exec("BEGIN");
|
|
425
|
+
db.exec(`
|
|
426
|
+
CREATE TABLE IF NOT EXISTS memory_archive (
|
|
427
|
+
id TEXT PRIMARY KEY,
|
|
428
|
+
content TEXT NOT NULL,
|
|
429
|
+
type TEXT NOT NULL,
|
|
430
|
+
priority INTEGER NOT NULL,
|
|
431
|
+
emotion_val REAL NOT NULL DEFAULT 0.0,
|
|
432
|
+
vitality REAL NOT NULL DEFAULT 0.0,
|
|
433
|
+
stability REAL NOT NULL DEFAULT 1.0,
|
|
434
|
+
access_count INTEGER NOT NULL DEFAULT 0,
|
|
435
|
+
last_accessed TEXT,
|
|
436
|
+
created_at TEXT NOT NULL,
|
|
437
|
+
updated_at TEXT NOT NULL,
|
|
438
|
+
archived_at TEXT NOT NULL,
|
|
439
|
+
archive_reason TEXT NOT NULL DEFAULT 'eviction',
|
|
440
|
+
source TEXT,
|
|
441
|
+
agent_id TEXT NOT NULL DEFAULT 'default',
|
|
442
|
+
hash TEXT,
|
|
443
|
+
emotion_tag TEXT,
|
|
444
|
+
source_session TEXT,
|
|
445
|
+
source_context TEXT,
|
|
446
|
+
observed_at TEXT
|
|
447
|
+
);
|
|
448
|
+
`);
|
|
449
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_archive_agent ON memory_archive(agent_id);");
|
|
450
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_archive_type ON memory_archive(type);");
|
|
451
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_archive_archived_at ON memory_archive(archived_at);");
|
|
452
|
+
db.prepare("INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('version', ?)").run(String(8));
|
|
453
|
+
db.exec("COMMIT");
|
|
454
|
+
} catch (e) {
|
|
455
|
+
try {
|
|
456
|
+
db.exec("ROLLBACK");
|
|
457
|
+
} catch {
|
|
458
|
+
}
|
|
459
|
+
throw e;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
406
462
|
var SCHEMA_VERSION, SCHEMA_SQL;
|
|
407
463
|
var init_db = __esm({
|
|
408
464
|
"src/core/db.ts"() {
|
|
409
465
|
"use strict";
|
|
410
|
-
SCHEMA_VERSION =
|
|
466
|
+
SCHEMA_VERSION = 8;
|
|
411
467
|
SCHEMA_SQL = `
|
|
412
468
|
-- Memory entries
|
|
413
469
|
CREATE TABLE IF NOT EXISTS memories (
|
|
@@ -513,6 +569,30 @@ CREATE TABLE IF NOT EXISTS schema_meta (
|
|
|
513
569
|
value TEXT NOT NULL
|
|
514
570
|
);
|
|
515
571
|
|
|
572
|
+
-- Memory archive (eviction archive)
|
|
573
|
+
CREATE TABLE IF NOT EXISTS memory_archive (
|
|
574
|
+
id TEXT PRIMARY KEY,
|
|
575
|
+
content TEXT NOT NULL,
|
|
576
|
+
type TEXT NOT NULL,
|
|
577
|
+
priority INTEGER NOT NULL,
|
|
578
|
+
emotion_val REAL NOT NULL DEFAULT 0.0,
|
|
579
|
+
vitality REAL NOT NULL DEFAULT 0.0,
|
|
580
|
+
stability REAL NOT NULL DEFAULT 1.0,
|
|
581
|
+
access_count INTEGER NOT NULL DEFAULT 0,
|
|
582
|
+
last_accessed TEXT,
|
|
583
|
+
created_at TEXT NOT NULL,
|
|
584
|
+
updated_at TEXT NOT NULL,
|
|
585
|
+
archived_at TEXT NOT NULL,
|
|
586
|
+
archive_reason TEXT NOT NULL DEFAULT 'eviction',
|
|
587
|
+
source TEXT,
|
|
588
|
+
agent_id TEXT NOT NULL DEFAULT 'default',
|
|
589
|
+
hash TEXT,
|
|
590
|
+
emotion_tag TEXT,
|
|
591
|
+
source_session TEXT,
|
|
592
|
+
source_context TEXT,
|
|
593
|
+
observed_at TEXT
|
|
594
|
+
);
|
|
595
|
+
|
|
516
596
|
-- Indexes for common queries
|
|
517
597
|
CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);
|
|
518
598
|
CREATE INDEX IF NOT EXISTS idx_memories_priority ON memories(priority);
|
|
@@ -523,6 +603,9 @@ CREATE INDEX IF NOT EXISTS idx_paths_memory ON paths(memory_id);
|
|
|
523
603
|
CREATE INDEX IF NOT EXISTS idx_paths_domain ON paths(domain);
|
|
524
604
|
CREATE INDEX IF NOT EXISTS idx_maintenance_jobs_phase_status ON maintenance_jobs(phase, status, started_at DESC);
|
|
525
605
|
CREATE INDEX IF NOT EXISTS idx_feedback_events_memory ON feedback_events(memory_id, created_at DESC);
|
|
606
|
+
CREATE INDEX IF NOT EXISTS idx_memory_archive_agent ON memory_archive(agent_id);
|
|
607
|
+
CREATE INDEX IF NOT EXISTS idx_memory_archive_type ON memory_archive(type);
|
|
608
|
+
CREATE INDEX IF NOT EXISTS idx_memory_archive_archived_at ON memory_archive(archived_at);
|
|
526
609
|
`;
|
|
527
610
|
}
|
|
528
611
|
});
|
|
@@ -1152,9 +1235,54 @@ function updateMemory(db, id, input) {
|
|
|
1152
1235
|
}
|
|
1153
1236
|
function deleteMemory(db, id) {
|
|
1154
1237
|
db.prepare("DELETE FROM memories_fts WHERE id = ?").run(id);
|
|
1238
|
+
try {
|
|
1239
|
+
db.prepare("DELETE FROM embeddings WHERE memory_id = ?").run(id);
|
|
1240
|
+
} catch {
|
|
1241
|
+
}
|
|
1155
1242
|
const result = db.prepare("DELETE FROM memories WHERE id = ?").run(id);
|
|
1156
1243
|
return result.changes > 0;
|
|
1157
1244
|
}
|
|
1245
|
+
function archiveMemory(db, id, reason, opts) {
|
|
1246
|
+
const mem = getMemory(db, id);
|
|
1247
|
+
if (!mem) return false;
|
|
1248
|
+
const minVitality = opts?.minVitality ?? 0.1;
|
|
1249
|
+
if (mem.vitality < minVitality) {
|
|
1250
|
+
deleteMemory(db, id);
|
|
1251
|
+
return "deleted";
|
|
1252
|
+
}
|
|
1253
|
+
const archivedAt = now();
|
|
1254
|
+
const archiveReason = reason ?? "eviction";
|
|
1255
|
+
db.prepare(
|
|
1256
|
+
`INSERT OR REPLACE INTO memory_archive
|
|
1257
|
+
(id, content, type, priority, emotion_val, vitality, stability, access_count,
|
|
1258
|
+
last_accessed, created_at, updated_at, archived_at, archive_reason,
|
|
1259
|
+
source, agent_id, hash, emotion_tag, source_session, source_context, observed_at)
|
|
1260
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
1261
|
+
).run(
|
|
1262
|
+
mem.id,
|
|
1263
|
+
mem.content,
|
|
1264
|
+
mem.type,
|
|
1265
|
+
mem.priority,
|
|
1266
|
+
mem.emotion_val,
|
|
1267
|
+
mem.vitality,
|
|
1268
|
+
mem.stability,
|
|
1269
|
+
mem.access_count,
|
|
1270
|
+
mem.last_accessed,
|
|
1271
|
+
mem.created_at,
|
|
1272
|
+
mem.updated_at,
|
|
1273
|
+
archivedAt,
|
|
1274
|
+
archiveReason,
|
|
1275
|
+
mem.source,
|
|
1276
|
+
mem.agent_id,
|
|
1277
|
+
mem.hash,
|
|
1278
|
+
mem.emotion_tag,
|
|
1279
|
+
mem.source_session,
|
|
1280
|
+
mem.source_context,
|
|
1281
|
+
mem.observed_at
|
|
1282
|
+
);
|
|
1283
|
+
deleteMemory(db, id);
|
|
1284
|
+
return "archived";
|
|
1285
|
+
}
|
|
1158
1286
|
function listMemories(db, opts) {
|
|
1159
1287
|
const conditions = [];
|
|
1160
1288
|
const params = [];
|
|
@@ -1649,21 +1777,20 @@ async function searchVectorBranch(db, query, opts) {
|
|
|
1649
1777
|
before: opts.before
|
|
1650
1778
|
});
|
|
1651
1779
|
}
|
|
1652
|
-
function
|
|
1653
|
-
const existingIds = new Set(results.map((r) => r.memory.id));
|
|
1780
|
+
function fetchRelatedLinks(db, sourceIds, agentId, excludeIds, maxPerSource = 5) {
|
|
1654
1781
|
const related = [];
|
|
1655
|
-
for (const
|
|
1782
|
+
for (const sourceId of sourceIds) {
|
|
1656
1783
|
const links = db.prepare(
|
|
1657
1784
|
`SELECT l.target_id, l.weight, m.*
|
|
1658
1785
|
FROM links l
|
|
1659
1786
|
JOIN memories m ON m.id = l.target_id
|
|
1660
1787
|
WHERE l.agent_id = ? AND l.source_id = ?
|
|
1661
1788
|
ORDER BY l.weight DESC
|
|
1662
|
-
LIMIT
|
|
1663
|
-
).all(agentId,
|
|
1789
|
+
LIMIT ?`
|
|
1790
|
+
).all(agentId, sourceId, maxPerSource);
|
|
1664
1791
|
for (const link of links) {
|
|
1665
|
-
if (
|
|
1666
|
-
|
|
1792
|
+
if (excludeIds.has(link.target_id)) continue;
|
|
1793
|
+
excludeIds.add(link.target_id);
|
|
1667
1794
|
const relatedMemory = {
|
|
1668
1795
|
id: link.id,
|
|
1669
1796
|
content: link.content,
|
|
@@ -1686,12 +1813,30 @@ function expandRelated(db, results, agentId, maxTotal) {
|
|
|
1686
1813
|
};
|
|
1687
1814
|
related.push({
|
|
1688
1815
|
memory: relatedMemory,
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
match_type: "related"
|
|
1816
|
+
sourceId,
|
|
1817
|
+
weight: link.weight
|
|
1692
1818
|
});
|
|
1693
1819
|
}
|
|
1694
1820
|
}
|
|
1821
|
+
return related;
|
|
1822
|
+
}
|
|
1823
|
+
function expandRelated(db, results, agentId, maxTotal) {
|
|
1824
|
+
const existingIds = new Set(results.map((r) => r.memory.id));
|
|
1825
|
+
const links = fetchRelatedLinks(
|
|
1826
|
+
db,
|
|
1827
|
+
results.map((r) => r.memory.id),
|
|
1828
|
+
agentId,
|
|
1829
|
+
existingIds
|
|
1830
|
+
);
|
|
1831
|
+
const related = links.map((link) => {
|
|
1832
|
+
const sourceResult = results.find((r) => r.memory.id === link.sourceId);
|
|
1833
|
+
return {
|
|
1834
|
+
memory: link.memory,
|
|
1835
|
+
score: (sourceResult?.score ?? 0) * link.weight * 0.6,
|
|
1836
|
+
related_source_id: link.sourceId,
|
|
1837
|
+
match_type: "related"
|
|
1838
|
+
};
|
|
1839
|
+
});
|
|
1695
1840
|
const directResults = results.map((r) => ({
|
|
1696
1841
|
...r,
|
|
1697
1842
|
match_type: "direct"
|
|
@@ -2192,7 +2337,8 @@ async function surfaceMemories(db, input) {
|
|
|
2192
2337
|
}),
|
|
2193
2338
|
lexical_rank: signal.queryRank ?? signal.recentRank ?? signal.taskRank,
|
|
2194
2339
|
semantic_rank: signal.semanticRank,
|
|
2195
|
-
semantic_similarity: signal.semanticSimilarity
|
|
2340
|
+
semantic_similarity: signal.semanticSimilarity,
|
|
2341
|
+
match_type: "direct"
|
|
2196
2342
|
};
|
|
2197
2343
|
}).sort((left, right) => {
|
|
2198
2344
|
if (right.score !== left.score) return right.score - left.score;
|
|
@@ -2201,6 +2347,42 @@ async function surfaceMemories(db, input) {
|
|
|
2201
2347
|
if (left.memory.priority !== right.memory.priority) return left.memory.priority - right.memory.priority;
|
|
2202
2348
|
return right.memory.updated_at.localeCompare(left.memory.updated_at);
|
|
2203
2349
|
}).slice(0, limit);
|
|
2350
|
+
if (input.related) {
|
|
2351
|
+
const existingIds = new Set(results.map((r) => r.memory.id));
|
|
2352
|
+
const links = fetchRelatedLinks(
|
|
2353
|
+
db,
|
|
2354
|
+
results.map((r) => r.memory.id),
|
|
2355
|
+
agentId,
|
|
2356
|
+
existingIds
|
|
2357
|
+
);
|
|
2358
|
+
const relatedResults = links.map((link) => {
|
|
2359
|
+
const sourceResult = results.find((r) => r.memory.id === link.sourceId);
|
|
2360
|
+
const feedbackSummary = getFeedbackSummary(db, link.memory.id, agentId);
|
|
2361
|
+
return {
|
|
2362
|
+
memory: link.memory,
|
|
2363
|
+
score: (sourceResult?.score ?? 0) * link.weight * 0.6,
|
|
2364
|
+
semantic_score: 0,
|
|
2365
|
+
lexical_score: 0,
|
|
2366
|
+
task_match: 0,
|
|
2367
|
+
vitality: link.memory.vitality,
|
|
2368
|
+
priority_prior: priorityPrior(link.memory.priority),
|
|
2369
|
+
feedback_score: feedbackSummary.score,
|
|
2370
|
+
feedback_summary: feedbackSummary,
|
|
2371
|
+
reason_codes: [`type:${link.memory.type}`, "related"],
|
|
2372
|
+
related_source_id: link.sourceId,
|
|
2373
|
+
match_type: "related"
|
|
2374
|
+
};
|
|
2375
|
+
});
|
|
2376
|
+
const maxTotal = Math.floor(limit * 1.5);
|
|
2377
|
+
const combined = [...results, ...relatedResults].sort((a, b) => b.score - a.score).slice(0, maxTotal);
|
|
2378
|
+
return {
|
|
2379
|
+
count: combined.length,
|
|
2380
|
+
query: trimmedQuery,
|
|
2381
|
+
task: trimmedTask,
|
|
2382
|
+
intent: input.intent,
|
|
2383
|
+
results: combined
|
|
2384
|
+
};
|
|
2385
|
+
}
|
|
2204
2386
|
return {
|
|
2205
2387
|
count: results.length,
|
|
2206
2388
|
query: trimmedQuery,
|
|
@@ -2949,13 +3131,31 @@ function rankEvictionCandidates(db, opts) {
|
|
|
2949
3131
|
return left.memory.priority - right.memory.priority;
|
|
2950
3132
|
});
|
|
2951
3133
|
}
|
|
3134
|
+
function parseEnvInt(envKey) {
|
|
3135
|
+
const raw = process.env[envKey];
|
|
3136
|
+
if (raw === void 0 || raw === "") return null;
|
|
3137
|
+
const n = Number.parseInt(raw, 10);
|
|
3138
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
3139
|
+
}
|
|
3140
|
+
function getTieredCapacity(opts) {
|
|
3141
|
+
const envMax = parseEnvInt("AGENT_MEMORY_MAX_MEMORIES");
|
|
3142
|
+
return {
|
|
3143
|
+
identity: parseEnvInt("AGENT_MEMORY_MAX_IDENTITY"),
|
|
3144
|
+
// default: null (unlimited)
|
|
3145
|
+
emotion: parseEnvInt("AGENT_MEMORY_MAX_EMOTION") ?? 50,
|
|
3146
|
+
knowledge: parseEnvInt("AGENT_MEMORY_MAX_KNOWLEDGE") ?? 250,
|
|
3147
|
+
event: parseEnvInt("AGENT_MEMORY_MAX_EVENT") ?? 50,
|
|
3148
|
+
total: opts?.maxMemories ?? (envMax ?? 350)
|
|
3149
|
+
};
|
|
3150
|
+
}
|
|
2952
3151
|
function runGovern(db, opts) {
|
|
2953
3152
|
const agentId = opts?.agent_id;
|
|
2954
|
-
const
|
|
2955
|
-
const maxMemories = opts?.maxMemories ?? (Number.isFinite(envMax) && envMax > 0 ? envMax : 200);
|
|
3153
|
+
const capacity = getTieredCapacity(opts);
|
|
2956
3154
|
let orphanPaths = 0;
|
|
2957
3155
|
let emptyMemories = 0;
|
|
2958
3156
|
let evicted = 0;
|
|
3157
|
+
let archived = 0;
|
|
3158
|
+
const evictedByType = {};
|
|
2959
3159
|
const transaction = db.transaction(() => {
|
|
2960
3160
|
const pathResult = agentId ? db.prepare(
|
|
2961
3161
|
`DELETE FROM paths
|
|
@@ -2965,17 +3165,48 @@ function runGovern(db, opts) {
|
|
|
2965
3165
|
orphanPaths = pathResult.changes;
|
|
2966
3166
|
const emptyResult = agentId ? db.prepare("DELETE FROM memories WHERE agent_id = ? AND TRIM(content) = ''").run(agentId) : db.prepare("DELETE FROM memories WHERE TRIM(content) = ''").run();
|
|
2967
3167
|
emptyMemories = emptyResult.changes;
|
|
3168
|
+
const typeLimits = [
|
|
3169
|
+
{ type: "identity", limit: capacity.identity },
|
|
3170
|
+
{ type: "emotion", limit: capacity.emotion },
|
|
3171
|
+
{ type: "knowledge", limit: capacity.knowledge },
|
|
3172
|
+
{ type: "event", limit: capacity.event }
|
|
3173
|
+
];
|
|
3174
|
+
const allCandidates = rankEvictionCandidates(db, { agent_id: agentId });
|
|
3175
|
+
const evictedIds = /* @__PURE__ */ new Set();
|
|
3176
|
+
for (const { type, limit } of typeLimits) {
|
|
3177
|
+
if (limit === null) continue;
|
|
3178
|
+
const typeCount = db.prepare(
|
|
3179
|
+
agentId ? "SELECT COUNT(*) as c FROM memories WHERE agent_id = ? AND type = ?" : "SELECT COUNT(*) as c FROM memories WHERE type = ?"
|
|
3180
|
+
).get(...agentId ? [agentId, type] : [type]).c;
|
|
3181
|
+
const excess = Math.max(0, typeCount - limit);
|
|
3182
|
+
if (excess <= 0) continue;
|
|
3183
|
+
const typeCandidates = allCandidates.filter((c) => c.memory.type === type && !evictedIds.has(c.memory.id));
|
|
3184
|
+
const toEvict = typeCandidates.slice(0, excess);
|
|
3185
|
+
for (const candidate of toEvict) {
|
|
3186
|
+
const result = archiveMemory(db, candidate.memory.id, "eviction");
|
|
3187
|
+
evictedIds.add(candidate.memory.id);
|
|
3188
|
+
evicted += 1;
|
|
3189
|
+
if (result === "archived") archived += 1;
|
|
3190
|
+
evictedByType[type] = (evictedByType[type] ?? 0) + 1;
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
2968
3193
|
const total = db.prepare(agentId ? "SELECT COUNT(*) as c FROM memories WHERE agent_id = ?" : "SELECT COUNT(*) as c FROM memories").get(...agentId ? [agentId] : []).c;
|
|
2969
|
-
const
|
|
2970
|
-
if (
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
3194
|
+
const globalExcess = Math.max(0, total - capacity.total);
|
|
3195
|
+
if (globalExcess > 0) {
|
|
3196
|
+
const globalCandidates = allCandidates.filter((c) => !evictedIds.has(c.memory.id));
|
|
3197
|
+
const toEvict = globalCandidates.slice(0, globalExcess);
|
|
3198
|
+
for (const candidate of toEvict) {
|
|
3199
|
+
const result = archiveMemory(db, candidate.memory.id, "eviction");
|
|
3200
|
+
evictedIds.add(candidate.memory.id);
|
|
3201
|
+
evicted += 1;
|
|
3202
|
+
if (result === "archived") archived += 1;
|
|
3203
|
+
const t = candidate.memory.type;
|
|
3204
|
+
evictedByType[t] = (evictedByType[t] ?? 0) + 1;
|
|
3205
|
+
}
|
|
2975
3206
|
}
|
|
2976
3207
|
});
|
|
2977
3208
|
transaction();
|
|
2978
|
-
return { orphanPaths, emptyMemories, evicted };
|
|
3209
|
+
return { orphanPaths, emptyMemories, evicted, archived, evictedByType };
|
|
2979
3210
|
}
|
|
2980
3211
|
|
|
2981
3212
|
// src/sleep/jobs.ts
|
|
@@ -3218,12 +3449,21 @@ function getMemoryStatus(db, input) {
|
|
|
3218
3449
|
const feedbackEvents = db.prepare(
|
|
3219
3450
|
"SELECT COUNT(*) as c FROM feedback_events WHERE agent_id = ?"
|
|
3220
3451
|
).get(agentId);
|
|
3452
|
+
const tiered = getTieredCapacity();
|
|
3453
|
+
const capacity = {
|
|
3454
|
+
identity: { count: stats.by_type.identity ?? 0, limit: tiered.identity },
|
|
3455
|
+
emotion: { count: stats.by_type.emotion ?? 0, limit: tiered.emotion },
|
|
3456
|
+
knowledge: { count: stats.by_type.knowledge ?? 0, limit: tiered.knowledge },
|
|
3457
|
+
event: { count: stats.by_type.event ?? 0, limit: tiered.event },
|
|
3458
|
+
total: { count: stats.total, limit: tiered.total }
|
|
3459
|
+
};
|
|
3221
3460
|
return {
|
|
3222
3461
|
...stats,
|
|
3223
3462
|
paths: totalPaths.c,
|
|
3224
3463
|
low_vitality: lowVitality.c,
|
|
3225
3464
|
feedback_events: feedbackEvents.c,
|
|
3226
|
-
agent_id: agentId
|
|
3465
|
+
agent_id: agentId,
|
|
3466
|
+
capacity
|
|
3227
3467
|
};
|
|
3228
3468
|
}
|
|
3229
3469
|
|
|
@@ -3357,6 +3597,8 @@ function formatRecallResponse(result) {
|
|
|
3357
3597
|
vector_rank: row.vector_rank,
|
|
3358
3598
|
bm25_score: row.bm25_score,
|
|
3359
3599
|
vector_score: row.vector_score,
|
|
3600
|
+
related_source_id: row.related_source_id,
|
|
3601
|
+
match_type: row.match_type,
|
|
3360
3602
|
updated_at: row.memory.updated_at
|
|
3361
3603
|
}))
|
|
3362
3604
|
};
|
|
@@ -3381,6 +3623,8 @@ function formatSurfaceResponse(result) {
|
|
|
3381
3623
|
feedback_score: row.feedback_score,
|
|
3382
3624
|
feedback_summary: row.feedback_summary,
|
|
3383
3625
|
reason_codes: row.reason_codes,
|
|
3626
|
+
related_source_id: row.related_source_id,
|
|
3627
|
+
match_type: row.match_type,
|
|
3384
3628
|
updated_at: row.memory.updated_at
|
|
3385
3629
|
}))
|
|
3386
3630
|
};
|
|
@@ -3550,7 +3794,11 @@ function createHttpServer(options) {
|
|
|
3550
3794
|
emotion_val: asNumber(body.emotion_val),
|
|
3551
3795
|
agent_id: asString(body.agent_id) ?? defaultAgentId,
|
|
3552
3796
|
conservative: asBoolean(body.conservative),
|
|
3553
|
-
provider: options?.provider
|
|
3797
|
+
provider: options?.provider,
|
|
3798
|
+
emotion_tag: asString(body.emotion_tag),
|
|
3799
|
+
source_session: asString(body.source_session),
|
|
3800
|
+
source_context: asString(body.source_context),
|
|
3801
|
+
observed_at: asString(body.observed_at)
|
|
3554
3802
|
});
|
|
3555
3803
|
sendJson(res, 200, result);
|
|
3556
3804
|
return;
|
|
@@ -3565,7 +3813,12 @@ function createHttpServer(options) {
|
|
|
3565
3813
|
query,
|
|
3566
3814
|
limit: asNumber(body.limit),
|
|
3567
3815
|
agent_id: asString(body.agent_id) ?? defaultAgentId,
|
|
3568
|
-
provider: options?.provider
|
|
3816
|
+
provider: options?.provider,
|
|
3817
|
+
related: asBoolean(body.related),
|
|
3818
|
+
after: asString(body.after),
|
|
3819
|
+
before: asString(body.before),
|
|
3820
|
+
recency_boost: asNumber(body.recency_boost),
|
|
3821
|
+
emotion_tag: asString(body.emotion_tag)
|
|
3569
3822
|
});
|
|
3570
3823
|
sendJson(res, 200, formatRecallResponse(result));
|
|
3571
3824
|
return;
|
|
@@ -3585,7 +3838,12 @@ function createHttpServer(options) {
|
|
|
3585
3838
|
types,
|
|
3586
3839
|
limit: asNumber(body.limit),
|
|
3587
3840
|
agent_id: asString(body.agent_id) ?? defaultAgentId,
|
|
3588
|
-
provider: options?.provider
|
|
3841
|
+
provider: options?.provider,
|
|
3842
|
+
related: asBoolean(body.related),
|
|
3843
|
+
after: asString(body.after),
|
|
3844
|
+
before: asString(body.before),
|
|
3845
|
+
recency_boost: asNumber(body.recency_boost),
|
|
3846
|
+
emotion_tag: asString(body.emotion_tag)
|
|
3589
3847
|
});
|
|
3590
3848
|
sendJson(res, 200, formatSurfaceResponse(result));
|
|
3591
3849
|
return;
|
|
@@ -3719,7 +3977,7 @@ function getAgentId() {
|
|
|
3719
3977
|
}
|
|
3720
3978
|
function printHelp() {
|
|
3721
3979
|
console.log(`
|
|
3722
|
-
\u{1F9E0} AgentMemory
|
|
3980
|
+
\u{1F9E0} AgentMemory \u2014 Sleep-cycle memory for AI agents
|
|
3723
3981
|
|
|
3724
3982
|
Usage: agent-memory <command> [options]
|
|
3725
3983
|
|