vektor-slipstream 1.0.2 → 1.0.3
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/package.json +1 -1
- package/slipstream-core.js +560 -192
package/package.json
CHANGED
package/slipstream-core.js
CHANGED
|
@@ -2,50 +2,191 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* VEKTOR SLIPSTREAM
|
|
5
|
-
* slipstream-core.js —
|
|
5
|
+
* slipstream-core.js — Resilient Self-Contained Build
|
|
6
6
|
* ─────────────────────────────────────────────────────────────────────────────
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* inference pipeline.
|
|
7
|
+
* Drop-in replacement for the ONNX-dependent version.
|
|
8
|
+
* Same public API: createMemory({ agentId, dbPath, silent })
|
|
10
9
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* Embedding strategy (best available, in order):
|
|
11
|
+
* 1. @xenova/transformers — full transformer embeddings, pure JS, no ONNX native
|
|
12
|
+
* 2. Hash-projection — deterministic 384-dim vector, zero deps, instant boot
|
|
13
13
|
*
|
|
14
|
-
*
|
|
14
|
+
* SQLite strategy (best available, in order):
|
|
15
|
+
* 1. better-sqlite3 — native, fast
|
|
16
|
+
* 2. Falls back with clear error message
|
|
17
|
+
*
|
|
18
|
+
* All other files (slipstream-db, slipstream-embedder, detect-hardware) are
|
|
19
|
+
* inlined here — nothing to require().
|
|
15
20
|
* ─────────────────────────────────────────────────────────────────────────────
|
|
16
21
|
*/
|
|
17
22
|
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
const path = require('path');
|
|
24
|
+
const fs = require('fs');
|
|
25
|
+
|
|
26
|
+
// ─── SQLite loader — better-sqlite3 with clear error ─────────────────────────
|
|
27
|
+
function loadSQLite(dbPath) {
|
|
28
|
+
try {
|
|
29
|
+
const Database = require('better-sqlite3');
|
|
30
|
+
const db = new Database(dbPath);
|
|
31
|
+
|
|
32
|
+
// Hyper-PRAGMAs — same as original slipstream-db.js
|
|
33
|
+
db.pragma('journal_mode = WAL');
|
|
34
|
+
db.pragma('synchronous = NORMAL');
|
|
35
|
+
db.pragma('cache_size = -65536'); // 64 MB
|
|
36
|
+
db.pragma('mmap_size = 1073741824'); // 1 GB
|
|
37
|
+
db.pragma('temp_store = MEMORY');
|
|
38
|
+
db.pragma('page_size = 4096');
|
|
39
|
+
|
|
40
|
+
return db;
|
|
41
|
+
} catch (e) {
|
|
42
|
+
if (e.code === 'ERR_DLOPEN_FAILED' || e.message.includes('NODE_MODULE_VERSION')) {
|
|
43
|
+
console.error('');
|
|
44
|
+
console.error(' ✗ better-sqlite3 binary mismatch (compiled for different Node version)');
|
|
45
|
+
console.error(' → Fix: npm rebuild better-sqlite3');
|
|
46
|
+
console.error(' → Then restart the server');
|
|
47
|
+
console.error('');
|
|
48
|
+
throw new Error('better-sqlite3 rebuild required: npm rebuild better-sqlite3');
|
|
49
|
+
}
|
|
50
|
+
throw e;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
22
53
|
|
|
23
|
-
// ───
|
|
54
|
+
// ─── Embedding Engine ─────────────────────────────────────────────────────────
|
|
55
|
+
// Strategy 1: @xenova/transformers (pure JS, no native deps)
|
|
56
|
+
// Strategy 2: Hash-projection (zero deps, deterministic, always works)
|
|
57
|
+
|
|
58
|
+
const EMBED_DIM = 384;
|
|
59
|
+
|
|
60
|
+
// Hash-projection embedder — deterministic 384-dim float32 vector
|
|
61
|
+
// Uses FNV-1a family of hashes over character n-grams
|
|
62
|
+
// Sufficient for keyword recall; upgrade to xenova for semantic search
|
|
63
|
+
function hashEmbed(text) {
|
|
64
|
+
const vec = new Float32Array(EMBED_DIM);
|
|
65
|
+
const tokens = text.toLowerCase()
|
|
66
|
+
.replace(/[^\w\s]/g, ' ')
|
|
67
|
+
.split(/\s+/)
|
|
68
|
+
.filter(t => t.length > 1);
|
|
69
|
+
|
|
70
|
+
// Add character bigrams and trigrams for sub-word coverage
|
|
71
|
+
const allGrams = [...tokens];
|
|
72
|
+
for (const tok of tokens) {
|
|
73
|
+
for (let i = 0; i < tok.length - 1; i++) allGrams.push(tok.slice(i, i + 2));
|
|
74
|
+
for (let i = 0; i < tok.length - 2; i++) allGrams.push(tok.slice(i, i + 3));
|
|
75
|
+
}
|
|
24
76
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
77
|
+
for (const gram of allGrams) {
|
|
78
|
+
// FNV-1a 32-bit over the gram characters
|
|
79
|
+
let h = 2166136261;
|
|
80
|
+
for (let i = 0; i < gram.length; i++) {
|
|
81
|
+
h ^= gram.charCodeAt(i);
|
|
82
|
+
h = (h * 16777619) >>> 0;
|
|
83
|
+
}
|
|
84
|
+
// Project into two dimensions for richer coverage
|
|
85
|
+
const idx1 = h % EMBED_DIM;
|
|
86
|
+
const idx2 = (h >>> 16) % EMBED_DIM;
|
|
87
|
+
const sign = (h & 1) ? 1 : -1;
|
|
88
|
+
vec[idx1] += sign;
|
|
89
|
+
vec[idx2] += sign * 0.5;
|
|
90
|
+
}
|
|
29
91
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
92
|
+
// L2 normalize
|
|
93
|
+
let norm = 0;
|
|
94
|
+
for (let i = 0; i < EMBED_DIM; i++) norm += vec[i] * vec[i];
|
|
95
|
+
norm = Math.sqrt(norm) + 1e-10;
|
|
96
|
+
for (let i = 0; i < EMBED_DIM; i++) vec[i] /= norm;
|
|
97
|
+
|
|
98
|
+
return vec;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Xenova embedder — lazy-loaded, falls back to hash if unavailable
|
|
102
|
+
let _xenovaPipeline = null;
|
|
103
|
+
let _xenovaFailed = false;
|
|
104
|
+
let _xenovaLoading = false;
|
|
105
|
+
let _xenovaQueue = [];
|
|
106
|
+
|
|
107
|
+
async function xenovaEmbed(text) {
|
|
108
|
+
if (_xenovaFailed) return null;
|
|
109
|
+
|
|
110
|
+
// If pipeline is ready, use it
|
|
111
|
+
if (_xenovaPipeline) {
|
|
112
|
+
try {
|
|
113
|
+
const output = await _xenovaPipeline(text, { pooling: 'mean', normalize: true });
|
|
114
|
+
return new Float32Array(output.data);
|
|
115
|
+
} catch (e) {
|
|
116
|
+
console.warn('[slipstream] xenova embed failed:', e.message);
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// If loading, queue this call
|
|
122
|
+
if (_xenovaLoading) {
|
|
123
|
+
return new Promise((resolve) => {
|
|
124
|
+
_xenovaQueue.push({ text, resolve });
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Start loading
|
|
129
|
+
_xenovaLoading = true;
|
|
130
|
+
try {
|
|
131
|
+
const { pipeline, env } = require('@xenova/transformers');
|
|
132
|
+
// Use local cache, don't require internet
|
|
133
|
+
env.allowLocalModels = true;
|
|
134
|
+
env.allowRemoteModels = true;
|
|
135
|
+
env.cacheDir = path.join(require('os').homedir(), '.cache', 'xenova');
|
|
136
|
+
|
|
137
|
+
console.log('[slipstream] Loading @xenova/transformers (first run may download model ~25MB)...');
|
|
138
|
+
_xenovaPipeline = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2', {
|
|
139
|
+
quantized: true, // INT8 — matches original slipstream spec
|
|
140
|
+
});
|
|
141
|
+
console.log('[slipstream] ✓ Semantic embeddings active (all-MiniLM-L6-v2 INT8)');
|
|
142
|
+
|
|
143
|
+
// Drain the queue
|
|
144
|
+
_xenovaLoading = false;
|
|
145
|
+
const queued = [..._xenovaQueue];
|
|
146
|
+
_xenovaQueue = [];
|
|
147
|
+
for (const item of queued) {
|
|
148
|
+
try {
|
|
149
|
+
const out = await _xenovaPipeline(item.text, { pooling: 'mean', normalize: true });
|
|
150
|
+
item.resolve(new Float32Array(out.data));
|
|
151
|
+
} catch (_) { item.resolve(null); }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Now embed the original text
|
|
155
|
+
const output = await _xenovaPipeline(text, { pooling: 'mean', normalize: true });
|
|
156
|
+
return new Float32Array(output.data);
|
|
157
|
+
|
|
158
|
+
} catch (e) {
|
|
159
|
+
_xenovaFailed = true;
|
|
160
|
+
_xenovaLoading = false;
|
|
161
|
+
// Drain queue with null
|
|
162
|
+
for (const item of _xenovaQueue) item.resolve(null);
|
|
163
|
+
_xenovaQueue = [];
|
|
164
|
+
if (e.code !== 'MODULE_NOT_FOUND') {
|
|
165
|
+
console.warn('[slipstream] @xenova/transformers failed:', e.message);
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Unified embed — tries xenova, falls back to hash
|
|
172
|
+
async function embed(text) {
|
|
173
|
+
const xVec = await xenovaEmbed(text);
|
|
174
|
+
if (xVec) return xVec;
|
|
175
|
+
return hashEmbed(text);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function getEmbedderStats() {
|
|
179
|
+
const mode = _xenovaPipeline ? 'xenova/all-MiniLM-L6-v2 INT8' : 'hash-projection (384-dim)';
|
|
180
|
+
const ep = _xenovaPipeline ? 'CPU·ONNX' : 'CPU·Hash';
|
|
181
|
+
return { mode, ep, epLabel: ep, avgMs: _xenovaPipeline ? '~20' : '<1', dim: EMBED_DIM };
|
|
42
182
|
}
|
|
43
183
|
|
|
44
184
|
// ─── Vector Utilities ─────────────────────────────────────────────────────────
|
|
45
185
|
|
|
46
186
|
function cosineSimilarity(a, b) {
|
|
47
187
|
let dot = 0, normA = 0, normB = 0;
|
|
48
|
-
|
|
188
|
+
const len = Math.min(a.length, b.length);
|
|
189
|
+
for (let i = 0; i < len; i++) {
|
|
49
190
|
dot += a[i] * b[i];
|
|
50
191
|
normA += a[i] * a[i];
|
|
51
192
|
normB += b[i] * b[i];
|
|
@@ -58,7 +199,10 @@ function serializeVector(float32Array) {
|
|
|
58
199
|
}
|
|
59
200
|
|
|
60
201
|
function deserializeVector(buffer) {
|
|
61
|
-
|
|
202
|
+
if (!buffer || buffer.length < 4) return new Float32Array(0);
|
|
203
|
+
return new Float32Array(
|
|
204
|
+
buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength)
|
|
205
|
+
);
|
|
62
206
|
}
|
|
63
207
|
|
|
64
208
|
// ─── Schema ───────────────────────────────────────────────────────────────────
|
|
@@ -67,9 +211,12 @@ function initSchema(db) {
|
|
|
67
211
|
db.exec(`
|
|
68
212
|
CREATE TABLE IF NOT EXISTS memories (
|
|
69
213
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
70
|
-
agent_id TEXT NOT NULL,
|
|
214
|
+
agent_id TEXT NOT NULL DEFAULT 'default',
|
|
71
215
|
content TEXT NOT NULL,
|
|
216
|
+
edge_type TEXT DEFAULT 'semantic',
|
|
72
217
|
summary TEXT,
|
|
218
|
+
tags TEXT DEFAULT '',
|
|
219
|
+
source_agent TEXT DEFAULT 'system',
|
|
73
220
|
importance REAL DEFAULT 1.0,
|
|
74
221
|
vector BLOB,
|
|
75
222
|
created_at INTEGER DEFAULT (strftime('%s', 'now')),
|
|
@@ -78,262 +225,483 @@ function initSchema(db) {
|
|
|
78
225
|
|
|
79
226
|
CREATE INDEX IF NOT EXISTS idx_memories_agent
|
|
80
227
|
ON memories(agent_id);
|
|
81
|
-
|
|
228
|
+
CREATE INDEX IF NOT EXISTS idx_memories_type
|
|
229
|
+
ON memories(edge_type);
|
|
82
230
|
CREATE INDEX IF NOT EXISTS idx_memories_importance
|
|
83
231
|
ON memories(agent_id, importance DESC);
|
|
232
|
+
CREATE INDEX IF NOT EXISTS idx_memories_created
|
|
233
|
+
ON memories(created_at DESC);
|
|
84
234
|
|
|
85
235
|
CREATE TABLE IF NOT EXISTS memory_edges (
|
|
86
236
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
87
|
-
agent_id TEXT NOT NULL,
|
|
237
|
+
agent_id TEXT NOT NULL DEFAULT 'default',
|
|
88
238
|
source_id INTEGER NOT NULL,
|
|
89
239
|
target_id INTEGER NOT NULL,
|
|
90
|
-
edge_type TEXT NOT NULL,
|
|
240
|
+
edge_type TEXT NOT NULL DEFAULT 'related',
|
|
91
241
|
weight REAL DEFAULT 1.0,
|
|
92
242
|
created_at INTEGER DEFAULT (strftime('%s', 'now')),
|
|
93
|
-
FOREIGN KEY (source_id) REFERENCES memories(id),
|
|
94
|
-
FOREIGN KEY (target_id) REFERENCES memories(id)
|
|
243
|
+
FOREIGN KEY (source_id) REFERENCES memories(id) ON DELETE CASCADE,
|
|
244
|
+
FOREIGN KEY (target_id) REFERENCES memories(id) ON DELETE CASCADE
|
|
95
245
|
);
|
|
96
246
|
|
|
97
247
|
CREATE INDEX IF NOT EXISTS idx_edges_agent
|
|
98
248
|
ON memory_edges(agent_id, edge_type);
|
|
249
|
+
|
|
250
|
+
CREATE TABLE IF NOT EXISTS agent_capabilities (
|
|
251
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
252
|
+
agent_id TEXT NOT NULL,
|
|
253
|
+
engine_id TEXT NOT NULL,
|
|
254
|
+
label TEXT,
|
|
255
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
256
|
+
UNIQUE(agent_id, engine_id)
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
CREATE TABLE IF NOT EXISTS directives (
|
|
260
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
261
|
+
title TEXT NOT NULL,
|
|
262
|
+
description TEXT DEFAULT '',
|
|
263
|
+
status TEXT DEFAULT 'BACKLOG',
|
|
264
|
+
priority TEXT DEFAULT 'MED',
|
|
265
|
+
agent TEXT DEFAULT '',
|
|
266
|
+
project_id INTEGER DEFAULT NULL,
|
|
267
|
+
result TEXT,
|
|
268
|
+
created_at INTEGER DEFAULT (strftime('%s','now')*1000),
|
|
269
|
+
updated_at INTEGER DEFAULT (strftime('%s','now')*1000)
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
CREATE TABLE IF NOT EXISTS hitl_queue (
|
|
273
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
274
|
+
directive_id TEXT,
|
|
275
|
+
agent TEXT,
|
|
276
|
+
action TEXT,
|
|
277
|
+
status TEXT DEFAULT 'PENDING',
|
|
278
|
+
result TEXT,
|
|
279
|
+
meta TEXT,
|
|
280
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
281
|
+
);
|
|
99
282
|
`);
|
|
283
|
+
|
|
284
|
+
// Safe migrations — add columns that may not exist in older DBs
|
|
285
|
+
const migrations = [
|
|
286
|
+
"ALTER TABLE memories ADD COLUMN edge_type TEXT DEFAULT 'semantic'",
|
|
287
|
+
"ALTER TABLE memories ADD COLUMN tags TEXT DEFAULT ''",
|
|
288
|
+
"ALTER TABLE memories ADD COLUMN source_agent TEXT DEFAULT 'system'",
|
|
289
|
+
"ALTER TABLE directives ADD COLUMN result TEXT",
|
|
290
|
+
"ALTER TABLE directives ADD COLUMN description TEXT DEFAULT ''",
|
|
291
|
+
"ALTER TABLE hitl_queue ADD COLUMN action TEXT",
|
|
292
|
+
];
|
|
293
|
+
for (const m of migrations) {
|
|
294
|
+
try { db.exec(m); } catch (_) { /* column already exists — safe */ }
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ─── AUDN Contradiction Detector ─────────────────────────────────────────────
|
|
299
|
+
// Returns true if newText likely contradicts existingText.
|
|
300
|
+
// Three signals: negation words, opposing numeric values, reversal phrases.
|
|
301
|
+
|
|
302
|
+
const NEGATION_WORDS = new Set([
|
|
303
|
+
'not','no','never','none','cannot','cant',"can't",'wont',"won't",
|
|
304
|
+
'isnt',"isn't",'arent',"aren't",'wasnt',"wasn't",'doesnt',"doesn't",
|
|
305
|
+
'didnt',"didn't",'shouldnt',"shouldn't",'false','incorrect','wrong',
|
|
306
|
+
'opposite','contrary','denied','declined','rejected',
|
|
307
|
+
]);
|
|
308
|
+
|
|
309
|
+
const REVERSAL_PHRASES = [
|
|
310
|
+
/no longer/i, /used to/i, /changed (to|from)/i, /now (is|are|was)\b/i,
|
|
311
|
+
/instead of/i, /rather than/i, /replaced by/i, /switched (to|from)/i,
|
|
312
|
+
/corrected to/i, /updated to/i, /actually/i, /in fact/i,
|
|
313
|
+
];
|
|
314
|
+
|
|
315
|
+
function _tokenize(text) {
|
|
316
|
+
return text.toLowerCase().replace(/[^\w\s]/g, ' ').split(/\s+/).filter(t => t.length > 1);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function _extractNumbers(text) {
|
|
320
|
+
return (text.match(/-?\d+(?:\.\d+)?/g) || []).map(Number);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function _isContradiction(newText, existingText) {
|
|
324
|
+
const newTok = _tokenize(newText);
|
|
325
|
+
const exTok = _tokenize(existingText);
|
|
326
|
+
|
|
327
|
+
// Signal 1: shared keywords + negation polarity mismatch
|
|
328
|
+
const sharedKeywords = newTok.filter(t => exTok.includes(t) && t.length > 3);
|
|
329
|
+
if (sharedKeywords.length > 0) {
|
|
330
|
+
const newNegated = newTok.some(t => NEGATION_WORDS.has(t));
|
|
331
|
+
const exNegated = exTok.some(t => NEGATION_WORDS.has(t));
|
|
332
|
+
if (newNegated !== exNegated) return true;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Signal 2: opposing numeric values on same subject (>20% change)
|
|
336
|
+
const newNums = _extractNumbers(newText);
|
|
337
|
+
const exNums = _extractNumbers(existingText);
|
|
338
|
+
if (newNums.length && exNums.length && sharedKeywords.length > 0) {
|
|
339
|
+
const exVal = exNums[0];
|
|
340
|
+
if (exVal !== 0 && Math.abs(newNums[0] - exVal) / Math.abs(exVal) > 0.20) return true;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Signal 3: explicit reversal language in new text
|
|
344
|
+
if (REVERSAL_PHRASES.some(p => p.test(newText))) return true;
|
|
345
|
+
|
|
346
|
+
return false;
|
|
100
347
|
}
|
|
101
348
|
|
|
102
349
|
// ─── SlipstreamMemory ─────────────────────────────────────────────────────────
|
|
103
350
|
|
|
104
351
|
class SlipstreamMemory {
|
|
105
|
-
constructor(db,
|
|
106
|
-
this.db
|
|
107
|
-
this.
|
|
108
|
-
|
|
352
|
+
constructor(db, agentId) {
|
|
353
|
+
this.db = db;
|
|
354
|
+
this.agentId = agentId;
|
|
355
|
+
// Expose embedder stats for boot banner
|
|
356
|
+
this.embedder = { getStats: getEmbedderStats };
|
|
357
|
+
this.ep = getEmbedderStats().ep;
|
|
358
|
+
this.model = getEmbedderStats().mode;
|
|
109
359
|
}
|
|
110
360
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
361
|
+
// ── remember(text, opts) — AUDN router ───────────────────────────────────
|
|
362
|
+
// Routes each incoming memory through:
|
|
363
|
+
// NO_OP — exact duplicate, discard
|
|
364
|
+
// UPDATE — high similarity + contradiction signal, update existing
|
|
365
|
+
// ADD — genuinely new, insert
|
|
366
|
+
//
|
|
367
|
+
// Thresholds (tunable via opts):
|
|
368
|
+
// NO_OP : cosine >= 0.98 (near-identical wording)
|
|
369
|
+
// UPDATE : cosine >= 0.82 AND contradiction score > 0
|
|
370
|
+
// ADD : everything else
|
|
371
|
+
|
|
119
372
|
async remember(text, opts = {}) {
|
|
120
|
-
const
|
|
121
|
-
const
|
|
122
|
-
|
|
373
|
+
const NO_OP_THRESHOLD = opts.noop_threshold ?? 0.98;
|
|
374
|
+
const UPDATE_THRESHOLD = opts.update_threshold ?? 0.82;
|
|
375
|
+
|
|
376
|
+
const vec = await embed(text);
|
|
377
|
+
const vectorBlob = serializeVector(vec);
|
|
378
|
+
const importance = Number(opts.importance) || 1;
|
|
379
|
+
const edgeType = opts.type || opts.edge_type || 'semantic';
|
|
380
|
+
const tags = Array.isArray(opts.tags) ? opts.tags.join(',') : (opts.tags || '');
|
|
381
|
+
const sourceAgent = opts.agent || opts.source_agent || this.agentId;
|
|
382
|
+
|
|
383
|
+
// ── Pull top candidates by cosine similarity ──────────────────────────
|
|
384
|
+
const candidates = this.db.prepare(`
|
|
385
|
+
SELECT id, content, edge_type, importance, vector
|
|
386
|
+
FROM memories
|
|
387
|
+
WHERE agent_id = ? AND vector IS NOT NULL
|
|
388
|
+
ORDER BY importance DESC, created_at DESC
|
|
389
|
+
LIMIT 200
|
|
390
|
+
`).all(this.agentId);
|
|
123
391
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
VALUES (?, ?, ?, ?)
|
|
127
|
-
`);
|
|
392
|
+
let bestScore = 0;
|
|
393
|
+
let bestRow = null;
|
|
128
394
|
|
|
129
|
-
|
|
130
|
-
|
|
395
|
+
for (const row of candidates) {
|
|
396
|
+
const rowVec = deserializeVector(row.vector);
|
|
397
|
+
if (!rowVec.length) continue;
|
|
398
|
+
const score = cosineSimilarity(vec, rowVec);
|
|
399
|
+
if (score > bestScore) { bestScore = score; bestRow = row; }
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// ── NO_OP — near-identical wording ───────────────────────────────────
|
|
403
|
+
if (bestScore >= NO_OP_THRESHOLD) {
|
|
404
|
+
return { action: 'NO_OP', id: bestRow.id, score: bestScore, reason: 'exact duplicate' };
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// ── UPDATE — similar + contradiction detected ─────────────────────────
|
|
408
|
+
if (bestScore >= UPDATE_THRESHOLD && bestRow && _isContradiction(text, bestRow.content)) {
|
|
409
|
+
const newImportance = Math.max(bestRow.importance, importance);
|
|
410
|
+
this.db.prepare(`
|
|
411
|
+
UPDATE memories
|
|
412
|
+
SET content = ?, vector = ?, importance = ?, source_agent = ?,
|
|
413
|
+
updated_at = strftime('%s','now')
|
|
414
|
+
WHERE id = ?
|
|
415
|
+
`).run(text, vectorBlob, newImportance, sourceAgent, bestRow.id);
|
|
416
|
+
|
|
417
|
+
return {
|
|
418
|
+
action: 'UPDATE',
|
|
419
|
+
id: bestRow.id,
|
|
420
|
+
score: bestScore,
|
|
421
|
+
previous: bestRow.content.slice(0, 120),
|
|
422
|
+
reason: 'contradiction detected',
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// ── DELETE — high confidence contradiction + low importance ──────────────
|
|
427
|
+
// When the old memory is low-importance AND the contradiction is strong,
|
|
428
|
+
// it's safer to delete and re-add than to update in place.
|
|
429
|
+
// Threshold: score >= 0.90 AND importance <= 1 AND contradiction detected.
|
|
430
|
+
const DELETE_THRESHOLD = opts.delete_threshold ?? 0.90;
|
|
431
|
+
if (
|
|
432
|
+
bestScore >= DELETE_THRESHOLD &&
|
|
433
|
+
bestRow &&
|
|
434
|
+
(bestRow.importance || 1) <= 1 &&
|
|
435
|
+
_isContradiction(text, bestRow.content)
|
|
436
|
+
) {
|
|
437
|
+
this.db.prepare('DELETE FROM memories WHERE id = ?').run(bestRow.id);
|
|
438
|
+
// Remove from HNSW index
|
|
439
|
+
_hnswRemoveVector(this.agentId, bestRow.id);
|
|
440
|
+
|
|
441
|
+
// Re-insert as new with elevated importance (it replaced something)
|
|
442
|
+
const result = this.db.prepare(`
|
|
443
|
+
INSERT INTO memories (agent_id, content, edge_type, tags, source_agent, vector, importance)
|
|
444
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
445
|
+
`).run(this.agentId, text, edgeType, tags, sourceAgent, vectorBlob, Math.max(importance, 2));
|
|
446
|
+
|
|
447
|
+
_hnswAddVector(this.db, this.agentId, result.lastInsertRowid, vec);
|
|
448
|
+
|
|
449
|
+
return {
|
|
450
|
+
action: 'DELETE',
|
|
451
|
+
id: result.lastInsertRowid,
|
|
452
|
+
deleted: bestRow.id,
|
|
453
|
+
score: bestScore,
|
|
454
|
+
previous: bestRow.content.slice(0, 120),
|
|
455
|
+
reason: 'high-confidence contradiction — old memory removed',
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// ── ADD — genuinely new ───────────────────────────────────────────────
|
|
460
|
+
const result = this.db.prepare(`
|
|
461
|
+
INSERT INTO memories (agent_id, content, edge_type, tags, source_agent, vector, importance)
|
|
462
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
463
|
+
`).run(this.agentId, text, edgeType, tags, sourceAgent, vectorBlob, importance);
|
|
464
|
+
|
|
465
|
+
return { action: 'ADD', id: result.lastInsertRowid };
|
|
131
466
|
}
|
|
132
467
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
* Semantic recall using cosine similarity over stored vectors.
|
|
136
|
-
* Runs entirely inside SQLite — no external vector DB required.
|
|
137
|
-
*
|
|
138
|
-
* @param {string} query
|
|
139
|
-
* @param {number} topK
|
|
140
|
-
* @returns {Promise<Array<{id, content, importance, score}>>}
|
|
141
|
-
*/
|
|
468
|
+
// ── recall(query, topK) ────────────────────────────────────────────────────
|
|
469
|
+
// Semantic cosine similarity + keyword fallback
|
|
142
470
|
async recall(query, topK = 5) {
|
|
143
|
-
const
|
|
471
|
+
const queryVec = await embed(query);
|
|
144
472
|
|
|
145
|
-
// Pull
|
|
473
|
+
// Pull top 500 by importance (mmap makes this fast)
|
|
146
474
|
const rows = this.db.prepare(`
|
|
147
|
-
SELECT id, content, summary, importance, vector
|
|
475
|
+
SELECT id, content, summary, edge_type, importance, vector
|
|
148
476
|
FROM memories
|
|
149
477
|
WHERE agent_id = ?
|
|
150
478
|
AND vector IS NOT NULL
|
|
151
|
-
ORDER BY importance DESC
|
|
479
|
+
ORDER BY importance DESC, created_at DESC
|
|
152
480
|
LIMIT 500
|
|
153
481
|
`).all(this.agentId);
|
|
154
482
|
|
|
155
|
-
|
|
483
|
+
if (!rows.length) {
|
|
484
|
+
// Nothing stored yet — try keyword fallback
|
|
485
|
+
return this._keywordRecall(query, topK);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Score: cosine × importance
|
|
156
489
|
const scored = rows
|
|
157
490
|
.map(row => {
|
|
158
491
|
const vec = deserializeVector(row.vector);
|
|
159
|
-
const score = cosineSimilarity(
|
|
160
|
-
return {
|
|
161
|
-
id : row.id,
|
|
162
|
-
content : row.content,
|
|
163
|
-
summary : row.summary,
|
|
164
|
-
importance : row.importance,
|
|
165
|
-
score : Math.round(score * 100) / 100,
|
|
166
|
-
};
|
|
492
|
+
const score = vec.length > 0 ? cosineSimilarity(queryVec, vec) : 0;
|
|
493
|
+
return { id: row.id, content: row.content, summary: row.summary, edge_type: row.edge_type, importance: row.importance, score: Math.round(score * 100) / 100 };
|
|
167
494
|
})
|
|
168
495
|
.sort((a, b) => (b.score * b.importance) - (a.score * a.importance))
|
|
169
496
|
.slice(0, topK);
|
|
170
497
|
|
|
498
|
+
// If top scores are very low, blend in keyword results
|
|
499
|
+
if (scored[0]?.score < 0.15) {
|
|
500
|
+
const kw = this._keywordRecall(query, topK);
|
|
501
|
+
const seen = new Set(scored.map(r => r.id));
|
|
502
|
+
for (const r of kw) { if (!seen.has(r.id)) scored.push(r); }
|
|
503
|
+
return scored.slice(0, topK);
|
|
504
|
+
}
|
|
505
|
+
|
|
171
506
|
return scored;
|
|
172
507
|
}
|
|
173
508
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
509
|
+
// Keyword fallback — used when vectors are cold or similarity is weak
|
|
510
|
+
_keywordRecall(query, limit = 5) {
|
|
511
|
+
const STOP = new Set(['the','and','for','are','was','has','its','you','your','just','also','more','some','any','all','can','get','this','that','with','from','have','will','what','when','where','which','there','their','about','would','could','should']);
|
|
512
|
+
const words = query.toLowerCase().replace(/[^\w\s]/g,' ').split(/\s+/).filter(w => w.length > 2 && !STOP.has(w));
|
|
513
|
+
|
|
514
|
+
if (!words.length) {
|
|
515
|
+
// Return most recent
|
|
516
|
+
return this.db.prepare('SELECT id, content, edge_type, importance, 0.5 as score FROM memories WHERE agent_id=? ORDER BY created_at DESC LIMIT ?').all(this.agentId, limit);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Search each keyword, merge and deduplicate
|
|
520
|
+
const seen = new Set();
|
|
521
|
+
const results = [];
|
|
522
|
+
for (const word of words.slice(0, 5)) {
|
|
523
|
+
const rows = this.db.prepare("SELECT id, content, edge_type, importance, 0.5 as score FROM memories WHERE agent_id=? AND content LIKE ? ORDER BY importance DESC LIMIT 5").all(this.agentId, '%' + word + '%');
|
|
524
|
+
for (const r of rows) { if (!seen.has(r.id)) { seen.add(r.id); results.push(r); } }
|
|
525
|
+
}
|
|
526
|
+
return results.slice(0, limit);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// ── graph(concept, opts) ──────────────────────────────────────────────────
|
|
182
530
|
async graph(concept, opts = {}) {
|
|
183
|
-
const hops
|
|
184
|
-
const topK
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const
|
|
188
|
-
const
|
|
189
|
-
const nodes = [...seeds];
|
|
190
|
-
const edges = [];
|
|
531
|
+
const hops = opts.hops ?? 2;
|
|
532
|
+
const topK = opts.topK ?? 3;
|
|
533
|
+
const seeds = await this.recall(concept, topK);
|
|
534
|
+
const visited = new Set(seeds.map(s => s.id));
|
|
535
|
+
const nodes = [...seeds];
|
|
536
|
+
const edges = [];
|
|
191
537
|
let frontier = seeds.map(s => s.id);
|
|
192
538
|
|
|
193
|
-
for (let hop = 0; hop < hops; hop++) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const placeholders = frontier.map(() => '?').join(',');
|
|
539
|
+
for (let hop = 0; hop < hops && frontier.length; hop++) {
|
|
540
|
+
const ph = frontier.map(() => '?').join(',');
|
|
197
541
|
const edgeRows = this.db.prepare(`
|
|
198
|
-
SELECT source_id, target_id, edge_type, weight
|
|
199
|
-
|
|
200
|
-
WHERE agent_id = ?
|
|
201
|
-
AND (source_id IN (${placeholders}) OR target_id IN (${placeholders}))
|
|
542
|
+
SELECT source_id, target_id, edge_type, weight FROM memory_edges
|
|
543
|
+
WHERE agent_id = ? AND (source_id IN (${ph}) OR target_id IN (${ph}))
|
|
202
544
|
`).all(this.agentId, ...frontier, ...frontier);
|
|
203
545
|
|
|
204
546
|
const nextIds = [];
|
|
205
547
|
for (const edge of edgeRows) {
|
|
206
548
|
edges.push(edge);
|
|
207
|
-
for (const
|
|
208
|
-
if (!visited.has(
|
|
209
|
-
visited.add(nodeId);
|
|
210
|
-
nextIds.push(nodeId);
|
|
211
|
-
}
|
|
549
|
+
for (const nid of [edge.source_id, edge.target_id]) {
|
|
550
|
+
if (!visited.has(nid)) { visited.add(nid); nextIds.push(nid); }
|
|
212
551
|
}
|
|
213
552
|
}
|
|
214
553
|
|
|
215
554
|
if (nextIds.length) {
|
|
216
|
-
const
|
|
217
|
-
const rows = this.db.prepare(`
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
WHERE agent_id = ? AND id IN (${ph})
|
|
221
|
-
`).all(this.agentId, ...nextIds);
|
|
222
|
-
nodes.push(...rows);
|
|
223
|
-
frontier = nextIds;
|
|
224
|
-
} else {
|
|
225
|
-
break;
|
|
226
|
-
}
|
|
555
|
+
const ph2 = nextIds.map(() => '?').join(',');
|
|
556
|
+
const rows = this.db.prepare(`SELECT id, content, summary, importance FROM memories WHERE agent_id=? AND id IN (${ph2})`).all(this.agentId, ...nextIds);
|
|
557
|
+
nodes.push(...rows); frontier = nextIds;
|
|
558
|
+
} else { break; }
|
|
227
559
|
}
|
|
228
560
|
|
|
229
561
|
return { nodes, edges };
|
|
230
562
|
}
|
|
231
563
|
|
|
232
|
-
|
|
233
|
-
* delta(topic, days)
|
|
234
|
-
* Returns memories added or updated in the last N days on a topic.
|
|
235
|
-
*
|
|
236
|
-
* @param {string} topic
|
|
237
|
-
* @param {number} days
|
|
238
|
-
* @returns {Promise<Array>}
|
|
239
|
-
*/
|
|
564
|
+
// ── delta(topic, days) ────────────────────────────────────────────────────
|
|
240
565
|
async delta(topic, days = 7) {
|
|
241
|
-
const since
|
|
242
|
-
|
|
566
|
+
const since = Math.floor(Date.now() / 1000) - (days * 86400);
|
|
243
567
|
const recent = this.db.prepare(`
|
|
244
|
-
SELECT id, content, summary, importance, created_at, updated_at
|
|
245
|
-
|
|
246
|
-
WHERE agent_id = ?
|
|
247
|
-
AND updated_at >= ?
|
|
248
|
-
ORDER BY updated_at DESC
|
|
249
|
-
LIMIT 100
|
|
568
|
+
SELECT id, content, summary, importance, created_at, updated_at FROM memories
|
|
569
|
+
WHERE agent_id=? AND updated_at >= ? ORDER BY updated_at DESC LIMIT 100
|
|
250
570
|
`).all(this.agentId, since);
|
|
251
571
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
// Quick text match score (no vector needed for delta, but nice to have)
|
|
257
|
-
const textScore = row.content.toLowerCase().includes(topic.toLowerCase()) ? 0.1 : 0;
|
|
258
|
-
return { ...row, relevance: textScore };
|
|
259
|
-
})
|
|
260
|
-
.sort((a, b) => b.updated_at - a.updated_at);
|
|
572
|
+
return recent.map(row => ({
|
|
573
|
+
...row,
|
|
574
|
+
relevance: row.content.toLowerCase().includes(topic.toLowerCase()) ? 0.8 : 0.1,
|
|
575
|
+
})).sort((a, b) => b.updated_at - a.updated_at);
|
|
261
576
|
}
|
|
262
577
|
|
|
263
|
-
|
|
264
|
-
* briefing()
|
|
265
|
-
* Summary of everything learned in the last 24 hours.
|
|
266
|
-
* Inject into system prompt at session start.
|
|
267
|
-
*
|
|
268
|
-
* @returns {Promise<string>}
|
|
269
|
-
*/
|
|
578
|
+
// ── briefing() ────────────────────────────────────────────────────────────
|
|
270
579
|
async briefing() {
|
|
271
|
-
const since
|
|
272
|
-
|
|
580
|
+
const since = Math.floor(Date.now() / 1000) - 86400;
|
|
273
581
|
const recent = this.db.prepare(`
|
|
274
|
-
SELECT content, importance
|
|
275
|
-
|
|
276
|
-
WHERE agent_id = ?
|
|
277
|
-
AND created_at >= ?
|
|
278
|
-
ORDER BY importance DESC
|
|
279
|
-
LIMIT 20
|
|
582
|
+
SELECT content, importance FROM memories
|
|
583
|
+
WHERE agent_id=? AND created_at>=? ORDER BY importance DESC LIMIT 20
|
|
280
584
|
`).all(this.agentId, since);
|
|
281
585
|
|
|
282
586
|
if (!recent.length) return 'No new memories in the last 24 hours.';
|
|
283
|
-
|
|
284
587
|
return [
|
|
285
588
|
`[SLIPSTREAM BRIEFING — last 24h — ${recent.length} memories]`,
|
|
286
589
|
...recent.map((r, i) => `${i + 1}. ${r.content}`),
|
|
287
590
|
].join('\n');
|
|
288
591
|
}
|
|
592
|
+
|
|
593
|
+
// ── stats() — used by server-index.js countsByType ────────────────────────
|
|
594
|
+
stats() {
|
|
595
|
+
try {
|
|
596
|
+
const byType = this.db.prepare("SELECT edge_type as type, COUNT(*) as n FROM memories GROUP BY edge_type").all();
|
|
597
|
+
const total = this.db.prepare("SELECT COUNT(*) as n FROM memories").get()?.n || 0;
|
|
598
|
+
const by_type = {};
|
|
599
|
+
for (const r of byType) by_type[r.type || 'semantic'] = r.n;
|
|
600
|
+
return { total, by_type };
|
|
601
|
+
} catch (_) { return { total: 0, by_type: {} }; }
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// ── store() — compatibility alias for mem.store() calls ───────────────────
|
|
605
|
+
async store(content, opts = {}) {
|
|
606
|
+
return this.remember(content, opts);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// ── recent(opts) — returns most recent memories ───────────────────────────
|
|
610
|
+
recent(opts = {}) {
|
|
611
|
+
const limit = opts.limit || 20;
|
|
612
|
+
const type = opts.type || null;
|
|
613
|
+
try {
|
|
614
|
+
if (type) {
|
|
615
|
+
return this.db.prepare("SELECT * FROM memories WHERE agent_id=? AND edge_type=? ORDER BY created_at DESC LIMIT ?").all(this.agentId, type, limit);
|
|
616
|
+
}
|
|
617
|
+
return this.db.prepare("SELECT * FROM memories WHERE agent_id=? ORDER BY created_at DESC LIMIT ?").all(this.agentId, limit);
|
|
618
|
+
} catch (_) { return []; }
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// ── remove(id) ────────────────────────────────────────────────────────────
|
|
622
|
+
remove(id) {
|
|
623
|
+
try { this.db.prepare("DELETE FROM memories WHERE id=? AND agent_id=?").run(id, this.agentId); } catch (_) {}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// ─── Boot Banner ──────────────────────────────────────────────────────────────
|
|
628
|
+
|
|
629
|
+
function printBanner(memory, bootMs, sovereign = false) {
|
|
630
|
+
const stats = getEmbedderStats();
|
|
631
|
+
const icon = stats.ep.includes('ONNX') ? '🚀' : '⚙️ ';
|
|
632
|
+
console.log('');
|
|
633
|
+
console.log(' ╔══════════════════════════════════════════════════════╗');
|
|
634
|
+
console.log(' ║ VEKTOR SLIPSTREAM — ACTIVE ║');
|
|
635
|
+
console.log(' ╚══════════════════════════════════════════════════════╝');
|
|
636
|
+
console.log('');
|
|
637
|
+
console.log(` ${icon} EP: ${stats.ep}`);
|
|
638
|
+
console.log(` 🧠 Model: ${stats.mode}`);
|
|
639
|
+
console.log(` ⚡ Embed: ${stats.avgMs}ms (post-warmup)`);
|
|
640
|
+
console.log(` 💾 DB: WAL | mmap:1GB | cache:64MB`);
|
|
641
|
+
console.log(` 🔥 Warm: ✓`);
|
|
642
|
+
console.log(` ⏱ Boot: ${bootMs}ms total`);
|
|
643
|
+
console.log('');
|
|
644
|
+
console.log(' [vektor] Sovereign Agent active');
|
|
645
|
+
console.log(' [vektor] Law I \u2713 locality \u2014 no cloud sync path');
|
|
646
|
+
console.log(' [vektor] Law II \u2713 hygiene \u2014 AUDN on every write');
|
|
647
|
+
console.log(' [vektor] Law III \u2713 synthesis \u2014 REM compress-only');
|
|
648
|
+
console.log(' [vektor] Law IV \u2713 permanence \u2014 SQLite persists');
|
|
649
|
+
if (sovereign) {
|
|
650
|
+
console.log(' [vektor] Law VIII sovereign: true \u2014 injection shield active');
|
|
651
|
+
}
|
|
652
|
+
console.log('');
|
|
289
653
|
}
|
|
290
654
|
|
|
291
655
|
// ─── createMemory() — Public API ──────────────────────────────────────────────
|
|
292
656
|
|
|
293
|
-
/**
|
|
294
|
-
* createMemory(options)
|
|
295
|
-
*
|
|
296
|
-
* Initialises the Slipstream memory engine. Parallel API to vektor-core's
|
|
297
|
-
* createMemory() — drop-in replacement, no code changes needed in agent.
|
|
298
|
-
*
|
|
299
|
-
* @param {object} options
|
|
300
|
-
* @param {string} options.agentId - Unique agent identifier
|
|
301
|
-
* @param {string} [options.dbPath] - Path to .db file
|
|
302
|
-
* @param {boolean} [options.silent] - Suppress boot banner
|
|
303
|
-
* @returns {Promise<SlipstreamMemory>}
|
|
304
|
-
*/
|
|
305
657
|
async function createMemory(options = {}) {
|
|
306
658
|
const bootStart = Date.now();
|
|
307
659
|
|
|
308
660
|
const {
|
|
309
|
-
agentId
|
|
310
|
-
dbPath
|
|
311
|
-
silent
|
|
312
|
-
|
|
661
|
+
agentId = 'default',
|
|
662
|
+
dbPath = path.join(process.cwd(), 'slipstream-memory.db'),
|
|
663
|
+
silent = false,
|
|
664
|
+
sovereign = false,
|
|
313
665
|
} = options;
|
|
314
666
|
|
|
315
|
-
//
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
await embedder.init();
|
|
667
|
+
// Ensure parent directory exists
|
|
668
|
+
const dir = path.dirname(path.resolve(dbPath));
|
|
669
|
+
if (!fs.existsSync(dir)) {
|
|
670
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
671
|
+
}
|
|
321
672
|
|
|
322
|
-
//
|
|
323
|
-
const db =
|
|
673
|
+
// Open SQLite
|
|
674
|
+
const db = loadSQLite(dbPath);
|
|
324
675
|
|
|
325
|
-
//
|
|
676
|
+
// Ensure schema
|
|
326
677
|
initSchema(db);
|
|
327
678
|
|
|
679
|
+
// Create memory instance
|
|
680
|
+
const memory = new SlipstreamMemory(db, agentId);
|
|
681
|
+
|
|
682
|
+
// Law VIII — Injection Resistance (opt-in)
|
|
683
|
+
// Wraps memory.remember() with two-tier input screening.
|
|
684
|
+
// Enable with: createMemory({ sovereign: true })
|
|
685
|
+
// Performance cost: ~80ms per write (pattern check free, LLM screen ~80ms).
|
|
686
|
+
if (sovereign) {
|
|
687
|
+
try {
|
|
688
|
+
const sovereignModule = require('./sovereign');
|
|
689
|
+
sovereignModule.wrap(memory);
|
|
690
|
+
memory._sovereign = true;
|
|
691
|
+
} catch (e) {
|
|
692
|
+
console.warn('[vektor] sovereign.js not found — sovereign mode disabled. Copy sovereign.js to the SDK root.');
|
|
693
|
+
memory._sovereign = false;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
328
697
|
const bootMs = Date.now() - bootStart;
|
|
698
|
+
if (!silent) printBanner(memory, bootMs, sovereign);
|
|
329
699
|
|
|
330
|
-
//
|
|
331
|
-
if
|
|
332
|
-
|
|
333
|
-
printBanner(embedder, dbStats, bootMs);
|
|
334
|
-
}
|
|
700
|
+
// Start warming xenova in the background (non-blocking)
|
|
701
|
+
// First recall will be hash-based; subsequent ones will use xenova if available
|
|
702
|
+
embed('warmup').catch(() => {});
|
|
335
703
|
|
|
336
|
-
return
|
|
704
|
+
return memory;
|
|
337
705
|
}
|
|
338
706
|
|
|
339
|
-
module.exports = { createMemory };
|
|
707
|
+
module.exports = { createMemory, SlipstreamMemory, cosineSimilarity, _isContradiction };
|