@prometheus-ai/memory 0.5.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/README.md +107 -0
- package/dist/types/cli.d.ts +35 -0
- package/dist/types/config.d.ts +77 -0
- package/dist/types/core/aaak.d.ts +55 -0
- package/dist/types/core/annotations.d.ts +75 -0
- package/dist/types/core/banks.d.ts +33 -0
- package/dist/types/core/beam/consolidate.d.ts +32 -0
- package/dist/types/core/beam/helpers.d.ts +76 -0
- package/dist/types/core/beam/index.d.ts +59 -0
- package/dist/types/core/beam/recall.d.ts +32 -0
- package/dist/types/core/beam/schema.d.ts +2 -0
- package/dist/types/core/beam/store.d.ts +35 -0
- package/dist/types/core/beam/types.d.ts +233 -0
- package/dist/types/core/binary-vectors.d.ts +54 -0
- package/dist/types/core/chat-normalize.d.ts +13 -0
- package/dist/types/core/content-sanitizer.d.ts +18 -0
- package/dist/types/core/cost-log.d.ts +13 -0
- package/dist/types/core/embeddings.d.ts +44 -0
- package/dist/types/core/entities.d.ts +7 -0
- package/dist/types/core/episodic-graph.d.ts +89 -0
- package/dist/types/core/extraction/client.d.ts +31 -0
- package/dist/types/core/extraction/diagnostics.d.ts +51 -0
- package/dist/types/core/extraction/prompts.d.ts +2 -0
- package/dist/types/core/extraction.d.ts +6 -0
- package/dist/types/core/index.d.ts +4 -0
- package/dist/types/core/llm-backends.d.ts +21 -0
- package/dist/types/core/local-llm.d.ts +15 -0
- package/dist/types/core/memory.d.ts +160 -0
- package/dist/types/core/migrations/e6-triplestore-split.d.ts +17 -0
- package/dist/types/core/migrations/index.d.ts +1 -0
- package/dist/types/core/mmr.d.ts +8 -0
- package/dist/types/core/orchestrator.d.ts +20 -0
- package/dist/types/core/patterns.d.ts +61 -0
- package/dist/types/core/plugins.d.ts +109 -0
- package/dist/types/core/polyphonic-recall.d.ts +66 -0
- package/dist/types/core/query-cache.d.ts +46 -0
- package/dist/types/core/query-intent.d.ts +20 -0
- package/dist/types/core/recall-diagnostics.d.ts +48 -0
- package/dist/types/core/runtime-options.d.ts +68 -0
- package/dist/types/core/shmr.d.ts +56 -0
- package/dist/types/core/streaming.d.ts +136 -0
- package/dist/types/core/synonyms.d.ts +46 -0
- package/dist/types/core/temporal-parser.d.ts +16 -0
- package/dist/types/core/token-counter.d.ts +8 -0
- package/dist/types/core/triples.d.ts +63 -0
- package/dist/types/core/typed-memory.d.ts +39 -0
- package/dist/types/core/vector-math.d.ts +1 -0
- package/dist/types/core/veracity-consolidation.d.ts +60 -0
- package/dist/types/core/weibull.d.ts +96 -0
- package/dist/types/db.d.ts +16 -0
- package/dist/types/diagnose.d.ts +24 -0
- package/dist/types/dr/index.d.ts +1 -0
- package/dist/types/dr/recovery.d.ts +68 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/mcp-server.d.ts +40 -0
- package/dist/types/mcp-tools.d.ts +484 -0
- package/dist/types/migrations/e6-triplestore-split.d.ts +1 -0
- package/dist/types/migrations/index.d.ts +1 -0
- package/dist/types/types.d.ts +145 -0
- package/dist/types/util/datetime.d.ts +8 -0
- package/dist/types/util/env.d.ts +10 -0
- package/dist/types/util/ids.d.ts +3 -0
- package/dist/types/util/lru.d.ts +12 -0
- package/dist/types/util/regex.d.ts +10 -0
- package/package.json +85 -0
- package/src/cli.ts +398 -0
- package/src/config.ts +326 -0
- package/src/core/aaak.ts +142 -0
- package/src/core/annotations.ts +457 -0
- package/src/core/banks.ts +133 -0
- package/src/core/beam/consolidate.ts +965 -0
- package/src/core/beam/helpers.ts +977 -0
- package/src/core/beam/index.ts +353 -0
- package/src/core/beam/recall.ts +1100 -0
- package/src/core/beam/schema.ts +423 -0
- package/src/core/beam/store.ts +829 -0
- package/src/core/beam/types.ts +268 -0
- package/src/core/binary-vectors.ts +317 -0
- package/src/core/chat-normalize.ts +160 -0
- package/src/core/content-sanitizer.ts +136 -0
- package/src/core/cost-log.ts +103 -0
- package/src/core/embeddings.ts +423 -0
- package/src/core/entities.ts +259 -0
- package/src/core/episodic-graph.ts +708 -0
- package/src/core/extraction/client.ts +162 -0
- package/src/core/extraction/diagnostics.ts +193 -0
- package/src/core/extraction/prompts.ts +31 -0
- package/src/core/extraction.ts +335 -0
- package/src/core/index.ts +30 -0
- package/src/core/llm-backends.ts +51 -0
- package/src/core/local-llm.ts +436 -0
- package/src/core/memory.ts +630 -0
- package/src/core/migrations/e6-triplestore-split.ts +211 -0
- package/src/core/migrations/index.ts +1 -0
- package/src/core/mmr.ts +71 -0
- package/src/core/orchestrator.ts +62 -0
- package/src/core/patterns.ts +484 -0
- package/src/core/plugins.ts +375 -0
- package/src/core/polyphonic-recall.ts +563 -0
- package/src/core/query-cache.ts +354 -0
- package/src/core/query-intent.ts +139 -0
- package/src/core/recall-diagnostics.ts +157 -0
- package/src/core/runtime-options.ts +119 -0
- package/src/core/shmr.ts +460 -0
- package/src/core/streaming.ts +419 -0
- package/src/core/synonyms.ts +197 -0
- package/src/core/temporal-parser.ts +363 -0
- package/src/core/token-counter.ts +30 -0
- package/src/core/triples.ts +454 -0
- package/src/core/typed-memory.ts +407 -0
- package/src/core/vector-math.ts +23 -0
- package/src/core/veracity-consolidation.ts +477 -0
- package/src/core/weibull.ts +124 -0
- package/src/db.ts +128 -0
- package/src/diagnose.ts +174 -0
- package/src/dr/index.ts +1 -0
- package/src/dr/recovery.ts +405 -0
- package/src/index.ts +33 -0
- package/src/mcp-server.ts +155 -0
- package/src/mcp-tools.ts +970 -0
- package/src/migrations/e6-triplestore-split.ts +1 -0
- package/src/migrations/index.ts +1 -0
- package/src/types.ts +157 -0
- package/src/util/datetime.ts +69 -0
- package/src/util/env.ts +65 -0
- package/src/util/ids.ts +19 -0
- package/src/util/lru.ts +48 -0
- package/src/util/regex.ts +165 -0
|
@@ -0,0 +1,829 @@
|
|
|
1
|
+
import type { Database, SQLQueryBindings } from "bun:sqlite";
|
|
2
|
+
import { transaction } from "../../db";
|
|
3
|
+
import { toUtcIso } from "../../util/datetime";
|
|
4
|
+
import { generateId } from "../../util/ids";
|
|
5
|
+
import { EpisodicGraph } from "../episodic-graph";
|
|
6
|
+
import { extractFactsSafe } from "../extraction";
|
|
7
|
+
import { getMnemopiRuntimeOptions, withMnemopiRuntimeOptions } from "../runtime-options";
|
|
8
|
+
import { storeFactStrings } from "./consolidate";
|
|
9
|
+
import { scheduleEmbedding, vecAvailable, vecInsert } from "./helpers";
|
|
10
|
+
import type {
|
|
11
|
+
BeamEvent,
|
|
12
|
+
BeamMemoryState,
|
|
13
|
+
BeamStats,
|
|
14
|
+
ImportStats,
|
|
15
|
+
Metadata,
|
|
16
|
+
RememberBatchItem,
|
|
17
|
+
RememberBatchOptions,
|
|
18
|
+
RememberOptions,
|
|
19
|
+
TrustTier,
|
|
20
|
+
Veracity,
|
|
21
|
+
} from "./types";
|
|
22
|
+
|
|
23
|
+
type Row = Record<string, unknown>;
|
|
24
|
+
type EventPayload = Omit<BeamEvent, "type" | "sessionId" | "timestamp">;
|
|
25
|
+
|
|
26
|
+
type StoreRememberOptions = RememberOptions & {
|
|
27
|
+
memoryId?: string;
|
|
28
|
+
memory_id?: string;
|
|
29
|
+
validUntil?: string | null;
|
|
30
|
+
valid_until?: string | null;
|
|
31
|
+
authorId?: string | null;
|
|
32
|
+
author_id?: string | null;
|
|
33
|
+
authorType?: string | null;
|
|
34
|
+
author_type?: string | null;
|
|
35
|
+
extractEntities?: boolean;
|
|
36
|
+
extract_entities?: boolean;
|
|
37
|
+
channelId?: string | null;
|
|
38
|
+
channel_id?: string | null;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
type StoreRememberBatchOptions = RememberBatchOptions & {
|
|
42
|
+
forceVeracity?: boolean;
|
|
43
|
+
force_veracity?: boolean;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const CANONICAL_VERACITY: Record<string, true> = {
|
|
47
|
+
true: true,
|
|
48
|
+
false: true,
|
|
49
|
+
stated: true,
|
|
50
|
+
inferred: true,
|
|
51
|
+
tool: true,
|
|
52
|
+
imported: true,
|
|
53
|
+
unknown: true,
|
|
54
|
+
};
|
|
55
|
+
const TRUST_TIERS: Record<string, true> = {
|
|
56
|
+
STATED: true,
|
|
57
|
+
DERIVED: true,
|
|
58
|
+
EXTERNAL_WRITE: true,
|
|
59
|
+
IMPORTED: true,
|
|
60
|
+
};
|
|
61
|
+
const SCRATCHPAD_MAX_ITEMS = Number.parseInt(process.env.PROMETHEUS_MEMORY_SP_MAX ?? "1000", 10);
|
|
62
|
+
|
|
63
|
+
function metadataJson(metadata: Metadata | null | undefined): string | null {
|
|
64
|
+
return metadata == null ? null : JSON.stringify(metadata);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function jsonObject(value: unknown): Record<string, unknown> {
|
|
68
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
69
|
+
? (value as Record<string, unknown>)
|
|
70
|
+
: {};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function isSqlBinding(value: unknown): value is SQLQueryBindings {
|
|
74
|
+
return (
|
|
75
|
+
value === null ||
|
|
76
|
+
typeof value === "string" ||
|
|
77
|
+
typeof value === "number" ||
|
|
78
|
+
typeof value === "bigint" ||
|
|
79
|
+
typeof value === "boolean" ||
|
|
80
|
+
value instanceof ArrayBuffer ||
|
|
81
|
+
(ArrayBuffer.isView(value) && !(value instanceof DataView))
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function sqlBinding(value: unknown, fallback: SQLQueryBindings): SQLQueryBindings {
|
|
86
|
+
return isSqlBinding(value) ? value : fallback;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function clampVeracity(value: unknown): Veracity {
|
|
90
|
+
if (typeof value !== "string") return "unknown";
|
|
91
|
+
const normalized = value.trim().toLowerCase();
|
|
92
|
+
return CANONICAL_VERACITY[normalized] === true ? normalized : "unknown";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function sourceToTrustTier(source: string | null | undefined): TrustTier {
|
|
96
|
+
switch ((source ?? "").toLowerCase()) {
|
|
97
|
+
case "conversation":
|
|
98
|
+
case "user":
|
|
99
|
+
case "assistant":
|
|
100
|
+
return "STATED";
|
|
101
|
+
case "tool":
|
|
102
|
+
case "api":
|
|
103
|
+
case "system":
|
|
104
|
+
return "EXTERNAL_WRITE";
|
|
105
|
+
case "import":
|
|
106
|
+
case "imported":
|
|
107
|
+
case "backup":
|
|
108
|
+
return "IMPORTED";
|
|
109
|
+
default:
|
|
110
|
+
return "STATED";
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function normalizeTrustTier(value: unknown, source: string): TrustTier {
|
|
115
|
+
if (value === null || value === undefined) return sourceToTrustTier(source);
|
|
116
|
+
if (typeof value === "string" && TRUST_TIERS[value] === true) return value;
|
|
117
|
+
return "STATED";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function emitEvent(beam: BeamMemoryState, type: string, data: EventPayload): void {
|
|
121
|
+
const event: BeamEvent = {
|
|
122
|
+
...data,
|
|
123
|
+
type,
|
|
124
|
+
sessionId: beam.sessionId,
|
|
125
|
+
timestamp: toUtcIso(),
|
|
126
|
+
};
|
|
127
|
+
const candidate = beam as BeamMemoryState & {
|
|
128
|
+
emitEvent?: (type: string, data: EventPayload) => void;
|
|
129
|
+
};
|
|
130
|
+
if (typeof candidate.emitEvent === "function") {
|
|
131
|
+
candidate.emitEvent(type, data);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
beam.eventEmitter?.(event);
|
|
135
|
+
void beam.pluginManager?.emit?.(event);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function invalidateCaches(beam: BeamMemoryState): void {
|
|
139
|
+
const cache = beam.caches as {
|
|
140
|
+
queryCache?: { invalidate?: () => void };
|
|
141
|
+
_queryCache?: { invalidate?: () => void };
|
|
142
|
+
};
|
|
143
|
+
cache.queryCache?.invalidate?.();
|
|
144
|
+
cache._queryCache?.invalidate?.();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function findDuplicate(beam: BeamMemoryState, content: string): string | null {
|
|
148
|
+
const row = beam.db
|
|
149
|
+
.prepare("SELECT id FROM working_memory WHERE content = ? AND session_id = ? LIMIT 1")
|
|
150
|
+
.get(content, beam.sessionId) as { id: string } | null;
|
|
151
|
+
return row?.id ?? null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function trimWorkingMemory(beam: BeamMemoryState): void {
|
|
155
|
+
const limit = beam.config.workingMemoryLimit;
|
|
156
|
+
if (!Number.isFinite(limit) || limit <= 0) return;
|
|
157
|
+
const ttlHours = beam.config.workingMemoryTtlHours;
|
|
158
|
+
const cutoff = toUtcIso(new Date(Date.now() - ttlHours * 3_600_000));
|
|
159
|
+
beam.db
|
|
160
|
+
.prepare(`
|
|
161
|
+
DELETE FROM working_memory
|
|
162
|
+
WHERE session_id = ?
|
|
163
|
+
AND consolidated_at IS NULL
|
|
164
|
+
AND (
|
|
165
|
+
timestamp < ? OR
|
|
166
|
+
id NOT IN (
|
|
167
|
+
SELECT id FROM working_memory
|
|
168
|
+
WHERE session_id = ? AND consolidated_at IS NULL
|
|
169
|
+
ORDER BY timestamp DESC
|
|
170
|
+
LIMIT ?
|
|
171
|
+
)
|
|
172
|
+
)
|
|
173
|
+
`)
|
|
174
|
+
.run(beam.sessionId, cutoff, beam.sessionId, limit);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function addTemporalAnnotations(beam: BeamMemoryState, memoryId: string, timestamp: string, source: string): void {
|
|
178
|
+
try {
|
|
179
|
+
beam.annotations?.add?.(memoryId, "occurred_on", timestamp.slice(0, 10));
|
|
180
|
+
if (source && source !== "conversation" && source !== "user" && source !== "assistant") {
|
|
181
|
+
beam.annotations?.add?.(memoryId, "has_source", source);
|
|
182
|
+
}
|
|
183
|
+
} catch {
|
|
184
|
+
// Annotation enrichment is best-effort, matching Python's non-blocking path.
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function proactiveLinkIfEnabled(
|
|
189
|
+
beam: BeamMemoryState,
|
|
190
|
+
memoryId: string,
|
|
191
|
+
content: string,
|
|
192
|
+
extractEntities: boolean,
|
|
193
|
+
): void {
|
|
194
|
+
if (process.env.PROMETHEUS_MEMORY_PROACTIVE_LINKING !== "1") return;
|
|
195
|
+
try {
|
|
196
|
+
const graph =
|
|
197
|
+
beam.episodicGraph instanceof EpisodicGraph
|
|
198
|
+
? beam.episodicGraph
|
|
199
|
+
: new EpisodicGraph({ db: beam.db, dbPath: beam.dbPath });
|
|
200
|
+
graph.ingestMemory(content, memoryId, {
|
|
201
|
+
sessionId: beam.sessionId,
|
|
202
|
+
linkExisting: true,
|
|
203
|
+
extractEntities,
|
|
204
|
+
});
|
|
205
|
+
} catch {
|
|
206
|
+
// Proactive graph enrichment must never block durable memory storage.
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Run the LLM fact extractor over freshly stored content and persist the
|
|
212
|
+
* resulting facts. Best-effort: failures (no LLM, closed DB, malformed output)
|
|
213
|
+
* are swallowed so they can never disrupt the synchronous `remember` that
|
|
214
|
+
* scheduled them.
|
|
215
|
+
*/
|
|
216
|
+
async function runFactExtraction(beam: BeamMemoryState, memoryId: string, content: string): Promise<void> {
|
|
217
|
+
try {
|
|
218
|
+
const facts = await extractFactsSafe(content);
|
|
219
|
+
if (facts.length === 0) return;
|
|
220
|
+
storeFactStrings(beam, facts, 0, memoryId);
|
|
221
|
+
invalidateCaches(beam);
|
|
222
|
+
} catch {
|
|
223
|
+
// Background fact extraction is best-effort and never surfaces to the caller.
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Schedule background fact extraction for a stored memory. `remember` is
|
|
229
|
+
* synchronous, so the async extractor is fired-and-forgotten; the promise is
|
|
230
|
+
* tracked on `beam.pendingExtractions` so callers can drain it via
|
|
231
|
+
* `flushExtractions()` (tests, graceful shutdown). The active runtime options
|
|
232
|
+
* (host LLM `complete`, model, prompt overrides) are captured here and
|
|
233
|
+
* re-entered inside the task because the AsyncLocalStorage scope set by
|
|
234
|
+
* `Mnemopi.#withRuntimeOptions` has already exited by the time the task runs.
|
|
235
|
+
*/
|
|
236
|
+
function scheduleFactExtraction(beam: BeamMemoryState, memoryId: string, content: string): void {
|
|
237
|
+
if (content.trim() === "") return;
|
|
238
|
+
const runtimeOptions = getMnemopiRuntimeOptions();
|
|
239
|
+
const task = withMnemopiRuntimeOptions(runtimeOptions, () => runFactExtraction(beam, memoryId, content));
|
|
240
|
+
const pending = beam.pendingExtractions;
|
|
241
|
+
if (pending !== undefined) {
|
|
242
|
+
pending.add(task);
|
|
243
|
+
void task.finally(() => pending.delete(task));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function rowToDict(row: Row): Row {
|
|
248
|
+
return { ...row };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function remember(beam: BeamMemoryState, content: string, options: StoreRememberOptions = {}): string {
|
|
252
|
+
const source = options.source ?? "conversation";
|
|
253
|
+
const importance = options.importance ?? 0.5;
|
|
254
|
+
const timestamp = options.timestamp ?? toUtcIso();
|
|
255
|
+
const scope = options.scope ?? "session";
|
|
256
|
+
const veracity = clampVeracity(options.veracity);
|
|
257
|
+
const trustTier = normalizeTrustTier(options.trustTier, source);
|
|
258
|
+
const memoryType = options.memoryType ?? "unknown";
|
|
259
|
+
const validUntil = options.validUntil ?? options.valid_until ?? null;
|
|
260
|
+
const authorId = options.authorId ?? options.author_id ?? beam.authorId;
|
|
261
|
+
const authorType = options.authorType ?? options.author_type ?? beam.authorType;
|
|
262
|
+
const channelId = options.channelId ?? options.channel_id ?? beam.channelId;
|
|
263
|
+
const metadata = options.metadata ?? null;
|
|
264
|
+
|
|
265
|
+
const existingId = findDuplicate(beam, content);
|
|
266
|
+
if (existingId !== null) {
|
|
267
|
+
beam.db
|
|
268
|
+
.prepare(`
|
|
269
|
+
UPDATE working_memory
|
|
270
|
+
SET importance = MAX(importance, ?), timestamp = ?, source = ?,
|
|
271
|
+
valid_until = COALESCE(?, valid_until),
|
|
272
|
+
scope = COALESCE(?, scope),
|
|
273
|
+
author_id = COALESCE(?, author_id),
|
|
274
|
+
author_type = COALESCE(?, author_type),
|
|
275
|
+
channel_id = COALESCE(?, channel_id),
|
|
276
|
+
memory_type = COALESCE(?, memory_type),
|
|
277
|
+
veracity = CASE WHEN ? != 'unknown' THEN ? ELSE veracity END,
|
|
278
|
+
trust_tier = COALESCE(?, trust_tier),
|
|
279
|
+
consolidated_at = NULL
|
|
280
|
+
WHERE id = ? AND session_id = ?
|
|
281
|
+
`)
|
|
282
|
+
.run(
|
|
283
|
+
importance,
|
|
284
|
+
timestamp,
|
|
285
|
+
source,
|
|
286
|
+
validUntil,
|
|
287
|
+
scope,
|
|
288
|
+
authorId,
|
|
289
|
+
authorType,
|
|
290
|
+
channelId,
|
|
291
|
+
memoryType,
|
|
292
|
+
veracity,
|
|
293
|
+
veracity,
|
|
294
|
+
trustTier,
|
|
295
|
+
existingId,
|
|
296
|
+
beam.sessionId,
|
|
297
|
+
);
|
|
298
|
+
emitEvent(beam, "MEMORY_UPDATED", {
|
|
299
|
+
memoryId: existingId,
|
|
300
|
+
content,
|
|
301
|
+
source,
|
|
302
|
+
importance,
|
|
303
|
+
metadata: metadata ?? undefined,
|
|
304
|
+
});
|
|
305
|
+
invalidateCaches(beam);
|
|
306
|
+
return existingId;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const memoryId = options.memoryId ?? options.memory_id ?? generateId(content, new Date(timestamp));
|
|
310
|
+
beam.db
|
|
311
|
+
.prepare(`
|
|
312
|
+
INSERT INTO working_memory
|
|
313
|
+
(id, content, source, timestamp, session_id, importance, metadata_json, valid_until, scope,
|
|
314
|
+
author_id, author_type, channel_id, veracity, memory_type, trust_tier)
|
|
315
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
316
|
+
`)
|
|
317
|
+
.run(
|
|
318
|
+
memoryId,
|
|
319
|
+
content,
|
|
320
|
+
source,
|
|
321
|
+
timestamp,
|
|
322
|
+
beam.sessionId,
|
|
323
|
+
importance,
|
|
324
|
+
metadataJson(metadata),
|
|
325
|
+
validUntil,
|
|
326
|
+
scope,
|
|
327
|
+
authorId,
|
|
328
|
+
authorType,
|
|
329
|
+
channelId,
|
|
330
|
+
veracity,
|
|
331
|
+
memoryType,
|
|
332
|
+
trustTier,
|
|
333
|
+
);
|
|
334
|
+
addTemporalAnnotations(beam, memoryId, timestamp, source);
|
|
335
|
+
proactiveLinkIfEnabled(beam, memoryId, content, Boolean(options.extractEntities ?? options.extract_entities));
|
|
336
|
+
trimWorkingMemory(beam);
|
|
337
|
+
emitEvent(beam, "MEMORY_ADDED", {
|
|
338
|
+
memoryId,
|
|
339
|
+
content,
|
|
340
|
+
source,
|
|
341
|
+
importance,
|
|
342
|
+
metadata: metadata ?? undefined,
|
|
343
|
+
});
|
|
344
|
+
scheduleEmbedding(beam, [{ memoryId, content }]);
|
|
345
|
+
if (options.extract === true) scheduleFactExtraction(beam, memoryId, content);
|
|
346
|
+
invalidateCaches(beam);
|
|
347
|
+
return memoryId;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export function rememberBatch(
|
|
351
|
+
beam: BeamMemoryState,
|
|
352
|
+
items: readonly RememberBatchItem[],
|
|
353
|
+
options: StoreRememberBatchOptions = {},
|
|
354
|
+
): string[] {
|
|
355
|
+
const timestamp = toUtcIso();
|
|
356
|
+
const ids: string[] = [];
|
|
357
|
+
const forceVeracity = options.forceVeracity ?? options.force_veracity ?? false;
|
|
358
|
+
const defaultVeracity = clampVeracity(options.veracity);
|
|
359
|
+
const defaultScope = options.scope ?? "session";
|
|
360
|
+
const trustTier = normalizeTrustTier(options.trustTier ?? "IMPORTED", "imported");
|
|
361
|
+
|
|
362
|
+
transaction(beam.db, () => {
|
|
363
|
+
const statement = beam.db.prepare(`
|
|
364
|
+
INSERT INTO working_memory
|
|
365
|
+
(id, content, source, timestamp, session_id, importance, metadata_json,
|
|
366
|
+
author_id, author_type, channel_id, memory_type, veracity, trust_tier, scope)
|
|
367
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
368
|
+
`);
|
|
369
|
+
for (const item of items) {
|
|
370
|
+
const itemTimestamp = item.timestamp ?? timestamp;
|
|
371
|
+
const memoryId = generateId(item.content, new Date(itemTimestamp));
|
|
372
|
+
ids.push(memoryId);
|
|
373
|
+
const source = item.source ?? "conversation";
|
|
374
|
+
const storeItem = item as StoreRememberOptions;
|
|
375
|
+
const itemVeracity = forceVeracity
|
|
376
|
+
? defaultVeracity
|
|
377
|
+
: item.veracity !== undefined
|
|
378
|
+
? clampVeracity(item.veracity)
|
|
379
|
+
: defaultVeracity;
|
|
380
|
+
statement.run(
|
|
381
|
+
memoryId,
|
|
382
|
+
item.content,
|
|
383
|
+
source,
|
|
384
|
+
itemTimestamp,
|
|
385
|
+
beam.sessionId,
|
|
386
|
+
item.importance ?? 0.5,
|
|
387
|
+
metadataJson(item.metadata ?? null),
|
|
388
|
+
storeItem.authorId ?? storeItem.author_id ?? beam.authorId,
|
|
389
|
+
storeItem.authorType ?? storeItem.author_type ?? beam.authorType,
|
|
390
|
+
storeItem.channelId ?? storeItem.channel_id ?? beam.channelId,
|
|
391
|
+
item.memoryType ?? options.memoryType ?? "unknown",
|
|
392
|
+
itemVeracity,
|
|
393
|
+
trustTier,
|
|
394
|
+
item.scope ?? defaultScope,
|
|
395
|
+
);
|
|
396
|
+
addTemporalAnnotations(beam, memoryId, itemTimestamp, source);
|
|
397
|
+
emitEvent(beam, "MEMORY_ADDED", {
|
|
398
|
+
memoryId,
|
|
399
|
+
content: item.content,
|
|
400
|
+
source,
|
|
401
|
+
importance: item.importance ?? 0.5,
|
|
402
|
+
metadata: item.metadata ?? undefined,
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
trimWorkingMemory(beam);
|
|
406
|
+
});
|
|
407
|
+
invalidateCaches(beam);
|
|
408
|
+
const embeddingItems: { memoryId: string; content: string }[] = [];
|
|
409
|
+
items.forEach((item, index) => {
|
|
410
|
+
const id = ids[index];
|
|
411
|
+
if (id === undefined) return;
|
|
412
|
+
embeddingItems.push({ memoryId: id, content: item.content });
|
|
413
|
+
});
|
|
414
|
+
scheduleEmbedding(beam, embeddingItems);
|
|
415
|
+
items.forEach((item, index) => {
|
|
416
|
+
const id = ids[index];
|
|
417
|
+
if (id !== undefined && (item.extract === true || options.extract === true)) {
|
|
418
|
+
scheduleFactExtraction(beam, id, item.content);
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
return ids;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export function getContext(beam: BeamMemoryState, limit = 10): Row[] {
|
|
425
|
+
const now = toUtcIso();
|
|
426
|
+
return (
|
|
427
|
+
beam.db
|
|
428
|
+
.prepare(`
|
|
429
|
+
SELECT id, content, source, timestamp, importance, scope
|
|
430
|
+
FROM working_memory
|
|
431
|
+
WHERE (session_id = ? OR scope = 'global')
|
|
432
|
+
AND (valid_until IS NULL OR valid_until > ?)
|
|
433
|
+
AND superseded_by IS NULL
|
|
434
|
+
ORDER BY
|
|
435
|
+
CASE WHEN scope = 'global' THEN 0 ELSE 1 END,
|
|
436
|
+
importance DESC,
|
|
437
|
+
timestamp DESC
|
|
438
|
+
LIMIT ?
|
|
439
|
+
`)
|
|
440
|
+
.all(beam.sessionId, now, limit) as Row[]
|
|
441
|
+
).map(rowToDict);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export function invalidate(beam: BeamMemoryState, memoryId: string, replacementId: string | null = null): boolean {
|
|
445
|
+
const now = toUtcIso();
|
|
446
|
+
const working = beam.db
|
|
447
|
+
.prepare(`
|
|
448
|
+
UPDATE working_memory
|
|
449
|
+
SET valid_until = ?, superseded_by = ?
|
|
450
|
+
WHERE id = ? AND (session_id = ? OR scope = 'global')
|
|
451
|
+
`)
|
|
452
|
+
.run(now, replacementId, memoryId, beam.sessionId);
|
|
453
|
+
if (working.changes > 0) return true;
|
|
454
|
+
const episodic = beam.db
|
|
455
|
+
.prepare(`
|
|
456
|
+
UPDATE episodic_memory
|
|
457
|
+
SET valid_until = ?, superseded_by = ?
|
|
458
|
+
WHERE id = ? AND (session_id = ? OR scope = 'global')
|
|
459
|
+
`)
|
|
460
|
+
.run(now, replacementId, memoryId, beam.sessionId);
|
|
461
|
+
return episodic.changes > 0;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
export function getWorkingStats(
|
|
465
|
+
beam: BeamMemoryState,
|
|
466
|
+
authorId: string | null = null,
|
|
467
|
+
authorType: string | null = null,
|
|
468
|
+
channelId: string | null = null,
|
|
469
|
+
): BeamStats {
|
|
470
|
+
const clauses: string[] = [];
|
|
471
|
+
const params: SQLQueryBindings[] = [];
|
|
472
|
+
if (authorId) {
|
|
473
|
+
clauses.push("author_id = ?");
|
|
474
|
+
params.push(authorId);
|
|
475
|
+
}
|
|
476
|
+
if (authorType) {
|
|
477
|
+
clauses.push("author_type = ?");
|
|
478
|
+
params.push(authorType);
|
|
479
|
+
}
|
|
480
|
+
if (channelId) {
|
|
481
|
+
clauses.push("channel_id = ?");
|
|
482
|
+
params.push(channelId);
|
|
483
|
+
}
|
|
484
|
+
const where = clauses.length === 0 ? "" : ` WHERE ${clauses.join(" AND ")}`;
|
|
485
|
+
const total = beam.db.prepare(`SELECT COUNT(*) AS total FROM working_memory${where}`).get(...params) as {
|
|
486
|
+
total: number;
|
|
487
|
+
};
|
|
488
|
+
const last = beam.db
|
|
489
|
+
.prepare(`SELECT timestamp FROM working_memory${where} ORDER BY timestamp DESC LIMIT 1`)
|
|
490
|
+
.get(...params) as { timestamp: string | null } | null;
|
|
491
|
+
return { total: total.total, count: total.total, last: last?.timestamp ?? null };
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export function getGlobalWorkingStats(beam: BeamMemoryState): BeamStats {
|
|
495
|
+
return getWorkingStats(beam);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
export function updateWorking(
|
|
499
|
+
beam: BeamMemoryState,
|
|
500
|
+
memoryId: string,
|
|
501
|
+
content: string | null = null,
|
|
502
|
+
importance: number | null = null,
|
|
503
|
+
): boolean {
|
|
504
|
+
const assignments: string[] = [];
|
|
505
|
+
const params: SQLQueryBindings[] = [];
|
|
506
|
+
if (content !== null) {
|
|
507
|
+
assignments.push("content = ?");
|
|
508
|
+
params.push(content);
|
|
509
|
+
}
|
|
510
|
+
if (importance !== null) {
|
|
511
|
+
assignments.push("importance = ?");
|
|
512
|
+
params.push(importance);
|
|
513
|
+
}
|
|
514
|
+
if (assignments.length === 0) return false;
|
|
515
|
+
params.push(memoryId, beam.sessionId);
|
|
516
|
+
const result = beam.db
|
|
517
|
+
.prepare(`UPDATE working_memory SET ${assignments.join(", ")} WHERE id = ? AND session_id = ?`)
|
|
518
|
+
.run(...params);
|
|
519
|
+
if (result.changes > 0) {
|
|
520
|
+
invalidateCaches(beam);
|
|
521
|
+
if (content !== null) scheduleEmbedding(beam, [{ memoryId, content }]);
|
|
522
|
+
}
|
|
523
|
+
return result.changes > 0;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
export function get(beam: BeamMemoryState, memoryId: string): Row | null {
|
|
527
|
+
const working = beam.db
|
|
528
|
+
.prepare(`
|
|
529
|
+
SELECT id, content, source, timestamp, session_id,
|
|
530
|
+
importance, metadata_json, veracity, created_at
|
|
531
|
+
FROM working_memory
|
|
532
|
+
WHERE id = ?
|
|
533
|
+
`)
|
|
534
|
+
.get(memoryId) as Row | null | undefined;
|
|
535
|
+
if (working != null) return { ...working, metadata: working.metadata_json, memory_store: "working" };
|
|
536
|
+
|
|
537
|
+
const episodic = beam.db
|
|
538
|
+
.prepare(`
|
|
539
|
+
SELECT id, content, source, timestamp, session_id,
|
|
540
|
+
importance, metadata_json, veracity, created_at
|
|
541
|
+
FROM episodic_memory
|
|
542
|
+
WHERE id = ? AND (session_id = ? OR scope = 'global')
|
|
543
|
+
`)
|
|
544
|
+
.get(memoryId, beam.sessionId) as Row | null | undefined;
|
|
545
|
+
return episodic == null ? null : { ...episodic, metadata: episodic.metadata_json, memory_store: "episodic" };
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
export function forgetWorking(beam: BeamMemoryState, memoryId: string): boolean {
|
|
549
|
+
let deleted = 0;
|
|
550
|
+
transaction(beam.db, () => {
|
|
551
|
+
const result = beam.db
|
|
552
|
+
.prepare("DELETE FROM working_memory WHERE id = ? AND session_id = ?")
|
|
553
|
+
.run(memoryId, beam.sessionId);
|
|
554
|
+
deleted = result.changes;
|
|
555
|
+
if (deleted > 0) {
|
|
556
|
+
beam.db.prepare("DELETE FROM annotations WHERE memory_id = ?").run(memoryId);
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
if (deleted > 0) invalidateCaches(beam);
|
|
560
|
+
return deleted > 0;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export function scratchpadWrite(beam: BeamMemoryState, content: string): string {
|
|
564
|
+
const padId = generateId(content);
|
|
565
|
+
const timestamp = toUtcIso();
|
|
566
|
+
beam.db
|
|
567
|
+
.prepare(`
|
|
568
|
+
INSERT INTO scratchpad (id, content, session_id, created_at, updated_at)
|
|
569
|
+
VALUES (?, ?, ?, ?, ?)
|
|
570
|
+
ON CONFLICT(id) DO UPDATE SET content = excluded.content, updated_at = excluded.updated_at
|
|
571
|
+
`)
|
|
572
|
+
.run(padId, content, beam.sessionId, timestamp, timestamp);
|
|
573
|
+
return padId;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
export function scratchpadRead(beam: BeamMemoryState): Row[] {
|
|
577
|
+
return (
|
|
578
|
+
beam.db
|
|
579
|
+
.prepare(`
|
|
580
|
+
SELECT id, content, created_at, updated_at
|
|
581
|
+
FROM scratchpad
|
|
582
|
+
WHERE session_id = ?
|
|
583
|
+
ORDER BY updated_at DESC
|
|
584
|
+
LIMIT ?
|
|
585
|
+
`)
|
|
586
|
+
.all(beam.sessionId, Number.isFinite(SCRATCHPAD_MAX_ITEMS) ? SCRATCHPAD_MAX_ITEMS : 1000) as Row[]
|
|
587
|
+
).map(rowToDict);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
export function scratchpadClear(beam: BeamMemoryState): void {
|
|
591
|
+
beam.db.prepare("DELETE FROM scratchpad WHERE session_id = ?").run(beam.sessionId);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
export function exportToDict(beam: BeamMemoryState): Record<string, unknown> {
|
|
595
|
+
const db = beam.db;
|
|
596
|
+
return {
|
|
597
|
+
prometheus_memory_export: {
|
|
598
|
+
version: "1.0",
|
|
599
|
+
export_date: toUtcIso(),
|
|
600
|
+
source_db: beam.dbPath ?? ":memory:",
|
|
601
|
+
component: "beam",
|
|
602
|
+
},
|
|
603
|
+
working_memory: db
|
|
604
|
+
.prepare(`
|
|
605
|
+
SELECT id, content, source, timestamp, session_id, importance,
|
|
606
|
+
metadata_json, valid_until, superseded_by, scope,
|
|
607
|
+
recall_count, last_recalled, created_at, veracity, consolidated_at,
|
|
608
|
+
memory_type, author_id, author_type, channel_id, trust_tier,
|
|
609
|
+
event_date, event_date_precision, temporal_tags
|
|
610
|
+
FROM working_memory
|
|
611
|
+
ORDER BY session_id, timestamp
|
|
612
|
+
`)
|
|
613
|
+
.all(),
|
|
614
|
+
episodic_memory: db
|
|
615
|
+
.prepare(`
|
|
616
|
+
SELECT rowid, id, content, source, timestamp, session_id, importance,
|
|
617
|
+
metadata_json, summary_of, valid_until, superseded_by, scope,
|
|
618
|
+
recall_count, last_recalled, created_at, veracity, memory_type,
|
|
619
|
+
author_id, author_type, channel_id, trust_tier,
|
|
620
|
+
event_date, event_date_precision, temporal_tags
|
|
621
|
+
FROM episodic_memory
|
|
622
|
+
ORDER BY session_id, timestamp
|
|
623
|
+
`)
|
|
624
|
+
.all(),
|
|
625
|
+
episodic_embeddings: [],
|
|
626
|
+
scratchpad: db
|
|
627
|
+
.prepare(`
|
|
628
|
+
SELECT id, content, session_id, created_at, updated_at
|
|
629
|
+
FROM scratchpad
|
|
630
|
+
ORDER BY session_id, updated_at
|
|
631
|
+
`)
|
|
632
|
+
.all(),
|
|
633
|
+
consolidation_log: db
|
|
634
|
+
.prepare(`
|
|
635
|
+
SELECT id, session_id, items_consolidated, summary_preview, created_at
|
|
636
|
+
FROM consolidation_log
|
|
637
|
+
ORDER BY session_id, created_at
|
|
638
|
+
`)
|
|
639
|
+
.all(),
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
export function importFromDict(beam: BeamMemoryState, data: Record<string, unknown>, force = false): ImportStats {
|
|
644
|
+
const stats = {
|
|
645
|
+
working_memory: { inserted: 0, skipped: 0, overwritten: 0 },
|
|
646
|
+
episodic_memory: { inserted: 0, skipped: 0, overwritten: 0, embeddings_inserted: 0 },
|
|
647
|
+
scratchpad: { inserted: 0, updated: 0 },
|
|
648
|
+
consolidation_log: { inserted: 0 },
|
|
649
|
+
} satisfies ImportStats;
|
|
650
|
+
const db: Database = beam.db;
|
|
651
|
+
const oldToNewRowid = new Map<number, number>();
|
|
652
|
+
|
|
653
|
+
transaction(db, () => {
|
|
654
|
+
for (const raw of Array.isArray(data.working_memory) ? data.working_memory : []) {
|
|
655
|
+
const item = jsonObject(raw);
|
|
656
|
+
const id = String(item.id ?? "");
|
|
657
|
+
if (id.length === 0) continue;
|
|
658
|
+
const exists = db.prepare("SELECT 1 FROM working_memory WHERE id = ?").get(id) !== null;
|
|
659
|
+
if (exists && !force) {
|
|
660
|
+
stats.working_memory.skipped++;
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
if (exists) {
|
|
664
|
+
db.prepare("DELETE FROM working_memory WHERE id = ?").run(id);
|
|
665
|
+
stats.working_memory.overwritten++;
|
|
666
|
+
} else {
|
|
667
|
+
stats.working_memory.inserted++;
|
|
668
|
+
}
|
|
669
|
+
db.prepare(`
|
|
670
|
+
INSERT INTO working_memory
|
|
671
|
+
(id, content, source, timestamp, session_id, importance, metadata_json,
|
|
672
|
+
valid_until, superseded_by, scope, recall_count, last_recalled, created_at,
|
|
673
|
+
veracity, consolidated_at, memory_type, author_id, author_type, channel_id,
|
|
674
|
+
trust_tier, event_date, event_date_precision, temporal_tags)
|
|
675
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
676
|
+
`).run(
|
|
677
|
+
id,
|
|
678
|
+
sqlBinding(item.content, ""),
|
|
679
|
+
sqlBinding(item.source, null),
|
|
680
|
+
sqlBinding(item.timestamp, null),
|
|
681
|
+
sqlBinding(item.session_id, "default"),
|
|
682
|
+
sqlBinding(item.importance, 0.5),
|
|
683
|
+
sqlBinding(item.metadata_json, "{}"),
|
|
684
|
+
sqlBinding(item.valid_until, null),
|
|
685
|
+
sqlBinding(item.superseded_by, null),
|
|
686
|
+
sqlBinding(item.scope, "session"),
|
|
687
|
+
sqlBinding(item.recall_count, 0),
|
|
688
|
+
sqlBinding(item.last_recalled, null),
|
|
689
|
+
sqlBinding(item.created_at, null),
|
|
690
|
+
clampVeracity(item.veracity),
|
|
691
|
+
sqlBinding(item.consolidated_at, null),
|
|
692
|
+
sqlBinding(item.memory_type, "unknown"),
|
|
693
|
+
sqlBinding(item.author_id, null),
|
|
694
|
+
sqlBinding(item.author_type, null),
|
|
695
|
+
sqlBinding(item.channel_id, null),
|
|
696
|
+
sqlBinding(item.trust_tier, "STATED"),
|
|
697
|
+
sqlBinding(item.event_date, null),
|
|
698
|
+
sqlBinding(item.event_date_precision, "unknown"),
|
|
699
|
+
sqlBinding(item.temporal_tags, "[]"),
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
for (const raw of Array.isArray(data.episodic_memory) ? data.episodic_memory : []) {
|
|
704
|
+
const item = jsonObject(raw);
|
|
705
|
+
const id = String(item.id ?? "");
|
|
706
|
+
if (id.length === 0) continue;
|
|
707
|
+
const exists = db.prepare("SELECT 1 FROM episodic_memory WHERE id = ?").get(id) !== null;
|
|
708
|
+
if (exists && !force) {
|
|
709
|
+
stats.episodic_memory.skipped++;
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
if (exists) {
|
|
713
|
+
const existingRow = db.prepare("SELECT rowid FROM episodic_memory WHERE id = ?").get(id) as {
|
|
714
|
+
rowid: number;
|
|
715
|
+
} | null;
|
|
716
|
+
if (existingRow !== null && vecAvailable(db)) {
|
|
717
|
+
try {
|
|
718
|
+
db.prepare("DELETE FROM vec_episodes WHERE rowid = ?").run(existingRow.rowid);
|
|
719
|
+
} catch {
|
|
720
|
+
// sqlite-vec cleanup is best-effort; import correctness takes precedence.
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
db.prepare("DELETE FROM episodic_memory WHERE id = ?").run(id);
|
|
724
|
+
stats.episodic_memory.overwritten++;
|
|
725
|
+
} else {
|
|
726
|
+
stats.episodic_memory.inserted++;
|
|
727
|
+
}
|
|
728
|
+
db.prepare(`
|
|
729
|
+
INSERT INTO episodic_memory
|
|
730
|
+
(id, content, source, timestamp, session_id, importance, metadata_json,
|
|
731
|
+
summary_of, valid_until, superseded_by, scope, recall_count, last_recalled, created_at,
|
|
732
|
+
veracity, memory_type, author_id, author_type, channel_id, trust_tier,
|
|
733
|
+
event_date, event_date_precision, temporal_tags)
|
|
734
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
735
|
+
`).run(
|
|
736
|
+
id,
|
|
737
|
+
sqlBinding(item.content, ""),
|
|
738
|
+
sqlBinding(item.source, null),
|
|
739
|
+
sqlBinding(item.timestamp, null),
|
|
740
|
+
sqlBinding(item.session_id, "default"),
|
|
741
|
+
sqlBinding(item.importance, 0.5),
|
|
742
|
+
sqlBinding(item.metadata_json, "{}"),
|
|
743
|
+
sqlBinding(item.summary_of, ""),
|
|
744
|
+
sqlBinding(item.valid_until, null),
|
|
745
|
+
sqlBinding(item.superseded_by, null),
|
|
746
|
+
sqlBinding(item.scope, "session"),
|
|
747
|
+
sqlBinding(item.recall_count, 0),
|
|
748
|
+
sqlBinding(item.last_recalled, null),
|
|
749
|
+
sqlBinding(item.created_at, null),
|
|
750
|
+
clampVeracity(item.veracity),
|
|
751
|
+
sqlBinding(item.memory_type, "unknown"),
|
|
752
|
+
sqlBinding(item.author_id, null),
|
|
753
|
+
sqlBinding(item.author_type, null),
|
|
754
|
+
sqlBinding(item.channel_id, null),
|
|
755
|
+
sqlBinding(item.trust_tier, "STATED"),
|
|
756
|
+
sqlBinding(item.event_date, null),
|
|
757
|
+
sqlBinding(item.event_date_precision, "unknown"),
|
|
758
|
+
sqlBinding(item.temporal_tags, "[]"),
|
|
759
|
+
);
|
|
760
|
+
const oldRowid = Number(item.rowid);
|
|
761
|
+
const newRow = db.prepare("SELECT rowid FROM episodic_memory WHERE id = ?").get(id) as {
|
|
762
|
+
rowid: number;
|
|
763
|
+
} | null;
|
|
764
|
+
if (Number.isFinite(oldRowid) && newRow !== null) oldToNewRowid.set(oldRowid, newRow.rowid);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
for (const raw of Array.isArray(data.episodic_embeddings) ? data.episodic_embeddings : []) {
|
|
768
|
+
const item = jsonObject(raw);
|
|
769
|
+
const oldRowid = Number(item.rowid);
|
|
770
|
+
const mappedRowid = oldToNewRowid.get(oldRowid);
|
|
771
|
+
const embedding = Array.isArray(item.embedding) ? item.embedding.map(value => Number(value)) : null;
|
|
772
|
+
if (mappedRowid === undefined || embedding === null || embedding.some(v => !Number.isFinite(v))) {
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
775
|
+
if (!vecAvailable(db)) continue;
|
|
776
|
+
try {
|
|
777
|
+
vecInsert(db, mappedRowid, embedding);
|
|
778
|
+
stats.episodic_memory.embeddings_inserted++;
|
|
779
|
+
} catch {
|
|
780
|
+
// Embedding import is best-effort when sqlite-vec is unavailable or degraded.
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
for (const raw of Array.isArray(data.scratchpad) ? data.scratchpad : []) {
|
|
785
|
+
const item = jsonObject(raw);
|
|
786
|
+
const id = String(item.id ?? "");
|
|
787
|
+
if (id.length === 0) continue;
|
|
788
|
+
const exists = db.prepare("SELECT 1 FROM scratchpad WHERE id = ?").get(id) !== null;
|
|
789
|
+
if (exists) {
|
|
790
|
+
db.prepare(
|
|
791
|
+
"UPDATE scratchpad SET content = ?, session_id = ?, created_at = ?, updated_at = ? WHERE id = ?",
|
|
792
|
+
).run(
|
|
793
|
+
sqlBinding(item.content, ""),
|
|
794
|
+
sqlBinding(item.session_id, "default"),
|
|
795
|
+
sqlBinding(item.created_at, null),
|
|
796
|
+
sqlBinding(item.updated_at, null),
|
|
797
|
+
id,
|
|
798
|
+
);
|
|
799
|
+
stats.scratchpad.updated++;
|
|
800
|
+
} else {
|
|
801
|
+
db.prepare(
|
|
802
|
+
"INSERT INTO scratchpad (id, content, session_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
|
|
803
|
+
).run(
|
|
804
|
+
id,
|
|
805
|
+
sqlBinding(item.content, ""),
|
|
806
|
+
sqlBinding(item.session_id, "default"),
|
|
807
|
+
sqlBinding(item.created_at, null),
|
|
808
|
+
sqlBinding(item.updated_at, null),
|
|
809
|
+
);
|
|
810
|
+
stats.scratchpad.inserted++;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
for (const raw of Array.isArray(data.consolidation_log) ? data.consolidation_log : []) {
|
|
815
|
+
const item = jsonObject(raw);
|
|
816
|
+
db.prepare(
|
|
817
|
+
"INSERT INTO consolidation_log (session_id, items_consolidated, summary_preview, created_at) VALUES (?, ?, ?, ?)",
|
|
818
|
+
).run(
|
|
819
|
+
sqlBinding(item.session_id, "default"),
|
|
820
|
+
sqlBinding(item.items_consolidated, 0),
|
|
821
|
+
sqlBinding(item.summary_preview, ""),
|
|
822
|
+
sqlBinding(item.created_at, null),
|
|
823
|
+
);
|
|
824
|
+
stats.consolidation_log.inserted++;
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
invalidateCaches(beam);
|
|
828
|
+
return stats;
|
|
829
|
+
}
|