@framers/agentos 0.1.105 → 0.1.107
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/AgentOS.d.ts +80 -0
- package/dist/api/AgentOS.d.ts.map +1 -1
- package/dist/api/AgentOS.js +128 -9
- package/dist/api/AgentOS.js.map +1 -1
- package/dist/api/AgentOSOrchestrator.d.ts.map +1 -1
- package/dist/api/AgentOSOrchestrator.js +32 -4
- package/dist/api/AgentOSOrchestrator.js.map +1 -1
- package/dist/api/agent.js +1 -1
- package/dist/api/agent.js.map +1 -1
- package/dist/api/streamText.d.ts.map +1 -1
- package/dist/api/streamText.js +5 -1
- package/dist/api/streamText.js.map +1 -1
- package/dist/memory/AgentMemory.d.ts +62 -16
- package/dist/memory/AgentMemory.d.ts.map +1 -1
- package/dist/memory/AgentMemory.js +236 -28
- package/dist/memory/AgentMemory.js.map +1 -1
- package/dist/memory/consolidation/ConsolidationLoop.d.ts.map +1 -1
- package/dist/memory/consolidation/ConsolidationLoop.js +32 -9
- package/dist/memory/consolidation/ConsolidationLoop.js.map +1 -1
- package/dist/memory/extension/MemoryToolsExtension.d.ts +53 -0
- package/dist/memory/extension/MemoryToolsExtension.d.ts.map +1 -0
- package/dist/memory/extension/MemoryToolsExtension.js +54 -0
- package/dist/memory/extension/MemoryToolsExtension.js.map +1 -0
- package/dist/memory/extension/StandaloneMemoryExtension.d.ts +27 -0
- package/dist/memory/extension/StandaloneMemoryExtension.d.ts.map +1 -0
- package/dist/memory/extension/StandaloneMemoryExtension.js +122 -0
- package/dist/memory/extension/StandaloneMemoryExtension.js.map +1 -0
- package/dist/memory/facade/Memory.d.ts +45 -0
- package/dist/memory/facade/Memory.d.ts.map +1 -1
- package/dist/memory/facade/Memory.js +296 -137
- package/dist/memory/facade/Memory.js.map +1 -1
- package/dist/memory/facade/types.d.ts +12 -8
- package/dist/memory/facade/types.d.ts.map +1 -1
- package/dist/memory/feedback/RetrievalFeedbackSignal.d.ts +14 -5
- package/dist/memory/feedback/RetrievalFeedbackSignal.d.ts.map +1 -1
- package/dist/memory/feedback/RetrievalFeedbackSignal.js +131 -20
- package/dist/memory/feedback/RetrievalFeedbackSignal.js.map +1 -1
- package/dist/memory/index.d.ts +7 -1
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +5 -1
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/integration/StandaloneMemoryBridge.d.ts +55 -0
- package/dist/memory/integration/StandaloneMemoryBridge.d.ts.map +1 -0
- package/dist/memory/integration/StandaloneMemoryBridge.js +398 -0
- package/dist/memory/integration/StandaloneMemoryBridge.js.map +1 -0
- package/dist/memory/io/CsvImporter.d.ts +51 -0
- package/dist/memory/io/CsvImporter.d.ts.map +1 -0
- package/dist/memory/io/CsvImporter.js +229 -0
- package/dist/memory/io/CsvImporter.js.map +1 -0
- package/dist/memory/io/index.d.ts +2 -0
- package/dist/memory/io/index.d.ts.map +1 -1
- package/dist/memory/io/index.js +2 -0
- package/dist/memory/io/index.js.map +1 -1
- package/dist/memory/store/tracePersistence.d.ts +68 -0
- package/dist/memory/store/tracePersistence.d.ts.map +1 -0
- package/dist/memory/store/tracePersistence.js +159 -0
- package/dist/memory/store/tracePersistence.js.map +1 -0
- package/dist/memory/tools/MemoryAddTool.d.ts.map +1 -1
- package/dist/memory/tools/MemoryAddTool.js +18 -6
- package/dist/memory/tools/MemoryAddTool.js.map +1 -1
- package/dist/memory/tools/MemoryMergeTool.d.ts.map +1 -1
- package/dist/memory/tools/MemoryMergeTool.js +41 -13
- package/dist/memory/tools/MemoryMergeTool.js.map +1 -1
- package/dist/memory/tools/MemorySearchTool.d.ts.map +1 -1
- package/dist/memory/tools/MemorySearchTool.js +24 -4
- package/dist/memory/tools/MemorySearchTool.js.map +1 -1
- package/dist/memory/tools/MemoryUpdateTool.d.ts.map +1 -1
- package/dist/memory/tools/MemoryUpdateTool.js +38 -16
- package/dist/memory/tools/MemoryUpdateTool.js.map +1 -1
- package/dist/orchestration/builders/nodes.d.ts +6 -0
- package/dist/orchestration/builders/nodes.d.ts.map +1 -1
- package/dist/orchestration/builders/nodes.js +13 -2
- package/dist/orchestration/builders/nodes.js.map +1 -1
- package/dist/orchestration/runtime/GraphRuntime.d.ts.map +1 -1
- package/dist/orchestration/runtime/GraphRuntime.js +15 -34
- package/dist/orchestration/runtime/GraphRuntime.js.map +1 -1
- package/dist/orchestration/runtime/NodeExecutor.d.ts.map +1 -1
- package/dist/orchestration/runtime/NodeExecutor.js +2 -16
- package/dist/orchestration/runtime/NodeExecutor.js.map +1 -1
- package/dist/orchestration/runtime/StateManager.d.ts.map +1 -1
- package/dist/orchestration/runtime/StateManager.js +15 -3
- package/dist/orchestration/runtime/StateManager.js.map +1 -1
- package/dist/orchestration/runtime/safeExpressionEvaluator.d.ts +38 -0
- package/dist/orchestration/runtime/safeExpressionEvaluator.d.ts.map +1 -0
- package/dist/orchestration/runtime/safeExpressionEvaluator.js +184 -0
- package/dist/orchestration/runtime/safeExpressionEvaluator.js.map +1 -0
- package/package.json +1 -1
|
@@ -22,14 +22,18 @@ import fs from 'node:fs/promises';
|
|
|
22
22
|
import os from 'node:os';
|
|
23
23
|
import path from 'node:path';
|
|
24
24
|
import { SqliteBrain } from '../store/SqliteBrain.js';
|
|
25
|
+
import { buildNaturalLanguageFtsQuery, buildInitialTraceMetadata, parseTraceMetadata, readPersistedDecayState, withPersistedDecayState, } from '../store/tracePersistence.js';
|
|
25
26
|
import { SqliteKnowledgeGraph } from '../store/SqliteKnowledgeGraph.js';
|
|
26
27
|
import { SqliteMemoryGraph } from '../store/SqliteMemoryGraph.js';
|
|
27
28
|
import { LoaderRegistry } from '../ingestion/LoaderRegistry.js';
|
|
28
29
|
import { FolderScanner } from '../ingestion/FolderScanner.js';
|
|
29
30
|
import { ChunkingEngine } from '../ingestion/ChunkingEngine.js';
|
|
31
|
+
import { UrlLoader } from '../ingestion/UrlLoader.js';
|
|
30
32
|
import { RetrievalFeedbackSignal } from '../feedback/RetrievalFeedbackSignal.js';
|
|
31
33
|
import { ConsolidationLoop } from '../consolidation/ConsolidationLoop.js';
|
|
32
|
-
import {
|
|
34
|
+
import { penalizeUnused, updateOnRetrieval } from '../decay/DecayModel.js';
|
|
35
|
+
import { JsonExporter, JsonImporter, MarkdownExporter, MarkdownImporter, ObsidianExporter, ObsidianImporter, SqliteExporter, SqliteImporter, ChatGptImporter, CsvImporter, } from '../io/index.js';
|
|
36
|
+
import { MemoryAddTool, MemoryUpdateTool, MemoryDeleteTool, MemoryMergeTool, MemorySearchTool, MemoryReflectTool, } from '../tools/index.js';
|
|
33
37
|
// ---------------------------------------------------------------------------
|
|
34
38
|
// Constants & defaults
|
|
35
39
|
// ---------------------------------------------------------------------------
|
|
@@ -104,6 +108,10 @@ export class Memory {
|
|
|
104
108
|
decay: true,
|
|
105
109
|
...config,
|
|
106
110
|
};
|
|
111
|
+
if (this._config.store !== 'sqlite') {
|
|
112
|
+
throw new Error(`Memory currently supports only the SQLite-backed facade at runtime. ` +
|
|
113
|
+
`Received store="${this._config.store}".`);
|
|
114
|
+
}
|
|
107
115
|
// Step 2: create SqliteBrain.
|
|
108
116
|
this._brain = new SqliteBrain(this._config.path);
|
|
109
117
|
// Step 3: check embedding dimension compatibility.
|
|
@@ -151,22 +159,26 @@ export class Memory {
|
|
|
151
159
|
*/
|
|
152
160
|
async remember(content, options) {
|
|
153
161
|
await this._initPromise;
|
|
154
|
-
const
|
|
155
|
-
const now = Date.now();
|
|
162
|
+
const contentHash = sha256(content);
|
|
156
163
|
const type = options?.type ?? 'episodic';
|
|
157
164
|
const scope = options?.scope ?? 'user';
|
|
158
165
|
const scopeId = options?.scopeId ?? '';
|
|
166
|
+
const existing = this._findExistingTraceByHash(contentHash, type, scope, scopeId);
|
|
167
|
+
if (existing) {
|
|
168
|
+
return this._buildTrace(existing);
|
|
169
|
+
}
|
|
170
|
+
const id = nextTraceId();
|
|
171
|
+
const now = Date.now();
|
|
159
172
|
const tags = options?.tags ?? [];
|
|
160
173
|
const entities = options?.entities ?? [];
|
|
161
174
|
const importance = options?.importance ?? 1.0;
|
|
162
|
-
const contentHash = sha256(content);
|
|
163
175
|
// Insert into memory_traces.
|
|
164
176
|
this._brain.db
|
|
165
177
|
.prepare(`INSERT INTO memory_traces
|
|
166
178
|
(id, type, scope, content, embedding, strength, created_at,
|
|
167
179
|
last_accessed, retrieval_count, tags, emotions, metadata, deleted)
|
|
168
180
|
VALUES (?, ?, ?, ?, NULL, ?, ?, NULL, 0, ?, ?, ?, 0)`)
|
|
169
|
-
.run(id, type, scope, content, importance, now, JSON.stringify(tags), JSON.stringify({}), JSON.stringify({
|
|
181
|
+
.run(id, type, scope, content, importance, now, JSON.stringify(tags), JSON.stringify({}), JSON.stringify(buildInitialTraceMetadata({}, { contentHash, entities, scopeId })));
|
|
170
182
|
// Sync FTS5 index. The external-content FTS5 table needs explicit insert.
|
|
171
183
|
this._brain.db
|
|
172
184
|
.prepare(`INSERT INTO memory_traces_fts (rowid, content, tags)
|
|
@@ -199,7 +211,7 @@ export class Memory {
|
|
|
199
211
|
retrieval_count: 0,
|
|
200
212
|
tags: JSON.stringify(tags),
|
|
201
213
|
emotions: JSON.stringify({}),
|
|
202
|
-
metadata: JSON.stringify({
|
|
214
|
+
metadata: JSON.stringify(buildInitialTraceMetadata({}, { contentHash, entities, scopeId })),
|
|
203
215
|
deleted: 0,
|
|
204
216
|
});
|
|
205
217
|
}
|
|
@@ -216,6 +228,10 @@ export class Memory {
|
|
|
216
228
|
*/
|
|
217
229
|
async recall(query, options) {
|
|
218
230
|
await this._initPromise;
|
|
231
|
+
const ftsQuery = buildNaturalLanguageFtsQuery(query);
|
|
232
|
+
if (!ftsQuery) {
|
|
233
|
+
return [];
|
|
234
|
+
}
|
|
219
235
|
const limit = options?.limit ?? 10;
|
|
220
236
|
const minStrength = options?.minStrength ?? 0;
|
|
221
237
|
// Build WHERE clause fragments for optional filters.
|
|
@@ -229,6 +245,10 @@ export class Memory {
|
|
|
229
245
|
conditions.push('t.scope = ?');
|
|
230
246
|
params.push(options.scope);
|
|
231
247
|
}
|
|
248
|
+
if (options?.scopeId) {
|
|
249
|
+
conditions.push(`json_extract(t.metadata, '$.scopeId') = ?`);
|
|
250
|
+
params.push(options.scopeId);
|
|
251
|
+
}
|
|
232
252
|
if (minStrength > 0) {
|
|
233
253
|
conditions.push('t.strength >= ?');
|
|
234
254
|
params.push(minStrength);
|
|
@@ -247,11 +267,12 @@ export class Memory {
|
|
|
247
267
|
ORDER BY (t.strength * abs(fts.rank)) DESC
|
|
248
268
|
LIMIT ?
|
|
249
269
|
`;
|
|
250
|
-
params.push(
|
|
270
|
+
params.push(ftsQuery, limit);
|
|
251
271
|
const rows = this._brain.db
|
|
252
272
|
.prepare(sql)
|
|
253
273
|
.all(...params);
|
|
254
|
-
|
|
274
|
+
const updatedRows = this._applyRecallAccessUpdates(rows);
|
|
275
|
+
return updatedRows.map((row) => ({
|
|
255
276
|
trace: this._buildTrace(row),
|
|
256
277
|
score: row.strength * Math.abs(row.rank),
|
|
257
278
|
}));
|
|
@@ -295,9 +316,12 @@ export class Memory {
|
|
|
295
316
|
chunksCreated: 0,
|
|
296
317
|
tracesCreated: 0,
|
|
297
318
|
};
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
319
|
+
const chunking = {
|
|
320
|
+
strategy: (this._config.ingestion?.chunkStrategy ?? 'semantic'),
|
|
321
|
+
chunkSize: this._config.ingestion?.chunkSize ?? 512,
|
|
322
|
+
chunkOverlap: this._config.ingestion?.chunkOverlap ?? 64,
|
|
323
|
+
};
|
|
324
|
+
const urlLoader = new UrlLoader(this._loaderRegistry);
|
|
301
325
|
try {
|
|
302
326
|
// Detect source type.
|
|
303
327
|
const stat = await fs.stat(source).catch(() => null);
|
|
@@ -315,51 +339,14 @@ export class Memory {
|
|
|
315
339
|
result.failed.push(...scanResult.failed);
|
|
316
340
|
// Chunk and store each loaded document.
|
|
317
341
|
for (const doc of scanResult.documents) {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
this._brain.db
|
|
327
|
-
.prepare(`INSERT OR IGNORE INTO documents
|
|
328
|
-
(id, path, format, title, content_hash, chunk_count, metadata, ingested_at)
|
|
329
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
330
|
-
.run(docId, doc.metadata.source ?? source, doc.format, doc.metadata.title ?? null, contentHash, chunks.length, JSON.stringify(doc.metadata), Date.now());
|
|
331
|
-
// Insert chunks and create traces.
|
|
332
|
-
// Insert memory_traces FIRST (document_chunks.trace_id is an FK).
|
|
333
|
-
for (const chunk of chunks) {
|
|
334
|
-
const chunkId = `chunk_${Date.now()}_${_traceCounter++}`;
|
|
335
|
-
const traceId = nextTraceId();
|
|
336
|
-
// 1. Create the memory trace for this chunk.
|
|
337
|
-
this._brain.db
|
|
338
|
-
.prepare(`INSERT INTO memory_traces
|
|
339
|
-
(id, type, scope, content, embedding, strength, created_at,
|
|
340
|
-
last_accessed, retrieval_count, tags, emotions, metadata, deleted)
|
|
341
|
-
VALUES (?, 'semantic', 'user', ?, NULL, 1.0, ?, NULL, 0, '[]', '{}', ?, 0)`)
|
|
342
|
-
.run(traceId, chunk.content, Date.now(), JSON.stringify({
|
|
343
|
-
content_hash: sha256(chunk.content),
|
|
344
|
-
document_id: docId,
|
|
345
|
-
chunk_index: chunk.index,
|
|
346
|
-
}));
|
|
347
|
-
// 2. Sync FTS index.
|
|
348
|
-
this._brain.db
|
|
349
|
-
.prepare(`INSERT INTO memory_traces_fts (rowid, content, tags)
|
|
350
|
-
VALUES (
|
|
351
|
-
(SELECT rowid FROM memory_traces WHERE id = ?),
|
|
352
|
-
?,
|
|
353
|
-
'[]'
|
|
354
|
-
)`)
|
|
355
|
-
.run(traceId, chunk.content);
|
|
356
|
-
// 3. Insert the document chunk (FK to memory_traces now satisfied).
|
|
357
|
-
this._brain.db
|
|
358
|
-
.prepare(`INSERT INTO document_chunks (id, document_id, trace_id, content, chunk_index, page_number, embedding)
|
|
359
|
-
VALUES (?, ?, ?, ?, ?, ?, NULL)`)
|
|
360
|
-
.run(chunkId, docId, traceId, chunk.content, chunk.index, chunk.pageNumber ?? null);
|
|
361
|
-
result.chunksCreated++;
|
|
362
|
-
result.tracesCreated++;
|
|
342
|
+
try {
|
|
343
|
+
await this._ingestLoadedDocument(doc.metadata.source ?? source, doc, chunking, result);
|
|
344
|
+
}
|
|
345
|
+
catch (err) {
|
|
346
|
+
result.failed.push({
|
|
347
|
+
path: doc.metadata.source ?? source,
|
|
348
|
+
error: err instanceof Error ? err.message : String(err),
|
|
349
|
+
});
|
|
363
350
|
}
|
|
364
351
|
}
|
|
365
352
|
}
|
|
@@ -368,49 +355,18 @@ export class Memory {
|
|
|
368
355
|
try {
|
|
369
356
|
const doc = await this._loaderRegistry.loadFile(source);
|
|
370
357
|
result.succeeded.push(source);
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
for (const chunk of chunks) {
|
|
384
|
-
const chunkId = `chunk_${Date.now()}_${_traceCounter++}`;
|
|
385
|
-
const traceId = nextTraceId();
|
|
386
|
-
// 1. Create the memory trace first (FK target for document_chunks).
|
|
387
|
-
this._brain.db
|
|
388
|
-
.prepare(`INSERT INTO memory_traces
|
|
389
|
-
(id, type, scope, content, embedding, strength, created_at,
|
|
390
|
-
last_accessed, retrieval_count, tags, emotions, metadata, deleted)
|
|
391
|
-
VALUES (?, 'semantic', 'user', ?, NULL, 1.0, ?, NULL, 0, '[]', '{}', ?, 0)`)
|
|
392
|
-
.run(traceId, chunk.content, Date.now(), JSON.stringify({
|
|
393
|
-
content_hash: sha256(chunk.content),
|
|
394
|
-
document_id: docId,
|
|
395
|
-
chunk_index: chunk.index,
|
|
396
|
-
}));
|
|
397
|
-
// 2. Sync FTS index.
|
|
398
|
-
this._brain.db
|
|
399
|
-
.prepare(`INSERT INTO memory_traces_fts (rowid, content, tags)
|
|
400
|
-
VALUES (
|
|
401
|
-
(SELECT rowid FROM memory_traces WHERE id = ?),
|
|
402
|
-
?,
|
|
403
|
-
'[]'
|
|
404
|
-
)`)
|
|
405
|
-
.run(traceId, chunk.content);
|
|
406
|
-
// 3. Insert document chunk (FK to memory_traces now satisfied).
|
|
407
|
-
this._brain.db
|
|
408
|
-
.prepare(`INSERT INTO document_chunks (id, document_id, trace_id, content, chunk_index, page_number, embedding)
|
|
409
|
-
VALUES (?, ?, ?, ?, ?, ?, NULL)`)
|
|
410
|
-
.run(chunkId, docId, traceId, chunk.content, chunk.index, chunk.pageNumber ?? null);
|
|
411
|
-
result.chunksCreated++;
|
|
412
|
-
result.tracesCreated++;
|
|
413
|
-
}
|
|
358
|
+
await this._ingestLoadedDocument(source, doc, chunking, result);
|
|
359
|
+
}
|
|
360
|
+
catch (err) {
|
|
361
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
362
|
+
result.failed.push({ path: source, error: message });
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
else if (urlLoader.canLoad(source)) {
|
|
366
|
+
try {
|
|
367
|
+
const doc = await urlLoader.load(source);
|
|
368
|
+
result.succeeded.push(source);
|
|
369
|
+
await this._ingestLoadedDocument(source, doc, chunking, result);
|
|
414
370
|
}
|
|
415
371
|
catch (err) {
|
|
416
372
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -418,10 +374,10 @@ export class Memory {
|
|
|
418
374
|
}
|
|
419
375
|
}
|
|
420
376
|
else {
|
|
421
|
-
//
|
|
377
|
+
// Unknown source.
|
|
422
378
|
result.failed.push({
|
|
423
379
|
path: source,
|
|
424
|
-
error: `Source "${source}" is not a file or
|
|
380
|
+
error: `Source "${source}" is not a file, directory, or supported URL.`,
|
|
425
381
|
});
|
|
426
382
|
}
|
|
427
383
|
}
|
|
@@ -530,11 +486,55 @@ export class Memory {
|
|
|
530
486
|
feedback(traceId, signal) {
|
|
531
487
|
if (!this._feedbackSignal)
|
|
532
488
|
return;
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
489
|
+
try {
|
|
490
|
+
const now = Date.now();
|
|
491
|
+
const row = this._brain.db
|
|
492
|
+
.prepare(`SELECT id, type, scope, content, embedding, strength, created_at,
|
|
493
|
+
last_accessed, retrieval_count, tags, emotions, metadata, deleted
|
|
494
|
+
FROM memory_traces
|
|
495
|
+
WHERE id = ?
|
|
496
|
+
LIMIT 1`)
|
|
497
|
+
.get(traceId);
|
|
498
|
+
this._brain.db
|
|
499
|
+
.prepare(`INSERT INTO retrieval_feedback (trace_id, signal, query, created_at)
|
|
500
|
+
VALUES (?, ?, NULL, ?)`)
|
|
501
|
+
.run(traceId, signal, now);
|
|
502
|
+
if (!row)
|
|
503
|
+
return;
|
|
504
|
+
if (signal === 'used') {
|
|
505
|
+
const update = updateOnRetrieval(this._buildTrace(row), now);
|
|
506
|
+
const metadata = JSON.stringify(withPersistedDecayState(parseTraceMetadata(row.metadata), {
|
|
507
|
+
stability: update.stability,
|
|
508
|
+
accessCount: update.accessCount,
|
|
509
|
+
reinforcementInterval: update.reinforcementInterval,
|
|
510
|
+
nextReinforcementAt: update.nextReinforcementAt,
|
|
511
|
+
}));
|
|
512
|
+
this._brain.db
|
|
513
|
+
.prepare(`UPDATE memory_traces
|
|
514
|
+
SET strength = ?, last_accessed = ?, retrieval_count = ?, metadata = ?
|
|
515
|
+
WHERE id = ?`)
|
|
516
|
+
.run(update.encodingStrength, update.lastAccessedAt, update.retrievalCount, metadata, traceId);
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
const penalty = penalizeUnused(this._buildTrace(row), now);
|
|
520
|
+
const existingDecay = readPersistedDecayState(parseTraceMetadata(row.metadata), row.retrieval_count);
|
|
521
|
+
const metadata = JSON.stringify(withPersistedDecayState(parseTraceMetadata(row.metadata), {
|
|
522
|
+
stability: penalty.stability,
|
|
523
|
+
accessCount: existingDecay.accessCount,
|
|
524
|
+
reinforcementInterval: existingDecay.reinforcementInterval,
|
|
525
|
+
...(existingDecay.nextReinforcementAt !== undefined
|
|
526
|
+
? { nextReinforcementAt: existingDecay.nextReinforcementAt }
|
|
527
|
+
: {}),
|
|
528
|
+
}));
|
|
529
|
+
this._brain.db
|
|
530
|
+
.prepare(`UPDATE memory_traces
|
|
531
|
+
SET strength = ?, last_accessed = ?, metadata = ?
|
|
532
|
+
WHERE id = ?`)
|
|
533
|
+
.run(penalty.encodingStrength, penalty.lastAccessedAt, metadata, traceId);
|
|
534
|
+
}
|
|
535
|
+
catch {
|
|
536
|
+
// Explicit feedback is best-effort; the caller should not fail on analytics updates.
|
|
537
|
+
}
|
|
538
538
|
}
|
|
539
539
|
// =========================================================================
|
|
540
540
|
// Import / Export
|
|
@@ -592,31 +592,68 @@ export class Memory {
|
|
|
592
592
|
async importFrom(source, options) {
|
|
593
593
|
await this._initPromise;
|
|
594
594
|
const format = await this._detectImportFormat(source, options);
|
|
595
|
+
let result;
|
|
595
596
|
switch (format) {
|
|
596
|
-
case 'json':
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
case '
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
return { imported: 0, skipped: 0, errors: [`Unsupported import format: "${format}"`] };
|
|
618
|
-
}
|
|
597
|
+
case 'json':
|
|
598
|
+
result = await new JsonImporter(this._brain).import(source);
|
|
599
|
+
break;
|
|
600
|
+
case 'markdown':
|
|
601
|
+
result = await new MarkdownImporter(this._brain).import(source);
|
|
602
|
+
break;
|
|
603
|
+
case 'obsidian':
|
|
604
|
+
result = await new ObsidianImporter(this._brain).import(source);
|
|
605
|
+
break;
|
|
606
|
+
case 'sqlite':
|
|
607
|
+
result = await new SqliteImporter(this._brain).import(source);
|
|
608
|
+
break;
|
|
609
|
+
case 'chatgpt':
|
|
610
|
+
result = await new ChatGptImporter(this._brain).import(source);
|
|
611
|
+
break;
|
|
612
|
+
case 'csv':
|
|
613
|
+
result = await new CsvImporter(this._brain).import(source);
|
|
614
|
+
break;
|
|
615
|
+
default:
|
|
616
|
+
result = { imported: 0, skipped: 0, errors: [`Unsupported import format: "${format}"`] };
|
|
617
|
+
break;
|
|
619
618
|
}
|
|
619
|
+
if (result.imported > 0) {
|
|
620
|
+
this._rebuildFtsIndex();
|
|
621
|
+
}
|
|
622
|
+
return result;
|
|
623
|
+
}
|
|
624
|
+
// =========================================================================
|
|
625
|
+
// Tool integration
|
|
626
|
+
// =========================================================================
|
|
627
|
+
/**
|
|
628
|
+
* Create runtime `ITool` instances backed by this memory facade's SQLite brain.
|
|
629
|
+
*
|
|
630
|
+
* This is the supported bridge from the standalone memory engine into
|
|
631
|
+
* AgentOS tool registration. The returned tools share this `Memory`
|
|
632
|
+
* instance's underlying SQLite database and consolidation loop.
|
|
633
|
+
*
|
|
634
|
+
* Typical usage:
|
|
635
|
+
* ```ts
|
|
636
|
+
* for (const tool of memory.createTools()) {
|
|
637
|
+
* await agentos.getToolOrchestrator().registerTool(tool);
|
|
638
|
+
* }
|
|
639
|
+
* ```
|
|
640
|
+
*
|
|
641
|
+
* When self-improvement is disabled, `memory_reflect` is omitted because
|
|
642
|
+
* there is no backing {@link ConsolidationLoop} instance.
|
|
643
|
+
*/
|
|
644
|
+
createTools(options) {
|
|
645
|
+
const tools = [
|
|
646
|
+
new MemoryAddTool(this._brain),
|
|
647
|
+
new MemoryUpdateTool(this._brain),
|
|
648
|
+
new MemoryDeleteTool(this._brain),
|
|
649
|
+
new MemoryMergeTool(this._brain),
|
|
650
|
+
new MemorySearchTool(this._brain),
|
|
651
|
+
];
|
|
652
|
+
const includeReflect = options?.includeReflect ?? true;
|
|
653
|
+
if (includeReflect && this._consolidationLoop) {
|
|
654
|
+
tools.push(new MemoryReflectTool(this._brain, this._consolidationLoop));
|
|
655
|
+
}
|
|
656
|
+
return tools;
|
|
620
657
|
}
|
|
621
658
|
// =========================================================================
|
|
622
659
|
// Health
|
|
@@ -730,13 +767,10 @@ export class Memory {
|
|
|
730
767
|
emotions = JSON.parse(row.emotions);
|
|
731
768
|
}
|
|
732
769
|
catch { /* empty */ }
|
|
733
|
-
|
|
734
|
-
try {
|
|
735
|
-
metadata = JSON.parse(row.metadata);
|
|
736
|
-
}
|
|
737
|
-
catch { /* empty */ }
|
|
770
|
+
const metadata = parseTraceMetadata(row.metadata);
|
|
738
771
|
const entities = Array.isArray(metadata.entities) ? metadata.entities : [];
|
|
739
772
|
const scopeId = typeof metadata.scopeId === 'string' ? metadata.scopeId : '';
|
|
773
|
+
const decayState = readPersistedDecayState(metadata, row.retrieval_count);
|
|
740
774
|
return {
|
|
741
775
|
id: row.id,
|
|
742
776
|
type: row.type,
|
|
@@ -760,17 +794,142 @@ export class Memory {
|
|
|
760
794
|
...emotions,
|
|
761
795
|
},
|
|
762
796
|
encodingStrength: row.strength,
|
|
763
|
-
stability:
|
|
797
|
+
stability: decayState.stability,
|
|
764
798
|
retrievalCount: row.retrieval_count,
|
|
765
799
|
lastAccessedAt: row.last_accessed ?? row.created_at,
|
|
766
|
-
accessCount:
|
|
767
|
-
reinforcementInterval:
|
|
800
|
+
accessCount: decayState.accessCount,
|
|
801
|
+
reinforcementInterval: decayState.reinforcementInterval,
|
|
802
|
+
...(decayState.nextReinforcementAt !== undefined
|
|
803
|
+
? { nextReinforcementAt: decayState.nextReinforcementAt }
|
|
804
|
+
: {}),
|
|
768
805
|
associatedTraceIds: [],
|
|
769
806
|
createdAt: row.created_at,
|
|
770
807
|
updatedAt: row.created_at,
|
|
771
808
|
isActive: row.deleted === 0,
|
|
772
809
|
};
|
|
773
810
|
}
|
|
811
|
+
/**
|
|
812
|
+
* Find an active trace previously stored with the same content hash.
|
|
813
|
+
*
|
|
814
|
+
* Checks both the facade-native `content_hash` metadata key and the
|
|
815
|
+
* importer-used `import_hash` key so dedup works across facade and import
|
|
816
|
+
* workflows.
|
|
817
|
+
*/
|
|
818
|
+
_findExistingTraceByHash(contentHash, type, scope, scopeId) {
|
|
819
|
+
return this._brain.db
|
|
820
|
+
.prepare(`SELECT id, type, scope, content, embedding, strength, created_at,
|
|
821
|
+
last_accessed, retrieval_count, tags, emotions, metadata, deleted
|
|
822
|
+
FROM memory_traces
|
|
823
|
+
WHERE deleted = 0
|
|
824
|
+
AND type = ?
|
|
825
|
+
AND scope = ?
|
|
826
|
+
AND ifnull(json_extract(metadata, '$.scopeId'), '') = ?
|
|
827
|
+
AND (
|
|
828
|
+
json_extract(metadata, '$.content_hash') = ?
|
|
829
|
+
OR json_extract(metadata, '$.import_hash') = ?
|
|
830
|
+
)
|
|
831
|
+
LIMIT 1`)
|
|
832
|
+
.get(type, scope, scopeId, contentHash, contentHash);
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Apply spaced-repetition access updates to recalled rows and persist the
|
|
836
|
+
* updated retrieval metadata back to SQLite.
|
|
837
|
+
*/
|
|
838
|
+
_applyRecallAccessUpdates(rows) {
|
|
839
|
+
if (rows.length === 0)
|
|
840
|
+
return rows;
|
|
841
|
+
const now = Date.now();
|
|
842
|
+
const updateStmt = this._brain.db.prepare(`UPDATE memory_traces
|
|
843
|
+
SET strength = ?, last_accessed = ?, retrieval_count = ?, metadata = ?
|
|
844
|
+
WHERE id = ?`);
|
|
845
|
+
return this._brain.db.transaction(() => rows.map((row) => {
|
|
846
|
+
const update = updateOnRetrieval(this._buildTrace(row), now);
|
|
847
|
+
const metadata = JSON.stringify(withPersistedDecayState(parseTraceMetadata(row.metadata), {
|
|
848
|
+
stability: update.stability,
|
|
849
|
+
accessCount: update.accessCount,
|
|
850
|
+
reinforcementInterval: update.reinforcementInterval,
|
|
851
|
+
nextReinforcementAt: update.nextReinforcementAt,
|
|
852
|
+
}));
|
|
853
|
+
updateStmt.run(update.encodingStrength, update.lastAccessedAt, update.retrievalCount, metadata, row.id);
|
|
854
|
+
return {
|
|
855
|
+
...row,
|
|
856
|
+
strength: update.encodingStrength,
|
|
857
|
+
last_accessed: update.lastAccessedAt,
|
|
858
|
+
retrieval_count: update.retrievalCount,
|
|
859
|
+
metadata,
|
|
860
|
+
};
|
|
861
|
+
}))();
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Persist one loaded document into the documents/chunks/traces tables.
|
|
865
|
+
*
|
|
866
|
+
* Document-level dedup is keyed by `documents.content_hash`, so re-ingesting
|
|
867
|
+
* the same source content is idempotent.
|
|
868
|
+
*/
|
|
869
|
+
async _ingestLoadedDocument(source, doc, chunking, result) {
|
|
870
|
+
const contentHash = sha256(doc.content);
|
|
871
|
+
const existingDoc = this._brain.db
|
|
872
|
+
.prepare(`SELECT id FROM documents WHERE content_hash = ? LIMIT 1`)
|
|
873
|
+
.get(contentHash);
|
|
874
|
+
if (existingDoc) {
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
const chunks = await this._chunkingEngine.chunk(doc.content, chunking);
|
|
878
|
+
const docId = `doc_${Date.now()}_${_traceCounter++}`;
|
|
879
|
+
this._brain.db
|
|
880
|
+
.prepare(`INSERT INTO documents
|
|
881
|
+
(id, path, format, title, content_hash, chunk_count, metadata, ingested_at)
|
|
882
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
883
|
+
.run(docId, doc.metadata.source ?? source, doc.format, doc.metadata.title ?? null, contentHash, chunks.length, JSON.stringify(doc.metadata), Date.now());
|
|
884
|
+
for (const chunk of chunks) {
|
|
885
|
+
const chunkId = `chunk_${Date.now()}_${_traceCounter++}`;
|
|
886
|
+
const traceId = nextTraceId();
|
|
887
|
+
const createdAt = Date.now();
|
|
888
|
+
this._brain.db
|
|
889
|
+
.prepare(`INSERT INTO memory_traces
|
|
890
|
+
(id, type, scope, content, embedding, strength, created_at,
|
|
891
|
+
last_accessed, retrieval_count, tags, emotions, metadata, deleted)
|
|
892
|
+
VALUES (?, 'semantic', 'user', ?, NULL, 1.0, ?, NULL, 0, '[]', '{}', ?, 0)`)
|
|
893
|
+
.run(traceId, chunk.content, createdAt, JSON.stringify(buildInitialTraceMetadata({
|
|
894
|
+
document_id: docId,
|
|
895
|
+
chunk_index: chunk.index,
|
|
896
|
+
}, { contentHash: sha256(chunk.content) })));
|
|
897
|
+
this._brain.db
|
|
898
|
+
.prepare(`INSERT INTO memory_traces_fts (rowid, content, tags)
|
|
899
|
+
VALUES (
|
|
900
|
+
(SELECT rowid FROM memory_traces WHERE id = ?),
|
|
901
|
+
?,
|
|
902
|
+
'[]'
|
|
903
|
+
)`)
|
|
904
|
+
.run(traceId, chunk.content);
|
|
905
|
+
if (this._config.graph && !this._memoryGraph.hasNode(traceId)) {
|
|
906
|
+
await this._memoryGraph.addNode(traceId, {
|
|
907
|
+
type: 'semantic',
|
|
908
|
+
scope: 'user',
|
|
909
|
+
scopeId: docId,
|
|
910
|
+
strength: 1.0,
|
|
911
|
+
createdAt,
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
this._brain.db
|
|
915
|
+
.prepare(`INSERT INTO document_chunks (id, document_id, trace_id, content, chunk_index, page_number, embedding)
|
|
916
|
+
VALUES (?, ?, ?, ?, ?, ?, NULL)`)
|
|
917
|
+
.run(chunkId, docId, traceId, chunk.content, chunk.index, chunk.pageNumber ?? null);
|
|
918
|
+
result.chunksCreated++;
|
|
919
|
+
result.tracesCreated++;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Rebuild the external-content FTS index after bulk import operations.
|
|
924
|
+
*/
|
|
925
|
+
_rebuildFtsIndex() {
|
|
926
|
+
try {
|
|
927
|
+
this._brain.db.exec(`INSERT INTO memory_traces_fts(memory_traces_fts) VALUES('rebuild')`);
|
|
928
|
+
}
|
|
929
|
+
catch {
|
|
930
|
+
// Best-effort; imports still succeed even if the FTS rebuild is unavailable.
|
|
931
|
+
}
|
|
932
|
+
}
|
|
774
933
|
/**
|
|
775
934
|
* Detect the export format from options or file extension.
|
|
776
935
|
*/
|