memtrails 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +11 -0
- package/dist/core/consolidate.d.ts +2 -2
- package/dist/core/consolidate.js +49 -24
- package/dist/core/extract.d.ts +3 -0
- package/dist/core/extract.js +7 -0
- package/dist/core/gather.js +4 -2
- package/dist/core/model.d.ts +2 -1
- package/dist/core/model.js +16 -6
- package/dist/core/profiles.js +5 -3
- package/dist/core/qdrant-binary.d.ts +13 -0
- package/dist/core/qdrant-binary.js +18 -6
- package/dist/core/qdrant-client.d.ts +0 -3
- package/dist/core/qdrant-client.js +0 -12
- package/dist/core/qdrant-process.d.ts +8 -1
- package/dist/core/qdrant-process.js +32 -10
- package/dist/core/rebuild.js +4 -1
- package/dist/core/recall.js +5 -3
- package/dist/core/sleep.js +8 -5
- package/dist/core/text.d.ts +2 -0
- package/dist/core/text.js +2 -1
- package/dist/importers/files.js +43 -5
- package/dist/importers/jsonl.d.ts +6 -0
- package/dist/importers/jsonl.js +10 -5
- package/dist/importers/lines.d.ts +13 -0
- package/dist/importers/lines.js +70 -0
- package/dist/importers/mbox.js +10 -5
- package/dist/llm.d.ts +9 -0
- package/dist/llm.js +42 -2
- package/dist/memory.d.ts +2 -1
- package/dist/memory.js +81 -22
- package/package.json +1 -1
package/dist/memory.js
CHANGED
|
@@ -136,6 +136,44 @@ function saveIndexLayout(dir) {
|
|
|
136
136
|
writeFileSync(`${path}.tmp`, JSON.stringify({ layout: INDEX_LAYOUT }), { mode: 0o600 });
|
|
137
137
|
renameSync(`${path}.tmp`, path);
|
|
138
138
|
}
|
|
139
|
+
/** Turns shared, and a turn alone: many share at once; one alone waits until no one shares, and keeps new sharers out
|
|
140
|
+
* while it waits, so a stream of them never holds it off. */
|
|
141
|
+
function turns() {
|
|
142
|
+
let sharing = 0;
|
|
143
|
+
let alone = false;
|
|
144
|
+
let waiting = 0;
|
|
145
|
+
let wakers = [];
|
|
146
|
+
const wake = () => { const w = wakers; wakers = []; for (const f of w)
|
|
147
|
+
f(); };
|
|
148
|
+
const until = async (ok) => { while (!ok())
|
|
149
|
+
await new Promise((r) => wakers.push(r)); };
|
|
150
|
+
return {
|
|
151
|
+
async shared(job) {
|
|
152
|
+
await until(() => !alone && !waiting);
|
|
153
|
+
sharing++;
|
|
154
|
+
try {
|
|
155
|
+
return await job();
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
sharing--;
|
|
159
|
+
wake();
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
async alone(job) {
|
|
163
|
+
waiting++;
|
|
164
|
+
await until(() => !alone && !sharing);
|
|
165
|
+
waiting--;
|
|
166
|
+
alone = true;
|
|
167
|
+
try {
|
|
168
|
+
return await job();
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
alone = false;
|
|
172
|
+
wake();
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
139
177
|
export async function openMemory(o) {
|
|
140
178
|
const dir = resolve(o.home);
|
|
141
179
|
const log = o.log ?? (() => { });
|
|
@@ -243,6 +281,20 @@ async function openIn(dir, o, log, started, release) {
|
|
|
243
281
|
return run;
|
|
244
282
|
};
|
|
245
283
|
let closed = null;
|
|
284
|
+
/** Every call (add, search, open, …) shares a turn; close() takes it alone, so it lets go of Qdrant, the model and the
|
|
285
|
+
* home only once the calls in flight are done. */
|
|
286
|
+
const calls = turns();
|
|
287
|
+
/** Adds share a turn; a rebuild takes it alone. An add beside a rebuild could write its points into the index about to
|
|
288
|
+
* be dropped and log its record after the rebuild read the log: logged, never indexed, and skipped as unchanged when
|
|
289
|
+
* added again. */
|
|
290
|
+
const adding = turns();
|
|
291
|
+
const check = () => { if (closing)
|
|
292
|
+
throw new Error('memory is closed'); };
|
|
293
|
+
/** A call's shared turn; refused once close() began, also when it waited for its turn meanwhile. */
|
|
294
|
+
const call = async (job) => {
|
|
295
|
+
check();
|
|
296
|
+
return calls.shared(async () => { check(); return job(); });
|
|
297
|
+
};
|
|
246
298
|
/** The text word by word scores an item by: its key, as search returns it. */
|
|
247
299
|
const keyText = (title, summary) => `${title}\n${summary}`;
|
|
248
300
|
/** A found item's whole text for the room: a record's own, or what consolidation wrote. */
|
|
@@ -271,7 +323,8 @@ async function openIn(dir, o, log, started, release) {
|
|
|
271
323
|
const room = `${x.request}\n${x.reply}`.slice(0, ROOM_TEXT);
|
|
272
324
|
return [keyText(stmTitle(x), rawKey(x)), ...sentenceSpans(room).map(([a, b]) => room.slice(a, b))];
|
|
273
325
|
});
|
|
274
|
-
const write =
|
|
326
|
+
const write = (entries, total) => call(() => adding.shared(async () => {
|
|
327
|
+
check(); // it may have waited for a rebuild that a close stopped
|
|
275
328
|
const fresh = entries.filter((x) => stm.meta(x.uid)?.fp !== x.fingerprint);
|
|
276
329
|
for (let i = 0; i < fresh.length; i += BATCH) {
|
|
277
330
|
const part = fresh.slice(i, i + BATCH);
|
|
@@ -281,7 +334,7 @@ async function openIn(dir, o, log, started, release) {
|
|
|
281
334
|
}
|
|
282
335
|
stm.compact();
|
|
283
336
|
return { added: fresh.length, unchanged: total - fresh.length };
|
|
284
|
-
};
|
|
337
|
+
}));
|
|
285
338
|
/** Unlocked: consolidate and sleep run it inside their own turn. */
|
|
286
339
|
const indexWords = async () => {
|
|
287
340
|
if (!encode)
|
|
@@ -351,16 +404,19 @@ async function openIn(dir, o, log, started, release) {
|
|
|
351
404
|
await Promise.all([stm.flush(), ltm.flush()]);
|
|
352
405
|
return uids.length;
|
|
353
406
|
}),
|
|
354
|
-
search: (s) => memorySearch(deps, s),
|
|
355
|
-
searchText: async (
|
|
407
|
+
search: (s) => call(() => memorySearch(deps, s)),
|
|
408
|
+
searchText: (s) => call(async () => searchText(await memorySearch(deps, s), s.order ?? 'relevance')),
|
|
356
409
|
async room(s) {
|
|
410
|
+
check();
|
|
357
411
|
if (!encode)
|
|
358
412
|
throw new Error('the room needs the local embedding model (embedding.modelDir, or no embedding.server)');
|
|
359
|
-
|
|
360
|
-
|
|
413
|
+
return call(async () => {
|
|
414
|
+
const found = await memorySearch(deps, { ...s, limit: s.limit ?? 20 });
|
|
415
|
+
return sentenceRoom(s.query, found.map(roomItem), encode, words, s.chars ?? ROOM_CHARS);
|
|
416
|
+
});
|
|
361
417
|
},
|
|
362
418
|
async open(id) {
|
|
363
|
-
const p = await memoryOpen(deps, id);
|
|
419
|
+
const p = await call(() => memoryOpen(deps, id));
|
|
364
420
|
if (!p)
|
|
365
421
|
return null;
|
|
366
422
|
// A record: its kind as given. Something learned: the records it was learned from, whole.
|
|
@@ -371,8 +427,8 @@ async function openIn(dir, o, log, started, release) {
|
|
|
371
427
|
.map((x) => ({ id: stmItem(x.uid), when: x.ts, title: x.title, body: episodeBody(x), ...(x.record ? { record: x.record } : {}) }));
|
|
372
428
|
return sources.length ? { ...p, sources } : p;
|
|
373
429
|
},
|
|
374
|
-
explore: (s) => memoryExplore(deps, s),
|
|
375
|
-
gather: (s, g) => memoryGather(deps, s, { ask, ...(s.limit ? { limit: s.limit } : {}), ...(g?.notes ? { notes: true } : {}) }),
|
|
430
|
+
explore: (s) => call(() => memoryExplore(deps, s)),
|
|
431
|
+
gather: (s, g) => call(() => memoryGather(deps, s, { ask, ...(s.limit ? { limit: s.limit } : {}), ...(g?.notes ? { notes: true } : {}) })),
|
|
376
432
|
consolidate: () => exclusive(async () => {
|
|
377
433
|
const worker = new Consolidator({ ...base, concurrency: o.concurrency ?? 4 });
|
|
378
434
|
const total = { windows: 0, added: 0, merged: 0, updated: 0, superseded: 0, linked: 0, dropped: 0, episodes: 0, observed: 0, cued: 0 };
|
|
@@ -397,31 +453,34 @@ async function openIn(dir, o, log, started, release) {
|
|
|
397
453
|
return r;
|
|
398
454
|
}),
|
|
399
455
|
indexWords: () => exclusive(indexWords),
|
|
400
|
-
rebuild: () => exclusive(async () => {
|
|
456
|
+
rebuild: () => exclusive(() => adding.alone(async () => {
|
|
401
457
|
rebuild.begin(); // before the drop: a crash, or a close, after it resumes at the next open
|
|
402
458
|
await client.dropCollection();
|
|
403
459
|
await client.ensureCollection();
|
|
404
460
|
await rebuild.run();
|
|
405
|
-
}),
|
|
461
|
+
})),
|
|
406
462
|
tools: () => buildAgentTools({
|
|
407
|
-
search: (s) =>
|
|
463
|
+
search: (s) => memory.search(s),
|
|
408
464
|
open: (id) => memory.open(id),
|
|
409
|
-
explore: (s) =>
|
|
465
|
+
explore: (s) => memory.explore(s),
|
|
410
466
|
}),
|
|
411
467
|
stats: () => ({ records: stm.size(), items: [...ltm.entries()].filter(([, m]) => m.status === 'active').length, words: words.size }),
|
|
412
468
|
close() {
|
|
413
469
|
closed ??= (async () => {
|
|
414
470
|
closing = true;
|
|
415
471
|
await queue; // the job running stops at its next step; those waiting are refused
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
472
|
+
// The adds and searches in flight end first; those waiting for their turn, and every call after, are refused.
|
|
473
|
+
await calls.alone(async () => {
|
|
474
|
+
try {
|
|
475
|
+
await book.settled();
|
|
476
|
+
usage.flush();
|
|
477
|
+
await proc?.stopAndWait();
|
|
478
|
+
await local?.close();
|
|
479
|
+
}
|
|
480
|
+
finally {
|
|
481
|
+
release();
|
|
482
|
+
}
|
|
483
|
+
});
|
|
425
484
|
})();
|
|
426
485
|
return closed;
|
|
427
486
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memtrails",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Retrieval and exploration for any text — e-mail, chat, notes, documents, news, commits, conversations. Searchable at once, on your own machine, in any language; optional consolidation into typed knowledge with a language model.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"retrieval",
|