@framers/agentos 0.1.101 → 0.1.103
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 +16 -0
- package/dist/api/agency.js +1 -1
- package/dist/api/agency.js.map +1 -1
- package/dist/api/strategies/graph.d.ts.map +1 -1
- package/dist/api/strategies/graph.js +1 -0
- package/dist/api/strategies/graph.js.map +1 -1
- package/dist/api/strategies/sequential.d.ts.map +1 -1
- package/dist/api/strategies/sequential.js +1 -0
- package/dist/api/strategies/sequential.js.map +1 -1
- package/dist/memory/config.d.ts +39 -0
- package/dist/memory/config.d.ts.map +1 -1
- package/dist/memory/config.js.map +1 -1
- package/dist/memory/consolidation/ConsolidationLoop.d.ts +177 -0
- package/dist/memory/consolidation/ConsolidationLoop.d.ts.map +1 -0
- package/dist/memory/consolidation/ConsolidationLoop.js +517 -0
- package/dist/memory/consolidation/ConsolidationLoop.js.map +1 -0
- package/dist/memory/consolidation/ConsolidationPipeline.d.ts.map +1 -1
- package/dist/memory/consolidation/ConsolidationPipeline.js +7 -0
- package/dist/memory/consolidation/ConsolidationPipeline.js.map +1 -1
- package/dist/memory/consolidation/index.d.ts +8 -0
- package/dist/memory/consolidation/index.d.ts.map +1 -0
- package/dist/memory/consolidation/index.js +7 -0
- package/dist/memory/consolidation/index.js.map +1 -0
- package/dist/memory/decay/DecayModel.d.ts +33 -0
- package/dist/memory/decay/DecayModel.d.ts.map +1 -1
- package/dist/memory/decay/DecayModel.js +31 -0
- package/dist/memory/decay/DecayModel.js.map +1 -1
- package/dist/memory/facade/Memory.d.ts +228 -0
- package/dist/memory/facade/Memory.d.ts.map +1 -0
- package/dist/memory/facade/Memory.js +823 -0
- package/dist/memory/facade/Memory.js.map +1 -0
- package/dist/memory/facade/index.d.ts +13 -0
- package/dist/memory/facade/index.d.ts.map +1 -0
- package/dist/memory/facade/index.js +11 -0
- package/dist/memory/facade/index.js.map +1 -0
- package/dist/memory/facade/types.d.ts +606 -0
- package/dist/memory/facade/types.d.ts.map +1 -0
- package/dist/memory/facade/types.js +11 -0
- package/dist/memory/facade/types.js.map +1 -0
- package/dist/memory/feedback/RetrievalFeedbackSignal.d.ts +132 -0
- package/dist/memory/feedback/RetrievalFeedbackSignal.d.ts.map +1 -0
- package/dist/memory/feedback/RetrievalFeedbackSignal.js +178 -0
- package/dist/memory/feedback/RetrievalFeedbackSignal.js.map +1 -0
- package/dist/memory/feedback/index.d.ts +13 -0
- package/dist/memory/feedback/index.d.ts.map +1 -0
- package/dist/memory/feedback/index.js +12 -0
- package/dist/memory/feedback/index.js.map +1 -0
- package/dist/memory/index.d.ts +22 -0
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +24 -0
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/ingestion/ChunkingEngine.d.ts +143 -0
- package/dist/memory/ingestion/ChunkingEngine.d.ts.map +1 -0
- package/dist/memory/ingestion/ChunkingEngine.js +508 -0
- package/dist/memory/ingestion/ChunkingEngine.js.map +1 -0
- package/dist/memory/ingestion/DoclingLoader.d.ts +44 -0
- package/dist/memory/ingestion/DoclingLoader.d.ts.map +1 -0
- package/dist/memory/ingestion/DoclingLoader.js +228 -0
- package/dist/memory/ingestion/DoclingLoader.js.map +1 -0
- package/dist/memory/ingestion/DocxLoader.d.ts +37 -0
- package/dist/memory/ingestion/DocxLoader.d.ts.map +1 -0
- package/dist/memory/ingestion/DocxLoader.js +111 -0
- package/dist/memory/ingestion/DocxLoader.js.map +1 -0
- package/dist/memory/ingestion/FolderScanner.d.ts +116 -0
- package/dist/memory/ingestion/FolderScanner.d.ts.map +1 -0
- package/dist/memory/ingestion/FolderScanner.js +127 -0
- package/dist/memory/ingestion/FolderScanner.js.map +1 -0
- package/dist/memory/ingestion/HtmlLoader.d.ts +49 -0
- package/dist/memory/ingestion/HtmlLoader.d.ts.map +1 -0
- package/dist/memory/ingestion/HtmlLoader.js +202 -0
- package/dist/memory/ingestion/HtmlLoader.js.map +1 -0
- package/dist/memory/ingestion/IDocumentLoader.d.ts +63 -0
- package/dist/memory/ingestion/IDocumentLoader.d.ts.map +1 -0
- package/dist/memory/ingestion/IDocumentLoader.js +11 -0
- package/dist/memory/ingestion/IDocumentLoader.js.map +1 -0
- package/dist/memory/ingestion/LoaderRegistry.d.ts +140 -0
- package/dist/memory/ingestion/LoaderRegistry.d.ts.map +1 -0
- package/dist/memory/ingestion/LoaderRegistry.js +229 -0
- package/dist/memory/ingestion/LoaderRegistry.js.map +1 -0
- package/dist/memory/ingestion/MarkdownLoader.d.ts +50 -0
- package/dist/memory/ingestion/MarkdownLoader.d.ts.map +1 -0
- package/dist/memory/ingestion/MarkdownLoader.js +169 -0
- package/dist/memory/ingestion/MarkdownLoader.js.map +1 -0
- package/dist/memory/ingestion/MultimodalAggregator.d.ts +88 -0
- package/dist/memory/ingestion/MultimodalAggregator.d.ts.map +1 -0
- package/dist/memory/ingestion/MultimodalAggregator.js +96 -0
- package/dist/memory/ingestion/MultimodalAggregator.js.map +1 -0
- package/dist/memory/ingestion/OcrPdfLoader.d.ts +41 -0
- package/dist/memory/ingestion/OcrPdfLoader.d.ts.map +1 -0
- package/dist/memory/ingestion/OcrPdfLoader.js +149 -0
- package/dist/memory/ingestion/OcrPdfLoader.js.map +1 -0
- package/dist/memory/ingestion/PdfLoader.d.ts +78 -0
- package/dist/memory/ingestion/PdfLoader.d.ts.map +1 -0
- package/dist/memory/ingestion/PdfLoader.js +179 -0
- package/dist/memory/ingestion/PdfLoader.js.map +1 -0
- package/dist/memory/ingestion/TextLoader.d.ts +66 -0
- package/dist/memory/ingestion/TextLoader.d.ts.map +1 -0
- package/dist/memory/ingestion/TextLoader.js +207 -0
- package/dist/memory/ingestion/TextLoader.js.map +1 -0
- package/dist/memory/ingestion/UrlLoader.d.ts +95 -0
- package/dist/memory/ingestion/UrlLoader.d.ts.map +1 -0
- package/dist/memory/ingestion/UrlLoader.js +174 -0
- package/dist/memory/ingestion/UrlLoader.js.map +1 -0
- package/dist/memory/io/ChatGptImporter.d.ts +85 -0
- package/dist/memory/io/ChatGptImporter.d.ts.map +1 -0
- package/dist/memory/io/ChatGptImporter.js +231 -0
- package/dist/memory/io/ChatGptImporter.js.map +1 -0
- package/dist/memory/io/JsonExporter.d.ts +67 -0
- package/dist/memory/io/JsonExporter.d.ts.map +1 -0
- package/dist/memory/io/JsonExporter.js +132 -0
- package/dist/memory/io/JsonExporter.js.map +1 -0
- package/dist/memory/io/JsonImporter.d.ts +84 -0
- package/dist/memory/io/JsonImporter.d.ts.map +1 -0
- package/dist/memory/io/JsonImporter.js +234 -0
- package/dist/memory/io/JsonImporter.js.map +1 -0
- package/dist/memory/io/MarkdownExporter.d.ts +95 -0
- package/dist/memory/io/MarkdownExporter.d.ts.map +1 -0
- package/dist/memory/io/MarkdownExporter.js +130 -0
- package/dist/memory/io/MarkdownExporter.js.map +1 -0
- package/dist/memory/io/MarkdownImporter.d.ts +84 -0
- package/dist/memory/io/MarkdownImporter.d.ts.map +1 -0
- package/dist/memory/io/MarkdownImporter.js +166 -0
- package/dist/memory/io/MarkdownImporter.js.map +1 -0
- package/dist/memory/io/ObsidianExporter.d.ts +80 -0
- package/dist/memory/io/ObsidianExporter.d.ts.map +1 -0
- package/dist/memory/io/ObsidianExporter.js +127 -0
- package/dist/memory/io/ObsidianExporter.js.map +1 -0
- package/dist/memory/io/ObsidianImporter.d.ts +93 -0
- package/dist/memory/io/ObsidianImporter.d.ts.map +1 -0
- package/dist/memory/io/ObsidianImporter.js +221 -0
- package/dist/memory/io/ObsidianImporter.js.map +1 -0
- package/dist/memory/io/SqliteExporter.d.ts +47 -0
- package/dist/memory/io/SqliteExporter.d.ts.map +1 -0
- package/dist/memory/io/SqliteExporter.js +56 -0
- package/dist/memory/io/SqliteExporter.js.map +1 -0
- package/dist/memory/io/SqliteImporter.d.ts +82 -0
- package/dist/memory/io/SqliteImporter.d.ts.map +1 -0
- package/dist/memory/io/SqliteImporter.js +232 -0
- package/dist/memory/io/SqliteImporter.js.map +1 -0
- package/dist/memory/io/index.d.ts +31 -0
- package/dist/memory/io/index.d.ts.map +1 -0
- package/dist/memory/io/index.js +31 -0
- package/dist/memory/io/index.js.map +1 -0
- package/dist/memory/store/SqliteBrain.d.ts +125 -0
- package/dist/memory/store/SqliteBrain.d.ts.map +1 -0
- package/dist/memory/store/SqliteBrain.js +407 -0
- package/dist/memory/store/SqliteBrain.js.map +1 -0
- package/dist/memory/store/SqliteKnowledgeGraph.d.ts +259 -0
- package/dist/memory/store/SqliteKnowledgeGraph.d.ts.map +1 -0
- package/dist/memory/store/SqliteKnowledgeGraph.js +1062 -0
- package/dist/memory/store/SqliteKnowledgeGraph.js.map +1 -0
- package/dist/memory/store/SqliteMemoryGraph.d.ts +251 -0
- package/dist/memory/store/SqliteMemoryGraph.d.ts.map +1 -0
- package/dist/memory/store/SqliteMemoryGraph.js +637 -0
- package/dist/memory/store/SqliteMemoryGraph.js.map +1 -0
- package/dist/memory/tools/MemoryAddTool.d.ts +98 -0
- package/dist/memory/tools/MemoryAddTool.d.ts.map +1 -0
- package/dist/memory/tools/MemoryAddTool.js +131 -0
- package/dist/memory/tools/MemoryAddTool.js.map +1 -0
- package/dist/memory/tools/MemoryDeleteTool.d.ts +83 -0
- package/dist/memory/tools/MemoryDeleteTool.d.ts.map +1 -0
- package/dist/memory/tools/MemoryDeleteTool.js +96 -0
- package/dist/memory/tools/MemoryDeleteTool.js.map +1 -0
- package/dist/memory/tools/MemoryMergeTool.d.ts +95 -0
- package/dist/memory/tools/MemoryMergeTool.d.ts.map +1 -0
- package/dist/memory/tools/MemoryMergeTool.js +164 -0
- package/dist/memory/tools/MemoryMergeTool.js.map +1 -0
- package/dist/memory/tools/MemoryReflectTool.d.ts +86 -0
- package/dist/memory/tools/MemoryReflectTool.d.ts.map +1 -0
- package/dist/memory/tools/MemoryReflectTool.js +102 -0
- package/dist/memory/tools/MemoryReflectTool.js.map +1 -0
- package/dist/memory/tools/MemorySearchTool.d.ts +117 -0
- package/dist/memory/tools/MemorySearchTool.d.ts.map +1 -0
- package/dist/memory/tools/MemorySearchTool.js +162 -0
- package/dist/memory/tools/MemorySearchTool.js.map +1 -0
- package/dist/memory/tools/MemoryUpdateTool.d.ts +92 -0
- package/dist/memory/tools/MemoryUpdateTool.d.ts.map +1 -0
- package/dist/memory/tools/MemoryUpdateTool.js +125 -0
- package/dist/memory/tools/MemoryUpdateTool.js.map +1 -0
- package/dist/memory/tools/index.d.ts +32 -0
- package/dist/memory/tools/index.d.ts.map +1 -0
- package/dist/memory/tools/index.js +26 -0
- package/dist/memory/tools/index.js.map +1 -0
- package/package.json +6 -1
|
@@ -0,0 +1,823 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Memory facade -- unified public API for the complete memory system.
|
|
3
|
+
*
|
|
4
|
+
* The `Memory` class wires together every subsystem built in Tasks 1-12:
|
|
5
|
+
* - SqliteBrain (unified SQLite connection with WAL, full schema)
|
|
6
|
+
* - SqliteKnowledgeGraph (IKnowledgeGraph backed by SQLite)
|
|
7
|
+
* - SqliteMemoryGraph (IMemoryGraph with spreading activation)
|
|
8
|
+
* - LoaderRegistry (document loaders: text, md, html, pdf, docx)
|
|
9
|
+
* - FolderScanner (recursive directory scanning)
|
|
10
|
+
* - ChunkingEngine (4 chunking strategies)
|
|
11
|
+
* - RetrievalFeedbackSignal (used/ignored detection)
|
|
12
|
+
* - ConsolidationLoop (6-step self-improvement)
|
|
13
|
+
* - I/O exporters and importers (JSON, Markdown, Obsidian, SQLite, ChatGPT)
|
|
14
|
+
*
|
|
15
|
+
* Consumers only need to import this single class to interact with the entire
|
|
16
|
+
* memory subsystem.
|
|
17
|
+
*
|
|
18
|
+
* @module memory/facade/Memory
|
|
19
|
+
*/
|
|
20
|
+
import crypto from 'node:crypto';
|
|
21
|
+
import fs from 'node:fs/promises';
|
|
22
|
+
import os from 'node:os';
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
import { SqliteBrain } from '../store/SqliteBrain.js';
|
|
25
|
+
import { SqliteKnowledgeGraph } from '../store/SqliteKnowledgeGraph.js';
|
|
26
|
+
import { SqliteMemoryGraph } from '../store/SqliteMemoryGraph.js';
|
|
27
|
+
import { LoaderRegistry } from '../ingestion/LoaderRegistry.js';
|
|
28
|
+
import { FolderScanner } from '../ingestion/FolderScanner.js';
|
|
29
|
+
import { ChunkingEngine } from '../ingestion/ChunkingEngine.js';
|
|
30
|
+
import { RetrievalFeedbackSignal } from '../feedback/RetrievalFeedbackSignal.js';
|
|
31
|
+
import { ConsolidationLoop } from '../consolidation/ConsolidationLoop.js';
|
|
32
|
+
import { JsonExporter, JsonImporter, MarkdownExporter, MarkdownImporter, ObsidianExporter, ObsidianImporter, SqliteExporter, SqliteImporter, ChatGptImporter, } from '../io/index.js';
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Constants & defaults
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
/** Monotonically increasing counter for trace IDs. */
|
|
37
|
+
let _traceCounter = 0;
|
|
38
|
+
/**
|
|
39
|
+
* Generate a unique, collision-free trace ID.
|
|
40
|
+
*
|
|
41
|
+
* Format: `mt_{timestamp}_{counter}` where the counter resets at process
|
|
42
|
+
* start but is never reused within a single process lifetime.
|
|
43
|
+
*/
|
|
44
|
+
function nextTraceId() {
|
|
45
|
+
return `mt_${Date.now()}_${_traceCounter++}`;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Compute SHA-256 hex digest of a string.
|
|
49
|
+
*/
|
|
50
|
+
function sha256(content) {
|
|
51
|
+
return crypto.createHash('sha256').update(content).digest('hex');
|
|
52
|
+
}
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Memory facade
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
/**
|
|
57
|
+
* Unified public API for the AgentOS memory system.
|
|
58
|
+
*
|
|
59
|
+
* One `Memory` instance manages the full lifecycle of an agent's memories:
|
|
60
|
+
* storing, retrieving, ingesting documents, building a knowledge graph,
|
|
61
|
+
* self-improving through consolidation, and importing/exporting data.
|
|
62
|
+
*
|
|
63
|
+
* ## Quick start
|
|
64
|
+
* ```ts
|
|
65
|
+
* const mem = new Memory({ store: 'sqlite', path: './brain.sqlite' });
|
|
66
|
+
*
|
|
67
|
+
* await mem.remember('The user prefers dark mode');
|
|
68
|
+
* const results = await mem.recall('dark mode preference');
|
|
69
|
+
* console.log(results[0].trace.content);
|
|
70
|
+
*
|
|
71
|
+
* await mem.close();
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export class Memory {
|
|
75
|
+
// -------------------------------------------------------------------
|
|
76
|
+
// Constructor
|
|
77
|
+
// -------------------------------------------------------------------
|
|
78
|
+
/**
|
|
79
|
+
* Create a new Memory instance and wire together all subsystems.
|
|
80
|
+
*
|
|
81
|
+
* Initialization sequence:
|
|
82
|
+
* 1. Merge `config` with defaults (store='sqlite', path=tmpdir, graph=true,
|
|
83
|
+
* selfImprove=true, decay=true).
|
|
84
|
+
* 2. Create `SqliteBrain(config.path)`.
|
|
85
|
+
* 3. Check embedding dimension compatibility (warn on mismatch).
|
|
86
|
+
* 4. Create `SqliteKnowledgeGraph(brain)`.
|
|
87
|
+
* 5. Create `SqliteMemoryGraph(brain)` and call `.initialize()`.
|
|
88
|
+
* 6. Create `LoaderRegistry()` (pre-registers all built-in loaders).
|
|
89
|
+
* 7. Create `FolderScanner(registry)`.
|
|
90
|
+
* 8. Create `ChunkingEngine()`.
|
|
91
|
+
* 9. If `selfImprove`: create `RetrievalFeedbackSignal(brain)` and
|
|
92
|
+
* `ConsolidationLoop(brain, memoryGraph)`.
|
|
93
|
+
*
|
|
94
|
+
* @param config - Optional configuration; see {@link MemoryConfig}.
|
|
95
|
+
*/
|
|
96
|
+
constructor(config) {
|
|
97
|
+
// Step 1: merge with defaults.
|
|
98
|
+
const randomSuffix = Math.random().toString(36).slice(2, 10);
|
|
99
|
+
this._config = {
|
|
100
|
+
store: 'sqlite',
|
|
101
|
+
path: path.join(os.tmpdir(), `brain-${randomSuffix}.sqlite`),
|
|
102
|
+
graph: true,
|
|
103
|
+
selfImprove: true,
|
|
104
|
+
decay: true,
|
|
105
|
+
...config,
|
|
106
|
+
};
|
|
107
|
+
// Step 2: create SqliteBrain.
|
|
108
|
+
this._brain = new SqliteBrain(this._config.path);
|
|
109
|
+
// Step 3: check embedding dimension compatibility.
|
|
110
|
+
const dimensions = this._config.embeddings?.dimensions ?? 1536;
|
|
111
|
+
const compatible = this._brain.checkEmbeddingCompat(dimensions);
|
|
112
|
+
if (!compatible) {
|
|
113
|
+
console.warn(`[Memory] Embedding dimension mismatch: expected ${dimensions} but brain ` +
|
|
114
|
+
`was previously configured with a different dimension. Vector similarity ` +
|
|
115
|
+
`searches may produce incorrect results.`);
|
|
116
|
+
}
|
|
117
|
+
// Step 4: create SqliteKnowledgeGraph.
|
|
118
|
+
this._knowledgeGraph = new SqliteKnowledgeGraph(this._brain);
|
|
119
|
+
// Step 5: create SqliteMemoryGraph and initialize.
|
|
120
|
+
this._memoryGraph = new SqliteMemoryGraph(this._brain);
|
|
121
|
+
this._initPromise = this._memoryGraph.initialize();
|
|
122
|
+
// Step 6: create LoaderRegistry.
|
|
123
|
+
this._loaderRegistry = new LoaderRegistry();
|
|
124
|
+
// Step 7: create FolderScanner.
|
|
125
|
+
this._folderScanner = new FolderScanner(this._loaderRegistry);
|
|
126
|
+
// Step 8: create ChunkingEngine.
|
|
127
|
+
this._chunkingEngine = new ChunkingEngine();
|
|
128
|
+
// Step 9: self-improvement subsystems.
|
|
129
|
+
if (this._config.selfImprove) {
|
|
130
|
+
this._feedbackSignal = new RetrievalFeedbackSignal(this._brain);
|
|
131
|
+
this._consolidationLoop = new ConsolidationLoop(this._brain, this._memoryGraph);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
this._feedbackSignal = null;
|
|
135
|
+
this._consolidationLoop = null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// =========================================================================
|
|
139
|
+
// Core memory operations
|
|
140
|
+
// =========================================================================
|
|
141
|
+
/**
|
|
142
|
+
* Store a new memory trace.
|
|
143
|
+
*
|
|
144
|
+
* Creates a trace in the `memory_traces` table with a unique ID, content
|
|
145
|
+
* hash for deduplication, and optional type/scope/tags metadata. If the
|
|
146
|
+
* memory graph is available the trace is also added as a graph node.
|
|
147
|
+
*
|
|
148
|
+
* @param content - The text content to remember.
|
|
149
|
+
* @param options - Optional metadata (type, scope, tags, importance, etc.).
|
|
150
|
+
* @returns The created MemoryTrace-like object.
|
|
151
|
+
*/
|
|
152
|
+
async remember(content, options) {
|
|
153
|
+
await this._initPromise;
|
|
154
|
+
const id = nextTraceId();
|
|
155
|
+
const now = Date.now();
|
|
156
|
+
const type = options?.type ?? 'episodic';
|
|
157
|
+
const scope = options?.scope ?? 'user';
|
|
158
|
+
const scopeId = options?.scopeId ?? '';
|
|
159
|
+
const tags = options?.tags ?? [];
|
|
160
|
+
const entities = options?.entities ?? [];
|
|
161
|
+
const importance = options?.importance ?? 1.0;
|
|
162
|
+
const contentHash = sha256(content);
|
|
163
|
+
// Insert into memory_traces.
|
|
164
|
+
this._brain.db
|
|
165
|
+
.prepare(`INSERT INTO memory_traces
|
|
166
|
+
(id, type, scope, content, embedding, strength, created_at,
|
|
167
|
+
last_accessed, retrieval_count, tags, emotions, metadata, deleted)
|
|
168
|
+
VALUES (?, ?, ?, ?, NULL, ?, ?, NULL, 0, ?, ?, ?, 0)`)
|
|
169
|
+
.run(id, type, scope, content, importance, now, JSON.stringify(tags), JSON.stringify({}), JSON.stringify({ content_hash: contentHash, entities, scopeId }));
|
|
170
|
+
// Sync FTS5 index. The external-content FTS5 table needs explicit insert.
|
|
171
|
+
this._brain.db
|
|
172
|
+
.prepare(`INSERT INTO memory_traces_fts (rowid, content, tags)
|
|
173
|
+
VALUES (
|
|
174
|
+
(SELECT rowid FROM memory_traces WHERE id = ?),
|
|
175
|
+
?,
|
|
176
|
+
?
|
|
177
|
+
)`)
|
|
178
|
+
.run(id, content, JSON.stringify(tags));
|
|
179
|
+
// Add to memory graph if available.
|
|
180
|
+
if (this._config.graph) {
|
|
181
|
+
await this._memoryGraph.addNode(id, {
|
|
182
|
+
type: type,
|
|
183
|
+
scope: scope,
|
|
184
|
+
scopeId,
|
|
185
|
+
strength: importance,
|
|
186
|
+
createdAt: now,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
// Build a MemoryTrace-shaped return value.
|
|
190
|
+
return this._buildTrace({
|
|
191
|
+
id,
|
|
192
|
+
type,
|
|
193
|
+
scope,
|
|
194
|
+
content,
|
|
195
|
+
embedding: null,
|
|
196
|
+
strength: importance,
|
|
197
|
+
created_at: now,
|
|
198
|
+
last_accessed: null,
|
|
199
|
+
retrieval_count: 0,
|
|
200
|
+
tags: JSON.stringify(tags),
|
|
201
|
+
emotions: JSON.stringify({}),
|
|
202
|
+
metadata: JSON.stringify({ content_hash: contentHash, entities, scopeId }),
|
|
203
|
+
deleted: 0,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Search for memory traces matching a natural-language query.
|
|
208
|
+
*
|
|
209
|
+
* Uses FTS5 full-text search with the Porter tokenizer. Results are ranked
|
|
210
|
+
* by `strength * abs(fts_rank)` and filtered by optional type/scope/strength
|
|
211
|
+
* criteria.
|
|
212
|
+
*
|
|
213
|
+
* @param query - Natural-language search query.
|
|
214
|
+
* @param options - Optional filters (limit, type, scope, minStrength).
|
|
215
|
+
* @returns Ranked array of {@link ScoredTrace} results.
|
|
216
|
+
*/
|
|
217
|
+
async recall(query, options) {
|
|
218
|
+
await this._initPromise;
|
|
219
|
+
const limit = options?.limit ?? 10;
|
|
220
|
+
const minStrength = options?.minStrength ?? 0;
|
|
221
|
+
// Build WHERE clause fragments for optional filters.
|
|
222
|
+
const conditions = ['t.deleted = 0'];
|
|
223
|
+
const params = [];
|
|
224
|
+
if (options?.type) {
|
|
225
|
+
conditions.push('t.type = ?');
|
|
226
|
+
params.push(options.type);
|
|
227
|
+
}
|
|
228
|
+
if (options?.scope) {
|
|
229
|
+
conditions.push('t.scope = ?');
|
|
230
|
+
params.push(options.scope);
|
|
231
|
+
}
|
|
232
|
+
if (minStrength > 0) {
|
|
233
|
+
conditions.push('t.strength >= ?');
|
|
234
|
+
params.push(minStrength);
|
|
235
|
+
}
|
|
236
|
+
const whereClause = conditions.length > 0
|
|
237
|
+
? `WHERE ${conditions.join(' AND ')}`
|
|
238
|
+
: '';
|
|
239
|
+
// FTS5 MATCH query joined with the main table.
|
|
240
|
+
// rank is negative (closer to 0 = better match), so we use abs().
|
|
241
|
+
const sql = `
|
|
242
|
+
SELECT t.*, fts.rank
|
|
243
|
+
FROM memory_traces_fts fts
|
|
244
|
+
JOIN memory_traces t ON t.rowid = fts.rowid
|
|
245
|
+
${whereClause}
|
|
246
|
+
AND memory_traces_fts MATCH ?
|
|
247
|
+
ORDER BY (t.strength * abs(fts.rank)) DESC
|
|
248
|
+
LIMIT ?
|
|
249
|
+
`;
|
|
250
|
+
params.push(query, limit);
|
|
251
|
+
const rows = this._brain.db
|
|
252
|
+
.prepare(sql)
|
|
253
|
+
.all(...params);
|
|
254
|
+
return rows.map((row) => ({
|
|
255
|
+
trace: this._buildTrace(row),
|
|
256
|
+
score: row.strength * Math.abs(row.rank),
|
|
257
|
+
}));
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Soft-delete a memory trace by setting `deleted = 1`.
|
|
261
|
+
*
|
|
262
|
+
* The trace remains in the database for audit/recovery purposes but is
|
|
263
|
+
* excluded from all recall queries and health reports.
|
|
264
|
+
*
|
|
265
|
+
* @param traceId - The ID of the trace to forget.
|
|
266
|
+
*/
|
|
267
|
+
async forget(traceId) {
|
|
268
|
+
await this._initPromise;
|
|
269
|
+
this._brain.db
|
|
270
|
+
.prepare('UPDATE memory_traces SET deleted = 1 WHERE id = ?')
|
|
271
|
+
.run(traceId);
|
|
272
|
+
}
|
|
273
|
+
// =========================================================================
|
|
274
|
+
// Document ingestion
|
|
275
|
+
// =========================================================================
|
|
276
|
+
/**
|
|
277
|
+
* Ingest documents from a file, directory, or URL.
|
|
278
|
+
*
|
|
279
|
+
* Workflow:
|
|
280
|
+
* 1. Detect source type (file, directory, or URL).
|
|
281
|
+
* 2. Load document(s) using the appropriate loader.
|
|
282
|
+
* 3. Chunk each document using the configured strategy.
|
|
283
|
+
* 4. For each chunk: insert into `document_chunks`, create a memory trace.
|
|
284
|
+
* 5. Record the document in the `documents` table.
|
|
285
|
+
*
|
|
286
|
+
* @param source - File path, directory path, or URL.
|
|
287
|
+
* @param options - Optional ingestion settings (recursive, include/exclude globs).
|
|
288
|
+
* @returns Summary of the ingestion run.
|
|
289
|
+
*/
|
|
290
|
+
async ingest(source, options) {
|
|
291
|
+
await this._initPromise;
|
|
292
|
+
const result = {
|
|
293
|
+
succeeded: [],
|
|
294
|
+
failed: [],
|
|
295
|
+
chunksCreated: 0,
|
|
296
|
+
tracesCreated: 0,
|
|
297
|
+
};
|
|
298
|
+
const chunkStrategy = this._config.ingestion?.chunkStrategy ?? 'semantic';
|
|
299
|
+
const chunkSize = this._config.ingestion?.chunkSize ?? 512;
|
|
300
|
+
const chunkOverlap = this._config.ingestion?.chunkOverlap ?? 64;
|
|
301
|
+
try {
|
|
302
|
+
// Detect source type.
|
|
303
|
+
const stat = await fs.stat(source).catch(() => null);
|
|
304
|
+
if (stat?.isDirectory()) {
|
|
305
|
+
// Directory scan.
|
|
306
|
+
const scanResult = await this._folderScanner.scan(source, {
|
|
307
|
+
recursive: options?.recursive ?? true,
|
|
308
|
+
include: options?.include,
|
|
309
|
+
exclude: options?.exclude,
|
|
310
|
+
onProgress: options?.onProgress
|
|
311
|
+
? (file, index, total) => options.onProgress(index, total, file)
|
|
312
|
+
: undefined,
|
|
313
|
+
});
|
|
314
|
+
result.succeeded.push(...scanResult.succeeded);
|
|
315
|
+
result.failed.push(...scanResult.failed);
|
|
316
|
+
// Chunk and store each loaded document.
|
|
317
|
+
for (const doc of scanResult.documents) {
|
|
318
|
+
const chunks = await this._chunkingEngine.chunk(doc.content, {
|
|
319
|
+
strategy: chunkStrategy,
|
|
320
|
+
chunkSize,
|
|
321
|
+
chunkOverlap,
|
|
322
|
+
});
|
|
323
|
+
const docId = `doc_${Date.now()}_${_traceCounter++}`;
|
|
324
|
+
const contentHash = sha256(doc.content);
|
|
325
|
+
// Insert document record.
|
|
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++;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
else if (stat?.isFile()) {
|
|
367
|
+
// Single file.
|
|
368
|
+
try {
|
|
369
|
+
const doc = await this._loaderRegistry.loadFile(source);
|
|
370
|
+
result.succeeded.push(source);
|
|
371
|
+
const chunks = await this._chunkingEngine.chunk(doc.content, {
|
|
372
|
+
strategy: chunkStrategy,
|
|
373
|
+
chunkSize,
|
|
374
|
+
chunkOverlap,
|
|
375
|
+
});
|
|
376
|
+
const docId = `doc_${Date.now()}_${_traceCounter++}`;
|
|
377
|
+
const contentHash = sha256(doc.content);
|
|
378
|
+
this._brain.db
|
|
379
|
+
.prepare(`INSERT OR IGNORE INTO documents
|
|
380
|
+
(id, path, format, title, content_hash, chunk_count, metadata, ingested_at)
|
|
381
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
382
|
+
.run(docId, source, doc.format, doc.metadata.title ?? null, contentHash, chunks.length, JSON.stringify(doc.metadata), Date.now());
|
|
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
|
+
}
|
|
414
|
+
}
|
|
415
|
+
catch (err) {
|
|
416
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
417
|
+
result.failed.push({ path: source, error: message });
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
// URL or unknown source -- treat as unsupported for now.
|
|
422
|
+
result.failed.push({
|
|
423
|
+
path: source,
|
|
424
|
+
error: `Source "${source}" is not a file or directory.`,
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
catch (err) {
|
|
429
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
430
|
+
result.failed.push({ path: source, error: message });
|
|
431
|
+
}
|
|
432
|
+
return result;
|
|
433
|
+
}
|
|
434
|
+
// =========================================================================
|
|
435
|
+
// Knowledge graph
|
|
436
|
+
// =========================================================================
|
|
437
|
+
/**
|
|
438
|
+
* Add or update an entity in the knowledge graph.
|
|
439
|
+
*
|
|
440
|
+
* Delegates to `SqliteKnowledgeGraph.upsertEntity()`. Accepts a partial
|
|
441
|
+
* entity; `id`, `createdAt`, and `updatedAt` are auto-generated when omitted.
|
|
442
|
+
*
|
|
443
|
+
* @param entity - Partial entity descriptor.
|
|
444
|
+
* @returns The complete, persisted entity.
|
|
445
|
+
*/
|
|
446
|
+
async addEntity(entity) {
|
|
447
|
+
await this._initPromise;
|
|
448
|
+
return this._knowledgeGraph.upsertEntity({
|
|
449
|
+
type: entity.type ?? 'concept',
|
|
450
|
+
label: entity.label ?? '',
|
|
451
|
+
properties: entity.properties ?? {},
|
|
452
|
+
confidence: entity.confidence ?? 1.0,
|
|
453
|
+
source: entity.source ?? {
|
|
454
|
+
type: 'system',
|
|
455
|
+
timestamp: new Date().toISOString(),
|
|
456
|
+
},
|
|
457
|
+
embedding: entity.embedding,
|
|
458
|
+
ownerId: entity.ownerId,
|
|
459
|
+
tags: entity.tags,
|
|
460
|
+
metadata: entity.metadata,
|
|
461
|
+
...(entity.id ? { id: entity.id } : {}),
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Add or update a relation (edge) in the knowledge graph.
|
|
466
|
+
*
|
|
467
|
+
* Delegates to `SqliteKnowledgeGraph.upsertRelation()`. Accepts a partial
|
|
468
|
+
* relation; `id` and `createdAt` are auto-generated when omitted.
|
|
469
|
+
*
|
|
470
|
+
* @param relation - Partial relation descriptor.
|
|
471
|
+
* @returns The complete, persisted relation.
|
|
472
|
+
*/
|
|
473
|
+
async addRelation(relation) {
|
|
474
|
+
await this._initPromise;
|
|
475
|
+
return this._knowledgeGraph.upsertRelation({
|
|
476
|
+
sourceId: relation.sourceId ?? '',
|
|
477
|
+
targetId: relation.targetId ?? '',
|
|
478
|
+
type: relation.type ?? 'related_to',
|
|
479
|
+
label: relation.label ?? '',
|
|
480
|
+
weight: relation.weight ?? 1.0,
|
|
481
|
+
bidirectional: relation.bidirectional ?? false,
|
|
482
|
+
confidence: relation.confidence ?? 1.0,
|
|
483
|
+
source: relation.source ?? {
|
|
484
|
+
type: 'system',
|
|
485
|
+
timestamp: new Date().toISOString(),
|
|
486
|
+
},
|
|
487
|
+
properties: relation.properties,
|
|
488
|
+
validFrom: relation.validFrom,
|
|
489
|
+
validTo: relation.validTo,
|
|
490
|
+
...(relation.id ? { id: relation.id } : {}),
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Access the underlying IKnowledgeGraph implementation.
|
|
495
|
+
*
|
|
496
|
+
* Useful for advanced queries (traversal, semantic search, neighbourhood
|
|
497
|
+
* lookups) that are not exposed on the facade directly.
|
|
498
|
+
*/
|
|
499
|
+
get graph() {
|
|
500
|
+
return this._knowledgeGraph;
|
|
501
|
+
}
|
|
502
|
+
// =========================================================================
|
|
503
|
+
// Self-improvement
|
|
504
|
+
// =========================================================================
|
|
505
|
+
/**
|
|
506
|
+
* Run one consolidation cycle (prune, merge, strengthen, derive, compact,
|
|
507
|
+
* re-index).
|
|
508
|
+
*
|
|
509
|
+
* @param options - Optional topic filter (reserved for future use).
|
|
510
|
+
* @returns Statistics from the consolidation run.
|
|
511
|
+
* @throws {Error} When `selfImprove` was set to `false` in the config.
|
|
512
|
+
*/
|
|
513
|
+
async consolidate(options) {
|
|
514
|
+
await this._initPromise;
|
|
515
|
+
if (!this._consolidationLoop) {
|
|
516
|
+
throw new Error('Memory.consolidate(): self-improvement is disabled. ' +
|
|
517
|
+
'Set selfImprove: true in the MemoryConfig to enable consolidation.');
|
|
518
|
+
}
|
|
519
|
+
return this._consolidationLoop.run(this._config.consolidation);
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Record retrieval feedback for a memory trace.
|
|
523
|
+
*
|
|
524
|
+
* Fire-and-forget: the feedback is persisted asynchronously and this method
|
|
525
|
+
* returns immediately without waiting for the write to complete.
|
|
526
|
+
*
|
|
527
|
+
* @param traceId - The ID of the trace being evaluated.
|
|
528
|
+
* @param signal - Whether the trace was `'used'` or `'ignored'` by the LLM.
|
|
529
|
+
*/
|
|
530
|
+
feedback(traceId, signal) {
|
|
531
|
+
if (!this._feedbackSignal)
|
|
532
|
+
return;
|
|
533
|
+
// Fire-and-forget: insert feedback row without awaiting.
|
|
534
|
+
this._brain.db
|
|
535
|
+
.prepare(`INSERT INTO retrieval_feedback (trace_id, signal, query, created_at)
|
|
536
|
+
VALUES (?, ?, NULL, ?)`)
|
|
537
|
+
.run(traceId, signal, Date.now());
|
|
538
|
+
}
|
|
539
|
+
// =========================================================================
|
|
540
|
+
// Import / Export
|
|
541
|
+
// =========================================================================
|
|
542
|
+
/**
|
|
543
|
+
* Export the memory store to a file or directory.
|
|
544
|
+
*
|
|
545
|
+
* Format is detected from `options.format` or the file extension:
|
|
546
|
+
* - `.json` -> JSON
|
|
547
|
+
* - `.sqlite` / `.db` -> SQLite file copy
|
|
548
|
+
* - directory path -> Markdown or Obsidian (based on `options.format`)
|
|
549
|
+
*
|
|
550
|
+
* @param outputPath - Path to write the export to.
|
|
551
|
+
* @param options - Optional format and content controls.
|
|
552
|
+
*/
|
|
553
|
+
async export(outputPath, options) {
|
|
554
|
+
await this._initPromise;
|
|
555
|
+
const format = this._detectExportFormat(outputPath, options);
|
|
556
|
+
switch (format) {
|
|
557
|
+
case 'json': {
|
|
558
|
+
const exporter = new JsonExporter(this._brain);
|
|
559
|
+
await exporter.export(outputPath, options);
|
|
560
|
+
break;
|
|
561
|
+
}
|
|
562
|
+
case 'markdown': {
|
|
563
|
+
const exporter = new MarkdownExporter(this._brain);
|
|
564
|
+
await exporter.export(outputPath, options);
|
|
565
|
+
break;
|
|
566
|
+
}
|
|
567
|
+
case 'obsidian': {
|
|
568
|
+
const exporter = new ObsidianExporter(this._brain);
|
|
569
|
+
await exporter.export(outputPath, options);
|
|
570
|
+
break;
|
|
571
|
+
}
|
|
572
|
+
case 'sqlite': {
|
|
573
|
+
const exporter = new SqliteExporter(this._brain);
|
|
574
|
+
await exporter.export(outputPath, options);
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
default: {
|
|
578
|
+
throw new Error(`Memory.export(): unsupported format "${format}".`);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Import memory data from a file or directory.
|
|
584
|
+
*
|
|
585
|
+
* Format is detected from `options.format`, the file extension, or by
|
|
586
|
+
* inspecting the content.
|
|
587
|
+
*
|
|
588
|
+
* @param source - Path to the import source (file or directory).
|
|
589
|
+
* @param options - Optional format hint and dedup settings.
|
|
590
|
+
* @returns Summary of the import operation.
|
|
591
|
+
*/
|
|
592
|
+
async importFrom(source, options) {
|
|
593
|
+
await this._initPromise;
|
|
594
|
+
const format = await this._detectImportFormat(source, options);
|
|
595
|
+
switch (format) {
|
|
596
|
+
case 'json': {
|
|
597
|
+
const importer = new JsonImporter(this._brain);
|
|
598
|
+
return importer.import(source);
|
|
599
|
+
}
|
|
600
|
+
case 'markdown': {
|
|
601
|
+
const importer = new MarkdownImporter(this._brain);
|
|
602
|
+
return importer.import(source);
|
|
603
|
+
}
|
|
604
|
+
case 'obsidian': {
|
|
605
|
+
const importer = new ObsidianImporter(this._brain);
|
|
606
|
+
return importer.import(source);
|
|
607
|
+
}
|
|
608
|
+
case 'sqlite': {
|
|
609
|
+
const importer = new SqliteImporter(this._brain);
|
|
610
|
+
return importer.import(source);
|
|
611
|
+
}
|
|
612
|
+
case 'chatgpt': {
|
|
613
|
+
const importer = new ChatGptImporter(this._brain);
|
|
614
|
+
return importer.import(source);
|
|
615
|
+
}
|
|
616
|
+
default: {
|
|
617
|
+
return { imported: 0, skipped: 0, errors: [`Unsupported import format: "${format}"`] };
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
// =========================================================================
|
|
622
|
+
// Health
|
|
623
|
+
// =========================================================================
|
|
624
|
+
/**
|
|
625
|
+
* Return a health snapshot of the memory store.
|
|
626
|
+
*
|
|
627
|
+
* Queries aggregate statistics from all tables and returns a
|
|
628
|
+
* {@link MemoryHealth} report.
|
|
629
|
+
*/
|
|
630
|
+
async health() {
|
|
631
|
+
await this._initPromise;
|
|
632
|
+
const db = this._brain.db;
|
|
633
|
+
// Total traces (active + deleted).
|
|
634
|
+
const totalRow = db
|
|
635
|
+
.prepare('SELECT COUNT(*) AS cnt FROM memory_traces')
|
|
636
|
+
.get();
|
|
637
|
+
const totalTraces = totalRow?.cnt ?? 0;
|
|
638
|
+
// Active traces (not deleted).
|
|
639
|
+
const activeRow = db
|
|
640
|
+
.prepare('SELECT COUNT(*) AS cnt FROM memory_traces WHERE deleted = 0')
|
|
641
|
+
.get();
|
|
642
|
+
const activeTraces = activeRow?.cnt ?? 0;
|
|
643
|
+
// Average strength of active traces.
|
|
644
|
+
const avgRow = db
|
|
645
|
+
.prepare('SELECT AVG(strength) AS avg_s FROM memory_traces WHERE deleted = 0')
|
|
646
|
+
.get();
|
|
647
|
+
const avgStrength = avgRow?.avg_s ?? 0;
|
|
648
|
+
// Weakest active trace.
|
|
649
|
+
const weakRow = db
|
|
650
|
+
.prepare('SELECT MIN(strength) AS min_s FROM memory_traces WHERE deleted = 0')
|
|
651
|
+
.get();
|
|
652
|
+
const weakestTraceStrength = weakRow?.min_s ?? 0;
|
|
653
|
+
// Knowledge graph counts.
|
|
654
|
+
const nodesRow = db
|
|
655
|
+
.prepare('SELECT COUNT(*) AS cnt FROM knowledge_nodes')
|
|
656
|
+
.get();
|
|
657
|
+
const graphNodes = nodesRow?.cnt ?? 0;
|
|
658
|
+
const edgesRow = db
|
|
659
|
+
.prepare('SELECT COUNT(*) AS cnt FROM knowledge_edges')
|
|
660
|
+
.get();
|
|
661
|
+
const graphEdges = edgesRow?.cnt ?? 0;
|
|
662
|
+
// Last consolidation timestamp.
|
|
663
|
+
const lastConsolRow = db
|
|
664
|
+
.prepare('SELECT ran_at FROM consolidation_log ORDER BY ran_at DESC LIMIT 1')
|
|
665
|
+
.get();
|
|
666
|
+
const lastConsolidation = lastConsolRow
|
|
667
|
+
? new Date(lastConsolRow.ran_at).toISOString()
|
|
668
|
+
: null;
|
|
669
|
+
// Traces per type.
|
|
670
|
+
const typeRows = db
|
|
671
|
+
.prepare('SELECT type, COUNT(*) AS cnt FROM memory_traces WHERE deleted = 0 GROUP BY type')
|
|
672
|
+
.all();
|
|
673
|
+
const tracesPerType = {};
|
|
674
|
+
for (const row of typeRows) {
|
|
675
|
+
tracesPerType[row.type] = row.cnt;
|
|
676
|
+
}
|
|
677
|
+
// Traces per scope.
|
|
678
|
+
const scopeRows = db
|
|
679
|
+
.prepare('SELECT scope, COUNT(*) AS cnt FROM memory_traces WHERE deleted = 0 GROUP BY scope')
|
|
680
|
+
.all();
|
|
681
|
+
const tracesPerScope = {};
|
|
682
|
+
for (const row of scopeRows) {
|
|
683
|
+
tracesPerScope[row.scope] = row.cnt;
|
|
684
|
+
}
|
|
685
|
+
// Total document chunks.
|
|
686
|
+
const docsRow = db
|
|
687
|
+
.prepare('SELECT COUNT(*) AS cnt FROM documents')
|
|
688
|
+
.get();
|
|
689
|
+
const documentsIngested = docsRow?.cnt ?? 0;
|
|
690
|
+
return {
|
|
691
|
+
totalTraces,
|
|
692
|
+
activeTraces,
|
|
693
|
+
avgStrength,
|
|
694
|
+
weakestTraceStrength,
|
|
695
|
+
graphNodes,
|
|
696
|
+
graphEdges,
|
|
697
|
+
lastConsolidation,
|
|
698
|
+
tracesPerType,
|
|
699
|
+
tracesPerScope,
|
|
700
|
+
documentsIngested,
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
// =========================================================================
|
|
704
|
+
// Lifecycle
|
|
705
|
+
// =========================================================================
|
|
706
|
+
/**
|
|
707
|
+
* Close the Memory instance and release all resources.
|
|
708
|
+
*
|
|
709
|
+
* Flushes the SQLite WAL and releases the file lock. Must be called when
|
|
710
|
+
* the agent shuts down.
|
|
711
|
+
*/
|
|
712
|
+
async close() {
|
|
713
|
+
await this._initPromise;
|
|
714
|
+
this._brain.close();
|
|
715
|
+
}
|
|
716
|
+
// =========================================================================
|
|
717
|
+
// Private helpers
|
|
718
|
+
// =========================================================================
|
|
719
|
+
/**
|
|
720
|
+
* Convert a raw `memory_traces` row into a `MemoryTrace` object.
|
|
721
|
+
*/
|
|
722
|
+
_buildTrace(row) {
|
|
723
|
+
let tags = [];
|
|
724
|
+
try {
|
|
725
|
+
tags = JSON.parse(row.tags);
|
|
726
|
+
}
|
|
727
|
+
catch { /* empty */ }
|
|
728
|
+
let emotions = {};
|
|
729
|
+
try {
|
|
730
|
+
emotions = JSON.parse(row.emotions);
|
|
731
|
+
}
|
|
732
|
+
catch { /* empty */ }
|
|
733
|
+
let metadata = {};
|
|
734
|
+
try {
|
|
735
|
+
metadata = JSON.parse(row.metadata);
|
|
736
|
+
}
|
|
737
|
+
catch { /* empty */ }
|
|
738
|
+
const entities = Array.isArray(metadata.entities) ? metadata.entities : [];
|
|
739
|
+
const scopeId = typeof metadata.scopeId === 'string' ? metadata.scopeId : '';
|
|
740
|
+
return {
|
|
741
|
+
id: row.id,
|
|
742
|
+
type: row.type,
|
|
743
|
+
scope: row.scope,
|
|
744
|
+
scopeId,
|
|
745
|
+
content: row.content,
|
|
746
|
+
entities,
|
|
747
|
+
tags,
|
|
748
|
+
provenance: {
|
|
749
|
+
sourceType: 'user_statement',
|
|
750
|
+
sourceTimestamp: row.created_at,
|
|
751
|
+
confidence: 1.0,
|
|
752
|
+
verificationCount: 0,
|
|
753
|
+
},
|
|
754
|
+
emotionalContext: {
|
|
755
|
+
valence: 0,
|
|
756
|
+
arousal: 0,
|
|
757
|
+
dominance: 0,
|
|
758
|
+
intensity: 0,
|
|
759
|
+
gmiMood: 'neutral',
|
|
760
|
+
...emotions,
|
|
761
|
+
},
|
|
762
|
+
encodingStrength: row.strength,
|
|
763
|
+
stability: 86400000, // 1 day default
|
|
764
|
+
retrievalCount: row.retrieval_count,
|
|
765
|
+
lastAccessedAt: row.last_accessed ?? row.created_at,
|
|
766
|
+
accessCount: row.retrieval_count,
|
|
767
|
+
reinforcementInterval: 86400000,
|
|
768
|
+
associatedTraceIds: [],
|
|
769
|
+
createdAt: row.created_at,
|
|
770
|
+
updatedAt: row.created_at,
|
|
771
|
+
isActive: row.deleted === 0,
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Detect the export format from options or file extension.
|
|
776
|
+
*/
|
|
777
|
+
_detectExportFormat(outputPath, options) {
|
|
778
|
+
if (options?.format)
|
|
779
|
+
return options.format;
|
|
780
|
+
const ext = path.extname(outputPath).toLowerCase();
|
|
781
|
+
switch (ext) {
|
|
782
|
+
case '.json': return 'json';
|
|
783
|
+
case '.sqlite':
|
|
784
|
+
case '.db': return 'sqlite';
|
|
785
|
+
default: return 'markdown';
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Detect the import format from options, file extension, or content inspection.
|
|
790
|
+
*/
|
|
791
|
+
async _detectImportFormat(source, options) {
|
|
792
|
+
if (options?.format && options.format !== 'auto')
|
|
793
|
+
return options.format;
|
|
794
|
+
const ext = path.extname(source).toLowerCase();
|
|
795
|
+
switch (ext) {
|
|
796
|
+
case '.json': {
|
|
797
|
+
// Check if it looks like a ChatGPT export.
|
|
798
|
+
try {
|
|
799
|
+
const head = await fs.readFile(source, { encoding: 'utf8', flag: 'r' });
|
|
800
|
+
if (head.includes('"mapping"') && head.includes('"conversation_id"')) {
|
|
801
|
+
return 'chatgpt';
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
catch { /* fall through */ }
|
|
805
|
+
return 'json';
|
|
806
|
+
}
|
|
807
|
+
case '.sqlite':
|
|
808
|
+
case '.db': return 'sqlite';
|
|
809
|
+
case '.csv': return 'csv';
|
|
810
|
+
default: {
|
|
811
|
+
// Check if source is a directory.
|
|
812
|
+
try {
|
|
813
|
+
const stat = await fs.stat(source);
|
|
814
|
+
if (stat.isDirectory())
|
|
815
|
+
return 'markdown';
|
|
816
|
+
}
|
|
817
|
+
catch { /* fall through */ }
|
|
818
|
+
return 'json';
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
//# sourceMappingURL=Memory.js.map
|