memtrails 0.3.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 +56 -0
- package/README.md +25 -9
- package/dist/core/consolidate.d.ts +24 -9
- package/dist/core/consolidate.js +101 -49
- package/dist/core/episodes.d.ts +1 -1
- package/dist/core/episodes.js +2 -8
- package/dist/core/extract.d.ts +3 -0
- package/dist/core/extract.js +12 -5
- package/dist/core/gather.d.ts +3 -0
- package/dist/core/gather.js +16 -10
- package/dist/core/model.d.ts +9 -4
- package/dist/core/model.js +39 -40
- package/dist/core/profiles.d.ts +1 -1
- package/dist/core/profiles.js +12 -9
- package/dist/core/qdrant-binary.d.ts +52 -5
- package/dist/core/qdrant-binary.js +197 -48
- package/dist/core/qdrant-client.d.ts +6 -21
- package/dist/core/qdrant-client.js +36 -100
- package/dist/core/qdrant-process.d.ts +30 -2
- package/dist/core/qdrant-process.js +217 -27
- package/dist/core/rebuild.d.ts +5 -1
- package/dist/core/rebuild.js +23 -5
- package/dist/core/recall-log.d.ts +3 -0
- package/dist/core/recall-log.js +6 -1
- package/dist/core/recall.js +19 -8
- package/dist/core/redact.d.ts +8 -0
- package/dist/core/redact.js +48 -9
- package/dist/core/sleep.d.ts +4 -3
- package/dist/core/sleep.js +27 -16
- package/dist/core/store.d.ts +7 -1
- package/dist/core/store.js +101 -33
- package/dist/core/text.d.ts +6 -2
- package/dist/core/text.js +19 -6
- package/dist/core/words.js +6 -2
- package/dist/core/writer.d.ts +2 -1
- package/dist/core/writer.js +9 -3
- package/dist/importers/files.js +96 -15
- package/dist/importers/git.js +3 -1
- 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 +32 -12
- package/dist/importers/slack.js +5 -2
- package/dist/importers/text.js +85 -14
- package/dist/llm.d.ts +10 -8
- package/dist/llm.js +53 -7
- package/dist/memory.d.ts +7 -2
- package/dist/memory.js +284 -57
- package/dist/records.js +6 -3
- package/dist/tools/agent-tools.d.ts +2 -5
- package/dist/tools/agent-tools.js +37 -21
- package/package.json +1 -1
|
@@ -6,8 +6,19 @@
|
|
|
6
6
|
* const tools = memory.tools(); // hand them to an MCP server, or to a model's tool calling
|
|
7
7
|
*/
|
|
8
8
|
import { z } from 'zod';
|
|
9
|
+
import { PROFILE_SHOWN } from '../core/profiles.js';
|
|
10
|
+
import { cut } from '../core/text.js';
|
|
11
|
+
import { dateSpan } from '../core/writer.js';
|
|
9
12
|
function tool(name, description, inputSchema, handler) {
|
|
10
|
-
|
|
13
|
+
const schema = z.object(inputSchema);
|
|
14
|
+
// Checked here too: a caller doing function calling hands the model's arguments straight to the handler.
|
|
15
|
+
const checked = async (args, extra) => {
|
|
16
|
+
const got = schema.safeParse(args ?? {});
|
|
17
|
+
if (!got.success)
|
|
18
|
+
return text(`Invalid arguments: ${got.error.issues.map((i) => `${i.path.join('.') || 'input'}: ${i.message}`).join('; ')}`, true);
|
|
19
|
+
return handler(got.data, extra);
|
|
20
|
+
};
|
|
21
|
+
return { name, description, inputSchema, jsonSchema: z.toJSONSchema(schema), handler: checked };
|
|
11
22
|
}
|
|
12
23
|
const DESCRIPTIONS = {
|
|
13
24
|
memory_search: 'Search memory: everything it was given (e-mails, messages, notes, documents, conversations, commits, …) and '
|
|
@@ -20,12 +31,18 @@ const DESCRIPTIONS = {
|
|
|
20
31
|
+ 'people, places, systems, names — and links between memories, to what a search for the words does not find. Returns '
|
|
21
32
|
+ 'what search found, what is related, and the concepts around it to explore next.',
|
|
22
33
|
};
|
|
34
|
+
/** What a model may send: a question, a topic or an id is a line, not a document; a few dozen collections at most. */
|
|
35
|
+
const TEXT_MAX = 2000;
|
|
36
|
+
const COLLECTIONS_MAX = 50;
|
|
23
37
|
const day = (ts) => (ts ? new Date(ts).toISOString().slice(0, 10) : '');
|
|
24
38
|
const hitLine = (h, n) => `${n + 1}. ${h.title} (${h.kind === 'episode' ? 'earlier work' : h.kind} · ${day(h.ts)}${h.record?.author ? ` · ${h.record.author}` : ''} · id ${h.id})`;
|
|
25
39
|
const WEEKDAY = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
26
40
|
const MONTH = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
27
41
|
/** A date as a reader reckons with it — the weekday too ("the Friday before …"): 2023-07-14 → "Fri 14 Jul 2023". */
|
|
28
42
|
export function readableDate(d) {
|
|
43
|
+
// Not a date (2023-13, 2023-02-30): shown as written rather than as "undefined 2023" or a day that does not exist.
|
|
44
|
+
if (!dateSpan(d))
|
|
45
|
+
return d;
|
|
29
46
|
const [y, m, dd] = d.split('-').map(Number);
|
|
30
47
|
if (!y)
|
|
31
48
|
return '';
|
|
@@ -44,10 +61,6 @@ export function lastedText(h) {
|
|
|
44
61
|
}
|
|
45
62
|
/** When a hit happened, for ordering by time: an observation's own date, else when it was learned. */
|
|
46
63
|
const when = (h) => h.eventAt || day(h.ts);
|
|
47
|
-
/** All of a profile — what memory knows about one subject, one dated fact a line (core/profiles.ts) — up to this:
|
|
48
|
-
* measured, a profile cut like any other item lost most of its gain. */
|
|
49
|
-
export const PROFILE_SHOWN = 1500;
|
|
50
|
-
const cutAt = (s, n) => (s.length > n ? `${s.slice(0, n)}…` : s);
|
|
51
64
|
/** What memory_search shows: an observation as one dated line (when it happened, and when it was told when that
|
|
52
65
|
* differs), a profile whole (up to PROFILE_SHOWN), anything else as its title line and summary — shorter when many
|
|
53
66
|
* are asked for. Best first, or by time. */
|
|
@@ -57,7 +70,7 @@ export function searchText(hits, order = 'relevance') {
|
|
|
57
70
|
const compact = list.length > 20;
|
|
58
71
|
const lines = list.map((h, n) => {
|
|
59
72
|
if (h.kind === 'profile')
|
|
60
|
-
return `${hitLine(h, n)}\n ${
|
|
73
|
+
return `${hitLine(h, n)}\n ${cut(h.summary, PROFILE_SHOWN).replace(/\n/g, '\n ')}`;
|
|
61
74
|
if (h.kind !== 'observation') {
|
|
62
75
|
// The passage that matched, when it was not the start: a fact said deep in a long request is what was found.
|
|
63
76
|
const matched = h.match ? ` … ${h.match.replace(/\s+/g, ' ')}` : '';
|
|
@@ -74,11 +87,12 @@ export function searchText(hits, order = 'relevance') {
|
|
|
74
87
|
});
|
|
75
88
|
return `${order === 'time' ? 'Oldest first — the date is when it happened (and when it was told, when that differs).\n' : ''}${lines.join('\n')}`;
|
|
76
89
|
}
|
|
77
|
-
|
|
90
|
+
/** memory_search, memory_open and memory_explore (when the provider explores) over a memory. */
|
|
91
|
+
export function buildAgentTools(p) {
|
|
78
92
|
return [
|
|
79
93
|
tool('memory_search', DESCRIPTIONS.memory_search, {
|
|
80
|
-
query: z.string().describe('What you need to know, in plain words.'),
|
|
81
|
-
collections: z.array(z.string()).optional().describe('Only these collections (default: all).'),
|
|
94
|
+
query: z.string().max(TEXT_MAX).describe('What you need to know, in plain words.'),
|
|
95
|
+
collections: z.array(z.string().max(TEXT_MAX)).max(COLLECTIONS_MAX).optional().describe('Only these collections (default: all).'),
|
|
82
96
|
limit: z.number().int().min(1).max(200).optional().describe('How many of each to return — dated observations, and other memories (default 20; 100 to 200 when you gather everything on a topic, list or count things, or need their dates).'),
|
|
83
97
|
order: z.enum(['relevance', 'time']).optional().describe('Best first (default), or oldest first — for what happened when, and in what order.'),
|
|
84
98
|
from: z.string().regex(/^\d{4}(-\d{2}(-\d{2})?)?$/).optional().describe('Only what happened on or after this date (YYYY-MM-DD, YYYY-MM or YYYY) — when the request names a time ("in June 2023", "last week").'),
|
|
@@ -94,25 +108,30 @@ function memoryTools(p) {
|
|
|
94
108
|
return text(err.message, true);
|
|
95
109
|
}
|
|
96
110
|
}),
|
|
97
|
-
tool('memory_open', DESCRIPTIONS.memory_open, { id: z.string().describe('The memory id, as memory_search or memory_explore gave it.') }, async (args) => {
|
|
98
|
-
|
|
111
|
+
tool('memory_open', DESCRIPTIONS.memory_open, { id: z.string().max(TEXT_MAX).describe('The memory id, as memory_search or memory_explore gave it.') }, async (args) => {
|
|
112
|
+
// An index that failed is not an id that is missing: the agent should not conclude the memory is gone.
|
|
113
|
+
let it;
|
|
114
|
+
try {
|
|
115
|
+
it = await p.open(args.id);
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
return text(`Memory could not be read: ${err.message}`, true);
|
|
119
|
+
}
|
|
99
120
|
if (!it)
|
|
100
121
|
return text(`There is no memory "${args.id}" (it may have been merged or cleared).`, true);
|
|
101
122
|
const files = Array.isArray(it.files) && it.files.length ? `\nFiles: ${it.files.join(', ')}` : '';
|
|
102
123
|
const told = day(Number(it.ts ?? 0));
|
|
103
124
|
const happened = [typeof it.eventAt === 'string' && it.eventAt ? readableDate(it.eventAt) : '', lastedText(it)].filter(Boolean).join(', ');
|
|
104
125
|
const when = happened ? `happened ${happened}; told ${readableDate(told)}` : told;
|
|
105
|
-
// The raw requests it was learned from, whole (the truth it is a key to)
|
|
126
|
+
// The raw requests it was learned from, whole (the truth it is a key to).
|
|
106
127
|
const sources = Array.isArray(it.sources) && it.sources.length
|
|
107
128
|
? `\n\n## Where it came from\n${it.sources.map((x) => `### ${day(x.when)} · ${x.title} (id ${x.id})\n${x.body}`).join('\n\n')}` : '';
|
|
108
|
-
|
|
109
|
-
? `\n\n## Texts merged into it\n${it.absorbed.map((x) => `### ${x.title}\n${x.summary}\n${x.body}`).join('\n\n')}` : '';
|
|
110
|
-
return text(`# ${String(it.title ?? '')}\n(${String(it.kind ?? '')} · ${when} · ${String(it.project ?? '')})\n\n${String(it.summary ?? '')}\n\n${String(it.body ?? '')}${files}${sources}${absorbed}`);
|
|
129
|
+
return text(`# ${String(it.title ?? '')}\n(${String(it.kind ?? '')} · ${when} · ${String(it.project ?? '')})\n\n${String(it.summary ?? '')}\n\n${String(it.body ?? '')}${files}${sources}`);
|
|
111
130
|
}),
|
|
112
131
|
...(p.explore ? [tool('memory_explore', DESCRIPTIONS.memory_explore, {
|
|
113
|
-
topic: z.string().optional().describe('What to explore, in plain words — a system, a project, a problem, a name.'),
|
|
114
|
-
from: z.array(z.string()).max(5).optional().describe('Memory ids to explore from, as memory_search or memory_explore gave them.'),
|
|
115
|
-
collections: z.array(z.string()).optional().describe('Only these collections (default: all).'),
|
|
132
|
+
topic: z.string().max(TEXT_MAX).optional().describe('What to explore, in plain words — a system, a project, a problem, a name.'),
|
|
133
|
+
from: z.array(z.string().max(TEXT_MAX)).max(5).optional().describe('Memory ids to explore from, as memory_search or memory_explore gave them.'),
|
|
134
|
+
collections: z.array(z.string().max(TEXT_MAX)).max(COLLECTIONS_MAX).optional().describe('Only these collections (default: all).'),
|
|
116
135
|
limit: z.number().int().min(1).max(20).optional().describe('How many related items to return (default 10).'),
|
|
117
136
|
}, async (args) => {
|
|
118
137
|
if (!args.topic?.trim() && !args.from?.length)
|
|
@@ -137,6 +156,3 @@ function memoryTools(p) {
|
|
|
137
156
|
];
|
|
138
157
|
}
|
|
139
158
|
const text = (t, isError = false) => ({ content: [{ type: 'text', text: t }], ...(isError ? { isError: true } : {}) });
|
|
140
|
-
export function buildAgentTools(p) {
|
|
141
|
-
return memoryTools(p);
|
|
142
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memtrails",
|
|
3
|
-
"version": "0.
|
|
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",
|