@smyslenny/agent-memory 5.0.2 → 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/CHANGELOG.md +39 -1
- package/README.md +1 -1
- package/dist/bin/agent-memory.js +197 -11
- package/dist/bin/agent-memory.js.map +1 -1
- package/dist/index.d.ts +65 -3
- package/dist/index.js +266 -11
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +336 -14
- package/dist/mcp/server.js.map +1 -1
- package/docs/README-zh.md +1 -2
- package/docs/integrations/generic.md +43 -3
- package/docs/integrations/openclaw.md +47 -7
- package/docs/migration-v3-v4.md +15 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -72,6 +72,46 @@ declare function createMemory(db: Database.Database, input: CreateMemoryInput):
|
|
|
72
72
|
declare function getMemory(db: Database.Database, id: string): Memory | null;
|
|
73
73
|
declare function updateMemory(db: Database.Database, id: string, input: UpdateMemoryInput): Memory | null;
|
|
74
74
|
declare function deleteMemory(db: Database.Database, id: string): boolean;
|
|
75
|
+
interface ArchivedMemory {
|
|
76
|
+
id: string;
|
|
77
|
+
content: string;
|
|
78
|
+
type: string;
|
|
79
|
+
priority: number;
|
|
80
|
+
emotion_val: number;
|
|
81
|
+
vitality: number;
|
|
82
|
+
stability: number;
|
|
83
|
+
access_count: number;
|
|
84
|
+
last_accessed: string | null;
|
|
85
|
+
created_at: string;
|
|
86
|
+
updated_at: string;
|
|
87
|
+
archived_at: string;
|
|
88
|
+
archive_reason: string;
|
|
89
|
+
source: string | null;
|
|
90
|
+
agent_id: string;
|
|
91
|
+
hash: string | null;
|
|
92
|
+
emotion_tag: string | null;
|
|
93
|
+
source_session: string | null;
|
|
94
|
+
source_context: string | null;
|
|
95
|
+
observed_at: string | null;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Archive a memory to memory_archive, then delete from memories.
|
|
99
|
+
* If minVitality is set (default 0.1), memories below that threshold
|
|
100
|
+
* are directly deleted without archiving — they're decayed noise.
|
|
101
|
+
* Returns "archived" if actually archived, "deleted" if skipped to direct delete,
|
|
102
|
+
* or false if memory not found.
|
|
103
|
+
*/
|
|
104
|
+
declare function archiveMemory(db: Database.Database, id: string, reason?: string, opts?: {
|
|
105
|
+
minVitality?: number;
|
|
106
|
+
}): "archived" | "deleted" | false;
|
|
107
|
+
declare function restoreMemory(db: Database.Database, id: string): Memory | null;
|
|
108
|
+
declare function listArchivedMemories(db: Database.Database, opts?: {
|
|
109
|
+
agent_id?: string;
|
|
110
|
+
limit?: number;
|
|
111
|
+
}): ArchivedMemory[];
|
|
112
|
+
declare function purgeArchive(db: Database.Database, opts?: {
|
|
113
|
+
agent_id?: string;
|
|
114
|
+
}): number;
|
|
75
115
|
declare function listMemories(db: Database.Database, opts?: {
|
|
76
116
|
agent_id?: string;
|
|
77
117
|
type?: MemoryType;
|
|
@@ -517,6 +557,10 @@ interface ReflectInput {
|
|
|
517
557
|
}
|
|
518
558
|
declare function reflectMemories(db: Database.Database, input: ReflectInput): Promise<ReflectRunResult>;
|
|
519
559
|
|
|
560
|
+
interface CapacityInfo {
|
|
561
|
+
count: number;
|
|
562
|
+
limit: number | null;
|
|
563
|
+
}
|
|
520
564
|
interface StatusResult {
|
|
521
565
|
total: number;
|
|
522
566
|
by_type: Record<string, number>;
|
|
@@ -525,6 +569,7 @@ interface StatusResult {
|
|
|
525
569
|
low_vitality: number;
|
|
526
570
|
feedback_events: number;
|
|
527
571
|
agent_id: string;
|
|
572
|
+
capacity: Record<string, CapacityInfo>;
|
|
528
573
|
}
|
|
529
574
|
declare function getMemoryStatus(db: Database.Database, input?: {
|
|
530
575
|
agent_id?: string;
|
|
@@ -764,6 +809,8 @@ interface GovernResult {
|
|
|
764
809
|
orphanPaths: number;
|
|
765
810
|
emptyMemories: number;
|
|
766
811
|
evicted: number;
|
|
812
|
+
archived: number;
|
|
813
|
+
evictedByType: Record<string, number>;
|
|
767
814
|
}
|
|
768
815
|
interface EvictionCandidate {
|
|
769
816
|
memory: Memory;
|
|
@@ -783,13 +830,28 @@ declare function computeEvictionScore(input: {
|
|
|
783
830
|
declare function rankEvictionCandidates(db: Database.Database, opts?: {
|
|
784
831
|
agent_id?: string;
|
|
785
832
|
}): EvictionCandidate[];
|
|
833
|
+
interface TieredCapacity {
|
|
834
|
+
identity: number | null;
|
|
835
|
+
emotion: number | null;
|
|
836
|
+
knowledge: number | null;
|
|
837
|
+
event: number | null;
|
|
838
|
+
total: number;
|
|
839
|
+
}
|
|
840
|
+
declare function getTieredCapacity(opts?: {
|
|
841
|
+
maxMemories?: number;
|
|
842
|
+
}): TieredCapacity;
|
|
786
843
|
/**
|
|
787
844
|
* Run governance checks and cleanup:
|
|
788
845
|
* 1. Remove orphan paths (no parent memory)
|
|
789
846
|
* 2. Remove empty memories (blank content)
|
|
790
|
-
* 3. Evict low-value memories when over capacity using
|
|
847
|
+
* 3. Evict low-value memories when over capacity using tiered + global eviction
|
|
848
|
+
*
|
|
849
|
+
* Tiered capacity can be set via env vars:
|
|
850
|
+
* AGENT_MEMORY_MAX_IDENTITY, AGENT_MEMORY_MAX_EMOTION,
|
|
851
|
+
* AGENT_MEMORY_MAX_KNOWLEDGE, AGENT_MEMORY_MAX_EVENT,
|
|
852
|
+
* AGENT_MEMORY_MAX_MEMORIES (global cap, default 350)
|
|
791
853
|
*
|
|
792
|
-
*
|
|
854
|
+
* Evicted memories are archived to memory_archive before deletion.
|
|
793
855
|
*/
|
|
794
856
|
declare function runGovern(db: Database.Database, opts?: {
|
|
795
857
|
agent_id?: string;
|
|
@@ -843,4 +905,4 @@ declare function formatNarrativeBoot(layers: {
|
|
|
843
905
|
*/
|
|
844
906
|
declare function boot(db: Database.Database, opts?: WarmBootOptions): WarmBootResult;
|
|
845
907
|
|
|
846
|
-
export { type AgentMemoryHttpServer, type ReflectInput as AppReflectInput, type ReflectProgressEvent as AppReflectProgressEvent, type AutoIngestWatcher, type AutoIngestWatcherOptions, type BootResult, type ConflictInfo, type ConflictType, type CreateMemoryInput, type DedupScoreBreakdown, type EmbeddingProvider, type EmbeddingProviderConfig, type EmbeddingProviderKind, type EmbeddingProviderOptions, type EmbeddingStatus, type EvictionCandidate, type ExportResult, type FeedbackEventInput, type FeedbackEventRecord, type FeedbackSource, type FeedbackSummary, type GovernResult, type GuardAction, type GuardInput, type GuardResult, type HttpJobStatus, type HttpServerOptions, type HybridRecallResponse, type HybridRecallResult, type IngestExtractedItem, type IngestResult, type IngestRunOptions, type MaintenanceJob, type MaintenancePhase, type MaintenanceStatus, type Memory, type MemoryType, type MergeContext, type MergePlan, type Path, type PendingEmbeddingRecord, type Priority, type RecallInput, type ReflectCheckpoint, type ReflectOptions, type ReflectProgressEvent$1 as ReflectProgressEvent, type ReflectRunResult, type ReflectRunners, type ReflectStats, type ReflectStep, type ReindexEmbeddingsResult, type ReindexInput, type ReindexProgressEvent, type ReindexSearchResult, type RelatedLink, type RememberInput, type SearchResult, type StatusResult, type StoredEmbedding, type SurfaceInput, type SurfaceIntent, type SurfaceResponse, type SurfaceResult, type SyncInput, type SyncResult, type TidyResult, type UpdateMemoryInput, type VectorSearchResult, type WarmBootOptions, type WarmBootResult, boot, buildFtsQuery, buildMergePlan, calculateVitality, classifyIngestType, completeMaintenanceJob, computeEvictionScore, contentHash, cosineSimilarity, countMemories, createEmbeddingProvider, createHttpServer, createInitialCheckpoint, createLocalHttpEmbeddingProvider, createMaintenanceJob, createMemory, createOpenAICompatibleEmbeddingProvider, createPath, decodeVector, deleteMemory, deletePath, encodeVector, exportMemories, extractIngestItems, failMaintenanceJob, fetchRelatedLinks, findResumableMaintenanceJob, formatNarrativeBoot, formatRelativeDate, fuseHybridResults, fusionScore, getConfiguredEmbeddingProviderId, getDecayedMemories, getEmbedding, getEmbeddingProvider, getEmbeddingProviderConfigFromEnv, getEmbeddingProviderFromEnv, getFeedbackScore, getFeedbackSummary, getMaintenanceJob, getMemory, getMemoryStatus, getPath, getPathByUri, getPathsByDomain, getPathsByMemory, getPathsByPrefix, guard, healthcheckEmbeddingProvider, ingestText, isStaleContent, listMemories, listPendingEmbeddings, loadWarmBootLayers, markAllEmbeddingsPending, markEmbeddingFailed, markMemoryEmbeddingPending, parseUri, priorityPrior, rankEvictionCandidates, rebuildBm25Index, recallMemories, recallMemory, recordAccess, recordFeedbackEvent, recordPassiveFeedback, reflectMemories, reindexEmbeddings, reindexMemories, reindexMemorySearch, rememberMemory, runAutoIngestWatcher, runDecay, runGovern, runReflectOrchestrator, runTidy, searchBM25, searchByVector, slugify, splitIngestBlocks, startHttpServer, surfaceMemories, syncBatch, syncOne, tokenize, updateMaintenanceCheckpoint, updateMemory, upsertReadyEmbedding };
|
|
908
|
+
export { type AgentMemoryHttpServer, type ReflectInput as AppReflectInput, type ReflectProgressEvent as AppReflectProgressEvent, type ArchivedMemory, type AutoIngestWatcher, type AutoIngestWatcherOptions, type BootResult, type CapacityInfo, type ConflictInfo, type ConflictType, type CreateMemoryInput, type DedupScoreBreakdown, type EmbeddingProvider, type EmbeddingProviderConfig, type EmbeddingProviderKind, type EmbeddingProviderOptions, type EmbeddingStatus, type EvictionCandidate, type ExportResult, type FeedbackEventInput, type FeedbackEventRecord, type FeedbackSource, type FeedbackSummary, type GovernResult, type GuardAction, type GuardInput, type GuardResult, type HttpJobStatus, type HttpServerOptions, type HybridRecallResponse, type HybridRecallResult, type IngestExtractedItem, type IngestResult, type IngestRunOptions, type MaintenanceJob, type MaintenancePhase, type MaintenanceStatus, type Memory, type MemoryType, type MergeContext, type MergePlan, type Path, type PendingEmbeddingRecord, type Priority, type RecallInput, type ReflectCheckpoint, type ReflectOptions, type ReflectProgressEvent$1 as ReflectProgressEvent, type ReflectRunResult, type ReflectRunners, type ReflectStats, type ReflectStep, type ReindexEmbeddingsResult, type ReindexInput, type ReindexProgressEvent, type ReindexSearchResult, type RelatedLink, type RememberInput, type SearchResult, type StatusResult, type StoredEmbedding, type SurfaceInput, type SurfaceIntent, type SurfaceResponse, type SurfaceResult, type SyncInput, type SyncResult, type TidyResult, type TieredCapacity, type UpdateMemoryInput, type VectorSearchResult, type WarmBootOptions, type WarmBootResult, archiveMemory, boot, buildFtsQuery, buildMergePlan, calculateVitality, classifyIngestType, completeMaintenanceJob, computeEvictionScore, contentHash, cosineSimilarity, countMemories, createEmbeddingProvider, createHttpServer, createInitialCheckpoint, createLocalHttpEmbeddingProvider, createMaintenanceJob, createMemory, createOpenAICompatibleEmbeddingProvider, createPath, decodeVector, deleteMemory, deletePath, encodeVector, exportMemories, extractIngestItems, failMaintenanceJob, fetchRelatedLinks, findResumableMaintenanceJob, formatNarrativeBoot, formatRelativeDate, fuseHybridResults, fusionScore, getConfiguredEmbeddingProviderId, getDecayedMemories, getEmbedding, getEmbeddingProvider, getEmbeddingProviderConfigFromEnv, getEmbeddingProviderFromEnv, getFeedbackScore, getFeedbackSummary, getMaintenanceJob, getMemory, getMemoryStatus, getPath, getPathByUri, getPathsByDomain, getPathsByMemory, getPathsByPrefix, getTieredCapacity, guard, healthcheckEmbeddingProvider, ingestText, isStaleContent, listArchivedMemories, listMemories, listPendingEmbeddings, loadWarmBootLayers, markAllEmbeddingsPending, markEmbeddingFailed, markMemoryEmbeddingPending, parseUri, priorityPrior, purgeArchive, rankEvictionCandidates, rebuildBm25Index, recallMemories, recallMemory, recordAccess, recordFeedbackEvent, recordPassiveFeedback, reflectMemories, reindexEmbeddings, reindexMemories, reindexMemorySearch, rememberMemory, restoreMemory, runAutoIngestWatcher, runDecay, runGovern, runReflectOrchestrator, runTidy, searchBM25, searchByVector, slugify, splitIngestBlocks, startHttpServer, surfaceMemories, syncBatch, syncOne, tokenize, updateMaintenanceCheckpoint, updateMemory, upsertReadyEmbedding };
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { createHash as createHash2 } from "crypto";
|
|
|
6
6
|
// src/core/db.ts
|
|
7
7
|
import Database from "better-sqlite3";
|
|
8
8
|
import { randomUUID } from "crypto";
|
|
9
|
-
var SCHEMA_VERSION =
|
|
9
|
+
var SCHEMA_VERSION = 8;
|
|
10
10
|
var SCHEMA_SQL = `
|
|
11
11
|
-- Memory entries
|
|
12
12
|
CREATE TABLE IF NOT EXISTS memories (
|
|
@@ -112,6 +112,30 @@ CREATE TABLE IF NOT EXISTS schema_meta (
|
|
|
112
112
|
value TEXT NOT NULL
|
|
113
113
|
);
|
|
114
114
|
|
|
115
|
+
-- Memory archive (eviction archive)
|
|
116
|
+
CREATE TABLE IF NOT EXISTS memory_archive (
|
|
117
|
+
id TEXT PRIMARY KEY,
|
|
118
|
+
content TEXT NOT NULL,
|
|
119
|
+
type TEXT NOT NULL,
|
|
120
|
+
priority INTEGER NOT NULL,
|
|
121
|
+
emotion_val REAL NOT NULL DEFAULT 0.0,
|
|
122
|
+
vitality REAL NOT NULL DEFAULT 0.0,
|
|
123
|
+
stability REAL NOT NULL DEFAULT 1.0,
|
|
124
|
+
access_count INTEGER NOT NULL DEFAULT 0,
|
|
125
|
+
last_accessed TEXT,
|
|
126
|
+
created_at TEXT NOT NULL,
|
|
127
|
+
updated_at TEXT NOT NULL,
|
|
128
|
+
archived_at TEXT NOT NULL,
|
|
129
|
+
archive_reason TEXT NOT NULL DEFAULT 'eviction',
|
|
130
|
+
source TEXT,
|
|
131
|
+
agent_id TEXT NOT NULL DEFAULT 'default',
|
|
132
|
+
hash TEXT,
|
|
133
|
+
emotion_tag TEXT,
|
|
134
|
+
source_session TEXT,
|
|
135
|
+
source_context TEXT,
|
|
136
|
+
observed_at TEXT
|
|
137
|
+
);
|
|
138
|
+
|
|
115
139
|
-- Indexes for common queries
|
|
116
140
|
CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);
|
|
117
141
|
CREATE INDEX IF NOT EXISTS idx_memories_priority ON memories(priority);
|
|
@@ -122,6 +146,9 @@ CREATE INDEX IF NOT EXISTS idx_paths_memory ON paths(memory_id);
|
|
|
122
146
|
CREATE INDEX IF NOT EXISTS idx_paths_domain ON paths(domain);
|
|
123
147
|
CREATE INDEX IF NOT EXISTS idx_maintenance_jobs_phase_status ON maintenance_jobs(phase, status, started_at DESC);
|
|
124
148
|
CREATE INDEX IF NOT EXISTS idx_feedback_events_memory ON feedback_events(memory_id, created_at DESC);
|
|
149
|
+
CREATE INDEX IF NOT EXISTS idx_memory_archive_agent ON memory_archive(agent_id);
|
|
150
|
+
CREATE INDEX IF NOT EXISTS idx_memory_archive_type ON memory_archive(type);
|
|
151
|
+
CREATE INDEX IF NOT EXISTS idx_memory_archive_archived_at ON memory_archive(archived_at);
|
|
125
152
|
`;
|
|
126
153
|
function isCountRow(row) {
|
|
127
154
|
return row !== null && typeof row === "object" && "c" in row && typeof row.c === "number";
|
|
@@ -213,6 +240,11 @@ function migrateDatabase(db, from, to) {
|
|
|
213
240
|
v = 7;
|
|
214
241
|
continue;
|
|
215
242
|
}
|
|
243
|
+
if (v === 7) {
|
|
244
|
+
migrateV7ToV8(db);
|
|
245
|
+
v = 8;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
216
248
|
throw new Error(`Unsupported schema migration path: v${from} \u2192 v${to} (stuck at v${v})`);
|
|
217
249
|
}
|
|
218
250
|
}
|
|
@@ -300,6 +332,8 @@ function inferSchemaVersion(db) {
|
|
|
300
332
|
const hasFeedbackEvents = tableExists(db, "feedback_events");
|
|
301
333
|
const hasEmotionTag = tableHasColumn(db, "memories", "emotion_tag");
|
|
302
334
|
const hasProvenance = tableHasColumn(db, "memories", "source_session") && tableHasColumn(db, "memories", "source_context") && tableHasColumn(db, "memories", "observed_at");
|
|
335
|
+
const hasMemoryArchive = tableExists(db, "memory_archive");
|
|
336
|
+
if (hasAgentScopedPaths && hasAgentScopedLinks && hasV4Embeddings && hasMaintenanceJobs && hasFeedbackEvents && hasEmotionTag && hasProvenance && hasMemoryArchive) return 8;
|
|
303
337
|
if (hasAgentScopedPaths && hasAgentScopedLinks && hasV4Embeddings && hasMaintenanceJobs && hasFeedbackEvents && hasEmotionTag && hasProvenance) return 7;
|
|
304
338
|
if (hasAgentScopedPaths && hasAgentScopedLinks && hasV4Embeddings && hasMaintenanceJobs && hasFeedbackEvents && hasEmotionTag) return 6;
|
|
305
339
|
if (hasAgentScopedPaths && hasAgentScopedLinks && hasV4Embeddings && hasMaintenanceJobs && hasFeedbackEvents) return 5;
|
|
@@ -341,6 +375,11 @@ function ensureIndexes(db) {
|
|
|
341
375
|
db.exec("CREATE INDEX IF NOT EXISTS idx_feedback_events_agent_source ON feedback_events(agent_id, source, created_at DESC);");
|
|
342
376
|
}
|
|
343
377
|
}
|
|
378
|
+
if (tableExists(db, "memory_archive")) {
|
|
379
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_archive_agent ON memory_archive(agent_id);");
|
|
380
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_archive_type ON memory_archive(type);");
|
|
381
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_archive_archived_at ON memory_archive(archived_at);");
|
|
382
|
+
}
|
|
344
383
|
}
|
|
345
384
|
function ensureFeedbackEventSchema(db) {
|
|
346
385
|
if (!tableExists(db, "feedback_events")) return;
|
|
@@ -516,6 +555,50 @@ function migrateV6ToV7(db) {
|
|
|
516
555
|
throw e;
|
|
517
556
|
}
|
|
518
557
|
}
|
|
558
|
+
function migrateV7ToV8(db) {
|
|
559
|
+
if (tableExists(db, "memory_archive")) {
|
|
560
|
+
db.prepare("INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('version', ?)").run(String(8));
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
try {
|
|
564
|
+
db.exec("BEGIN");
|
|
565
|
+
db.exec(`
|
|
566
|
+
CREATE TABLE IF NOT EXISTS memory_archive (
|
|
567
|
+
id TEXT PRIMARY KEY,
|
|
568
|
+
content TEXT NOT NULL,
|
|
569
|
+
type TEXT NOT NULL,
|
|
570
|
+
priority INTEGER NOT NULL,
|
|
571
|
+
emotion_val REAL NOT NULL DEFAULT 0.0,
|
|
572
|
+
vitality REAL NOT NULL DEFAULT 0.0,
|
|
573
|
+
stability REAL NOT NULL DEFAULT 1.0,
|
|
574
|
+
access_count INTEGER NOT NULL DEFAULT 0,
|
|
575
|
+
last_accessed TEXT,
|
|
576
|
+
created_at TEXT NOT NULL,
|
|
577
|
+
updated_at TEXT NOT NULL,
|
|
578
|
+
archived_at TEXT NOT NULL,
|
|
579
|
+
archive_reason TEXT NOT NULL DEFAULT 'eviction',
|
|
580
|
+
source TEXT,
|
|
581
|
+
agent_id TEXT NOT NULL DEFAULT 'default',
|
|
582
|
+
hash TEXT,
|
|
583
|
+
emotion_tag TEXT,
|
|
584
|
+
source_session TEXT,
|
|
585
|
+
source_context TEXT,
|
|
586
|
+
observed_at TEXT
|
|
587
|
+
);
|
|
588
|
+
`);
|
|
589
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_archive_agent ON memory_archive(agent_id);");
|
|
590
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_archive_type ON memory_archive(type);");
|
|
591
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_memory_archive_archived_at ON memory_archive(archived_at);");
|
|
592
|
+
db.prepare("INSERT OR REPLACE INTO schema_meta (key, value) VALUES ('version', ?)").run(String(8));
|
|
593
|
+
db.exec("COMMIT");
|
|
594
|
+
} catch (e) {
|
|
595
|
+
try {
|
|
596
|
+
db.exec("ROLLBACK");
|
|
597
|
+
} catch {
|
|
598
|
+
}
|
|
599
|
+
throw e;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
519
602
|
|
|
520
603
|
// src/search/tokenizer.ts
|
|
521
604
|
import { readFileSync } from "fs";
|
|
@@ -1169,9 +1252,118 @@ function updateMemory(db, id, input) {
|
|
|
1169
1252
|
}
|
|
1170
1253
|
function deleteMemory(db, id) {
|
|
1171
1254
|
db.prepare("DELETE FROM memories_fts WHERE id = ?").run(id);
|
|
1255
|
+
try {
|
|
1256
|
+
db.prepare("DELETE FROM embeddings WHERE memory_id = ?").run(id);
|
|
1257
|
+
} catch {
|
|
1258
|
+
}
|
|
1172
1259
|
const result = db.prepare("DELETE FROM memories WHERE id = ?").run(id);
|
|
1173
1260
|
return result.changes > 0;
|
|
1174
1261
|
}
|
|
1262
|
+
function archiveMemory(db, id, reason, opts) {
|
|
1263
|
+
const mem = getMemory(db, id);
|
|
1264
|
+
if (!mem) return false;
|
|
1265
|
+
const minVitality = opts?.minVitality ?? 0.1;
|
|
1266
|
+
if (mem.vitality < minVitality) {
|
|
1267
|
+
deleteMemory(db, id);
|
|
1268
|
+
return "deleted";
|
|
1269
|
+
}
|
|
1270
|
+
const archivedAt = now();
|
|
1271
|
+
const archiveReason = reason ?? "eviction";
|
|
1272
|
+
db.prepare(
|
|
1273
|
+
`INSERT OR REPLACE INTO memory_archive
|
|
1274
|
+
(id, content, type, priority, emotion_val, vitality, stability, access_count,
|
|
1275
|
+
last_accessed, created_at, updated_at, archived_at, archive_reason,
|
|
1276
|
+
source, agent_id, hash, emotion_tag, source_session, source_context, observed_at)
|
|
1277
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
1278
|
+
).run(
|
|
1279
|
+
mem.id,
|
|
1280
|
+
mem.content,
|
|
1281
|
+
mem.type,
|
|
1282
|
+
mem.priority,
|
|
1283
|
+
mem.emotion_val,
|
|
1284
|
+
mem.vitality,
|
|
1285
|
+
mem.stability,
|
|
1286
|
+
mem.access_count,
|
|
1287
|
+
mem.last_accessed,
|
|
1288
|
+
mem.created_at,
|
|
1289
|
+
mem.updated_at,
|
|
1290
|
+
archivedAt,
|
|
1291
|
+
archiveReason,
|
|
1292
|
+
mem.source,
|
|
1293
|
+
mem.agent_id,
|
|
1294
|
+
mem.hash,
|
|
1295
|
+
mem.emotion_tag,
|
|
1296
|
+
mem.source_session,
|
|
1297
|
+
mem.source_context,
|
|
1298
|
+
mem.observed_at
|
|
1299
|
+
);
|
|
1300
|
+
deleteMemory(db, id);
|
|
1301
|
+
return "archived";
|
|
1302
|
+
}
|
|
1303
|
+
function restoreMemory(db, id) {
|
|
1304
|
+
const archived = db.prepare("SELECT * FROM memory_archive WHERE id = ?").get(id);
|
|
1305
|
+
if (!archived) return null;
|
|
1306
|
+
db.prepare(
|
|
1307
|
+
`INSERT INTO memories
|
|
1308
|
+
(id, content, type, priority, emotion_val, vitality, stability, access_count,
|
|
1309
|
+
last_accessed, created_at, updated_at, source, agent_id, hash, emotion_tag,
|
|
1310
|
+
source_session, source_context, observed_at)
|
|
1311
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
1312
|
+
).run(
|
|
1313
|
+
archived.id,
|
|
1314
|
+
archived.content,
|
|
1315
|
+
archived.type,
|
|
1316
|
+
archived.priority,
|
|
1317
|
+
archived.emotion_val,
|
|
1318
|
+
archived.vitality,
|
|
1319
|
+
archived.stability,
|
|
1320
|
+
archived.access_count,
|
|
1321
|
+
archived.last_accessed,
|
|
1322
|
+
archived.created_at,
|
|
1323
|
+
now(),
|
|
1324
|
+
// updated_at = restore time
|
|
1325
|
+
archived.source,
|
|
1326
|
+
archived.agent_id,
|
|
1327
|
+
archived.hash,
|
|
1328
|
+
archived.emotion_tag,
|
|
1329
|
+
archived.source_session,
|
|
1330
|
+
archived.source_context,
|
|
1331
|
+
archived.observed_at
|
|
1332
|
+
);
|
|
1333
|
+
db.prepare("INSERT INTO memories_fts (id, content) VALUES (?, ?)").run(
|
|
1334
|
+
archived.id,
|
|
1335
|
+
tokenizeForIndex(archived.content)
|
|
1336
|
+
);
|
|
1337
|
+
if (archived.hash) {
|
|
1338
|
+
const providerId = getConfiguredEmbeddingProviderId();
|
|
1339
|
+
if (providerId) {
|
|
1340
|
+
try {
|
|
1341
|
+
markMemoryEmbeddingPending(db, archived.id, providerId, archived.hash);
|
|
1342
|
+
} catch {
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
db.prepare("DELETE FROM memory_archive WHERE id = ?").run(id);
|
|
1347
|
+
return getMemory(db, archived.id);
|
|
1348
|
+
}
|
|
1349
|
+
function listArchivedMemories(db, opts) {
|
|
1350
|
+
const agentId = opts?.agent_id;
|
|
1351
|
+
const limit = opts?.limit ?? 20;
|
|
1352
|
+
if (agentId) {
|
|
1353
|
+
return db.prepare(
|
|
1354
|
+
"SELECT * FROM memory_archive WHERE agent_id = ? ORDER BY archived_at DESC LIMIT ?"
|
|
1355
|
+
).all(agentId, limit);
|
|
1356
|
+
}
|
|
1357
|
+
return db.prepare(
|
|
1358
|
+
"SELECT * FROM memory_archive ORDER BY archived_at DESC LIMIT ?"
|
|
1359
|
+
).all(limit);
|
|
1360
|
+
}
|
|
1361
|
+
function purgeArchive(db, opts) {
|
|
1362
|
+
if (opts?.agent_id) {
|
|
1363
|
+
return db.prepare("DELETE FROM memory_archive WHERE agent_id = ?").run(opts.agent_id).changes;
|
|
1364
|
+
}
|
|
1365
|
+
return db.prepare("DELETE FROM memory_archive").run().changes;
|
|
1366
|
+
}
|
|
1175
1367
|
function listMemories(db, opts) {
|
|
1176
1368
|
const conditions = [];
|
|
1177
1369
|
const params = [];
|
|
@@ -2775,13 +2967,31 @@ function rankEvictionCandidates(db, opts) {
|
|
|
2775
2967
|
return left.memory.priority - right.memory.priority;
|
|
2776
2968
|
});
|
|
2777
2969
|
}
|
|
2970
|
+
function parseEnvInt(envKey) {
|
|
2971
|
+
const raw = process.env[envKey];
|
|
2972
|
+
if (raw === void 0 || raw === "") return null;
|
|
2973
|
+
const n = Number.parseInt(raw, 10);
|
|
2974
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
2975
|
+
}
|
|
2976
|
+
function getTieredCapacity(opts) {
|
|
2977
|
+
const envMax = parseEnvInt("AGENT_MEMORY_MAX_MEMORIES");
|
|
2978
|
+
return {
|
|
2979
|
+
identity: parseEnvInt("AGENT_MEMORY_MAX_IDENTITY"),
|
|
2980
|
+
// default: null (unlimited)
|
|
2981
|
+
emotion: parseEnvInt("AGENT_MEMORY_MAX_EMOTION") ?? 50,
|
|
2982
|
+
knowledge: parseEnvInt("AGENT_MEMORY_MAX_KNOWLEDGE") ?? 250,
|
|
2983
|
+
event: parseEnvInt("AGENT_MEMORY_MAX_EVENT") ?? 50,
|
|
2984
|
+
total: opts?.maxMemories ?? (envMax ?? 350)
|
|
2985
|
+
};
|
|
2986
|
+
}
|
|
2778
2987
|
function runGovern(db, opts) {
|
|
2779
2988
|
const agentId = opts?.agent_id;
|
|
2780
|
-
const
|
|
2781
|
-
const maxMemories = opts?.maxMemories ?? (Number.isFinite(envMax) && envMax > 0 ? envMax : 200);
|
|
2989
|
+
const capacity = getTieredCapacity(opts);
|
|
2782
2990
|
let orphanPaths = 0;
|
|
2783
2991
|
let emptyMemories = 0;
|
|
2784
2992
|
let evicted = 0;
|
|
2993
|
+
let archived = 0;
|
|
2994
|
+
const evictedByType = {};
|
|
2785
2995
|
const transaction = db.transaction(() => {
|
|
2786
2996
|
const pathResult = agentId ? db.prepare(
|
|
2787
2997
|
`DELETE FROM paths
|
|
@@ -2791,17 +3001,48 @@ function runGovern(db, opts) {
|
|
|
2791
3001
|
orphanPaths = pathResult.changes;
|
|
2792
3002
|
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();
|
|
2793
3003
|
emptyMemories = emptyResult.changes;
|
|
3004
|
+
const typeLimits = [
|
|
3005
|
+
{ type: "identity", limit: capacity.identity },
|
|
3006
|
+
{ type: "emotion", limit: capacity.emotion },
|
|
3007
|
+
{ type: "knowledge", limit: capacity.knowledge },
|
|
3008
|
+
{ type: "event", limit: capacity.event }
|
|
3009
|
+
];
|
|
3010
|
+
const allCandidates = rankEvictionCandidates(db, { agent_id: agentId });
|
|
3011
|
+
const evictedIds = /* @__PURE__ */ new Set();
|
|
3012
|
+
for (const { type, limit } of typeLimits) {
|
|
3013
|
+
if (limit === null) continue;
|
|
3014
|
+
const typeCount = db.prepare(
|
|
3015
|
+
agentId ? "SELECT COUNT(*) as c FROM memories WHERE agent_id = ? AND type = ?" : "SELECT COUNT(*) as c FROM memories WHERE type = ?"
|
|
3016
|
+
).get(...agentId ? [agentId, type] : [type]).c;
|
|
3017
|
+
const excess = Math.max(0, typeCount - limit);
|
|
3018
|
+
if (excess <= 0) continue;
|
|
3019
|
+
const typeCandidates = allCandidates.filter((c) => c.memory.type === type && !evictedIds.has(c.memory.id));
|
|
3020
|
+
const toEvict = typeCandidates.slice(0, excess);
|
|
3021
|
+
for (const candidate of toEvict) {
|
|
3022
|
+
const result = archiveMemory(db, candidate.memory.id, "eviction");
|
|
3023
|
+
evictedIds.add(candidate.memory.id);
|
|
3024
|
+
evicted += 1;
|
|
3025
|
+
if (result === "archived") archived += 1;
|
|
3026
|
+
evictedByType[type] = (evictedByType[type] ?? 0) + 1;
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
2794
3029
|
const total = db.prepare(agentId ? "SELECT COUNT(*) as c FROM memories WHERE agent_id = ?" : "SELECT COUNT(*) as c FROM memories").get(...agentId ? [agentId] : []).c;
|
|
2795
|
-
const
|
|
2796
|
-
if (
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
3030
|
+
const globalExcess = Math.max(0, total - capacity.total);
|
|
3031
|
+
if (globalExcess > 0) {
|
|
3032
|
+
const globalCandidates = allCandidates.filter((c) => !evictedIds.has(c.memory.id));
|
|
3033
|
+
const toEvict = globalCandidates.slice(0, globalExcess);
|
|
3034
|
+
for (const candidate of toEvict) {
|
|
3035
|
+
const result = archiveMemory(db, candidate.memory.id, "eviction");
|
|
3036
|
+
evictedIds.add(candidate.memory.id);
|
|
3037
|
+
evicted += 1;
|
|
3038
|
+
if (result === "archived") archived += 1;
|
|
3039
|
+
const t = candidate.memory.type;
|
|
3040
|
+
evictedByType[t] = (evictedByType[t] ?? 0) + 1;
|
|
3041
|
+
}
|
|
2801
3042
|
}
|
|
2802
3043
|
});
|
|
2803
3044
|
transaction();
|
|
2804
|
-
return { orphanPaths, emptyMemories, evicted };
|
|
3045
|
+
return { orphanPaths, emptyMemories, evicted, archived, evictedByType };
|
|
2805
3046
|
}
|
|
2806
3047
|
|
|
2807
3048
|
// src/sleep/jobs.ts
|
|
@@ -3042,12 +3283,21 @@ function getMemoryStatus(db, input) {
|
|
|
3042
3283
|
const feedbackEvents = db.prepare(
|
|
3043
3284
|
"SELECT COUNT(*) as c FROM feedback_events WHERE agent_id = ?"
|
|
3044
3285
|
).get(agentId);
|
|
3286
|
+
const tiered = getTieredCapacity();
|
|
3287
|
+
const capacity = {
|
|
3288
|
+
identity: { count: stats.by_type.identity ?? 0, limit: tiered.identity },
|
|
3289
|
+
emotion: { count: stats.by_type.emotion ?? 0, limit: tiered.emotion },
|
|
3290
|
+
knowledge: { count: stats.by_type.knowledge ?? 0, limit: tiered.knowledge },
|
|
3291
|
+
event: { count: stats.by_type.event ?? 0, limit: tiered.event },
|
|
3292
|
+
total: { count: stats.total, limit: tiered.total }
|
|
3293
|
+
};
|
|
3045
3294
|
return {
|
|
3046
3295
|
...stats,
|
|
3047
3296
|
paths: totalPaths.c,
|
|
3048
3297
|
low_vitality: lowVitality.c,
|
|
3049
3298
|
feedback_events: feedbackEvents.c,
|
|
3050
|
-
agent_id: agentId
|
|
3299
|
+
agent_id: agentId,
|
|
3300
|
+
capacity
|
|
3051
3301
|
};
|
|
3052
3302
|
}
|
|
3053
3303
|
|
|
@@ -4001,6 +4251,7 @@ function boot(db, opts) {
|
|
|
4001
4251
|
return result;
|
|
4002
4252
|
}
|
|
4003
4253
|
export {
|
|
4254
|
+
archiveMemory,
|
|
4004
4255
|
boot,
|
|
4005
4256
|
buildFtsQuery,
|
|
4006
4257
|
buildMergePlan,
|
|
@@ -4048,11 +4299,13 @@ export {
|
|
|
4048
4299
|
getPathsByDomain,
|
|
4049
4300
|
getPathsByMemory,
|
|
4050
4301
|
getPathsByPrefix,
|
|
4302
|
+
getTieredCapacity,
|
|
4051
4303
|
guard,
|
|
4052
4304
|
healthcheckEmbeddingProvider,
|
|
4053
4305
|
ingestText,
|
|
4054
4306
|
isCountRow,
|
|
4055
4307
|
isStaleContent,
|
|
4308
|
+
listArchivedMemories,
|
|
4056
4309
|
listMemories,
|
|
4057
4310
|
listPendingEmbeddings,
|
|
4058
4311
|
loadWarmBootLayers,
|
|
@@ -4062,6 +4315,7 @@ export {
|
|
|
4062
4315
|
openDatabase,
|
|
4063
4316
|
parseUri,
|
|
4064
4317
|
priorityPrior,
|
|
4318
|
+
purgeArchive,
|
|
4065
4319
|
rankEvictionCandidates,
|
|
4066
4320
|
rebuildBm25Index,
|
|
4067
4321
|
recallMemories,
|
|
@@ -4074,6 +4328,7 @@ export {
|
|
|
4074
4328
|
reindexMemories,
|
|
4075
4329
|
reindexMemorySearch,
|
|
4076
4330
|
rememberMemory,
|
|
4331
|
+
restoreMemory,
|
|
4077
4332
|
runAutoIngestWatcher,
|
|
4078
4333
|
runDecay,
|
|
4079
4334
|
runGovern,
|